Files
todo-app/apps/api/src/routes/comments.ts
Hammer 98ea0427bb Initial todo app setup
- Backend: Bun + Elysia + Drizzle ORM + PostgreSQL
- Frontend: React + Vite + TailwindCSS + Zustand
- Auth: better-auth with invite-only system
- Features: Tasks, Projects, Sections, Labels, Comments
- Hammer API: Dedicated endpoints for AI assistant integration
- Unit tests: 24 passing tests
- Docker: Compose file for deployment
2026-01-28 14:02:15 +00:00

147 lines
3.4 KiB
TypeScript

import { Elysia, t } from 'elysia';
import { db } from '../db';
import { comments, tasks } from '../db/schema';
import { eq, and, asc } from 'drizzle-orm';
import type { User } from '../lib/auth';
export const commentRoutes = new Elysia({ prefix: '/comments' })
// Get comments for a task
.get('/task/:taskId', async ({ params, user, set }) => {
// Verify task ownership
const task = await db.query.tasks.findFirst({
where: and(
eq(tasks.id, params.taskId),
eq(tasks.userId, (user as User).id)
),
});
if (!task) {
set.status = 404;
throw new Error('Task not found');
}
const taskComments = await db.query.comments.findMany({
where: eq(comments.taskId, params.taskId),
orderBy: [asc(comments.createdAt)],
with: {
user: {
columns: { id: true, name: true, image: true },
},
},
});
return taskComments;
}, {
params: t.Object({
taskId: t.String(),
}),
})
// Create comment
.post('/', async ({ body, user }) => {
const userId = (user as User).id;
// Verify task ownership
const task = await db.query.tasks.findFirst({
where: and(
eq(tasks.id, body.taskId),
eq(tasks.userId, userId)
),
});
if (!task) {
throw new Error('Task not found');
}
const [comment] = await db.insert(comments).values({
taskId: body.taskId,
userId,
content: body.content,
attachments: body.attachments || [],
}).returning();
// Return with user info
const fullComment = await db.query.comments.findFirst({
where: eq(comments.id, comment.id),
with: {
user: {
columns: { id: true, name: true, image: true },
},
},
});
return fullComment;
}, {
body: t.Object({
taskId: t.String(),
content: t.String({ minLength: 1 }),
attachments: t.Optional(t.Array(t.Object({
name: t.String(),
url: t.String(),
type: t.String(),
}))),
}),
})
// Update comment
.patch('/:id', async ({ params, body, user, set }) => {
const existing = await db.query.comments.findFirst({
where: and(
eq(comments.id, params.id),
eq(comments.userId, (user as User).id)
),
});
if (!existing) {
set.status = 404;
throw new Error('Comment not found');
}
const [updated] = await db
.update(comments)
.set({
content: body.content,
attachments: body.attachments,
updatedAt: new Date(),
})
.where(eq(comments.id, params.id))
.returning();
return updated;
}, {
params: t.Object({
id: t.String(),
}),
body: t.Object({
content: t.Optional(t.String({ minLength: 1 })),
attachments: t.Optional(t.Array(t.Object({
name: t.String(),
url: t.String(),
type: t.String(),
}))),
}),
})
// Delete comment
.delete('/:id', async ({ params, user, set }) => {
const existing = await db.query.comments.findFirst({
where: and(
eq(comments.id, params.id),
eq(comments.userId, (user as User).id)
),
});
if (!existing) {
set.status = 404;
throw new Error('Comment not found');
}
await db.delete(comments).where(eq(comments.id, params.id));
return { success: true };
}, {
params: t.Object({
id: t.String(),
}),
});