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:
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
},
|
||||
|
||||
@@ -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(),
|
||||
);
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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],
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
10
src/types/env.d.ts
vendored
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user