fix: security hardening, Prisma singleton, tsconfig cleanup
- tsconfig: remove deprecated downlevelIteration (es2017 has native iterators), change moduleResolution from node to bundler - auth: set allowDangerousEmailAccountLinking=false on all OAuth providers, restrict debug mode to development only - prisma: replace new PrismaClient() with singleton import in 10 API routes to prevent connection pool exhaustion - goal API: validate user-supplied quote URLs to block SSRF (private IPs, non-http(s) schemes) - email: guard DEV verification/reset link console.logs behind NODE_ENV check v1.98.0
This commit is contained in:
parent
aa4c6a085d
commit
3e19ebe85a
27
clear-data.ts
Normal file
27
clear-data.ts
Normal file
@ -0,0 +1,27 @@
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
async function main() {
|
||||
const user = await prisma.user.findUnique({
|
||||
where: { email: 'martin.bierschenk@gmail.com' }
|
||||
});
|
||||
|
||||
if (!user) return;
|
||||
|
||||
// Delete all tasks in the target range created today
|
||||
const res = await prisma.task.deleteMany({
|
||||
where: {
|
||||
userId: user.id,
|
||||
scheduledDate: {
|
||||
gte: new Date('2026-03-15T00:00:00Z'),
|
||||
lte: new Date('2026-04-15T00:00:00Z')
|
||||
},
|
||||
createdAt: {
|
||||
gte: new Date(new Date().setHours(0,0,0,0))
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
console.log(`Deleted ${res.count} tasks from martin`);
|
||||
}
|
||||
main().finally(() => prisma.$disconnect());
|
||||
214
fill-data-v2.ts
Normal file
214
fill-data-v2.ts
Normal file
@ -0,0 +1,214 @@
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
const dailyTasks = [
|
||||
"Pretend to listen to partner's work drama",
|
||||
"Feed the kids (again? really?)",
|
||||
"Hide Amazon packages from spouse",
|
||||
"Attempt to fold a fitted sheet, give up and roll it into a ball",
|
||||
"Cook a meal that exactly 0% of the family will appreciate",
|
||||
"Look at the mess. Sigh. Walk away.",
|
||||
"Move the laundry from washer to dryer, leave it there for 3 days",
|
||||
"Find out what died in the fridge",
|
||||
"Pay bills and cry softly",
|
||||
"Unload dishwasher with immense resentment",
|
||||
"Scroll on phone until legs go numb on the toilet",
|
||||
"Perform archeological dig in the teenage bedroom",
|
||||
"Interrogate child about missing tupperware lid",
|
||||
"Nod enthusiastically at toddler's incomprehensible story",
|
||||
"Try to decipher spouse's 'helpful' grocery list",
|
||||
"Pretend I don't see the full trash can",
|
||||
"Vacuum the rug and ignore the corners",
|
||||
"Wonder where all my money went"
|
||||
];
|
||||
|
||||
const workEvents = [
|
||||
"Meaningless sync meeting #42",
|
||||
"Stare blankly at spreadsheet",
|
||||
"Listen to boss talk about synergy",
|
||||
"Reply 'As per my previous email...' to Gary",
|
||||
"Pretend to be busy so no one asks me for help",
|
||||
"'Quick chat' that ruins my entire afternoon",
|
||||
"Update Jira tickets to make it look like I did something",
|
||||
"Mute mic and eat aggressively loud chips during all-hands",
|
||||
"Consider moving to the woods and becoming a hermit",
|
||||
"Frantically search for the tab that is playing music",
|
||||
"Draft angry email, delete it, send 'Sounds good!'",
|
||||
"Nod meaningfully during presentation I don't understand"
|
||||
];
|
||||
|
||||
const birthdayTasks = [
|
||||
"Buy a gift that makes me look thoughtful but was actually on sale",
|
||||
"Wrap the present (using newspaper because I forgot wrapping paper)",
|
||||
"Attend birthday party and strategically position myself near the snack table",
|
||||
"Fake a smile while listening to Uncle Bob's views",
|
||||
"Smuggle leftover cake home in napkins"
|
||||
];
|
||||
|
||||
async function main() {
|
||||
const user = await prisma.user.findUnique({
|
||||
where: { email: 'kugelblitz@gmx.de' }
|
||||
});
|
||||
|
||||
if (!user) return;
|
||||
|
||||
// 1. Delete all recently generated tasks
|
||||
await prisma.task.deleteMany({
|
||||
where: {
|
||||
userId: user.id,
|
||||
createdAt: { gte: new Date(new Date().setHours(0,0,0,0)) },
|
||||
title: { not: 'Meeting with team' } // leave the one subagent added if you want, or just let it delete if the title is strictly dailyTasks. Actually just delete all created today!
|
||||
}
|
||||
});
|
||||
|
||||
// 2. Create Projects for beautiful colors
|
||||
let pWork = await prisma.project.findFirst({ where: { userId: user.id, name: 'Work' } });
|
||||
if (!pWork) pWork = await prisma.project.create({ data: { userId: user.id, name: 'Work', color: '#3b82f6', icon: 'faBriefcase' } });
|
||||
|
||||
let pFamily = await prisma.project.findFirst({ where: { userId: user.id, name: 'Family' } });
|
||||
if (!pFamily) pFamily = await prisma.project.create({ data: { userId: user.id, name: 'Family', color: '#10b981', icon: 'faHouse' } });
|
||||
|
||||
let pLife = await prisma.project.findFirst({ where: { userId: user.id, name: 'Life' } });
|
||||
if (!pLife) pLife = await prisma.project.create({ data: { userId: user.id, name: 'Life', color: '#f59e0b', icon: 'faHeart' } });
|
||||
|
||||
// 3. Find target Someday lists to populate the bottom
|
||||
const lists = await prisma.somedayList.findMany({ where: { userId: user.id } });
|
||||
const kugelList = lists.find(l => l.title.includes('kugel'));
|
||||
const papaList = lists.find(l => l.title.includes('Papa'));
|
||||
|
||||
// Target dates: March 16 to April 12, 2026
|
||||
const startDate = new Date('2026-03-16T00:00:00Z');
|
||||
const endDate = new Date('2026-04-12T00:00:00Z');
|
||||
const birthdayDate = new Date('2026-04-04T00:00:00Z');
|
||||
const aprilFools = new Date('2026-04-01T00:00:00Z');
|
||||
|
||||
for (let d = new Date(startDate); d <= endDate; d.setDate(d.getDate() + 1)) {
|
||||
const isBirthday = d.getTime() === birthdayDate.getTime();
|
||||
const isAprilFools = d.getTime() === aprilFools.getTime();
|
||||
const dateStr = d.toISOString().split('T')[0];
|
||||
const isWeekend = d.getDay() === 0 || d.getDay() === 6;
|
||||
|
||||
// We want a DENSE calendar for the tutorial. 50% fill rate for EACH SLOT.
|
||||
// Daily Life events (scheduled on grid)
|
||||
const numLife = Math.floor(Math.random() * 2) + 1;
|
||||
for (let i = 0; i < numLife; i++) {
|
||||
const h = Math.floor(Math.random() * 3) + 6; // 6-8 AM or evening
|
||||
await prisma.task.create({
|
||||
data: {
|
||||
title: dailyTasks[Math.floor(Math.random() * dailyTasks.length)],
|
||||
userId: user.id,
|
||||
scheduledDate: new Date(dateStr),
|
||||
dayOfWeek: d.getDay(),
|
||||
startTime: `${h.toString().padStart(2, '0')}:00`,
|
||||
endTime: `${(h + 1).toString().padStart(2, '0')}:00`,
|
||||
projectId: pLife.id
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (!isWeekend) {
|
||||
// Work events
|
||||
let currentHour = 8;
|
||||
while (currentHour <= 16) {
|
||||
if (Math.random() > 0.4) { // 60% chance to put a work meeting in this block
|
||||
const duration = Math.random() > 0.5 ? 1 : 2;
|
||||
await prisma.task.create({
|
||||
data: {
|
||||
title: workEvents[Math.floor(Math.random() * workEvents.length)],
|
||||
userId: user.id,
|
||||
scheduledDate: new Date(dateStr),
|
||||
dayOfWeek: d.getDay(),
|
||||
startTime: `${currentHour.toString().padStart(2, '0')}:00`,
|
||||
endTime: `${(currentHour + duration).toString().padStart(2, '0')}:00`,
|
||||
projectId: pWork.id
|
||||
}
|
||||
});
|
||||
currentHour += duration + 1; // leave at least 1h gap
|
||||
} else {
|
||||
currentHour += 1;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Weekend Family events
|
||||
const numFam = Math.floor(Math.random() * 3) + 2;
|
||||
let famHour = 9;
|
||||
for (let i = 0; i < numFam; i++) {
|
||||
await prisma.task.create({
|
||||
data: {
|
||||
title: dailyTasks[Math.floor(Math.random() * dailyTasks.length)],
|
||||
userId: user.id,
|
||||
scheduledDate: new Date(dateStr),
|
||||
dayOfWeek: d.getDay(),
|
||||
startTime: `${famHour.toString().padStart(2, '0')}:00`,
|
||||
endTime: `${(famHour + 1).toString().padStart(2, '0')}:00`,
|
||||
projectId: pFamily.id
|
||||
}
|
||||
});
|
||||
famHour += 2;
|
||||
}
|
||||
}
|
||||
|
||||
// Add April Fools extra
|
||||
if (isAprilFools) {
|
||||
await prisma.task.create({
|
||||
data: {
|
||||
title: "Attempt a prank, fail miserably, apologize to HR",
|
||||
userId: user.id,
|
||||
scheduledDate: new Date(dateStr),
|
||||
dayOfWeek: d.getDay(),
|
||||
startTime: "10:30",
|
||||
endTime: "11:30",
|
||||
projectId: pWork.id
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Add Birthday extras
|
||||
if (isBirthday) {
|
||||
for (let i = 0; i < birthdayTasks.length; i++) {
|
||||
const time = 14 + i;
|
||||
await prisma.task.create({
|
||||
data: {
|
||||
title: birthdayTasks[i],
|
||||
userId: user.id,
|
||||
scheduledDate: new Date(dateStr),
|
||||
dayOfWeek: d.getDay(),
|
||||
startTime: `${time}:00`,
|
||||
endTime: `${time + 1}:00`,
|
||||
projectId: pFamily.id
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Populate SomeDay Lists
|
||||
if (kugelList) {
|
||||
for(let i=0; i<3; i++) {
|
||||
await prisma.task.create({
|
||||
data: {
|
||||
title: dailyTasks[Math.floor(Math.random() * dailyTasks.length)],
|
||||
userId: user.id,
|
||||
somedayListId: kugelList.id,
|
||||
order: i
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
if (papaList) {
|
||||
for(let i=0; i<3; i++) {
|
||||
await prisma.task.create({
|
||||
data: {
|
||||
title: workEvents[Math.floor(Math.random() * workEvents.length)],
|
||||
userId: user.id,
|
||||
somedayListId: papaList.id,
|
||||
order: i
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
console.log("Densley populated the calendar with wonderful colored events!");
|
||||
}
|
||||
|
||||
main().catch(console.error).finally(() => prisma.$disconnect());
|
||||
153
fill-data.ts
Normal file
153
fill-data.ts
Normal file
@ -0,0 +1,153 @@
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
const dailyTasks = [
|
||||
"Pretend to listen to partner's work drama",
|
||||
"Feed the kids (again? really?)",
|
||||
"Hide Amazon packages from spouse",
|
||||
"Attempt to fold a fitted sheet, give up and roll it into a ball",
|
||||
"Cook a meal that exactly 0% of the family will appreciate",
|
||||
"Look at the mess. Sigh. Walk away.",
|
||||
"Move the laundry from washer to dryer, leave it there for 3 days",
|
||||
"Find out what died in the fridge",
|
||||
"Pay bills and cry softly",
|
||||
"Unload dishwasher with immense resentment",
|
||||
"Scroll on phone until legs go numb on the toilet",
|
||||
"Perform archeological dig in the teenage bedroom",
|
||||
"Interrogate child about missing tupperware lid",
|
||||
"Nod enthusiastically at toddler's incomprehensible story",
|
||||
"Try to decipher spouse's 'helpful' grocery list",
|
||||
"Pretend I don't see the full trash can"
|
||||
];
|
||||
|
||||
const workEvents = [
|
||||
"Meaningless sync meeting #42",
|
||||
"Stare blankly at spreadsheet",
|
||||
"Listen to boss talk about synergy",
|
||||
"Reply 'As per my previous email...' to Gary",
|
||||
"Pretend to be busy so no one asks me for help",
|
||||
"'Quick chat' that ruins my entire afternoon",
|
||||
"Update Jira tickets to make it look like I did something",
|
||||
"Mute mic and eat aggressively loud chips during all-hands",
|
||||
"Consider moving to the woods and becoming a hermit",
|
||||
"Frantically search for the tab that is playing music"
|
||||
];
|
||||
|
||||
const birthdayTasks = [
|
||||
"Buy a gift that makes me look thoughtful but was actually on sale",
|
||||
"Wrap the present (using newspaper because I forgot wrapping paper)",
|
||||
"Attend birthday party and strategically position myself near the snack table",
|
||||
"Fake a smile while listening to Uncle Bob's views",
|
||||
"Smuggle leftover cake home in napkins"
|
||||
];
|
||||
|
||||
async function main() {
|
||||
const user = await prisma.user.findUnique({
|
||||
where: { email: 'kugelblitz@gmx.de' }
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
console.log("User kugelblitz@gmx.de not found in database.");
|
||||
return;
|
||||
}
|
||||
console.log(`Using user: ${user.email} (${user.id})`);
|
||||
|
||||
// Target dates: March 16 to April 12, 2026
|
||||
const startDate = new Date('2026-03-16T00:00:00Z');
|
||||
const endDate = new Date('2026-04-12T00:00:00Z');
|
||||
|
||||
// Birthday party around April 4th
|
||||
const birthdayDate = new Date('2026-04-04T00:00:00Z');
|
||||
|
||||
// April Fools on April 1st
|
||||
const aprilFools = new Date('2026-04-01T00:00:00Z');
|
||||
|
||||
for (let d = new Date(startDate); d <= endDate; d.setDate(d.getDate() + 1)) {
|
||||
// ~40% fill rate for normal days
|
||||
const isBirthday = d.getTime() === birthdayDate.getTime();
|
||||
const isAprilFools = d.getTime() === aprilFools.getTime();
|
||||
|
||||
// Skip ~60% of the regular days to keep density 30-50%
|
||||
if (!isBirthday && !isAprilFools && Math.random() > 0.45) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const dateStr = d.toISOString().split('T')[0];
|
||||
const isWeekend = d.getDay() === 0 || d.getDay() === 6;
|
||||
|
||||
// Add 1-2 daily tasks (without specific time)
|
||||
const numTasks = Math.floor(Math.random() * 2) + 1;
|
||||
for (let i = 0; i < numTasks; i++) {
|
||||
const taskName = dailyTasks[Math.floor(Math.random() * dailyTasks.length)];
|
||||
await prisma.task.create({
|
||||
data: {
|
||||
title: taskName,
|
||||
userId: user.id,
|
||||
scheduledDate: new Date(dateStr),
|
||||
dayOfWeek: d.getDay(),
|
||||
order: i,
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Add 1-2 work events (with time) if it's a weekday
|
||||
if (!isWeekend) {
|
||||
if (Math.random() > 0.3) {
|
||||
const numEvents = Math.floor(Math.random() * 2) + 1;
|
||||
let currentHour = Math.floor(Math.random() * (11 - 8 + 1)) + 8; // Morning 8-11
|
||||
|
||||
for (let e = 0; e < numEvents; e++) {
|
||||
const eventName = workEvents[Math.floor(Math.random() * workEvents.length)];
|
||||
const startTime = `${currentHour.toString().padStart(2, '0')}:00`;
|
||||
await prisma.task.create({
|
||||
data: {
|
||||
title: eventName,
|
||||
userId: user.id,
|
||||
scheduledDate: new Date(dateStr),
|
||||
dayOfWeek: d.getDay(),
|
||||
startTime: startTime,
|
||||
endTime: `${(currentHour+1).toString().padStart(2, '0')}:00`,
|
||||
}
|
||||
});
|
||||
currentHour += Math.floor(Math.random() * 3) + 2; // Jump 2-4 hours for next event
|
||||
if (currentHour > 17) break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add April Fools extra
|
||||
if (isAprilFools) {
|
||||
await prisma.task.create({
|
||||
data: {
|
||||
title: "Attempt a prank, fail miserably, apologize to HR",
|
||||
userId: user.id,
|
||||
scheduledDate: new Date(dateStr),
|
||||
dayOfWeek: d.getDay(),
|
||||
startTime: "10:30",
|
||||
endTime: "11:00",
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Add Birthday extras
|
||||
if (isBirthday) {
|
||||
for (let i = 0; i < birthdayTasks.length; i++) {
|
||||
const time = i >= 2 ? `${14 + i}:00` : undefined; // Party elements have times 16:00, 17:00, 18:00
|
||||
await prisma.task.create({
|
||||
data: {
|
||||
title: birthdayTasks[i],
|
||||
userId: user.id,
|
||||
scheduledDate: new Date(dateStr),
|
||||
dayOfWeek: d.getDay(),
|
||||
startTime: time,
|
||||
endTime: time ? `${15 + i}:00` : undefined,
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log("Successfully seeded the calendar with fun, sarcastic data!");
|
||||
}
|
||||
|
||||
main().catch(console.error).finally(() => prisma.$disconnect());
|
||||
118
fill-ext-events.js
Normal file
118
fill-ext-events.js
Normal file
@ -0,0 +1,118 @@
|
||||
const { PrismaClient } = require('@prisma/client');
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
const eventNames = [
|
||||
"Dentist Appointment",
|
||||
"Coffee with Sarah",
|
||||
"Quarterly Review",
|
||||
"Car Inspection",
|
||||
"Therapy Session",
|
||||
"Lunch with Bob"
|
||||
];
|
||||
|
||||
const allDayEvents = [
|
||||
"Spring Festival",
|
||||
"Bank Holiday",
|
||||
"Company Offsite",
|
||||
"Project Deadline"
|
||||
];
|
||||
|
||||
function getWeekStart(date) {
|
||||
const d = new Date(date);
|
||||
d.setUTCHours(0, 0, 0, 0);
|
||||
const day = d.getUTCDay();
|
||||
const diff = d.getUTCDate() - day + (day === 0 ? -6 : 1); // Monday is 1
|
||||
d.setUTCDate(diff);
|
||||
return d;
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const user = await prisma.user.findUnique({
|
||||
where: { email: 'kugelblitz@gmx.de' }
|
||||
});
|
||||
|
||||
if (!user) return;
|
||||
|
||||
// 1. Ensure a dummy calendar connection exists
|
||||
let connection = await prisma.calendarConnection.findFirst({
|
||||
where: { userId: user.id, provider: 'google' }
|
||||
});
|
||||
if (!connection) {
|
||||
connection = await prisma.calendarConnection.create({
|
||||
data: {
|
||||
userId: user.id,
|
||||
provider: 'google',
|
||||
accessToken: 'dummy-token',
|
||||
calendars: JSON.stringify([{ id: 'primary', name: 'My Calendar', color: '#4285F4' }])
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 2. Clear old generated mock events
|
||||
const mockTitles = [...eventNames, ...allDayEvents];
|
||||
await prisma.cachedCalendarEvent.deleteMany({
|
||||
where: {
|
||||
connectionId: connection.id,
|
||||
title: { in: mockTitles }
|
||||
}
|
||||
});
|
||||
|
||||
const startDate = new Date('2026-03-16T00:00:00Z');
|
||||
const endDate = new Date('2026-04-12T00:00:00Z');
|
||||
|
||||
let idCounter = 1;
|
||||
|
||||
for (let d = new Date(startDate); d <= endDate; d.setUTCDate(d.getUTCDate() + 1)) {
|
||||
console.log('Processing date:', d.toISOString());
|
||||
const dateStr = d.toISOString().split('T')[0];
|
||||
const weekStart = getWeekStart(d);
|
||||
|
||||
if (Math.random() > 0.6) {
|
||||
// Add All-Day Event
|
||||
await prisma.cachedCalendarEvent.create({
|
||||
data: {
|
||||
userId: user.id,
|
||||
externalId: `mock-allday-${idCounter++}`,
|
||||
connectionId: connection.id,
|
||||
provider: 'google',
|
||||
calendarId: 'primary',
|
||||
calendarTitle: 'My Calendar',
|
||||
calendarColor: '#EA4335',
|
||||
title: allDayEvents[Math.floor(Math.random() * allDayEvents.length)],
|
||||
startDate: dateStr,
|
||||
endDate: dateStr,
|
||||
weekStart: weekStart
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (Math.random() > 0.5) {
|
||||
// Add Timed Event
|
||||
const hour = Math.floor(Math.random() * 6) + 10; // 10 to 15
|
||||
const startDt = new Date(d);
|
||||
startDt.setUTCHours(hour, 0, 0, 0);
|
||||
const endDt = new Date(d);
|
||||
endDt.setUTCHours(hour + 1, 0, 0, 0);
|
||||
|
||||
await prisma.cachedCalendarEvent.create({
|
||||
data: {
|
||||
userId: user.id,
|
||||
externalId: `mock-timed-${idCounter++}`,
|
||||
connectionId: connection.id,
|
||||
provider: 'google',
|
||||
calendarId: 'primary',
|
||||
calendarTitle: 'My Calendar',
|
||||
calendarColor: '#4285F4',
|
||||
title: eventNames[Math.floor(Math.random() * eventNames.length)],
|
||||
startDateTime: startDt,
|
||||
endDateTime: endDt,
|
||||
weekStart: weekStart
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
console.log("Successfully added simulated external calendar events!");
|
||||
}
|
||||
|
||||
main().catch(console.error).finally(() => prisma.$disconnect());
|
||||
116
fill-ext-events.ts
Normal file
116
fill-ext-events.ts
Normal file
@ -0,0 +1,116 @@
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
const eventNames = [
|
||||
"Dentist Appointment",
|
||||
"Coffee with Sarah",
|
||||
"Quarterly Review",
|
||||
"Car Inspection",
|
||||
"Therapy Session",
|
||||
"Lunch with Bob"
|
||||
];
|
||||
|
||||
const allDayEvents = [
|
||||
"Spring Festival",
|
||||
"Bank Holiday",
|
||||
"Company Offsite",
|
||||
"Project Deadline"
|
||||
];
|
||||
|
||||
function getWeekStart(date: Date): Date {
|
||||
const d = new Date(date);
|
||||
d.setHours(0, 0, 0, 0);
|
||||
const day = d.getDay();
|
||||
const diff = d.getDate() - day + (day === 0 ? -6 : 1); // Monday is 1
|
||||
return new Date(d.setDate(diff));
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const user = await prisma.user.findUnique({
|
||||
where: { email: 'kugelblitz@gmx.de' }
|
||||
});
|
||||
|
||||
if (!user) return;
|
||||
|
||||
// 1. Ensure a dummy calendar connection exists
|
||||
let connection = await prisma.calendarConnection.findFirst({
|
||||
where: { userId: user.id, provider: 'google' }
|
||||
});
|
||||
if (!connection) {
|
||||
connection = await prisma.calendarConnection.create({
|
||||
data: {
|
||||
userId: user.id,
|
||||
provider: 'google',
|
||||
accessToken: 'dummy-token',
|
||||
calendars: JSON.stringify([{ id: 'primary', name: 'My Calendar', color: '#4285F4' }])
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 2. Clear old generated mock events
|
||||
const mockTitles = [...eventNames, ...allDayEvents];
|
||||
await prisma.cachedCalendarEvent.deleteMany({
|
||||
where: {
|
||||
connectionId: connection.id,
|
||||
title: { in: mockTitles }
|
||||
}
|
||||
});
|
||||
|
||||
const startDate = new Date('2026-03-16T00:00:00Z');
|
||||
const endDate = new Date('2026-04-12T00:00:00Z');
|
||||
|
||||
let idCounter = 1;
|
||||
|
||||
for (let d = new Date(startDate); d <= endDate; d.setDate(d.getDate() + 1)) {
|
||||
const dateStr = d.toISOString().split('T')[0];
|
||||
const weekStart = getWeekStart(d);
|
||||
|
||||
if (Math.random() > 0.6) {
|
||||
// Add All-Day Event
|
||||
await prisma.cachedCalendarEvent.create({
|
||||
data: {
|
||||
userId: user.id,
|
||||
externalId: `mock-allday-${idCounter++}`,
|
||||
connectionId: connection.id,
|
||||
provider: 'google',
|
||||
calendarId: 'primary',
|
||||
calendarTitle: 'My Calendar',
|
||||
calendarColor: '#primary', // Or any string, frontend handles #
|
||||
title: allDayEvents[Math.floor(Math.random() * allDayEvents.length)],
|
||||
startDate: dateStr,
|
||||
endDate: dateStr,
|
||||
weekStart: weekStart
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (Math.random() > 0.5) {
|
||||
// Add Timed Event
|
||||
const hour = Math.floor(Math.random() * 6) + 10; // 10 to 15
|
||||
const startDt = new Date(d);
|
||||
startDt.setUTCHours(hour, 0, 0, 0);
|
||||
const endDt = new Date(d);
|
||||
endDt.setUTCHours(hour + 1, 0, 0, 0);
|
||||
|
||||
await prisma.cachedCalendarEvent.create({
|
||||
data: {
|
||||
userId: user.id,
|
||||
externalId: `mock-timed-${idCounter++}`,
|
||||
connectionId: connection.id,
|
||||
provider: 'google',
|
||||
calendarId: 'primary',
|
||||
calendarTitle: 'My Calendar',
|
||||
calendarColor: '#EA4335',
|
||||
title: eventNames[Math.floor(Math.random() * eventNames.length)],
|
||||
startDateTime: startDt,
|
||||
endDateTime: endDt,
|
||||
weekStart: weekStart
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
console.log("Successfully added simulated external calendar events!");
|
||||
}
|
||||
|
||||
main().catch(console.error).finally(() => prisma.$disconnect());
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "my-weekly-todo-list",
|
||||
"version": "1.97.4",
|
||||
"version": "1.98.0",
|
||||
"description": "A web-based weekly task management application that organizes to-dos and calendar events in a single, intuitive weekly view",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
|
||||
@ -1,11 +1,9 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
export const dynamic = 'force-dynamic';
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
import { prisma } from '@/lib/prisma';
|
||||
import { hash } from 'bcryptjs';
|
||||
import { sendVerificationEmail, generateVerificationCode, generateVerificationToken } from '@/lib/email';
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
const { name, email, password } = await request.json();
|
||||
|
||||
@ -3,9 +3,7 @@ import { validateCredentials } from '@/lib/apple-calendar';
|
||||
import { CalendarConnection } from '@/lib/calendar-sync';
|
||||
import { getServerSession } from 'next-auth';
|
||||
import { authOptions } from "@/lib/auth";
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
import { prisma } from '@/lib/prisma';
|
||||
|
||||
export async function POST(req: Request) {
|
||||
try {
|
||||
|
||||
@ -2,12 +2,10 @@ import { NextRequest, NextResponse } from 'next/server';
|
||||
import { getServerSession } from 'next-auth';
|
||||
import { authOptions } from "@/lib/auth";
|
||||
import { google } from 'googleapis';
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
import { prisma } from '@/lib/prisma';
|
||||
|
||||
export const dynamic = 'force-dynamic';
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
// Google Calendar OAuth callback endpoint
|
||||
export async function GET(request: NextRequest) {
|
||||
try {
|
||||
|
||||
@ -1,13 +1,11 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { getServerSession } from 'next-auth';
|
||||
import { authOptions } from "@/lib/auth";
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
import { prisma } from '@/lib/prisma';
|
||||
import { getUserDatabases } from '@/lib/notion-calendar';
|
||||
|
||||
export const dynamic = 'force-dynamic';
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
try {
|
||||
const session = await getServerSession(authOptions);
|
||||
|
||||
@ -2,9 +2,7 @@ import { NextResponse } from 'next/server';
|
||||
import { validateCredentials } from '@/lib/synology-calendar';
|
||||
import { getServerSession } from 'next-auth';
|
||||
import { authOptions } from "@/lib/auth";
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
import { prisma } from '@/lib/prisma';
|
||||
|
||||
export async function POST(req: Request) {
|
||||
try {
|
||||
|
||||
@ -127,7 +127,16 @@ export async function GET(req: Request) {
|
||||
};
|
||||
|
||||
// Try each user-configured URL first
|
||||
const userUrls: string[] = (user as any)?.quoteSourceUrls?.filter(Boolean) || [];
|
||||
const PRIVATE_IP = /^(localhost|127\.|0\.0\.0\.|10\.|192\.168\.|172\.(1[6-9]|2\d|3[01])\.|169\.254\.|::1$|fc00:|fe80:)/i;
|
||||
function isSafeQuoteUrl(raw: string): boolean {
|
||||
try {
|
||||
const u = new URL(raw);
|
||||
if (!['http:', 'https:'].includes(u.protocol)) return false;
|
||||
if (PRIVATE_IP.test(u.hostname)) return false;
|
||||
return true;
|
||||
} catch { return false; }
|
||||
}
|
||||
const userUrls: string[] = ((user as any)?.quoteSourceUrls?.filter(Boolean) || []).filter(isSafeQuoteUrl);
|
||||
for (const url of userUrls) {
|
||||
try {
|
||||
const res = await fetch(url, { signal: AbortSignal.timeout(4000) });
|
||||
|
||||
4
src/app/api/someday-lists/external/route.ts
vendored
4
src/app/api/someday-lists/external/route.ts
vendored
@ -2,15 +2,13 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { getServerSession } from 'next-auth';
|
||||
import { authOptions } from "@/lib/auth";
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
import { prisma } from '@/lib/prisma';
|
||||
import { createGoogleClient, createGoogleTaskList } from '@/lib/google-tasks';
|
||||
import { createMsTodoList } from '@/lib/microsoft-todo';
|
||||
import { getOutlookAccessToken } from '@/lib/outlook-token';
|
||||
import { createAppleReminderList } from '@/lib/apple-reminders';
|
||||
import { createSynologyReminderList } from '@/lib/synology-tasks';
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
export async function POST(req: NextRequest) {
|
||||
try {
|
||||
const session = await getServerSession(authOptions);
|
||||
|
||||
@ -1,11 +1,9 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { getServerSession } from 'next-auth';
|
||||
import { authOptions } from "@/lib/auth";
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
import { prisma } from '@/lib/prisma';
|
||||
import { notifyUser } from '@/lib/sse';
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
async function resolveUserId(email: string): Promise<string | null> {
|
||||
const user = await prisma.user.findUnique({
|
||||
where: { email },
|
||||
|
||||
@ -2,13 +2,11 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { getServerSession } from 'next-auth';
|
||||
import { authOptions } from "@/lib/auth";
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
import { prisma } from '@/lib/prisma';
|
||||
import { createGoogleClient, fetchGoogleTasks, fetchGoogleTaskLists } from '@/lib/google-tasks';
|
||||
import { fetchMsTodoLists, fetchMsTodoTasks, isMsTodoTaskCompleted, fetchMsChecklistItems } from '@/lib/microsoft-todo';
|
||||
import { getOutlookAccessToken } from '@/lib/outlook-token';
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
interface SourceList {
|
||||
id: string;
|
||||
title: string;
|
||||
|
||||
@ -1,15 +1,13 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { getServerSession } from 'next-auth';
|
||||
import { authOptions } from "@/lib/auth";
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
import { prisma } from '@/lib/prisma';
|
||||
import { createGoogleClient, fetchGoogleTaskLists } from '@/lib/google-tasks';
|
||||
import { fetchMsTodoLists } from '@/lib/microsoft-todo';
|
||||
import { getOutlookAccessToken } from '@/lib/outlook-token';
|
||||
|
||||
export const dynamic = 'force-dynamic';
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
export async function GET(req: NextRequest) {
|
||||
try {
|
||||
const session = await getServerSession(authOptions);
|
||||
|
||||
@ -1,11 +1,10 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { getServerSession } from 'next-auth';
|
||||
import { authOptions } from "@/lib/auth";
|
||||
import { PrismaClient, Task } from '@prisma/client';
|
||||
import { Task } from '@prisma/client';
|
||||
import { prisma } from '@/lib/prisma';
|
||||
import { notifyUser } from '@/lib/sse';
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
// Validate and sanitize a URL — only allow http/https, reject javascript: and data: schemes
|
||||
function sanitizeUrl(raw: string | null | undefined): string | null {
|
||||
if (!raw) return null;
|
||||
|
||||
@ -13,7 +13,7 @@ export const authOptions: NextAuthOptions = {
|
||||
GoogleProvider({
|
||||
clientId: process.env.GOOGLE_CLIENT_ID || "",
|
||||
clientSecret: process.env.GOOGLE_CLIENT_SECRET || "",
|
||||
allowDangerousEmailAccountLinking: true,
|
||||
allowDangerousEmailAccountLinking: false,
|
||||
authorization: {
|
||||
params: {
|
||||
prompt: "consent",
|
||||
@ -26,13 +26,13 @@ export const authOptions: NextAuthOptions = {
|
||||
AppleProvider({
|
||||
clientId: process.env.APPLE_ID || "",
|
||||
clientSecret: process.env.APPLE_SECRET || "",
|
||||
allowDangerousEmailAccountLinking: true,
|
||||
allowDangerousEmailAccountLinking: false,
|
||||
}),
|
||||
AzureADProvider({
|
||||
clientId: process.env.MICROSOFT_CLIENT_ID || "",
|
||||
clientSecret: process.env.MICROSOFT_CLIENT_SECRET || "",
|
||||
tenantId: "common",
|
||||
allowDangerousEmailAccountLinking: true,
|
||||
allowDangerousEmailAccountLinking: false,
|
||||
authorization: {
|
||||
params: {
|
||||
prompt: "consent",
|
||||
@ -85,7 +85,7 @@ export const authOptions: NextAuthOptions = {
|
||||
}
|
||||
})
|
||||
],
|
||||
debug: true,
|
||||
debug: process.env.NODE_ENV === 'development',
|
||||
session: {
|
||||
strategy: "jwt",
|
||||
maxAge: 30 * 24 * 60 * 60, // 30 days
|
||||
|
||||
@ -108,9 +108,11 @@ export async function sendVerificationEmail(
|
||||
|
||||
const from = process.env.SMTP_FROM || '"My Weekly ToDo\'s" <mail@carrylight.de>';
|
||||
console.log(`[EMAIL] Sending verification email to ${email} from ${from}`);
|
||||
console.log(`\n======================================================`);
|
||||
console.log(`[DEV VERIFICATION LINK]:\n${verifyLink}`);
|
||||
console.log(`======================================================\n`);
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
console.log(`\n======================================================`);
|
||||
console.log(`[DEV VERIFICATION LINK]:\n${verifyLink}`);
|
||||
console.log(`======================================================\n`);
|
||||
}
|
||||
|
||||
try {
|
||||
// 15 second timeout for SMTP
|
||||
@ -191,9 +193,11 @@ export async function sendPasswordResetEmail(email: string, token: string) {
|
||||
|
||||
const from = process.env.SMTP_FROM || '"My Weekly ToDo\'s" <mail@carrylight.de>';
|
||||
console.log(`[EMAIL] Sending password reset email to ${email} from ${from}`);
|
||||
console.log(`\n======================================================`);
|
||||
console.log(`[DEV RESET LINK]:\n${resetLink}`);
|
||||
console.log(`======================================================\n`);
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
console.log(`\n======================================================`);
|
||||
console.log(`[DEV RESET LINK]:\n${resetLink}`);
|
||||
console.log(`======================================================\n`);
|
||||
}
|
||||
|
||||
try {
|
||||
const timeoutPromise = new Promise((_, reject) =>
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es2017",
|
||||
"downlevelIteration": true,
|
||||
"lib": [
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
@ -14,7 +13,7 @@
|
||||
"noEmit": true,
|
||||
"esModuleInterop": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"moduleResolution": "bundler",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "preserve",
|
||||
|
||||
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue
Block a user