Compare commits

..

5 Commits

Author SHA1 Message Date
291d2744af docs: update README
add folder info about middleware
2025-12-18 23:43:12 -05:00
e9864f42b1 feat: use luxon for TTL 2025-12-18 23:43:11 -05:00
0cdfbd5c56 chore: add debug logging to help understand why episode updates won't run 2025-12-18 23:43:11 -05:00
1958110679 chore: remove no longer needed isRetrying boolean 2025-12-18 23:43:11 -05:00
3294d3c6a2 fix: 'upcoming' titles failing to fetch
wasn't returning "Page" from the anilist json response
2025-12-18 23:43:10 -05:00
7 changed files with 18 additions and 37 deletions

View File

@@ -15,7 +15,7 @@ type AiringSchedule = {
id: number; id: number;
}; };
export async function getUpcomingTitlesFromAnilist() { export async function getUpcomingTitlesFromAnilist(req: HonoRequest) {
const durableObjectId = env.ANILIST_DO.idFromName("GLOBAL"); const durableObjectId = env.ANILIST_DO.idFromName("GLOBAL");
const stub = env.ANILIST_DO.get(durableObjectId); const stub = env.ANILIST_DO.get(durableObjectId);

View File

@@ -9,8 +9,8 @@ import { getUpcomingTitlesFromAnilist } from "./anilist";
const app = new Hono(); const app = new Hono();
export async function checkUpcomingTitles() { app.post("/", async (c) => {
const titles = await getUpcomingTitlesFromAnilist(); const titles = await getUpcomingTitlesFromAnilist(c.req);
await Promise.allSettled( await Promise.allSettled(
titles.map(async (title) => { titles.map(async (title) => {
@@ -44,10 +44,6 @@ export async function checkUpcomingTitles() {
}); });
}), }),
); );
}
app.post("/", async (c) => {
await checkUpcomingTitles();
return c.json(SuccessResponse, 200); return c.json(SuccessResponse, 200);
}); });

View File

@@ -51,7 +51,7 @@ describe('requests the "/title" route', () => {
headers: new Headers({ "x-anilist-token": "asd" }), headers: new Headers({ "x-anilist-token": "asd" }),
}); });
await expect(response.json()).resolves.toMatchSnapshot(); expect(await response.json()).toMatchSnapshot();
expect(response.status).toBe(200); expect(response.status).toBe(200);
}); });
@@ -63,7 +63,7 @@ describe('requests the "/title" route', () => {
const response = await app.request("/title?id=10"); const response = await app.request("/title?id=10");
await expect(response.json()).resolves.toMatchSnapshot(); expect(await response.json()).toMatchSnapshot();
expect(response.status).toBe(200); expect(response.status).toBe(200);
}); });
@@ -75,7 +75,7 @@ describe('requests the "/title" route', () => {
const response = await app.request("/title?id=-1"); const response = await app.request("/title?id=-1");
await expect(response.json()).resolves.toEqual({ success: false }); expect(await response.json()).toEqual({ success: false });
expect(response.status).toBe(404); expect(response.status).toBe(404);
}); });
}); });

View File

@@ -12,8 +12,6 @@ import {
} from "~/libs/tasks/queueTask"; } from "~/libs/tasks/queueTask";
import { maybeUpdateLastConnectedAt } from "~/middleware/maybeUpdateLastConnectedAt"; import { maybeUpdateLastConnectedAt } from "~/middleware/maybeUpdateLastConnectedAt";
import { checkUpcomingTitles } from "./controllers/internal/upcoming-titles";
export const app = new OpenAPIHono<{ Bindings: Env }>(); export const app = new OpenAPIHono<{ Bindings: Env }>();
app.use(maybeUpdateLastConnectedAt); app.use(maybeUpdateLastConnectedAt);
@@ -123,20 +121,9 @@ export default {
}); });
}, },
async scheduled(event, env, ctx) { async scheduled(event, env, ctx) {
switch (event.cron) { const { processDelayedTasks } =
case "0 */12 * * *": await import("~/libs/tasks/processDelayedTasks");
const { processDelayedTasks } = await processDelayedTasks(env);
await import("~/libs/tasks/processDelayedTasks");
await processDelayedTasks(env);
break;
case "0 18 * * *":
const { checkUpcomingTitles } =
await import("~/controllers/internal/upcoming-titles");
await checkUpcomingTitles();
break;
default:
throw new Error(`Unhandled cron: ${event.cron}`);
}
}, },
} satisfies ExportedHandler<Env>; } satisfies ExportedHandler<Env>;

View File

@@ -43,7 +43,7 @@ export class AnilistDurableObject extends DurableObject {
async getTitle( async getTitle(
id: number, id: number,
userId?: number, userId?: string,
token?: string, token?: string,
): Promise<Title | null> { ): Promise<Title | null> {
const promises: Promise<any>[] = [ const promises: Promise<any>[] = [
@@ -135,7 +135,8 @@ export class AnilistDurableObject extends DurableObject {
nextSeason, nextSeason,
nextYear, nextYear,
limit, limit,
}); page,
}).then((data) => data?.Page);
}, },
DateTime.now().plus({ days: 1 }), DateTime.now().plus({ days: 1 }),
); );
@@ -154,8 +155,7 @@ export class AnilistDurableObject extends DurableObject {
nextSeason, nextSeason,
nextYear, nextYear,
limit, limit,
page, });
}).then((data) => data?.Page);
}, },
DateTime.now().plus({ days: 1 }), DateTime.now().plus({ days: 1 }),
); );
@@ -276,11 +276,13 @@ export class AnilistDurableObject extends DurableObject {
const result = await fetcher(); const result = await fetcher();
await this.state.storage.put(key, result); await this.state.storage.put(key, result);
console.debug(`Retrieved alarms from cache:`, Object.entries(alarms));
const calculatedTtl = typeof ttl === "function" ? ttl(result) : ttl; const calculatedTtl = typeof ttl === "function" ? ttl(result) : ttl;
if (calculatedTtl) { if (calculatedTtl) {
const alarmTime = calculatedTtl.toMillis(); const alarmTime = calculatedTtl.toMillis();
await this.state.storage.setAlarm(alarmTime); await this.state.storage.setAlarm(alarmTime);
console.debug(`Deleting storage key ${storageKey} & alarm ${key}`);
await this.state.storage.put(`alarm:${key}`, alarmTime); await this.state.storage.put(`alarm:${key}`, alarmTime);
} }
@@ -290,13 +292,11 @@ export class AnilistDurableObject extends DurableObject {
async alarm() { async alarm() {
const now = Date.now(); const now = Date.now();
const alarms = await this.state.storage.list({ prefix: "alarm:" }); const alarms = await this.state.storage.list({ prefix: "alarm:" });
console.debug(`Retrieved alarms from cache:`, Object.entries(alarms));
for (const [key, ttl] of Object.entries(alarms)) { for (const [key, ttl] of Object.entries(alarms)) {
if (now >= ttl) { if (now >= ttl) {
// The key in alarms is `alarm:${storageKey}` // The key in alarms is `alarm:${storageKey}`
// We want to delete the storageKey // We want to delete the storageKey
const storageKey = key.replace("alarm:", ""); const storageKey = key.replace("alarm:", "");
console.debug(`Deleting storage key ${storageKey} & alarm ${key}`);
await this.state.storage.delete(storageKey); await this.state.storage.delete(storageKey);
await this.state.storage.delete(key); await this.state.storage.delete(key);
} }

View File

@@ -3,13 +3,11 @@ import mapKeys from "lodash.mapkeys";
import { Case, changeStringCase } from "../changeStringCase"; import { Case, changeStringCase } from "../changeStringCase";
export function getAdminSdkCredentials( export function getAdminSdkCredentials(env: Cloudflare.Env = cloudflareEnv) {
env: Cloudflare.Env = cloudflareEnv,
): AdminSdkCredentials {
return mapKeys( return mapKeys(
JSON.parse(env.ADMIN_SDK_JSON) as AdminSdkCredentials, JSON.parse(env.ADMIN_SDK_JSON) as AdminSdkCredentials,
(_, key) => changeStringCase(key, Case.snake_case, Case.camelCase), (_, key) => changeStringCase(key, Case.snake_case, Case.camelCase),
) satisfies AdminSdkCredentials; );
} }
export interface AdminSdkCredentials { export interface AdminSdkCredentials {

View File

@@ -67,7 +67,7 @@ id = "c8db249d8ee7462b91f9c374321776e4"
preview_id = "ff38240eb2aa4b1388c705f4974f5aec" preview_id = "ff38240eb2aa4b1388c705f4974f5aec"
[triggers] [triggers]
crons = ["0 */12 * * *", "0 18 * * *"] crons = ["0 */12 * * *"]
[[d1_databases]] [[d1_databases]]
binding = "DB" binding = "DB"