diff --git a/api/src/errors/background-error.ts b/api/src/errors/background-error.ts new file mode 100644 index 0000000..338a5ad --- /dev/null +++ b/api/src/errors/background-error.ts @@ -0,0 +1,49 @@ +// Error classes for background jobs + +// Class for custom background errors +export class BackgroundJobError extends Error{ + cause?: unknown; + + constructor(message: string, cause?: unknown){ + super(message); + this.name = this.constructor.name; + this.cause = cause; + } +} + +// Errors based on class BackgroundJobError +export class BackgroundDatabaseError extends BackgroundJobError{ + constructor(cause?: unknown){ + super('Database request failed.', cause); + } +} + +export class BackgroundFetchError extends BackgroundJobError{ + constructor(cause?: unknown){ + super('Fetching data failed', cause); + } +} + +export class AttractionImportError extends BackgroundJobError{ + constructor(cause?: unknown){ + super('Failed to import attractions into database.', cause); + } +} + +export class ThemeparkUpdateError extends BackgroundJobError{ + constructor(cause?: unknown){ + super('Failed to update themepark data.', cause); + } +} + +export class KVParseError extends BackgroundJobError{ + constructor(key: string, cause?: unknown){ + super(`Failed to parse JSON from KV, affected key: ${key}`, cause); + } +} + +export class SendNotificationError extends BackgroundJobError{ + constructor(cause?: unknown){ + super('Failed to send notification.', cause); + } +} \ No newline at end of file diff --git a/api/src/types/error.ts b/api/src/errors/http-error.ts similarity index 100% rename from api/src/types/error.ts rename to api/src/errors/http-error.ts diff --git a/api/src/jobs/send-notifications.ts b/api/src/jobs/send-notifications.ts index d3ee432..af7c610 100644 --- a/api/src/jobs/send-notifications.ts +++ b/api/src/jobs/send-notifications.ts @@ -3,6 +3,7 @@ import { getDbEnv } from '../db/client' import { subscribedThemeparks, attractionSubscriptions } from "../db/schema"; import { SubscribedThemeparks } from "../types/subscribed-themeparks"; import { AttractionSubscription } from "../types/attraction-subscriptions"; +import { KVParseError, SendNotificationError } from "../errors/background-error"; import httpRequest from "../lib/http-request"; import fetchAttractions from "../lib/fetch-attractions"; @@ -11,6 +12,7 @@ import fetchAttractions from "../lib/fetch-attractions"; * waittime in cache & send notification about changes in waittime. * @param env Connection to Cloudflare */ +// TODO: split into batches by cron, when more than 10 parks have to be fetched -> like update-attraction-list.ts export default async function updateWaittimes(env: Env): Promise{ const db = getDbEnv(env); const subscribedParks = await db.select().from(subscribedThemeparks); @@ -54,7 +56,7 @@ async function getJsonFromKV(env: Env, key: string, defaultValue: T): Promise return JSON.parse(cache) as T; } catch(e){ - throw new Error(`Failed to parse JSON from KV, affected key: ${key}, error: ${e}`); + throw new KVParseError(key, e); } } @@ -131,7 +133,7 @@ async function sendNotification(webhookUrl: string, message: string, type: strin }); } catch(e){ - throw new Error(`Failed to send notification: ${e}`); + throw new SendNotificationError(e); } } diff --git a/api/src/jobs/update-attraction-list.ts b/api/src/jobs/update-attraction-list.ts index 0a9c38b..eddfeba 100644 --- a/api/src/jobs/update-attraction-list.ts +++ b/api/src/jobs/update-attraction-list.ts @@ -3,6 +3,7 @@ import { attraction, themepark } from '../db/schema' import { inArray } from 'drizzle-orm' import { Attraction } from '../types/attraction' import { ThemeparkSelect } from '../types/themepark' +import { AttractionImportError, BackgroundDatabaseError } from '../errors/background-error' import asyncBatchJob from '../lib/async-batch-job' import fetchAttractions from '../lib/fetch-attractions' @@ -24,7 +25,7 @@ async function getThemeparks(env: Env): Promise{ return themeparks; } catch(e){ - throw new Error(`Failed to get themeparks from database: ${e}`); + throw new BackgroundDatabaseError(e); } } @@ -50,7 +51,7 @@ async function getAttractionsByParks(env: Env, parks: ThemeparkAPI[]): Promise{ - try{ - const themeparks = await getThemeparks(env); // all themeparks - const executionHour = new Date(timestamp).getUTCHours(); // current hour, in which job is executed - const executionTimes = getExecutionCountFromCron(cron, 1); // how often the job is executed + const themeparks = await getThemeparks(env); // all themeparks + const executionHour = new Date(timestamp).getUTCHours(); // current hour, in which job is executed + const executionTimes = getExecutionCountFromCron(cron, 1); // how often the job is executed - const batchSize = Math.ceil(themeparks.length / executionTimes); // calculate batch size, so that each park gets updated - const hourIndex = executionHour - 1; + const batchSize = Math.ceil(themeparks.length / executionTimes); // calculate batch size, so that each park gets updated + const hourIndex = executionHour - 1; - // time examples - // 01:00 -> 1757811600000 - // 02:00 -> 1757815200000 - // 03:00 -> 1757818800000 + // time examples + // 01:00 -> 1757811600000 + // 02:00 -> 1757815200000 + // 03:00 -> 1757818800000 - const batch = themeparks.slice(hourIndex * batchSize, (hourIndex + 1) * batchSize); // slice array into right batch + const batch = themeparks.slice(hourIndex * batchSize, (hourIndex + 1) * batchSize); // slice array into right batch - // import attractions from current time batch - await importAttractionsByParks(env, batch); - } - catch(e){ - throw new Error(`Failed to split attraction import by time: ${e}`); - } + // import attractions from current time batch + await importAttractionsByParks(env, batch); } /** diff --git a/api/src/jobs/update-themepark-data.ts b/api/src/jobs/update-themepark-data.ts index 025641a..daec8de 100644 --- a/api/src/jobs/update-themepark-data.ts +++ b/api/src/jobs/update-themepark-data.ts @@ -1,6 +1,7 @@ import { getDbEnv } from '../db/client' import { themepark } from '../db/schema' import { countryCodesDE } from '../lib/countries' +import { BackgroundFetchError, ThemeparkUpdateError } from '../errors/background-error' import httpRequest from '../lib/http-request' import asyncBatchJob from '../lib/async-batch-job' @@ -30,7 +31,7 @@ async function fetchThemeparks( return result; } catch(e){ - throw new Error(`Fetching themeparks failed: ${e}`); + throw new BackgroundFetchError(e); } } @@ -67,6 +68,6 @@ export async function updateThemeparkData(env: Env): Promise{ } } catch(e){ - console.error(`Failed to update themepark data: ${e}`); + throw new ThemeparkUpdateError(e); } } \ No newline at end of file diff --git a/api/src/lib/user-auth.ts b/api/src/lib/user-auth.ts index fbc0ae9..f71b586 100644 --- a/api/src/lib/user-auth.ts +++ b/api/src/lib/user-auth.ts @@ -4,7 +4,7 @@ import { UserSelect } from "../types/user"; import { user } from "../db/schema"; import { like } from "drizzle-orm"; import { DrizzleD1Database } from "drizzle-orm/d1"; -import { MissingMailError, UserInactiveError, DatabaseError } from "../types/error"; +import { MissingMailError, UserInactiveError, DatabaseError } from "../errors/http-error"; /** * Returns the details of a user from the given context diff --git a/api/src/routes/attraction.ts b/api/src/routes/attraction.ts index f523f54..291d429 100644 --- a/api/src/routes/attraction.ts +++ b/api/src/routes/attraction.ts @@ -2,7 +2,7 @@ import { Hono, Context } from 'hono' import { getDbContext } from '../db/client' import { attractionNotification, notificationMethod } from '../db/schema' import { and, eq } from 'drizzle-orm' -import { DatabaseError, InvalidParameter, MissingParameter } from '../types/error' +import { DatabaseError, InvalidParameter, MissingParameter } from '../errors/http-error' import { getUser } from '../lib/user-auth' import { Message } from '../types/response' import { NotificationMethodSelect } from '../types/notification-method' diff --git a/api/src/routes/themepark.ts b/api/src/routes/themepark.ts index 49d3060..c1b4d03 100644 --- a/api/src/routes/themepark.ts +++ b/api/src/routes/themepark.ts @@ -3,7 +3,7 @@ import { getDbContext } from '../db/client' import { themepark, attraction } from '../db/schema' import { responseCache } from '../lib/cache' import { eq } from 'drizzle-orm' -import { DatabaseError } from '../types/error' +import { DatabaseError } from '../errors/http-error' const app = new Hono()