Prepare worker and Discord snapshots through bounded catch-up
This commit is contained in:
@@ -3,7 +3,7 @@ import { cachedAvatar } from './avatar-cache';
|
||||
import { createCanvas, GlobalFonts } from "@napi-rs/canvas";
|
||||
import { existsSync } from "node:fs";
|
||||
import { eq } from "drizzle-orm";
|
||||
import type { AppDatabase } from "../auth";
|
||||
import { publicUser, type AppDatabase } from "../auth";
|
||||
import { users } from "../db/schema";
|
||||
import { HabitService, ApiError } from "../habits/service";
|
||||
import { addDays } from "../habits/calendar";
|
||||
@@ -25,11 +25,16 @@ for (const [file, family] of [
|
||||
}
|
||||
}
|
||||
|
||||
export function liveCardData(db: AppDatabase, userId: string, now: number) {
|
||||
export async function loadLiveCardData(db: AppDatabase, userId: string, now: () => number) {
|
||||
const user = db.select().from(users).where(eq(users.id, userId)).get();
|
||||
if (!user) throw new ApiError(404, 'Account unavailable.');
|
||||
return HabitService.withPrepared(db, publicUser(user), now, service => liveCardData(db, userId, service.now, service));
|
||||
}
|
||||
export function liveCardData(db: AppDatabase, userId: string, now: number, prepared?: HabitService) {
|
||||
const user = db.select().from(users).where(eq(users.id, userId)).get();
|
||||
if (!user) throw new ApiError(404, "Account unavailable.");
|
||||
const service = new HabitService(db, { ...user, displayName: user.globalName ?? user.username, avatarUrl: null }, now);
|
||||
service.sync();
|
||||
const service = prepared ?? new HabitService(db, { ...user, displayName: user.globalName ?? user.username, avatarUrl: null }, now);
|
||||
if (!prepared) service.sync();
|
||||
const from = addDays(service.today, -29);
|
||||
const avatarUrl = /^\d{17,20}$/.test(user.discordId) && /^(?:a_)?[a-f0-9]{32}$/i.test(user.avatarHash ?? '')
|
||||
? `https://cdn.discordapp.com/avatars/${user.discordId}/${user.avatarHash}.png?size=128` : null;
|
||||
|
||||
@@ -8,7 +8,7 @@ import type { AppDatabase, AuthEnv, createAuth } from "../auth";
|
||||
import { liveDiscordCards, users } from "../db/schema";
|
||||
import { ApiError } from "../habits/service";
|
||||
import { endOfDay, localDate } from "../habits/calendar";
|
||||
import { liveCardData, renderLiveCard } from "./live-card-image";
|
||||
import { loadLiveCardData, renderLiveCard } from "./live-card-image";
|
||||
import type { PublicUser } from "../shared/user";
|
||||
import type { DiscordSharingConfig } from "./config";
|
||||
import type { DiscordFetch } from "./routes";
|
||||
@@ -65,7 +65,7 @@ export function createLiveDiscord(db: AppDatabase, auth: ReturnType<typeof creat
|
||||
const delay = Math.max(current.retryAt ?? 0, channelRetryAt) - now();
|
||||
if (delay > 0) { schedule(id, delay); return; }
|
||||
update(id, { dirty: false, retryAt: null });
|
||||
const data = liveCardData(db, id, now());
|
||||
const data = await loadLiveCardData(db, id, now);
|
||||
// Hash the rendered content, excluding timestamps and private task details.
|
||||
const hash = createHash('sha256').update(JSON.stringify({ data, theme: current.theme, renderer: 3, content: liveMessageText })).digest('hex');
|
||||
if (current.messageId && current.imageHash === hash) {
|
||||
@@ -157,7 +157,7 @@ export function createLiveDiscord(db: AppDatabase, auth: ReturnType<typeof creat
|
||||
app.get('/', c => c.json(state(c.get('user').id)));
|
||||
app.get('/preview', async c => {
|
||||
const theme = c.req.query('theme') === 'dark' ? 'dark' : 'light';
|
||||
const image = await renderLiveCard(liveCardData(db, c.get('user').id, now()), theme, c.get('user').id);
|
||||
const image = await renderLiveCard(await loadLiveCardData(db, c.get('user').id, now), theme, c.get('user').id);
|
||||
c.header('Content-Type', 'image/png'); c.header('Content-Disposition', 'inline; filename="minabot-live-progress.png"');
|
||||
return c.body(new Uint8Array(image));
|
||||
});
|
||||
|
||||
@@ -78,17 +78,19 @@ export function createSharingRoutes(db: AppDatabase, auth: ReturnType<typeof cre
|
||||
|
||||
app.post("/preview", async c => {
|
||||
const input = await json(c.req.raw, shareInput);
|
||||
const service = new HabitService(db, c.get("user"), now()); service.sync();
|
||||
if (input.to > service.today) throw new ApiError(422, "The end date cannot be after today.");
|
||||
const result: ShareData = { from: input.from, to: input.to, timezone: service.user.timezone, habits: input.habitIds.map(id => {
|
||||
const habit = service.current(id);
|
||||
const chart = service.calendar([id], input.from, input.to, service.settings(id), false);
|
||||
return { name: habit.name, color: chart.settings.mainColor,
|
||||
legend: { empty: chart.settings.emptyColor, complete: chart.settings.mainColor,
|
||||
partial: chart.days.some(d => d.shadeCount > 1) ? [0.25, 0.5, 0.75].map(ratio => shade(ratio, 4, chart.settings, false).color) : [] },
|
||||
completed: chart.days.filter(d => d.completed > 0).length, scheduled: chart.days.filter(d => d.due > 0).length,
|
||||
days: chart.days.map(d => ({ date: d.date, color: d.color, due: d.due > 0, complete: d.completed > 0, ratio: d.ratio })) };
|
||||
}) };
|
||||
const result = await HabitService.withPrepared(db, c.get("user"), now, service => {
|
||||
if (input.to > service.today) throw new ApiError(422, "The end date cannot be after today.");
|
||||
const result: ShareData = { from: input.from, to: input.to, timezone: service.user.timezone, habits: input.habitIds.map(id => {
|
||||
const habit = service.current(id);
|
||||
const chart = service.calendar([id], input.from, input.to, service.settings(id), false);
|
||||
return { name: habit.name, color: chart.settings.mainColor,
|
||||
legend: { empty: chart.settings.emptyColor, complete: chart.settings.mainColor,
|
||||
partial: chart.days.some(d => d.shadeCount > 1) ? [0.25, 0.5, 0.75].map(ratio => shade(ratio, 4, chart.settings, false).color) : [] },
|
||||
completed: chart.days.filter(d => d.completed > 0).length, scheduled: chart.days.filter(d => d.due > 0).length,
|
||||
days: chart.days.map(d => ({ date: d.date, color: d.color, due: d.due > 0, complete: d.completed > 0, ratio: d.ratio })) };
|
||||
}) };
|
||||
return result;
|
||||
});
|
||||
result.botAvatarUrl = await botAvatarUrl();
|
||||
return c.json(result);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user