2025-09-21 19:35:41 +01:00
|
|
|
import 'dotenv/config';
|
|
|
|
|
|
2026-05-19 09:59:56 +01:00
|
|
|
import { API_SERVER_URL } from '@api/constants';
|
2025-11-24 01:00:50 +01:00
|
|
|
|
2026-03-09 11:18:22 +01:00
|
|
|
import { Hono } from '@api/utils/hono';
|
2025-12-02 15:27:41 +01:00
|
|
|
import { accountRouter } from '@api/routes/account';
|
2026-03-09 11:18:22 +01:00
|
|
|
import { auth } from '@cio/db/auth';
|
|
|
|
|
import { communityRouter } from '@api/routes/community';
|
2026-05-19 11:23:07 +01:00
|
|
|
import { isPublicCorsPath, sessionCors } from '@api/middlewares/cors';
|
2026-03-09 11:18:22 +01:00
|
|
|
import { courseRouter } from '@api/routes/course';
|
2025-12-02 15:27:41 +01:00
|
|
|
import { dashAnalyticsRouter } from '@api/routes/dash';
|
2026-03-09 11:18:22 +01:00
|
|
|
import { domainRouter } from '@api/routes/domain/domain';
|
|
|
|
|
import { inviteRouter } from '@api/routes/invite';
|
|
|
|
|
import { logger } from 'hono/logger';
|
|
|
|
|
// ROUTES
|
|
|
|
|
import { mailRouter } from '@api/routes/mail';
|
2026-05-18 03:47:22 +01:00
|
|
|
import { jobsRouter } from '@api/routes/jobs';
|
2025-12-02 09:54:38 +01:00
|
|
|
import { mediaRouter } from '@api/routes/media';
|
2025-11-24 01:00:50 +01:00
|
|
|
import { onboardingRouter } from '@api/routes/onboarding';
|
|
|
|
|
import { organizationRouter } from '@api/routes/organization';
|
2026-05-09 06:05:03 +01:00
|
|
|
import { publicCourseRouter } from '@api/routes/org-site';
|
2026-03-09 11:18:22 +01:00
|
|
|
import { licenseRouter } from '@api/routes/license';
|
|
|
|
|
import { organizationSsoRouter } from '@api/routes/organization/sso';
|
|
|
|
|
import { organizationTokenAuthRouter } from '@api/routes/organization/token-auth';
|
2026-04-12 08:58:41 +01:00
|
|
|
import { v1Router } from '@api/routes/v1';
|
2026-03-09 11:18:22 +01:00
|
|
|
import { ssoDiscoveryRouter } from '@api/routes/sso/discovery';
|
2026-04-27 11:40:38 +01:00
|
|
|
import { publicWidgetsRouter } from '@api/routes/widgets';
|
2026-03-09 11:18:22 +01:00
|
|
|
import { prettyJSON } from 'hono/pretty-json';
|
|
|
|
|
import rateLimiter from '@api/middlewares/rate-limiter';
|
|
|
|
|
import { secureHeaders } from 'hono/secure-headers';
|
|
|
|
|
import { signupGuard } from '@api/middlewares/signup-guard';
|
2026-04-12 08:58:41 +01:00
|
|
|
import { programRouter } from '@api/routes/program';
|
2026-03-09 11:18:22 +01:00
|
|
|
import { unsplashRouter } from '@api/routes/unsplash/unsplash';
|
2026-05-09 06:05:03 +01:00
|
|
|
import { agentRouter } from '@api/routes/agent';
|
2026-05-18 03:47:22 +01:00
|
|
|
import { internalRouter } from '@api/routes/internal';
|
|
|
|
|
import { mountQueueDashboard } from '@api/routes/admin/queues';
|
2025-09-21 19:35:41 +01:00
|
|
|
|
|
|
|
|
// Create Hono app with chaining for RPC support
|
|
|
|
|
export const app = new Hono()
|
|
|
|
|
// Middleware
|
|
|
|
|
.use('*', logger())
|
|
|
|
|
.use('*', prettyJSON())
|
|
|
|
|
.use('*', secureHeaders())
|
2026-05-19 10:24:18 +01:00
|
|
|
.use('*', async (c, next) => {
|
2026-05-19 11:23:07 +01:00
|
|
|
if (isPublicCorsPath(c.req.path)) return next();
|
2026-05-19 10:24:18 +01:00
|
|
|
|
|
|
|
|
return sessionCors(c, next);
|
|
|
|
|
})
|
2025-11-24 01:00:50 +01:00
|
|
|
.use('*', rateLimiter)
|
|
|
|
|
.use('*', async (c, next) => {
|
2026-05-19 11:23:07 +01:00
|
|
|
if (isPublicCorsPath(c.req.path)) {
|
2026-05-19 10:24:18 +01:00
|
|
|
c.set('user', null);
|
|
|
|
|
c.set('session', null);
|
|
|
|
|
c.set('orgRoles', {});
|
|
|
|
|
|
|
|
|
|
return next();
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-19 08:15:35 +01:00
|
|
|
let session: Awaited<ReturnType<typeof auth.api.getSession>> | null = null;
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
session = await auth.api.getSession({ headers: c.req.raw.headers });
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('auth.api.getSession error:', error);
|
|
|
|
|
}
|
2025-11-24 01:00:50 +01:00
|
|
|
|
|
|
|
|
if (!session) {
|
|
|
|
|
c.set('user', null);
|
|
|
|
|
c.set('session', null);
|
2026-04-27 11:40:38 +01:00
|
|
|
c.set('orgRoles', {});
|
2025-11-24 01:00:50 +01:00
|
|
|
await next();
|
|
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
c.set('user', session.user);
|
|
|
|
|
c.set('session', session.session);
|
2026-04-27 11:40:38 +01:00
|
|
|
c.set('orgRoles', (session as { orgRoles?: Record<string, number> }).orgRoles ?? {});
|
2025-11-24 01:00:50 +01:00
|
|
|
await next();
|
|
|
|
|
})
|
2025-09-21 19:35:41 +01:00
|
|
|
|
|
|
|
|
// Routes
|
|
|
|
|
.get('/', (c) =>
|
|
|
|
|
c.json({
|
2025-11-24 01:00:50 +01:00
|
|
|
message: `"Welcome to Classroomio.com API - docs are at ${API_SERVER_URL}/docs"`
|
2025-09-21 19:35:41 +01:00
|
|
|
})
|
|
|
|
|
)
|
2026-03-09 11:18:22 +01:00
|
|
|
.use('/api/auth/sign-up/*', signupGuard)
|
2025-11-24 01:00:50 +01:00
|
|
|
.on(['POST', 'GET'], '/api/auth/*', (c) => auth.handler(c.req.raw))
|
|
|
|
|
.get('/session', async (c) => {
|
|
|
|
|
const session = c.get('session');
|
|
|
|
|
const user = c.get('user');
|
|
|
|
|
|
|
|
|
|
if (!user) return c.body(null, 401);
|
|
|
|
|
|
|
|
|
|
return c.json({
|
|
|
|
|
session,
|
|
|
|
|
user
|
|
|
|
|
});
|
|
|
|
|
})
|
|
|
|
|
.route('/onboarding', onboardingRouter)
|
|
|
|
|
.route('/account', accountRouter)
|
2025-09-21 19:35:41 +01:00
|
|
|
.route('/course', courseRouter)
|
2026-03-09 11:18:22 +01:00
|
|
|
.route('/domain', domainRouter)
|
2025-09-21 19:35:41 +01:00
|
|
|
.route('/mail', mailRouter)
|
2025-12-02 09:54:38 +01:00
|
|
|
.route('/media', mediaRouter)
|
2026-05-18 03:47:22 +01:00
|
|
|
.route('/jobs', jobsRouter)
|
2026-03-09 11:18:22 +01:00
|
|
|
.route('/license', licenseRouter)
|
2025-11-24 01:00:50 +01:00
|
|
|
.route('/organization', organizationRouter)
|
2026-03-09 11:18:22 +01:00
|
|
|
.route('/organization/sso', organizationSsoRouter)
|
|
|
|
|
.route('/organization/token-auth', organizationTokenAuthRouter)
|
|
|
|
|
.route('/sso', ssoDiscoveryRouter)
|
2025-12-02 15:27:41 +01:00
|
|
|
.route('/dash', dashAnalyticsRouter)
|
2025-12-22 19:57:27 +01:00
|
|
|
.route('/community', communityRouter)
|
2026-03-09 11:18:22 +01:00
|
|
|
.route('/invite', inviteRouter)
|
2026-05-09 06:05:03 +01:00
|
|
|
.route('/org-site/course', publicCourseRouter)
|
2026-04-12 08:58:41 +01:00
|
|
|
.route('/public-api/v1', v1Router)
|
|
|
|
|
.route('/program', programRouter)
|
2026-03-09 11:18:22 +01:00
|
|
|
.route('/unsplash', unsplashRouter)
|
2026-04-27 11:40:38 +01:00
|
|
|
.route('/widgets', publicWidgetsRouter)
|
2026-05-18 03:47:22 +01:00
|
|
|
.route('/internal', internalRouter)
|
2026-05-09 06:05:03 +01:00
|
|
|
.route('/agent', agentRouter)
|
2025-09-21 19:35:41 +01:00
|
|
|
|
|
|
|
|
// Error handling
|
|
|
|
|
.onError((err, c) => {
|
|
|
|
|
console.error('Error:', err);
|
|
|
|
|
return c.json({ error: 'Internal Server Error' }, 500);
|
|
|
|
|
});
|
2026-05-18 03:47:22 +01:00
|
|
|
|
|
|
|
|
// Dev-only BullMQ dashboard at /admin/queues. Mounted after the typed RPC
|
|
|
|
|
// chain so it doesn't pollute the client type.
|
|
|
|
|
mountQueueDashboard(app);
|