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:
101
apps/api/src/index.ts
Normal file
101
apps/api/src/index.ts
Normal 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;
|
||||
Reference in New Issue
Block a user