This commit is contained in:
Vendored
+3
@@ -0,0 +1,3 @@
|
||||
declare const router: import("express-serve-static-core").Router;
|
||||
export default router;
|
||||
//# sourceMappingURL=auth.d.ts.map
|
||||
Vendored
+1
@@ -0,0 +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"}
|
||||
Vendored
+76
@@ -0,0 +1,76 @@
|
||||
"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 zod_1 = require("zod");
|
||||
const router = (0, express_1.Router)();
|
||||
const usernameSchema = zod_1.z
|
||||
.string()
|
||||
.min(3)
|
||||
.max(30)
|
||||
.regex(/^[a-zA-Z0-9_.-]+$/, 'Username can only contain letters, numbers, dots, dashes and underscores');
|
||||
const registerSchema = zod_1.z.object({
|
||||
username: usernameSchema,
|
||||
password: zod_1.z.string().min(8),
|
||||
});
|
||||
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');
|
||||
}
|
||||
function verifyPassword(password, hash) {
|
||||
return hashPassword(password) === hash;
|
||||
}
|
||||
router.post('/register', (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) {
|
||||
throw new errorHandler_1.AppError('USERNAME_EXISTS', 'Username already registered', 409);
|
||||
}
|
||||
const userId = (0, auth_1.generateId)('user');
|
||||
const now = (0, auth_1.getCurrentTimestamp)();
|
||||
await db_1.db.insert(schema_1.users).values({
|
||||
id: userId,
|
||||
username: data.username,
|
||||
passwordHash: hashPassword(data.password),
|
||||
createdAt: now,
|
||||
});
|
||||
const token = (0, auth_1.generateToken)({ userId, username: data.username });
|
||||
res.status(201).json({
|
||||
user: { id: userId, username: data.username },
|
||||
token,
|
||||
});
|
||||
}));
|
||||
router.post('/login', (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)) {
|
||||
throw new errorHandler_1.AppError('INVALID_CREDENTIALS', 'Invalid username or password', 401);
|
||||
}
|
||||
const token = (0, auth_1.generateToken)({ userId: user[0].id, username: user[0].username });
|
||||
res.json({
|
||||
user: { id: user[0].id, username: user[0].username },
|
||||
token,
|
||||
});
|
||||
}));
|
||||
router.get('/me', (0, asyncHandler_1.asyncHandler)(async (req, res) => {
|
||||
if (!req.user) {
|
||||
throw new errorHandler_1.AppError('UNAUTHORIZED', 'Authentication required', 401);
|
||||
}
|
||||
const user = await db_1.db.select().from(schema_1.users).where((0, drizzle_orm_1.eq)(schema_1.users.id, req.user.userId)).limit(1);
|
||||
if (user.length === 0) {
|
||||
throw new errorHandler_1.AppError('NOT_FOUND', 'User not found', 404);
|
||||
}
|
||||
res.json({ id: user[0].id, username: user[0].username });
|
||||
}));
|
||||
exports.default = router;
|
||||
//# sourceMappingURL=auth.js.map
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/routes/auth.ts"],"names":[],"mappings":";;AAAA,qCAAoD;AACpD,wDAAqD;AACrD,8BAA2B;AAC3B,yCAAqC;AACrC,6CAAiC;AACjC,wCAA+E;AAC/E,6DAAsD;AACtD,6BAAwB;AAExB,MAAM,MAAM,GAAG,IAAA,gBAAM,GAAE,CAAC;AAExB,MAAM,cAAc,GAAG,OAAC;KACrB,MAAM,EAAE;KACR,GAAG,CAAC,CAAC,CAAC;KACN,GAAG,CAAC,EAAE,CAAC;KACP,KAAK,CAAC,mBAAmB,EAAE,0EAA0E,CAAC,CAAC;AAE1G,MAAM,cAAc,GAAG,OAAC,CAAC,MAAM,CAAC;IAC9B,QAAQ,EAAE,cAAc;IACxB,QAAQ,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;CAC5B,CAAC,CAAC;AAEH,MAAM,WAAW,GAAG,OAAC,CAAC,MAAM,CAAC;IAC3B,QAAQ,EAAE,OAAC,CAAC,MAAM,EAAE;IACpB,QAAQ,EAAE,OAAC,CAAC,MAAM,EAAE;CACrB,CAAC,CAAC;AAEH,2DAA2D;AAC3D,SAAS,YAAY,CAAC,QAAgB;IACpC,2DAA2D;IAC3D,OAAO,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;AAClD,CAAC;AAED,SAAS,cAAc,CAAC,QAAgB,EAAE,IAAY;IACpD,OAAO,YAAY,CAAC,QAAQ,CAAC,KAAK,IAAI,CAAC;AACzC,CAAC;AAED,MAAM,CAAC,IAAI,CAAC,WAAW,EAAE,IAAA,2BAAY,EAAC,KAAK,EAAE,GAAY,EAAE,GAAa,EAAE,EAAE;IAC1E,MAAM,IAAI,GAAG,cAAc,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAE5C,MAAM,QAAQ,GAAG,MAAM,OAAE,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,cAAK,CAAC,CAAC,KAAK,CAAC,IAAA,gBAAE,EAAC,cAAK,CAAC,QAAQ,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IACjG,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACxB,MAAM,IAAI,uBAAQ,CAAC,iBAAiB,EAAE,6BAA6B,EAAE,GAAG,CAAC,CAAC;IAC5E,CAAC;IAED,MAAM,MAAM,GAAG,IAAA,iBAAU,EAAC,MAAM,CAAC,CAAC;IAClC,MAAM,GAAG,GAAG,IAAA,0BAAmB,GAAE,CAAC;IAElC,MAAM,OAAE,CAAC,MAAM,CAAC,cAAK,CAAC,CAAC,MAAM,CAAC;QAC5B,EAAE,EAAE,MAAM;QACV,QAAQ,EAAE,IAAI,CAAC,QAAQ;QACvB,YAAY,EAAE,YAAY,CAAC,IAAI,CAAC,QAAQ,CAAC;QACzC,SAAS,EAAE,GAAG;KACf,CAAC,CAAC;IAEH,MAAM,KAAK,GAAG,IAAA,oBAAa,EAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC;IAEjE,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC;QACnB,IAAI,EAAE,EAAE,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE;QAC7C,KAAK;KACN,CAAC,CAAC;AACL,CAAC,CAAC,CAAC,CAAC;AAEJ,MAAM,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAA,2BAAY,EAAC,KAAK,EAAE,GAAY,EAAE,GAAa,EAAE,EAAE;IACvE,MAAM,IAAI,GAAG,WAAW,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAEzC,MAAM,IAAI,GAAG,MAAM,OAAE,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,cAAK,CAAC,CAAC,KAAK,CAAC,IAAA,gBAAE,EAAC,cAAK,CAAC,QAAQ,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IAC7F,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,EAAE,CAAC;QAC9E,MAAM,IAAI,uBAAQ,CAAC,qBAAqB,EAAE,8BAA8B,EAAE,GAAG,CAAC,CAAC;IACjF,CAAC;IAED,MAAM,KAAK,GAAG,IAAA,oBAAa,EAAC,EAAE,MAAM,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,QAAQ,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC;IAEhF,GAAG,CAAC,IAAI,CAAC;QACP,IAAI,EAAE,EAAE,EAAE,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,QAAQ,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE;QACpD,KAAK;KACN,CAAC,CAAC;AACL,CAAC,CAAC,CAAC,CAAC;AAEJ,MAAM,CAAC,GAAG,CAAC,KAAK,EAAE,IAAA,2BAAY,EAAC,KAAK,EAAE,GAAY,EAAE,GAAa,EAAE,EAAE;IACnE,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC;QACd,MAAM,IAAI,uBAAQ,CAAC,cAAc,EAAE,yBAAyB,EAAE,GAAG,CAAC,CAAC;IACrE,CAAC;IAED,MAAM,IAAI,GAAG,MAAM,OAAE,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,cAAK,CAAC,CAAC,KAAK,CAAC,IAAA,gBAAE,EAAC,cAAK,CAAC,EAAE,EAAE,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IACzF,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACtB,MAAM,IAAI,uBAAQ,CAAC,WAAW,EAAE,gBAAgB,EAAE,GAAG,CAAC,CAAC;IACzD,CAAC;IAED,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,QAAQ,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC;AAC3D,CAAC,CAAC,CAAC,CAAC;AAEJ,kBAAe,MAAM,CAAC"}
|
||||
Vendored
+3
@@ -0,0 +1,3 @@
|
||||
declare const router: import("express-serve-static-core").Router;
|
||||
export default router;
|
||||
//# sourceMappingURL=categories.d.ts.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"categories.d.ts","sourceRoot":"","sources":["../../src/routes/categories.ts"],"names":[],"mappings":"AASA,QAAA,MAAM,MAAM,4CAAW,CAAC;AAqFxB,eAAe,MAAM,CAAC"}
|
||||
Vendored
+78
@@ -0,0 +1,78 @@
|
||||
"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 userCategories = await db_1.db
|
||||
.select()
|
||||
.from(schema_1.categories)
|
||||
.where((0, drizzle_orm_1.eq)(schema_1.categories.userId, req.user.userId))
|
||||
.orderBy((0, drizzle_orm_1.asc)(schema_1.categories.order), (0, drizzle_orm_1.asc)(schema_1.categories.createdAt));
|
||||
res.json({ categories: userCategories });
|
||||
}));
|
||||
router.post('/', (0, asyncHandler_1.asyncHandler)(async (req, res) => {
|
||||
const data = validation_1.categoryCreateSchema.parse(req.body);
|
||||
const userId = req.user.userId;
|
||||
const now = Date.now();
|
||||
const maxOrder = await db_1.db
|
||||
.select({ order: schema_1.categories.order })
|
||||
.from(schema_1.categories)
|
||||
.where((0, drizzle_orm_1.eq)(schema_1.categories.userId, userId))
|
||||
.orderBy((0, drizzle_orm_1.desc)(schema_1.categories.order))
|
||||
.limit(1);
|
||||
const newCategory = {
|
||||
id: `cat_${Date.now().toString(36)}${Math.random().toString(36).slice(2, 6)}`,
|
||||
userId,
|
||||
name: data.name,
|
||||
color: data.color,
|
||||
order: data.order ?? (maxOrder[0]?.order ?? -1) + 1,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
};
|
||||
await db_1.db.insert(schema_1.categories).values(newCategory);
|
||||
res.status(201).json(newCategory);
|
||||
}));
|
||||
router.patch('/:id', (0, asyncHandler_1.asyncHandler)(async (req, res) => {
|
||||
const data = validation_1.categoryUpdateSchema.parse(req.body);
|
||||
const userId = req.user.userId;
|
||||
const existing = await db_1.db
|
||||
.select()
|
||||
.from(schema_1.categories)
|
||||
.where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(schema_1.categories.id, req.params.id), (0, drizzle_orm_1.eq)(schema_1.categories.userId, userId)))
|
||||
.limit(1);
|
||||
if (existing.length === 0) {
|
||||
throw new errorHandler_1.AppError('NOT_FOUND', 'Category not found', 404);
|
||||
}
|
||||
const now = Date.now();
|
||||
const updated = await db_1.db
|
||||
.update(schema_1.categories)
|
||||
.set({ ...data, updatedAt: now })
|
||||
.where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(schema_1.categories.id, req.params.id), (0, drizzle_orm_1.eq)(schema_1.categories.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.categories)
|
||||
.where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(schema_1.categories.id, req.params.id), (0, drizzle_orm_1.eq)(schema_1.categories.userId, userId)))
|
||||
.limit(1);
|
||||
if (existing.length === 0) {
|
||||
throw new errorHandler_1.AppError('NOT_FOUND', 'Category not found', 404);
|
||||
}
|
||||
await db_1.db
|
||||
.delete(schema_1.categories)
|
||||
.where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(schema_1.categories.id, req.params.id), (0, drizzle_orm_1.eq)(schema_1.categories.userId, userId)));
|
||||
res.status(204).send();
|
||||
}));
|
||||
exports.default = router;
|
||||
//# sourceMappingURL=categories.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"categories.js","sourceRoot":"","sources":["../../src/routes/categories.ts"],"names":[],"mappings":"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"}
|
||||
Vendored
+3
@@ -0,0 +1,3 @@
|
||||
declare const router: import("express-serve-static-core").Router;
|
||||
export default router;
|
||||
//# sourceMappingURL=friends.d.ts.map
|
||||
Vendored
+1
@@ -0,0 +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"}
|
||||
Vendored
+130
@@ -0,0 +1,130 @@
|
||||
"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_2.authMiddleware);
|
||||
async function findUsernames(ids) {
|
||||
if (ids.length === 0)
|
||||
return new Map();
|
||||
const rows = await db_1.db.select().from(schema_1.users).where((0, drizzle_orm_1.or)(...ids.map((id) => (0, drizzle_orm_1.eq)(schema_1.users.id, id))));
|
||||
return new Map(rows.map((u) => [u.id, u.username]));
|
||||
}
|
||||
router.get('/', (0, asyncHandler_1.asyncHandler)(async (req, res) => {
|
||||
const userId = req.user.userId;
|
||||
const outgoingRows = await db_1.db
|
||||
.select()
|
||||
.from(schema_1.friendships)
|
||||
.where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(schema_1.friendships.userId, userId), (0, drizzle_orm_1.eq)(schema_1.friendships.status, 'pending')));
|
||||
const incomingRows = await db_1.db
|
||||
.select()
|
||||
.from(schema_1.friendships)
|
||||
.where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(schema_1.friendships.friendId, userId), (0, drizzle_orm_1.eq)(schema_1.friendships.status, 'pending')));
|
||||
const acceptedRows = 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.eq)(schema_1.friendships.status, 'accepted')));
|
||||
const friendIds = acceptedRows.map((r) => (r.userId === userId ? r.friendId : r.userId));
|
||||
const outgoingIds = outgoingRows.map((r) => r.friendId);
|
||||
const incomingIds = incomingRows.map((r) => r.userId);
|
||||
const usernames = await findUsernames([...friendIds, ...outgoingIds, ...incomingIds]);
|
||||
const friends = acceptedRows.map((r) => {
|
||||
const friendId = r.userId === userId ? r.friendId : r.userId;
|
||||
return { id: friendId, username: usernames.get(friendId) ?? '' };
|
||||
});
|
||||
const outgoing = outgoingRows.map((r) => ({
|
||||
id: r.id,
|
||||
username: usernames.get(r.friendId) ?? '',
|
||||
requestId: r.id,
|
||||
status: 'pending',
|
||||
}));
|
||||
const incoming = incomingRows.map((r) => ({
|
||||
id: r.userId,
|
||||
username: usernames.get(r.userId) ?? '',
|
||||
requestId: r.id,
|
||||
status: 'pending',
|
||||
}));
|
||||
res.json({ friends, incoming, outgoing });
|
||||
}));
|
||||
router.post('/requests', (0, asyncHandler_1.asyncHandler)(async (req, res) => {
|
||||
const { username } = validation_1.friendRequestSchema.parse(req.body);
|
||||
const userId = req.user.userId;
|
||||
if (username.toLowerCase() === (await meUsername(userId))) {
|
||||
throw new errorHandler_1.AppError('SELF_REQUEST', 'You cannot add yourself', 400);
|
||||
}
|
||||
const target = await db_1.db.select().from(schema_1.users).where((0, drizzle_orm_1.eq)(schema_1.users.username, username)).limit(1);
|
||||
if (target.length === 0) {
|
||||
throw new errorHandler_1.AppError('USER_NOT_FOUND', 'No user with that username found', 404);
|
||||
}
|
||||
const existing = await db_1.db
|
||||
.select()
|
||||
.from(schema_1.friendships)
|
||||
.where((0, drizzle_orm_1.or)((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(schema_1.friendships.userId, userId), (0, drizzle_orm_1.eq)(schema_1.friendships.friendId, target[0].id)), (0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(schema_1.friendships.userId, target[0].id), (0, drizzle_orm_1.eq)(schema_1.friendships.friendId, userId))))
|
||||
.limit(1);
|
||||
if (existing.length > 0) {
|
||||
throw new errorHandler_1.AppError('ALREADY_FRIENDS', existing[0].status === 'accepted' ? 'You are already friends' : 'Friend request already pending', 409);
|
||||
}
|
||||
const now = Date.now();
|
||||
await db_1.db.insert(schema_1.friendships).values({
|
||||
id: (0, auth_1.generateId)('friend'),
|
||||
userId,
|
||||
friendId: target[0].id,
|
||||
status: 'pending',
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
});
|
||||
res.status(201).json({ success: true });
|
||||
}));
|
||||
router.post('/requests/:id/accept', (0, asyncHandler_1.asyncHandler)(async (req, res) => {
|
||||
const userId = req.user.userId;
|
||||
const requestId = req.params.id;
|
||||
const row = await db_1.db.select().from(schema_1.friendships).where((0, drizzle_orm_1.eq)(schema_1.friendships.id, requestId)).limit(1);
|
||||
if (row.length === 0) {
|
||||
throw new errorHandler_1.AppError('NOT_FOUND', 'Request not found', 404);
|
||||
}
|
||||
if (row[0].friendId !== userId) {
|
||||
throw new errorHandler_1.AppError('FORBIDDEN', 'This request was not sent to you', 403);
|
||||
}
|
||||
if (row[0].status !== 'pending') {
|
||||
throw new errorHandler_1.AppError('INVALID_STATE', 'Request is no longer pending', 409);
|
||||
}
|
||||
await db_1.db
|
||||
.update(schema_1.friendships)
|
||||
.set({ status: 'accepted', updatedAt: Date.now() })
|
||||
.where((0, drizzle_orm_1.eq)(schema_1.friendships.id, requestId));
|
||||
res.json({ success: true });
|
||||
}));
|
||||
router.delete('/requests/:id', (0, asyncHandler_1.asyncHandler)(async (req, res) => {
|
||||
const userId = req.user.userId;
|
||||
const requestId = req.params.id;
|
||||
const row = await db_1.db.select().from(schema_1.friendships).where((0, drizzle_orm_1.eq)(schema_1.friendships.id, requestId)).limit(1);
|
||||
if (row.length === 0) {
|
||||
throw new errorHandler_1.AppError('NOT_FOUND', 'Request not found', 404);
|
||||
}
|
||||
if (row[0].userId !== userId && row[0].friendId !== userId) {
|
||||
throw new errorHandler_1.AppError('FORBIDDEN', 'Not allowed', 403);
|
||||
}
|
||||
await db_1.db.delete(schema_1.friendships).where((0, drizzle_orm_1.eq)(schema_1.friendships.id, requestId));
|
||||
res.json({ success: true });
|
||||
}));
|
||||
router.delete('/:friendId', (0, asyncHandler_1.asyncHandler)(async (req, res) => {
|
||||
const userId = req.user.userId;
|
||||
const friendId = req.params.friendId;
|
||||
await db_1.db
|
||||
.delete(schema_1.friendships)
|
||||
.where((0, drizzle_orm_1.or)((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(schema_1.friendships.userId, userId), (0, drizzle_orm_1.eq)(schema_1.friendships.friendId, friendId)), (0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(schema_1.friendships.userId, friendId), (0, drizzle_orm_1.eq)(schema_1.friendships.friendId, userId))));
|
||||
res.json({ success: true });
|
||||
}));
|
||||
async function meUsername(userId) {
|
||||
const me = await db_1.db.select().from(schema_1.users).where((0, drizzle_orm_1.eq)(schema_1.users.id, userId)).limit(1);
|
||||
return me.length > 0 ? me[0].username.toLowerCase() : '';
|
||||
}
|
||||
exports.default = router;
|
||||
//# sourceMappingURL=friends.js.map
|
||||
Vendored
+1
File diff suppressed because one or more lines are too long
Vendored
+3
@@ -0,0 +1,3 @@
|
||||
declare const router: import("express-serve-static-core").Router;
|
||||
export default router;
|
||||
//# sourceMappingURL=subtasks.d.ts.map
|
||||
+1
@@ -0,0 +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"}
|
||||
Vendored
+114
@@ -0,0 +1,114 @@
|
||||
"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('/task/:taskId', (0, asyncHandler_1.asyncHandler)(async (req, res) => {
|
||||
const userId = req.user.userId;
|
||||
// Verify task exists and belongs to user
|
||||
const task = await db_1.db
|
||||
.select()
|
||||
.from(schema_1.tasks)
|
||||
.where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(schema_1.tasks.id, req.params.taskId), (0, drizzle_orm_1.eq)(schema_1.tasks.userId, userId)))
|
||||
.limit(1);
|
||||
if (task.length === 0) {
|
||||
throw new errorHandler_1.AppError('NOT_FOUND', 'Task not found', 404);
|
||||
}
|
||||
const taskSubtasks = await db_1.db
|
||||
.select()
|
||||
.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 });
|
||||
}));
|
||||
router.post('/task/:taskId', (0, asyncHandler_1.asyncHandler)(async (req, res) => {
|
||||
const data = validation_1.subtaskCreateSchema.parse(req.body);
|
||||
const userId = req.user.userId;
|
||||
const task = await db_1.db
|
||||
.select()
|
||||
.from(schema_1.tasks)
|
||||
.where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(schema_1.tasks.id, req.params.taskId), (0, drizzle_orm_1.eq)(schema_1.tasks.userId, userId)))
|
||||
.limit(1);
|
||||
if (task.length === 0) {
|
||||
throw new errorHandler_1.AppError('NOT_FOUND', 'Task not found', 404);
|
||||
}
|
||||
const now = Date.now();
|
||||
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)))
|
||||
.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)}`;
|
||||
const newSubtask = {
|
||||
id: subtaskId,
|
||||
userId,
|
||||
taskId: req.params.taskId,
|
||||
title: data.title,
|
||||
completed: false,
|
||||
order: data.order ?? (maxOrder[0]?.order ?? -1) + 1,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
};
|
||||
await db_1.db.insert(schema_1.subtasks).values(newSubtask);
|
||||
// Update task updatedAt
|
||||
await db_1.db
|
||||
.update(schema_1.tasks)
|
||||
.set({ updatedAt: now })
|
||||
.where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(schema_1.tasks.id, req.params.taskId), (0, drizzle_orm_1.eq)(schema_1.tasks.userId, userId)));
|
||||
res.status(201).json(newSubtask);
|
||||
}));
|
||||
router.patch('/:id', (0, asyncHandler_1.asyncHandler)(async (req, res) => {
|
||||
const data = validation_1.subtaskUpdateSchema.parse(req.body);
|
||||
const userId = req.user.userId;
|
||||
const existing = await db_1.db
|
||||
.select()
|
||||
.from(schema_1.subtasks)
|
||||
.where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(schema_1.subtasks.id, req.params.id), (0, drizzle_orm_1.eq)(schema_1.subtasks.userId, userId)))
|
||||
.limit(1);
|
||||
if (existing.length === 0) {
|
||||
throw new errorHandler_1.AppError('NOT_FOUND', 'Subtask not found', 404);
|
||||
}
|
||||
const now = Date.now();
|
||||
const updated = await db_1.db
|
||||
.update(schema_1.subtasks)
|
||||
.set({ ...data, updatedAt: now })
|
||||
.where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(schema_1.subtasks.id, req.params.id), (0, drizzle_orm_1.eq)(schema_1.subtasks.userId, userId)))
|
||||
.returning();
|
||||
// Update task updatedAt
|
||||
await db_1.db
|
||||
.update(schema_1.tasks)
|
||||
.set({ updatedAt: now })
|
||||
.where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(schema_1.tasks.id, existing[0].taskId), (0, drizzle_orm_1.eq)(schema_1.tasks.userId, userId)));
|
||||
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.subtasks)
|
||||
.where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(schema_1.subtasks.id, req.params.id), (0, drizzle_orm_1.eq)(schema_1.subtasks.userId, userId)))
|
||||
.limit(1);
|
||||
if (existing.length === 0) {
|
||||
throw new errorHandler_1.AppError('NOT_FOUND', 'Subtask not found', 404);
|
||||
}
|
||||
const taskId = existing[0].taskId;
|
||||
await db_1.db
|
||||
.delete(schema_1.subtasks)
|
||||
.where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(schema_1.subtasks.id, req.params.id), (0, drizzle_orm_1.eq)(schema_1.subtasks.userId, userId)));
|
||||
// Update task updatedAt
|
||||
await db_1.db
|
||||
.update(schema_1.tasks)
|
||||
.set({ updatedAt: Date.now() })
|
||||
.where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(schema_1.tasks.id, taskId), (0, drizzle_orm_1.eq)(schema_1.tasks.userId, userId)));
|
||||
res.status(204).send();
|
||||
}));
|
||||
exports.default = router;
|
||||
//# sourceMappingURL=subtasks.js.map
|
||||
Vendored
+1
File diff suppressed because one or more lines are too long
Vendored
+3
@@ -0,0 +1,3 @@
|
||||
declare const router: import("express-serve-static-core").Router;
|
||||
export default router;
|
||||
//# sourceMappingURL=sync.d.ts.map
|
||||
Vendored
+1
@@ -0,0 +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"}
|
||||
Vendored
+289
@@ -0,0 +1,289 @@
|
||||
"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
|
||||
Vendored
+1
File diff suppressed because one or more lines are too long
Vendored
+3
@@ -0,0 +1,3 @@
|
||||
declare const router: import("express-serve-static-core").Router;
|
||||
export default router;
|
||||
//# sourceMappingURL=tasks.d.ts.map
|
||||
Vendored
+1
@@ -0,0 +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"}
|
||||
Vendored
+254
@@ -0,0 +1,254 @@
|
||||
"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 zod_1 = require("zod");
|
||||
const router = (0, express_1.Router)();
|
||||
router.use(auth_1.authMiddleware);
|
||||
function applyTaskFilters(query, userId, filters) {
|
||||
const conditions = [(0, drizzle_orm_1.eq)(schema_1.tasks.userId, userId)];
|
||||
if (filters.categoryId) {
|
||||
conditions.push((0, drizzle_orm_1.eq)(schema_1.tasks.categoryId, filters.categoryId));
|
||||
}
|
||||
if (filters.completed !== undefined) {
|
||||
conditions.push((0, drizzle_orm_1.eq)(schema_1.tasks.completed, filters.completed));
|
||||
}
|
||||
if (filters.dueBefore) {
|
||||
conditions.push((0, drizzle_orm_1.lte)(schema_1.tasks.dueDate, filters.dueBefore));
|
||||
}
|
||||
if (filters.dueAfter) {
|
||||
conditions.push((0, drizzle_orm_1.gte)(schema_1.tasks.dueDate, filters.dueAfter));
|
||||
}
|
||||
if (filters.priority) {
|
||||
conditions.push((0, drizzle_orm_1.eq)(schema_1.tasks.priority, filters.priority));
|
||||
}
|
||||
return query.where((0, drizzle_orm_1.and)(...conditions));
|
||||
}
|
||||
function applyTaskSorting(query, sortBy = 'dueDate', sortOrder = 'asc') {
|
||||
const orderFn = sortOrder === 'desc' ? drizzle_orm_1.desc : drizzle_orm_1.asc;
|
||||
const columnMap = {
|
||||
dueDate: schema_1.tasks.dueDate,
|
||||
priority: schema_1.tasks.priority,
|
||||
createdAt: schema_1.tasks.createdAt,
|
||||
title: schema_1.tasks.title,
|
||||
};
|
||||
return query.orderBy(orderFn(columnMap[sortBy] || schema_1.tasks.dueDate));
|
||||
}
|
||||
router.get('/', (0, asyncHandler_1.asyncHandler)(async (req, res) => {
|
||||
const filters = validation_1.taskQuerySchema.parse(req.query);
|
||||
const userId = req.user.userId;
|
||||
let query = db_1.db.select().from(schema_1.tasks);
|
||||
query = applyTaskFilters(query, userId, filters);
|
||||
query = applyTaskSorting(query, filters.sortBy, filters.sortOrder);
|
||||
const limit = filters.limit ?? 50;
|
||||
const offset = filters.offset ?? 0;
|
||||
query = query.limit(limit).offset(offset);
|
||||
const results = await query;
|
||||
// Fetch subtasks for each task
|
||||
const taskIds = results.map((t) => t.id);
|
||||
let taskSubtasks = [];
|
||||
if (taskIds.length > 0) {
|
||||
taskSubtasks = 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.inArray)(schema_1.subtasks.taskId, taskIds)))
|
||||
.orderBy((0, drizzle_orm_1.asc)(schema_1.subtasks.order));
|
||||
}
|
||||
const subtasksByTask = taskSubtasks.reduce((acc, st) => {
|
||||
if (!acc[st.taskId])
|
||||
acc[st.taskId] = [];
|
||||
acc[st.taskId].push(st);
|
||||
return acc;
|
||||
}, {});
|
||||
const tasksWithSubtasks = results.map((task) => ({
|
||||
...task,
|
||||
subtasks: subtasksByTask[task.id] || [],
|
||||
}));
|
||||
// Get total count
|
||||
const countQuery = db_1.db.select({ count: (0, drizzle_orm_1.sql) `count(*)` }).from(schema_1.tasks);
|
||||
const countResult = await applyTaskFilters(countQuery, userId, filters);
|
||||
const total = Number(countResult[0]?.count ?? 0);
|
||||
res.json({ tasks: tasksWithSubtasks, total, limit, offset });
|
||||
}));
|
||||
router.get('/:id', (0, asyncHandler_1.asyncHandler)(async (req, res) => {
|
||||
const userId = req.user.userId;
|
||||
const task = await db_1.db
|
||||
.select()
|
||||
.from(schema_1.tasks)
|
||||
.where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(schema_1.tasks.id, req.params.id), (0, drizzle_orm_1.eq)(schema_1.tasks.userId, userId)))
|
||||
.limit(1);
|
||||
if (task.length === 0) {
|
||||
throw new errorHandler_1.AppError('NOT_FOUND', 'Task not found', 404);
|
||||
}
|
||||
const taskSubtasks = await db_1.db
|
||||
.select()
|
||||
.from(schema_1.subtasks)
|
||||
.where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(schema_1.subtasks.taskId, req.params.id), (0, drizzle_orm_1.eq)(schema_1.subtasks.userId, userId)))
|
||||
.orderBy((0, drizzle_orm_1.asc)(schema_1.subtasks.order));
|
||||
res.json({ ...task[0], subtasks: taskSubtasks });
|
||||
}));
|
||||
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);
|
||||
}
|
||||
const taskId = `task_${Date.now().toString(36)}${Math.random().toString(36).slice(2, 6)}`;
|
||||
const newTask = {
|
||||
id: taskId,
|
||||
userId,
|
||||
categoryId: data.categoryId,
|
||||
title: data.title,
|
||||
description: data.description ?? '',
|
||||
priority: data.priority ?? 'none',
|
||||
completed: false,
|
||||
dueDate: data.dueDate ?? 0,
|
||||
dueTime: data.dueTime ?? '',
|
||||
endTime: data.endTime ?? '',
|
||||
assigneeId: data.assigneeId ?? null,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
};
|
||||
await db_1.db.insert(schema_1.tasks).values(newTask);
|
||||
// Create subtasks if provided
|
||||
if (data.subtasks && data.subtasks.length > 0) {
|
||||
const subtaskValues = data.subtasks.map((st, index) => ({
|
||||
id: `sub_${Date.now().toString(36)}${Math.random().toString(36).slice(2, 6)}${index}`,
|
||||
userId,
|
||||
taskId,
|
||||
title: st.title,
|
||||
completed: false,
|
||||
order: index,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
}));
|
||||
await db_1.db.insert(schema_1.subtasks).values(subtaskValues);
|
||||
}
|
||||
const createdSubtasks = await db_1.db
|
||||
.select()
|
||||
.from(schema_1.subtasks)
|
||||
.where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(schema_1.subtasks.taskId, taskId), (0, drizzle_orm_1.eq)(schema_1.subtasks.userId, userId)))
|
||||
.orderBy((0, drizzle_orm_1.asc)(schema_1.subtasks.order));
|
||||
res.status(201).json({ ...newTask, subtasks: createdSubtasks });
|
||||
}));
|
||||
router.patch('/:id', (0, asyncHandler_1.asyncHandler)(async (req, res) => {
|
||||
const data = validation_1.taskUpdateSchema.parse(req.body);
|
||||
const userId = req.user.userId;
|
||||
const existing = await db_1.db
|
||||
.select()
|
||||
.from(schema_1.tasks)
|
||||
.where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(schema_1.tasks.id, req.params.id), (0, drizzle_orm_1.eq)(schema_1.tasks.userId, userId)))
|
||||
.limit(1);
|
||||
if (existing.length === 0) {
|
||||
throw new errorHandler_1.AppError('NOT_FOUND', 'Task not found', 404);
|
||||
}
|
||||
// Verify category if provided
|
||||
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 now = Date.now();
|
||||
const updated = await db_1.db
|
||||
.update(schema_1.tasks)
|
||||
.set({ ...data, updatedAt: now })
|
||||
.where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(schema_1.tasks.id, req.params.id), (0, drizzle_orm_1.eq)(schema_1.tasks.userId, userId)))
|
||||
.returning();
|
||||
const taskSubtasks = await db_1.db
|
||||
.select()
|
||||
.from(schema_1.subtasks)
|
||||
.where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(schema_1.subtasks.taskId, req.params.id), (0, drizzle_orm_1.eq)(schema_1.subtasks.userId, userId)))
|
||||
.orderBy((0, drizzle_orm_1.asc)(schema_1.subtasks.order));
|
||||
res.json({ ...updated[0], subtasks: taskSubtasks });
|
||||
}));
|
||||
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.tasks)
|
||||
.where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(schema_1.tasks.id, req.params.id), (0, drizzle_orm_1.eq)(schema_1.tasks.userId, userId)))
|
||||
.limit(1);
|
||||
if (existing.length === 0) {
|
||||
throw new errorHandler_1.AppError('NOT_FOUND', 'Task not found', 404);
|
||||
}
|
||||
await db_1.db
|
||||
.delete(schema_1.tasks)
|
||||
.where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(schema_1.tasks.id, req.params.id), (0, drizzle_orm_1.eq)(schema_1.tasks.userId, userId)));
|
||||
res.status(204).send();
|
||||
}));
|
||||
router.post('/batch', (0, asyncHandler_1.asyncHandler)(async (req, res) => {
|
||||
const { operations } = zod_1.z.object({
|
||||
operations: zod_1.z.array(zod_1.z.union([
|
||||
zod_1.z.object({ type: zod_1.z.literal('create'), data: validation_1.taskCreateSchema }),
|
||||
zod_1.z.object({ type: zod_1.z.literal('update'), id: zod_1.z.string(), data: validation_1.taskUpdateSchema }),
|
||||
zod_1.z.object({ type: zod_1.z.literal('delete'), id: zod_1.z.string() }),
|
||||
])),
|
||||
}).parse(req.body);
|
||||
const userId = req.user.userId;
|
||||
const results = [];
|
||||
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);
|
||||
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({
|
||||
id: taskId,
|
||||
userId,
|
||||
...op.data,
|
||||
completed: false,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
});
|
||||
results.push({ id: taskId, success: true });
|
||||
}
|
||||
else if (op.type === 'update') {
|
||||
await db_1.db
|
||||
.update(schema_1.tasks)
|
||||
.set({ ...op.data, updatedAt: Date.now() })
|
||||
.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)));
|
||||
results.push({ id: op.id, success: true });
|
||||
}
|
||||
else if (op.type === 'delete') {
|
||||
await db_1.db
|
||||
.delete(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)));
|
||||
results.push({ id: op.id, success: true });
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
results.push({
|
||||
id: 'id' in op ? op.id : 'unknown',
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Unknown error'
|
||||
});
|
||||
}
|
||||
}
|
||||
res.json({ results });
|
||||
}));
|
||||
exports.default = router;
|
||||
//# sourceMappingURL=tasks.js.map
|
||||
Vendored
+1
File diff suppressed because one or more lines are too long
Vendored
+3
@@ -0,0 +1,3 @@
|
||||
declare const router: import("express-serve-static-core").Router;
|
||||
export default router;
|
||||
//# sourceMappingURL=users.d.ts.map
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"users.d.ts","sourceRoot":"","sources":["../../src/routes/users.ts"],"names":[],"mappings":"AASA,QAAA,MAAM,MAAM,4CAAW,CAAC;AAqGxB,eAAe,MAAM,CAAC"}
|
||||
Vendored
+98
@@ -0,0 +1,98 @@
|
||||
"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('/me', (0, asyncHandler_1.asyncHandler)(async (req, res) => {
|
||||
const userId = req.user.userId;
|
||||
const user = await db_1.db.select().from(schema_1.users).where((0, drizzle_orm_1.eq)(schema_1.users.id, userId)).limit(1);
|
||||
if (user.length === 0) {
|
||||
throw new errorHandler_1.AppError('NOT_FOUND', 'User not found', 404);
|
||||
}
|
||||
const settings = await db_1.db
|
||||
.select()
|
||||
.from(schema_1.userSettings)
|
||||
.where((0, drizzle_orm_1.eq)(schema_1.userSettings.userId, userId))
|
||||
.limit(1);
|
||||
let defaultCategory = settings[0]?.defaultCategory;
|
||||
if (defaultCategory) {
|
||||
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, defaultCategory), (0, drizzle_orm_1.eq)(schema_1.categories.userId, userId)))
|
||||
.limit(1);
|
||||
if (cat.length === 0) {
|
||||
defaultCategory = null;
|
||||
}
|
||||
}
|
||||
res.json({
|
||||
id: user[0].id,
|
||||
username: user[0].username,
|
||||
settings: {
|
||||
darkMode: settings[0]?.darkMode ?? false,
|
||||
notifications: settings[0]?.notifications ?? true,
|
||||
reminderTime: settings[0]?.reminderTime ?? '09:00',
|
||||
defaultCategory,
|
||||
sortBy: settings[0]?.sortBy ?? 'dueDate',
|
||||
sortOrder: settings[0]?.sortOrder ?? 'asc',
|
||||
},
|
||||
createdAt: user[0].createdAt,
|
||||
});
|
||||
}));
|
||||
router.patch('/me/settings', (0, asyncHandler_1.asyncHandler)(async (req, res) => {
|
||||
const data = validation_1.userSettingsSchema.parse(req.body);
|
||||
const userId = req.user.userId;
|
||||
// Validate defaultCategory if provided
|
||||
if (data.defaultCategory) {
|
||||
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.defaultCategory), (0, drizzle_orm_1.eq)(schema_1.categories.userId, userId)))
|
||||
.limit(1);
|
||||
if (cat.length === 0) {
|
||||
throw new errorHandler_1.AppError('NOT_FOUND', 'Default category not found', 404);
|
||||
}
|
||||
}
|
||||
const now = Date.now();
|
||||
const existing = await db_1.db
|
||||
.select()
|
||||
.from(schema_1.userSettings)
|
||||
.where((0, drizzle_orm_1.eq)(schema_1.userSettings.userId, userId))
|
||||
.limit(1);
|
||||
if (existing.length === 0) {
|
||||
await db_1.db.insert(schema_1.userSettings).values({
|
||||
userId,
|
||||
...data,
|
||||
updatedAt: now,
|
||||
});
|
||||
}
|
||||
else {
|
||||
await db_1.db
|
||||
.update(schema_1.userSettings)
|
||||
.set({ ...data, updatedAt: now })
|
||||
.where((0, drizzle_orm_1.eq)(schema_1.userSettings.userId, userId));
|
||||
}
|
||||
res.status(200).json({ success: true });
|
||||
}));
|
||||
router.get('/search', (0, asyncHandler_1.asyncHandler)(async (req, res) => {
|
||||
const { q } = req.query;
|
||||
const userId = req.user.userId;
|
||||
if (!q || typeof q !== 'string' || q.length < 2) {
|
||||
return res.json([]);
|
||||
}
|
||||
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.ilike)(schema_1.users.username, `%${q}%`), (0, drizzle_orm_1.eq)(schema_1.users.id, userId)))
|
||||
.limit(10);
|
||||
res.json(results);
|
||||
}));
|
||||
exports.default = router;
|
||||
//# sourceMappingURL=users.js.map
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"users.js","sourceRoot":"","sources":["../../src/routes/users.ts"],"names":[],"mappings":"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"}
|
||||
Reference in New Issue
Block a user