a cache for slack profile pictures and emojis
1import * as Sentry from "@sentry/bun";
2import { serve } from "bun";
3import { getEmojiUrl } from "../utils/emojiHelper";
4import { SlackCache } from "./cache";
5import dashboard from "./dashboard.html";
6import { buildRoutes, getSwaggerSpec } from "./lib/route-builder";
7import { createApiRoutes } from "./routes/api-routes";
8import { SlackWrapper } from "./slackWrapper";
9import swagger from "./swagger.html";
10
11// Initialize Sentry if DSN is provided
12if (process.env.SENTRY_DSN) {
13 console.log("Sentry DSN provided, error monitoring is enabled");
14 Sentry.init({
15 environment: process.env.NODE_ENV,
16 dsn: process.env.SENTRY_DSN,
17 tracesSampleRate: 0.5,
18 ignoreErrors: ["Not Found", "404", "user_not_found", "emoji_not_found"],
19 });
20} else {
21 console.warn("Sentry DSN not provided, error monitoring is disabled");
22}
23
24// Initialize SlackWrapper and Cache
25const slackApp = new SlackWrapper();
26const cache = new SlackCache(
27 process.env.DATABASE_PATH ?? "./data/cachet.db",
28 25,
29 async () => {
30 console.log("Fetching emojis from Slack");
31 const emojis = await slackApp.getEmojiList();
32 const emojiEntries = Object.entries(emojis)
33 .map(([name, url]) => {
34 if (typeof url === "string" && url.startsWith("alias:")) {
35 const aliasName = url.substring(6);
36 const aliasUrl = emojis[aliasName] ?? getEmojiUrl(aliasName);
37
38 if (!aliasUrl) {
39 console.warn(`Could not find alias for ${aliasName}`);
40 return null;
41 }
42
43 return {
44 name,
45 imageUrl: aliasUrl,
46 alias: aliasName,
47 };
48 }
49 return {
50 name,
51 imageUrl: url,
52 alias: null,
53 };
54 })
55 .filter(
56 (
57 entry,
58 ): entry is { name: string; imageUrl: string; alias: string | null } =>
59 entry !== null,
60 );
61
62 console.log("Batch inserting emojis");
63 await cache.batchInsertEmojis(emojiEntries);
64 console.log("Finished batch inserting emojis");
65 },
66);
67
68// Inject SlackWrapper into cache for background user updates
69cache.setSlackWrapper(slackApp);
70
71// Create the typed API routes with injected dependencies
72const apiRoutes = createApiRoutes(cache, slackApp);
73
74// Build Bun-compatible routes and generate Swagger
75const typedRoutes = buildRoutes(apiRoutes);
76const generatedSwagger = getSwaggerSpec();
77
78// Legacy routes (non-API)
79const legacyRoutes = {
80 "/dashboard": dashboard,
81 "/swagger": swagger,
82 "/swagger.json": async (_: Request) => {
83 return Response.json(generatedSwagger);
84 },
85 "/favicon.ico": async (_: Request) => {
86 return new Response(Bun.file("./favicon.ico"));
87 },
88
89 // Root route - redirect to dashboard for browsers
90 "/": async (request: Request) => {
91 const userAgent = request.headers.get("user-agent") || "";
92
93 if (
94 userAgent.toLowerCase().includes("mozilla") ||
95 userAgent.toLowerCase().includes("chrome") ||
96 userAgent.toLowerCase().includes("safari")
97 ) {
98 return new Response(null, {
99 status: 302,
100 headers: { Location: "/dashboard" },
101 });
102 }
103
104 return new Response(
105 "Hello World from Cachet 😊\n\n---\nSee /swagger for docs\nSee /dashboard for analytics\n---",
106 );
107 },
108};
109
110// Merge all routes
111const allRoutes = {
112 ...legacyRoutes,
113 ...typedRoutes,
114};
115
116// Start the server
117const server = serve({
118 routes: allRoutes,
119 port: process.env.PORT ? parseInt(process.env.PORT, 10) : 3000,
120});
121
122console.log(`🚀 Server running on http://localhost:${server.port}`);
123
124export { cache, slackApp };