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
This commit is contained in:
2026-01-28 14:02:15 +00:00
commit 98ea0427bb
58 changed files with 6605 additions and 0 deletions

101
apps/api/src/index.ts Normal file
View File

@@ -0,0 +1,101 @@
import { Elysia } from 'elysia';
import { cors } from '@elysiajs/cors';
import { auth } from './lib/auth';
import { authRoutes } from './routes/auth';
import { adminRoutes } from './routes/admin';
import { projectRoutes } from './routes/projects';
import { taskRoutes } from './routes/tasks';
import { labelRoutes } from './routes/labels';
import { commentRoutes } from './routes/comments';
import { hammerRoutes } from './routes/hammer';
import type { User } from './lib/auth';
const app = new Elysia()
// CORS
.use(cors({
origin: process.env.ALLOWED_ORIGINS?.split(',') || [
'http://localhost:5173',
'https://todo.donovankelly.xyz',
],
credentials: true,
exposeHeaders: ['set-auth-token'],
}))
// Health check
.get('/health', () => ({
status: 'ok',
timestamp: new Date().toISOString(),
version: '0.1.0',
}))
// BetterAuth routes (login, register, session, etc.)
.all('/api/auth/*', async ({ request }) => {
return auth.handler(request);
})
// Public auth routes (invite acceptance)
.use(authRoutes)
// Hammer API (uses separate API key auth)
.group('/api', app => app.use(hammerRoutes))
// Protected routes - require auth
.derive(async ({ request, set }): Promise<{ user: User }> => {
const session = await auth.api.getSession({
headers: request.headers,
});
if (!session?.user) {
set.status = 401;
throw new Error('Unauthorized');
}
return { user: session.user as User };
})
// Authenticated API routes
.group('/api', app => app
.use(adminRoutes)
.use(projectRoutes)
.use(taskRoutes)
.use(labelRoutes)
.use(commentRoutes)
)
// Error handler
.onError(({ code, error, set, path }) => {
console.error(`[${new Date().toISOString()}] ERROR on ${path}:`, {
code,
message: error.message,
stack: process.env.NODE_ENV !== 'production' ? error.stack : undefined,
});
if (code === 'VALIDATION') {
set.status = 400;
return { error: 'Validation error', details: error.message };
}
if (error.message === 'Unauthorized') {
set.status = 401;
return { error: 'Unauthorized' };
}
if (error.message === 'Admin access required') {
set.status = 403;
return { error: 'Forbidden: Admin access required' };
}
if (error.message.includes('not found')) {
set.status = 404;
return { error: error.message };
}
set.status = 500;
return { error: 'Internal server error' };
})
.listen(process.env.PORT || 3001);
console.log(`🚀 Todo API running at ${app.server?.hostname}:${app.server?.port}`);
export type App = typeof app;