refactor: ♻️emoves Env parameter

Removes the `Env` parameter from several functions to simplify their signatures and rely on the global `env` for configuration.

This change reduces the number of arguments passed around, making the code cleaner and easier to maintain.
This commit is contained in:
2025-08-10 19:22:14 -04:00
parent 0b0078328c
commit 8175d73df1
26 changed files with 8716 additions and 184 deletions

View File

@@ -1,12 +1,10 @@
import { OpenAPIHono, createRoute, z } from "@hono/zod-openapi";
import { env } from "hono/adapter";
import { streamSSE } from "hono/streaming";
import { fetchEpisodes } from "~/controllers/episodes/getByAniListId";
import { maybeScheduleNextAiringEpisode } from "~/libs/maybeScheduleNextAiringEpisode";
import { associateDeviceIdWithUsername } from "~/models/token";
import { setWatchStatus } from "~/models/watchStatus";
import type { Env } from "~/types/env";
import { EpisodesResponseSchema } from "~/types/episode";
import { ErrorResponse, ErrorResponseSchema } from "~/types/schema";
import { Title } from "~/types/title";
@@ -87,7 +85,7 @@ const route = createRoute({
},
});
const app = new OpenAPIHono<Env>();
const app = new OpenAPIHono<Cloudflare.Env>();
app.openapi(route, async (c) => {
const deviceId =
@@ -112,11 +110,7 @@ app.openapi(route, async (c) => {
}
try {
await associateDeviceIdWithUsername(
env(c, "workerd"),
deviceId!,
user.name!,
);
await associateDeviceIdWithUsername(deviceId!, user.name!);
} catch (error) {
console.error("Failed to associate device");
console.error(error);
@@ -157,17 +151,12 @@ app.openapi(route, async (c) => {
const mediaListEntry = media.mediaListEntry;
if (mediaListEntry) {
const { wasAdded } = await setWatchStatus(
env(c, "workerd"),
deviceId!,
media.id,
mediaListEntry.status,
);
if (wasAdded) {
await maybeScheduleNextAiringEpisode(
env<Env, typeof c>(c, "workerd"),
c.req,
media.id,
);
await maybeScheduleNextAiringEpisode(c.req, media.id);
}
}

View File

@@ -4,14 +4,12 @@ import { PromiseTimedOutError, promiseTimeout } from "~/libs/promiseTimeout";
import { readEnvVariable } from "~/libs/readEnvVariable";
import { sortByProperty } from "~/libs/sortByProperty";
import { getValue, setValue } from "~/models/kv";
import type { Env } from "~/types/env";
import type { EpisodesResponse } from "~/types/episode";
export async function getEpisodesFromAnify(
env: Env,
aniListId: number,
): Promise<EpisodesResponse | null> {
if (await shouldSkipAnify(env, aniListId)) {
if (await shouldSkipAnify(aniListId)) {
console.log("Skipping Anify for title", aniListId);
return null;
}
@@ -33,7 +31,6 @@ export async function getEpisodesFromAnify(
DateTime.now().plus({ minutes: 1 }).toISO(),
);
setValue(
env,
"anify_killswitch_till",
DateTime.now().plus({ minutes: 1 }).toISO(),
);
@@ -93,11 +90,8 @@ export async function getEpisodesFromAnify(
};
}
export async function shouldSkipAnify(
env: Env,
aniListId: number,
): Promise<boolean> {
if (!readEnvVariable(env, "ENABLE_ANIFY")) {
export async function shouldSkipAnify(aniListId: number): Promise<boolean> {
if (!readEnvVariable("ENABLE_ANIFY")) {
return true;
}
@@ -114,7 +108,7 @@ export async function shouldSkipAnify(
return true;
}
return await getValue(env, "anify_killswitch_till").then((dateTime) => {
return await getValue("anify_killswitch_till").then((dateTime) => {
if (!dateTime) {
return false;
}

View File

@@ -1,6 +1,5 @@
import { OpenAPIHono, createRoute, z } from "@hono/zod-openapi";
import type { Env } from "~/types/env";
import { EpisodesResponseSchema } from "~/types/episode";
import {
AniListIdQuerySchema,
@@ -37,7 +36,7 @@ const route = createRoute({
},
});
const app = new OpenAPIHono<Env>();
const app = new OpenAPIHono<Cloudflare.Env>();
export function fetchEpisodes(aniListId: number, shouldRetry: boolean = false) {
return import("./aniwatch")

View File

@@ -1,6 +1,5 @@
import { OpenAPIHono, createRoute, z } from "@hono/zod-openapi";
import type { Env } from "~/types/env";
import { FetchUrlResponse } from "~/types/episode/fetch-url-response";
import {
AniListIdQuerySchema,
@@ -65,7 +64,7 @@ const route = createRoute({
},
});
const app = new OpenAPIHono<Env>();
const app = new OpenAPIHono<Cloudflare.Env>();
export async function fetchEpisodeUrl({
id,

View File

@@ -2,7 +2,6 @@ import { OpenAPIHono, createRoute, z } from "@hono/zod-openapi";
import { env } from "hono/adapter";
import { updateWatchStatus } from "~/controllers/watch-status";
import type { Env } from "~/types/env";
import {
AniListIdQuerySchema,
EpisodeNumberSchema,
@@ -63,7 +62,7 @@ const route = createRoute({
},
});
const app = new OpenAPIHono<Env>();
const app = new OpenAPIHono<Cloudflare.Env>();
app.openapi(route, async (c) => {
const aniListToken = c.req.header("X-AniList-Token");
@@ -85,13 +84,7 @@ app.openapi(route, async (c) => {
isComplete,
);
if (isComplete) {
await updateWatchStatus(
env(c, "workerd"),
c.req,
deviceId,
aniListId,
"COMPLETED",
);
await updateWatchStatus(c.req, deviceId, aniListId, "COMPLETED");
}
if (!user) {

View File

@@ -1,6 +1,5 @@
import { zValidator } from "@hono/zod-validator";
import { Hono } from "hono";
import { env } from "hono/adapter";
import { z } from "zod";
import { fetchEpisodeUrl } from "~/controllers/episodes/getEpisodeUrl";
@@ -9,7 +8,6 @@ import { sendFcmMessage } from "~/libs/gcloud/sendFcmMessage";
import { maybeScheduleNextAiringEpisode } from "~/libs/maybeScheduleNextAiringEpisode";
import { getTokensSubscribedToTitle } from "~/models/token";
import { isWatchingTitle } from "~/models/watchStatus";
import type { Env } from "~/types/env";
import {
AniListIdSchema,
EpisodeNumberSchema,
@@ -36,7 +34,7 @@ app.post(
`Internal new episode route, aniListId: ${aniListId}, episodeNumber: ${episodeNumber}`,
);
if (!(await isWatchingTitle(env<Env, typeof c>(c, "workerd"), aniListId))) {
if (!(await isWatchingTitle(aniListId))) {
console.log(`Title ${aniListId} is no longer being watched`);
return c.json(
{ success: true, result: { isNoLongerWatching: true } },
@@ -53,35 +51,25 @@ app.post(
);
}
const tokens = await getTokensSubscribedToTitle(
env<Env, typeof c>(c, "workerd"),
aniListId,
);
const tokens = await getTokensSubscribedToTitle(aniListId);
await Promise.allSettled(
tokens.map(async (token) => {
return sendFcmMessage(
getAdminSdkCredentials(env<Env, typeof c>(c, "workerd")),
{
token,
data: {
type: "new_episode",
episodes: JSON.stringify(episodes),
episodeStreamInfo: JSON.stringify(fetchUrlResult),
aniListId: aniListId.toString(),
episodeNumber: episodeNumber.toString(),
},
android: { priority: "high" },
return sendFcmMessage(getAdminSdkCredentials(), {
token,
data: {
type: "new_episode",
episodes: JSON.stringify(episodes),
episodeStreamInfo: JSON.stringify(fetchUrlResult),
aniListId: aniListId.toString(),
episodeNumber: episodeNumber.toString(),
},
);
android: { priority: "high" },
});
}),
);
await maybeScheduleNextAiringEpisode(
env<Env, typeof c>(c, "workerd"),
c.req,
aniListId,
);
await maybeScheduleNextAiringEpisode(c.req, aniListId);
return c.json(SuccessResponse, 200);
},

View File

@@ -6,7 +6,6 @@ import { DateTime } from "luxon";
import { maybeScheduleNextAiringEpisode } from "~/libs/maybeScheduleNextAiringEpisode";
import { getValue, setValue } from "~/models/kv";
import { filterUnreleasedTitles } from "~/models/unreleasedTitles";
import type { Env } from "~/types/env";
import type { Title } from "~/types/title";
import { MediaFragment } from "~/types/title/mediaFragment";
@@ -49,12 +48,11 @@ type AiringSchedule = {
id: number;
};
export async function getUpcomingTitlesFromAnilist(env: Env, req: HonoRequest) {
export async function getUpcomingTitlesFromAnilist(req: HonoRequest) {
const client = new GraphQLClient("https://graphql.anilist.co/");
const lastCheckedScheduleAt = await getValue(
env,
"schedule_last_checked_at",
).then((value) => (value ? Number(value) : DateTime.now().toUnixInteger()));
const lastCheckedScheduleAt = await getValue("schedule_last_checked_at").then(
(value) => (value ? Number(value) : DateTime.now().toUnixInteger()),
);
const twoDaysFromNow = DateTime.now().plus({ days: 2 }).toUnixInteger();
let currentPage = 1;
@@ -72,7 +70,6 @@ export async function getUpcomingTitlesFromAnilist(env: Env, req: HonoRequest) {
const { airingSchedules, pageInfo } = Page!;
plannedToWatchTitles = plannedToWatchTitles.union(
await filterUnreleasedTitles(
env,
airingSchedules!.map((schedule) => schedule!.media?.id!),
),
);
@@ -90,7 +87,7 @@ export async function getUpcomingTitlesFromAnilist(env: Env, req: HonoRequest) {
await Promise.all(
Array.from(plannedToWatchTitles).map((titleId) =>
maybeScheduleNextAiringEpisode(env, req, titleId),
maybeScheduleNextAiringEpisode(req, titleId),
),
);
@@ -99,7 +96,6 @@ export async function getUpcomingTitlesFromAnilist(env: Env, req: HonoRequest) {
}
await setValue(
env,
"schedule_last_checked_at",
scheduleList[scheduleList.length - 1].airingAt.toString(),
);

View File

@@ -1,10 +1,8 @@
import { Hono } from "hono";
import { env } from "hono/adapter";
import { DateTime } from "luxon";
import { getAdminSdkCredentials } from "~/libs/gcloud/getAdminSdkCredentials";
import { sendFcmMessage } from "~/libs/gcloud/sendFcmMessage";
import type { Env } from "~/types/env";
import { SuccessResponse } from "~/types/schema";
import { getUpcomingTitlesFromAnilist } from "./anilist";
@@ -12,10 +10,7 @@ import { getUpcomingTitlesFromAnilist } from "./anilist";
const app = new Hono();
app.post("/", async (c) => {
const titles = await getUpcomingTitlesFromAnilist(
env<Env, typeof c>(c, "workerd"),
c.req,
);
const titles = await getUpcomingTitlesFromAnilist(c.req);
await Promise.allSettled(
titles.map(async (title) => {
@@ -24,7 +19,7 @@ app.post("/", async (c) => {
title.media.title?.english ??
"Unknown Title";
return sendFcmMessage(getAdminSdkCredentials(c.env), {
return sendFcmMessage(getAdminSdkCredentials(), {
topic: "newTitles",
data: {
type: "new_title",

View File

@@ -4,7 +4,6 @@ import { env } from "hono/adapter";
import { getAdminSdkCredentials } from "~/libs/gcloud/getAdminSdkCredentials";
import { verifyFcmToken } from "~/libs/gcloud/verifyFcmToken";
import { saveToken } from "~/models/token";
import type { Env } from "~/types/env";
import {
ErrorResponse,
ErrorResponseSchema,
@@ -68,15 +67,12 @@ app.openapi(route, async (c) => {
const { token, deviceId } = await c.req.json<typeof SaveTokenRequest._type>();
try {
const isValidToken = await verifyFcmToken(
token,
getAdminSdkCredentials(c.env),
);
const isValidToken = await verifyFcmToken(token, getAdminSdkCredentials());
if (!isValidToken) {
return c.json(ErrorResponse, 401);
}
await saveToken(env(c, "workerd"), deviceId, token);
await saveToken(deviceId, token);
} catch (error) {
console.error("Failed to save token");
console.error(error);

View File

@@ -1,13 +1,11 @@
import { OpenAPIHono, createRoute, z } from "@hono/zod-openapi";
import type { HonoRequest } from "hono";
import { env } from "hono/adapter";
import { maybeScheduleNextAiringEpisode } from "~/libs/maybeScheduleNextAiringEpisode";
import { buildNewEpisodeTaskId } from "~/libs/tasks/id";
import { queueTask } from "~/libs/tasks/queueTask";
import { removeTask } from "~/libs/tasks/removeTask";
import { setWatchStatus } from "~/models/watchStatus";
import type { Env } from "~/types/env";
import {
AniListIdSchema,
ErrorResponse,
@@ -19,7 +17,7 @@ import { WatchStatus } from "~/types/title/watchStatus";
import { maybeUpdateWatchStatusOnAnilist } from "./anilist";
const app = new OpenAPIHono<Env>();
const app = new OpenAPIHono<Cloudflare.Env>();
const UpdateWatchStatusRequest = z.object({
deviceId: z.string(),
@@ -67,22 +65,20 @@ const route = createRoute({
});
export async function updateWatchStatus(
env: Env,
req: HonoRequest,
deviceId: string,
titleId: number,
watchStatus: WatchStatus | null,
) {
const { wasAdded, wasDeleted } = await setWatchStatus(
env,
deviceId,
Number(titleId),
watchStatus,
);
if (wasAdded) {
await maybeScheduleNextAiringEpisode(env, req, titleId);
await maybeScheduleNextAiringEpisode(req, titleId);
} else if (wasDeleted) {
await removeTask(env, "new-episode", buildNewEpisodeTaskId(titleId));
await removeTask("new-episode", buildNewEpisodeTaskId(titleId));
}
}
@@ -97,13 +93,7 @@ app.openapi(route, async (c) => {
if (!isRetrying) {
try {
await updateWatchStatus(
env(c, "workerd"),
c.req,
deviceId,
titleId,
watchStatus,
);
await updateWatchStatus(c.req, deviceId, titleId, watchStatus);
} catch (error) {
console.error("Error setting watch status");
console.error(error);
@@ -125,7 +115,6 @@ app.openapi(route, async (c) => {
}
await queueTask(
env(c, "workerd"),
"anilist-updates",
{
deviceId,

View File

@@ -1,13 +1,12 @@
import { env as cloudflareEnv } from "cloudflare:workers";
import mapKeys from "lodash.mapkeys";
import type { Env } from "~/types/env";
import { Case, changeStringCase } from "../changeStringCase";
import { readEnvVariable } from "../readEnvVariable";
export function getAdminSdkCredentials(env: Env) {
export function getAdminSdkCredentials(env: Cloudflare.Env = cloudflareEnv) {
return mapKeys(
readEnvVariable<AdminSdkCredentials>(env, "ADMIN_SDK_JSON"),
readEnvVariable<AdminSdkCredentials>("ADMIN_SDK_JSON", env),
(_, key) => changeStringCase(key, Case.snake_case, Case.camelCase),
) as unknown as AdminSdkCredentials;
}

View File

@@ -5,14 +5,12 @@ import {
addUnreleasedTitle,
removeUnreleasedTitle,
} from "~/models/unreleasedTitles";
import type { Env } from "~/types/env";
import { getNextEpisodeTimeUntilAiring } from "./anilist/getNextEpisodeAiringAt";
import { getCurrentDomain } from "./getCurrentDomain";
import { queueTask } from "./tasks/queueTask";
export async function maybeScheduleNextAiringEpisode(
env: Env,
req: HonoRequest,
aniListId: number,
) {
@@ -27,7 +25,7 @@ export async function maybeScheduleNextAiringEpisode(
DateTime.fromSeconds(nextAiring.airingAt).diffNow("hours").hours >= 720
) {
if (status === "NOT_YET_RELEASED") {
await addUnreleasedTitle(env, aniListId);
await addUnreleasedTitle(aniListId);
}
return;
@@ -35,10 +33,9 @@ export async function maybeScheduleNextAiringEpisode(
const { airingAt, episode: nextEpisode } = nextAiring;
await queueTask(
env,
"new-episode",
{ aniListId, episodeNumber: nextEpisode },
{ req, scheduleConfig: { epochTime: airingAt } },
);
await removeUnreleasedTitle(env, aniListId);
await removeUnreleasedTitle(aniListId);
}

View File

@@ -6,31 +6,30 @@ describe("readEnvVariable", () => {
describe("env & variable defined", () => {
it("returns boolean", () => {
expect(
readEnvVariable<boolean>({ ENABLE_ANIFY: "false" }, "ENABLE_ANIFY"),
readEnvVariable<boolean>("ENABLE_ANIFY", { ENABLE_ANIFY: "false" }),
).toBe(false);
});
it("returns string", () => {
expect(
readEnvVariable<string>(
{ QSTASH_TOKEN: "ehf73g8gyriuvnieojwicbg83hc" },
"QSTASH_TOKEN",
),
readEnvVariable<string>("QSTASH_TOKEN", {
QSTASH_TOKEN: "ehf73g8gyriuvnieojwicbg83hc",
}),
).toBe("ehf73g8gyriuvnieojwicbg83hc");
});
it("returns number", () => {
expect(
readEnvVariable<number>({ NUM_RETRIES: "123" }, "NUM_RETRIES"),
readEnvVariable<number>("NUM_RETRIES", { NUM_RETRIES: "123" }),
).toBe(123);
});
});
it("env defined but variable not defined, returns default value", () => {
expect(readEnvVariable<boolean>({ FOO: "bar" }, "ENABLE_ANIFY")).toBe(true);
expect(readEnvVariable<boolean>("ENABLE_ANIFY", { FOO: "bar" })).toBe(true);
});
it("env not defined, returns default value", () => {
expect(readEnvVariable<boolean>(undefined, "ENABLE_ANIFY")).toBe(true);
expect(readEnvVariable<boolean>("ENABLE_ANIFY", undefined)).toBe(true);
});
});

View File

@@ -1,15 +1,14 @@
import { env as cloudflareEnv } from "cloudflare:workers";
import type { Bindings } from "hono/types";
import type { Env } from "~/types/env";
type EnvVariable = keyof Omit<Env, "Bindings" | "Variables">;
type EnvVariable = keyof Cloudflare.Env;
const defaultValues: Record<EnvVariable, any> = {
ENABLE_ANIFY: true,
};
export function readEnvVariable<T>(
env: Bindings | undefined,
envVariable: EnvVariable,
env: Bindings | undefined = cloudflareEnv,
): T {
try {
return JSON.parse(env?.[envVariable] ?? null) ?? defaultValues[envVariable];

View File

@@ -1,8 +1,8 @@
import { env as cloudflareEnv } from "cloudflare:workers";
import type { HonoRequest } from "hono";
import isEqual from "lodash.isequal";
import { DateTime, type DurationLike } from "luxon";
import type { Env } from "~/types/env";
import type { WatchStatus } from "~/types/title/watchStatus";
import { FailedToQueueTaskError } from "../errors/FailedToQueueTask";
@@ -24,20 +24,20 @@ type QueueBody = {
};
type ScheduleConfig =
| { delay: DurationLike; epochTime: never }
| { epochTime: number; delay: never };
| { delay: DurationLike; epochTime?: never }
| { epochTime: number; delay?: never };
interface QueueTaskOptionalArgs {
scheduleConfig?: ScheduleConfig;
/** when req is not provided, that means the task is being created locally */
req?: HonoRequest;
env?: Cloudflare.Env;
}
export async function queueTask(
env: Env,
queueName: QueueName,
body: QueueBody[QueueName],
{ scheduleConfig, req }: QueueTaskOptionalArgs = {},
{ scheduleConfig, req, env = cloudflareEnv }: QueueTaskOptionalArgs = {},
) {
const domain = req
? getCurrentDomain(req)
@@ -114,7 +114,7 @@ export async function queueTask(
}
async function checkIfTaskExists(
env: Env,
env: Cloudflare.Env,
queueName: QueueName,
taskId: string,
expectedBody: QueueBody[QueueName],

View File

@@ -1,14 +1,10 @@
import type { Env } from "~/types/env";
import { env } from "cloudflare:workers";
import { getAdminSdkCredentials } from "../gcloud/getAdminSdkCredentials";
import { getGoogleAuthToken } from "../gcloud/getGoogleAuthToken";
import type { QueueName } from "./queueName";
export async function removeTask(
env: Env,
queueName: QueueName,
taskId: string,
) {
export async function removeTask(queueName: QueueName, taskId: string) {
const adminSdkCredentials = getAdminSdkCredentials(env);
const { projectId } = adminSdkCredentials;

View File

@@ -1,7 +1,5 @@
import type { Env } from "~/types/env";
/** Should only be used when it doesn't make sense for 'Bindings' or 'Variables' to be set. Otherwise, use getTestEnv(). */
export function getTestEnvVariables(): Omit<Env, "Bindings" | "Variables"> {
export function getTestEnvVariables(): Cloudflare.Env {
return getTestEnv();
}
@@ -10,7 +8,7 @@ export function getTestEnv({
ENABLE_ANIFY = "true",
TURSO_AUTH_TOKEN = "123",
TURSO_URL = "http://127.0.0.1:3001",
}: Partial<Env> = {}): Env {
}: Partial<Cloudflare.Env> = {}): Cloudflare.Env {
return {
ADMIN_SDK_JSON,
ENABLE_ANIFY,

View File

@@ -1,25 +1,20 @@
import { createClient } from "@libsql/client";
import { env as cloudflareEnv } from "cloudflare:workers";
import { drizzle } from "drizzle-orm/libsql";
import type { Env } from "~/types/env";
type Db = ReturnType<typeof createDb>;
type Db = ReturnType<typeof drizzle>;
let db: Db | null = null;
export function getDb(env: Env): Db {
export function getDb(env: Cloudflare.Env = cloudflareEnv): Db {
if (db) {
return db;
}
db = createDb(env);
return db;
}
function createDb(env: Env) {
const client = createClient({
url: env.TURSO_URL,
authToken: env.TURSO_AUTH_TOKEN,
});
return drizzle(client);
db = drizzle(client);
return db;
}

View File

@@ -1,13 +1,12 @@
import { env } from "cloudflare:workers";
import { eq } from "drizzle-orm";
import type { Env } from "~/types/env";
import { getDb } from "./db";
import { keyValueTable } from "./schema";
export type Key = (typeof keyValueTable.key.enumValues)[number];
export function getValue(env: Env, key: Key): Promise<string | undefined> {
export function getValue(key: Key): Promise<string | undefined> {
return getDb(env)
.select()
.from(keyValueTable)
@@ -15,7 +14,7 @@ export function getValue(env: Env, key: Key): Promise<string | undefined> {
.then((results) => results[0]?.value);
}
export function setValue(env: Env, key: Key, value: string) {
export function setValue(key: Key, value: string) {
return getDb(env)
.insert(keyValueTable)
.values({ key, value })
@@ -23,7 +22,7 @@ export function setValue(env: Env, key: Key, value: string) {
.run();
}
export function deleteValue(env: Env, key: Key) {
export function deleteValue(key: Key) {
return getDb(env)
.delete(keyValueTable)
.where(eq(keyValueTable.key, key))

View File

@@ -1,15 +1,14 @@
import { env } from "cloudflare:workers";
import { and, eq, gt, sql } from "drizzle-orm";
import type { Env } from "~/types/env";
import { getDb } from "./db";
import { deviceTokensTable, watchStatusTable } from "./schema";
export function saveToken(env: Env, deviceId: string, token: string) {
return insertToken(env, deviceId, token);
export function saveToken(deviceId: string, token: string) {
return insertToken(deviceId, token);
}
function insertToken(env: Env, deviceId: string, token: string) {
function insertToken(deviceId: string, token: string) {
return getDb(env)
.insert(deviceTokensTable)
.values({ deviceId, token })
@@ -21,7 +20,6 @@ function insertToken(env: Env, deviceId: string, token: string) {
}
export function associateDeviceIdWithUsername(
env: Env,
deviceId: string,
username: string,
) {
@@ -32,7 +30,7 @@ export function associateDeviceIdWithUsername(
.run();
}
export function updateDeviceLastConnectedAt(env: Env, deviceId: string) {
export function updateDeviceLastConnectedAt(deviceId: string) {
return getDb(env)
.update(deviceTokensTable)
.set({ lastConnectedAt: sql`(CURRENT_TIMESTAMP)` })
@@ -40,7 +38,7 @@ export function updateDeviceLastConnectedAt(env: Env, deviceId: string) {
.run();
}
export function getTokensSubscribedToTitle(env: Env, titleId: number) {
export function getTokensSubscribedToTitle(titleId: number) {
return getDb(env)
.select({ token: deviceTokensTable.token })
.from(deviceTokensTable)

View File

@@ -1,11 +1,10 @@
import { env } from "cloudflare:workers";
import { eq, inArray } from "drizzle-orm";
import type { Env } from "~/types/env";
import { getDb } from "./db";
import { unreleasedTitlesTable } from "./schema";
export function addUnreleasedTitle(env: Env, titleId: number) {
export function addUnreleasedTitle(titleId: number) {
return getDb(env)
.insert(unreleasedTitlesTable)
.values({ titleId })
@@ -13,7 +12,7 @@ export function addUnreleasedTitle(env: Env, titleId: number) {
.run();
}
export function filterUnreleasedTitles(env: Env, titleIds: number[]) {
export function filterUnreleasedTitles(titleIds: number[]) {
return getDb(env)
.select({ titleId: unreleasedTitlesTable.titleId })
.from(unreleasedTitlesTable)
@@ -22,7 +21,7 @@ export function filterUnreleasedTitles(env: Env, titleIds: number[]) {
.then((results) => new Set(results.map(({ titleId }) => titleId)));
}
export function removeUnreleasedTitle(env: Env, titleId: number) {
export function removeUnreleasedTitle(titleId: number) {
return getDb(env)
.delete(unreleasedTitlesTable)
.where(eq(unreleasedTitlesTable.titleId, titleId))

View File

@@ -1,6 +1,6 @@
import { env } from "cloudflare:workers";
import { and, count, eq } from "drizzle-orm";
import type { Env } from "~/types/env";
import { WatchStatusValues } from "~/types/title/watchStatus";
import { getDb } from "./db";
@@ -13,7 +13,6 @@ import { watchStatusTable } from "./schema";
* - wasDeleted: whether any users are still watching the title
*/
export function setWatchStatus(
env: Env,
deviceId: string,
titleId: number,
watchStatus: (typeof WatchStatusValues)[number] | null,
@@ -21,9 +20,9 @@ export function setWatchStatus(
let dbAction;
const isSavingTitle = watchStatus === "CURRENT" || watchStatus === "PLANNING";
if (isSavingTitle) {
dbAction = saveTitle(env, deviceId, titleId);
dbAction = saveTitle(deviceId, titleId);
} else {
dbAction = removeTitle(env, deviceId, titleId);
dbAction = removeTitle(deviceId, titleId);
}
return dbAction
@@ -40,14 +39,14 @@ export function setWatchStatus(
}));
}
function saveTitle(env: Env, deviceId: string, titleId: number) {
function saveTitle(deviceId: string, titleId: number) {
return getDb(env)
.insert(watchStatusTable)
.values({ deviceId, titleId })
.onConflictDoNothing();
}
function removeTitle(env: Env, deviceId: string, titleId: number) {
function removeTitle(deviceId: string, titleId: number) {
return getDb(env)
.delete(watchStatusTable)
.where(
@@ -58,7 +57,7 @@ function removeTitle(env: Env, deviceId: string, titleId: number) {
);
}
export function isWatchingTitle(env: Env, titleId: number) {
export function isWatchingTitle(titleId: number) {
return getDb(env)
.select()
.from(watchStatusTable)

View File

@@ -112,13 +112,15 @@ async function triggerNextEpisodeRoute(titleId: number) {
}
return queueTask(
process.env,
"new-episode",
{
aniListId: titleId,
episodeNumber: title.nextAiringEpisode.episode,
},
{ scheduleConfig: { epochTime: title.nextAiringEpisode.airingAt } },
{
scheduleConfig: { epochTime: title.nextAiringEpisode.airingAt },
env: process.env,
},
)
.then(() => true)
.catch((error) => {

10
src/types/env.d.ts vendored
View File

@@ -1,10 +0,0 @@
import type { Env as HonoEnv } from "hono";
// Generated by Wrangler by running `wrangler types src/types/env.d.ts`
interface Env extends HonoEnv, Record<string, unknown> {
TURSO_URL: string;
TURSO_AUTH_TOKEN: string;
ENABLE_ANIFY: string;
ADMIN_SDK_JSON: string;
}