From 4c3d1a118c2a22ab5e3a57999053f02aa7968784 Mon Sep 17 00:00:00 2001 From: tech08mag Date: Sun, 9 Aug 2026 21:42:54 +0200 Subject: [PATCH] fixxed color selector in the category settings and fixxed drag and drop subtask --- API_SPEC.md | 55 ++ backend/dist/db/schema.d.ts | 649 ++++++++++++++---- backend/dist/db/schema.d.ts.map | 2 +- backend/dist/db/schema.js | 58 +- backend/dist/db/schema.js.map | 2 +- backend/dist/index.d.ts.map | 2 +- backend/dist/index.js | 2 + backend/dist/index.js.map | 2 +- backend/dist/routes/auth.d.ts.map | 2 +- backend/dist/routes/auth.js | 110 ++- backend/dist/routes/auth.js.map | 2 +- backend/dist/routes/friends.d.ts.map | 2 +- backend/dist/routes/friends.js | 11 + backend/dist/routes/friends.js.map | 2 +- backend/dist/routes/repeatProfiles.d.ts | 3 + backend/dist/routes/repeatProfiles.d.ts.map | 1 + backend/dist/routes/repeatProfiles.js | 74 ++ backend/dist/routes/repeatProfiles.js.map | 1 + backend/dist/routes/subtasks.d.ts.map | 2 +- backend/dist/routes/subtasks.js | 30 +- backend/dist/routes/subtasks.js.map | 2 +- backend/dist/routes/sync.d.ts.map | 2 +- backend/dist/routes/sync.js | 286 +++++++- backend/dist/routes/sync.js.map | 2 +- backend/dist/routes/tasks.d.ts.map | 2 +- backend/dist/routes/tasks.js | 63 +- backend/dist/routes/tasks.js.map | 2 +- backend/dist/utils/auth.d.ts | 2 +- backend/dist/utils/auth.d.ts.map | 2 +- backend/dist/utils/auth.js | 15 +- backend/dist/utils/auth.js.map | 2 +- backend/dist/utils/validation.d.ts | 460 ++++++++++--- backend/dist/utils/validation.d.ts.map | 2 +- backend/dist/utils/validation.js | 85 ++- backend/dist/utils/validation.js.map | 2 +- backend/src/db/schema.ts | 51 +- backend/src/routes/subtasks.ts | 6 +- backend/src/routes/sync.ts | 290 +++++++- backend/src/routes/tasks.ts | 32 +- backend/src/utils/validation.ts | 41 +- carry-your-live/app.json | 2 + carry-your-live/app/(tabs)/_layout.tsx | 44 +- carry-your-live/app/(tabs)/calendar.tsx | 112 +-- carry-your-live/app/(tabs)/index.tsx | 30 +- carry-your-live/app/(tabs)/settings.tsx | 30 +- carry-your-live/app/(tabs)/stats.tsx | 12 +- carry-your-live/app/subtask-detail.tsx | 2 +- carry-your-live/app/task-detail.tsx | 20 +- carry-your-live/package-lock.json | 37 +- carry-your-live/package.json | 2 + .../src/components/AssigneeSelector.tsx | 13 +- .../src/components/CategoryEditorModal.tsx | 178 ++--- .../src/components/CategoryFilter.tsx | 71 +- .../src/components/CategorySelector.tsx | 9 +- .../src/components/ColorWheel.native.tsx | 45 +- .../src/components/ColorWheel.web.tsx | 18 +- .../src/components/DateTimeInput.web.tsx | 22 +- .../src/components/DateTimePicker.tsx | 26 +- .../src/components/FloatingActionButton.tsx | 8 +- .../src/components/FormButtons.tsx | 11 +- .../src/components/FriendsModal.tsx | 2 +- carry-your-live/src/components/Header.tsx | 6 +- carry-your-live/src/components/LegalModal.tsx | 2 +- carry-your-live/src/components/ListItem.tsx | 4 +- .../src/components/OptionPickerModal.tsx | 4 +- .../src/components/PrioritySelector.tsx | 2 +- .../src/components/QuickAddBar.tsx | 96 +-- .../src/components/ReminderSelector.tsx | 8 +- .../src/components/RepeatSelector.tsx | 14 +- .../src/components/ServerUrlModal.tsx | 2 +- .../src/components/SubtaskItem.tsx | 97 +-- .../src/components/SubtasksSection.tsx | 14 +- carry-your-live/src/components/SyncStatus.tsx | 10 +- carry-your-live/src/components/TabBarIcon.tsx | 52 +- .../src/components/TaskDeleteModal.tsx | 4 +- carry-your-live/src/components/TaskItem.tsx | 85 +-- carry-your-live/src/components/TaskList.tsx | 162 ++++- .../src/components/TaskOverflowMenu.tsx | 16 +- .../src/components/WheelTimePicker.tsx | 6 +- carry-your-live/src/database/index.ts | 4 +- carry-your-live/src/database/migrations.ts | 13 + carry-your-live/src/database/schema.ts | 10 +- carry-your-live/src/database/sync.ts | 132 +++- carry-your-live/src/database/tombstones.ts | 65 ++ carry-your-live/src/models/Task.ts | 1 - carry-your-live/src/models/Tombstone.ts | 12 + carry-your-live/src/services/auth.ts | 9 +- carry-your-live/src/theme.tsx | 36 +- carry-your-live/src/theme/tokens.ts | 194 ++++++ carry-your-live/src/types/index.ts | 6 + carry-your-live/src/utils/categoryActions.ts | 2 + .../src/utils/repeatProfileActions.ts | 2 + carry-your-live/src/utils/tabHistory.ts | 16 + carry-your-live/src/utils/taskActions.ts | 102 ++- creds.txt | 6 +- {media => design drafts}/clock.jpg | Bin {media => design drafts}/draft.png | Bin opencode.json | 4 + todo.md | 2 +- 99 files changed, 3335 insertions(+), 920 deletions(-) create mode 100644 backend/dist/routes/repeatProfiles.d.ts create mode 100644 backend/dist/routes/repeatProfiles.d.ts.map create mode 100644 backend/dist/routes/repeatProfiles.js create mode 100644 backend/dist/routes/repeatProfiles.js.map create mode 100644 carry-your-live/src/database/tombstones.ts create mode 100644 carry-your-live/src/models/Tombstone.ts create mode 100644 carry-your-live/src/theme/tokens.ts create mode 100644 carry-your-live/src/utils/tabHistory.ts rename {media => design drafts}/clock.jpg (100%) rename {media => design drafts}/draft.png (100%) create mode 100644 opencode.json diff --git a/API_SPEC.md b/API_SPEC.md index d42d2f1..10abdfd 100644 --- a/API_SPEC.md +++ b/API_SPEC.md @@ -57,6 +57,7 @@ GET /sync { "id": "sub_1", "taskId": "task_1", + "parentSubtaskId": null, "title": "Setup Expo project", "completed": true, "order": 0, @@ -64,10 +65,21 @@ GET /sync "updatedAt": 1699000000000 } ], + "repeatProfiles": [], + "friendships": [], + "deleted": [ + { + "entity": "tasks", + "id": "task_5", + "updatedAt": 1699150000000 + } + ], "timestamp": 1699150000000 } ``` +`deleted` contains every row tombstoned (deleted) since `since` — one entry per entity type/id. Clients must remove the row locally (and any related rows: a task deletion implies its subtasks, a category deletion implies its tasks and their subtasks, a parent subtask deletion implies its children). + **Error Responses** | Status | Code | Message | |--------|------|---------| @@ -114,6 +126,7 @@ POST /sync/push { "id": "sub_new", "taskId": "task_new", + "parentSubtaskId": null, "title": "Subtask 1", "completed": false, "order": 0, @@ -122,10 +135,19 @@ POST /sync/push } ] }, + "deleted": [ + { + "entity": "categories", + "id": "cat_gone", + "updatedAt": 1699150000000 + } + ], "lastPulledAt": 1699100000000 } ``` +`deleted` is a list of tombstones the client recorded locally since its last successful sync. Deletions are resolved by last-writer-wins: if the server row's `updatedAt` is newer than the tombstone's `updatedAt`, the deletion is rejected and reported as a `server_wins` conflict (with the server row as `serverVersion`) so the client re-pulls it. Accepted deletions remove the rows server-side; cascaded rows (subtasks of a deleted task, tasks/subtasks of a deleted category, children of a deleted parent subtask) are tombstoned automatically so every device removes them. + **Response** (200 OK) ```json { @@ -568,6 +590,16 @@ interface Task { completed: boolean; dueDate: number; // Unix timestamp (ms), 0 if not set dueTime: string; // HH:MM format, empty if not set + endTime: string; // HH:MM format, empty if not set + allDay: boolean; + repeat: 'none' | 'daily' | 'weekly' | 'monthly' | 'custom'; + repeatInterval: number; + repeatDays: string; // comma separated weekday numbers, e.g. "0,2,4" + seriesId: string; // repeating-series id, empty if not part of a series + reminder: 'none' | 'at_time' | '15' | '30' | '60' | '120' | '1440'; + reminders: string; // comma separated reminder values, empty if none + assigneeId: string | null; + completedAt: number | null; // Unix timestamp (ms) when completed createdAt: number; updatedAt: number; subtasks?: Subtask[]; @@ -579,14 +611,37 @@ interface Task { interface Subtask { id: string; taskId: string; + parentSubtaskId: string | null; // id of the parent subtask, null for top-level title: string; + description: string; + priority: 'none' | 'low' | 'medium' | 'high' | 'critical'; completed: boolean; + dueDate: number; + dueTime: string; + endTime: string; + allDay: boolean; + repeat: 'none' | 'daily' | 'weekly' | 'monthly' | 'custom'; + repeatInterval: number; + repeatDays: string; + seriesId: string; + reminder: 'none' | 'at_time' | '15' | '30' | '60' | '120' | '1440'; + reminders: string; + assigneeId: string | null; order: number; createdAt: number; updatedAt: number; } ``` +### Tombstone +```typescript +interface Tombstone { + entity: 'categories' | 'tasks' | 'subtasks' | 'repeatProfiles' | 'friendships'; + id: string; // id of the deleted row + updatedAt: number; // Unix timestamp (ms) of the deletion +} +``` + ### UserSettings ```typescript interface UserSettings { diff --git a/backend/dist/db/schema.d.ts b/backend/dist/db/schema.d.ts index 20a995e..bef1b4a 100644 --- a/backend/dist/db/schema.d.ts +++ b/backend/dist/db/schema.d.ts @@ -1,3 +1,5 @@ +export declare const ENTITIES: readonly ["categories", "tasks", "subtasks", "repeatProfiles", "friendships"]; +export type EntityName = (typeof ENTITIES)[number]; export declare const users: import("drizzle-orm/pg-core").PgTableWithColumns<{ name: "users"; schema: undefined; @@ -50,6 +52,38 @@ export declare const users: import("drizzle-orm/pg-core").PgTableWithColumns<{ baseColumn: never; generated: undefined; }, {}, {}>; + resetToken: import("drizzle-orm/pg-core").PgColumn<{ + name: "reset_token"; + tableName: "users"; + dataType: "string"; + columnType: "PgText"; + data: string; + driverParam: string; + notNull: false; + hasDefault: false; + isPrimaryKey: false; + isAutoincrement: false; + hasRuntimeDefault: false; + enumValues: [string, ...string[]]; + baseColumn: never; + generated: undefined; + }, {}, {}>; + resetTokenExpiry: import("drizzle-orm/pg-core").PgColumn<{ + name: "reset_token_expiry"; + tableName: "users"; + dataType: "number"; + columnType: "PgBigInt53"; + data: number; + driverParam: string | number; + notNull: false; + hasDefault: false; + isPrimaryKey: false; + isAutoincrement: false; + hasRuntimeDefault: false; + enumValues: undefined; + baseColumn: never; + generated: undefined; + }, {}, {}>; createdAt: import("drizzle-orm/pg-core").PgColumn<{ name: "created_at"; tableName: "users"; @@ -334,7 +368,7 @@ export declare const tasks: import("drizzle-orm/pg-core").PgTableWithColumns<{ columnType: "PgText"; data: string; driverParam: string; - notNull: true; + notNull: false; hasDefault: false; isPrimaryKey: false; isAutoincrement: false; @@ -455,6 +489,22 @@ export declare const tasks: import("drizzle-orm/pg-core").PgTableWithColumns<{ baseColumn: never; generated: undefined; }, {}, {}>; + allDay: import("drizzle-orm/pg-core").PgColumn<{ + name: "all_day"; + tableName: "tasks"; + dataType: "boolean"; + columnType: "PgBoolean"; + data: boolean; + driverParam: boolean; + notNull: true; + hasDefault: true; + isPrimaryKey: false; + isAutoincrement: false; + hasRuntimeDefault: false; + enumValues: undefined; + baseColumn: never; + generated: undefined; + }, {}, {}>; repeat: import("drizzle-orm/pg-core").PgColumn<{ name: "repeat"; tableName: "tasks"; @@ -551,6 +601,38 @@ export declare const tasks: import("drizzle-orm/pg-core").PgTableWithColumns<{ baseColumn: never; generated: undefined; }, {}, {}>; + reminders: import("drizzle-orm/pg-core").PgColumn<{ + name: "reminders"; + tableName: "tasks"; + dataType: "string"; + columnType: "PgText"; + data: string; + driverParam: string; + notNull: true; + hasDefault: true; + isPrimaryKey: false; + isAutoincrement: false; + hasRuntimeDefault: false; + enumValues: [string, ...string[]]; + baseColumn: never; + generated: undefined; + }, {}, {}>; + completedAt: import("drizzle-orm/pg-core").PgColumn<{ + name: "completed_at"; + tableName: "tasks"; + dataType: "number"; + columnType: "PgBigInt53"; + data: number; + driverParam: string | number; + notNull: false; + hasDefault: false; + isPrimaryKey: false; + isAutoincrement: false; + hasRuntimeDefault: false; + enumValues: undefined; + baseColumn: never; + generated: undefined; + }, {}, {}>; createdAt: import("drizzle-orm/pg-core").PgColumn<{ name: "created_at"; tableName: "tasks"; @@ -586,6 +668,436 @@ export declare const tasks: import("drizzle-orm/pg-core").PgTableWithColumns<{ }; dialect: "pg"; }>; +export declare const tombstones: import("drizzle-orm/pg-core").PgTableWithColumns<{ + name: "tombstones"; + schema: undefined; + columns: { + entity: import("drizzle-orm/pg-core").PgColumn<{ + name: "entity"; + tableName: "tombstones"; + dataType: "string"; + columnType: "PgText"; + data: "categories" | "tasks" | "subtasks" | "repeatProfiles" | "friendships"; + driverParam: string; + notNull: true; + hasDefault: false; + isPrimaryKey: false; + isAutoincrement: false; + hasRuntimeDefault: false; + enumValues: ["categories", "tasks", "subtasks", "repeatProfiles", "friendships"]; + baseColumn: never; + generated: undefined; + }, {}, {}>; + entityId: import("drizzle-orm/pg-core").PgColumn<{ + name: "entity_id"; + tableName: "tombstones"; + dataType: "string"; + columnType: "PgText"; + data: string; + driverParam: string; + notNull: true; + hasDefault: false; + isPrimaryKey: false; + isAutoincrement: false; + hasRuntimeDefault: false; + enumValues: [string, ...string[]]; + baseColumn: never; + generated: undefined; + }, {}, {}>; + userId: import("drizzle-orm/pg-core").PgColumn<{ + name: "user_id"; + tableName: "tombstones"; + dataType: "string"; + columnType: "PgText"; + data: string; + driverParam: string; + notNull: true; + hasDefault: false; + isPrimaryKey: false; + isAutoincrement: false; + hasRuntimeDefault: false; + enumValues: [string, ...string[]]; + baseColumn: never; + generated: undefined; + }, {}, {}>; + updatedAt: import("drizzle-orm/pg-core").PgColumn<{ + name: "updated_at"; + tableName: "tombstones"; + dataType: "number"; + columnType: "PgBigInt53"; + data: number; + driverParam: string | number; + notNull: true; + hasDefault: false; + isPrimaryKey: false; + isAutoincrement: false; + hasRuntimeDefault: false; + enumValues: undefined; + baseColumn: never; + generated: undefined; + }, {}, {}>; + }; + dialect: "pg"; +}>; +export declare const subtasks: import("drizzle-orm/pg-core").PgTableWithColumns<{ + name: "subtasks"; + schema: undefined; + columns: { + id: import("drizzle-orm/pg-core").PgColumn<{ + name: "id"; + tableName: "subtasks"; + dataType: "string"; + columnType: "PgText"; + data: string; + driverParam: string; + notNull: true; + hasDefault: false; + isPrimaryKey: true; + isAutoincrement: false; + hasRuntimeDefault: false; + enumValues: [string, ...string[]]; + baseColumn: never; + generated: undefined; + }, {}, {}>; + userId: import("drizzle-orm/pg-core").PgColumn<{ + name: "user_id"; + tableName: "subtasks"; + dataType: "string"; + columnType: "PgText"; + data: string; + driverParam: string; + notNull: true; + hasDefault: false; + isPrimaryKey: false; + isAutoincrement: false; + hasRuntimeDefault: false; + enumValues: [string, ...string[]]; + baseColumn: never; + generated: undefined; + }, {}, {}>; + taskId: import("drizzle-orm/pg-core").PgColumn<{ + name: "task_id"; + tableName: "subtasks"; + dataType: "string"; + columnType: "PgText"; + data: string; + driverParam: string; + notNull: true; + hasDefault: false; + isPrimaryKey: false; + isAutoincrement: false; + hasRuntimeDefault: false; + enumValues: [string, ...string[]]; + baseColumn: never; + generated: undefined; + }, {}, {}>; + parentSubtaskId: import("drizzle-orm/pg-core").PgColumn<{ + name: "parent_subtask_id"; + tableName: "subtasks"; + dataType: "string"; + columnType: "PgText"; + data: string; + driverParam: string; + notNull: false; + hasDefault: false; + isPrimaryKey: false; + isAutoincrement: false; + hasRuntimeDefault: false; + enumValues: [string, ...string[]]; + baseColumn: never; + generated: undefined; + }, {}, {}>; + title: import("drizzle-orm/pg-core").PgColumn<{ + name: "title"; + tableName: "subtasks"; + dataType: "string"; + columnType: "PgText"; + data: string; + driverParam: string; + notNull: true; + hasDefault: false; + isPrimaryKey: false; + isAutoincrement: false; + hasRuntimeDefault: false; + enumValues: [string, ...string[]]; + baseColumn: never; + generated: undefined; + }, {}, {}>; + description: import("drizzle-orm/pg-core").PgColumn<{ + name: "description"; + tableName: "subtasks"; + dataType: "string"; + columnType: "PgText"; + data: string; + driverParam: string; + notNull: true; + hasDefault: true; + isPrimaryKey: false; + isAutoincrement: false; + hasRuntimeDefault: false; + enumValues: [string, ...string[]]; + baseColumn: never; + generated: undefined; + }, {}, {}>; + priority: import("drizzle-orm/pg-core").PgColumn<{ + name: "priority"; + tableName: "subtasks"; + dataType: "string"; + columnType: "PgText"; + data: "none" | "low" | "medium" | "high" | "critical"; + driverParam: string; + notNull: true; + hasDefault: true; + isPrimaryKey: false; + isAutoincrement: false; + hasRuntimeDefault: false; + enumValues: ["none", "low", "medium", "high", "critical"]; + baseColumn: never; + generated: undefined; + }, {}, {}>; + completed: import("drizzle-orm/pg-core").PgColumn<{ + name: "completed"; + tableName: "subtasks"; + dataType: "boolean"; + columnType: "PgBoolean"; + data: boolean; + driverParam: boolean; + notNull: true; + hasDefault: true; + isPrimaryKey: false; + isAutoincrement: false; + hasRuntimeDefault: false; + enumValues: undefined; + baseColumn: never; + generated: undefined; + }, {}, {}>; + dueDate: import("drizzle-orm/pg-core").PgColumn<{ + name: "due_date"; + tableName: "subtasks"; + dataType: "number"; + columnType: "PgBigInt53"; + data: number; + driverParam: string | number; + notNull: true; + hasDefault: true; + isPrimaryKey: false; + isAutoincrement: false; + hasRuntimeDefault: false; + enumValues: undefined; + baseColumn: never; + generated: undefined; + }, {}, {}>; + dueTime: import("drizzle-orm/pg-core").PgColumn<{ + name: "due_time"; + tableName: "subtasks"; + dataType: "string"; + columnType: "PgText"; + data: string; + driverParam: string; + notNull: true; + hasDefault: true; + isPrimaryKey: false; + isAutoincrement: false; + hasRuntimeDefault: false; + enumValues: [string, ...string[]]; + baseColumn: never; + generated: undefined; + }, {}, {}>; + endTime: import("drizzle-orm/pg-core").PgColumn<{ + name: "end_time"; + tableName: "subtasks"; + dataType: "string"; + columnType: "PgText"; + data: string; + driverParam: string; + notNull: true; + hasDefault: true; + isPrimaryKey: false; + isAutoincrement: false; + hasRuntimeDefault: false; + enumValues: [string, ...string[]]; + baseColumn: never; + generated: undefined; + }, {}, {}>; + allDay: import("drizzle-orm/pg-core").PgColumn<{ + name: "all_day"; + tableName: "subtasks"; + dataType: "boolean"; + columnType: "PgBoolean"; + data: boolean; + driverParam: boolean; + notNull: true; + hasDefault: true; + isPrimaryKey: false; + isAutoincrement: false; + hasRuntimeDefault: false; + enumValues: undefined; + baseColumn: never; + generated: undefined; + }, {}, {}>; + repeat: import("drizzle-orm/pg-core").PgColumn<{ + name: "repeat"; + tableName: "subtasks"; + dataType: "string"; + columnType: "PgText"; + data: "custom" | "none" | "daily" | "weekly" | "monthly"; + driverParam: string; + notNull: true; + hasDefault: true; + isPrimaryKey: false; + isAutoincrement: false; + hasRuntimeDefault: false; + enumValues: ["none", "daily", "weekly", "monthly", "custom"]; + baseColumn: never; + generated: undefined; + }, {}, {}>; + repeatInterval: import("drizzle-orm/pg-core").PgColumn<{ + name: "repeat_interval"; + tableName: "subtasks"; + dataType: "number"; + columnType: "PgInteger"; + data: number; + driverParam: string | number; + notNull: true; + hasDefault: true; + isPrimaryKey: false; + isAutoincrement: false; + hasRuntimeDefault: false; + enumValues: undefined; + baseColumn: never; + generated: undefined; + }, {}, {}>; + repeatDays: import("drizzle-orm/pg-core").PgColumn<{ + name: "repeat_days"; + tableName: "subtasks"; + dataType: "string"; + columnType: "PgText"; + data: string; + driverParam: string; + notNull: true; + hasDefault: true; + isPrimaryKey: false; + isAutoincrement: false; + hasRuntimeDefault: false; + enumValues: [string, ...string[]]; + baseColumn: never; + generated: undefined; + }, {}, {}>; + seriesId: import("drizzle-orm/pg-core").PgColumn<{ + name: "series_id"; + tableName: "subtasks"; + dataType: "string"; + columnType: "PgText"; + data: string; + driverParam: string; + notNull: true; + hasDefault: true; + isPrimaryKey: false; + isAutoincrement: false; + hasRuntimeDefault: false; + enumValues: [string, ...string[]]; + baseColumn: never; + generated: undefined; + }, {}, {}>; + reminder: import("drizzle-orm/pg-core").PgColumn<{ + name: "reminder"; + tableName: "subtasks"; + dataType: "string"; + columnType: "PgText"; + data: "none" | "at_time" | "15" | "30" | "60" | "120" | "1440"; + driverParam: string; + notNull: true; + hasDefault: true; + isPrimaryKey: false; + isAutoincrement: false; + hasRuntimeDefault: false; + enumValues: ["none", "at_time", "15", "30", "60", "120", "1440"]; + baseColumn: never; + generated: undefined; + }, {}, {}>; + reminders: import("drizzle-orm/pg-core").PgColumn<{ + name: "reminders"; + tableName: "subtasks"; + dataType: "string"; + columnType: "PgText"; + data: string; + driverParam: string; + notNull: true; + hasDefault: true; + isPrimaryKey: false; + isAutoincrement: false; + hasRuntimeDefault: false; + enumValues: [string, ...string[]]; + baseColumn: never; + generated: undefined; + }, {}, {}>; + assigneeId: import("drizzle-orm/pg-core").PgColumn<{ + name: "assignee_id"; + tableName: "subtasks"; + dataType: "string"; + columnType: "PgText"; + data: string; + driverParam: string; + notNull: false; + hasDefault: false; + isPrimaryKey: false; + isAutoincrement: false; + hasRuntimeDefault: false; + enumValues: [string, ...string[]]; + baseColumn: never; + generated: undefined; + }, {}, {}>; + order: import("drizzle-orm/pg-core").PgColumn<{ + name: "order"; + tableName: "subtasks"; + dataType: "number"; + columnType: "PgInteger"; + data: number; + driverParam: string | number; + notNull: true; + hasDefault: true; + isPrimaryKey: false; + isAutoincrement: false; + hasRuntimeDefault: false; + enumValues: undefined; + baseColumn: never; + generated: undefined; + }, {}, {}>; + createdAt: import("drizzle-orm/pg-core").PgColumn<{ + name: "created_at"; + tableName: "subtasks"; + dataType: "number"; + columnType: "PgBigInt53"; + data: number; + driverParam: string | number; + notNull: true; + hasDefault: true; + isPrimaryKey: false; + isAutoincrement: false; + hasRuntimeDefault: false; + enumValues: undefined; + baseColumn: never; + generated: undefined; + }, {}, {}>; + updatedAt: import("drizzle-orm/pg-core").PgColumn<{ + name: "updated_at"; + tableName: "subtasks"; + dataType: "number"; + columnType: "PgBigInt53"; + data: number; + driverParam: string | number; + notNull: true; + hasDefault: true; + isPrimaryKey: false; + isAutoincrement: false; + hasRuntimeDefault: false; + enumValues: undefined; + baseColumn: never; + generated: undefined; + }, {}, {}>; + }; + dialect: "pg"; +}>; export declare const repeatProfiles: import("drizzle-orm/pg-core").PgTableWithColumns<{ name: "repeat_profiles"; schema: undefined; @@ -721,141 +1233,6 @@ export declare const repeatProfiles: import("drizzle-orm/pg-core").PgTableWithCo }; dialect: "pg"; }>; -export declare const subtasks: import("drizzle-orm/pg-core").PgTableWithColumns<{ - name: "subtasks"; - schema: undefined; - columns: { - id: import("drizzle-orm/pg-core").PgColumn<{ - name: "id"; - tableName: "subtasks"; - dataType: "string"; - columnType: "PgText"; - data: string; - driverParam: string; - notNull: true; - hasDefault: false; - isPrimaryKey: true; - isAutoincrement: false; - hasRuntimeDefault: false; - enumValues: [string, ...string[]]; - baseColumn: never; - generated: undefined; - }, {}, {}>; - userId: import("drizzle-orm/pg-core").PgColumn<{ - name: "user_id"; - tableName: "subtasks"; - dataType: "string"; - columnType: "PgText"; - data: string; - driverParam: string; - notNull: true; - hasDefault: false; - isPrimaryKey: false; - isAutoincrement: false; - hasRuntimeDefault: false; - enumValues: [string, ...string[]]; - baseColumn: never; - generated: undefined; - }, {}, {}>; - taskId: import("drizzle-orm/pg-core").PgColumn<{ - name: "task_id"; - tableName: "subtasks"; - dataType: "string"; - columnType: "PgText"; - data: string; - driverParam: string; - notNull: true; - hasDefault: false; - isPrimaryKey: false; - isAutoincrement: false; - hasRuntimeDefault: false; - enumValues: [string, ...string[]]; - baseColumn: never; - generated: undefined; - }, {}, {}>; - title: import("drizzle-orm/pg-core").PgColumn<{ - name: "title"; - tableName: "subtasks"; - dataType: "string"; - columnType: "PgText"; - data: string; - driverParam: string; - notNull: true; - hasDefault: false; - isPrimaryKey: false; - isAutoincrement: false; - hasRuntimeDefault: false; - enumValues: [string, ...string[]]; - baseColumn: never; - generated: undefined; - }, {}, {}>; - completed: import("drizzle-orm/pg-core").PgColumn<{ - name: "completed"; - tableName: "subtasks"; - dataType: "boolean"; - columnType: "PgBoolean"; - data: boolean; - driverParam: boolean; - notNull: true; - hasDefault: true; - isPrimaryKey: false; - isAutoincrement: false; - hasRuntimeDefault: false; - enumValues: undefined; - baseColumn: never; - generated: undefined; - }, {}, {}>; - order: import("drizzle-orm/pg-core").PgColumn<{ - name: "order"; - tableName: "subtasks"; - dataType: "number"; - columnType: "PgInteger"; - data: number; - driverParam: string | number; - notNull: true; - hasDefault: true; - isPrimaryKey: false; - isAutoincrement: false; - hasRuntimeDefault: false; - enumValues: undefined; - baseColumn: never; - generated: undefined; - }, {}, {}>; - createdAt: import("drizzle-orm/pg-core").PgColumn<{ - name: "created_at"; - tableName: "subtasks"; - dataType: "number"; - columnType: "PgBigInt53"; - data: number; - driverParam: string | number; - notNull: true; - hasDefault: true; - isPrimaryKey: false; - isAutoincrement: false; - hasRuntimeDefault: false; - enumValues: undefined; - baseColumn: never; - generated: undefined; - }, {}, {}>; - updatedAt: import("drizzle-orm/pg-core").PgColumn<{ - name: "updated_at"; - tableName: "subtasks"; - dataType: "number"; - columnType: "PgBigInt53"; - data: number; - driverParam: string | number; - notNull: true; - hasDefault: true; - isPrimaryKey: false; - isAutoincrement: false; - hasRuntimeDefault: false; - enumValues: undefined; - baseColumn: never; - generated: undefined; - }, {}, {}>; - }; - dialect: "pg"; -}>; export declare const userSettings: import("drizzle-orm/pg-core").PgTableWithColumns<{ name: "user_settings"; schema: undefined; diff --git a/backend/dist/db/schema.d.ts.map b/backend/dist/db/schema.d.ts.map index 8de16e8..2f5aa0b 100644 --- a/backend/dist/db/schema.d.ts.map +++ b/backend/dist/db/schema.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/db/schema.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,KAAK;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAKhB,CAAC;AAEH,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAevB,CAAC;AAEF,eAAO,MAAM,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAQrB,CAAC;AAEH,eAAO,MAAM,KAAK;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAmBhB,CAAC;AAEH,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EASzB,CAAC;AAEH,eAAO,MAAM,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EASnB,CAAC;AAEH,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EASvB,CAAC"} \ No newline at end of file +{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/db/schema.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,QAAQ,+EAAgF,CAAC;AACtG,MAAM,MAAM,UAAU,GAAG,CAAC,OAAO,QAAQ,CAAC,CAAC,MAAM,CAAC,CAAC;AAEnD,eAAO,MAAM,KAAK;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAOhB,CAAC;AAEH,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAevB,CAAC;AAEF,eAAO,MAAM,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAQrB,CAAC;AAEH,eAAO,MAAM,KAAK;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAsBhB,CAAC;AAEH,eAAO,MAAM,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAYtB,CAAC;AAGF,eAAO,MAAM,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA2BlB,CAAC;AAEJ,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EASzB,CAAC;AAEH,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EASvB,CAAC"} \ No newline at end of file diff --git a/backend/dist/db/schema.js b/backend/dist/db/schema.js index 65fdc7a..25a520e 100644 --- a/backend/dist/db/schema.js +++ b/backend/dist/db/schema.js @@ -1,12 +1,15 @@ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -exports.userSettings = exports.subtasks = exports.repeatProfiles = exports.tasks = exports.categories = exports.friendships = exports.users = void 0; +exports.userSettings = exports.repeatProfiles = exports.subtasks = exports.tombstones = exports.tasks = exports.categories = exports.friendships = exports.users = exports.ENTITIES = void 0; const pg_core_1 = require("drizzle-orm/pg-core"); const drizzle_orm_1 = require("drizzle-orm"); +exports.ENTITIES = ['categories', 'tasks', 'subtasks', 'repeatProfiles', 'friendships']; exports.users = (0, pg_core_1.pgTable)('users', { id: (0, pg_core_1.text)('id').primaryKey(), username: (0, pg_core_1.text)('username').notNull().unique(), passwordHash: (0, pg_core_1.text)('password_hash').notNull(), + resetToken: (0, pg_core_1.text)('reset_token'), + resetTokenExpiry: (0, pg_core_1.bigint)('reset_token_expiry', { mode: 'number' }), createdAt: (0, pg_core_1.bigint)('created_at', { mode: 'number' }).notNull().default((0, drizzle_orm_1.sql) `EXTRACT(EPOCH FROM NOW()) * 1000`), }); exports.friendships = (0, pg_core_1.pgTable)('friendships', { @@ -33,7 +36,7 @@ exports.categories = (0, pg_core_1.pgTable)('categories', { exports.tasks = (0, pg_core_1.pgTable)('tasks', { id: (0, pg_core_1.text)('id').primaryKey(), userId: (0, pg_core_1.text)('user_id').notNull().references(() => exports.users.id, { onDelete: 'cascade' }), - categoryId: (0, pg_core_1.text)('category_id').notNull().references(() => exports.categories.id, { onDelete: 'cascade' }), + categoryId: (0, pg_core_1.text)('category_id').references(() => exports.categories.id, { onDelete: 'cascade' }), title: (0, pg_core_1.text)('title').notNull(), description: (0, pg_core_1.text)('description').notNull().default(''), priority: (0, pg_core_1.text)('priority', { enum: ['none', 'low', 'medium', 'high', 'critical'] }).notNull().default('none'), @@ -41,15 +44,56 @@ exports.tasks = (0, pg_core_1.pgTable)('tasks', { dueDate: (0, pg_core_1.bigint)('due_date', { mode: 'number' }).notNull().default(0), dueTime: (0, pg_core_1.text)('due_time').notNull().default(''), endTime: (0, pg_core_1.text)('end_time').notNull().default(''), + allDay: (0, pg_core_1.boolean)('all_day').notNull().default(false), repeat: (0, pg_core_1.text)('repeat', { enum: ['none', 'daily', 'weekly', 'monthly', 'custom'] }).notNull().default('none'), repeatInterval: (0, pg_core_1.integer)('repeat_interval').notNull().default(1), repeatDays: (0, pg_core_1.text)('repeat_days').notNull().default(''), seriesId: (0, pg_core_1.text)('series_id').notNull().default(''), assigneeId: (0, pg_core_1.text)('assignee_id').references(() => exports.users.id, { onDelete: 'set null' }), reminder: (0, pg_core_1.text)('reminder', { enum: ['none', 'at_time', '15', '30', '60', '120', '1440'] }).notNull().default('none'), + reminders: (0, pg_core_1.text)('reminders').notNull().default(''), + completedAt: (0, pg_core_1.bigint)('completed_at', { mode: 'number' }), createdAt: (0, pg_core_1.bigint)('created_at', { mode: 'number' }).notNull().default((0, drizzle_orm_1.sql) `EXTRACT(EPOCH FROM NOW()) * 1000`), updatedAt: (0, pg_core_1.bigint)('updated_at', { mode: 'number' }).notNull().default((0, drizzle_orm_1.sql) `EXTRACT(EPOCH FROM NOW()) * 1000`), }); +exports.tombstones = (0, pg_core_1.pgTable)('tombstones', { + entity: (0, pg_core_1.text)('entity', { enum: exports.ENTITIES }).notNull(), + entityId: (0, pg_core_1.text)('entity_id').notNull(), + userId: (0, pg_core_1.text)('user_id').notNull().references(() => exports.users.id, { onDelete: 'cascade' }), + updatedAt: (0, pg_core_1.bigint)('updated_at', { mode: 'number' }).notNull(), +}, (t) => ({ + pk: (0, pg_core_1.primaryKey)({ columns: [t.entity, t.entityId] }), + userIdx: (0, pg_core_1.index)('tombstones_user_updated_idx').on(t.userId, t.updatedAt), +})); +// Define subtasks with explicit type to avoid circular reference +exports.subtasks = (0, pg_core_1.pgTable)('subtasks', { + id: (0, pg_core_1.text)('id').primaryKey(), + userId: (0, pg_core_1.text)('user_id').notNull().references(() => exports.users.id, { onDelete: 'cascade' }), + taskId: (0, pg_core_1.text)('task_id').notNull().references(() => exports.tasks.id, { onDelete: 'cascade' }), + parentSubtaskId: (0, pg_core_1.text)('parent_subtask_id').references(() => exports.subtasks.id, { onDelete: 'cascade' }), + title: (0, pg_core_1.text)('title').notNull(), + description: (0, pg_core_1.text)('description').notNull().default(''), + priority: (0, pg_core_1.text)('priority', { enum: ['none', 'low', 'medium', 'high', 'critical'] }).notNull().default('none'), + completed: (0, pg_core_1.boolean)('completed').notNull().default(false), + dueDate: (0, pg_core_1.bigint)('due_date', { mode: 'number' }).notNull().default(0), + dueTime: (0, pg_core_1.text)('due_time').notNull().default(''), + endTime: (0, pg_core_1.text)('end_time').notNull().default(''), + allDay: (0, pg_core_1.boolean)('all_day').notNull().default(false), + repeat: (0, pg_core_1.text)('repeat', { enum: ['none', 'daily', 'weekly', 'monthly', 'custom'] }).notNull().default('none'), + repeatInterval: (0, pg_core_1.integer)('repeat_interval').notNull().default(1), + repeatDays: (0, pg_core_1.text)('repeat_days').notNull().default(''), + seriesId: (0, pg_core_1.text)('series_id').notNull().default(''), + reminder: (0, pg_core_1.text)('reminder', { enum: ['none', 'at_time', '15', '30', '60', '120', '1440'] }).notNull().default('none'), + reminders: (0, pg_core_1.text)('reminders').notNull().default(''), + assigneeId: (0, pg_core_1.text)('assignee_id').references(() => exports.users.id, { onDelete: 'set null' }), + order: (0, pg_core_1.integer)('order').notNull().default(0), + createdAt: (0, pg_core_1.bigint)('created_at', { mode: 'number' }).notNull().default((0, drizzle_orm_1.sql) `EXTRACT(EPOCH FROM NOW()) * 1000`), + updatedAt: (0, pg_core_1.bigint)('updated_at', { mode: 'number' }).notNull().default((0, drizzle_orm_1.sql) `EXTRACT(EPOCH FROM NOW()) * 1000`), +}, (t) => ({ + parentIdx: (0, pg_core_1.index)('subtasks_parent_idx').on(t.parentSubtaskId), + taskIdx: (0, pg_core_1.index)('subtasks_task_idx').on(t.taskId), + userIdx: (0, pg_core_1.index)('subtasks_user_idx').on(t.userId), +})); exports.repeatProfiles = (0, pg_core_1.pgTable)('repeat_profiles', { id: (0, pg_core_1.text)('id').primaryKey(), userId: (0, pg_core_1.text)('user_id').notNull().references(() => exports.users.id, { onDelete: 'cascade' }), @@ -60,16 +104,6 @@ exports.repeatProfiles = (0, pg_core_1.pgTable)('repeat_profiles', { createdAt: (0, pg_core_1.bigint)('created_at', { mode: 'number' }).notNull().default((0, drizzle_orm_1.sql) `EXTRACT(EPOCH FROM NOW()) * 1000`), updatedAt: (0, pg_core_1.bigint)('updated_at', { mode: 'number' }).notNull().default((0, drizzle_orm_1.sql) `EXTRACT(EPOCH FROM NOW()) * 1000`), }); -exports.subtasks = (0, pg_core_1.pgTable)('subtasks', { - id: (0, pg_core_1.text)('id').primaryKey(), - userId: (0, pg_core_1.text)('user_id').notNull().references(() => exports.users.id, { onDelete: 'cascade' }), - taskId: (0, pg_core_1.text)('task_id').notNull().references(() => exports.tasks.id, { onDelete: 'cascade' }), - title: (0, pg_core_1.text)('title').notNull(), - completed: (0, pg_core_1.boolean)('completed').notNull().default(false), - order: (0, pg_core_1.integer)('order').notNull().default(0), - createdAt: (0, pg_core_1.bigint)('created_at', { mode: 'number' }).notNull().default((0, drizzle_orm_1.sql) `EXTRACT(EPOCH FROM NOW()) * 1000`), - updatedAt: (0, pg_core_1.bigint)('updated_at', { mode: 'number' }).notNull().default((0, drizzle_orm_1.sql) `EXTRACT(EPOCH FROM NOW()) * 1000`), -}); exports.userSettings = (0, pg_core_1.pgTable)('user_settings', { userId: (0, pg_core_1.text)('user_id').primaryKey().references(() => exports.users.id, { onDelete: 'cascade' }), darkMode: (0, pg_core_1.boolean)('dark_mode').notNull().default(false), diff --git a/backend/dist/db/schema.js.map b/backend/dist/db/schema.js.map index 8d8623a..611a9cd 100644 --- a/backend/dist/db/schema.js.map +++ b/backend/dist/db/schema.js.map @@ -1 +1 @@ -{"version":3,"file":"schema.js","sourceRoot":"","sources":["../../src/db/schema.ts"],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"schema.js","sourceRoot":"","sources":["../../src/db/schema.ts"],"names":[],"mappings":";;;AAAA,iDAAsH;AACtH,6CAAkC;AAErB,QAAA,QAAQ,GAAG,CAAC,YAAY,EAAE,OAAO,EAAE,UAAU,EAAE,gBAAgB,EAAE,aAAa,CAAU,CAAC;AAGzF,QAAA,KAAK,GAAG,IAAA,iBAAO,EAAC,OAAO,EAAE;IACpC,EAAE,EAAE,IAAA,cAAI,EAAC,IAAI,CAAC,CAAC,UAAU,EAAE;IAC3B,QAAQ,EAAE,IAAA,cAAI,EAAC,UAAU,CAAC,CAAC,OAAO,EAAE,CAAC,MAAM,EAAE;IAC7C,YAAY,EAAE,IAAA,cAAI,EAAC,eAAe,CAAC,CAAC,OAAO,EAAE;IAC7C,UAAU,EAAE,IAAA,cAAI,EAAC,aAAa,CAAC;IAC/B,gBAAgB,EAAE,IAAA,gBAAM,EAAC,oBAAoB,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;IAClE,SAAS,EAAE,IAAA,gBAAM,EAAC,YAAY,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,IAAA,iBAAG,EAAA,kCAAkC,CAAC;CAC7G,CAAC,CAAC;AAEU,QAAA,WAAW,GAAG,IAAA,iBAAO,EAChC,aAAa,EACb;IACE,EAAE,EAAE,IAAA,cAAI,EAAC,IAAI,CAAC,CAAC,UAAU,EAAE;IAC3B,MAAM,EAAE,IAAA,cAAI,EAAC,SAAS,CAAC,CAAC,OAAO,EAAE,CAAC,UAAU,CAAC,GAAG,EAAE,CAAC,aAAK,CAAC,EAAE,EAAE,EAAE,QAAQ,EAAE,SAAS,EAAE,CAAC;IACrF,QAAQ,EAAE,IAAA,cAAI,EAAC,WAAW,CAAC,CAAC,OAAO,EAAE,CAAC,UAAU,CAAC,GAAG,EAAE,CAAC,aAAK,CAAC,EAAE,EAAE,EAAE,QAAQ,EAAE,SAAS,EAAE,CAAC;IACzF,MAAM,EAAE,IAAA,cAAI,EAAC,QAAQ,EAAE,EAAE,IAAI,EAAE,CAAC,SAAS,EAAE,UAAU,CAAC,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,SAAS,CAAC;IACtF,SAAS,EAAE,IAAA,gBAAM,EAAC,YAAY,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,IAAA,iBAAG,EAAA,kCAAkC,CAAC;IAC5G,SAAS,EAAE,IAAA,gBAAM,EAAC,YAAY,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,IAAA,iBAAG,EAAA,kCAAkC,CAAC;CAC7G,EACD,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IACN,UAAU,EAAE,IAAA,gBAAM,EAAC,kBAAkB,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,QAAQ,CAAC;IAC/D,OAAO,EAAE,IAAA,eAAK,EAAC,sBAAsB,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC;IACnD,SAAS,EAAE,IAAA,eAAK,EAAC,wBAAwB,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC;CAC1D,CAAC,CACH,CAAC;AAEW,QAAA,UAAU,GAAG,IAAA,iBAAO,EAAC,YAAY,EAAE;IAC9C,EAAE,EAAE,IAAA,cAAI,EAAC,IAAI,CAAC,CAAC,UAAU,EAAE;IAC3B,MAAM,EAAE,IAAA,cAAI,EAAC,SAAS,CAAC,CAAC,OAAO,EAAE,CAAC,UAAU,CAAC,GAAG,EAAE,CAAC,aAAK,CAAC,EAAE,EAAE,EAAE,QAAQ,EAAE,SAAS,EAAE,CAAC;IACrF,IAAI,EAAE,IAAA,cAAI,EAAC,MAAM,CAAC,CAAC,OAAO,EAAE;IAC5B,KAAK,EAAE,IAAA,cAAI,EAAC,OAAO,CAAC,CAAC,OAAO,EAAE;IAC9B,KAAK,EAAE,IAAA,iBAAO,EAAC,OAAO,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC;IAC5C,SAAS,EAAE,IAAA,gBAAM,EAAC,YAAY,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,IAAA,iBAAG,EAAA,kCAAkC,CAAC;IAC5G,SAAS,EAAE,IAAA,gBAAM,EAAC,YAAY,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,IAAA,iBAAG,EAAA,kCAAkC,CAAC;CAC7G,CAAC,CAAC;AAEU,QAAA,KAAK,GAAG,IAAA,iBAAO,EAAC,OAAO,EAAE;IACpC,EAAE,EAAE,IAAA,cAAI,EAAC,IAAI,CAAC,CAAC,UAAU,EAAE;IAC3B,MAAM,EAAE,IAAA,cAAI,EAAC,SAAS,CAAC,CAAC,OAAO,EAAE,CAAC,UAAU,CAAC,GAAG,EAAE,CAAC,aAAK,CAAC,EAAE,EAAE,EAAE,QAAQ,EAAE,SAAS,EAAE,CAAC;IACrF,UAAU,EAAE,IAAA,cAAI,EAAC,aAAa,CAAC,CAAC,UAAU,CAAC,GAAG,EAAE,CAAC,kBAAU,CAAC,EAAE,EAAE,EAAE,QAAQ,EAAE,SAAS,EAAE,CAAC;IACxF,KAAK,EAAE,IAAA,cAAI,EAAC,OAAO,CAAC,CAAC,OAAO,EAAE;IAC9B,WAAW,EAAE,IAAA,cAAI,EAAC,aAAa,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC;IACtD,QAAQ,EAAE,IAAA,cAAI,EAAC,UAAU,EAAE,EAAE,IAAI,EAAE,CAAC,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC;IAC7G,SAAS,EAAE,IAAA,iBAAO,EAAC,WAAW,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC;IACxD,OAAO,EAAE,IAAA,gBAAM,EAAC,UAAU,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC;IACpE,OAAO,EAAE,IAAA,cAAI,EAAC,UAAU,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC;IAC/C,OAAO,EAAE,IAAA,cAAI,EAAC,UAAU,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC;IAC/C,MAAM,EAAE,IAAA,iBAAO,EAAC,SAAS,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC;IACnD,MAAM,EAAE,IAAA,cAAI,EAAC,QAAQ,EAAE,EAAE,IAAI,EAAE,CAAC,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,QAAQ,CAAC,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC;IAC5G,cAAc,EAAE,IAAA,iBAAO,EAAC,iBAAiB,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC;IAC/D,UAAU,EAAE,IAAA,cAAI,EAAC,aAAa,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC;IACrD,QAAQ,EAAE,IAAA,cAAI,EAAC,WAAW,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC;IACjD,UAAU,EAAE,IAAA,cAAI,EAAC,aAAa,CAAC,CAAC,UAAU,CAAC,GAAG,EAAE,CAAC,aAAK,CAAC,EAAE,EAAE,EAAE,QAAQ,EAAE,UAAU,EAAE,CAAC;IACpF,QAAQ,EAAE,IAAA,cAAI,EAAC,UAAU,EAAE,EAAE,IAAI,EAAE,CAAC,MAAM,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,CAAC,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC;IACpH,SAAS,EAAE,IAAA,cAAI,EAAC,WAAW,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC;IAClD,WAAW,EAAE,IAAA,gBAAM,EAAC,cAAc,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;IACvD,SAAS,EAAE,IAAA,gBAAM,EAAC,YAAY,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,IAAA,iBAAG,EAAA,kCAAkC,CAAC;IAC5G,SAAS,EAAE,IAAA,gBAAM,EAAC,YAAY,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,IAAA,iBAAG,EAAA,kCAAkC,CAAC;CAC7G,CAAC,CAAC;AAEU,QAAA,UAAU,GAAG,IAAA,iBAAO,EAC/B,YAAY,EACZ;IACE,MAAM,EAAE,IAAA,cAAI,EAAC,QAAQ,EAAE,EAAE,IAAI,EAAE,gBAAQ,EAAE,CAAC,CAAC,OAAO,EAAE;IACpD,QAAQ,EAAE,IAAA,cAAI,EAAC,WAAW,CAAC,CAAC,OAAO,EAAE;IACrC,MAAM,EAAE,IAAA,cAAI,EAAC,SAAS,CAAC,CAAC,OAAO,EAAE,CAAC,UAAU,CAAC,GAAG,EAAE,CAAC,aAAK,CAAC,EAAE,EAAE,EAAE,QAAQ,EAAE,SAAS,EAAE,CAAC;IACrF,SAAS,EAAE,IAAA,gBAAM,EAAC,YAAY,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAC,OAAO,EAAE;CAC9D,EACD,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IACN,EAAE,EAAE,IAAA,oBAAU,EAAC,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC;IACnD,OAAO,EAAE,IAAA,eAAK,EAAC,6BAA6B,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,SAAS,CAAC;CACxE,CAAC,CACH,CAAC;AAEF,iEAAiE;AACpD,QAAA,QAAQ,GAAG,IAAA,iBAAO,EAAC,UAAU,EAAE;IAC1C,EAAE,EAAE,IAAA,cAAI,EAAC,IAAI,CAAC,CAAC,UAAU,EAAE;IAC3B,MAAM,EAAE,IAAA,cAAI,EAAC,SAAS,CAAC,CAAC,OAAO,EAAE,CAAC,UAAU,CAAC,GAAG,EAAE,CAAC,aAAK,CAAC,EAAE,EAAE,EAAE,QAAQ,EAAE,SAAS,EAAE,CAAC;IACrF,MAAM,EAAE,IAAA,cAAI,EAAC,SAAS,CAAC,CAAC,OAAO,EAAE,CAAC,UAAU,CAAC,GAAG,EAAE,CAAC,aAAK,CAAC,EAAE,EAAE,EAAE,QAAQ,EAAE,SAAS,EAAE,CAAC;IACrF,eAAe,EAAE,IAAA,cAAI,EAAC,mBAAmB,CAAC,CAAC,UAAU,CAAC,GAAgB,EAAE,CAAC,gBAAQ,CAAC,EAAE,EAAE,EAAE,QAAQ,EAAE,SAAS,EAAE,CAAC;IAC9G,KAAK,EAAE,IAAA,cAAI,EAAC,OAAO,CAAC,CAAC,OAAO,EAAE;IAC9B,WAAW,EAAE,IAAA,cAAI,EAAC,aAAa,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC;IACtD,QAAQ,EAAE,IAAA,cAAI,EAAC,UAAU,EAAE,EAAE,IAAI,EAAE,CAAC,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC;IAC7G,SAAS,EAAE,IAAA,iBAAO,EAAC,WAAW,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC;IACxD,OAAO,EAAE,IAAA,gBAAM,EAAC,UAAU,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC;IACpE,OAAO,EAAE,IAAA,cAAI,EAAC,UAAU,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC;IAC/C,OAAO,EAAE,IAAA,cAAI,EAAC,UAAU,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC;IAC/C,MAAM,EAAE,IAAA,iBAAO,EAAC,SAAS,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC;IACnD,MAAM,EAAE,IAAA,cAAI,EAAC,QAAQ,EAAE,EAAE,IAAI,EAAE,CAAC,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,QAAQ,CAAC,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC;IAC5G,cAAc,EAAE,IAAA,iBAAO,EAAC,iBAAiB,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC;IAC/D,UAAU,EAAE,IAAA,cAAI,EAAC,aAAa,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC;IACrD,QAAQ,EAAE,IAAA,cAAI,EAAC,WAAW,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC;IACjD,QAAQ,EAAE,IAAA,cAAI,EAAC,UAAU,EAAE,EAAE,IAAI,EAAE,CAAC,MAAM,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,CAAC,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC;IACpH,SAAS,EAAE,IAAA,cAAI,EAAC,WAAW,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC;IAClD,UAAU,EAAE,IAAA,cAAI,EAAC,aAAa,CAAC,CAAC,UAAU,CAAC,GAAG,EAAE,CAAC,aAAK,CAAC,EAAE,EAAE,EAAE,QAAQ,EAAE,UAAU,EAAE,CAAC;IACpF,KAAK,EAAE,IAAA,iBAAO,EAAC,OAAO,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC;IAC5C,SAAS,EAAE,IAAA,gBAAM,EAAC,YAAY,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,IAAA,iBAAG,EAAA,kCAAkC,CAAC;IAC5G,SAAS,EAAE,IAAA,gBAAM,EAAC,YAAY,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,IAAA,iBAAG,EAAA,kCAAkC,CAAC;CAC7G,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IACT,SAAS,EAAE,IAAA,eAAK,EAAC,qBAAqB,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,eAAe,CAAC;IAC7D,OAAO,EAAE,IAAA,eAAK,EAAC,mBAAmB,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC;IAChD,OAAO,EAAE,IAAA,eAAK,EAAC,mBAAmB,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC;CACjD,CAAC,CAAC,CAAC;AAES,QAAA,cAAc,GAAG,IAAA,iBAAO,EAAC,iBAAiB,EAAE;IACvD,EAAE,EAAE,IAAA,cAAI,EAAC,IAAI,CAAC,CAAC,UAAU,EAAE;IAC3B,MAAM,EAAE,IAAA,cAAI,EAAC,SAAS,CAAC,CAAC,OAAO,EAAE,CAAC,UAAU,CAAC,GAAG,EAAE,CAAC,aAAK,CAAC,EAAE,EAAE,EAAE,QAAQ,EAAE,SAAS,EAAE,CAAC;IACrF,IAAI,EAAE,IAAA,cAAI,EAAC,MAAM,CAAC,CAAC,OAAO,EAAE;IAC5B,MAAM,EAAE,IAAA,cAAI,EAAC,QAAQ,EAAE,EAAE,IAAI,EAAE,CAAC,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,QAAQ,CAAC,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC;IAC5G,cAAc,EAAE,IAAA,iBAAO,EAAC,iBAAiB,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC;IAC/D,UAAU,EAAE,IAAA,cAAI,EAAC,aAAa,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC;IACrD,SAAS,EAAE,IAAA,gBAAM,EAAC,YAAY,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,IAAA,iBAAG,EAAA,kCAAkC,CAAC;IAC5G,SAAS,EAAE,IAAA,gBAAM,EAAC,YAAY,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,IAAA,iBAAG,EAAA,kCAAkC,CAAC;CAC7G,CAAC,CAAC;AAEU,QAAA,YAAY,GAAG,IAAA,iBAAO,EAAC,eAAe,EAAE;IACnD,MAAM,EAAE,IAAA,cAAI,EAAC,SAAS,CAAC,CAAC,UAAU,EAAE,CAAC,UAAU,CAAC,GAAG,EAAE,CAAC,aAAK,CAAC,EAAE,EAAE,EAAE,QAAQ,EAAE,SAAS,EAAE,CAAC;IACxF,QAAQ,EAAE,IAAA,iBAAO,EAAC,WAAW,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC;IACvD,aAAa,EAAE,IAAA,iBAAO,EAAC,eAAe,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC;IAC/D,YAAY,EAAE,IAAA,cAAI,EAAC,eAAe,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC;IAC9D,eAAe,EAAE,IAAA,cAAI,EAAC,kBAAkB,CAAC;IACzC,MAAM,EAAE,IAAA,cAAI,EAAC,SAAS,EAAE,EAAE,IAAI,EAAE,CAAC,SAAS,EAAE,UAAU,EAAE,OAAO,EAAE,WAAW,CAAC,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,SAAS,CAAC;IAC7G,SAAS,EAAE,IAAA,cAAI,EAAC,YAAY,EAAE,EAAE,IAAI,EAAE,CAAC,KAAK,EAAE,MAAM,CAAC,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC;IACjF,SAAS,EAAE,IAAA,gBAAM,EAAC,YAAY,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,IAAA,iBAAG,EAAA,kCAAkC,CAAC;CAC7G,CAAC,CAAC"} \ No newline at end of file diff --git a/backend/dist/index.d.ts.map b/backend/dist/index.d.ts.map index 9ded81d..e765888 100644 --- a/backend/dist/index.d.ts.map +++ b/backend/dist/index.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,eAAe,CAAC;AAYvB,QAAA,MAAM,GAAG,6CAAY,CAAC;AAkCtB,eAAe,GAAG,CAAC"} \ No newline at end of file +{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,eAAe,CAAC;AAavB,QAAA,MAAM,GAAG,6CAAY,CAAC;AAmCtB,eAAe,GAAG,CAAC"} \ No newline at end of file diff --git a/backend/dist/index.js b/backend/dist/index.js index 4876945..6e3f6e2 100644 --- a/backend/dist/index.js +++ b/backend/dist/index.js @@ -12,6 +12,7 @@ const tasks_1 = __importDefault(require("./routes/tasks")); const subtasks_1 = __importDefault(require("./routes/subtasks")); const users_1 = __importDefault(require("./routes/users")); const friends_1 = __importDefault(require("./routes/friends")); +const repeatProfiles_1 = __importDefault(require("./routes/repeatProfiles")); const sync_1 = __importDefault(require("./routes/sync")); const errorHandler_1 = require("./middleware/errorHandler"); const app = (0, express_1.default)(); @@ -31,6 +32,7 @@ app.use('/api/categories', categories_1.default); app.use('/api/tasks', tasks_1.default); app.use('/api/subtasks', subtasks_1.default); app.use('/api/users', users_1.default); +app.use('/api/repeat-profiles', repeatProfiles_1.default); app.use('/api/sync', sync_1.default); app.use('/api/friends', friends_1.default); // 404 handler diff --git a/backend/dist/index.js.map b/backend/dist/index.js.map index 069d30f..44eb684 100644 --- a/backend/dist/index.js.map +++ b/backend/dist/index.js.map @@ -1 +1 @@ -{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;AAAA,yBAAuB;AACvB,sDAA8B;AAC9B,gDAAwB;AACxB,yDAAuC;AACvC,qEAAiD;AACjD,2DAAwC;AACxC,iEAA8C;AAC9C,2DAAwC;AACxC,+DAA4C;AAC5C,yDAAuC;AACvC,4DAA0E;AAE1E,MAAM,GAAG,GAAG,IAAA,iBAAO,GAAE,CAAC;AACtB,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,IAAI,IAAI,CAAC;AAEtC,GAAG,CAAC,GAAG,CAAC,IAAA,cAAI,EAAC;IACX,MAAM,EAAE,IAAI;IACZ,WAAW,EAAE,IAAI;CAClB,CAAC,CAAC,CAAC;AACJ,GAAG,CAAC,GAAG,CAAC,iBAAO,CAAC,IAAI,EAAE,CAAC,CAAC;AAExB,eAAe;AACf,GAAG,CAAC,GAAG,CAAC,SAAS,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE;IAC9B,GAAG,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;AACpD,CAAC,CAAC,CAAC;AAEH,aAAa;AACb,GAAG,CAAC,GAAG,CAAC,WAAW,EAAE,cAAU,CAAC,CAAC;AACjC,GAAG,CAAC,GAAG,CAAC,iBAAiB,EAAE,oBAAc,CAAC,CAAC;AAC3C,GAAG,CAAC,GAAG,CAAC,YAAY,EAAE,eAAU,CAAC,CAAC;AAClC,GAAG,CAAC,GAAG,CAAC,eAAe,EAAE,kBAAa,CAAC,CAAC;AACxC,GAAG,CAAC,GAAG,CAAC,YAAY,EAAE,eAAU,CAAC,CAAC;AAClC,GAAG,CAAC,GAAG,CAAC,WAAW,EAAE,cAAU,CAAC,CAAC;AACjC,GAAG,CAAC,GAAG,CAAC,cAAc,EAAE,iBAAY,CAAC,CAAC;AAEtC,cAAc;AACd,GAAG,CAAC,GAAG,CAAC,8BAAe,CAAC,CAAC;AAEzB,gBAAgB;AAChB,GAAG,CAAC,GAAG,CAAC,2BAAY,CAAC,CAAC;AAEtB,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,GAAG,EAAE;IACpB,OAAO,CAAC,GAAG,CAAC,yCAAyC,IAAI,EAAE,CAAC,CAAC;IAC7D,OAAO,CAAC,GAAG,CAAC,wCAAwC,IAAI,MAAM,CAAC,CAAC;AAClE,CAAC,CAAC,CAAC;AAEH,kBAAe,GAAG,CAAC"} \ No newline at end of file +{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;AAAA,yBAAuB;AACvB,sDAA8B;AAC9B,gDAAwB;AACxB,yDAAuC;AACvC,qEAAiD;AACjD,2DAAwC;AACxC,iEAA8C;AAC9C,2DAAwC;AACxC,+DAA4C;AAC5C,6EAA0D;AAC1D,yDAAuC;AACvC,4DAA0E;AAE1E,MAAM,GAAG,GAAG,IAAA,iBAAO,GAAE,CAAC;AACtB,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,IAAI,IAAI,CAAC;AAEtC,GAAG,CAAC,GAAG,CAAC,IAAA,cAAI,EAAC;IACX,MAAM,EAAE,IAAI;IACZ,WAAW,EAAE,IAAI;CAClB,CAAC,CAAC,CAAC;AACJ,GAAG,CAAC,GAAG,CAAC,iBAAO,CAAC,IAAI,EAAE,CAAC,CAAC;AAExB,eAAe;AACf,GAAG,CAAC,GAAG,CAAC,SAAS,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE;IAC9B,GAAG,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;AACpD,CAAC,CAAC,CAAC;AAEH,aAAa;AACb,GAAG,CAAC,GAAG,CAAC,WAAW,EAAE,cAAU,CAAC,CAAC;AACjC,GAAG,CAAC,GAAG,CAAC,iBAAiB,EAAE,oBAAc,CAAC,CAAC;AAC3C,GAAG,CAAC,GAAG,CAAC,YAAY,EAAE,eAAU,CAAC,CAAC;AAClC,GAAG,CAAC,GAAG,CAAC,eAAe,EAAE,kBAAa,CAAC,CAAC;AACxC,GAAG,CAAC,GAAG,CAAC,YAAY,EAAE,eAAU,CAAC,CAAC;AAClC,GAAG,CAAC,GAAG,CAAC,sBAAsB,EAAE,wBAAmB,CAAC,CAAC;AACrD,GAAG,CAAC,GAAG,CAAC,WAAW,EAAE,cAAU,CAAC,CAAC;AACjC,GAAG,CAAC,GAAG,CAAC,cAAc,EAAE,iBAAY,CAAC,CAAC;AAEtC,cAAc;AACd,GAAG,CAAC,GAAG,CAAC,8BAAe,CAAC,CAAC;AAEzB,gBAAgB;AAChB,GAAG,CAAC,GAAG,CAAC,2BAAY,CAAC,CAAC;AAEtB,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,GAAG,EAAE;IACpB,OAAO,CAAC,GAAG,CAAC,yCAAyC,IAAI,EAAE,CAAC,CAAC;IAC7D,OAAO,CAAC,GAAG,CAAC,wCAAwC,IAAI,MAAM,CAAC,CAAC;AAClE,CAAC,CAAC,CAAC;AAEH,kBAAe,GAAG,CAAC"} \ No newline at end of file diff --git a/backend/dist/routes/auth.d.ts.map b/backend/dist/routes/auth.d.ts.map index a864a5d..198a8f6 100644 --- a/backend/dist/routes/auth.d.ts.map +++ b/backend/dist/routes/auth.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/routes/auth.ts"],"names":[],"mappings":"AASA,QAAA,MAAM,MAAM,4CAAW,CAAC;AAmFxB,eAAe,MAAM,CAAC"} \ No newline at end of file +{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/routes/auth.ts"],"names":[],"mappings":"AAYA,QAAA,MAAM,MAAM,4CAAW,CAAC;AA0LxB,eAAe,MAAM,CAAC"} \ No newline at end of file diff --git a/backend/dist/routes/auth.js b/backend/dist/routes/auth.js index dd52d53..5160af0 100644 --- a/backend/dist/routes/auth.js +++ b/backend/dist/routes/auth.js @@ -1,4 +1,7 @@ "use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); const express_1 = require("express"); const asyncHandler_1 = require("../utils/asyncHandler"); @@ -8,7 +11,47 @@ const drizzle_orm_1 = require("drizzle-orm"); const auth_1 = require("../utils/auth"); const errorHandler_1 = require("../middleware/errorHandler"); const zod_1 = require("zod"); +const bcryptjs_1 = __importDefault(require("bcryptjs")); +const express_rate_limit_1 = __importDefault(require("express-rate-limit")); +const crypto_1 = __importDefault(require("crypto")); const router = (0, express_1.Router)(); +// Rate limiting for auth endpoints +const authLimiter = (0, express_rate_limit_1.default)({ + windowMs: 15 * 60 * 1000, // 15 minutes + max: 10, // limit each IP to 10 requests per windowMs + message: { + error: { + code: 'RATE_LIMITED', + message: 'Too many attempts. Please try again later.', + }, + }, + standardHeaders: true, + legacyHeaders: false, +}); +const loginLimiter = (0, express_rate_limit_1.default)({ + windowMs: 15 * 60 * 1000, // 15 minutes + max: 5, // stricter limit for login + message: { + error: { + code: 'RATE_LIMITED', + message: 'Too many login attempts. Please try again later.', + }, + }, + standardHeaders: true, + legacyHeaders: false, +}); +const passwordResetLimiter = (0, express_rate_limit_1.default)({ + windowMs: 60 * 60 * 1000, // 1 hour + max: 3, // very strict for password reset + message: { + error: { + code: 'RATE_LIMITED', + message: 'Too many password reset requests. Please try again later.', + }, + }, + standardHeaders: true, + legacyHeaders: false, +}); const usernameSchema = zod_1.z .string() .min(3) @@ -22,15 +65,24 @@ const loginSchema = zod_1.z.object({ username: zod_1.z.string(), password: zod_1.z.string(), }); -// In production, use bcrypt or argon2 for password hashing -function hashPassword(password) { - // Simple hash for demo - replace with bcrypt in production - return Buffer.from(password).toString('base64'); +const forgotPasswordSchema = zod_1.z.object({ + username: zod_1.z.string().min(1), +}); +const resetPasswordSchema = zod_1.z.object({ + token: zod_1.z.string().min(1), + password: zod_1.z.string().min(8), +}); +const BCRYPT_ROUNDS = 12; +async function hashPassword(password) { + return bcryptjs_1.default.hash(password, BCRYPT_ROUNDS); } -function verifyPassword(password, hash) { - return hashPassword(password) === hash; +async function verifyPassword(password, hash) { + return bcryptjs_1.default.compare(password, hash); } -router.post('/register', (0, asyncHandler_1.asyncHandler)(async (req, res) => { +function generateResetToken() { + return crypto_1.default.randomBytes(32).toString('hex'); +} +router.post('/register', authLimiter, (0, asyncHandler_1.asyncHandler)(async (req, res) => { const data = registerSchema.parse(req.body); const existing = await db_1.db.select().from(schema_1.users).where((0, drizzle_orm_1.eq)(schema_1.users.username, data.username)).limit(1); if (existing.length > 0) { @@ -41,7 +93,7 @@ router.post('/register', (0, asyncHandler_1.asyncHandler)(async (req, res) => { await db_1.db.insert(schema_1.users).values({ id: userId, username: data.username, - passwordHash: hashPassword(data.password), + passwordHash: await hashPassword(data.password), createdAt: now, }); const token = (0, auth_1.generateToken)({ userId, username: data.username }); @@ -50,7 +102,7 @@ router.post('/register', (0, asyncHandler_1.asyncHandler)(async (req, res) => { token, }); })); -router.post('/login', (0, asyncHandler_1.asyncHandler)(async (req, res) => { +router.post('/login', loginLimiter, (0, asyncHandler_1.asyncHandler)(async (req, res) => { const data = loginSchema.parse(req.body); const user = await db_1.db.select().from(schema_1.users).where((0, drizzle_orm_1.eq)(schema_1.users.username, data.username)).limit(1); if (user.length === 0 || !verifyPassword(data.password, user[0].passwordHash)) { @@ -72,5 +124,45 @@ router.get('/me', (0, asyncHandler_1.asyncHandler)(async (req, res) => { } res.json({ id: user[0].id, username: user[0].username }); })); +router.post('/forgot-password', passwordResetLimiter, (0, asyncHandler_1.asyncHandler)(async (req, res) => { + const data = forgotPasswordSchema.parse(req.body); + const user = await db_1.db.select().from(schema_1.users).where((0, drizzle_orm_1.eq)(schema_1.users.username, data.username)).limit(1); + // Always return success to prevent username enumeration + if (user.length === 0) { + res.json({ success: true }); + return; + } + const resetToken = generateResetToken(); + const resetTokenExpiry = Date.now() + 3600000; // 1 hour + await db_1.db + .update(schema_1.users) + .set({ resetToken, resetTokenExpiry }) + .where((0, drizzle_orm_1.eq)(schema_1.users.id, user[0].id)); + // In production, send email with reset link + // For now, return token in response (dev only) + if (process.env.NODE_ENV !== 'production') { + res.json({ success: true, resetToken }); + } + else { + res.json({ success: true }); + } +})); +router.post('/reset-password', passwordResetLimiter, (0, asyncHandler_1.asyncHandler)(async (req, res) => { + const data = resetPasswordSchema.parse(req.body); + const user = await db_1.db + .select() + .from(schema_1.users) + .where((0, drizzle_orm_1.eq)(schema_1.users.resetToken, data.token)) + .limit(1); + if (user.length === 0 || !user[0].resetTokenExpiry || user[0].resetTokenExpiry < Date.now()) { + throw new errorHandler_1.AppError('INVALID_TOKEN', 'Invalid or expired reset token', 400); + } + const newPasswordHash = await hashPassword(data.password); + await db_1.db + .update(schema_1.users) + .set({ passwordHash: newPasswordHash, resetToken: null, resetTokenExpiry: null }) + .where((0, drizzle_orm_1.eq)(schema_1.users.id, user[0].id)); + res.json({ success: true }); +})); exports.default = router; //# sourceMappingURL=auth.js.map \ No newline at end of file diff --git a/backend/dist/routes/auth.js.map b/backend/dist/routes/auth.js.map index a5c22b7..cac9795 100644 --- a/backend/dist/routes/auth.js.map +++ b/backend/dist/routes/auth.js.map @@ -1 +1 @@ -{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/routes/auth.ts"],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/routes/auth.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/backend/dist/routes/friends.d.ts.map b/backend/dist/routes/friends.d.ts.map index 6938b5d..19ea64d 100644 --- a/backend/dist/routes/friends.d.ts.map +++ b/backend/dist/routes/friends.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"friends.d.ts","sourceRoot":"","sources":["../../src/routes/friends.ts"],"names":[],"mappings":"AAUA,QAAA,MAAM,MAAM,4CAAW,CAAC;AAsKxB,eAAe,MAAM,CAAC"} \ No newline at end of file +{"version":3,"file":"friends.d.ts","sourceRoot":"","sources":["../../src/routes/friends.ts"],"names":[],"mappings":"AAUA,QAAA,MAAM,MAAM,4CAAW,CAAC;AAuLxB,eAAe,MAAM,CAAC"} \ No newline at end of file diff --git a/backend/dist/routes/friends.js b/backend/dist/routes/friends.js index f4b4eb8..d38337a 100644 --- a/backend/dist/routes/friends.js +++ b/backend/dist/routes/friends.js @@ -53,6 +53,17 @@ router.get('/', (0, asyncHandler_1.asyncHandler)(async (req, res) => { })); res.json({ friends, incoming, outgoing }); })); +router.get('/search', (0, asyncHandler_1.asyncHandler)(async (req, res) => { + const { q } = validation_1.searchQuerySchema.parse(req.query); + const userId = req.user.userId; + const results = await db_1.db + .select({ id: schema_1.users.id, username: schema_1.users.username }) + .from(schema_1.users) + .where((0, drizzle_orm_1.and)((0, drizzle_orm_1.ne)(schema_1.users.id, userId), (0, drizzle_orm_1.ilike)(schema_1.users.username, `%${q}%`))) + .orderBy((0, drizzle_orm_1.asc)(schema_1.users.username)) + .limit(20); + res.json(results); +})); router.post('/requests', (0, asyncHandler_1.asyncHandler)(async (req, res) => { const { username } = validation_1.friendRequestSchema.parse(req.body); const userId = req.user.userId; diff --git a/backend/dist/routes/friends.js.map b/backend/dist/routes/friends.js.map index 98227b0..9ee5a83 100644 --- a/backend/dist/routes/friends.js.map +++ b/backend/dist/routes/friends.js.map @@ -1 +1 @@ -{"version":3,"file":"friends.js","sourceRoot":"","sources":["../../src/routes/friends.ts"],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"friends.js","sourceRoot":"","sources":["../../src/routes/friends.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/backend/dist/routes/repeatProfiles.d.ts b/backend/dist/routes/repeatProfiles.d.ts new file mode 100644 index 0000000..0178a36 --- /dev/null +++ b/backend/dist/routes/repeatProfiles.d.ts @@ -0,0 +1,3 @@ +declare const router: import("express-serve-static-core").Router; +export default router; +//# sourceMappingURL=repeatProfiles.d.ts.map \ No newline at end of file diff --git a/backend/dist/routes/repeatProfiles.d.ts.map b/backend/dist/routes/repeatProfiles.d.ts.map new file mode 100644 index 0000000..3737b5e --- /dev/null +++ b/backend/dist/routes/repeatProfiles.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"repeatProfiles.d.ts","sourceRoot":"","sources":["../../src/routes/repeatProfiles.ts"],"names":[],"mappings":"AAUA,QAAA,MAAM,MAAM,4CAAW,CAAC;AA+ExB,eAAe,MAAM,CAAC"} \ No newline at end of file diff --git a/backend/dist/routes/repeatProfiles.js b/backend/dist/routes/repeatProfiles.js new file mode 100644 index 0000000..ae1f4b7 --- /dev/null +++ b/backend/dist/routes/repeatProfiles.js @@ -0,0 +1,74 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const express_1 = require("express"); +const asyncHandler_1 = require("../utils/asyncHandler"); +const db_1 = require("../db"); +const schema_1 = require("../db/schema"); +const drizzle_orm_1 = require("drizzle-orm"); +const auth_1 = require("../utils/auth"); +const auth_2 = require("../utils/auth"); +const errorHandler_1 = require("../middleware/errorHandler"); +const validation_1 = require("../utils/validation"); +const router = (0, express_1.Router)(); +router.use(auth_1.authMiddleware); +router.get('/', (0, asyncHandler_1.asyncHandler)(async (req, res) => { + const profiles = await db_1.db + .select() + .from(schema_1.repeatProfiles) + .where((0, drizzle_orm_1.eq)(schema_1.repeatProfiles.userId, req.user.userId)) + .orderBy((0, drizzle_orm_1.asc)(schema_1.repeatProfiles.createdAt)); + res.json({ profiles }); +})); +router.post('/', (0, asyncHandler_1.asyncHandler)(async (req, res) => { + const data = validation_1.repeatProfileSchema.parse(req.body); + const userId = req.user.userId; + const now = Date.now(); + const newProfile = { + id: (0, auth_2.generateId)('rp'), + userId, + name: data.name, + repeat: data.repeat, + repeatInterval: data.repeatInterval, + repeatDays: data.repeatDays, + createdAt: now, + updatedAt: now, + }; + await db_1.db.insert(schema_1.repeatProfiles).values(newProfile); + res.status(201).json(newProfile); +})); +router.patch('/:id', (0, asyncHandler_1.asyncHandler)(async (req, res) => { + const data = validation_1.repeatProfileUpdateSchema.parse(req.body); + const userId = req.user.userId; + const existing = await db_1.db + .select() + .from(schema_1.repeatProfiles) + .where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(schema_1.repeatProfiles.id, req.params.id), (0, drizzle_orm_1.eq)(schema_1.repeatProfiles.userId, userId))) + .limit(1); + if (existing.length === 0) { + throw new errorHandler_1.AppError('NOT_FOUND', 'Repeat profile not found', 404); + } + const now = Date.now(); + const updated = await db_1.db + .update(schema_1.repeatProfiles) + .set({ ...data, updatedAt: now }) + .where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(schema_1.repeatProfiles.id, req.params.id), (0, drizzle_orm_1.eq)(schema_1.repeatProfiles.userId, userId))) + .returning(); + res.json(updated[0]); +})); +router.delete('/:id', (0, asyncHandler_1.asyncHandler)(async (req, res) => { + const userId = req.user.userId; + const existing = await db_1.db + .select() + .from(schema_1.repeatProfiles) + .where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(schema_1.repeatProfiles.id, req.params.id), (0, drizzle_orm_1.eq)(schema_1.repeatProfiles.userId, userId))) + .limit(1); + if (existing.length === 0) { + throw new errorHandler_1.AppError('NOT_FOUND', 'Repeat profile not found', 404); + } + await db_1.db + .delete(schema_1.repeatProfiles) + .where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(schema_1.repeatProfiles.id, req.params.id), (0, drizzle_orm_1.eq)(schema_1.repeatProfiles.userId, userId))); + res.status(204).send(); +})); +exports.default = router; +//# sourceMappingURL=repeatProfiles.js.map \ No newline at end of file diff --git a/backend/dist/routes/repeatProfiles.js.map b/backend/dist/routes/repeatProfiles.js.map new file mode 100644 index 0000000..6b9b895 --- /dev/null +++ b/backend/dist/routes/repeatProfiles.js.map @@ -0,0 +1 @@ +{"version":3,"file":"repeatProfiles.js","sourceRoot":"","sources":["../../src/routes/repeatProfiles.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/backend/dist/routes/subtasks.d.ts.map b/backend/dist/routes/subtasks.d.ts.map index 3b14960..e46792d 100644 --- a/backend/dist/routes/subtasks.d.ts.map +++ b/backend/dist/routes/subtasks.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"subtasks.d.ts","sourceRoot":"","sources":["../../src/routes/subtasks.ts"],"names":[],"mappings":"AASA,QAAA,MAAM,MAAM,4CAAW,CAAC;AAmIxB,eAAe,MAAM,CAAC"} \ No newline at end of file +{"version":3,"file":"subtasks.d.ts","sourceRoot":"","sources":["../../src/routes/subtasks.ts"],"names":[],"mappings":"AASA,QAAA,MAAM,MAAM,4CAAW,CAAC;AAoKxB,eAAe,MAAM,CAAC"} \ No newline at end of file diff --git a/backend/dist/routes/subtasks.js b/backend/dist/routes/subtasks.js index 6b0b6f6..a175dee 100644 --- a/backend/dist/routes/subtasks.js +++ b/backend/dist/routes/subtasks.js @@ -10,6 +10,16 @@ const errorHandler_1 = require("../middleware/errorHandler"); const validation_1 = require("../utils/validation"); const router = (0, express_1.Router)(); router.use(auth_1.authMiddleware); +// Helper to build nested subtask tree +const buildSubtaskTree = (allSubtasks, parentId = null) => { + return allSubtasks + .filter((s) => s.parentSubtaskId === parentId) + .sort((a, b) => a.order - b.order) + .map((s) => ({ + ...s, + subtasks: buildSubtaskTree(allSubtasks, s.id), + })); +}; router.get('/task/:taskId', (0, asyncHandler_1.asyncHandler)(async (req, res) => { const userId = req.user.userId; // Verify task exists and belongs to user @@ -26,7 +36,8 @@ router.get('/task/:taskId', (0, asyncHandler_1.asyncHandler)(async (req, res) => .from(schema_1.subtasks) .where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(schema_1.subtasks.taskId, req.params.taskId), (0, drizzle_orm_1.eq)(schema_1.subtasks.userId, userId))) .orderBy((0, drizzle_orm_1.asc)(schema_1.subtasks.order)); - res.json({ subtasks: taskSubtasks }); + const nestedSubtasks = buildSubtaskTree(taskSubtasks); + res.json({ subtasks: nestedSubtasks }); })); router.post('/task/:taskId', (0, asyncHandler_1.asyncHandler)(async (req, res) => { const data = validation_1.subtaskCreateSchema.parse(req.body); @@ -40,10 +51,11 @@ router.post('/task/:taskId', (0, asyncHandler_1.asyncHandler)(async (req, res) = throw new errorHandler_1.AppError('NOT_FOUND', 'Task not found', 404); } const now = Date.now(); + const parentSubtaskId = data.parentSubtaskId || null; const maxOrder = await db_1.db .select({ order: schema_1.subtasks.order }) .from(schema_1.subtasks) - .where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(schema_1.subtasks.taskId, req.params.taskId), (0, drizzle_orm_1.eq)(schema_1.subtasks.userId, userId))) + .where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(schema_1.subtasks.taskId, req.params.taskId), (0, drizzle_orm_1.eq)(schema_1.subtasks.userId, userId), parentSubtaskId ? (0, drizzle_orm_1.eq)(schema_1.subtasks.parentSubtaskId, parentSubtaskId) : (0, drizzle_orm_1.isNull)(schema_1.subtasks.parentSubtaskId))) .orderBy((0, drizzle_orm_1.desc)(schema_1.subtasks.order)) .limit(1); const subtaskId = `sub_${Date.now().toString(36)}${Math.random().toString(36).slice(2, 6)}`; @@ -51,8 +63,22 @@ router.post('/task/:taskId', (0, asyncHandler_1.asyncHandler)(async (req, res) = id: subtaskId, userId, taskId: req.params.taskId, + parentSubtaskId, title: data.title, + description: data.description ?? '', + priority: data.priority ?? 'none', completed: false, + dueDate: data.dueDate ?? 0, + dueTime: data.dueTime ?? '', + endTime: data.endTime ?? '', + allDay: data.allDay ?? false, + repeat: data.repeat ?? 'none', + repeatInterval: data.repeatInterval ?? 1, + repeatDays: data.repeatDays ?? '', + seriesId: data.seriesId ?? '', + reminder: data.reminder ?? 'none', + reminders: data.reminders ?? '', + assigneeId: data.assigneeId ?? null, order: data.order ?? (maxOrder[0]?.order ?? -1) + 1, createdAt: now, updatedAt: now, diff --git a/backend/dist/routes/subtasks.js.map b/backend/dist/routes/subtasks.js.map index 263d232..f87ff41 100644 --- a/backend/dist/routes/subtasks.js.map +++ b/backend/dist/routes/subtasks.js.map @@ -1 +1 @@ -{"version":3,"file":"subtasks.js","sourceRoot":"","sources":["../../src/routes/subtasks.ts"],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"subtasks.js","sourceRoot":"","sources":["../../src/routes/subtasks.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/backend/dist/routes/sync.d.ts.map b/backend/dist/routes/sync.d.ts.map index a3538a2..1956dde 100644 --- a/backend/dist/routes/sync.d.ts.map +++ b/backend/dist/routes/sync.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"sync.d.ts","sourceRoot":"","sources":["../../src/routes/sync.ts"],"names":[],"mappings":"AASA,QAAA,MAAM,MAAM,4CAAW,CAAC;AA8SxB,eAAe,MAAM,CAAC"} \ No newline at end of file +{"version":3,"file":"sync.d.ts","sourceRoot":"","sources":["../../src/routes/sync.ts"],"names":[],"mappings":"AASA,QAAA,MAAM,MAAM,4CAAW,CAAC;AA2lBxB,eAAe,MAAM,CAAC"} \ No newline at end of file diff --git a/backend/dist/routes/sync.js b/backend/dist/routes/sync.js index dd97cb8..7f106e1 100644 --- a/backend/dist/routes/sync.js +++ b/backend/dist/routes/sync.js @@ -27,9 +27,8 @@ router.get('/', (0, asyncHandler_1.asyncHandler)(async (req, res) => { .where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(schema_1.tasks.userId, userId), (0, drizzle_orm_1.gte)(schema_1.tasks.updatedAt, sinceDate))) .orderBy((0, drizzle_orm_1.asc)(schema_1.tasks.updatedAt)); // Fetch subtasks changed since timestamp - const taskIds = changedTasks.map(t => t.id); let changedSubtasks = []; - if (taskIds.length > 0) { + if (changedTasks.length > 0) { changedSubtasks = await db_1.db .select() .from(schema_1.subtasks) @@ -37,7 +36,7 @@ router.get('/', (0, asyncHandler_1.asyncHandler)(async (req, res) => { .orderBy((0, drizzle_orm_1.asc)(schema_1.subtasks.updatedAt)); } else { - // Also fetch subtasks for tasks that might have been deleted (we track by updatedAt) + // Also fetch subtasks for tasks that might have been deleted changedSubtasks = await db_1.db .select() .from(schema_1.subtasks) @@ -56,6 +55,12 @@ router.get('/', (0, asyncHandler_1.asyncHandler)(async (req, res) => { .from(schema_1.friendships) .where((0, drizzle_orm_1.and)((0, drizzle_orm_1.or)((0, drizzle_orm_1.eq)(schema_1.friendships.userId, userId), (0, drizzle_orm_1.eq)(schema_1.friendships.friendId, userId)), (0, drizzle_orm_1.gte)(schema_1.friendships.updatedAt, sinceDate))) .orderBy((0, drizzle_orm_1.asc)(schema_1.friendships.updatedAt)); + // Fetch tombstones (deletions) changed since timestamp + const changedTombstones = await db_1.db + .select() + .from(schema_1.tombstones) + .where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(schema_1.tombstones.userId, userId), (0, drizzle_orm_1.gte)(schema_1.tombstones.updatedAt, sinceDate))) + .orderBy((0, drizzle_orm_1.asc)(schema_1.tombstones.updatedAt)); const timestamp = Date.now(); res.json({ categories: changedCategories, @@ -63,6 +68,7 @@ router.get('/', (0, asyncHandler_1.asyncHandler)(async (req, res) => { subtasks: changedSubtasks, repeatProfiles: changedRepeatProfiles, friendships: changedFriendships, + deleted: changedTombstones.map((t) => ({ entity: t.entity, id: t.entityId, updatedAt: t.updatedAt })), timestamp, }); })); @@ -73,6 +79,65 @@ router.post('/push', (0, asyncHandler_1.asyncHandler)(async (req, res) => { const timestamp = Date.now(); try { await db_1.db.transaction(async (tx) => { + // Ensure every referenced category exists (FK integrity) so a stale or + // never-synced category reference cannot fail the entire push. Missing + // categories are recreated as a fallback and the client heals on pull. + const referencedCategoryIds = new Set(); + for (const task of data.changes.tasks ?? []) { + if (task.categoryId) + referencedCategoryIds.add(task.categoryId); + } + for (const sub of data.changes.subtasks ?? []) { + const task = (data.changes.tasks ?? []).find((t) => t.id === sub.taskId); + if (task?.categoryId) + referencedCategoryIds.add(task.categoryId); + } + if (referencedCategoryIds.size > 0) { + const existing = await tx + .select({ id: schema_1.categories.id }) + .from(schema_1.categories) + .where((0, drizzle_orm_1.inArray)(schema_1.categories.id, [...referencedCategoryIds])); + const existingIds = new Set(existing.map((c) => c.id)); + const missing = [...referencedCategoryIds].filter((id) => !existingIds.has(id)); + if (missing.length > 0) { + const rows = await tx + .select({ max: (0, drizzle_orm_1.sql) `max(${schema_1.categories.order})` }) + .from(schema_1.categories) + .where((0, drizzle_orm_1.eq)(schema_1.categories.userId, userId)); + const startOrder = (rows[0]?.max ?? -1) + 1; + await tx.insert(schema_1.categories).values(missing.map((id, i) => ({ + id, + userId, + name: 'Default', + color: '#9E9E9E', + order: startOrder + i, + createdAt: Date.now(), + updatedAt: Date.now(), + }))); + } + } + // Sanitize assignee references: only real user ids may be stored (FK). + // Stale/unknown assignee ids are silently dropped to null instead of + // failing the whole push transaction. + const assigneeIds = new Set(); + for (const task of data.changes.tasks ?? []) { + if (task.assigneeId) + assigneeIds.add(task.assigneeId); + } + for (const sub of data.changes.subtasks ?? []) { + if (sub.assigneeId) + assigneeIds.add(sub.assigneeId); + } + const validAssignees = new Set(); + if (assigneeIds.size > 0) { + const rows = await tx + .select({ id: schema_1.users.id }) + .from(schema_1.users) + .where((0, drizzle_orm_1.inArray)(schema_1.users.id, [...assigneeIds])); + for (const r of rows) + validAssignees.add(r.id); + } + const sanitizeAssignee = (a) => a && validAssignees.has(a) ? a : null; // Process categories if (data.changes.categories && data.changes.categories.length > 0) { for (const cat of data.changes.categories) { @@ -131,6 +196,20 @@ router.post('/push', (0, asyncHandler_1.asyncHandler)(async (req, res) => { }); continue; // Server wins } + // Prevent completing tasks with future due dates + if (task.completed === true) { + const effectiveDueDate = task.dueDate ?? existing[0].dueDate; + if (!(0, validation_1.canCompleteTask)(effectiveDueDate)) { + conflicts.push({ + entity: 'tasks', + id: task.id, + serverVersion: existing[0], + clientVersion: task, + resolution: 'server_wins', + }); + continue; + } + } await tx .update(schema_1.tasks) .set({ @@ -140,14 +219,17 @@ router.post('/push', (0, asyncHandler_1.asyncHandler)(async (req, res) => { priority: task.priority, completed: task.completed, dueDate: task.dueDate, - dueTime: task.dueTime, + dueTime: task.dueTime ?? '', endTime: task.endTime ?? '', + allDay: task.allDay ?? false, repeat: task.repeat ?? 'none', repeatInterval: task.repeatInterval ?? 1, repeatDays: task.repeatDays ?? '', seriesId: task.seriesId ?? '', reminder: task.reminder ?? 'none', - assigneeId: task.assigneeId ?? null, + reminders: task.reminders ?? '', + assigneeId: sanitizeAssignee(task.assigneeId), + completedAt: task.completedAt ?? null, updatedAt: task.updatedAt, }) .where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(schema_1.tasks.id, task.id), (0, drizzle_orm_1.eq)(schema_1.tasks.userId, userId))); @@ -155,6 +237,8 @@ router.post('/push', (0, asyncHandler_1.asyncHandler)(async (req, res) => { else { await tx.insert(schema_1.tasks).values({ ...task, + assigneeId: sanitizeAssignee(task.assigneeId), + allDay: task.allDay ?? false, userId, }); } @@ -184,8 +268,22 @@ router.post('/push', (0, asyncHandler_1.asyncHandler)(async (req, res) => { .update(schema_1.subtasks) .set({ taskId: sub.taskId, + parentSubtaskId: sub.parentSubtaskId ?? null, title: sub.title, + description: sub.description ?? '', + priority: sub.priority ?? 'none', completed: sub.completed, + dueDate: sub.dueDate ?? 0, + dueTime: sub.dueTime ?? '', + endTime: sub.endTime ?? '', + allDay: sub.allDay ?? false, + repeat: sub.repeat ?? 'none', + repeatInterval: sub.repeatInterval ?? 1, + repeatDays: sub.repeatDays ?? '', + seriesId: sub.seriesId ?? '', + reminder: sub.reminder ?? 'none', + reminders: sub.reminders ?? '', + assigneeId: sanitizeAssignee(sub.assigneeId), order: sub.order, updatedAt: sub.updatedAt, }) @@ -194,6 +292,7 @@ router.post('/push', (0, asyncHandler_1.asyncHandler)(async (req, res) => { else { await tx.insert(schema_1.subtasks).values({ ...sub, + assigneeId: sanitizeAssignee(sub.assigneeId), userId, }); } @@ -273,6 +372,13 @@ router.post('/push', (0, asyncHandler_1.asyncHandler)(async (req, res) => { } } } + // Process deletions (tombstones) - last so they see the state produced + // by the upserts above and resolve by last-writer-wins. + if (data.deleted && data.deleted.length > 0) { + for (const deleted of data.deleted) { + await applyTombstone(tx, deleted.entity, deleted.id, deleted.updatedAt, userId, conflicts); + } + } }); } catch (error) { @@ -285,5 +391,175 @@ router.post('/push', (0, asyncHandler_1.asyncHandler)(async (req, res) => { conflicts, }); })); +// Upsert a tombstone row, keeping the latest updatedAt. +async function upsertTombstone(tx, entity, entityId, updatedAt, userId) { + const existing = await tx + .select({ updatedAt: schema_1.tombstones.updatedAt }) + .from(schema_1.tombstones) + .where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(schema_1.tombstones.entity, entity), (0, drizzle_orm_1.eq)(schema_1.tombstones.entityId, entityId))) + .limit(1); + const merged = Math.max(existing[0]?.updatedAt ?? 0, updatedAt); + if (existing.length > 0) { + await tx + .update(schema_1.tombstones) + .set({ updatedAt: merged }) + .where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(schema_1.tombstones.entity, entity), (0, drizzle_orm_1.eq)(schema_1.tombstones.entityId, entityId))); + } + else { + await tx.insert(schema_1.tombstones).values({ entity, entityId, userId, updatedAt: merged }); + } +} +// Apply a client deletion. LWW: if the server row is newer than the deletion +// timestamp, the deletion is rejected (server_wins conflict) so the client +// re-pulls the row. Accepted deletions cascade tombstones to every FK-cascaded +// child so all devices remove them too. +async function applyTombstone(tx, entity, id, deletedAt, userId, conflicts) { + const tombstoneOf = (e, ids) => ids.forEach((i) => upsertTombstone(tx, e, i, deletedAt, userId)); + if (entity === 'tasks') { + const row = await tx + .select() + .from(schema_1.tasks) + .where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(schema_1.tasks.id, id), (0, drizzle_orm_1.eq)(schema_1.tasks.userId, userId))) + .limit(1); + if (row.length === 0) { + await upsertTombstone(tx, entity, id, deletedAt, userId); + return; + } + if (row[0].updatedAt > deletedAt) { + conflicts.push({ + entity: 'tasks', + id, + serverVersion: row[0], + clientVersion: { id, updatedAt: deletedAt }, + resolution: 'server_wins', + }); + return; + } + const children = await tx + .select({ id: schema_1.subtasks.id }) + .from(schema_1.subtasks) + .where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(schema_1.subtasks.taskId, id), (0, drizzle_orm_1.eq)(schema_1.subtasks.userId, userId))); + tombstoneOf('subtasks', children.map((c) => c.id)); + await tx.delete(schema_1.tasks).where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(schema_1.tasks.id, id), (0, drizzle_orm_1.eq)(schema_1.tasks.userId, userId))); + await upsertTombstone(tx, entity, id, deletedAt, userId); + return; + } + if (entity === 'categories') { + const row = await tx + .select() + .from(schema_1.categories) + .where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(schema_1.categories.id, id), (0, drizzle_orm_1.eq)(schema_1.categories.userId, userId))) + .limit(1); + if (row.length === 0) { + await upsertTombstone(tx, entity, id, deletedAt, userId); + return; + } + if (row[0].updatedAt > deletedAt) { + conflicts.push({ + entity: 'categories', + id, + serverVersion: row[0], + clientVersion: { id, updatedAt: deletedAt }, + resolution: 'server_wins', + }); + return; + } + // Deleting the category cascades its tasks (and their subtasks) - + // tombstone all of them so every client removes them. + const catTasks = await tx + .select() + .from(schema_1.tasks) + .where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(schema_1.tasks.categoryId, id), (0, drizzle_orm_1.eq)(schema_1.tasks.userId, userId))); + for (const taskRow of catTasks) { + const subIds = await tx + .select({ id: schema_1.subtasks.id }) + .from(schema_1.subtasks) + .where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(schema_1.subtasks.taskId, taskRow.id), (0, drizzle_orm_1.eq)(schema_1.subtasks.userId, userId))); + tombstoneOf('subtasks', subIds.map((s) => s.id)); + tombstoneOf('tasks', [taskRow.id]); + } + await tx.delete(schema_1.categories).where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(schema_1.categories.id, id), (0, drizzle_orm_1.eq)(schema_1.categories.userId, userId))); + await upsertTombstone(tx, entity, id, deletedAt, userId); + return; + } + if (entity === 'subtasks') { + const row = await tx + .select() + .from(schema_1.subtasks) + .where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(schema_1.subtasks.id, id), (0, drizzle_orm_1.eq)(schema_1.subtasks.userId, userId))) + .limit(1); + if (row.length === 0) { + await upsertTombstone(tx, entity, id, deletedAt, userId); + return; + } + if (row[0].updatedAt > deletedAt) { + conflicts.push({ + entity: 'subtasks', + id, + serverVersion: row[0], + clientVersion: { id, updatedAt: deletedAt }, + resolution: 'server_wins', + }); + return; + } + // Deleting a parent subtask cascades its children in PG - tombstone them. + const children = await tx + .select({ id: schema_1.subtasks.id }) + .from(schema_1.subtasks) + .where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(schema_1.subtasks.parentSubtaskId, id), (0, drizzle_orm_1.eq)(schema_1.subtasks.userId, userId))); + tombstoneOf('subtasks', children.map((c) => c.id)); + await tx.delete(schema_1.subtasks).where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(schema_1.subtasks.id, id), (0, drizzle_orm_1.eq)(schema_1.subtasks.userId, userId))); + await upsertTombstone(tx, entity, id, deletedAt, userId); + return; + } + if (entity === 'repeatProfiles') { + const row = await tx + .select() + .from(schema_1.repeatProfiles) + .where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(schema_1.repeatProfiles.id, id), (0, drizzle_orm_1.eq)(schema_1.repeatProfiles.userId, userId))) + .limit(1); + if (row.length === 0) { + await upsertTombstone(tx, entity, id, deletedAt, userId); + return; + } + if (row[0].updatedAt > deletedAt) { + conflicts.push({ + entity: 'repeatProfiles', + id, + serverVersion: row[0], + clientVersion: { id, updatedAt: deletedAt }, + resolution: 'server_wins', + }); + return; + } + await tx.delete(schema_1.repeatProfiles).where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(schema_1.repeatProfiles.id, id), (0, drizzle_orm_1.eq)(schema_1.repeatProfiles.userId, userId))); + await upsertTombstone(tx, entity, id, deletedAt, userId); + return; + } + if (entity === 'friendships') { + const row = await tx + .select() + .from(schema_1.friendships) + .where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(schema_1.friendships.id, id), (0, drizzle_orm_1.or)((0, drizzle_orm_1.eq)(schema_1.friendships.userId, userId), (0, drizzle_orm_1.eq)(schema_1.friendships.friendId, userId)))) + .limit(1); + if (row.length === 0) { + await upsertTombstone(tx, entity, id, deletedAt, userId); + return; + } + if (row[0].updatedAt > deletedAt) { + conflicts.push({ + entity: 'friendships', + id, + serverVersion: row[0], + clientVersion: { id, updatedAt: deletedAt }, + resolution: 'server_wins', + }); + return; + } + await tx.delete(schema_1.friendships).where((0, drizzle_orm_1.eq)(schema_1.friendships.id, id)); + await upsertTombstone(tx, entity, id, deletedAt, userId); + return; + } +} exports.default = router; //# sourceMappingURL=sync.js.map \ No newline at end of file diff --git a/backend/dist/routes/sync.js.map b/backend/dist/routes/sync.js.map index 1f0e36f..4dacacb 100644 --- a/backend/dist/routes/sync.js.map +++ b/backend/dist/routes/sync.js.map @@ -1 +1 @@ -{"version":3,"file":"sync.js","sourceRoot":"","sources":["../../src/routes/sync.ts"],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"sync.js","sourceRoot":"","sources":["../../src/routes/sync.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/backend/dist/routes/tasks.d.ts.map b/backend/dist/routes/tasks.d.ts.map index 906edc7..d989cd1 100644 --- a/backend/dist/routes/tasks.d.ts.map +++ b/backend/dist/routes/tasks.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"tasks.d.ts","sourceRoot":"","sources":["../../src/routes/tasks.ts"],"names":[],"mappings":"AAUA,QAAA,MAAM,MAAM,4CAAW,CAAC;AA8RxB,eAAe,MAAM,CAAC"} \ No newline at end of file +{"version":3,"file":"tasks.d.ts","sourceRoot":"","sources":["../../src/routes/tasks.ts"],"names":[],"mappings":"AAUA,QAAA,MAAM,MAAM,4CAAW,CAAC;AA+TxB,eAAe,MAAM,CAAC"} \ No newline at end of file diff --git a/backend/dist/routes/tasks.js b/backend/dist/routes/tasks.js index 777e624..1919222 100644 --- a/backend/dist/routes/tasks.js +++ b/backend/dist/routes/tasks.js @@ -97,14 +97,16 @@ router.post('/', (0, asyncHandler_1.asyncHandler)(async (req, res) => { const data = validation_1.taskCreateSchema.parse(req.body); const userId = req.user.userId; const now = Date.now(); - // Verify category exists and belongs to user - const cat = await db_1.db - .select() - .from(schema_1.categories) - .where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(schema_1.categories.id, data.categoryId), (0, drizzle_orm_1.eq)(schema_1.categories.userId, userId))) - .limit(1); - if (cat.length === 0) { - throw new errorHandler_1.AppError('NOT_FOUND', 'Category not found', 404); + // Verify category exists and belongs to user (optional - tasks may be uncategorized) + if (data.categoryId) { + const cat = await db_1.db + .select() + .from(schema_1.categories) + .where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(schema_1.categories.id, data.categoryId), (0, drizzle_orm_1.eq)(schema_1.categories.userId, userId))) + .limit(1); + if (cat.length === 0) { + throw new errorHandler_1.AppError('NOT_FOUND', 'Category not found', 404); + } } const taskId = `task_${Date.now().toString(36)}${Math.random().toString(36).slice(2, 6)}`; const newTask = { @@ -118,7 +120,10 @@ router.post('/', (0, asyncHandler_1.asyncHandler)(async (req, res) => { dueDate: data.dueDate ?? 0, dueTime: data.dueTime ?? '', endTime: data.endTime ?? '', + allDay: data.allDay ?? false, assigneeId: data.assigneeId ?? null, + reminder: data.reminder ?? 'none', + reminders: data.reminders ?? '', createdAt: now, updatedAt: now, }; @@ -166,6 +171,13 @@ router.patch('/:id', (0, asyncHandler_1.asyncHandler)(async (req, res) => { throw new errorHandler_1.AppError('NOT_FOUND', 'Category not found', 404); } } + // Prevent completing tasks with future due dates + if (data.completed === true) { + const effectiveDueDate = data.dueDate ?? existing[0].dueDate; + if (!(0, validation_1.canCompleteTask)(effectiveDueDate)) { + throw new errorHandler_1.AppError('VALIDATION_ERROR', 'Cannot mark a task as completed if its due date is in the future', 400); + } + } const now = Date.now(); const updated = await db_1.db .update(schema_1.tasks) @@ -207,13 +219,15 @@ router.post('/batch', (0, asyncHandler_1.asyncHandler)(async (req, res) => { for (const op of operations) { try { if (op.type === 'create') { - const cat = await db_1.db - .select() - .from(schema_1.categories) - .where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(schema_1.categories.id, op.data.categoryId), (0, drizzle_orm_1.eq)(schema_1.categories.userId, userId))) - .limit(1); - if (cat.length === 0) - throw new errorHandler_1.AppError('NOT_FOUND', 'Category not found', 404); + if (op.data.categoryId) { + const cat = await db_1.db + .select() + .from(schema_1.categories) + .where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(schema_1.categories.id, op.data.categoryId), (0, drizzle_orm_1.eq)(schema_1.categories.userId, userId))) + .limit(1); + if (cat.length === 0) + throw new errorHandler_1.AppError('NOT_FOUND', 'Category not found', 404); + } const taskId = `task_${Date.now().toString(36)}${Math.random().toString(36).slice(2, 6)}`; const now = Date.now(); await db_1.db.insert(schema_1.tasks).values({ @@ -227,6 +241,25 @@ router.post('/batch', (0, asyncHandler_1.asyncHandler)(async (req, res) => { results.push({ id: taskId, success: true }); } else if (op.type === 'update') { + if (op.data.completed === true) { + let effectiveDueDate; + if (op.data.dueDate !== undefined) { + effectiveDueDate = op.data.dueDate; + } + else { + const existingTask = await db_1.db + .select({ dueDate: schema_1.tasks.dueDate }) + .from(schema_1.tasks) + .where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(schema_1.tasks.id, op.id), (0, drizzle_orm_1.eq)(schema_1.tasks.userId, userId))) + .limit(1); + if (existingTask.length === 0) + throw new errorHandler_1.AppError('NOT_FOUND', 'Task not found', 404); + effectiveDueDate = existingTask[0].dueDate; + } + if (!(0, validation_1.canCompleteTask)(effectiveDueDate)) { + throw new errorHandler_1.AppError('VALIDATION_ERROR', 'Cannot mark a task as completed if its due date is in the future', 400); + } + } await db_1.db .update(schema_1.tasks) .set({ ...op.data, updatedAt: Date.now() }) diff --git a/backend/dist/routes/tasks.js.map b/backend/dist/routes/tasks.js.map index 6728c61..760dc3a 100644 --- a/backend/dist/routes/tasks.js.map +++ b/backend/dist/routes/tasks.js.map @@ -1 +1 @@ -{"version":3,"file":"tasks.js","sourceRoot":"","sources":["../../src/routes/tasks.ts"],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"tasks.js","sourceRoot":"","sources":["../../src/routes/tasks.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/backend/dist/utils/auth.d.ts b/backend/dist/utils/auth.d.ts index f00a1c3..aa3b074 100644 --- a/backend/dist/utils/auth.d.ts +++ b/backend/dist/utils/auth.d.ts @@ -2,7 +2,7 @@ import { Request, Response, NextFunction } from 'express'; import { AuthPayload } from '../types'; export declare function generateToken(payload: AuthPayload): string; export declare function verifyToken(token: string): AuthPayload | null; -export declare function authMiddleware(req: Request, res: Response, next: NextFunction): void; +export declare function authMiddleware(req: Request, res: Response, next: NextFunction): Promise; export declare function optionalAuthMiddleware(req: Request, res: Response, next: NextFunction): void; export declare function generateId(prefix?: string): string; export declare function getCurrentTimestamp(): number; diff --git a/backend/dist/utils/auth.d.ts.map b/backend/dist/utils/auth.d.ts.map index 310af50..ae53160 100644 --- a/backend/dist/utils/auth.d.ts.map +++ b/backend/dist/utils/auth.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/utils/auth.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAE1D,OAAO,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAKvC,wBAAgB,aAAa,CAAC,OAAO,EAAE,WAAW,GAAG,MAAM,CAE1D;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI,CAM7D;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,YAAY,GAAG,IAAI,CA4BpF;AAED,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,YAAY,GAAG,IAAI,CAW5F;AAED,wBAAgB,UAAU,CAAC,MAAM,GAAE,MAAW,GAAG,MAAM,CAItD;AAED,wBAAgB,mBAAmB,IAAI,MAAM,CAE5C"} \ No newline at end of file +{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/utils/auth.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAK1D,OAAO,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAKvC,wBAAgB,aAAa,CAAC,OAAO,EAAE,WAAW,GAAG,MAAM,CAE1D;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI,CAM7D;AAED,wBAAsB,cAAc,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAuCnG;AAED,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,YAAY,GAAG,IAAI,CAW5F;AAED,wBAAgB,UAAU,CAAC,MAAM,GAAE,MAAW,GAAG,MAAM,CAItD;AAED,wBAAgB,mBAAmB,IAAI,MAAM,CAE5C"} \ No newline at end of file diff --git a/backend/dist/utils/auth.js b/backend/dist/utils/auth.js index 055d866..26dd167 100644 --- a/backend/dist/utils/auth.js +++ b/backend/dist/utils/auth.js @@ -10,6 +10,9 @@ exports.optionalAuthMiddleware = optionalAuthMiddleware; exports.generateId = generateId; exports.getCurrentTimestamp = getCurrentTimestamp; const jsonwebtoken_1 = __importDefault(require("jsonwebtoken")); +const db_1 = require("../db"); +const schema_1 = require("../db/schema"); +const drizzle_orm_1 = require("drizzle-orm"); const JWT_SECRET = process.env.JWT_SECRET || 'your-super-secret-jwt-key-change-in-production'; const JWT_EXPIRES_IN = '7d'; function generateToken(payload) { @@ -23,7 +26,7 @@ function verifyToken(token) { return null; } } -function authMiddleware(req, res, next) { +async function authMiddleware(req, res, next) { const authHeader = req.headers.authorization; if (!authHeader || !authHeader.startsWith('Bearer ')) { res.status(401).json({ @@ -45,6 +48,16 @@ function authMiddleware(req, res, next) { }); return; } + const user = await db_1.db.select({ id: schema_1.users.id }).from(schema_1.users).where((0, drizzle_orm_1.eq)(schema_1.users.id, payload.userId)).limit(1); + if (user.length === 0) { + res.status(401).json({ + error: { + code: 'UNAUTHORIZED', + message: 'User no longer exists, please log in again', + }, + }); + return; + } req.user = payload; next(); } diff --git a/backend/dist/utils/auth.js.map b/backend/dist/utils/auth.js.map index 693012b..a9298a7 100644 --- a/backend/dist/utils/auth.js.map +++ b/backend/dist/utils/auth.js.map @@ -1 +1 @@ -{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/utils/auth.ts"],"names":[],"mappings":";;;;;AAOA,sCAEC;AAED,kCAMC;AAED,wCA4BC;AAED,wDAWC;AAED,gCAIC;AAED,kDAEC;AArED,gEAA+B;AAG/B,MAAM,UAAU,GAAG,OAAO,CAAC,GAAG,CAAC,UAAU,IAAI,gDAAgD,CAAC;AAC9F,MAAM,cAAc,GAAG,IAAI,CAAC;AAE5B,SAAgB,aAAa,CAAC,OAAoB;IAChD,OAAO,sBAAG,CAAC,IAAI,CAAC,OAAO,EAAE,UAAU,EAAE,EAAE,SAAS,EAAE,cAAc,EAAE,CAAC,CAAC;AACtE,CAAC;AAED,SAAgB,WAAW,CAAC,KAAa;IACvC,IAAI,CAAC;QACH,OAAO,sBAAG,CAAC,MAAM,CAAC,KAAK,EAAE,UAAU,CAAgB,CAAC;IACtD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,SAAgB,cAAc,CAAC,GAAY,EAAE,GAAa,EAAE,IAAkB;IAC5E,MAAM,UAAU,GAAG,GAAG,CAAC,OAAO,CAAC,aAAa,CAAC;IAE7C,IAAI,CAAC,UAAU,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;QACrD,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC;YACnB,KAAK,EAAE;gBACL,IAAI,EAAE,cAAc;gBACpB,OAAO,EAAE,yCAAyC;aACnD;SACF,CAAC,CAAC;QACH,OAAO;IACT,CAAC;IAED,MAAM,KAAK,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IAClC,MAAM,OAAO,GAAG,WAAW,CAAC,KAAK,CAAC,CAAC;IAEnC,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC;YACnB,KAAK,EAAE;gBACL,IAAI,EAAE,cAAc;gBACpB,OAAO,EAAE,0BAA0B;aACpC;SACF,CAAC,CAAC;QACH,OAAO;IACT,CAAC;IAED,GAAG,CAAC,IAAI,GAAG,OAAO,CAAC;IACnB,IAAI,EAAE,CAAC;AACT,CAAC;AAED,SAAgB,sBAAsB,CAAC,GAAY,EAAE,GAAa,EAAE,IAAkB;IACpF,MAAM,UAAU,GAAG,GAAG,CAAC,OAAO,CAAC,aAAa,CAAC;IAE7C,IAAI,UAAU,IAAI,UAAU,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;QACnD,MAAM,KAAK,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;QAClC,MAAM,OAAO,GAAG,WAAW,CAAC,KAAK,CAAC,CAAC;QACnC,IAAI,OAAO,EAAE,CAAC;YACZ,GAAG,CAAC,IAAI,GAAG,OAAO,CAAC;QACrB,CAAC;IACH,CAAC;IACD,IAAI,EAAE,CAAC;AACT,CAAC;AAED,SAAgB,UAAU,CAAC,SAAiB,EAAE;IAC5C,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;IAC1C,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IACvD,OAAO,GAAG,MAAM,GAAG,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,SAAS,GAAG,MAAM,EAAE,CAAC;AAC9D,CAAC;AAED,SAAgB,mBAAmB;IACjC,OAAO,IAAI,CAAC,GAAG,EAAE,CAAC;AACpB,CAAC"} \ No newline at end of file +{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/utils/auth.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/backend/dist/utils/validation.d.ts b/backend/dist/utils/validation.d.ts index eb4fb11..a79e5c4 100644 --- a/backend/dist/utils/validation.d.ts +++ b/backend/dist/utils/validation.d.ts @@ -1,4 +1,5 @@ import { z } from 'zod'; +export declare function canCompleteTask(dueDate: number): boolean; export declare const categoryCreateSchema: z.ZodObject<{ name: z.ZodString; color: z.ZodString; @@ -26,20 +27,25 @@ export declare const categoryUpdateSchema: z.ZodObject<{ order?: number | undefined; }>; export declare const repeatSchema: z.ZodEnum<["none", "daily", "weekly", "monthly", "custom"]>; +export declare const repeatFieldSchema: z.ZodOptional, z.ZodLiteral<"">]>, "custom" | "none" | "daily" | "weekly" | "monthly", "" | "custom" | "none" | "daily" | "weekly" | "monthly">>; +export declare const repeatIntervalFieldSchema: z.ZodOptional>; export declare const taskCreateSchema: z.ZodObject<{ title: z.ZodString; description: z.ZodOptional; - categoryId: z.ZodString; + categoryId: z.ZodNullable>; priority: z.ZodOptional>; dueDate: z.ZodOptional; - dueTime: z.ZodNullable>; + dueTime: z.ZodEffects, z.ZodLiteral<"">]>, string, string | undefined>; endTime: z.ZodUnion<[z.ZodOptional, z.ZodLiteral<"">]>; - repeat: z.ZodOptional>; - repeatInterval: z.ZodOptional; + allDay: z.ZodOptional; + repeat: z.ZodOptional, z.ZodLiteral<"">]>, "custom" | "none" | "daily" | "weekly" | "monthly", "" | "custom" | "none" | "daily" | "weekly" | "monthly">>; + repeatInterval: z.ZodOptional>; repeatDays: z.ZodOptional; seriesId: z.ZodOptional; reminder: z.ZodOptional>; + reminders: z.ZodOptional; assigneeId: z.ZodOptional>; + completedAt: z.ZodOptional>; subtasks: z.ZodOptional, "many">>; }, "strip", z.ZodTypeAny, { - categoryId: string; + categoryId: string | null; title: string; + dueTime: string; + subtasks?: { + title: string; + }[] | undefined; description?: string | undefined; priority?: "none" | "low" | "medium" | "high" | "critical" | undefined; dueDate?: number | undefined; - dueTime?: string | null | undefined; endTime?: string | undefined; + allDay?: boolean | undefined; repeat?: "custom" | "none" | "daily" | "weekly" | "monthly" | undefined; repeatInterval?: number | undefined; repeatDays?: string | undefined; seriesId?: string | undefined; assigneeId?: string | null | undefined; reminder?: "none" | "at_time" | "15" | "30" | "60" | "120" | "1440" | undefined; - subtasks?: { - title: string; - }[] | undefined; + reminders?: string | undefined; + completedAt?: number | null | undefined; }, { - categoryId: string; + categoryId: string | null; title: string; + subtasks?: { + title: string; + }[] | undefined; description?: string | undefined; priority?: "none" | "low" | "medium" | "high" | "critical" | undefined; dueDate?: number | undefined; - dueTime?: string | null | undefined; + dueTime?: string | undefined; endTime?: string | undefined; - repeat?: "custom" | "none" | "daily" | "weekly" | "monthly" | undefined; + allDay?: boolean | undefined; + repeat?: "" | "custom" | "none" | "daily" | "weekly" | "monthly" | undefined; repeatInterval?: number | undefined; repeatDays?: string | undefined; seriesId?: string | undefined; assigneeId?: string | null | undefined; reminder?: "none" | "at_time" | "15" | "30" | "60" | "120" | "1440" | undefined; - subtasks?: { - title: string; - }[] | undefined; + reminders?: string | undefined; + completedAt?: number | null | undefined; }>; export declare const taskUpdateSchema: z.ZodObject<{ title: z.ZodOptional; description: z.ZodOptional; - categoryId: z.ZodOptional; + categoryId: z.ZodEffects>, string | null | undefined, string | null | undefined>; priority: z.ZodOptional>; completed: z.ZodOptional; dueDate: z.ZodOptional; - dueTime: z.ZodNullable>; + dueTime: z.ZodEffects, z.ZodLiteral<"">]>, string | undefined, string | undefined>; endTime: z.ZodOptional; + reminders: z.ZodOptional; assigneeId: z.ZodOptional>; + completedAt: z.ZodOptional>; }, "strip", z.ZodTypeAny, { - categoryId?: string | undefined; + categoryId?: string | null | undefined; title?: string | undefined; description?: string | undefined; priority?: "none" | "low" | "medium" | "high" | "critical" | undefined; completed?: boolean | undefined; dueDate?: number | undefined; - dueTime?: string | null | undefined; + dueTime?: string | undefined; endTime?: string | undefined; assigneeId?: string | null | undefined; + reminders?: string | undefined; + completedAt?: number | null | undefined; }, { - categoryId?: string | undefined; + categoryId?: string | null | undefined; title?: string | undefined; description?: string | undefined; priority?: "none" | "low" | "medium" | "high" | "critical" | undefined; completed?: boolean | undefined; dueDate?: number | undefined; - dueTime?: string | null | undefined; + dueTime?: string | undefined; endTime?: string | undefined; assigneeId?: string | null | undefined; + reminders?: string | undefined; + completedAt?: number | null | undefined; }>; export declare const subtaskCreateSchema: z.ZodObject<{ title: z.ZodString; + description: z.ZodOptional; + priority: z.ZodOptional>; + dueDate: z.ZodOptional; + dueTime: z.ZodEffects, z.ZodLiteral<"">]>, string, string | undefined>; + endTime: z.ZodUnion<[z.ZodOptional, z.ZodLiteral<"">]>; + allDay: z.ZodOptional; + repeat: z.ZodOptional, z.ZodLiteral<"">]>, "custom" | "none" | "daily" | "weekly" | "monthly", "" | "custom" | "none" | "daily" | "weekly" | "monthly">>; + repeatInterval: z.ZodOptional>; + repeatDays: z.ZodOptional; + seriesId: z.ZodOptional; + reminder: z.ZodOptional>; + reminders: z.ZodOptional; + assigneeId: z.ZodOptional>; order: z.ZodOptional; + parentSubtaskId: z.ZodOptional>; }, "strip", z.ZodTypeAny, { title: string; + dueTime: string; order?: number | undefined; + description?: string | undefined; + priority?: "none" | "low" | "medium" | "high" | "critical" | undefined; + dueDate?: number | undefined; + endTime?: string | undefined; + allDay?: boolean | undefined; + repeat?: "custom" | "none" | "daily" | "weekly" | "monthly" | undefined; + repeatInterval?: number | undefined; + repeatDays?: string | undefined; + seriesId?: string | undefined; + assigneeId?: string | null | undefined; + reminder?: "none" | "at_time" | "15" | "30" | "60" | "120" | "1440" | undefined; + reminders?: string | undefined; + parentSubtaskId?: string | null | undefined; }, { title: string; order?: number | undefined; + description?: string | undefined; + priority?: "none" | "low" | "medium" | "high" | "critical" | undefined; + dueDate?: number | undefined; + dueTime?: string | undefined; + endTime?: string | undefined; + allDay?: boolean | undefined; + repeat?: "" | "custom" | "none" | "daily" | "weekly" | "monthly" | undefined; + repeatInterval?: number | undefined; + repeatDays?: string | undefined; + seriesId?: string | undefined; + assigneeId?: string | null | undefined; + reminder?: "none" | "at_time" | "15" | "30" | "60" | "120" | "1440" | undefined; + reminders?: string | undefined; + parentSubtaskId?: string | null | undefined; }>; export declare const subtaskUpdateSchema: z.ZodObject<{ title: z.ZodOptional; + description: z.ZodOptional; + priority: z.ZodOptional>; completed: z.ZodOptional; + dueDate: z.ZodOptional; + dueTime: z.ZodEffects, z.ZodLiteral<"">]>, string | undefined, string | undefined>; + endTime: z.ZodOptional; + allDay: z.ZodOptional; + repeat: z.ZodOptional, z.ZodLiteral<"">]>, "custom" | "none" | "daily" | "weekly" | "monthly", "" | "custom" | "none" | "daily" | "weekly" | "monthly">>; + repeatInterval: z.ZodOptional>; + repeatDays: z.ZodOptional; + seriesId: z.ZodOptional; + reminder: z.ZodOptional>; + reminders: z.ZodOptional; + assigneeId: z.ZodOptional>; order: z.ZodOptional; + parentSubtaskId: z.ZodOptional>; }, "strip", z.ZodTypeAny, { order?: number | undefined; title?: string | undefined; + description?: string | undefined; + priority?: "none" | "low" | "medium" | "high" | "critical" | undefined; completed?: boolean | undefined; + dueDate?: number | undefined; + dueTime?: string | undefined; + endTime?: string | undefined; + allDay?: boolean | undefined; + repeat?: "custom" | "none" | "daily" | "weekly" | "monthly" | undefined; + repeatInterval?: number | undefined; + repeatDays?: string | undefined; + seriesId?: string | undefined; + assigneeId?: string | null | undefined; + reminder?: "none" | "at_time" | "15" | "30" | "60" | "120" | "1440" | undefined; + reminders?: string | undefined; + parentSubtaskId?: string | null | undefined; }, { order?: number | undefined; title?: string | undefined; + description?: string | undefined; + priority?: "none" | "low" | "medium" | "high" | "critical" | undefined; completed?: boolean | undefined; + dueDate?: number | undefined; + dueTime?: string | undefined; + endTime?: string | undefined; + allDay?: boolean | undefined; + repeat?: "" | "custom" | "none" | "daily" | "weekly" | "monthly" | undefined; + repeatInterval?: number | undefined; + repeatDays?: string | undefined; + seriesId?: string | undefined; + assigneeId?: string | null | undefined; + reminder?: "none" | "at_time" | "15" | "30" | "60" | "120" | "1440" | undefined; + reminders?: string | undefined; + parentSubtaskId?: string | null | undefined; }>; export declare const userSettingsSchema: z.ZodObject<{ darkMode: z.ZodOptional; @@ -174,6 +276,29 @@ export declare const repeatProfileSchema: z.ZodObject<{ repeatInterval: number; repeatDays: string; }>; +export declare const repeatProfileUpdateSchema: z.ZodObject<{ + name: z.ZodOptional; + repeat: z.ZodOptional>; + repeatInterval: z.ZodOptional; + repeatDays: z.ZodOptional; +}, "strip", z.ZodTypeAny, { + name?: string | undefined; + repeat?: "custom" | "none" | "daily" | "weekly" | "monthly" | undefined; + repeatInterval?: number | undefined; + repeatDays?: string | undefined; +}, { + name?: string | undefined; + repeat?: "custom" | "none" | "daily" | "weekly" | "monthly" | undefined; + repeatInterval?: number | undefined; + repeatDays?: string | undefined; +}>; +export declare const searchQuerySchema: z.ZodObject<{ + q: z.ZodString; +}, "strip", z.ZodTypeAny, { + q: string; +}, { + q: string; +}>; export declare const friendRequestSchema: z.ZodObject<{ username: z.ZodString; }, "strip", z.ZodTypeAny, { @@ -203,6 +328,19 @@ export declare const friendshipSchema: z.ZodObject<{ status: "pending" | "accepted"; updatedAt: number; }>; +export declare const tombstoneSchema: z.ZodObject<{ + entity: z.ZodEnum<["categories", "tasks", "subtasks", "repeatProfiles", "friendships"]>; + id: z.ZodString; + updatedAt: z.ZodNumber; +}, "strip", z.ZodTypeAny, { + id: string; + updatedAt: number; + entity: "categories" | "tasks" | "subtasks" | "repeatProfiles" | "friendships"; +}, { + id: string; + updatedAt: number; + entity: "categories" | "tasks" | "subtasks" | "repeatProfiles" | "friendships"; +}>; export declare const pushChangesSchema: z.ZodObject<{ changes: z.ZodObject<{ categories: z.ZodOptional; - categoryId: z.ZodString; + categoryId: z.ZodNullable>; priority: z.ZodOptional>; dueDate: z.ZodOptional; - dueTime: z.ZodNullable>; + dueTime: z.ZodEffects, z.ZodLiteral<"">]>, string, string | undefined>; endTime: z.ZodUnion<[z.ZodOptional, z.ZodLiteral<"">]>; - repeat: z.ZodOptional>; - repeatInterval: z.ZodOptional; + allDay: z.ZodOptional; + repeat: z.ZodOptional, z.ZodLiteral<"">]>, "custom" | "none" | "daily" | "weekly" | "monthly", "" | "custom" | "none" | "daily" | "weekly" | "monthly">>; + repeatInterval: z.ZodOptional>; repeatDays: z.ZodOptional; seriesId: z.ZodOptional; reminder: z.ZodOptional>; + reminders: z.ZodOptional; assigneeId: z.ZodOptional>; subtasks: z.ZodOptional>; }, "strip", z.ZodTypeAny, { id: string; createdAt: number; updatedAt: number; - categoryId: string; + categoryId: string | null; title: string; completed: boolean; + dueTime: string; + subtasks?: { + title: string; + }[] | undefined; description?: string | undefined; priority?: "none" | "low" | "medium" | "high" | "critical" | undefined; dueDate?: number | undefined; - dueTime?: string | null | undefined; endTime?: string | undefined; + allDay?: boolean | undefined; repeat?: "custom" | "none" | "daily" | "weekly" | "monthly" | undefined; repeatInterval?: number | undefined; repeatDays?: string | undefined; seriesId?: string | undefined; assigneeId?: string | null | undefined; reminder?: "none" | "at_time" | "15" | "30" | "60" | "120" | "1440" | undefined; - subtasks?: { - title: string; - }[] | undefined; + reminders?: string | undefined; + completedAt?: number | null | undefined; }, { id: string; createdAt: number; updatedAt: number; - categoryId: string; + categoryId: string | null; title: string; completed: boolean; + subtasks?: { + title: string; + }[] | undefined; description?: string | undefined; priority?: "none" | "low" | "medium" | "high" | "critical" | undefined; dueDate?: number | undefined; - dueTime?: string | null | undefined; + dueTime?: string | undefined; endTime?: string | undefined; - repeat?: "custom" | "none" | "daily" | "weekly" | "monthly" | undefined; + allDay?: boolean | undefined; + repeat?: "" | "custom" | "none" | "daily" | "weekly" | "monthly" | undefined; repeatInterval?: number | undefined; repeatDays?: string | undefined; seriesId?: string | undefined; assigneeId?: string | null | undefined; reminder?: "none" | "at_time" | "15" | "30" | "60" | "120" | "1440" | undefined; - subtasks?: { - title: string; - }[] | undefined; + reminders?: string | undefined; + completedAt?: number | null | undefined; }>, "many">>; subtasks: z.ZodOptional; + priority: z.ZodOptional>; + dueDate: z.ZodOptional; + dueTime: z.ZodEffects, z.ZodLiteral<"">]>, string, string | undefined>; + endTime: z.ZodUnion<[z.ZodOptional, z.ZodLiteral<"">]>; + allDay: z.ZodOptional; + repeat: z.ZodOptional, z.ZodLiteral<"">]>, "custom" | "none" | "daily" | "weekly" | "monthly", "" | "custom" | "none" | "daily" | "weekly" | "monthly">>; + repeatInterval: z.ZodOptional>; + repeatDays: z.ZodOptional; + seriesId: z.ZodOptional; + reminder: z.ZodOptional>; + reminders: z.ZodOptional; + assigneeId: z.ZodOptional>; order: z.ZodOptional; + parentSubtaskId: z.ZodOptional>; } & { id: z.ZodString; taskId: z.ZodString; @@ -312,8 +473,22 @@ export declare const pushChangesSchema: z.ZodObject<{ updatedAt: number; title: string; completed: boolean; + dueTime: string; taskId: string; order?: number | undefined; + description?: string | undefined; + priority?: "none" | "low" | "medium" | "high" | "critical" | undefined; + dueDate?: number | undefined; + endTime?: string | undefined; + allDay?: boolean | undefined; + repeat?: "custom" | "none" | "daily" | "weekly" | "monthly" | undefined; + repeatInterval?: number | undefined; + repeatDays?: string | undefined; + seriesId?: string | undefined; + assigneeId?: string | null | undefined; + reminder?: "none" | "at_time" | "15" | "30" | "60" | "120" | "1440" | undefined; + reminders?: string | undefined; + parentSubtaskId?: string | null | undefined; }, { id: string; createdAt: number; @@ -322,6 +497,20 @@ export declare const pushChangesSchema: z.ZodObject<{ completed: boolean; taskId: string; order?: number | undefined; + description?: string | undefined; + priority?: "none" | "low" | "medium" | "high" | "critical" | undefined; + dueDate?: number | undefined; + dueTime?: string | undefined; + endTime?: string | undefined; + allDay?: boolean | undefined; + repeat?: "" | "custom" | "none" | "daily" | "weekly" | "monthly" | undefined; + repeatInterval?: number | undefined; + repeatDays?: string | undefined; + seriesId?: string | undefined; + assigneeId?: string | null | undefined; + reminder?: "none" | "at_time" | "15" | "30" | "60" | "120" | "1440" | undefined; + reminders?: string | undefined; + parentSubtaskId?: string | null | undefined; }>, "many">>; repeatProfiles: z.ZodOptional, "many">>; }, "strip", z.ZodTypeAny, { - friendships?: { - id: string; - createdAt: number; - userId: string; - friendId: string; - status: "pending" | "accepted"; - updatedAt: number; - }[] | undefined; categories?: { id: string; name: string; @@ -392,23 +573,26 @@ export declare const pushChangesSchema: z.ZodObject<{ id: string; createdAt: number; updatedAt: number; - categoryId: string; + categoryId: string | null; title: string; completed: boolean; + dueTime: string; + subtasks?: { + title: string; + }[] | undefined; description?: string | undefined; priority?: "none" | "low" | "medium" | "high" | "critical" | undefined; dueDate?: number | undefined; - dueTime?: string | null | undefined; endTime?: string | undefined; + allDay?: boolean | undefined; repeat?: "custom" | "none" | "daily" | "weekly" | "monthly" | undefined; repeatInterval?: number | undefined; repeatDays?: string | undefined; seriesId?: string | undefined; assigneeId?: string | null | undefined; reminder?: "none" | "at_time" | "15" | "30" | "60" | "120" | "1440" | undefined; - subtasks?: { - title: string; - }[] | undefined; + reminders?: string | undefined; + completedAt?: number | null | undefined; }[] | undefined; subtasks?: { id: string; @@ -416,8 +600,22 @@ export declare const pushChangesSchema: z.ZodObject<{ updatedAt: number; title: string; completed: boolean; + dueTime: string; taskId: string; order?: number | undefined; + description?: string | undefined; + priority?: "none" | "low" | "medium" | "high" | "critical" | undefined; + dueDate?: number | undefined; + endTime?: string | undefined; + allDay?: boolean | undefined; + repeat?: "custom" | "none" | "daily" | "weekly" | "monthly" | undefined; + repeatInterval?: number | undefined; + repeatDays?: string | undefined; + seriesId?: string | undefined; + assigneeId?: string | null | undefined; + reminder?: "none" | "at_time" | "15" | "30" | "60" | "120" | "1440" | undefined; + reminders?: string | undefined; + parentSubtaskId?: string | null | undefined; }[] | undefined; repeatProfiles?: { id: string; @@ -428,15 +626,15 @@ export declare const pushChangesSchema: z.ZodObject<{ repeatInterval: number; repeatDays: string; }[] | undefined; + friendships?: { + id: string; + createdAt: number; + userId: string; + friendId: string; + status: "pending" | "accepted"; + updatedAt: number; + }[] | undefined; }, { - friendships?: { - id: string; - createdAt: number; - userId: string; - friendId: string; - status: "pending" | "accepted"; - updatedAt: number; - }[] | undefined; categories?: { id: string; name: string; @@ -449,23 +647,26 @@ export declare const pushChangesSchema: z.ZodObject<{ id: string; createdAt: number; updatedAt: number; - categoryId: string; + categoryId: string | null; title: string; completed: boolean; + subtasks?: { + title: string; + }[] | undefined; description?: string | undefined; priority?: "none" | "low" | "medium" | "high" | "critical" | undefined; dueDate?: number | undefined; - dueTime?: string | null | undefined; + dueTime?: string | undefined; endTime?: string | undefined; - repeat?: "custom" | "none" | "daily" | "weekly" | "monthly" | undefined; + allDay?: boolean | undefined; + repeat?: "" | "custom" | "none" | "daily" | "weekly" | "monthly" | undefined; repeatInterval?: number | undefined; repeatDays?: string | undefined; seriesId?: string | undefined; assigneeId?: string | null | undefined; reminder?: "none" | "at_time" | "15" | "30" | "60" | "120" | "1440" | undefined; - subtasks?: { - title: string; - }[] | undefined; + reminders?: string | undefined; + completedAt?: number | null | undefined; }[] | undefined; subtasks?: { id: string; @@ -475,6 +676,20 @@ export declare const pushChangesSchema: z.ZodObject<{ completed: boolean; taskId: string; order?: number | undefined; + description?: string | undefined; + priority?: "none" | "low" | "medium" | "high" | "critical" | undefined; + dueDate?: number | undefined; + dueTime?: string | undefined; + endTime?: string | undefined; + allDay?: boolean | undefined; + repeat?: "" | "custom" | "none" | "daily" | "weekly" | "monthly" | undefined; + repeatInterval?: number | undefined; + repeatDays?: string | undefined; + seriesId?: string | undefined; + assigneeId?: string | null | undefined; + reminder?: "none" | "at_time" | "15" | "30" | "60" | "120" | "1440" | undefined; + reminders?: string | undefined; + parentSubtaskId?: string | null | undefined; }[] | undefined; repeatProfiles?: { id: string; @@ -485,18 +700,31 @@ export declare const pushChangesSchema: z.ZodObject<{ repeatInterval: number; repeatDays: string; }[] | undefined; + friendships?: { + id: string; + createdAt: number; + userId: string; + friendId: string; + status: "pending" | "accepted"; + updatedAt: number; + }[] | undefined; }>; + deleted: z.ZodOptional; + id: z.ZodString; + updatedAt: z.ZodNumber; + }, "strip", z.ZodTypeAny, { + id: string; + updatedAt: number; + entity: "categories" | "tasks" | "subtasks" | "repeatProfiles" | "friendships"; + }, { + id: string; + updatedAt: number; + entity: "categories" | "tasks" | "subtasks" | "repeatProfiles" | "friendships"; + }>, "many">>; lastPulledAt: z.ZodNumber; }, "strip", z.ZodTypeAny, { changes: { - friendships?: { - id: string; - createdAt: number; - userId: string; - friendId: string; - status: "pending" | "accepted"; - updatedAt: number; - }[] | undefined; categories?: { id: string; name: string; @@ -509,23 +737,26 @@ export declare const pushChangesSchema: z.ZodObject<{ id: string; createdAt: number; updatedAt: number; - categoryId: string; + categoryId: string | null; title: string; completed: boolean; + dueTime: string; + subtasks?: { + title: string; + }[] | undefined; description?: string | undefined; priority?: "none" | "low" | "medium" | "high" | "critical" | undefined; dueDate?: number | undefined; - dueTime?: string | null | undefined; endTime?: string | undefined; + allDay?: boolean | undefined; repeat?: "custom" | "none" | "daily" | "weekly" | "monthly" | undefined; repeatInterval?: number | undefined; repeatDays?: string | undefined; seriesId?: string | undefined; assigneeId?: string | null | undefined; reminder?: "none" | "at_time" | "15" | "30" | "60" | "120" | "1440" | undefined; - subtasks?: { - title: string; - }[] | undefined; + reminders?: string | undefined; + completedAt?: number | null | undefined; }[] | undefined; subtasks?: { id: string; @@ -533,8 +764,22 @@ export declare const pushChangesSchema: z.ZodObject<{ updatedAt: number; title: string; completed: boolean; + dueTime: string; taskId: string; order?: number | undefined; + description?: string | undefined; + priority?: "none" | "low" | "medium" | "high" | "critical" | undefined; + dueDate?: number | undefined; + endTime?: string | undefined; + allDay?: boolean | undefined; + repeat?: "custom" | "none" | "daily" | "weekly" | "monthly" | undefined; + repeatInterval?: number | undefined; + repeatDays?: string | undefined; + seriesId?: string | undefined; + assigneeId?: string | null | undefined; + reminder?: "none" | "at_time" | "15" | "30" | "60" | "120" | "1440" | undefined; + reminders?: string | undefined; + parentSubtaskId?: string | null | undefined; }[] | undefined; repeatProfiles?: { id: string; @@ -545,18 +790,23 @@ export declare const pushChangesSchema: z.ZodObject<{ repeatInterval: number; repeatDays: string; }[] | undefined; + friendships?: { + id: string; + createdAt: number; + userId: string; + friendId: string; + status: "pending" | "accepted"; + updatedAt: number; + }[] | undefined; }; lastPulledAt: number; + deleted?: { + id: string; + updatedAt: number; + entity: "categories" | "tasks" | "subtasks" | "repeatProfiles" | "friendships"; + }[] | undefined; }, { changes: { - friendships?: { - id: string; - createdAt: number; - userId: string; - friendId: string; - status: "pending" | "accepted"; - updatedAt: number; - }[] | undefined; categories?: { id: string; name: string; @@ -569,23 +819,26 @@ export declare const pushChangesSchema: z.ZodObject<{ id: string; createdAt: number; updatedAt: number; - categoryId: string; + categoryId: string | null; title: string; completed: boolean; + subtasks?: { + title: string; + }[] | undefined; description?: string | undefined; priority?: "none" | "low" | "medium" | "high" | "critical" | undefined; dueDate?: number | undefined; - dueTime?: string | null | undefined; + dueTime?: string | undefined; endTime?: string | undefined; - repeat?: "custom" | "none" | "daily" | "weekly" | "monthly" | undefined; + allDay?: boolean | undefined; + repeat?: "" | "custom" | "none" | "daily" | "weekly" | "monthly" | undefined; repeatInterval?: number | undefined; repeatDays?: string | undefined; seriesId?: string | undefined; assigneeId?: string | null | undefined; reminder?: "none" | "at_time" | "15" | "30" | "60" | "120" | "1440" | undefined; - subtasks?: { - title: string; - }[] | undefined; + reminders?: string | undefined; + completedAt?: number | null | undefined; }[] | undefined; subtasks?: { id: string; @@ -595,6 +848,20 @@ export declare const pushChangesSchema: z.ZodObject<{ completed: boolean; taskId: string; order?: number | undefined; + description?: string | undefined; + priority?: "none" | "low" | "medium" | "high" | "critical" | undefined; + dueDate?: number | undefined; + dueTime?: string | undefined; + endTime?: string | undefined; + allDay?: boolean | undefined; + repeat?: "" | "custom" | "none" | "daily" | "weekly" | "monthly" | undefined; + repeatInterval?: number | undefined; + repeatDays?: string | undefined; + seriesId?: string | undefined; + assigneeId?: string | null | undefined; + reminder?: "none" | "at_time" | "15" | "30" | "60" | "120" | "1440" | undefined; + reminders?: string | undefined; + parentSubtaskId?: string | null | undefined; }[] | undefined; repeatProfiles?: { id: string; @@ -605,8 +872,21 @@ export declare const pushChangesSchema: z.ZodObject<{ repeatInterval: number; repeatDays: string; }[] | undefined; + friendships?: { + id: string; + createdAt: number; + userId: string; + friendId: string; + status: "pending" | "accepted"; + updatedAt: number; + }[] | undefined; }; lastPulledAt: number; + deleted?: { + id: string; + updatedAt: number; + entity: "categories" | "tasks" | "subtasks" | "repeatProfiles" | "friendships"; + }[] | undefined; }>; export declare const syncQuerySchema: z.ZodObject<{ since: z.ZodPipeline, z.ZodNumber>; diff --git a/backend/dist/utils/validation.d.ts.map b/backend/dist/utils/validation.d.ts.map index 4064461..65140d6 100644 --- a/backend/dist/utils/validation.d.ts.map +++ b/backend/dist/utils/validation.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../../src/utils/validation.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,eAAO,MAAM,oBAAoB;;;;;;;;;;;;EAI/B,CAAC;AAEH,eAAO,MAAM,oBAAoB;;;;;;;;;;;;EAI/B,CAAC;AAEH,eAAO,MAAM,YAAY,6DAA2D,CAAC;AAErF,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAe3B,CAAC;AAEH,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAU3B,CAAC;AAEH,eAAO,MAAM,mBAAmB;;;;;;;;;EAG9B,CAAC;AAEH,eAAO,MAAM,mBAAmB;;;;;;;;;;;;EAI9B,CAAC;AAEH,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;EAO7B,CAAC;AAEH,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;EAK9B,CAAC;AAEH,eAAO,MAAM,mBAAmB;;;;;;EAE9B,CAAC;AAEH,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;;EAO3B,CAAC;AAEH,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAS5B,CAAC;AAEH,eAAO,MAAM,eAAe;;;;;;EAE1B,CAAC;AAEH,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAU1B,CAAC"} \ No newline at end of file +{"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../../src/utils/validation.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAKxD;AAED,eAAO,MAAM,oBAAoB;;;;;;;;;;;;EAI/B,CAAC;AAEH,eAAO,MAAM,oBAAoB;;;;;;;;;;;;EAI/B,CAAC;AAEH,eAAO,MAAM,YAAY,6DAA2D,CAAC;AAIrF,eAAO,MAAM,iBAAiB,uOAGjB,CAAC;AACd,eAAO,MAAM,yBAAyB,0DAMzB,CAAC;AAEd,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAkB3B,CAAC;AAEH,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAY3B,CAAC;AAEH,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAiB9B,CAAC;AAEH,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAkB9B,CAAC;AAEH,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;EAO7B,CAAC;AAEH,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;EAK9B,CAAC;AAEH,eAAO,MAAM,yBAAyB;;;;;;;;;;;;;;;EAKpC,CAAC;AAEH,eAAO,MAAM,iBAAiB;;;;;;EAE5B,CAAC;AAEH,eAAO,MAAM,mBAAmB;;;;;;EAE9B,CAAC;AAEH,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;;EAO3B,CAAC;AAEH,eAAO,MAAM,eAAe;;;;;;;;;;;;EAI1B,CAAC;AAEH,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAU5B,CAAC;AAEH,eAAO,MAAM,eAAe;;;;;;EAE1B,CAAC;AAEH,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAU1B,CAAC"} \ No newline at end of file diff --git a/backend/dist/utils/validation.js b/backend/dist/utils/validation.js index 78ac86d..e5488da 100644 --- a/backend/dist/utils/validation.js +++ b/backend/dist/utils/validation.js @@ -1,7 +1,15 @@ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -exports.taskQuerySchema = exports.syncQuerySchema = exports.pushChangesSchema = exports.friendshipSchema = exports.friendRequestSchema = exports.repeatProfileSchema = exports.userSettingsSchema = exports.subtaskUpdateSchema = exports.subtaskCreateSchema = exports.taskUpdateSchema = exports.taskCreateSchema = exports.repeatSchema = exports.categoryUpdateSchema = exports.categoryCreateSchema = void 0; +exports.taskQuerySchema = exports.syncQuerySchema = exports.pushChangesSchema = exports.tombstoneSchema = exports.friendshipSchema = exports.friendRequestSchema = exports.searchQuerySchema = exports.repeatProfileUpdateSchema = exports.repeatProfileSchema = exports.userSettingsSchema = exports.subtaskUpdateSchema = exports.subtaskCreateSchema = exports.taskUpdateSchema = exports.taskCreateSchema = exports.repeatIntervalFieldSchema = exports.repeatFieldSchema = exports.repeatSchema = exports.categoryUpdateSchema = exports.categoryCreateSchema = void 0; +exports.canCompleteTask = canCompleteTask; const zod_1 = require("zod"); +function canCompleteTask(dueDate) { + if (dueDate === 0) + return true; + const now = new Date(); + const endOfToday = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 23, 59, 59, 999).getTime(); + return dueDate <= endOfToday; +} exports.categoryCreateSchema = zod_1.z.object({ name: zod_1.z.string().min(1).max(50), color: zod_1.z.string().regex(/^#[0-9A-Fa-f]{6}$/), @@ -13,41 +21,87 @@ exports.categoryUpdateSchema = zod_1.z.object({ order: zod_1.z.number().int().min(0).optional(), }); exports.repeatSchema = zod_1.z.enum(['none', 'daily', 'weekly', 'monthly', 'custom']); +// The client stores '' / 0 for unset repeat values; normalize them so stale or +// legacy rows never fail validation on push. +exports.repeatFieldSchema = exports.repeatSchema + .or(zod_1.z.literal('')) + .transform((v) => (v === '' ? 'none' : v)) + .optional(); +exports.repeatIntervalFieldSchema = zod_1.z + .number() + .int() + .min(0) + .max(30) + .transform((v) => Math.max(1, v)) + .optional(); exports.taskCreateSchema = zod_1.z.object({ title: zod_1.z.string().min(1).max(100), description: zod_1.z.string().max(1000).optional(), - categoryId: zod_1.z.string().min(1), + categoryId: zod_1.z.string().max(100).transform((v) => (v === '' ? null : v)).nullable(), priority: zod_1.z.enum(['none', 'low', 'medium', 'high', 'critical']).optional(), dueDate: zod_1.z.number().int().min(0).optional(), - dueTime: zod_1.z.string().regex(/^([01]\d|2[0-3]):([0-5]\d)$/).optional().nullable(), + dueTime: zod_1.z.string().regex(/^([01]\d|2[0-3]):([0-5]\d)$/).optional().or(zod_1.z.literal('')).transform(v => v ?? ''), endTime: zod_1.z.string().regex(/^([01]\d|2[0-3]):([0-5]\d)$/).optional().or(zod_1.z.literal('')), - repeat: exports.repeatSchema.optional(), - repeatInterval: zod_1.z.number().int().min(1).max(30).optional(), + allDay: zod_1.z.boolean().optional(), + repeat: exports.repeatFieldSchema, + repeatInterval: exports.repeatIntervalFieldSchema, repeatDays: zod_1.z.string().max(20).optional(), seriesId: zod_1.z.string().max(50).optional(), reminder: zod_1.z.enum(['none', 'at_time', '15', '30', '60', '120', '1440']).optional(), + reminders: zod_1.z.string().max(100).optional(), assigneeId: zod_1.z.string().nullable().optional(), + completedAt: zod_1.z.number().int().min(0).nullable().optional(), subtasks: zod_1.z.array(zod_1.z.object({ title: zod_1.z.string().min(1).max(100) })).optional(), }); exports.taskUpdateSchema = zod_1.z.object({ title: zod_1.z.string().min(1).max(100).optional(), description: zod_1.z.string().max(1000).optional(), - categoryId: zod_1.z.string().min(1).optional(), + categoryId: zod_1.z.string().max(100).nullable().optional().transform((v) => (v === '' ? null : v)), priority: zod_1.z.enum(['none', 'low', 'medium', 'high', 'critical']).optional(), completed: zod_1.z.boolean().optional(), dueDate: zod_1.z.number().int().min(0).optional(), - dueTime: zod_1.z.string().regex(/^([01]\d|2[0-3]):([0-5]\d)$/).optional().nullable(), + dueTime: zod_1.z.string().regex(/^([01]\d|2[0-3]):([0-5]\d)$/).optional().or(zod_1.z.literal('')).transform(v => v === '' ? undefined : v), endTime: zod_1.z.string().regex(/^([01]\d|2[0-3]):([0-5]\d)$/).optional(), + reminders: zod_1.z.string().max(100).optional(), assigneeId: zod_1.z.string().nullable().optional(), + completedAt: zod_1.z.number().int().min(0).nullable().optional(), }); exports.subtaskCreateSchema = zod_1.z.object({ title: zod_1.z.string().min(1).max(100), + description: zod_1.z.string().max(1000).optional(), + priority: zod_1.z.enum(['none', 'low', 'medium', 'high', 'critical']).optional(), + dueDate: zod_1.z.number().int().min(0).optional(), + dueTime: zod_1.z.string().regex(/^([01]\d|2[0-3]):([0-5]\d)$/).optional().or(zod_1.z.literal('')).transform(v => v ?? ''), + endTime: zod_1.z.string().regex(/^([01]\d|2[0-3]):([0-5]\d)$/).optional().or(zod_1.z.literal('')), + allDay: zod_1.z.boolean().optional(), + repeat: exports.repeatFieldSchema, + repeatInterval: exports.repeatIntervalFieldSchema, + repeatDays: zod_1.z.string().max(20).optional(), + seriesId: zod_1.z.string().max(50).optional(), + reminder: zod_1.z.enum(['none', 'at_time', '15', '30', '60', '120', '1440']).optional(), + reminders: zod_1.z.string().max(100).optional(), + assigneeId: zod_1.z.string().nullable().optional(), order: zod_1.z.number().int().min(0).optional(), + parentSubtaskId: zod_1.z.string().nullable().optional(), }); exports.subtaskUpdateSchema = zod_1.z.object({ title: zod_1.z.string().min(1).max(100).optional(), + description: zod_1.z.string().max(1000).optional(), + priority: zod_1.z.enum(['none', 'low', 'medium', 'high', 'critical']).optional(), completed: zod_1.z.boolean().optional(), + dueDate: zod_1.z.number().int().min(0).optional(), + dueTime: zod_1.z.string().regex(/^([01]\d|2[0-3]):([0-5]\d)$/).optional().or(zod_1.z.literal('')).transform(v => v === '' ? undefined : v), + endTime: zod_1.z.string().regex(/^([01]\d|2[0-3]):([0-5]\d)$/).optional(), + allDay: zod_1.z.boolean().optional(), + repeat: exports.repeatFieldSchema, + repeatInterval: exports.repeatIntervalFieldSchema, + repeatDays: zod_1.z.string().max(20).optional(), + seriesId: zod_1.z.string().max(50).optional(), + reminder: zod_1.z.enum(['none', 'at_time', '15', '30', '60', '120', '1440']).optional(), + reminders: zod_1.z.string().max(100).optional(), + assigneeId: zod_1.z.string().nullable().optional(), order: zod_1.z.number().int().min(0).optional(), + parentSubtaskId: zod_1.z.string().nullable().optional(), }); exports.userSettingsSchema = zod_1.z.object({ darkMode: zod_1.z.boolean().optional(), @@ -63,6 +117,15 @@ exports.repeatProfileSchema = zod_1.z.object({ repeatInterval: zod_1.z.number().int().min(1).max(30), repeatDays: zod_1.z.string().max(20), }); +exports.repeatProfileUpdateSchema = zod_1.z.object({ + name: zod_1.z.string().min(1).max(50).optional(), + repeat: exports.repeatSchema.optional(), + repeatInterval: zod_1.z.number().int().min(1).max(30).optional(), + repeatDays: zod_1.z.string().max(20).optional(), +}); +exports.searchQuerySchema = zod_1.z.object({ + q: zod_1.z.string().min(1).max(50), +}); exports.friendRequestSchema = zod_1.z.object({ username: zod_1.z.string().min(1).max(50), }); @@ -74,14 +137,20 @@ exports.friendshipSchema = zod_1.z.object({ createdAt: zod_1.z.number(), updatedAt: zod_1.z.number(), }); +exports.tombstoneSchema = zod_1.z.object({ + entity: zod_1.z.enum(['categories', 'tasks', 'subtasks', 'repeatProfiles', 'friendships']), + id: zod_1.z.string(), + updatedAt: zod_1.z.number().int().min(0), +}); exports.pushChangesSchema = zod_1.z.object({ changes: zod_1.z.object({ categories: zod_1.z.array(exports.categoryCreateSchema.extend({ id: zod_1.z.string(), createdAt: zod_1.z.number(), updatedAt: zod_1.z.number() })).optional(), - tasks: zod_1.z.array(exports.taskCreateSchema.extend({ id: zod_1.z.string(), completed: zod_1.z.boolean(), createdAt: zod_1.z.number(), updatedAt: zod_1.z.number() })).optional(), + tasks: zod_1.z.array(exports.taskCreateSchema.extend({ id: zod_1.z.string(), completed: zod_1.z.boolean(), createdAt: zod_1.z.number(), updatedAt: zod_1.z.number(), completedAt: zod_1.z.number().int().min(0).nullable().optional() })).optional(), subtasks: zod_1.z.array(exports.subtaskCreateSchema.extend({ id: zod_1.z.string(), taskId: zod_1.z.string(), completed: zod_1.z.boolean(), createdAt: zod_1.z.number(), updatedAt: zod_1.z.number() })).optional(), repeatProfiles: zod_1.z.array(exports.repeatProfileSchema.extend({ id: zod_1.z.string(), createdAt: zod_1.z.number(), updatedAt: zod_1.z.number() })).optional(), friendships: zod_1.z.array(exports.friendshipSchema).optional(), }), + deleted: zod_1.z.array(exports.tombstoneSchema).optional(), lastPulledAt: zod_1.z.number().int().min(0), }); exports.syncQuerySchema = zod_1.z.object({ diff --git a/backend/dist/utils/validation.js.map b/backend/dist/utils/validation.js.map index df61242..b210b96 100644 --- a/backend/dist/utils/validation.js.map +++ b/backend/dist/utils/validation.js.map @@ -1 +1 @@ -{"version":3,"file":"validation.js","sourceRoot":"","sources":["../../src/utils/validation.ts"],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"validation.js","sourceRoot":"","sources":["../../src/utils/validation.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/backend/src/db/schema.ts b/backend/src/db/schema.ts index 3318804..ace6a53 100644 --- a/backend/src/db/schema.ts +++ b/backend/src/db/schema.ts @@ -1,6 +1,9 @@ -import { pgTable, text, integer, bigint, boolean, timestamp, unique, index } from 'drizzle-orm/pg-core'; +import { pgTable, text, integer, bigint, boolean, unique, index, primaryKey, AnyPgColumn } from 'drizzle-orm/pg-core'; import { sql } from 'drizzle-orm'; +export const ENTITIES = ['categories', 'tasks', 'subtasks', 'repeatProfiles', 'friendships'] as const; +export type EntityName = (typeof ENTITIES)[number]; + export const users = pgTable('users', { id: text('id').primaryKey(), username: text('username').notNull().unique(), @@ -40,7 +43,7 @@ export const categories = pgTable('categories', { export const tasks = pgTable('tasks', { id: text('id').primaryKey(), userId: text('user_id').notNull().references(() => users.id, { onDelete: 'cascade' }), - categoryId: text('category_id').notNull().references(() => categories.id, { onDelete: 'cascade' }), + categoryId: text('category_id').references(() => categories.id, { onDelete: 'cascade' }), title: text('title').notNull(), description: text('description').notNull().default(''), priority: text('priority', { enum: ['none', 'low', 'medium', 'high', 'critical'] }).notNull().default('none'), @@ -56,26 +59,31 @@ export const tasks = pgTable('tasks', { assigneeId: text('assignee_id').references(() => users.id, { onDelete: 'set null' }), reminder: text('reminder', { enum: ['none', 'at_time', '15', '30', '60', '120', '1440'] }).notNull().default('none'), reminders: text('reminders').notNull().default(''), + completedAt: bigint('completed_at', { mode: 'number' }), createdAt: bigint('created_at', { mode: 'number' }).notNull().default(sql`EXTRACT(EPOCH FROM NOW()) * 1000`), updatedAt: bigint('updated_at', { mode: 'number' }).notNull().default(sql`EXTRACT(EPOCH FROM NOW()) * 1000`), }); -export const repeatProfiles = pgTable('repeat_profiles', { - id: text('id').primaryKey(), - userId: text('user_id').notNull().references(() => users.id, { onDelete: 'cascade' }), - name: text('name').notNull(), - repeat: text('repeat', { enum: ['none', 'daily', 'weekly', 'monthly', 'custom'] }).notNull().default('none'), - repeatInterval: integer('repeat_interval').notNull().default(1), - repeatDays: text('repeat_days').notNull().default(''), - createdAt: bigint('created_at', { mode: 'number' }).notNull().default(sql`EXTRACT(EPOCH FROM NOW()) * 1000`), - updatedAt: bigint('updated_at', { mode: 'number' }).notNull().default(sql`EXTRACT(EPOCH FROM NOW()) * 1000`), -}); +export const tombstones = pgTable( + 'tombstones', + { + entity: text('entity', { enum: ENTITIES }).notNull(), + entityId: text('entity_id').notNull(), + userId: text('user_id').notNull().references(() => users.id, { onDelete: 'cascade' }), + updatedAt: bigint('updated_at', { mode: 'number' }).notNull(), + }, + (t) => ({ + pk: primaryKey({ columns: [t.entity, t.entityId] }), + userIdx: index('tombstones_user_updated_idx').on(t.userId, t.updatedAt), + }) +); +// Define subtasks with explicit type to avoid circular reference export const subtasks = pgTable('subtasks', { id: text('id').primaryKey(), userId: text('user_id').notNull().references(() => users.id, { onDelete: 'cascade' }), taskId: text('task_id').notNull().references(() => tasks.id, { onDelete: 'cascade' }), - parentSubtaskId: text('parent_subtask_id').references(() => subtasks.id, { onDelete: 'cascade' }), + parentSubtaskId: text('parent_subtask_id').references((): AnyPgColumn => subtasks.id, { onDelete: 'cascade' }), title: text('title').notNull(), description: text('description').notNull().default(''), priority: text('priority', { enum: ['none', 'low', 'medium', 'high', 'critical'] }).notNull().default('none'), @@ -94,6 +102,21 @@ export const subtasks = pgTable('subtasks', { order: integer('order').notNull().default(0), createdAt: bigint('created_at', { mode: 'number' }).notNull().default(sql`EXTRACT(EPOCH FROM NOW()) * 1000`), updatedAt: bigint('updated_at', { mode: 'number' }).notNull().default(sql`EXTRACT(EPOCH FROM NOW()) * 1000`), +}, (t) => ({ + parentIdx: index('subtasks_parent_idx').on(t.parentSubtaskId), + taskIdx: index('subtasks_task_idx').on(t.taskId), + userIdx: index('subtasks_user_idx').on(t.userId), +})); + +export const repeatProfiles = pgTable('repeat_profiles', { + id: text('id').primaryKey(), + userId: text('user_id').notNull().references(() => users.id, { onDelete: 'cascade' }), + name: text('name').notNull(), + repeat: text('repeat', { enum: ['none', 'daily', 'weekly', 'monthly', 'custom'] }).notNull().default('none'), + repeatInterval: integer('repeat_interval').notNull().default(1), + repeatDays: text('repeat_days').notNull().default(''), + createdAt: bigint('created_at', { mode: 'number' }).notNull().default(sql`EXTRACT(EPOCH FROM NOW()) * 1000`), + updatedAt: bigint('updated_at', { mode: 'number' }).notNull().default(sql`EXTRACT(EPOCH FROM NOW()) * 1000`), }); export const userSettings = pgTable('user_settings', { @@ -105,4 +128,4 @@ export const userSettings = pgTable('user_settings', { sortBy: text('sort_by', { enum: ['dueDate', 'priority', 'title', 'createdAt'] }).notNull().default('dueDate'), sortOrder: text('sort_order', { enum: ['asc', 'desc'] }).notNull().default('asc'), updatedAt: bigint('updated_at', { mode: 'number' }).notNull().default(sql`EXTRACT(EPOCH FROM NOW()) * 1000`), -}); \ No newline at end of file +}); diff --git a/backend/src/routes/subtasks.ts b/backend/src/routes/subtasks.ts index f60205d..1aac289 100644 --- a/backend/src/routes/subtasks.ts +++ b/backend/src/routes/subtasks.ts @@ -2,7 +2,7 @@ import { Router, Request, Response } from 'express'; import { asyncHandler } from '../utils/asyncHandler'; import { db } from '../db'; import { subtasks, tasks } from '../db/schema'; -import { eq, and, asc, desc } from 'drizzle-orm'; +import { eq, and, asc, desc, isNull } from 'drizzle-orm'; import { authMiddleware } from '../utils/auth'; import { AppError } from '../middleware/errorHandler'; import { subtaskCreateSchema, subtaskUpdateSchema } from '../utils/validation'; @@ -70,7 +70,7 @@ router.post('/task/:taskId', asyncHandler(async (req: Request, res: Response) => .where(and( eq(subtasks.taskId, req.params.taskId), eq(subtasks.userId, userId), - parentSubtaskId ? eq(subtasks.parentSubtaskId, parentSubtaskId) : eq(subtasks.parentSubtaskId, null) + parentSubtaskId ? eq(subtasks.parentSubtaskId, parentSubtaskId) : isNull(subtasks.parentSubtaskId) )) .orderBy(desc(subtasks.order)) .limit(1); @@ -117,7 +117,7 @@ router.patch('/:id', asyncHandler(async (req: Request, res: Response) => { const data = subtaskUpdateSchema.parse(req.body); const userId = req.user!.userId; - const existing = await db + const existing: any[] = await db .select() .from(subtasks) .where(and(eq(subtasks.id, req.params.id), eq(subtasks.userId, userId))) diff --git a/backend/src/routes/sync.ts b/backend/src/routes/sync.ts index 13727b6..236e46c 100644 --- a/backend/src/routes/sync.ts +++ b/backend/src/routes/sync.ts @@ -1,8 +1,8 @@ import { Router, Request, Response } from 'express'; import { asyncHandler } from '../utils/asyncHandler'; import { db } from '../db'; -import { categories, tasks, subtasks, repeatProfiles, users, friendships } from '../db/schema'; -import { eq, and, gte, lte, asc, or, inArray, sql } from 'drizzle-orm'; +import { categories, tasks, subtasks, repeatProfiles, users, friendships, tombstones, type EntityName } from '../db/schema'; +import { eq, and, gte, asc, or, inArray, sql } from 'drizzle-orm'; import { authMiddleware } from '../utils/auth'; import { AppError } from '../middleware/errorHandler'; import { syncQuerySchema, pushChangesSchema, canCompleteTask } from '../utils/validation'; @@ -32,17 +32,15 @@ router.get('/', asyncHandler(async (req: Request, res: Response) => { .orderBy(asc(tasks.updatedAt)); // Fetch subtasks changed since timestamp - const taskIds = changedTasks.map(t => t.id); let changedSubtasks: any[] = []; - - if (taskIds.length > 0) { + if (changedTasks.length > 0) { changedSubtasks = await db .select() .from(subtasks) .where(and(eq(subtasks.userId, userId), gte(subtasks.updatedAt, sinceDate))) .orderBy(asc(subtasks.updatedAt)); } else { - // Also fetch subtasks for tasks that might have been deleted (we track by updatedAt) + // Also fetch subtasks for tasks that might have been deleted changedSubtasks = await db .select() .from(subtasks) @@ -67,6 +65,13 @@ router.get('/', asyncHandler(async (req: Request, res: Response) => { )) .orderBy(asc(friendships.updatedAt)); + // Fetch tombstones (deletions) changed since timestamp + const changedTombstones = await db + .select() + .from(tombstones) + .where(and(eq(tombstones.userId, userId), gte(tombstones.updatedAt, sinceDate))) + .orderBy(asc(tombstones.updatedAt)); + const timestamp = Date.now(); res.json({ @@ -75,6 +80,7 @@ router.get('/', asyncHandler(async (req: Request, res: Response) => { subtasks: changedSubtasks, repeatProfiles: changedRepeatProfiles, friendships: changedFriendships, + deleted: changedTombstones.map((t) => ({ entity: t.entity, id: t.entityId, updatedAt: t.updatedAt })), timestamp, }); })); @@ -125,6 +131,27 @@ router.post('/push', asyncHandler(async (req: Request, res: Response) => { } } + // Sanitize assignee references: only real user ids may be stored (FK). + // Stale/unknown assignee ids are silently dropped to null instead of + // failing the whole push transaction. + const assigneeIds = new Set(); + for (const task of data.changes.tasks ?? []) { + if (task.assigneeId) assigneeIds.add(task.assigneeId); + } + for (const sub of data.changes.subtasks ?? []) { + if (sub.assigneeId) assigneeIds.add(sub.assigneeId); + } + const validAssignees = new Set(); + if (assigneeIds.size > 0) { + const rows = await tx + .select({ id: users.id }) + .from(users) + .where(inArray(users.id, [...assigneeIds])); + for (const r of rows) validAssignees.add(r.id); + } + const sanitizeAssignee = (a: string | null | undefined): string | null => + a && validAssignees.has(a) ? a : null; + // Process categories if (data.changes.categories && data.changes.categories.length > 0) { for (const cat of data.changes.categories) { @@ -185,7 +212,7 @@ router.post('/push', asyncHandler(async (req: Request, res: Response) => { resolution: 'server_wins', }); continue; // Server wins - } + } // Prevent completing tasks with future due dates if (task.completed === true) { @@ -203,30 +230,32 @@ router.post('/push', asyncHandler(async (req: Request, res: Response) => { } await tx - .update(tasks) - .set({ - title: task.title, - description: task.description, - categoryId: task.categoryId, - priority: task.priority, - completed: task.completed, - dueDate: task.dueDate, - dueTime: task.dueTime ?? '', - endTime: task.endTime ?? '', - allDay: task.allDay ?? false, - repeat: task.repeat ?? 'none', - repeatInterval: task.repeatInterval ?? 1, - repeatDays: task.repeatDays ?? '', - seriesId: task.seriesId ?? '', - reminder: task.reminder ?? 'none', - reminders: task.reminders ?? '', - assigneeId: task.assigneeId ?? null, - updatedAt: task.updatedAt, - }) - .where(and(eq(tasks.id, task.id), eq(tasks.userId, userId))); + .update(tasks) + .set({ + title: task.title, + description: task.description, + categoryId: task.categoryId, + priority: task.priority, + completed: task.completed, + dueDate: task.dueDate, + dueTime: task.dueTime ?? '', + endTime: task.endTime ?? '', + allDay: task.allDay ?? false, + repeat: task.repeat ?? 'none', + repeatInterval: task.repeatInterval ?? 1, + repeatDays: task.repeatDays ?? '', + seriesId: task.seriesId ?? '', + reminder: task.reminder ?? 'none', + reminders: task.reminders ?? '', + assigneeId: sanitizeAssignee(task.assigneeId), + completedAt: task.completedAt ?? null, + updatedAt: task.updatedAt, + }) + .where(and(eq(tasks.id, task.id), eq(tasks.userId, userId))); } else { await tx.insert(tasks).values({ ...task, + assigneeId: sanitizeAssignee(task.assigneeId), allDay: task.allDay ?? false, userId, }); @@ -260,6 +289,7 @@ router.post('/push', asyncHandler(async (req: Request, res: Response) => { .update(subtasks) .set({ taskId: sub.taskId, + parentSubtaskId: sub.parentSubtaskId ?? null, title: sub.title, description: sub.description ?? '', priority: sub.priority ?? 'none', @@ -274,7 +304,7 @@ router.post('/push', asyncHandler(async (req: Request, res: Response) => { seriesId: sub.seriesId ?? '', reminder: sub.reminder ?? 'none', reminders: sub.reminders ?? '', - assigneeId: sub.assigneeId ?? null, + assigneeId: sanitizeAssignee(sub.assigneeId), order: sub.order, updatedAt: sub.updatedAt, }) @@ -282,6 +312,7 @@ router.post('/push', asyncHandler(async (req: Request, res: Response) => { } else { await tx.insert(subtasks).values({ ...sub, + assigneeId: sanitizeAssignee(sub.assigneeId), userId, }); } @@ -365,6 +396,14 @@ router.post('/push', asyncHandler(async (req: Request, res: Response) => { } } } + + // Process deletions (tombstones) - last so they see the state produced + // by the upserts above and resolve by last-writer-wins. + if (data.deleted && data.deleted.length > 0) { + for (const deleted of data.deleted) { + await applyTombstone(tx, deleted.entity, deleted.id, deleted.updatedAt, userId, conflicts); + } + } }); } catch (error) { console.error('Sync push error:', error); @@ -378,4 +417,197 @@ router.post('/push', asyncHandler(async (req: Request, res: Response) => { }); })); +// Upsert a tombstone row, keeping the latest updatedAt. +async function upsertTombstone( + tx: any, + entity: EntityName, + entityId: string, + updatedAt: number, + userId: string +): Promise { + const existing = await tx + .select({ updatedAt: tombstones.updatedAt }) + .from(tombstones) + .where(and(eq(tombstones.entity, entity), eq(tombstones.entityId, entityId))) + .limit(1); + + const merged = Math.max(existing[0]?.updatedAt ?? 0, updatedAt); + if (existing.length > 0) { + await tx + .update(tombstones) + .set({ updatedAt: merged }) + .where(and(eq(tombstones.entity, entity), eq(tombstones.entityId, entityId))); + } else { + await tx.insert(tombstones).values({ entity, entityId, userId, updatedAt: merged }); + } +} + +// Apply a client deletion. LWW: if the server row is newer than the deletion +// timestamp, the deletion is rejected (server_wins conflict) so the client +// re-pulls the row. Accepted deletions cascade tombstones to every FK-cascaded +// child so all devices remove them too. +async function applyTombstone( + tx: any, + entity: EntityName, + id: string, + deletedAt: number, + userId: string, + conflicts: any[] +): Promise { + const tombstoneOf = (e: EntityName, ids: string[]) => ids.forEach((i) => upsertTombstone(tx, e, i, deletedAt, userId)); + + if (entity === 'tasks') { + const row = await tx + .select() + .from(tasks) + .where(and(eq(tasks.id, id), eq(tasks.userId, userId))) + .limit(1); + if (row.length === 0) { + await upsertTombstone(tx, entity, id, deletedAt, userId); + return; + } + if (row[0].updatedAt > deletedAt) { + conflicts.push({ + entity: 'tasks', + id, + serverVersion: row[0], + clientVersion: { id, updatedAt: deletedAt }, + resolution: 'server_wins', + }); + return; + } + const children = await tx + .select({ id: subtasks.id }) + .from(subtasks) + .where(and(eq(subtasks.taskId, id), eq(subtasks.userId, userId))); + tombstoneOf('subtasks', children.map((c: any) => c.id)); + await tx.delete(tasks).where(and(eq(tasks.id, id), eq(tasks.userId, userId))); + await upsertTombstone(tx, entity, id, deletedAt, userId); + return; + } + + if (entity === 'categories') { + const row = await tx + .select() + .from(categories) + .where(and(eq(categories.id, id), eq(categories.userId, userId))) + .limit(1); + if (row.length === 0) { + await upsertTombstone(tx, entity, id, deletedAt, userId); + return; + } + if (row[0].updatedAt > deletedAt) { + conflicts.push({ + entity: 'categories', + id, + serverVersion: row[0], + clientVersion: { id, updatedAt: deletedAt }, + resolution: 'server_wins', + }); + return; + } + // Deleting the category cascades its tasks (and their subtasks) - + // tombstone all of them so every client removes them. + const catTasks = await tx + .select() + .from(tasks) + .where(and(eq(tasks.categoryId, id), eq(tasks.userId, userId))); + for (const taskRow of catTasks) { + const subIds = await tx + .select({ id: subtasks.id }) + .from(subtasks) + .where(and(eq(subtasks.taskId, taskRow.id), eq(subtasks.userId, userId))); + tombstoneOf('subtasks', subIds.map((s: any) => s.id)); + tombstoneOf('tasks', [taskRow.id]); + } + await tx.delete(categories).where(and(eq(categories.id, id), eq(categories.userId, userId))); + await upsertTombstone(tx, entity, id, deletedAt, userId); + return; + } + + if (entity === 'subtasks') { + const row = await tx + .select() + .from(subtasks) + .where(and(eq(subtasks.id, id), eq(subtasks.userId, userId))) + .limit(1); + if (row.length === 0) { + await upsertTombstone(tx, entity, id, deletedAt, userId); + return; + } + if (row[0].updatedAt > deletedAt) { + conflicts.push({ + entity: 'subtasks', + id, + serverVersion: row[0], + clientVersion: { id, updatedAt: deletedAt }, + resolution: 'server_wins', + }); + return; + } + // Deleting a parent subtask cascades its children in PG - tombstone them. + const children = await tx + .select({ id: subtasks.id }) + .from(subtasks) + .where(and(eq(subtasks.parentSubtaskId, id), eq(subtasks.userId, userId))); + tombstoneOf('subtasks', children.map((c: any) => c.id)); + await tx.delete(subtasks).where(and(eq(subtasks.id, id), eq(subtasks.userId, userId))); + await upsertTombstone(tx, entity, id, deletedAt, userId); + return; + } + + if (entity === 'repeatProfiles') { + const row = await tx + .select() + .from(repeatProfiles) + .where(and(eq(repeatProfiles.id, id), eq(repeatProfiles.userId, userId))) + .limit(1); + if (row.length === 0) { + await upsertTombstone(tx, entity, id, deletedAt, userId); + return; + } + if (row[0].updatedAt > deletedAt) { + conflicts.push({ + entity: 'repeatProfiles', + id, + serverVersion: row[0], + clientVersion: { id, updatedAt: deletedAt }, + resolution: 'server_wins', + }); + return; + } + await tx.delete(repeatProfiles).where(and(eq(repeatProfiles.id, id), eq(repeatProfiles.userId, userId))); + await upsertTombstone(tx, entity, id, deletedAt, userId); + return; + } + + if (entity === 'friendships') { + const row = await tx + .select() + .from(friendships) + .where(and( + eq(friendships.id, id), + or(eq(friendships.userId, userId), eq(friendships.friendId, userId)) + )) + .limit(1); + if (row.length === 0) { + await upsertTombstone(tx, entity, id, deletedAt, userId); + return; + } + if (row[0].updatedAt > deletedAt) { + conflicts.push({ + entity: 'friendships', + id, + serverVersion: row[0], + clientVersion: { id, updatedAt: deletedAt }, + resolution: 'server_wins', + }); + return; + } + await tx.delete(friendships).where(eq(friendships.id, id)); + await upsertTombstone(tx, entity, id, deletedAt, userId); + return; + } +} + export default router; \ No newline at end of file diff --git a/backend/src/routes/tasks.ts b/backend/src/routes/tasks.ts index 8ebee37..495fbba 100644 --- a/backend/src/routes/tasks.ts +++ b/backend/src/routes/tasks.ts @@ -116,15 +116,17 @@ router.post('/', asyncHandler(async (req: Request, res: Response) => { const userId = req.user!.userId; const now = Date.now(); - // Verify category exists and belongs to user - const cat = await db - .select() - .from(categories) - .where(and(eq(categories.id, data.categoryId), eq(categories.userId, userId))) - .limit(1); + // Verify category exists and belongs to user (optional - tasks may be uncategorized) + if (data.categoryId) { + const cat = await db + .select() + .from(categories) + .where(and(eq(categories.id, data.categoryId), eq(categories.userId, userId))) + .limit(1); - if (cat.length === 0) { - throw new AppError('NOT_FOUND', 'Category not found', 404); + if (cat.length === 0) { + throw new AppError('NOT_FOUND', 'Category not found', 404); + } } const taskId = `task_${Date.now().toString(36)}${Math.random().toString(36).slice(2, 6)}`; @@ -261,12 +263,14 @@ router.post('/batch', asyncHandler(async (req: Request, res: Response) => { for (const op of operations) { try { if (op.type === 'create') { - const cat = await db - .select() - .from(categories) - .where(and(eq(categories.id, op.data.categoryId), eq(categories.userId, userId))) - .limit(1); - if (cat.length === 0) throw new AppError('NOT_FOUND', 'Category not found', 404); + if (op.data.categoryId) { + const cat = await db + .select() + .from(categories) + .where(and(eq(categories.id, op.data.categoryId), eq(categories.userId, userId))) + .limit(1); + if (cat.length === 0) throw new AppError('NOT_FOUND', 'Category not found', 404); + } const taskId = `task_${Date.now().toString(36)}${Math.random().toString(36).slice(2, 6)}`; const now = Date.now(); diff --git a/backend/src/utils/validation.ts b/backend/src/utils/validation.ts index 77c89d1..32dc1d7 100644 --- a/backend/src/utils/validation.ts +++ b/backend/src/utils/validation.ts @@ -21,29 +21,44 @@ export const categoryUpdateSchema = z.object({ export const repeatSchema = z.enum(['none', 'daily', 'weekly', 'monthly', 'custom']); +// The client stores '' / 0 for unset repeat values; normalize them so stale or +// legacy rows never fail validation on push. +export const repeatFieldSchema = repeatSchema + .or(z.literal('')) + .transform((v) => (v === '' ? 'none' : v)) + .optional(); +export const repeatIntervalFieldSchema = z + .number() + .int() + .min(0) + .max(30) + .transform((v) => Math.max(1, v)) + .optional(); + export const taskCreateSchema = z.object({ title: z.string().min(1).max(100), description: z.string().max(1000).optional(), - categoryId: z.string().min(1), + categoryId: z.string().max(100).transform((v) => (v === '' ? null : v)).nullable(), priority: z.enum(['none', 'low', 'medium', 'high', 'critical']).optional(), dueDate: z.number().int().min(0).optional(), dueTime: z.string().regex(/^([01]\d|2[0-3]):([0-5]\d)$/).optional().or(z.literal('')).transform(v => v ?? ''), endTime: z.string().regex(/^([01]\d|2[0-3]):([0-5]\d)$/).optional().or(z.literal('')), allDay: z.boolean().optional(), - repeat: repeatSchema.optional(), - repeatInterval: z.number().int().min(1).max(30).optional(), + repeat: repeatFieldSchema, + repeatInterval: repeatIntervalFieldSchema, repeatDays: z.string().max(20).optional(), seriesId: z.string().max(50).optional(), reminder: z.enum(['none', 'at_time', '15', '30', '60', '120', '1440']).optional(), reminders: z.string().max(100).optional(), assigneeId: z.string().nullable().optional(), + completedAt: z.number().int().min(0).nullable().optional(), subtasks: z.array(z.object({ title: z.string().min(1).max(100) })).optional(), }); export const taskUpdateSchema = z.object({ title: z.string().min(1).max(100).optional(), description: z.string().max(1000).optional(), - categoryId: z.string().min(1).optional(), + categoryId: z.string().max(100).nullable().optional().transform((v) => (v === '' ? null : v)), priority: z.enum(['none', 'low', 'medium', 'high', 'critical']).optional(), completed: z.boolean().optional(), dueDate: z.number().int().min(0).optional(), @@ -51,6 +66,7 @@ export const taskUpdateSchema = z.object({ endTime: z.string().regex(/^([01]\d|2[0-3]):([0-5]\d)$/).optional(), reminders: z.string().max(100).optional(), assigneeId: z.string().nullable().optional(), + completedAt: z.number().int().min(0).nullable().optional(), }); export const subtaskCreateSchema = z.object({ @@ -61,8 +77,8 @@ export const subtaskCreateSchema = z.object({ dueTime: z.string().regex(/^([01]\d|2[0-3]):([0-5]\d)$/).optional().or(z.literal('')).transform(v => v ?? ''), endTime: z.string().regex(/^([01]\d|2[0-3]):([0-5]\d)$/).optional().or(z.literal('')), allDay: z.boolean().optional(), - repeat: repeatSchema.optional(), - repeatInterval: z.number().int().min(1).max(30).optional(), + repeat: repeatFieldSchema, + repeatInterval: repeatIntervalFieldSchema, repeatDays: z.string().max(20).optional(), seriesId: z.string().max(50).optional(), reminder: z.enum(['none', 'at_time', '15', '30', '60', '120', '1440']).optional(), @@ -81,8 +97,8 @@ export const subtaskUpdateSchema = z.object({ dueTime: z.string().regex(/^([01]\d|2[0-3]):([0-5]\d)$/).optional().or(z.literal('')).transform(v => v === '' ? undefined : v), endTime: z.string().regex(/^([01]\d|2[0-3]):([0-5]\d)$/).optional(), allDay: z.boolean().optional(), - repeat: repeatSchema.optional(), - repeatInterval: z.number().int().min(1).max(30).optional(), + repeat: repeatFieldSchema, + repeatInterval: repeatIntervalFieldSchema, repeatDays: z.string().max(20).optional(), seriesId: z.string().max(50).optional(), reminder: z.enum(['none', 'at_time', '15', '30', '60', '120', '1440']).optional(), @@ -132,14 +148,21 @@ export const friendshipSchema = z.object({ updatedAt: z.number(), }); +export const tombstoneSchema = z.object({ + entity: z.enum(['categories', 'tasks', 'subtasks', 'repeatProfiles', 'friendships']), + id: z.string(), + updatedAt: z.number().int().min(0), +}); + export const pushChangesSchema = z.object({ changes: z.object({ categories: z.array(categoryCreateSchema.extend({ id: z.string(), createdAt: z.number(), updatedAt: z.number() })).optional(), - tasks: z.array(taskCreateSchema.extend({ id: z.string(), completed: z.boolean(), createdAt: z.number(), updatedAt: z.number() })).optional(), + tasks: z.array(taskCreateSchema.extend({ id: z.string(), completed: z.boolean(), createdAt: z.number(), updatedAt: z.number(), completedAt: z.number().int().min(0).nullable().optional() })).optional(), subtasks: z.array(subtaskCreateSchema.extend({ id: z.string(), taskId: z.string(), completed: z.boolean(), createdAt: z.number(), updatedAt: z.number() })).optional(), repeatProfiles: z.array(repeatProfileSchema.extend({ id: z.string(), createdAt: z.number(), updatedAt: z.number() })).optional(), friendships: z.array(friendshipSchema).optional(), }), + deleted: z.array(tombstoneSchema).optional(), lastPulledAt: z.number().int().min(0), }); diff --git a/carry-your-live/app.json b/carry-your-live/app.json index 14b0e30..b8302e7 100644 --- a/carry-your-live/app.json +++ b/carry-your-live/app.json @@ -6,11 +6,13 @@ "orientation": "portrait", "icon": "./assets/icon.png", "userInterfaceStyle": "dark", + "backgroundColor": "#121212", "ios": { "supportsTablet": true }, "android": { "softwareKeyboardLayoutMode": "resize", + "backgroundColor": "#121212", "adaptiveIcon": { "backgroundColor": "#E6F4FE", "foregroundImage": "./assets/android-icon-foreground.png", diff --git a/carry-your-live/app/(tabs)/_layout.tsx b/carry-your-live/app/(tabs)/_layout.tsx index 3c77013..2203f44 100644 --- a/carry-your-live/app/(tabs)/_layout.tsx +++ b/carry-your-live/app/(tabs)/_layout.tsx @@ -3,6 +3,7 @@ import React from 'react'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { TabBarIcon } from '@/components/TabBarIcon'; import { useSettings } from '@/theme'; +import { setLastVisitedTab } from '@/utils/tabHistory'; export default function TabLayout() { const { theme } = useSettings(); @@ -12,27 +13,44 @@ export default function TabLayout() { ({ + focus: () => { + if (route.name !== 'settings') { + setLastVisitedTab(route.name); + } + }, + })} > ( - + title: 'ToDo', + tabBarIcon: ({ color, focused }) => ( + ), }} /> @@ -40,8 +58,8 @@ export default function TabLayout() { name="calendar" options={{ title: 'Calendar', - tabBarIcon: ({ focused, color }) => ( - + tabBarIcon: ({ color, focused }) => ( + ), }} /> @@ -49,8 +67,8 @@ export default function TabLayout() { name="stats" options={{ title: 'Stats', - tabBarIcon: ({ focused, color }) => ( - + tabBarIcon: ({ color, focused }) => ( + ), }} /> @@ -58,11 +76,11 @@ export default function TabLayout() { name="settings" options={{ title: 'Settings', - tabBarIcon: ({ focused, color }) => ( - + tabBarIcon: ({ color, focused }) => ( + ), }} /> ); -} +} \ No newline at end of file diff --git a/carry-your-live/app/(tabs)/calendar.tsx b/carry-your-live/app/(tabs)/calendar.tsx index 3c760ad..31ae45f 100644 --- a/carry-your-live/app/(tabs)/calendar.tsx +++ b/carry-your-live/app/(tabs)/calendar.tsx @@ -1,6 +1,6 @@ import React, { useMemo, useRef, useState, useCallback, useEffect } from 'react'; -import { View, Text, StyleSheet, SafeAreaView, ScrollView, TouchableOpacity, Animated, Dimensions } from 'react-native'; -import { useRouter } from 'expo-router'; +import { View, Text, StyleSheet, SafeAreaView, ScrollView, TouchableOpacity, Animated, Dimensions, BackHandler, KeyboardAvoidingView } from 'react-native'; +import { useRouter, useFocusEffect } from 'expo-router'; import { Gesture, GestureDetector } from 'react-native-gesture-handler'; import { Header } from '@/components/Header'; import { useTasksByDate, useTasksInMonth } from '@/hooks/useTasks'; @@ -15,10 +15,10 @@ import Task from '@/models/Task'; import { format, addMonths, addDays, startOfMonth, isSameDay, isSameMonth, isToday } from 'date-fns'; import { Q } from '@nozbe/watermelondb'; import Svg, { Path, Circle } from 'react-native-svg'; +import { desaturate } from '@/theme'; import type { ThemeColors } from '@/theme'; const WIDTH = Dimensions.get('window').width; -const ORANGE = '#FF7043'; const GERMAN_WEEKDAYS = ['mo', 'di', 'mi', 'do', 'fr', 'sa', 'so']; const MONTH_NAMES = [ 'January', 'February', 'March', 'April', 'May', 'June', @@ -43,6 +43,13 @@ export default function CalendarScreen() { const selectedDateRef = useRef(selectedDate); selectedDateRef.current = selectedDate; + useFocusEffect( + useCallback(() => { + const sub = BackHandler.addEventListener('hardwareBackPress', () => true); + return () => sub.remove(); + }, []) + ); + const translateX = useRef(new Animated.Value(0)).current; const animatingRef = useRef(false); const gridWidthRef = useRef(WIDTH); @@ -181,17 +188,17 @@ export default function CalendarScreen() { return ( - - -
+
- - { - gridWidthRef.current = e.nativeEvent.layout.width; - }} - style={[styles.calendarArea, { transform: [{ translateX }] }]} - > + + + + { + gridWidthRef.current = e.nativeEvent.layout.width; + }} + style={[styles.calendarArea, { transform: [{ translateX }] }]} + > transitionTo(-1)} @@ -199,7 +206,7 @@ export default function CalendarScreen() { activeOpacity={0.7} > - + @@ -207,7 +214,7 @@ export default function CalendarScreen() { setMonthPickerVisible(true)} activeOpacity={0.7} style={styles.monthButton}> {format(visibleMonth, 'MMMM')} - + setYearPickerVisible(true)} activeOpacity={0.7} style={styles.yearButton}> @@ -221,7 +228,7 @@ export default function CalendarScreen() { activeOpacity={0.7} > - + @@ -252,8 +259,9 @@ export default function CalendarScreen() { ))} + - + {format(selectedDate, 'EEEE, MMMM d')} {selectedDayTasks.length > 0 && ( @@ -276,14 +284,14 @@ export default function CalendarScreen() { onPress={() => handleToggleComplete(task.id)} activeOpacity={0.7} > - + {task.completed ? ( <> ) : ( - + )} @@ -303,9 +311,7 @@ export default function CalendarScreen() { openTaskEdit(task.id)} activeOpacity={0.7} accessibilityRole="button" accessibilityLabel={`Edit ${task.title}`}> - - - + @@ -337,28 +343,27 @@ export default function CalendarScreen() { )} - + + - {modals(() => {})} + {modals(() => {})} - setMonthPickerVisible(false)} - /> - setYearPickerVisible(false)} - /> - - + setMonthPickerVisible(false)} + /> + setYearPickerVisible(false)} + /> ); } @@ -376,7 +381,7 @@ function cellTitle(tasks: Task[]): string | null { function cellColor(tasks: Task[], categories: { id: string; color: string }[]): string { if (tasks.length === 0) return '#8E8E8E'; const cat = categories.find((c) => c.id === tasks[0].categoryId); - return cat?.color ?? '#8E8E8E'; + return cat ? desaturate(cat.color, 0.3) : '#8E8E8E'; } interface DayCellProps { @@ -393,7 +398,7 @@ interface DayCellProps { const DayCell = React.memo(function DayCell({ day, label, dotColor, selected, today, inMonth, theme, onPress }: DayCellProps) { return ( onPress(day)} activeOpacity={0.7} > @@ -401,8 +406,8 @@ const DayCell = React.memo(function DayCell({ day, label, dotColor, selected, to style={[ styles.dayNumber, { color: theme.text }, - !inMonth && { color: theme.borderStrong }, - today && !selected && { color: ORANGE }, + !inMonth && { color: theme.textMuted }, + today && !selected && { color: theme.accent }, selected && styles.dayNumberSelected, ]} > @@ -425,12 +430,15 @@ const styles = StyleSheet.create({ container: { flex: 1, }, - flex: { + scrollContent: { + paddingBottom: 16, + flexGrow: 1, + }, + scrollBody: { flex: 1, }, - scrollContent: { - paddingBottom: 96, - flexGrow: 1, + kbAvoid: { + flex: 1, }, calendarArea: { paddingHorizontal: 12, @@ -565,8 +573,8 @@ const styles = StyleSheet.create({ alignItems: 'center', }, checkCircle: { - width: 24, - marginRight: 10, + width: 30, + marginRight: 12, }, eventTitleTouch: { flex: 1, diff --git a/carry-your-live/app/(tabs)/index.tsx b/carry-your-live/app/(tabs)/index.tsx index d852f5a..fd78d33 100644 --- a/carry-your-live/app/(tabs)/index.tsx +++ b/carry-your-live/app/(tabs)/index.tsx @@ -1,5 +1,6 @@ -import React from 'react'; -import { View, Text, StyleSheet, SafeAreaView } from 'react-native'; +import React, { useCallback } from 'react'; +import { View, Text, StyleSheet, SafeAreaView, BackHandler, KeyboardAvoidingView } from 'react-native'; +import { useFocusEffect } from 'expo-router'; import { Header } from '@/components/Header'; import { CategoryFilter } from '@/components/CategoryFilter'; import { TaskList } from '@/components/TaskList'; @@ -12,6 +13,13 @@ export default function TasksScreen() { const { theme } = useSettings(); const [selectedCategory, setSelectedCategory] = React.useState('all'); + useFocusEffect( + useCallback(() => { + const sub = BackHandler.addEventListener('hardwareBackPress', () => true); + return () => sub.remove(); + }, []) + ); + if (!isReady) { return ( @@ -22,12 +30,17 @@ export default function TasksScreen() { return ( -
+
- - + + + + ); } @@ -37,11 +50,14 @@ const styles = StyleSheet.create({ flex: 1, }, categoryFilterWrapper: { - height: 36, + justifyContent: 'center', + }, + kbAvoid: { + flex: 1, }, loadingContainer: { flex: 1, justifyContent: 'center', alignItems: 'center', }, -}); +}); \ No newline at end of file diff --git a/carry-your-live/app/(tabs)/settings.tsx b/carry-your-live/app/(tabs)/settings.tsx index 16c4981..a8a2321 100644 --- a/carry-your-live/app/(tabs)/settings.tsx +++ b/carry-your-live/app/(tabs)/settings.tsx @@ -1,5 +1,6 @@ -import React, { useState } from 'react'; -import { View, Text, StyleSheet, SafeAreaView, ScrollView, Switch, TouchableOpacity, Alert, Linking, Modal, Pressable } from 'react-native'; +import React, { useState, useCallback } from 'react'; +import { View, Text, StyleSheet, SafeAreaView, ScrollView, Switch, TouchableOpacity, Alert, Linking, Modal, Pressable, BackHandler } from 'react-native'; +import { useFocusEffect, useRouter } from 'expo-router'; import { Header } from '@/components/Header'; import { ListItem } from '@/components/ListItem'; import { OptionPickerModal } from '@/components/OptionPickerModal'; @@ -17,8 +18,10 @@ import { getLastSyncTime } from '@/database/sync'; import Category from '@/models/Category'; import Svg, { Path } from 'react-native-svg'; import ColorWheel from '@/components/ColorWheel'; +import { getLastVisitedTab, tabHref } from '@/utils/tabHistory'; export default function SettingsScreen() { + const router = useRouter(); const { theme, notifications, setNotifications, defaultCategoryId, setDefaultCategoryId, sortBy, setSortBy, reminderPreference, setReminderPreference, apiUrl, accentColor, setAccentColor, todoAheadDays, setTodoAheadDays } = useSettings(); const categories = useCategories(); const [picker, setPicker] = useState(null); @@ -75,8 +78,21 @@ export default function SettingsScreen() { refreshSyncStatus(); }, []); + useFocusEffect( + useCallback(() => { + const sub = BackHandler.addEventListener('hardwareBackPress', () => { + const target = getLastVisitedTab(); + if (target && target !== 'settings') { + router.navigate(tabHref(target)); + } + return true; + }); + return () => sub.remove(); + }, [router]) + ); + const defaultCategory = categories.find((c) => c.id === defaultCategoryId); - const defaultCategoryLabel = defaultCategory?.name ?? (categories.length > 0 ? categories[0].name : 'None'); + const defaultCategoryLabel = defaultCategory?.name ?? 'None'; const reminderLabel = REMINDER_OPTIONS.find((o) => o.value === reminderPreference)?.label ?? 'No reminder'; const openCustomAccent = () => { @@ -123,7 +139,7 @@ export default function SettingsScreen() { > - + Add Category @@ -242,8 +258,8 @@ export default function SettingsScreen() { ({ value: c.id, label: c.name, color: c.color }))} - selectedValue={defaultCategoryId || categories[0]?.id} + options={[{ value: '', label: 'None', color: '#9E9E9E' }, ...categories.map((c) => ({ value: c.id, label: c.name, color: c.color }))]} + selectedValue={defaultCategoryId} onSelect={handleDefaultCategory} onClose={() => setPicker(null)} /> @@ -298,7 +314,7 @@ export default function SettingsScreen() { Custom Accent Color setCustomAccentVisible(false)} hitSlop={{ top: 8, bottom: 8, left: 8, right: 8 }}> - + diff --git a/carry-your-live/app/(tabs)/stats.tsx b/carry-your-live/app/(tabs)/stats.tsx index b6c9c87..480aad7 100644 --- a/carry-your-live/app/(tabs)/stats.tsx +++ b/carry-your-live/app/(tabs)/stats.tsx @@ -1,5 +1,6 @@ -import React from 'react'; -import { View, Text, StyleSheet, SafeAreaView, ScrollView } from 'react-native'; +import React, { useCallback } from 'react'; +import { View, Text, StyleSheet, SafeAreaView, ScrollView, BackHandler } from 'react-native'; +import { useFocusEffect } from 'expo-router'; import { Header } from '@/components/Header'; import { useStats } from '@/hooks/useStats'; import { useSettings } from '@/theme'; @@ -8,6 +9,13 @@ export default function StatsScreen() { const { theme } = useSettings(); const stats = useStats(); + useFocusEffect( + useCallback(() => { + const sub = BackHandler.addEventListener('hardwareBackPress', () => true); + return () => sub.remove(); + }, []) + ); + const maxDaily = Math.max(1, ...stats.daily.map((d) => d.count)); return ( diff --git a/carry-your-live/app/subtask-detail.tsx b/carry-your-live/app/subtask-detail.tsx index 2e8d7a7..4b6e7a3 100644 --- a/carry-your-live/app/subtask-detail.tsx +++ b/carry-your-live/app/subtask-detail.tsx @@ -160,7 +160,7 @@ export default function SubtaskDetailScreen() { rightAction={ - + } diff --git a/carry-your-live/app/task-detail.tsx b/carry-your-live/app/task-detail.tsx index 29d262e..c5bec8b 100644 --- a/carry-your-live/app/task-detail.tsx +++ b/carry-your-live/app/task-detail.tsx @@ -58,8 +58,8 @@ function CollapsibleSection({ title, children, defaultExpanded = false, icon }: - + } @@ -285,8 +285,8 @@ export default function TaskDetailScreen() { - - + + } defaultExpanded={!!dueDate}> @@ -297,7 +297,7 @@ export default function TaskDetailScreen() { - - + + } defaultExpanded={!!assigneeId}> - + } defaultExpanded={!!methods.getValues('description')}> =3.0.0" + } + }, "node_modules/react-native-reanimated": { "version": "4.5.1", "resolved": "https://registry.npmjs.org/react-native-reanimated/-/react-native-reanimated-4.5.1.tgz", diff --git a/carry-your-live/package.json b/carry-your-live/package.json index 3b4942b..d4b0d36 100644 --- a/carry-your-live/package.json +++ b/carry-your-live/package.json @@ -16,12 +16,14 @@ "expo-router": "~57.0.10", "expo-sqlite": "~57.0.1", "expo-status-bar": "~57.0.1", + "expo-system-ui": "~57.0.2", "expo-updates": "~57.0.12", "react": "19.2.3", "react-dom": "19.2.3", "react-hook-form": "^7.51.5", "react-native": "0.86.2", "react-native-gesture-handler": "2.32.0", + "react-native-keyboard-controller": "1.21.9", "react-native-safe-area-context": "5.7.0", "react-native-screens": "4.26.0", "react-native-svg": "^15.15.4", diff --git a/carry-your-live/src/components/AssigneeSelector.tsx b/carry-your-live/src/components/AssigneeSelector.tsx index 6f6bff5..a3609db 100644 --- a/carry-your-live/src/components/AssigneeSelector.tsx +++ b/carry-your-live/src/components/AssigneeSelector.tsx @@ -60,8 +60,11 @@ export function AssigneeSelector({ value, onChange, disabled = false }: Assignee await sendFriendRequest(username); onChange(friendId); setShowModal(false); - } catch (err) { - console.error('Failed to add friend:', err); + } catch (err: any) { + console.error('Failed to add friend:', err.message, err.details || ''); + if (err.details) { + console.error('Validation details:', JSON.stringify(err.details, null, 2)); + } } }; @@ -76,7 +79,7 @@ export function AssigneeSelector({ value, onChange, disabled = false }: Assignee onPress={() => !disabled && setShowModal(true)} activeOpacity={0.8} > - 👤 + 👤 Assignee Unassigned {selectedFriend && ( - + )} @@ -158,7 +161,7 @@ export function AssigneeSelector({ value, onChange, disabled = false }: Assignee {item.username} {value === item.id && ( - + )} )} diff --git a/carry-your-live/src/components/CategoryEditorModal.tsx b/carry-your-live/src/components/CategoryEditorModal.tsx index 85bb332..9ea4ba5 100644 --- a/carry-your-live/src/components/CategoryEditorModal.tsx +++ b/carry-your-live/src/components/CategoryEditorModal.tsx @@ -1,5 +1,5 @@ import React, { useState } from 'react'; -import { Modal, View, Text, StyleSheet, TextInput, TouchableOpacity, ScrollView, KeyboardAvoidingView } from 'react-native'; +import { Modal, View, Text, StyleSheet, TextInput, TouchableOpacity, ScrollView, KeyboardAvoidingView, Platform, Pressable } from 'react-native'; import Category from '@/models/Category'; import { useSettings } from '@/theme'; import { CATEGORY_COLORS } from '@/constants'; @@ -59,96 +59,111 @@ export function CategoryEditorModal({ visible, category, categoryCount, onClose return ( - - - - - {category ? 'Edit Category' : 'New Category'} - - - - - - - - - Name - - - Color - - {CATEGORY_COLORS.map((c) => ( - setColor(c)} - activeOpacity={0.8} - accessibilityRole="radio" - accessibilityLabel={`Color ${c}`} - accessibilityState={{ selected: color === c }} - > - {color === c && ( - - - - )} - - ))} - - - - - {category && ( - - - Delete - - - )} - + {}} + > + - Save - - - - + + + {category ? 'Edit Category' : 'New Category'} + + + + + + + + + Name + + + Color + + {CATEGORY_COLORS.map((c) => ( + setColor(c)} + activeOpacity={0.8} + accessibilityRole="radio" + accessibilityLabel={`Color ${c}`} + accessibilityState={{ selected: color === c }} + > + {color === c && ( + + + + )} + + ))} + + + + + {category && ( + + + Delete + + + )} + + Save + + + + + ); } const styles = StyleSheet.create({ + fill: { + flex: 1, + }, overlay: { flex: 1, - backgroundColor: 'rgba(0,0,0,0.4)', + backgroundColor: 'rgba(0,0,0,0.55)', alignItems: 'center', justifyContent: 'center', padding: 24, @@ -156,6 +171,7 @@ const styles = StyleSheet.create({ sheet: { width: '100%', maxWidth: 380, + maxHeight: '100%', borderRadius: 16, padding: 20, gap: 8, diff --git a/carry-your-live/src/components/CategoryFilter.tsx b/carry-your-live/src/components/CategoryFilter.tsx index 8d65860..8d44197 100644 --- a/carry-your-live/src/components/CategoryFilter.tsx +++ b/carry-your-live/src/components/CategoryFilter.tsx @@ -1,8 +1,9 @@ import React from 'react'; import { View, Text, StyleSheet, ScrollView, TouchableOpacity } from 'react-native'; +import { useRouter } from 'expo-router'; import { useUniqueCategories } from '@/hooks/useDatabase'; import { useSettings, ThemeColors } from '@/theme'; -import Category from '@/models/Category'; +import Svg, { Path, Circle } from 'react-native-svg'; interface CategoryFilterProps { selected: string; @@ -12,10 +13,7 @@ interface CategoryFilterProps { export function CategoryFilter({ selected, onSelect }: CategoryFilterProps) { const categories = useUniqueCategories(); const { theme } = useSettings(); - - if (categories.length === 0) { - return null; - } + const router = useRouter(); return ( ))} + router.push('/settings')} + activeOpacity={0.7} + accessibilityRole="button" + accessibilityLabel="Manage categories" + hitSlop={{ top: 8, bottom: 8, left: 4, right: 4 }} + > + + + + + + + Manage + ); } @@ -66,18 +80,18 @@ function CategoryButton({ name, color, selected, onPress, theme }: CategoryButto ]} onPress={onPress} activeOpacity={0.8} + hitSlop={{ top: 6, bottom: 6, left: 4, right: 4 }} > {name} @@ -87,39 +101,48 @@ function CategoryButton({ name, color, selected, onPress, theme }: CategoryButto const styles = StyleSheet.create({ scrollView: { - paddingVertical: 0, - marginBottom: 0, + paddingVertical: 6, }, container: { paddingHorizontal: 12, - paddingTop: 0, - paddingBottom: 0, - gap: 6, - alignItems: 'flex-start', + gap: 8, + alignItems: 'center', }, button: { flexDirection: 'row', alignItems: 'center', paddingHorizontal: 12, - paddingVertical: 6, - borderRadius: 16, - borderWidth: 1, - minWidth: 64, + paddingVertical: 10, + borderRadius: 18, + borderWidth: 1.5, + minHeight: 38, justifyContent: 'center', - gap: 6, + gap: 8, }, colorDot: { width: 8, height: 8, borderRadius: 4, }, - colorDotSelected: { - width: 10, - height: 10, - borderRadius: 5, - }, buttonText: { fontSize: 12, fontWeight: '500', }, -}); + manageButton: { + alignSelf: 'center', + flexDirection: 'row', + alignItems: 'center', + gap: 6, + paddingHorizontal: 12, + paddingVertical: 10, + borderRadius: 18, + borderWidth: 1, + borderStyle: 'dashed', + minHeight: 38, + justifyContent: 'center', + }, + manageLabel: { + fontSize: 12, + fontWeight: '600', + }, +}); \ No newline at end of file diff --git a/carry-your-live/src/components/CategorySelector.tsx b/carry-your-live/src/components/CategorySelector.tsx index 6504c2b..99d1df1 100644 --- a/carry-your-live/src/components/CategorySelector.tsx +++ b/carry-your-live/src/components/CategorySelector.tsx @@ -2,6 +2,7 @@ import React, { useState } from 'react'; import { View, Text, StyleSheet, TouchableOpacity, ScrollView, Modal, Pressable, KeyboardAvoidingView } from 'react-native'; import { useCategories } from '@/hooks/useDatabase'; import { useSettings } from '@/theme'; +import Svg, { Path } from 'react-native-svg'; interface CategorySelectorProps { value: string; @@ -37,7 +38,7 @@ export function CategorySelector({ value, onChange, error }: CategorySelectorPro - + {error && {error}} @@ -72,7 +73,7 @@ export function CategorySelector({ value, onChange, error }: CategorySelectorPro None @@ -100,7 +101,7 @@ export function CategorySelector({ value, onChange, error }: CategorySelectorPro {category.name} @@ -120,8 +121,6 @@ export function CategorySelector({ value, onChange, error }: CategorySelectorPro ); } -import Svg, { Path } from 'react-native-svg'; - const styles = StyleSheet.create({ container: { gap: 6, diff --git a/carry-your-live/src/components/ColorWheel.native.tsx b/carry-your-live/src/components/ColorWheel.native.tsx index ef91d53..ecf24e0 100644 --- a/carry-your-live/src/components/ColorWheel.native.tsx +++ b/carry-your-live/src/components/ColorWheel.native.tsx @@ -69,31 +69,44 @@ export default function ColorWheel({ color, onChange }: ColorWheelProps) { onChange(newColor); }, [hue, saturation, value, onChange]); + const updateFromWheel = (locationX: number, locationY: number) => { + const center = WHEEL_SIZE / 2; + const dx = locationX - center; + const dy = locationY - center; + const distance = Math.sqrt(dx * dx + dy * dy); + const radius = (WHEEL_SIZE - THUMB_SIZE) / 2; + if (distance > radius) return; + const angle = Math.atan2(dy, dx) * (180 / Math.PI); + let h = angle + 180; + if (h >= 360) h -= 360; + setHue(h); + setSaturation(distance / radius); + setValue(1 - distance / radius); + }; + const wheelPanResponder = React.useMemo(() => PanResponder.create({ onStartShouldSetPanResponder: () => true, onMoveShouldSetPanResponder: () => true, - onPanResponderMove: (_, gesture) => { - const center = WHEEL_SIZE / 2; - const dx = gesture.moveX - center; - const dy = gesture.moveY - center; - const distance = Math.sqrt(dx * dx + dy * dy); - const radius = (WHEEL_SIZE - THUMB_SIZE) / 2; - if (distance > radius) return; - const angle = Math.atan2(dy, dx) * (180 / Math.PI); - let h = angle + 180; - if (h >= 360) h -= 360; - setHue(h); - setSaturation(distance / radius); - setValue(1 - distance / radius); + onPanResponderGrant: (event) => { + updateFromWheel(event.nativeEvent.locationX, event.nativeEvent.locationY); + }, + onPanResponderMove: (event) => { + updateFromWheel(event.nativeEvent.locationX, event.nativeEvent.locationY); }, }), []); + const setHueFromLocation = (locationX: number) => { + setHue(Math.min(360, Math.max(0, (locationX / WHEEL_SIZE) * 360))); + }; + const huePanResponder = React.useMemo(() => PanResponder.create({ onStartShouldSetPanResponder: () => true, onMoveShouldSetPanResponder: () => true, - onPanResponderMove: (_, gesture) => { - const h = (gesture.moveX / WHEEL_SIZE) * 360; - setHue(Math.min(360, Math.max(0, h))); + onPanResponderGrant: (event) => { + setHueFromLocation(event.nativeEvent.locationX); + }, + onPanResponderMove: (event) => { + setHueFromLocation(event.nativeEvent.locationX); }, }), []); diff --git a/carry-your-live/src/components/ColorWheel.web.tsx b/carry-your-live/src/components/ColorWheel.web.tsx index 91fc68e..5ca6d4c 100644 --- a/carry-your-live/src/components/ColorWheel.web.tsx +++ b/carry-your-live/src/components/ColorWheel.web.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect, useMemo, useRef } from 'react'; +import React, { useState, useEffect, useMemo } from 'react'; import { View, Text, StyleSheet } from 'react-native'; import { useSettings } from '@/theme'; import Svg, { Rect, Circle, Defs, LinearGradient, Stop } from 'react-native-svg'; @@ -55,8 +55,6 @@ export default function ColorWheel({ color, onChange }: ColorWheelProps) { const [saturation, setSaturation] = useState(initialHsv.s); const [value, setValue] = useState(initialHsv.v); const [prevColor, setPrevColor] = useState(color); - const wheelRef = useRef(null); - const hueRef = useRef(null); if (prevColor !== color) { setPrevColor(color); @@ -71,14 +69,13 @@ export default function ColorWheel({ color, onChange }: ColorWheelProps) { }, [hue, saturation, value, onChange]); const handleWheelMouseDown = (e: React.MouseEvent) => { + const rect = e.currentTarget.getBoundingClientRect(); + const center = WHEEL_SIZE / 2; + const radius = (WHEEL_SIZE - THUMB_SIZE) / 2; const handleMove = (moveEvent: MouseEvent) => { - if (!wheelRef.current) return; - const rect = (wheelRef.current as unknown as HTMLElement).getBoundingClientRect(); - const center = WHEEL_SIZE / 2; const dx = moveEvent.clientX - rect.left - center; const dy = moveEvent.clientY - rect.top - center; const distance = Math.sqrt(dx * dx + dy * dy); - const radius = (WHEEL_SIZE - THUMB_SIZE) / 2; if (distance > radius) return; const angle = Math.atan2(dy, dx) * (180 / Math.PI); let h = angle + 180; @@ -97,9 +94,8 @@ export default function ColorWheel({ color, onChange }: ColorWheelProps) { }; const handleHueMouseDown = (e: React.MouseEvent) => { + const rect = e.currentTarget.getBoundingClientRect(); const handleMove = (moveEvent: MouseEvent) => { - if (!hueRef.current) return; - const rect = (hueRef.current as unknown as HTMLElement).getBoundingClientRect(); const h = ((moveEvent.clientX - rect.left) / WHEEL_SIZE) * 360; setHue(Math.min(360, Math.max(0, h))); }; @@ -120,7 +116,7 @@ export default function ColorWheel({ color, onChange }: ColorWheelProps) { return ( - + @@ -155,7 +151,7 @@ export default function ColorWheel({ color, onChange }: ColorWheelProps) { - + diff --git a/carry-your-live/src/components/DateTimeInput.web.tsx b/carry-your-live/src/components/DateTimeInput.web.tsx index 4760f29..bc57735 100644 --- a/carry-your-live/src/components/DateTimeInput.web.tsx +++ b/carry-your-live/src/components/DateTimeInput.web.tsx @@ -1,6 +1,7 @@ import React, { useState } from 'react'; import { View, Text, TouchableOpacity, StyleSheet, Modal } from 'react-native'; import { DateTimePickerEvent } from '@react-native-community/datetimepicker'; +import { useSettings } from '@/theme'; interface WebDateTimeInputProps { value: Date; @@ -32,6 +33,7 @@ export default function WebDateTimeInput({ onDismiss, is24Hour, }: WebDateTimeInputProps) { + const { theme } = useSettings(); const [inputValue, setInputValue] = useState(() => dateInputValue(value)); const [prevValue, setPrevValue] = useState(value); @@ -57,11 +59,11 @@ export default function WebDateTimeInput({ onChange(event, date); }; - const bgColor = '#1E1E1E'; - const textColor = '#FFFFFF'; - const borderColor = '#333333'; - const overlayColor = 'rgba(0,0,0,0.6)'; - const mutedColor = '#9E9E9E'; + const bgColor = theme.sheetBg; + const textColor = theme.text; + const borderColor = theme.borderStrong; + const overlayColor = theme.overlay; + const mutedColor = theme.textFaint; return ( @@ -90,7 +92,7 @@ export default function WebDateTimeInput({ borderRadius: 10, border: `1px solid ${borderColor}`, outline: 'none', - backgroundColor: '#2C2C2C', + backgroundColor: theme.inputBg, color: textColor, fontFamily: 'inherit', marginVertical: 16, @@ -101,14 +103,14 @@ export default function WebDateTimeInput({ Clear - - Done + + Done @@ -159,11 +161,9 @@ const styles = StyleSheet.create({ paddingVertical: 10, paddingHorizontal: 16, borderRadius: 10, - backgroundColor: '#E53935', }, doneButtonText: { fontSize: 15, fontWeight: '600', - color: '#FFFFFF', }, }); \ No newline at end of file diff --git a/carry-your-live/src/components/DateTimePicker.tsx b/carry-your-live/src/components/DateTimePicker.tsx index 6d4fb58..8e8d46b 100644 --- a/carry-your-live/src/components/DateTimePicker.tsx +++ b/carry-your-live/src/components/DateTimePicker.tsx @@ -55,8 +55,8 @@ export function DateTimePickerComponent({ control }: DateTimePickerComponentProp activeOpacity={0.8} > - - + + {field.value ? formatTime12(field.value) : placeholder} - {field.value && ( + {field.value ? ( { @@ -82,10 +82,10 @@ export function DateTimePickerComponent({ control }: DateTimePickerComponentProp hitSlop={{ top: 8, bottom: 8, left: 8, right: 8 }} > - + - )} + ) : null} ); }} @@ -114,14 +114,14 @@ export function DateTimePickerComponent({ control }: DateTimePickerComponentProp @@ -135,17 +135,17 @@ export function DateTimePickerComponent({ control }: DateTimePickerComponentProp > {field.value ? format(field.value, 'MMM d, yyyy') : 'Due Date'} - {field.value && ( + {field.value ? ( setDateValue(null)} hitSlop={{ top: 8, bottom: 8, left: 8, right: 8 }} > - + - )} + ) : null} ); }} @@ -183,7 +183,7 @@ export function DateTimePickerComponent({ control }: DateTimePickerComponentProp {endTimeInvalid && ( - + diff --git a/carry-your-live/src/components/FloatingActionButton.tsx b/carry-your-live/src/components/FloatingActionButton.tsx index 81b6b19..f0e9217 100644 --- a/carry-your-live/src/components/FloatingActionButton.tsx +++ b/carry-your-live/src/components/FloatingActionButton.tsx @@ -2,9 +2,11 @@ import React, { useState } from 'react'; import { StyleSheet, TouchableOpacity, Animated, Easing } from 'react-native'; import { useRouter } from 'expo-router'; import Svg, { Path } from 'react-native-svg'; +import { useSettings } from '@/theme'; export function FloatingActionButton() { const router = useRouter(); + const { theme } = useSettings(); const [scaleAnim] = React.useState(new Animated.Value(1)); const [rotateAnim] = React.useState(new Animated.Value(0)); const [rotation, setRotation] = useState(0); @@ -52,7 +54,7 @@ export function FloatingActionButton() { ]} > Cancel - + {isSubmitting ? 'Saving...' : submitLabel} @@ -88,19 +88,12 @@ const styles = StyleSheet.create({ flex: 1, paddingVertical: 12, borderRadius: 10, - backgroundColor: '#E53935', alignItems: 'center', justifyContent: 'center', - shadowColor: '#E53935', - shadowOffset: { width: 0, height: 2 }, - shadowOpacity: 0.3, - shadowRadius: 6, - elevation: 3, }, submitButtonText: { fontSize: 15, fontWeight: '600', - color: '#FFFFFF', }, buttonDisabled: { opacity: 0.6, diff --git a/carry-your-live/src/components/FriendsModal.tsx b/carry-your-live/src/components/FriendsModal.tsx index 5e74ef5..a201ed5 100644 --- a/carry-your-live/src/components/FriendsModal.tsx +++ b/carry-your-live/src/components/FriendsModal.tsx @@ -148,7 +148,7 @@ export function FriendsModal({ visible, onClose }: FriendsModalProps) { style={[styles.removeBtn, { backgroundColor: theme.accentSoft, borderColor: theme.accent }]} onPress={() => handleRemove(item.id)} > - Remove + Remove )} diff --git a/carry-your-live/src/components/Header.tsx b/carry-your-live/src/components/Header.tsx index b12efa6..daed94f 100644 --- a/carry-your-live/src/components/Header.tsx +++ b/carry-your-live/src/components/Header.tsx @@ -16,8 +16,8 @@ export function Header({ title, showLogo, rightAction }: HeaderProps) { {showLogo && ( - - + + )} {title} @@ -48,12 +48,10 @@ const styles = StyleSheet.create({ width: 32, height: 32, borderRadius: 10, - backgroundColor: '#E53935', alignItems: 'center', justifyContent: 'center', }, logoText: { - color: '#FFFFFF', fontSize: 18, fontWeight: '700', }, diff --git a/carry-your-live/src/components/LegalModal.tsx b/carry-your-live/src/components/LegalModal.tsx index 8dd8780..0a6ad6d 100644 --- a/carry-your-live/src/components/LegalModal.tsx +++ b/carry-your-live/src/components/LegalModal.tsx @@ -136,7 +136,7 @@ export function LegalModal({ visible, type, onClose }: LegalModalProps) { {title} - + diff --git a/carry-your-live/src/components/ListItem.tsx b/carry-your-live/src/components/ListItem.tsx index 742885f..ada97b0 100644 --- a/carry-your-live/src/components/ListItem.tsx +++ b/carry-your-live/src/components/ListItem.tsx @@ -42,8 +42,8 @@ export function ListItem({ title, subtitle, leftElement, rightElement, onPress, @@ -87,7 +87,7 @@ export function OptionPickerModal({ visible, title, options, selectedValue, onSe accessibilityRole="button" accessibilityLabel="Cancel" > - Cancel + Cancel } contentContainerStyle={styles.listContent} diff --git a/carry-your-live/src/components/PrioritySelector.tsx b/carry-your-live/src/components/PrioritySelector.tsx index 4f2c7d3..faf928c 100644 --- a/carry-your-live/src/components/PrioritySelector.tsx +++ b/carry-your-live/src/components/PrioritySelector.tsx @@ -46,7 +46,7 @@ export function PrioritySelector({ value, onChange }: PrioritySelectorProps) { {priority.label} diff --git a/carry-your-live/src/components/QuickAddBar.tsx b/carry-your-live/src/components/QuickAddBar.tsx index 93862bf..9dd5dea 100644 --- a/carry-your-live/src/components/QuickAddBar.tsx +++ b/carry-your-live/src/components/QuickAddBar.tsx @@ -1,6 +1,5 @@ import React, { useState, useEffect, useRef } from 'react'; -import { View, StyleSheet, TextInput, TouchableOpacity, Platform, Keyboard, Animated, Easing } from 'react-native'; -import { useSafeAreaInsets } from 'react-native-safe-area-context'; +import { View, StyleSheet, TextInput, TouchableOpacity, Keyboard, Text } from 'react-native'; import { database, collections } from '@/database'; import { useCategories } from '@/hooks/useDatabase'; import { useSettings } from '@/theme'; @@ -15,13 +14,11 @@ interface QuickAddBarProps { export function QuickAddBar({ dueDate = 0, placeholder }: QuickAddBarProps) { const { theme, defaultCategoryId } = useSettings(); - const insets = useSafeAreaInsets(); const categories = useCategories(); const [title, setTitle] = useState(''); const [categoryId, setCategoryId] = useState(() => defaultCategoryId || ''); const [categoryPickerVisible, setCategoryPickerVisible] = useState(false); const inputRef = useRef(null); - const keyboardHeight = useRef(new Animated.Value(0)).current; const categoryColor = categories.find((c) => c.id === categoryId)?.color ?? '#9E9E9E'; @@ -33,32 +30,6 @@ export function QuickAddBar({ dueDate = 0, placeholder }: QuickAddBarProps) { }); }, []); - useEffect(() => { - const showListener = Keyboard.addListener('keyboardDidShow', (e) => { - const height = e.endCoordinates.height; - Animated.timing(keyboardHeight, { - toValue: height, - duration: 250, - easing: Easing.out(Easing.cubic), - useNativeDriver: false, - }).start(); - }); - - const hideListener = Keyboard.addListener('keyboardDidHide', () => { - Animated.timing(keyboardHeight, { - toValue: 0, - duration: 250, - easing: Easing.out(Easing.cubic), - useNativeDriver: false, - }).start(); - }); - - return () => { - showListener.remove(); - hideListener.remove(); - }; - }, []); - const handleAdd = async () => { const trimmed = title.trim(); if (!trimmed) return; @@ -84,16 +55,11 @@ export function QuickAddBar({ dueDate = 0, placeholder }: QuickAddBarProps) { }); }); setTitle(''); + Keyboard.dismiss(); }; - const animatedBottom = keyboardHeight.interpolate({ - inputRange: [0, 500], - outputRange: [0, insets.bottom + 500], - extrapolate: 'clamp', - }); - return ( - + - + - - - + + Add a Task + + + + @@ -147,16 +116,14 @@ export function QuickAddBar({ dueDate = 0, placeholder }: QuickAddBarProps) { onSelect={(value) => setCategoryId(Array.isArray(value) ? value[0] : value)} onClose={() => setCategoryPickerVisible(false)} /> - + ); } const styles = StyleSheet.create({ wrapper: { - position: 'absolute', - left: 8, - right: 8, - bottom: 12, + paddingHorizontal: 8, + paddingTop: 4, }, bar: { flexDirection: 'row', @@ -166,11 +133,12 @@ const styles = StyleSheet.create({ paddingVertical: 8, borderRadius: 14, borderWidth: 1, + borderBottomWidth: 1, shadowColor: '#000', - shadowOffset: { width: 0, height: 2 }, - shadowOpacity: 0.08, + shadowOffset: { width: 0, height: -2 }, + shadowOpacity: 0.10, shadowRadius: 6, - elevation: 4, + elevation: 6, }, categoryButton: { flexDirection: 'row', @@ -178,7 +146,7 @@ const styles = StyleSheet.create({ gap: 3, paddingVertical: 6, paddingHorizontal: 6, - borderRadius: 8, + borderRadius: 10, borderWidth: 1, height: 40, }, @@ -194,13 +162,23 @@ const styles = StyleSheet.create({ minHeight: 40, }, submit: { - width: 40, + minWidth: 88, height: 40, - borderRadius: 8, + paddingHorizontal: 12, + borderRadius: 10, alignItems: 'center', justifyContent: 'center', }, + submitLabelRow: { + flexDirection: 'row', + alignItems: 'center', + gap: 6, + }, + submitText: { + fontSize: 13, + fontWeight: '700', + }, submitDisabled: { opacity: 0.5, }, -}); +}); \ No newline at end of file diff --git a/carry-your-live/src/components/ReminderSelector.tsx b/carry-your-live/src/components/ReminderSelector.tsx index 3d7f56b..1d955fa 100644 --- a/carry-your-live/src/components/ReminderSelector.tsx +++ b/carry-your-live/src/components/ReminderSelector.tsx @@ -43,13 +43,13 @@ export function ReminderSelector({ value, onChange, hasDueDate }: ReminderSelect 0 ? theme.accent : theme.textMuted} - strokeWidth={1.5} + stroke={selectedReminders.length > 0 ? theme.accentText : theme.textSecondary} + strokeWidth={1.8} strokeLinecap="round" strokeLinejoin="round" fill="none" /> - 0 ? theme.accent : 'transparent'} stroke={selectedReminders.length > 0 ? theme.accent : theme.textMuted} strokeWidth={1.5} /> + 0 ? theme.accent : 'transparent'} stroke={selectedReminders.length > 0 ? theme.accentText : theme.textSecondary} strokeWidth={1.8} /> - + diff --git a/carry-your-live/src/components/RepeatSelector.tsx b/carry-your-live/src/components/RepeatSelector.tsx index 6dff5d2..6d36a16 100644 --- a/carry-your-live/src/components/RepeatSelector.tsx +++ b/carry-your-live/src/components/RepeatSelector.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { View, Text, StyleSheet, TouchableOpacity, Modal, TextInput, Pressable, KeyboardAvoidingView } from 'react-native'; -import { Repeat, REPEAT_OPTIONS, WEEKDAY_LABELS, repeatDaysFromString } from '@/types'; +import { Repeat, REPEAT_OPTIONS, WEEKDAY_LABELS, WEEKDAY_ORDER, weekdaysInDisplayOrder, repeatDaysFromString } from '@/types'; import { useSettings } from '@/theme'; import { useRepeatProfiles } from '@/hooks/useDatabase'; import { createRepeatProfile, deleteRepeatProfile } from '@/utils/repeatProfileActions'; @@ -168,7 +168,7 @@ export function RepeatSelector({ value, interval, days, onChange }: RepeatSelect style={[ styles.chipText, { color: theme.textFaint }, - value === option.value && { color: theme.accent, fontWeight: '600' }, + value === option.value && { color: theme.accentStrong, fontWeight: '600' }, ]} > {option.label.replace('No Repeat', 'None')} @@ -212,9 +212,9 @@ export function RepeatSelector({ value, interval, days, onChange }: RepeatSelect {isDaysBased(value) && ( - {WEEKDAY_LABELS.map((label, day) => ( + {WEEKDAY_ORDER.map((day) => ( - {label} + {WEEKDAY_LABELS[day]} ))} @@ -252,7 +252,7 @@ export function RepeatSelector({ value, interval, days, onChange }: RepeatSelect - Save as profile + Save as profile )} @@ -271,7 +271,7 @@ export function RepeatSelector({ value, interval, days, onChange }: RepeatSelect Save repeat profile {`${REPEAT_OPTIONS.find((o) => o.value === value)?.label.replace('No Repeat', 'None')}, every ${interval} ${unitLabel(value)}${interval > 1 ? 's' : ''}`} - {isDaysBased(value) && days.length > 0 ? ` · ${days.map((d) => WEEKDAY_LABELS[d]).join(' ')}` : ''} + {isDaysBased(value) && days.length > 0 ? ` · ${weekdaysInDisplayOrder(days).map((d) => WEEKDAY_LABELS[d]).join(' ')}` : ''} Backend URL - + diff --git a/carry-your-live/src/components/SubtaskItem.tsx b/carry-your-live/src/components/SubtaskItem.tsx index 9d62c1c..1f68e06 100644 --- a/carry-your-live/src/components/SubtaskItem.tsx +++ b/carry-your-live/src/components/SubtaskItem.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React, { useState, useEffect } from 'react'; import { View, StyleSheet } from 'react-native'; import { SubtaskData } from '@/types'; import { TaskItem } from './TaskItem'; @@ -6,63 +6,78 @@ import { useSettings } from '@/theme'; interface SubtaskItemProps { subtask: SubtaskData; - onToggle: () => void | Promise; - onDelete?: () => void; - onPress?: () => void; - onLongPress?: () => void; - onMenuOpen?: () => void; + onToggle?: (subtask: SubtaskData) => void | Promise; + onDelete?: (subtask: SubtaskData) => void; + onPress?: (subtask: SubtaskData) => void; + onLongPress?: (subtask: SubtaskData) => void; + onMenuOpen?: (subtask: SubtaskData) => void; selected?: boolean; selectionMode?: boolean; draggable?: boolean; - onDragStart?: () => void; + onDragStart?: (subtask: SubtaskData) => void; onDragUpdate?: (absoluteY: number) => void; onDragEnd?: (absoluteY: number) => void; depth?: number; categoryColor?: string; + registerRef?: (subtaskId: string, parentTaskId: string, ref: View | null) => void; + hoveredId?: string | null; } -export const SubtaskItem = React.memo(function SubtaskItem({ - subtask, - onToggle, - onDelete, - onPress, - onLongPress, - onMenuOpen, +export const SubtaskItem = React.memo(function SubtaskItem({ + subtask, + onToggle, + onDelete, + onPress, + onLongPress, + onMenuOpen, selected, selectionMode, - draggable, - onDragStart, - onDragUpdate, - onDragEnd, + draggable, + onDragStart, + onDragUpdate, + onDragEnd, depth = 1, - categoryColor + categoryColor, + registerRef, + hoveredId, }: SubtaskItemProps) { const { theme } = useSettings(); - const [expanded, setExpanded] = useState(true); + const [expanded, setExpanded] = useState(false); const hasChildren = subtask.subtasks && subtask.subtasks.length > 0; + const hovered = hoveredId === subtask.id; const handleExpand = () => setExpanded(!expanded); + useEffect(() => { + if (hovered && hasChildren && !expanded) { + setExpanded(true); + } + }, [hovered, hasChildren, expanded]); + return ( - {})} - onLongPress={onLongPress} - onMenuOpen={onMenuOpen} - selected={selected} - selectionMode={selectionMode} - completedSection={subtask.completed} - draggable={draggable} - onDragStart={onDragStart} - onDragUpdate={onDragUpdate} - onDragEnd={onDragEnd} - categoryColor={categoryColor} - /> + registerRef?.(subtask.id, subtask.taskId, ref)}> + onToggle?.(subtask)} + onDelete={() => onDelete?.(subtask)} + onPress={onPress ? () => onPress(subtask) : handleExpand} + onLongPress={() => onLongPress?.(subtask)} + onMenuOpen={() => onMenuOpen?.(subtask)} + selected={selected} + selectionMode={selectionMode} + completedSection={subtask.completed} + draggable={draggable} + hovered={hovered} + onDragStart={() => onDragStart?.(subtask)} + onDragUpdate={onDragUpdate} + onDragEnd={onDragEnd} + categoryColor={categoryColor} + /> + {hasChildren && expanded && ( {subtask.subtasks @@ -72,7 +87,7 @@ export const SubtaskItem = React.memo(function SubtaskItem({ {}} + onToggle={onToggle} onDelete={onDelete} onPress={onPress} onLongPress={onLongPress} @@ -85,6 +100,8 @@ export const SubtaskItem = React.memo(function SubtaskItem({ onDragEnd={onDragEnd} depth={depth + 1} categoryColor={categoryColor} + registerRef={registerRef} + hoveredId={hoveredId} /> ))} @@ -97,4 +114,4 @@ const styles = StyleSheet.create({ nestedSubtasks: { marginTop: 4, }, -}); +}); \ No newline at end of file diff --git a/carry-your-live/src/components/SubtasksSection.tsx b/carry-your-live/src/components/SubtasksSection.tsx index b3db8bf..3516290 100644 --- a/carry-your-live/src/components/SubtasksSection.tsx +++ b/carry-your-live/src/components/SubtasksSection.tsx @@ -48,7 +48,7 @@ export function SubtasksSection({ control }: SubtasksSectionProps) { Subtasks {items.length > 0 && ( - {items.length} + {items.length} )} @@ -67,8 +67,8 @@ export function SubtasksSection({ control }: SubtasksSectionProps) { - Add Subtask + Add Subtask @@ -131,8 +131,8 @@ function SubtaskItem({ index, value, onChange, onRemove }: SubtaskItemProps) { - + diff --git a/carry-your-live/src/components/SyncStatus.tsx b/carry-your-live/src/components/SyncStatus.tsx index fe0664e..df323ad 100644 --- a/carry-your-live/src/components/SyncStatus.tsx +++ b/carry-your-live/src/components/SyncStatus.tsx @@ -66,21 +66,21 @@ export default function SyncStatus({ compact = false }: SyncStatusProps) { case 'success': return ( - + ); case 'error': return ( - - + + ); default: return ( - - + + ); } diff --git a/carry-your-live/src/components/TabBarIcon.tsx b/carry-your-live/src/components/TabBarIcon.tsx index 5fb2b27..c0c147a 100644 --- a/carry-your-live/src/components/TabBarIcon.tsx +++ b/carry-your-live/src/components/TabBarIcon.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { ColorValue } from 'react-native'; -import Svg, { Path, Circle } from 'react-native-svg'; +import Svg, { Path, Circle, Rect } from 'react-native-svg'; interface TabBarIconProps { name: 'checklist' | 'calendar' | 'gear' | 'stats'; @@ -15,48 +15,64 @@ export function TabBarIcon({ name, focused, color, size = 24 }: TabBarIconProps) {name === 'checklist' && ( <> - {focused && ( - - )} + + + )} {name === 'calendar' && ( <> - + {focused && ( + <> + + + + + )} )} {name === 'gear' && ( - + <> + + + )} {name === 'stats' && ( <> diff --git a/carry-your-live/src/components/TaskDeleteModal.tsx b/carry-your-live/src/components/TaskDeleteModal.tsx index 954ef1e..a0b30e3 100644 --- a/carry-your-live/src/components/TaskDeleteModal.tsx +++ b/carry-your-live/src/components/TaskDeleteModal.tsx @@ -46,7 +46,7 @@ export function TaskDeleteModal({ visible, taskId, taskTitle, isRepeating, subta {subtask ? 'Delete Subtask' : 'Delete Task'} - + @@ -71,7 +71,7 @@ export function TaskDeleteModal({ visible, taskId, taskTitle, isRepeating, subta onPress={() => onDelete(option.scope)} activeOpacity={0.7} > - {option.label} + {option.label} {option.hint && {option.hint}} ))} diff --git a/carry-your-live/src/components/TaskItem.tsx b/carry-your-live/src/components/TaskItem.tsx index 7a42d5d..42a29d4 100644 --- a/carry-your-live/src/components/TaskItem.tsx +++ b/carry-your-live/src/components/TaskItem.tsx @@ -133,11 +133,10 @@ export const TaskItem = React.memo(function TaskItem({ task, onToggle, onDelete, const hasDueDate = task.dueDate > 0; const isOverdue = hasDueDate && !task.completed && task.dueDate < startOfToday.getTime(); const isDueToday = hasDueDate && !task.completed && task.dueDate >= startOfToday.getTime() && task.dueDate <= endOfToday.getTime(); - const canComplete = !hasDueDate || isOverdue || isDueToday; - return { hasDueDate, isOverdue, isDueToday, canComplete }; + return { hasDueDate, isOverdue, isDueToday }; }, [task.dueDate, task.completed]); - const { hasDueDate, isOverdue, isDueToday, canComplete } = dueInfo; + const { hasDueDate, isOverdue, isDueToday } = dueInfo; const formattedDueDate = React.useMemo( () => formatDueDate(task.dueDate, task.dueTime, task.endTime || ''), @@ -158,17 +157,6 @@ export const TaskItem = React.memo(function TaskItem({ task, onToggle, onDelete, const renderLeftActions = (progress: Animated.AnimatedInterpolation) => { const translateX = progress.interpolate({ inputRange: [0, 1], outputRange: [-80, 0] }); - if (!canComplete && !task.completed) { - return ( - - - - - - Not Due - - ); - } return ( @@ -189,9 +177,7 @@ const renderLeftActions = (progress: Animated.AnimatedInterpolation) => swipeableRef.current?.close(); }} onSwipeableLeftOpen={() => { - if (canComplete || task.completed) { - onToggle(); - } + onToggle(); swipeableRef.current?.close(); }} overshootRight={false} @@ -234,21 +220,21 @@ const renderLeftActions = (progress: Animated.AnimatedInterpolation) => ) : null} - + {task.completed ? ( <> ) : ( - + )} @@ -286,8 +272,8 @@ const renderLeftActions = (progress: Animated.AnimatedInterpolation) => ) => @@ -314,8 +300,8 @@ const renderLeftActions = (progress: Animated.AnimatedInterpolation) => ) => {hasDueDate && ( - - + + ) => >(new Map()); const [dropIndicator, setDropIndicator] = useState<{ targetId: string | null; position: 'above' | 'below' } | null>(null); const itemRefs = useRef>(new Map()); - const dragStateRef = useRef<{ taskId: string; positions: Record } | null>(null); - const subtaskDragRef = useRef<{ subtaskId: string; parentTaskId: string; positions: Record } | null>(null); + const subtaskRefs = useRef>(new Map()); + const dragStateRef = useRef<{ taskId: string; positions: Record; subPositions: Record } | null>(null); + const subtaskDragRef = useRef<{ subtaskId: string; parentTaskId: string; positions: Record; subPositions: Record } | null>(null); const { modals, openTaskDelete, @@ -78,6 +80,14 @@ export function TaskList({ categoryId = 'all', onSelectionChange }: TaskListProp } }, []); + const registerSubtaskRef = useCallback((subtaskId: string, parentTaskId: string, ref: View | null) => { + if (ref) { + subtaskRefs.current.set(subtaskId, { ref, parentTaskId }); + } else { + subtaskRefs.current.delete(subtaskId); + } + }, []); + const sortedTasks = useMemo(() => { const sorted = [...tasks]; switch (sortBy) { @@ -210,7 +220,30 @@ export function TaskList({ categoryId = 'all', onSelectionChange }: TaskListProp return positions; }, []); - const findHoverTarget = useCallback((absoluteY: number, draggedId: string, positions: Record) => { + const measureAll = useCallback(async () => { + const positions = await measureItems(); + const subPositions: Record = {}; + const entries = Array.from(subtaskRefs.current.entries()); + await Promise.all(entries.map(([id, entry]) => { + return new Promise((resolve) => { + entry.ref?.measureInWindow((_x, y, _w, h) => { + subPositions[id] = { top: y, bottom: y + h }; + resolve(); + }); + }); + })); + return { positions, subPositions }; + }, [measureItems]); + + const findHoverTarget = useCallback((absoluteY: number, draggedId: string, positions: Record, subPositions: Record) => { + const subEntries = Object.entries(subPositions); + for (let i = subEntries.length - 1; i >= 0; i--) { + const [id, p] = subEntries[i]; + if (id === draggedId) continue; + if (absoluteY >= p.top && absoluteY <= p.bottom) { + return id; + } + } for (const [id, p] of Object.entries(positions)) { if (id === draggedId) continue; if (absoluteY >= p.top && absoluteY <= p.bottom) { @@ -228,17 +261,43 @@ export function TaskList({ categoryId = 'all', onSelectionChange }: TaskListProp }, []); const handleDragStart = useCallback(async (taskId: string) => { - dragStateRef.current = { taskId, positions: await measureItems() }; - }, [measureItems]); + dragStateRef.current = { taskId, ...(await measureAll()) }; + }, [measureAll]); + + const refreshDragPositions = useCallback(() => { + measureAll().then((m) => { + if (subtaskDragRef.current) { + subtaskDragRef.current = { ...subtaskDragRef.current, ...m }; + } else if (dragStateRef.current) { + dragStateRef.current = { ...dragStateRef.current, ...m }; + } + }); + }, [measureAll]); + + const updateHoverTarget = useCallback((target: string | null) => { + let changed = false; + setHoverTaskId((prev) => { + if (prev === target) return prev; + changed = true; + return target; + }); + if (changed && target) { + setTimeout(refreshDragPositions, 80); + } + }, [refreshDragPositions]); const handleDragUpdate = useCallback((absoluteY: number) => { const state = dragStateRef.current; if (!state) return; - const target = findHoverTarget(absoluteY, state.taskId, state.positions); - setHoverTaskId((prev) => (prev === target ? prev : target)); + const target = findHoverTarget(absoluteY, state.taskId, state.positions, state.subPositions); + updateHoverTarget(target); if (target) { - const position = calculateDropPosition(absoluteY, target, state.positions); - setDropIndicator({ targetId: target, position }); + if (state.subPositions[target]) { + setDropIndicator(null); + } else { + const position = calculateDropPosition(absoluteY, target, state.positions); + setDropIndicator({ targetId: target, position }); + } } else { // Check if below last item const positions = state.positions; @@ -249,7 +308,7 @@ export function TaskList({ categoryId = 'all', onSelectionChange }: TaskListProp setDropIndicator(null); } } - }, [findHoverTarget, calculateDropPosition]); + }, [findHoverTarget, calculateDropPosition, updateHoverTarget]); const handleDragEnd = useCallback((absoluteY: number) => { const state = dragStateRef.current; @@ -258,25 +317,50 @@ export function TaskList({ categoryId = 'all', onSelectionChange }: TaskListProp setDropIndicator(null); if (!state) return; - const target = findHoverTarget(absoluteY, state.taskId, state.positions); + const target = findHoverTarget(absoluteY, state.taskId, state.positions, state.subPositions); if (target) { - (async () => { - await convertTaskToSubtask(state.taskId, target); - refreshAll(); - })(); + if (state.subPositions[target]) { + const parentTaskId = subtaskRefs.current.get(target)?.parentTaskId ?? state.taskId; + (async () => { + await convertTaskToSubtask(state.taskId, parentTaskId, target); + await fetchSubtasks(parentTaskId); + refreshAll(); + })(); + } else { + (async () => { + await convertTaskToSubtask(state.taskId, target); + refreshAll(); + })(); + } } - }, [findHoverTarget, refreshAll]); + }, [findHoverTarget, refreshAll, fetchSubtasks]); const handleSubtaskDragStart = useCallback(async (subtaskId: string, parentTaskId: string) => { - subtaskDragRef.current = { subtaskId, parentTaskId, positions: await measureItems() }; - }, [measureItems]); + subtaskDragRef.current = { subtaskId, parentTaskId, ...(await measureAll()) }; + }, [measureAll]); const handleSubtaskDragUpdate = useCallback((absoluteY: number) => { const state = subtaskDragRef.current; if (!state) return; - const target = findHoverTarget(absoluteY, state.subtaskId, state.positions); - setHoverTaskId((prev) => (prev === target ? prev : target)); - }, [findHoverTarget]); + const target = findHoverTarget(absoluteY, state.subtaskId, state.positions, state.subPositions); + updateHoverTarget(target); + if (target) { + if (state.subPositions[target]) { + setDropIndicator(null); + } else { + const position = calculateDropPosition(absoluteY, target, state.positions); + setDropIndicator({ targetId: target, position }); + } + } else { + const positions = state.positions; + const lastItem = Object.values(positions).reduce((max, p) => p.bottom > max.bottom ? p : max, { bottom: 0 }); + if (absoluteY > lastItem.bottom) { + setDropIndicator({ targetId: null, position: 'below' }); + } else { + setDropIndicator(null); + } + } + }, [findHoverTarget, calculateDropPosition, updateHoverTarget]); const handleSubtaskDragEnd = useCallback(async (absoluteY: number) => { const state = subtaskDragRef.current; @@ -285,12 +369,16 @@ export function TaskList({ categoryId = 'all', onSelectionChange }: TaskListProp setDropIndicator(null); if (!state) return; - const target = findHoverTarget(absoluteY, state.subtaskId, state.positions); - if (target === state.parentTaskId) return; + const target = findHoverTarget(absoluteY, state.subtaskId, state.positions, state.subPositions); + if (target === state.subtaskId) return; if (target) { - await moveSubtaskToTask(state.subtaskId, target); - await fetchSubtasks(target); + if (state.subPositions[target]) { + await setSubtaskParent(state.subtaskId, target); + } else if (target !== state.parentTaskId) { + await moveSubtaskToTask(state.subtaskId, target); + await fetchSubtasks(target); + } } else { await convertSubtaskToTask(state.subtaskId); } @@ -315,6 +403,8 @@ export function TaskList({ categoryId = 'all', onSelectionChange }: TaskListProp selectionMode={selectionMode} hovered={hoverTaskId === item.id} registerRef={registerRef} + registerSubtaskRef={registerSubtaskRef} + hoverTaskId={hoverTaskId} onToggle={handleToggle} onDelete={openTaskDelete} onExpand={toggleExpand} @@ -323,7 +413,6 @@ export function TaskList({ categoryId = 'all', onSelectionChange }: TaskListProp onMenuOpen={(task) => openTaskEdit(task.id)} onSubtaskToggle={handleSubtaskToggle} onSubtaskDelete={openSubtaskDelete} - onSubtaskEdit={openSubtaskEdit} onSubtaskMenuOpen={(subtask) => openSubtaskEdit(subtask.id)} onDragStart={handleDragStart} onDragUpdate={handleDragUpdate} @@ -420,7 +509,7 @@ export function TaskList({ categoryId = 'all', onSelectionChange }: TaskListProp - + Cancel @@ -455,6 +544,8 @@ interface TaskRowProps { selectionMode: boolean; hovered: boolean; registerRef: (taskId: string, ref: View | null) => void; + registerSubtaskRef: (subtaskId: string, parentTaskId: string, ref: View | null) => void; + hoverTaskId: string | null; onToggle: (taskId: string) => void | Promise; onDelete: (task: Task) => void; onExpand: (taskId: string) => void | Promise; @@ -463,7 +554,6 @@ interface TaskRowProps { onMenuOpen: (task: Task) => void; onSubtaskToggle: (subtaskId: string, taskId: string) => void; onSubtaskDelete: (subtask: SubtaskData) => void; - onSubtaskEdit: (subtaskId: string) => void; onSubtaskMenuOpen: (subtask: SubtaskData) => void; onDragStart: (taskId: string) => void; onDragUpdate: (absoluteY: number) => void; @@ -485,7 +575,9 @@ const TaskRow = React.memo(function TaskRow({ selected, selectionMode, hovered, + hoverTaskId, registerRef, + registerSubtaskRef, onToggle, onDelete, onExpand, @@ -494,7 +586,6 @@ const TaskRow = React.memo(function TaskRow({ onMenuOpen, onSubtaskToggle, onSubtaskDelete, - onSubtaskEdit, onSubtaskMenuOpen, onDragStart, onDragUpdate, @@ -540,12 +631,13 @@ const TaskRow = React.memo(function TaskRow({ {expanded && subtasks.length > 0 && ( {sortedSubtasks.map((sub) => ( - onSubtaskToggle(sub.id, task.id)} + onSubtaskToggle(sub.id, task.id)} onDelete={() => onSubtaskDelete(sub)} - onPress={() => onSubtaskEdit(sub.id)} onMenuOpen={() => onSubtaskMenuOpen(sub)} selected={selectedIds.has(sub.id)} selectionMode={selectionMode} @@ -569,7 +661,7 @@ const styles = StyleSheet.create({ listContent: { paddingHorizontal: 16, paddingTop: 8, - paddingBottom: 120, + paddingBottom: 12, }, loadingContainer: { flex: 1, diff --git a/carry-your-live/src/components/TaskOverflowMenu.tsx b/carry-your-live/src/components/TaskOverflowMenu.tsx index 100ae0b..d86dd92 100644 --- a/carry-your-live/src/components/TaskOverflowMenu.tsx +++ b/carry-your-live/src/components/TaskOverflowMenu.tsx @@ -47,19 +47,19 @@ export function TaskOverflowMenu({ { key: 'edit', label: 'Edit', - icon: , + icon: , onPress: onEdit, }, { key: 'complete', label: task.completed ? 'Mark Incomplete' : 'Mark Complete', - icon: , + icon: , onPress: onToggleComplete, }, { key: 'duplicate', label: 'Duplicate', - icon: , + icon: , onPress: onDuplicate, }, ]; @@ -77,14 +77,14 @@ export function TaskOverflowMenu({ { key: 'priority', label: 'Change Priority', - icon: , + icon: , onPress: onChangePriority, }, { key: 'delete', label: 'Delete', destructive: true, - icon: , + icon: , onPress: onDelete, }, ); @@ -93,7 +93,7 @@ export function TaskOverflowMenu({ actions.push({ key: 'addSubtask', label: 'Add Subtask', - icon: , + icon: , onPress: onAddSubtask, }); } @@ -138,8 +138,8 @@ function Circle2() { const { theme } = useSettings(); return ( - - + + ); } diff --git a/carry-your-live/src/components/WheelTimePicker.tsx b/carry-your-live/src/components/WheelTimePicker.tsx index 75214b4..d6b86da 100644 --- a/carry-your-live/src/components/WheelTimePicker.tsx +++ b/carry-your-live/src/components/WheelTimePicker.tsx @@ -212,7 +212,7 @@ export function WheelTimePicker({ {title} - + {formatTime12(hourIndex, minuteIndex)} @@ -220,7 +220,7 @@ export function WheelTimePicker({ onPress={() => onConfirm(`${String(hourIndex).padStart(2, '0')}:${String(minuteIndex).padStart(2, '0')}`)} hitSlop={{ top: 8, bottom: 8, left: 8, right: 8 }} > - Done + Done @@ -238,7 +238,7 @@ export function WheelTimePicker({ onIndexChange={setHourIndex} /> - : + : ('subtasks'), repeatProfiles: database.collections.get('repeat_profiles'), friendships: database.collections.get('friendships'), + tombstones: database.collections.get('tombstones'), }; diff --git a/carry-your-live/src/database/migrations.ts b/carry-your-live/src/database/migrations.ts index 6e2ecbb..c21eb6f 100644 --- a/carry-your-live/src/database/migrations.ts +++ b/carry-your-live/src/database/migrations.ts @@ -180,5 +180,18 @@ export const migrations = schemaMigrations({ }), ], }, + { + toVersion: 17, + steps: [ + createTable({ + name: 'tombstones', + columns: [ + { name: 'entity', type: 'string' }, + { name: 'entity_id', type: 'string', isIndexed: true }, + { name: 'deleted_at', type: 'number', isIndexed: true }, + ], + }), + ], + }, ], }); diff --git a/carry-your-live/src/database/schema.ts b/carry-your-live/src/database/schema.ts index 704dca3..375e7fb 100644 --- a/carry-your-live/src/database/schema.ts +++ b/carry-your-live/src/database/schema.ts @@ -1,7 +1,7 @@ import { appSchema, tableSchema } from '@nozbe/watermelondb'; export const schema = appSchema({ - version: 16, + version: 17, tables: [ tableSchema({ name: 'categories', @@ -85,5 +85,13 @@ export const schema = appSchema({ { name: 'updated_at', type: 'number', isIndexed: true }, ], }), + tableSchema({ + name: 'tombstones', + columns: [ + { name: 'entity', type: 'string' }, + { name: 'entity_id', type: 'string', isIndexed: true }, + { name: 'deleted_at', type: 'number', isIndexed: true }, + ], + }), ], }); \ No newline at end of file diff --git a/carry-your-live/src/database/sync.ts b/carry-your-live/src/database/sync.ts index f0080f4..435b80e 100644 --- a/carry-your-live/src/database/sync.ts +++ b/carry-your-live/src/database/sync.ts @@ -3,11 +3,14 @@ import { database, collections } from './index'; import AsyncStorage from '@react-native-async-storage/async-storage'; import { Q } from '@nozbe/watermelondb'; import { apiFetch, getAuthToken } from '@/services/auth'; +import { cancelTaskReminder } from '@/services/notifications'; import Category from '@/models/Category'; import Task from '@/models/Task'; import Subtask from '@/models/Subtask'; import RepeatProfile from '@/models/RepeatProfile'; import Friendship from '@/models/Friendship'; +import { TombstoneEntity } from '@/models/Tombstone'; +import { fetchPendingTombstones, removeTombstonesInBatch } from './tombstones'; const LAST_PULLED_AT_KEY = 'sync:lastPulledAt'; const LAST_RUN_AT_KEY = 'sync:lastRunAt'; @@ -79,11 +82,13 @@ export async function runSync(): Promise { timestamp: 0, }; - const conflicts = await pushChanges(); + const { conflicts, pushedDeletions } = await pushChanges(); result.conflicts = conflicts.length; await applyConflicts(conflicts); + await pruneSyncedTombstones(conflicts, pushedDeletions); + const lastPulledAt = await getLastPulledAt(); const response = await apiFetch(`/sync?since=${lastPulledAt}`); const data = await response.json(); @@ -97,7 +102,7 @@ export async function runSync(): Promise { return result; } -async function pushChanges(): Promise { +async function pushChanges(): Promise<{ conflicts: PushConflict[]; pushedDeletions: { entity: TombstoneEntity; id: string }[] }> { const lastPulledAt = await getLastPulledAt(); const [tasks, subtasks, repeatProfiles, friendships] = await Promise.all([ @@ -125,6 +130,7 @@ async function pushChanges(): Promise { reminder: t.reminder || 'none', reminders: t.reminders || '', assigneeId: t.assigneeId || null, + completedAt: t.completedAt ?? null, createdAt: t.createdAt.getTime(), updatedAt: t.updatedAt.getTime(), }); @@ -143,6 +149,7 @@ async function pushChanges(): Promise { const changedSubtasks = subtasks.map((s) => ({ id: s.id, taskId: s.taskId, + parentSubtaskId: s.parentSubtaskId || null, title: s.title, description: s.description ?? '', priority: s.priority ?? 'none', @@ -221,14 +228,22 @@ async function pushChanges(): Promise { updatedAt: f.updatedAt.getTime(), })); + const pendingTombstones = await fetchPendingTombstones(lastPulledAt); + const changedDeletions = pendingTombstones.map((t) => ({ + entity: t.entity, + id: t.entityId, + updatedAt: t.deletedAt.getTime(), + })); + if ( changedCategories.length === 0 && changedTasks.length === 0 && changedSubtasks.length === 0 && changedRepeatProfiles.length === 0 && - changedFriendships.length === 0 + changedFriendships.length === 0 && + changedDeletions.length === 0 ) { - return []; + return { conflicts: [], pushedDeletions: [] }; } const response = await apiFetch('/sync/push', { @@ -241,12 +256,40 @@ async function pushChanges(): Promise { repeatProfiles: changedRepeatProfiles, friendships: changedFriendships, }, + deleted: changedDeletions, lastPulledAt, }), }); const data = await response.json(); - return (data.conflicts ?? []) as PushConflict[]; + return { + conflicts: (data.conflicts ?? []) as PushConflict[], + pushedDeletions: pendingTombstones.map((t) => ({ entity: t.entity, id: t.entityId })), + }; +} + +// Remove local tombstones that were just accepted by the server. Tombstones +// whose deletion lost to a newer server row (conflict) are kept so the row +// stays marked as deleted locally after the server version wins. +async function pruneSyncedTombstones( + conflicts: PushConflict[], + pushedDeletions: { entity: TombstoneEntity; id: string }[] +): Promise { + if (pushedDeletions.length === 0) return; + const conflictKeys = new Set(conflicts.map((c) => `${c.entity}:${c.id}`)); + const accepted = pushedDeletions.filter((d) => !conflictKeys.has(`${d.entity}:${d.id}`)); + const grouped = new Map(); + for (const t of accepted) { + const list = grouped.get(t.entity) ?? []; + list.push(t.id); + grouped.set(t.entity, list); + } + if (accepted.length === 0) return; + await database.write(async () => { + for (const [entity, ids] of grouped) { + await removeTombstonesInBatch(entity, ids); + } + }); } async function applyConflicts(conflicts: PushConflict[]): Promise { @@ -272,6 +315,9 @@ async function applyConflicts(conflicts: PushConflict[]): Promise { await upsertFriendship(row, true); break; } + // The server version of this row is newer - drop any pending local + // tombstone so the resurrected row is kept and synced going forward. + await removeTombstonesInBatch(conflict.entity, [conflict.id]); } }); } @@ -282,10 +328,16 @@ async function applyServerChanges(data: { subtasks: ServerRow[]; repeatProfiles: ServerRow[]; friendships: ServerRow[]; + deleted?: { entity: TombstoneEntity; id: string; updatedAt: number }[]; }): Promise { const counts = { categories: 0, tasks: 0, subtasks: 0, repeatProfiles: 0, friendships: 0 }; await database.write(async () => { + // Apply deletions first so rows that were removed server-side are gone + // before any upsert re-imports their data. + for (const tomb of data.deleted ?? []) { + await applyRemoteTombstone(tomb); + } for (const cat of data.categories) { if (await upsertCategory(cat, false)) counts.categories++; } @@ -306,6 +358,68 @@ async function applyServerChanges(data: { return counts; } +// Apply a server-side deletion to the local DB, cascading to every related +// row (category -> tasks -> subtasks, task -> subtasks, subtask -> children). +// LWW: if the local row is newer than the deletion, it survives. +async function applyRemoteTombstone(tomb: { entity: TombstoneEntity; id: string; updatedAt: number }): Promise { + const { entity, id, updatedAt } = tomb; + const row = await findOrNull(collections[entity as keyof typeof collections] as any, id); + if (row) { + if (row.updatedAt.getTime() > updatedAt) { + return; // local edit is newer than the remote deletion - keep it + } + await destroyLocalEntityCascade(entity, id); + } else { + // Row already gone locally - clear the tombstone if the server has it. + await removeTombstonesInBatch(entity, [id]); + } +} + +async function destroyLocalEntityCascade(entity: TombstoneEntity, id: string): Promise { + if (entity === 'tasks') { + const subtasks = await collections.subtasks.query(Q.where('task_id', id)).fetch(); + for (const s of subtasks) { + await destroyLocalEntityCascade('subtasks', s.id); + } + const task = await findOrNull(collections.tasks, id).catch(() => null); + if (task) { + await task.destroyPermanently(); + await cancelTaskReminder(id).catch(() => {}); + } + return; + } + if (entity === 'categories') { + const tasks = await collections.tasks.query(Q.where('category_id', id)).fetch(); + for (const t of tasks) { + await destroyLocalEntityCascade('tasks', t.id); + } + const cat = await findOrNull(collections.categories, id).catch(() => null); + if (cat) await cat.destroyPermanently(); + return; + } + if (entity === 'subtasks') { + const children = await collections.subtasks.query(Q.where('parent_subtask_id', id)).fetch(); + for (const c of children) { + await destroyLocalEntityCascade('subtasks', c.id); + } + const sub = await findOrNull(collections.subtasks, id).catch(() => null); + if (sub) { + await sub.destroyPermanently(); + await cancelTaskReminder(id).catch(() => {}); + } + return; + } + if (entity === 'repeatProfiles') { + const p = await findOrNull(collections.repeatProfiles, id).catch(() => null); + if (p) await p.destroyPermanently(); + return; + } + if (entity === 'friendships') { + const f = await findOrNull(collections.friendships, id).catch(() => null); + if (f) await f.destroyPermanently(); + } +} + async function upsertCategory(row: ServerRow, force: boolean): Promise { const local = await findOrNull(collections.categories, row.id); if (!local) { @@ -347,7 +461,9 @@ async function upsertTask(row: ServerRow, force: boolean): Promise { t.repeatDays = String(row.repeatDays ?? ''); t.seriesId = String(row.seriesId ?? ''); t.reminder = row.reminder ?? 'none'; + t.reminders = String(row.reminders ?? ''); t.assigneeId = row.assigneeId ?? null; + t.completedAt = row.completedAt == null ? null : Number(row.completedAt); t.createdAt = new Date(row.createdAt ?? Date.now()); t.updatedAt = new Date(row.updatedAt ?? Date.now()); }); @@ -370,7 +486,9 @@ async function upsertTask(row: ServerRow, force: boolean): Promise { t.repeatDays = String(row.repeatDays ?? t.repeatDays); t.seriesId = String(row.seriesId ?? t.seriesId); t.reminder = row.reminder ?? t.reminder; + t.reminders = String(row.reminders ?? t.reminders ?? ''); t.assigneeId = row.assigneeId ?? t.assigneeId; + t.completedAt = row.completedAt == null ? null : Number(row.completedAt ?? t.completedAt); t.updatedAt = new Date(row.updatedAt ?? Date.now()); }); return true; @@ -410,6 +528,7 @@ async function upsertSubtask(row: ServerRow, force: boolean): Promise { if (!local) { await collections.subtasks.create((s) => { s.taskId = String(row.taskId ?? ''); + s.parentSubtaskId = row.parentSubtaskId == null ? null : String(row.parentSubtaskId); s.title = String(row.title ?? ''); s.description = String(row.description ?? ''); s.priority = row.priority ?? 'none'; @@ -435,6 +554,7 @@ async function upsertSubtask(row: ServerRow, force: boolean): Promise { } await local.update((s) => { s.taskId = String(row.taskId ?? s.taskId); + s.parentSubtaskId = row.parentSubtaskId == null ? null : String(row.parentSubtaskId); s.title = String(row.title ?? s.title); s.description = String(row.description ?? s.description); s.priority = row.priority ?? s.priority; @@ -572,4 +692,4 @@ export function isSyncInFlight(): boolean { export function isWatchingForUpdates(): boolean { return watcherActive; -} +} \ No newline at end of file diff --git a/carry-your-live/src/database/tombstones.ts b/carry-your-live/src/database/tombstones.ts new file mode 100644 index 0000000..1381078 --- /dev/null +++ b/carry-your-live/src/database/tombstones.ts @@ -0,0 +1,65 @@ +import { database, collections } from './index'; +import { Q } from '@nozbe/watermelondb'; +import Tombstone, { TombstoneEntity } from '@/models/Tombstone'; + +// Batch variant: must be called from inside an existing database.write(). +export async function recordTombstonesInBatch(entity: TombstoneEntity, ids: string[]): Promise { + const uniqueIds = [...new Set(ids)].filter(Boolean); + for (const id of uniqueIds) { + const existing = await collections.tombstones + .query(Q.where('entity', entity), Q.where('entity_id', id)) + .fetch(); + const now = new Date(); + if (existing.length > 0) { + await existing[0].update((t) => { + t.deletedAt = now; + }); + } else { + await collections.tombstones.create((t) => { + t.entity = entity; + t.entityId = id; + t.deletedAt = now; + }); + } + } +} + +// Batch variant: must be called from inside an existing database.write(). +export async function removeTombstonesInBatch(entity: TombstoneEntity, ids: string[]): Promise { + for (const id of ids) { + const existing = await collections.tombstones + .query(Q.where('entity', entity), Q.where('entity_id', id)) + .fetch(); + for (const row of existing) { + await row.destroyPermanently(); + } + } +} + +export async function recordTombstone(entity: TombstoneEntity, id: string): Promise { + await database.write(async () => { + await recordTombstonesInBatch(entity, [id]); + }); +} + +export async function recordTombstones(entity: TombstoneEntity, ids: string[]): Promise { + await database.write(async () => { + await recordTombstonesInBatch(entity, ids); + }); +} + +export async function removeTombstone(entity: TombstoneEntity, id: string): Promise { + await database.write(async () => { + await removeTombstonesInBatch(entity, [id]); + }); +} + +export async function removeTombstones(entity: TombstoneEntity, ids: string[]): Promise { + await database.write(async () => { + await removeTombstonesInBatch(entity, ids); + }); +} + +export async function fetchPendingTombstones(sinceMs: number): Promise { + return collections.tombstones.query(Q.where('deleted_at', Q.gt(sinceMs))).fetch(); +} diff --git a/carry-your-live/src/models/Task.ts b/carry-your-live/src/models/Task.ts index 309cee4..4e9bb11 100644 --- a/carry-your-live/src/models/Task.ts +++ b/carry-your-live/src/models/Task.ts @@ -23,7 +23,6 @@ export default class Task extends Model { @field('repeat') repeat!: Repeat; @field('repeat_interval') repeatInterval!: number; @field('repeat_days') repeatDays!: string; - @field('color') color!: string; @field('series_id') seriesId!: string; @field('reminder') reminder!: Reminder; @field('reminders') reminders!: string; diff --git a/carry-your-live/src/models/Tombstone.ts b/carry-your-live/src/models/Tombstone.ts new file mode 100644 index 0000000..5bd620a --- /dev/null +++ b/carry-your-live/src/models/Tombstone.ts @@ -0,0 +1,12 @@ +import { Model } from '@nozbe/watermelondb'; +import { field, date } from '@nozbe/watermelondb/decorators'; + +export type TombstoneEntity = 'categories' | 'tasks' | 'subtasks' | 'repeatProfiles' | 'friendships'; + +export default class Tombstone extends Model { + static table = 'tombstones'; + + @field('entity') entity!: TombstoneEntity; + @field('entity_id') entityId!: string; + @date('deleted_at') deletedAt!: Date; +} diff --git a/carry-your-live/src/services/auth.ts b/carry-your-live/src/services/auth.ts index 37e6137..b1c8b17 100644 --- a/carry-your-live/src/services/auth.ts +++ b/carry-your-live/src/services/auth.ts @@ -97,15 +97,22 @@ export async function apiFetch(path: string, options: RequestInit = {}): Promise const response = await fetch(`${apiBaseUrl}${path}`, { ...options, headers }); if (!response.ok) { let message = `Request failed (${response.status})`; + let details: any = null; try { const body = await response.json(); if (body && typeof body.message === 'string') { message = body.message; } + if (body && body.error && body.error.details) { + details = body.error.details; + } } catch { // ignore } - throw new Error(message); + const error = new Error(message); + (error as any).details = details; + (error as any).status = response.status; + throw error; } return response; } diff --git a/carry-your-live/src/theme.tsx b/carry-your-live/src/theme.tsx index 27c88eb..db4fecc 100644 --- a/carry-your-live/src/theme.tsx +++ b/carry-your-live/src/theme.tsx @@ -34,6 +34,7 @@ export interface ThemeColors { textFaint: string; textMuted: string; accent: string; + accentStrong: string; accentSoft: string; accentBorder: string; accentText: string; @@ -74,6 +75,13 @@ export function mixHex(color: string, target: string, ratio: number): string { return `#${toHexByte(r1 + (r2 - r1) * ratio)}${toHexByte(g1 + (g2 - g1) * ratio)}${toHexByte(b1 + (b2 - b1) * ratio)}`.toUpperCase(); } +export function desaturate(hex: string, amount: number): string { + const [r, g, b] = parseHex(hex); + const gray = 0.2126 * r + 0.7152 * g + 0.0722 * b; + const shift = 1 - amount; + return `#${toHexByte(gray + (r - gray) * shift)}${toHexByte(gray + (g - gray) * shift)}${toHexByte(gray + (b - gray) * shift)}`.toUpperCase(); +} + function accentTextColor(accent: string): string { const [r, g, b] = parseHex(accent).map((v) => v / 255); const linear = (v: number) => (v <= 0.03928 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4)); @@ -82,24 +90,26 @@ function accentTextColor(accent: string): string { } export function colors(accent: string = DEFAULT_ACCENT): ThemeColors { + const softAccent = desaturate(accent, 0.35); return { background: '#121212', card: '#1E1E1E', - cardAlt: '#262626', - border: '#2A2A2A', - borderStrong: '#3A3A3A', - text: '#F5F5F5', - textSecondary: '#E0E0E0', - textFaint: '#BDBDBD', - textMuted: '#8E8E8E', + cardAlt: '#28292C', + border: '#2C2E33', + borderStrong: '#3C3E45', + text: '#F2F3F5', + textSecondary: '#E3E4E8', + textFaint: '#C4C6CC', + textMuted: '#9C9FA7', accent, - accentSoft: mixHex(accent, '#121212', 0.12), - accentBorder: mixHex(accent, '#121212', 0.25), + accentStrong: mixHex(accent, '#FFFFFF', 0.55), + accentSoft: mixHex(softAccent, '#121212', 0.18), + accentBorder: mixHex(desaturate(accent, 0.15), '#121212', 0.5), accentText: accentTextColor(accent), - inputBg: '#1A1A1A', + inputBg: '#1A1B1E', overlay: 'rgba(0,0,0,0.6)', - sheetBg: '#242424', - tabBarBg: '#1A1A1A', + sheetBg: '#26282D', + tabBarBg: '#16171A', }; } @@ -225,3 +235,5 @@ export function useSettings() { } return context; } + +export * from './theme/tokens'; diff --git a/carry-your-live/src/theme/tokens.ts b/carry-your-live/src/theme/tokens.ts new file mode 100644 index 0000000..1a435d5 --- /dev/null +++ b/carry-your-live/src/theme/tokens.ts @@ -0,0 +1,194 @@ +export const SPACING = { + xs: 4, + sm: 8, + md: 12, + lg: 16, + xl: 20, + xxl: 24, + xxxl: 32, +} as const; + +export const BORDER_RADIUS = { + xs: 6, + sm: 8, + md: 10, + lg: 12, + xl: 16, + xxl: 20, + round: 999, + circle: 50, +} as const; + +export const SHADOWS = { + none: { + shadowColor: 'transparent', + shadowOffset: { width: 0, height: 0 }, + shadowOpacity: 0, + shadowRadius: 0, + elevation: 0, + }, + xs: { + shadowColor: '#000', + shadowOffset: { width: 0, height: 1 }, + shadowOpacity: 0.04, + shadowRadius: 2, + elevation: 1, + }, + sm: { + shadowColor: '#000', + shadowOffset: { width: 0, height: 2 }, + shadowOpacity: 0.06, + shadowRadius: 4, + elevation: 2, + }, + md: { + shadowColor: '#000', + shadowOffset: { width: 0, height: 4 }, + shadowOpacity: 0.08, + shadowRadius: 8, + elevation: 4, + }, + lg: { + shadowColor: '#000', + shadowOffset: { width: 0, height: 8 }, + shadowOpacity: 0.12, + shadowRadius: 16, + elevation: 8, + }, + xl: { + shadowColor: '#000', + shadowOffset: { width: 0, height: 12 }, + shadowOpacity: 0.16, + shadowRadius: 24, + elevation: 12, + }, + inner: { + shadowColor: '#000', + shadowOffset: { width: 0, height: 1 }, + shadowOpacity: 0.03, + shadowRadius: 2, + elevation: 0, + }, +} as const; + +export const TYPOGRAPHY = { + display: { + fontSize: 34, + fontWeight: '700' as const, + lineHeight: 42, + letterSpacing: -0.5, + }, + h1: { + fontSize: 28, + fontWeight: '700' as const, + lineHeight: 36, + letterSpacing: -0.3, + }, + h2: { + fontSize: 22, + fontWeight: '700' as const, + lineHeight: 30, + letterSpacing: -0.2, + }, + h3: { + fontSize: 18, + fontWeight: '600' as const, + lineHeight: 24, + letterSpacing: 0, + }, + h4: { + fontSize: 16, + fontWeight: '600' as const, + lineHeight: 22, + letterSpacing: 0, + }, + body: { + fontSize: 16, + fontWeight: '400' as const, + lineHeight: 24, + letterSpacing: 0, + }, + bodyStrong: { + fontSize: 16, + fontWeight: '600' as const, + lineHeight: 24, + letterSpacing: 0, + }, + bodySmall: { + fontSize: 14, + fontWeight: '400' as const, + lineHeight: 20, + letterSpacing: 0, + }, + bodySmallStrong: { + fontSize: 14, + fontWeight: '600' as const, + lineHeight: 20, + letterSpacing: 0, + }, + caption: { + fontSize: 12, + fontWeight: '400' as const, + lineHeight: 16, + letterSpacing: 0.2, + }, + captionStrong: { + fontSize: 12, + fontWeight: '600' as const, + lineHeight: 16, + letterSpacing: 0.2, + }, + overline: { + fontSize: 11, + fontWeight: '600' as const, + lineHeight: 14, + letterSpacing: 0.8, + textTransform: 'uppercase' as const, + }, + button: { + fontSize: 15, + fontWeight: '600' as const, + lineHeight: 20, + letterSpacing: 0.2, + }, + buttonSmall: { + fontSize: 13, + fontWeight: '600' as const, + lineHeight: 18, + letterSpacing: 0.2, + }, +} as const; + +export const TRANSITIONS = { + fast: 120, + normal: 200, + slow: 300, + slower: 400, +} as const; + +export const EASING = { + easeOut: 'ease-out', + easeInOut: 'ease-in-out', + spring: 'spring', +} as const; + +export const LAYOUT = { + screenPadding: SPACING.lg, + cardPadding: SPACING.lg, + sectionGap: SPACING.xl, + itemGap: SPACING.md, + inlineGap: SPACING.sm, +} as const; + +export const TOUCH_TARGET = { + minSize: 44, + comfortableSize: 48, +} as const; + +export const Z_INDEX = { + base: 0, + dropdown: 100, + modal: 200, + toast: 300, + tooltip: 400, +} as const; \ No newline at end of file diff --git a/carry-your-live/src/types/index.ts b/carry-your-live/src/types/index.ts index 0df524f..4193c69 100644 --- a/carry-your-live/src/types/index.ts +++ b/carry-your-live/src/types/index.ts @@ -12,6 +12,12 @@ export const REPEAT_OPTIONS: { value: Repeat; label: string }[] = [ export const WEEKDAY_LABELS = ['S', 'M', 'T', 'W', 'T', 'F', 'S']; +export const WEEKDAY_ORDER = [1, 2, 3, 4, 5, 6, 0]; + +export function weekdaysInDisplayOrder(days: number[]): number[] { + return WEEKDAY_ORDER.filter((day) => days.includes(day)); +} + export function repeatDaysToString(days: number[]): string { return [...days].sort().join(','); } diff --git a/carry-your-live/src/utils/categoryActions.ts b/carry-your-live/src/utils/categoryActions.ts index 4a4d4cd..23f10e3 100644 --- a/carry-your-live/src/utils/categoryActions.ts +++ b/carry-your-live/src/utils/categoryActions.ts @@ -1,5 +1,6 @@ import { database, collections } from '@/database'; import { Q } from '@nozbe/watermelondb'; +import { recordTombstonesInBatch } from '@/database/tombstones'; export async function createCategory(name: string, color: string): Promise { await database.write(async () => { @@ -42,5 +43,6 @@ export async function deleteCategory(categoryId: string): Promise { const category = await collections.categories.find(categoryId); await category.destroyPermanently(); + await recordTombstonesInBatch('categories', [categoryId]); }); } diff --git a/carry-your-live/src/utils/repeatProfileActions.ts b/carry-your-live/src/utils/repeatProfileActions.ts index fe9a8b1..5b9c727 100644 --- a/carry-your-live/src/utils/repeatProfileActions.ts +++ b/carry-your-live/src/utils/repeatProfileActions.ts @@ -1,5 +1,6 @@ import { database, collections } from '../database'; import { Repeat } from '@/types'; +import { recordTombstonesInBatch } from '../database/tombstones'; export async function createRepeatProfile( name: string, @@ -24,5 +25,6 @@ export async function deleteRepeatProfile(id: string): Promise { await database.write(async () => { const profile = await collections.repeatProfiles.find(id); await profile.destroyPermanently(); + await recordTombstonesInBatch('repeatProfiles', [id]); }); } diff --git a/carry-your-live/src/utils/tabHistory.ts b/carry-your-live/src/utils/tabHistory.ts new file mode 100644 index 0000000..bd57121 --- /dev/null +++ b/carry-your-live/src/utils/tabHistory.ts @@ -0,0 +1,16 @@ +export const TAB_BAR_HEIGHT = 62; + +let lastVisitedTab = 'index'; + +export function setLastVisitedTab(tab: string): void { + lastVisitedTab = tab; +} + +export function getLastVisitedTab(): string { + return lastVisitedTab; +} + +export function tabHref(tab: string): string { + if (tab === 'index') return '/'; + return `/${tab}`; +} \ No newline at end of file diff --git a/carry-your-live/src/utils/taskActions.ts b/carry-your-live/src/utils/taskActions.ts index c351b96..1e2e10a 100644 --- a/carry-your-live/src/utils/taskActions.ts +++ b/carry-your-live/src/utils/taskActions.ts @@ -2,6 +2,7 @@ import { database, collections } from '@/database'; import { Q } from '@nozbe/watermelondb'; import { Priority, Repeat, Reminder, SubtaskData } from '@/types'; import { scheduleTaskReminder, cancelTaskReminder } from '@/services/notifications'; +import { recordTombstonesInBatch } from '@/database/tombstones'; function mapSubtaskRow(s: any, taskId?: string): SubtaskData { return { @@ -147,6 +148,10 @@ export async function deleteTaskWithSubtasks(taskId: string): Promise { } const task = await collections.tasks.find(taskId); await task.destroyPermanently(); + if (subtasks.length > 0) { + await recordTombstonesInBatch('subtasks', subtasks.map((s) => s.id)); + } + await recordTombstonesInBatch('tasks', [taskId]); } export type TaskDeleteScope = 'this' | 'future' | 'all'; @@ -200,7 +205,7 @@ export async function toggleTaskComplete(taskId: string): Promise t.updatedAt = new Date(); }); if (completing && task.repeat !== 'none' && task.dueDate) { - nextOccurrence = await createNextOccurrence(task); + nextOccurrence = await createNextOccurrenceIfNeeded(task); } }); if (nextOccurrence) await scheduleTaskReminder(nextOccurrence); @@ -275,7 +280,15 @@ export async function deleteSubtask(subtaskId: string): Promise { await database.write(async () => { const subtask = await collections.subtasks.find(subtaskId); parentTaskId = subtask.taskId; + const children = await collections.subtasks.query(Q.where('parent_subtask_id', subtaskId)).fetch(); + for (const child of children) { + await child.update((c) => { + c.parentSubtaskId = null; + c.updatedAt = new Date(); + }); + } await subtask.destroyPermanently(); + await recordTombstonesInBatch('subtasks', [subtaskId]); }); await cancelTaskReminder(subtaskId); if (parentTaskId) { @@ -379,17 +392,21 @@ export async function deleteTask(taskId: string): Promise { }); } -export async function convertTaskToSubtask(taskId: string, parentTaskId: string): Promise { +export async function convertTaskToSubtask(taskId: string, parentTaskId: string, parentSubtaskId: string | null = null): Promise { if (taskId === parentTaskId) return; await database.write(async () => { const task = await collections.tasks.find(taskId); const ownSubtasks = await collections.subtasks.query(Q.where('task_id', taskId)).fetch(); - const existing = await collections.subtasks.query(Q.where('task_id', parentTaskId)).fetch(); + const existing = await collections.subtasks.query( + Q.where('task_id', parentTaskId), + parentSubtaskId ? Q.where('parent_subtask_id', parentSubtaskId) : Q.where('parent_subtask_id', null) + ).fetch(); const now = new Date(); let order = existing.length; - await collections.subtasks.create((s) => { + const created = await collections.subtasks.create((s) => { s.taskId = parentTaskId; + s.parentSubtaskId = parentSubtaskId; s.title = task.title; s.description = task.description; s.priority = task.priority; @@ -412,6 +429,7 @@ export async function convertTaskToSubtask(taskId: string, parentTaskId: string) for (const subtask of ownSubtasks) { await collections.subtasks.create((s) => { s.taskId = parentTaskId; + s.parentSubtaskId = created.id; s.title = subtask.title; s.description = subtask.description; s.priority = subtask.priority; @@ -432,7 +450,13 @@ export async function convertTaskToSubtask(taskId: string, parentTaskId: string) }); } + const parent = await collections.tasks.find(parentTaskId); + await parent.update((p) => { + p.updatedAt = now; + }); + await task.destroyPermanently(); + await recordTombstonesInBatch('tasks', [taskId]); }); } @@ -464,7 +488,16 @@ export async function convertSubtaskToTask(subtaskId: string): Promise { }); newTaskId = task.id; + const children = await collections.subtasks.query(Q.where('parent_subtask_id', subtaskId)).fetch(); + for (const child of children) { + await child.update((c) => { + c.parentSubtaskId = null; + c.updatedAt = now; + }); + } + await subtask.destroyPermanently(); + await recordTombstonesInBatch('subtasks', [subtaskId]); await parent.update((p) => { p.updatedAt = now; }); @@ -487,6 +520,7 @@ export async function moveSubtaskToTask(subtaskId: string, toTaskId: string): Pr await subtask.update((s) => { s.taskId = toTaskId; + s.parentSubtaskId = null; s.order = siblings.length; s.updatedAt = now; }); @@ -505,6 +539,44 @@ export async function moveSubtaskToTask(subtaskId: string, toTaskId: string): Pr }); } +export async function setSubtaskParent(subtaskId: string, newParentId: string): Promise { + if (subtaskId === newParentId) return; + await database.write(async () => { + const subtask = await collections.subtasks.find(subtaskId); + if (subtask.parentSubtaskId === newParentId) return; + + let cursor: any = null; + try { + cursor = await collections.subtasks.find(newParentId); + } catch { + return; + } + const now = new Date(); + let guard = 0; + while (cursor && guard++ < 200) { + if (cursor.id === subtaskId) return; + if (!cursor.parentSubtaskId) break; + cursor = await collections.subtasks.find(cursor.parentSubtaskId); + } + + const siblings = await collections.subtasks.query( + Q.where('task_id', subtask.taskId), + Q.where('parent_subtask_id', newParentId) + ).fetch(); + + await subtask.update((s) => { + s.parentSubtaskId = newParentId; + s.order = siblings.length; + s.updatedAt = now; + }); + + const task = await collections.tasks.find(subtask.taskId); + await task.update((t) => { + t.updatedAt = now; + }); + }); +} + export async function setTaskCompleted(taskId: string, completed: boolean): Promise { let nextOccurrence: any = null; await database.write(async () => { @@ -515,7 +587,7 @@ export async function setTaskCompleted(taskId: string, completed: boolean): Prom t.updatedAt = new Date(); }); if (completed && task.repeat !== 'none' && task.dueDate) { - nextOccurrence = await createNextOccurrence(task); + nextOccurrence = await createNextOccurrenceIfNeeded(task); } }); if (nextOccurrence) await scheduleTaskReminder(nextOccurrence); @@ -524,7 +596,23 @@ export async function setTaskCompleted(taskId: string, completed: boolean): Prom else await scheduleTaskReminder(task); } -async function createNextOccurrence(task: any): Promise { +async function createNextOccurrenceIfNeeded(task: any): Promise { + const seriesId = task.seriesId || newSeriesId(); + const existing = await collections.tasks.query( + Q.where('series_id', seriesId), + Q.where('completed', false) + ).fetch(); + if (existing.length > 0) return null; + const next = await createNextOccurrence(task, seriesId); + if (!task.seriesId) { + await task.update((t: any) => { + t.seriesId = seriesId; + }); + } + return next; +} + +async function createNextOccurrence(task: any, seriesId: string): Promise { const nextDate = computeNextOccurrence(task.dueDate, task.repeat, task.repeatInterval, task.repeatDays); const subtasks = await collections.subtasks.query(Q.where('task_id', task.id)).fetch(); const now = new Date(); @@ -542,7 +630,7 @@ async function createNextOccurrence(task: any): Promise { t.repeat = task.repeat; t.repeatInterval = task.repeatInterval || 1; t.repeatDays = task.repeatDays || ''; - t.seriesId = task.seriesId || newSeriesId(); + t.seriesId = seriesId; t.reminder = task.reminder || 'none'; t.assigneeId = task.assigneeId ?? null; t.createdAt = now; diff --git a/creds.txt b/creds.txt index 31ae4be..f733e01 100644 --- a/creds.txt +++ b/creds.txt @@ -1,2 +1,6 @@ tech -osuhhgoshghgjs \ No newline at end of file +osuhhgoshghgjs + + +test +SFGHDHTHHDHRERTHEH \ No newline at end of file diff --git a/media/clock.jpg b/design drafts/clock.jpg similarity index 100% rename from media/clock.jpg rename to design drafts/clock.jpg diff --git a/media/draft.png b/design drafts/draft.png similarity index 100% rename from media/draft.png rename to design drafts/draft.png diff --git a/opencode.json b/opencode.json new file mode 100644 index 0000000..14b0154 --- /dev/null +++ b/opencode.json @@ -0,0 +1,4 @@ +{ + "$schema": "https://opencode.ai/config.json", + "subagent_depth": 999999 +} \ No newline at end of file diff --git a/todo.md b/todo.md index b74c35c..045c2f7 100644 --- a/todo.md +++ b/todo.md @@ -31,4 +31,4 @@ - [ ] bottom page buttons need to be reworked looking ass currently. - [ ] add a task button should say "Add a Task" and (the button to add have an arrow pointing upwards instead of a cross) could look better - [ ] space between add task button and bottom bar should be zero of the task below should be blurred currently task visible between the two looks weird -- [ ] Stats page rework (when time is right) \ No newline at end of file +- [ ] Stats page rework (when time is right)