"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 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 { since } = validation_1.syncQuerySchema.parse(req.query); const userId = req.user.userId; const sinceDate = since; // Fetch categories changed since timestamp const changedCategories = await db_1.db .select() .from(schema_1.categories) .where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(schema_1.categories.userId, userId), (0, drizzle_orm_1.gte)(schema_1.categories.updatedAt, sinceDate))) .orderBy((0, drizzle_orm_1.asc)(schema_1.categories.updatedAt)); // Fetch tasks changed since timestamp const changedTasks = await db_1.db .select() .from(schema_1.tasks) .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) { changedSubtasks = await db_1.db .select() .from(schema_1.subtasks) .where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(schema_1.subtasks.userId, userId), (0, drizzle_orm_1.gte)(schema_1.subtasks.updatedAt, sinceDate))) .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) changedSubtasks = await db_1.db .select() .from(schema_1.subtasks) .where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(schema_1.subtasks.userId, userId), (0, drizzle_orm_1.gte)(schema_1.subtasks.updatedAt, sinceDate))) .orderBy((0, drizzle_orm_1.asc)(schema_1.subtasks.updatedAt)); } // Fetch repeat profiles changed since timestamp const changedRepeatProfiles = await db_1.db .select() .from(schema_1.repeatProfiles) .where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(schema_1.repeatProfiles.userId, userId), (0, drizzle_orm_1.gte)(schema_1.repeatProfiles.updatedAt, sinceDate))) .orderBy((0, drizzle_orm_1.asc)(schema_1.repeatProfiles.updatedAt)); // Fetch friendships changed since timestamp const changedFriendships = await db_1.db .select() .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)); const timestamp = Date.now(); res.json({ categories: changedCategories, tasks: changedTasks, subtasks: changedSubtasks, repeatProfiles: changedRepeatProfiles, friendships: changedFriendships, timestamp, }); })); router.post('/push', (0, asyncHandler_1.asyncHandler)(async (req, res) => { const data = validation_1.pushChangesSchema.parse(req.body); const userId = req.user.userId; const conflicts = []; const timestamp = Date.now(); try { await db_1.db.transaction(async (tx) => { // Process categories if (data.changes.categories && data.changes.categories.length > 0) { for (const cat of data.changes.categories) { const existing = await tx .select() .from(schema_1.categories) .where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(schema_1.categories.id, cat.id), (0, drizzle_orm_1.eq)(schema_1.categories.userId, userId))) .limit(1); if (existing.length > 0) { // Check for conflict if (existing[0].updatedAt > cat.updatedAt) { conflicts.push({ entity: 'categories', id: cat.id, serverVersion: existing[0], clientVersion: cat, resolution: 'server_wins', }); continue; // Server wins } await tx .update(schema_1.categories) .set({ name: cat.name, color: cat.color, order: cat.order, updatedAt: cat.updatedAt, }) .where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(schema_1.categories.id, cat.id), (0, drizzle_orm_1.eq)(schema_1.categories.userId, userId))); } else { await tx.insert(schema_1.categories).values({ ...cat, userId, }); } } } // Process tasks if (data.changes.tasks && data.changes.tasks.length > 0) { for (const task of data.changes.tasks) { const existing = await tx .select() .from(schema_1.tasks) .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))) .limit(1); if (existing.length > 0) { // Check for conflict if (existing[0].updatedAt > task.updatedAt) { conflicts.push({ entity: 'tasks', id: task.id, serverVersion: existing[0], clientVersion: task, resolution: 'server_wins', }); continue; // Server wins } await tx .update(schema_1.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 ?? '', repeat: task.repeat ?? 'none', repeatInterval: task.repeatInterval ?? 1, repeatDays: task.repeatDays ?? '', seriesId: task.seriesId ?? '', reminder: task.reminder ?? 'none', assigneeId: task.assigneeId ?? 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))); } else { await tx.insert(schema_1.tasks).values({ ...task, userId, }); } } } // Process subtasks if (data.changes.subtasks && data.changes.subtasks.length > 0) { for (const sub of data.changes.subtasks) { const existing = await tx .select() .from(schema_1.subtasks) .where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(schema_1.subtasks.id, sub.id), (0, drizzle_orm_1.eq)(schema_1.subtasks.userId, userId))) .limit(1); if (existing.length > 0) { // Check for conflict if (existing[0].updatedAt > sub.updatedAt) { conflicts.push({ entity: 'subtasks', id: sub.id, serverVersion: existing[0], clientVersion: sub, resolution: 'server_wins', }); continue; // Server wins } await tx .update(schema_1.subtasks) .set({ taskId: sub.taskId, title: sub.title, completed: sub.completed, order: sub.order, updatedAt: sub.updatedAt, }) .where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(schema_1.subtasks.id, sub.id), (0, drizzle_orm_1.eq)(schema_1.subtasks.userId, userId))); } else { await tx.insert(schema_1.subtasks).values({ ...sub, userId, }); } } } // Process repeat profiles if (data.changes.repeatProfiles && data.changes.repeatProfiles.length > 0) { for (const profile of data.changes.repeatProfiles) { const existing = await tx .select() .from(schema_1.repeatProfiles) .where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(schema_1.repeatProfiles.id, profile.id), (0, drizzle_orm_1.eq)(schema_1.repeatProfiles.userId, userId))) .limit(1); if (existing.length > 0) { // Check for conflict if (existing[0].updatedAt > profile.updatedAt) { conflicts.push({ entity: 'repeatProfiles', id: profile.id, serverVersion: existing[0], clientVersion: profile, resolution: 'server_wins', }); continue; // Server wins } await tx .update(schema_1.repeatProfiles) .set({ name: profile.name, repeat: profile.repeat, repeatInterval: profile.repeatInterval, repeatDays: profile.repeatDays, updatedAt: profile.updatedAt, }) .where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(schema_1.repeatProfiles.id, profile.id), (0, drizzle_orm_1.eq)(schema_1.repeatProfiles.userId, userId))); } else { await tx.insert(schema_1.repeatProfiles).values({ ...profile, userId, }); } } } // Process friendships if (data.changes.friendships && data.changes.friendships.length > 0) { for (const friendship of data.changes.friendships) { const existing = await tx .select() .from(schema_1.friendships) .where((0, drizzle_orm_1.eq)(schema_1.friendships.id, friendship.id)) .limit(1); if (existing.length > 0) { // Check for conflict if (existing[0].updatedAt > friendship.updatedAt) { conflicts.push({ entity: 'friendships', id: friendship.id, serverVersion: existing[0], clientVersion: friendship, resolution: 'server_wins', }); continue; // Server wins } await tx .update(schema_1.friendships) .set({ userId: friendship.userId, friendId: friendship.friendId, status: friendship.status, updatedAt: friendship.updatedAt, }) .where((0, drizzle_orm_1.eq)(schema_1.friendships.id, friendship.id)); } else { await tx.insert(schema_1.friendships).values(friendship); } } } }); } catch (error) { console.error('Sync push error:', error); throw new errorHandler_1.AppError('SERVER_ERROR', 'Failed to process sync push', 500); } res.json({ success: true, timestamp, conflicts, }); })); exports.default = router; //# sourceMappingURL=sync.js.map