- Create shared authMiddleware plugin with scoped derive for proper type propagation - Each route file now uses authMiddleware instead of relying on parent derive - Fix error handler to use instanceof Error checks for message/stack access - Fix null vs undefined type mismatch in hammer route auth validation - Fix invite role type assertion for enum compatibility - Fix test type assertions to avoid impossible comparisons
89 lines
2.4 KiB
TypeScript
89 lines
2.4 KiB
TypeScript
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';
|
|
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))
|
|
|
|
// Authenticated API routes (auth middleware is in each route plugin)
|
|
.group('/api', app => app
|
|
.use(adminRoutes)
|
|
.use(projectRoutes)
|
|
.use(taskRoutes)
|
|
.use(labelRoutes)
|
|
.use(commentRoutes)
|
|
)
|
|
|
|
// Error handler
|
|
.onError(({ code, error, set, path }) => {
|
|
const message = error instanceof Error ? error.message : String(error);
|
|
const stack = error instanceof Error ? error.stack : undefined;
|
|
|
|
console.error(`[${new Date().toISOString()}] ERROR on ${path}:`, {
|
|
code,
|
|
message,
|
|
stack: process.env.NODE_ENV !== 'production' ? stack : undefined,
|
|
});
|
|
|
|
if (code === 'VALIDATION') {
|
|
set.status = 400;
|
|
return { error: 'Validation error', details: message };
|
|
}
|
|
|
|
if (message === 'Unauthorized') {
|
|
set.status = 401;
|
|
return { error: 'Unauthorized' };
|
|
}
|
|
|
|
if (message === 'Admin access required') {
|
|
set.status = 403;
|
|
return { error: 'Forbidden: Admin access required' };
|
|
}
|
|
|
|
if (message.includes('not found')) {
|
|
set.status = 404;
|
|
return { 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;
|