feat: add live Discord sharing functionality with dynamic habit progress images

- Implemented live card image generation for user habits using canvas.
- Created API endpoints for starting and managing live Discord sharing sessions.
- Added tests for live Discord sharing to ensure functionality and reliability.
- Introduced CSS styles for the live Discord sharing interface.
- Enhanced error handling and state management for Discord message updates.
This commit is contained in:
syntaxbullet
2026-09-07 11:36:47 +02:00
parent 00729096bf
commit c7aa5a1c65
22 changed files with 2792 additions and 7 deletions

View File

@@ -1,3 +1,4 @@
import { createLiveDiscord } from "./sharing/live-discord";
import { createReminderRoutes } from "./reminders/routes";
import { errorDetails } from "./ops/monitor";
import { createAccountRoutes } from "./account/routes";
@@ -19,12 +20,19 @@ export function createApi(db: AppDatabase, config: AuthConfig, request?: FetchDi
await next();
});
const auth = createAuth(db, config, request, now);
const liveSharing = createLiveDiscord(db, auth, sharingConfig, now ?? Date.now, discordRequest);
app.use("*", async (c, next) => {
await next();
if (!["GET", "HEAD", "OPTIONS"].includes(c.req.method) && /^\/api\/(habits|me|account)(\/|$)/.test(c.req.path) && c.res.status < 400 && c.get("user"))
liveSharing.changed(c.get("user").id);
});
app.get("/api/health", c => {
c.header("Cache-Control", "no-store");
db.get(sql`SELECT 1`);
if (operations.healthy && !operations.healthy()) return c.json({ status: "degraded" }, 503);
return c.json({ status: "ok" });
});
app.route("/api/sharing/live", liveSharing.routes);
app.route("/api/auth", auth.routes);
app.route("/api/reminders", createReminderRoutes(db, auth, Boolean(sharingConfig.token), now ?? Date.now));
app.route("/api/account", createAccountRoutes(db, auth, now ?? Date.now));
@@ -41,5 +49,5 @@ export function createApi(db: AppDatabase, config: AuthConfig, request?: FetchDi
(operations.log ?? (event => console.error(JSON.stringify(event))))(event);
return c.json({ error: "Internal server error", requestId }, 500);
});
return app;
return Object.assign(app, { stopLiveSharing: liveSharing.stop, flushLiveSharing: liveSharing.flush });
}