The Appview for the kipclip.com atproto bookmarking service
at main 137 lines 4.1 kB view raw
1/** 2 * Main entry point for kipclip Fresh application. 3 * Orchestrates route registration and middleware setup. 4 */ 5 6// Load environment variables from .env file (local development) 7import { load } from "@std/dotenv"; 8try { 9 await load({ export: true }); 10 console.log("✅ Loaded .env file"); 11} catch (error) { 12 console.warn("⚠️ Failed to load .env file:", error.message); 13} 14 15import { App, staticFiles } from "@fresh/core"; 16import { initializeTables } from "./lib/db.ts"; 17import { initOAuth } from "./lib/oauth-config.ts"; 18import { captureError } from "./lib/sentry.ts"; 19 20// Route modules 21import { registerAuthRoutes } from "./routes/api/auth.ts"; 22import { registerBookmarkRoutes } from "./routes/api/bookmarks.ts"; 23import { registerInitialDataRoutes } from "./routes/api/initial-data.ts"; 24import { registerSettingsRoutes } from "./routes/api/settings.ts"; 25import { registerBulkRoutes } from "./routes/api/bulk.ts"; 26import { registerImportRoutes } from "./routes/api/import.ts"; 27import { registerPreferencesRoutes } from "./routes/api/preferences.ts"; 28import { registerShareApiRoutes } from "./routes/api/share.ts"; 29import { registerTagRoutes } from "./routes/api/tags.ts"; 30import { registerOAuthRoutes } from "./routes/oauth.ts"; 31import { registerRssRoutes } from "./routes/share/rss.ts"; 32import { registerShareTargetRoutes } from "./routes/share-target.ts"; 33import { registerStaticRoutes } from "./routes/static.ts"; 34 35// Run database migrations on startup 36await initializeTables(); 37 38// Create the Fresh app 39let app = new App(); 40 41// ============================================================================ 42// Middleware 43// ============================================================================ 44 45// Error handling middleware 46app = app.use(async (ctx) => { 47 try { 48 return await ctx.next(); 49 } catch (err) { 50 captureError(err, { url: ctx.req.url, method: ctx.req.method }); 51 throw err; 52 } 53}); 54 55// Initialize OAuth on first request (derives BASE_URL from request if not set) 56app = app.use(async (ctx) => { 57 initOAuth(ctx.req); 58 return await ctx.next(); 59}); 60 61// Security headers middleware 62app = app.use(async (ctx) => { 63 const response = await ctx.next(); 64 65 // Prevent clickjacking 66 response.headers.set("X-Frame-Options", "DENY"); 67 68 // Prevent MIME type sniffing 69 response.headers.set("X-Content-Type-Options", "nosniff"); 70 71 // Control referrer information 72 response.headers.set("Referrer-Policy", "strict-origin-when-cross-origin"); 73 74 // HTTPS enforcement (1 year) 75 response.headers.set( 76 "Strict-Transport-Security", 77 "max-age=31536000; includeSubDomains", 78 ); 79 80 // Restrict browser features 81 response.headers.set( 82 "Permissions-Policy", 83 "camera=(), microphone=(), geolocation=()", 84 ); 85 86 return response; 87}); 88 89// ============================================================================ 90// Register routes 91// ============================================================================ 92 93// OAuth routes (login, callback, metadata) 94app = registerOAuthRoutes(app); 95 96// Auth API routes (session, logout) 97app = registerAuthRoutes(app); 98 99// Bookmark API routes 100app = registerBookmarkRoutes(app); 101 102// Bulk operations API routes 103app = registerBulkRoutes(app); 104 105// Import API routes 106app = registerImportRoutes(app); 107 108// Tag API routes 109app = registerTagRoutes(app); 110 111// Initial data API route (combined bookmarks + tags + settings) 112app = registerInitialDataRoutes(app); 113 114// Settings API routes 115app = registerSettingsRoutes(app); 116 117// Preferences API routes (PDS-backed user preferences) 118app = registerPreferencesRoutes(app); 119 120// Share API routes (public bookmark sharing) 121app = registerShareApiRoutes(app); 122 123// RSS feed routes 124app = registerRssRoutes(app); 125 126// Share target routes (PWA share functionality) 127app = registerShareTargetRoutes(app); 128 129// Serve static files from /static directory (must be after API routes to 130// prevent staticFiles() from intercepting POST/PUT/DELETE requests with 405) 131app.use(staticFiles()); 132 133// Static files and SPA routing (must be last) 134app = registerStaticRoutes(app, import.meta.url); 135 136// Export app for Fresh build system 137export { app };