Merge branch 'main' into dev
# Conflicts: # package.json # tsconfig.tsbuildinfo
This commit is contained in:
commit
a040ba128b
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,12 +1,10 @@
|
||||
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';
|
||||
import { languageFromAcceptLanguage } from '@/lib/emailTemplates';
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
const body = 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;
|
||||
|
||||
@ -114,6 +114,22 @@ function dayDateLabel(d: Date, de: boolean): string {
|
||||
});
|
||||
}
|
||||
|
||||
// Strip Apple Calendar icon encodings (=h=g, =i=g, etc.) and emoji from
|
||||
// event titles before PDF rendering. Helvetica cannot render these glyphs and
|
||||
// they appear as raw escape sequences. The colored dot/chip already identifies
|
||||
// the calendar visually, so the icon character carries no extra information.
|
||||
function pdfTitle(s: string | null | undefined, maxLen = 60): string {
|
||||
if (!s) return '';
|
||||
return s
|
||||
.replace(/(?:=[a-zA-Z])+/g, '') // Apple CalDAV icon escapes: =h=g, =i=g, =k=g …
|
||||
.replace(/[\u{1F000}-\u{1FFFF}]/gu, '') // emoji (supplementary plane)
|
||||
.replace(/[\u{2600}-\u{27BF}]/gu, '') // misc symbols & dingbats
|
||||
.replace(/[\uE000-\uF8FF]/g, '') // Apple private-use area
|
||||
.replace(/[\uFE00-\uFE0F\u200D]/g, '') // variation selectors / ZWJ
|
||||
.trim()
|
||||
.slice(0, maxLen);
|
||||
}
|
||||
|
||||
function fmtHour(h: number): string {
|
||||
return `${String(h).padStart(2, '0')}:00`;
|
||||
}
|
||||
@ -454,7 +470,7 @@ function WeekCalendarPDF({
|
||||
return React.createElement(View, {
|
||||
key: t.id,
|
||||
style: { borderRadius: 2, paddingVertical: 1.5, paddingHorizontal: 4, marginBottom: 2, backgroundColor: DONE_BG },
|
||||
}, React.createElement(Text, { style: { fontSize: 7.5, color: DONE_TEXT } }, (t.title || '').slice(0, 36)));
|
||||
}, React.createElement(Text, { style: { fontSize: 7.5, color: DONE_TEXT } }, pdfTitle(t.title, 36)));
|
||||
}
|
||||
if (t.isExternal) {
|
||||
// Calendar event: solid color chip with white text + dot indicator (matches webapp style)
|
||||
@ -473,7 +489,7 @@ function WeekCalendarPDF({
|
||||
React.createElement(View, {
|
||||
style: { width: 5, height: 5, borderRadius: 2.5, backgroundColor: evTextColor, opacity: 0.7, marginRight: 3, flexShrink: 0 },
|
||||
}),
|
||||
React.createElement(Text, { style: { fontFamily: 'Helvetica-Bold', fontSize: 7.5, color: evTextColor, flex: 1 } }, (t.title || '').slice(0, 36)),
|
||||
React.createElement(Text, { style: { fontFamily: 'Helvetica-Bold', fontSize: 7.5, color: evTextColor, flex: 1 } }, pdfTitle(t.title, 36)),
|
||||
);
|
||||
}
|
||||
// User-created all-day task: left border only, no background fill
|
||||
@ -481,7 +497,7 @@ function WeekCalendarPDF({
|
||||
return React.createElement(View, {
|
||||
key: t.id,
|
||||
style: { borderLeftWidth: 2, borderLeftColor: accent, paddingVertical: 1.5, paddingHorizontal: 4, marginBottom: 2 },
|
||||
}, React.createElement(Text, { style: { fontSize: 7.5, color: '#374151' } }, (t.title || '').slice(0, 36)));
|
||||
}, React.createElement(Text, { style: { fontSize: 7.5, color: '#374151' } }, pdfTitle(t.title, 36)));
|
||||
}),
|
||||
allDayTasks.length > 5 ? React.createElement(Text, {
|
||||
style: { fontSize: 6, color: '#3b82f6', marginTop: 1 },
|
||||
@ -575,7 +591,7 @@ function WeekCalendarPDF({
|
||||
key: t.id,
|
||||
style: { marginBottom: 1, paddingLeft: 1, paddingVertical: 1 },
|
||||
},
|
||||
React.createElement(Text, { style: { fontSize: 7.5, color: DONE_TEXT, lineHeight: 1.3, textDecoration: 'line-through' } }, (t.title || '').slice(0, 60)),
|
||||
React.createElement(Text, { style: { fontSize: 7.5, color: DONE_TEXT, lineHeight: 1.3, textDecoration: 'line-through' } }, pdfTitle(t.title)),
|
||||
);
|
||||
}
|
||||
|
||||
@ -589,7 +605,7 @@ function WeekCalendarPDF({
|
||||
paddingLeft: 3, paddingVertical: 1, marginBottom: 1,
|
||||
},
|
||||
},
|
||||
React.createElement(Text, { style: { fontFamily: 'Helvetica-Bold', fontSize: 7.5, color: '#1e293b', lineHeight: 1.3 } }, (t.title || '').slice(0, 60)),
|
||||
React.createElement(Text, { style: { fontFamily: 'Helvetica-Bold', fontSize: 7.5, color: '#1e293b', lineHeight: 1.3 } }, pdfTitle(t.title)),
|
||||
);
|
||||
}
|
||||
|
||||
@ -601,7 +617,7 @@ function WeekCalendarPDF({
|
||||
paddingVertical: 1, marginBottom: 1,
|
||||
},
|
||||
},
|
||||
React.createElement(Text, { style: { fontSize: 7.5, color: '#1e293b', lineHeight: 1.3 } }, (t.title || '').slice(0, 60)),
|
||||
React.createElement(Text, { style: { fontSize: 7.5, color: '#1e293b', lineHeight: 1.3 } }, pdfTitle(t.title)),
|
||||
);
|
||||
}),
|
||||
);
|
||||
@ -719,7 +735,7 @@ function WeekCalendarPDF({
|
||||
}),
|
||||
React.createElement(Text, {
|
||||
style: { fontSize: 7, color: '#374151', lineHeight: 1.3, flex: 1 },
|
||||
}, (t.title || '').slice(0, 52)),
|
||||
}, pdfTitle(t.title)),
|
||||
)
|
||||
),
|
||||
list.tasks.length > 18 ? React.createElement(Text, {
|
||||
|
||||
@ -488,6 +488,25 @@ export function GridTaskBlock({
|
||||
</svg>
|
||||
</button>
|
||||
)}
|
||||
{/* Rolling indicator */}
|
||||
{task.isRolling && !task.completed && (
|
||||
<span
|
||||
title="Auto-rolling task"
|
||||
style={{
|
||||
display: "inline-flex",
|
||||
alignItems: "center",
|
||||
padding: "1px 3px",
|
||||
color: "var(--weekly-teal, #009a9a)",
|
||||
marginLeft: "3px",
|
||||
flexShrink: 0,
|
||||
}}
|
||||
>
|
||||
<svg viewBox="0 0 24 24" width="10" height="10" stroke="currentColor" strokeWidth="2.5" fill="none" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
|
||||
<polyline points="23 4 23 10 17 10" />
|
||||
<path d="M20.49 15a9 9 0 1 1-2.12-9.36L23 10" />
|
||||
</svg>
|
||||
</span>
|
||||
)}
|
||||
{weatherEnabled && (() => {
|
||||
const provider = task.externalProvider
|
||||
|| (task.externalId?.startsWith("synology::") ? "synology" : 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
|
||||
|
||||
@ -88,9 +88,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}`);
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
console.log(`\n======================================================`);
|
||||
console.log(`[DEV VERIFICATION LINK]:\n${verifyLink}`);
|
||||
console.log(`======================================================\n`);
|
||||
}
|
||||
|
||||
try {
|
||||
const timeoutPromise = new Promise((_, reject) =>
|
||||
@ -159,9 +161,11 @@ export async function sendPasswordResetEmail(email: string, token: string, langu
|
||||
|
||||
const from = process.env.SMTP_FROM || '"My Weekly ToDo\'s" <mail@carrylight.de>';
|
||||
console.log(`[EMAIL] Sending password reset email to ${email} from ${from}`);
|
||||
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) =>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user