Files
paste.es/api/app.ts
Malin bc9f96cbd4 feat: rebrand Hemmelig to paste.es for cloudhost.es
- Set Spanish as default language with ephemeral/encrypted privacy focus
- Translate all user-facing strings and legal pages to Spanish
- Replace Norwegian flag with Spanish flag in footer
- Remove Hemmelig/terces.cloud links, add cloudhost.es sponsorship
- Rewrite PrivacyPage: zero data collection, ephemeral design emphasis
- Rewrite TermsPage: Spanish law, RGPD, paste.es/CloudHost.es references
- Update PWA manifest, HTML meta tags, package.json branding
- Rename webhook headers to X-Paste-Event / X-Paste-Signature
- Update API docs title and contact to paste.es / cloudhost.es

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-24 09:30:19 +01:00

159 lines
4.1 KiB
TypeScript

import { Hono } from 'hono';
import { cors } from 'hono/cors';
import { csrf } from 'hono/csrf';
import { etag, RETAINED_304_HEADERS } from 'hono/etag';
import { HTTPException } from 'hono/http-exception';
import { logger } from 'hono/logger';
import { requestId } from 'hono/request-id';
import { secureHeaders } from 'hono/secure-headers';
import { timeout } from 'hono/timeout';
import { trimTrailingSlash } from 'hono/trailing-slash';
import { ZodError } from 'zod';
import { auth } from './auth';
import config from './config';
import startJobs from './jobs';
import prisma from './lib/db';
import ratelimit from './middlewares/ratelimit';
import routes from './routes';
// Initialize Hono app
const app = new Hono<{
Variables: {
user: typeof auth.$Infer.Session.user | null;
session: typeof auth.$Infer.Session.session | null;
};
}>();
// Global error handler
app.onError((err, c) => {
const requestId = c.get('requestId') || 'unknown';
// Handle Zod validation errors
if (err instanceof ZodError) {
console.error(`[${requestId}] Validation error:`, err.flatten());
return c.json(
{
error: 'Validation failed',
details: err.flatten().fieldErrors,
},
400
);
}
// Handle HTTP exceptions (thrown by Hono or middleware)
if (err instanceof HTTPException) {
console.error(`[${requestId}] HTTP exception:`, {
status: err.status,
message: err.message,
});
return c.json({ error: err.message }, err.status);
}
// Handle all other errors
console.error(`[${requestId}] Unhandled error:`, {
error: err.message,
stack: err.stack,
});
// Don't expose internal error details in production
return c.json({ error: 'Internal server error' }, 500);
});
// Handle 404 - route not found
app.notFound((c) => {
return c.json({ error: 'Not found' }, 404);
});
// Start the background jobs
startJobs();
// Add the middlewares
// More middlewares can be found here:
// https://hono.dev/docs/middleware/builtin/basic-auth
app.use(secureHeaders());
app.use(logger());
app.use(trimTrailingSlash());
app.use(`/*`, requestId());
app.use(`/*`, timeout(15 * 1000)); // 15 seconds timeout to the API calls
app.use(ratelimit);
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/ETag
app.use(
`/*`,
etag({
retainedHeaders: ['x-message', ...RETAINED_304_HEADERS],
})
);
// Configure CORS with trusted origins
const trustedOrigins = config.get<string[]>('trustedOrigins', []);
app.use(
`/*`,
cors({
origin: trustedOrigins,
credentials: true,
})
);
// Configure CSRF protection (exclude auth routes for OAuth callbacks)
app.use('/*', async (c, next) => {
// Skip CSRF for auth routes (OAuth callbacks come from external origins)
if (c.req.path.startsWith('/auth/')) {
return next();
}
return csrf({
origin: trustedOrigins,
})(c, next);
});
// Custom middlewares
app.use('*', async (c, next) => {
const session = await auth.api.getSession({ headers: c.req.raw.headers });
if (!session) {
c.set('user', null);
c.set('session', null);
return next();
}
c.set('user', session.user);
c.set('session', session.session);
return next();
});
// Add the routes
app.on(['POST', 'GET'], `/auth/*`, (c) => {
return auth.handler(c.req.raw);
});
// Add the application routes
app.route('/', routes);
// https://hono.dev/docs/guides/rpc#rpc
export type AppType = typeof routes;
export default app;
// Handle graceful shutdown
process.on('SIGINT', async () => {
await prisma.$disconnect();
console.info('Disconnected from database');
process.exit(0);
});
// Handle uncaught exceptions
process.on('uncaughtException', (error: Error) => {
console.error('Uncaught Exception', {
error: error.message,
stack: error.stack,
});
process.exit(1);
});
// Handle unhandled promise rejections
process.on('unhandledRejection', (reason: unknown) => {
console.error('Unhandled Rejection', { reason });
process.exit(1);
});