The Appview for the kipclip.com atproto bookmarking service
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 };