Repository navigation
Expand file tree
/
Copy pathindex.ts
More file actions
165 lines (146 loc) · 5.73 KB
/
Copy pathindex.ts
File metadata and controls
165 lines (146 loc) · 5.73 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
import './loadEnv.js'
import { Hono } from 'hono'
import { logger } from 'hono/logger'
import { cors } from 'hono/cors'
import projects from './routes/projects.js'
import columns from './routes/columns.js'
import cards from './routes/cards.js'
import sprints from './routes/sprints.js'
import comments from './routes/comments.js'
import labels from './routes/labels.js'
import presets from './routes/presets.js'
import lanes from './routes/lanes.js'
import activity from './routes/activity.js'
import dashboard from './routes/dashboard.js'
import testcases from './routes/testcases.js'
import epics from './routes/epics.js'
import features from './routes/features.js'
import authRoutes from './routes/auth.js'
import users from './routes/users.js'
import projectAccess from './routes/projectAccess.js'
import notifications from './routes/notifications.js'
import sse from './routes/sse.js'
import dependencies from './routes/dependencies.js'
import roadmap from './routes/roadmap.js'
import reports from './routes/reports.js'
import configRoute from './routes/config.js'
import adminSettings from './routes/adminSettings.js'
import aiRoutes from './routes/ai.js'
import retrospectives from './routes/retrospectives.js'
import calendar from './routes/calendar.js'
import webhooks from './routes/webhooks.js'
import cardLinks from './routes/cardLinks.js'
import attachmentsRoutes from './routes/attachments.js'
import mcpRoute from './routes/mcp.js'
import mcpTokensRoute from './routes/mcpTokens.js'
import { requireAuth } from './middleware/requireAuth.js'
import { openApiSpec } from './lib/openapi/index.js'
import { startDueDateJob } from './lib/dueDateJob.js'
import { swaggerUI } from '@hono/swagger-ui'
import { db } from './db/index.js'
// Ensure the DB is initialised (runs schema + seed on first boot)
import './db/index.js'
startDueDateJob()
const app = new Hono()
app.use('*', logger())
const allowedOrigin = process.env.ALLOWED_ORIGIN || 'http://localhost:5173'
app.use('/api/*', cors({ origin: allowedOrigin, credentials: true }))
// Global error handler for unhandled exceptions
app.onError((err, c) => {
console.error('Unhandled error:', err)
return c.json({ data: null, error: 'internal server error' }, 500)
})
// Health check at root (no /api prefix) with DB connectivity check
app.get('/health', async (c) => {
let dbStatus = 'ok'
try {
await db.get('SELECT 1')
} catch {
dbStatus = 'error'
}
const httpStatus = dbStatus === 'ok' ? 200 : 503
return c.json(
{ data: { status: dbStatus === 'ok' ? 'ok' : 'degraded', service: 'slateflow', db: dbStatus }, error: null },
httpStatus
)
})
// Backward compat: redirect /api/health to /health
app.get('/api/health', (c) => c.redirect('/health', 301))
// Public routes — registered BEFORE the requireAuth middleware
app.route('/api', authRoutes)
app.route('/api', configRoute)
app.route('/api', webhooks)
app.route('/mcp', mcpRoute)
// Public OpenAPI spec and Swagger UI
app.get('/api/openapi.json', (c) => c.json(openApiSpec))
app.get('/api/docs', swaggerUI({ url: '/api/openapi.json' }))
// All subsequent /api/* routes require authentication
app.use('/api/*', requireAuth)
app.route('/api', projects)
app.route('/api', columns)
app.route('/api', cards)
app.route('/api', sprints)
app.route('/api', comments)
app.route('/api', labels)
app.route('/api', presets)
app.route('/api', lanes)
app.route('/api', activity)
app.route('/api', dashboard)
app.route('/api', testcases)
app.route('/api', epics)
app.route('/api', features)
app.route('/api', users)
app.route('/api', projectAccess)
app.route('/api', notifications)
app.route('/api', sse)
app.route('/api', dependencies)
app.route('/api', roadmap)
app.route('/api', reports)
app.route('/api', adminSettings)
app.route('/api', aiRoutes)
app.route('/api', retrospectives)
app.route('/api', calendar)
app.route('/api', cardLinks)
app.route('/api', attachmentsRoutes)
app.route('/api', mcpTokensRoute)
// Static file serving for uploads (dev + production)
if (!process.versions.bun) {
const { serveStatic } = await import('@hono/node-server/serve-static')
app.use('/uploads/*', serveStatic({ root: '.' }))
}
// In production, serve the built React client and handle SPA routing
if (process.env.NODE_ENV === 'production' && !process.versions.bun) {
const { serveStatic } = await import('@hono/node-server/serve-static')
app.use('/*', serveStatic({ root: './client/dist' }))
// SPA fallback — return index.html for paths that aren't static assets
app.get('*', serveStatic({ path: 'index.html', root: './client/dist' }))
}
app.notFound((c) => c.json({ data: null, error: 'not found' }, 404))
const port = Number(process.env.PORT) || 3000
// Surface AI misconfiguration at boot instead of on the first AI request.
{
const { isEnabled } = await import('./lib/featureFlags.js')
if (await isEnabled('ai')) {
if (!process.env.AI_PROVIDER) {
console.warn('[ai] the "ai" feature is enabled but AI_PROVIDER is unset — AI endpoints will fail until configured')
} else if (process.env.AI_PROVIDER !== 'ollama' && !process.env.AI_API_KEY) {
console.warn(`[ai] AI_PROVIDER="${process.env.AI_PROVIDER}" is set but AI_API_KEY is missing — AI endpoints will fail until configured`)
}
}
}
// Bun: export default { port, fetch } is picked up automatically
// Node.js / tsx: use @hono/node-server
if (!process.versions.bun) {
const { serve } = await import('@hono/node-server')
const server = serve({ fetch: app.fetch, port }, () => {
console.log(`Server running on http://localhost:${port}`)
})
server.on('error', (err: NodeJS.ErrnoException) => {
if (err.code === 'EADDRINUSE') {
console.error(`Port ${port} is already in use. Kill the old process and retry.`)
process.exit(1)
}
throw err
})
}
export default { port, fetch: app.fetch }