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>
This commit is contained in:
158
api/app.ts
Normal file
158
api/app.ts
Normal file
@@ -0,0 +1,158 @@
|
||||
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);
|
||||
});
|
||||
Reference in New Issue
Block a user