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';
|
import { NextRequest, NextResponse } from 'next/server';
|
||||||
export const dynamic = 'force-dynamic';
|
export const dynamic = 'force-dynamic';
|
||||||
import { PrismaClient } from '@prisma/client';
|
import { prisma } from '@/lib/prisma';
|
||||||
import { hash } from 'bcryptjs';
|
import { hash } from 'bcryptjs';
|
||||||
import { sendVerificationEmail, generateVerificationCode, generateVerificationToken } from '@/lib/email';
|
import { sendVerificationEmail, generateVerificationCode, generateVerificationToken } from '@/lib/email';
|
||||||
import { languageFromAcceptLanguage } from '@/lib/emailTemplates';
|
import { languageFromAcceptLanguage } from '@/lib/emailTemplates';
|
||||||
|
|
||||||
const prisma = new PrismaClient();
|
|
||||||
|
|
||||||
export async function POST(request: NextRequest) {
|
export async function POST(request: NextRequest) {
|
||||||
try {
|
try {
|
||||||
const body = await request.json();
|
const body = await request.json();
|
||||||
|
|||||||
@ -3,9 +3,7 @@ import { validateCredentials } from '@/lib/apple-calendar';
|
|||||||
import { CalendarConnection } from '@/lib/calendar-sync';
|
import { CalendarConnection } from '@/lib/calendar-sync';
|
||||||
import { getServerSession } from 'next-auth';
|
import { getServerSession } from 'next-auth';
|
||||||
import { authOptions } from "@/lib/auth";
|
import { authOptions } from "@/lib/auth";
|
||||||
import { PrismaClient } from '@prisma/client';
|
import { prisma } from '@/lib/prisma';
|
||||||
|
|
||||||
const prisma = new PrismaClient();
|
|
||||||
|
|
||||||
export async function POST(req: Request) {
|
export async function POST(req: Request) {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@ -2,12 +2,10 @@ import { NextRequest, NextResponse } from 'next/server';
|
|||||||
import { getServerSession } from 'next-auth';
|
import { getServerSession } from 'next-auth';
|
||||||
import { authOptions } from "@/lib/auth";
|
import { authOptions } from "@/lib/auth";
|
||||||
import { google } from 'googleapis';
|
import { google } from 'googleapis';
|
||||||
import { PrismaClient } from '@prisma/client';
|
import { prisma } from '@/lib/prisma';
|
||||||
|
|
||||||
export const dynamic = 'force-dynamic';
|
export const dynamic = 'force-dynamic';
|
||||||
|
|
||||||
const prisma = new PrismaClient();
|
|
||||||
|
|
||||||
// Google Calendar OAuth callback endpoint
|
// Google Calendar OAuth callback endpoint
|
||||||
export async function GET(request: NextRequest) {
|
export async function GET(request: NextRequest) {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@ -1,13 +1,11 @@
|
|||||||
import { NextRequest, NextResponse } from 'next/server';
|
import { NextRequest, NextResponse } from 'next/server';
|
||||||
import { getServerSession } from 'next-auth';
|
import { getServerSession } from 'next-auth';
|
||||||
import { authOptions } from "@/lib/auth";
|
import { authOptions } from "@/lib/auth";
|
||||||
import { PrismaClient } from '@prisma/client';
|
import { prisma } from '@/lib/prisma';
|
||||||
import { getUserDatabases } from '@/lib/notion-calendar';
|
import { getUserDatabases } from '@/lib/notion-calendar';
|
||||||
|
|
||||||
export const dynamic = 'force-dynamic';
|
export const dynamic = 'force-dynamic';
|
||||||
|
|
||||||
const prisma = new PrismaClient();
|
|
||||||
|
|
||||||
export async function GET(request: NextRequest) {
|
export async function GET(request: NextRequest) {
|
||||||
try {
|
try {
|
||||||
const session = await getServerSession(authOptions);
|
const session = await getServerSession(authOptions);
|
||||||
|
|||||||
@ -2,9 +2,7 @@ import { NextResponse } from 'next/server';
|
|||||||
import { validateCredentials } from '@/lib/synology-calendar';
|
import { validateCredentials } from '@/lib/synology-calendar';
|
||||||
import { getServerSession } from 'next-auth';
|
import { getServerSession } from 'next-auth';
|
||||||
import { authOptions } from "@/lib/auth";
|
import { authOptions } from "@/lib/auth";
|
||||||
import { PrismaClient } from '@prisma/client';
|
import { prisma } from '@/lib/prisma';
|
||||||
|
|
||||||
const prisma = new PrismaClient();
|
|
||||||
|
|
||||||
export async function POST(req: Request) {
|
export async function POST(req: Request) {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@ -127,7 +127,16 @@ export async function GET(req: Request) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Try each user-configured URL first
|
// 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) {
|
for (const url of userUrls) {
|
||||||
try {
|
try {
|
||||||
const res = await fetch(url, { signal: AbortSignal.timeout(4000) });
|
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 { NextRequest, NextResponse } from 'next/server';
|
||||||
import { getServerSession } from 'next-auth';
|
import { getServerSession } from 'next-auth';
|
||||||
import { authOptions } from "@/lib/auth";
|
import { authOptions } from "@/lib/auth";
|
||||||
import { PrismaClient } from '@prisma/client';
|
import { prisma } from '@/lib/prisma';
|
||||||
import { createGoogleClient, createGoogleTaskList } from '@/lib/google-tasks';
|
import { createGoogleClient, createGoogleTaskList } from '@/lib/google-tasks';
|
||||||
import { createMsTodoList } from '@/lib/microsoft-todo';
|
import { createMsTodoList } from '@/lib/microsoft-todo';
|
||||||
import { getOutlookAccessToken } from '@/lib/outlook-token';
|
import { getOutlookAccessToken } from '@/lib/outlook-token';
|
||||||
import { createAppleReminderList } from '@/lib/apple-reminders';
|
import { createAppleReminderList } from '@/lib/apple-reminders';
|
||||||
import { createSynologyReminderList } from '@/lib/synology-tasks';
|
import { createSynologyReminderList } from '@/lib/synology-tasks';
|
||||||
|
|
||||||
const prisma = new PrismaClient();
|
|
||||||
|
|
||||||
export async function POST(req: NextRequest) {
|
export async function POST(req: NextRequest) {
|
||||||
try {
|
try {
|
||||||
const session = await getServerSession(authOptions);
|
const session = await getServerSession(authOptions);
|
||||||
|
|||||||
@ -1,11 +1,9 @@
|
|||||||
import { NextRequest, NextResponse } from 'next/server';
|
import { NextRequest, NextResponse } from 'next/server';
|
||||||
import { getServerSession } from 'next-auth';
|
import { getServerSession } from 'next-auth';
|
||||||
import { authOptions } from "@/lib/auth";
|
import { authOptions } from "@/lib/auth";
|
||||||
import { PrismaClient } from '@prisma/client';
|
import { prisma } from '@/lib/prisma';
|
||||||
import { notifyUser } from '@/lib/sse';
|
import { notifyUser } from '@/lib/sse';
|
||||||
|
|
||||||
const prisma = new PrismaClient();
|
|
||||||
|
|
||||||
async function resolveUserId(email: string): Promise<string | null> {
|
async function resolveUserId(email: string): Promise<string | null> {
|
||||||
const user = await prisma.user.findUnique({
|
const user = await prisma.user.findUnique({
|
||||||
where: { email },
|
where: { email },
|
||||||
|
|||||||
@ -2,13 +2,11 @@
|
|||||||
import { NextRequest, NextResponse } from 'next/server';
|
import { NextRequest, NextResponse } from 'next/server';
|
||||||
import { getServerSession } from 'next-auth';
|
import { getServerSession } from 'next-auth';
|
||||||
import { authOptions } from "@/lib/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 { createGoogleClient, fetchGoogleTasks, fetchGoogleTaskLists } from '@/lib/google-tasks';
|
||||||
import { fetchMsTodoLists, fetchMsTodoTasks, isMsTodoTaskCompleted, fetchMsChecklistItems } from '@/lib/microsoft-todo';
|
import { fetchMsTodoLists, fetchMsTodoTasks, isMsTodoTaskCompleted, fetchMsChecklistItems } from '@/lib/microsoft-todo';
|
||||||
import { getOutlookAccessToken } from '@/lib/outlook-token';
|
import { getOutlookAccessToken } from '@/lib/outlook-token';
|
||||||
|
|
||||||
const prisma = new PrismaClient();
|
|
||||||
|
|
||||||
interface SourceList {
|
interface SourceList {
|
||||||
id: string;
|
id: string;
|
||||||
title: string;
|
title: string;
|
||||||
|
|||||||
@ -1,15 +1,13 @@
|
|||||||
import { NextRequest, NextResponse } from 'next/server';
|
import { NextRequest, NextResponse } from 'next/server';
|
||||||
import { getServerSession } from 'next-auth';
|
import { getServerSession } from 'next-auth';
|
||||||
import { authOptions } from "@/lib/auth";
|
import { authOptions } from "@/lib/auth";
|
||||||
import { PrismaClient } from '@prisma/client';
|
import { prisma } from '@/lib/prisma';
|
||||||
import { createGoogleClient, fetchGoogleTaskLists } from '@/lib/google-tasks';
|
import { createGoogleClient, fetchGoogleTaskLists } from '@/lib/google-tasks';
|
||||||
import { fetchMsTodoLists } from '@/lib/microsoft-todo';
|
import { fetchMsTodoLists } from '@/lib/microsoft-todo';
|
||||||
import { getOutlookAccessToken } from '@/lib/outlook-token';
|
import { getOutlookAccessToken } from '@/lib/outlook-token';
|
||||||
|
|
||||||
export const dynamic = 'force-dynamic';
|
export const dynamic = 'force-dynamic';
|
||||||
|
|
||||||
const prisma = new PrismaClient();
|
|
||||||
|
|
||||||
export async function GET(req: NextRequest) {
|
export async function GET(req: NextRequest) {
|
||||||
try {
|
try {
|
||||||
const session = await getServerSession(authOptions);
|
const session = await getServerSession(authOptions);
|
||||||
|
|||||||
@ -1,11 +1,10 @@
|
|||||||
import { NextRequest, NextResponse } from 'next/server';
|
import { NextRequest, NextResponse } from 'next/server';
|
||||||
import { getServerSession } from 'next-auth';
|
import { getServerSession } from 'next-auth';
|
||||||
import { authOptions } from "@/lib/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';
|
import { notifyUser } from '@/lib/sse';
|
||||||
|
|
||||||
const prisma = new PrismaClient();
|
|
||||||
|
|
||||||
// Validate and sanitize a URL — only allow http/https, reject javascript: and data: schemes
|
// Validate and sanitize a URL — only allow http/https, reject javascript: and data: schemes
|
||||||
function sanitizeUrl(raw: string | null | undefined): string | null {
|
function sanitizeUrl(raw: string | null | undefined): string | null {
|
||||||
if (!raw) return 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 {
|
function fmtHour(h: number): string {
|
||||||
return `${String(h).padStart(2, '0')}:00`;
|
return `${String(h).padStart(2, '0')}:00`;
|
||||||
}
|
}
|
||||||
@ -454,7 +470,7 @@ function WeekCalendarPDF({
|
|||||||
return React.createElement(View, {
|
return React.createElement(View, {
|
||||||
key: t.id,
|
key: t.id,
|
||||||
style: { borderRadius: 2, paddingVertical: 1.5, paddingHorizontal: 4, marginBottom: 2, backgroundColor: DONE_BG },
|
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) {
|
if (t.isExternal) {
|
||||||
// Calendar event: solid color chip with white text + dot indicator (matches webapp style)
|
// Calendar event: solid color chip with white text + dot indicator (matches webapp style)
|
||||||
@ -473,7 +489,7 @@ function WeekCalendarPDF({
|
|||||||
React.createElement(View, {
|
React.createElement(View, {
|
||||||
style: { width: 5, height: 5, borderRadius: 2.5, backgroundColor: evTextColor, opacity: 0.7, marginRight: 3, flexShrink: 0 },
|
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
|
// User-created all-day task: left border only, no background fill
|
||||||
@ -481,7 +497,7 @@ function WeekCalendarPDF({
|
|||||||
return React.createElement(View, {
|
return React.createElement(View, {
|
||||||
key: t.id,
|
key: t.id,
|
||||||
style: { borderLeftWidth: 2, borderLeftColor: accent, paddingVertical: 1.5, paddingHorizontal: 4, marginBottom: 2 },
|
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, {
|
allDayTasks.length > 5 ? React.createElement(Text, {
|
||||||
style: { fontSize: 6, color: '#3b82f6', marginTop: 1 },
|
style: { fontSize: 6, color: '#3b82f6', marginTop: 1 },
|
||||||
@ -575,7 +591,7 @@ function WeekCalendarPDF({
|
|||||||
key: t.id,
|
key: t.id,
|
||||||
style: { marginBottom: 1, paddingLeft: 1, paddingVertical: 1 },
|
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,
|
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,
|
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, {
|
React.createElement(Text, {
|
||||||
style: { fontSize: 7, color: '#374151', lineHeight: 1.3, flex: 1 },
|
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, {
|
list.tasks.length > 18 ? React.createElement(Text, {
|
||||||
|
|||||||
@ -488,6 +488,25 @@ export function GridTaskBlock({
|
|||||||
</svg>
|
</svg>
|
||||||
</button>
|
</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 && (() => {
|
{weatherEnabled && (() => {
|
||||||
const provider = task.externalProvider
|
const provider = task.externalProvider
|
||||||
|| (task.externalId?.startsWith("synology::") ? "synology" : null);
|
|| (task.externalId?.startsWith("synology::") ? "synology" : null);
|
||||||
|
|||||||
@ -13,7 +13,7 @@ export const authOptions: NextAuthOptions = {
|
|||||||
GoogleProvider({
|
GoogleProvider({
|
||||||
clientId: process.env.GOOGLE_CLIENT_ID || "",
|
clientId: process.env.GOOGLE_CLIENT_ID || "",
|
||||||
clientSecret: process.env.GOOGLE_CLIENT_SECRET || "",
|
clientSecret: process.env.GOOGLE_CLIENT_SECRET || "",
|
||||||
allowDangerousEmailAccountLinking: true,
|
allowDangerousEmailAccountLinking: false,
|
||||||
authorization: {
|
authorization: {
|
||||||
params: {
|
params: {
|
||||||
prompt: "consent",
|
prompt: "consent",
|
||||||
@ -26,13 +26,13 @@ export const authOptions: NextAuthOptions = {
|
|||||||
AppleProvider({
|
AppleProvider({
|
||||||
clientId: process.env.APPLE_ID || "",
|
clientId: process.env.APPLE_ID || "",
|
||||||
clientSecret: process.env.APPLE_SECRET || "",
|
clientSecret: process.env.APPLE_SECRET || "",
|
||||||
allowDangerousEmailAccountLinking: true,
|
allowDangerousEmailAccountLinking: false,
|
||||||
}),
|
}),
|
||||||
AzureADProvider({
|
AzureADProvider({
|
||||||
clientId: process.env.MICROSOFT_CLIENT_ID || "",
|
clientId: process.env.MICROSOFT_CLIENT_ID || "",
|
||||||
clientSecret: process.env.MICROSOFT_CLIENT_SECRET || "",
|
clientSecret: process.env.MICROSOFT_CLIENT_SECRET || "",
|
||||||
tenantId: "common",
|
tenantId: "common",
|
||||||
allowDangerousEmailAccountLinking: true,
|
allowDangerousEmailAccountLinking: false,
|
||||||
authorization: {
|
authorization: {
|
||||||
params: {
|
params: {
|
||||||
prompt: "consent",
|
prompt: "consent",
|
||||||
@ -85,7 +85,7 @@ export const authOptions: NextAuthOptions = {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
],
|
],
|
||||||
debug: true,
|
debug: process.env.NODE_ENV === 'development',
|
||||||
session: {
|
session: {
|
||||||
strategy: "jwt",
|
strategy: "jwt",
|
||||||
maxAge: 30 * 24 * 60 * 60, // 30 days
|
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>';
|
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(`[EMAIL] Sending verification email to ${email} from ${from}`);
|
||||||
|
if (process.env.NODE_ENV === 'development') {
|
||||||
console.log(`\n======================================================`);
|
console.log(`\n======================================================`);
|
||||||
console.log(`[DEV VERIFICATION LINK]:\n${verifyLink}`);
|
console.log(`[DEV VERIFICATION LINK]:\n${verifyLink}`);
|
||||||
console.log(`======================================================\n`);
|
console.log(`======================================================\n`);
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const timeoutPromise = new Promise((_, reject) =>
|
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>';
|
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(`[EMAIL] Sending password reset email to ${email} from ${from}`);
|
||||||
|
if (process.env.NODE_ENV === 'development') {
|
||||||
console.log(`\n======================================================`);
|
console.log(`\n======================================================`);
|
||||||
console.log(`[DEV RESET LINK]:\n${resetLink}`);
|
console.log(`[DEV RESET LINK]:\n${resetLink}`);
|
||||||
console.log(`======================================================\n`);
|
console.log(`======================================================\n`);
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const timeoutPromise = new Promise((_, reject) =>
|
const timeoutPromise = new Promise((_, reject) =>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user