mirror of
https://github.com/michivonah/themepark-assistant.git
synced 2025-12-22 22:16:29 +01:00
improve error handling in background jobs #3
This commit is contained in:
parent
6a60e3c10a
commit
04ae271e1e
8 changed files with 75 additions and 27 deletions
49
api/src/errors/background-error.ts
Normal file
49
api/src/errors/background-error.ts
Normal file
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -3,6 +3,7 @@ import { getDbEnv } from '../db/client'
|
||||||
import { subscribedThemeparks, attractionSubscriptions } from "../db/schema";
|
import { subscribedThemeparks, attractionSubscriptions } from "../db/schema";
|
||||||
import { SubscribedThemeparks } from "../types/subscribed-themeparks";
|
import { SubscribedThemeparks } from "../types/subscribed-themeparks";
|
||||||
import { AttractionSubscription } from "../types/attraction-subscriptions";
|
import { AttractionSubscription } from "../types/attraction-subscriptions";
|
||||||
|
import { KVParseError, SendNotificationError } from "../errors/background-error";
|
||||||
import httpRequest from "../lib/http-request";
|
import httpRequest from "../lib/http-request";
|
||||||
import fetchAttractions from "../lib/fetch-attractions";
|
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.
|
* waittime in cache & send notification about changes in waittime.
|
||||||
* @param env Connection to Cloudflare
|
* @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<void>{
|
export default async function updateWaittimes(env: Env): Promise<void>{
|
||||||
const db = getDbEnv(env);
|
const db = getDbEnv(env);
|
||||||
const subscribedParks = await db.select().from(subscribedThemeparks);
|
const subscribedParks = await db.select().from(subscribedThemeparks);
|
||||||
|
|
@ -54,7 +56,7 @@ async function getJsonFromKV<T>(env: Env, key: string, defaultValue: T): Promise
|
||||||
return JSON.parse(cache) as T;
|
return JSON.parse(cache) as T;
|
||||||
}
|
}
|
||||||
catch(e){
|
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){
|
catch(e){
|
||||||
throw new Error(`Failed to send notification: ${e}`);
|
throw new SendNotificationError(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import { attraction, themepark } from '../db/schema'
|
||||||
import { inArray } from 'drizzle-orm'
|
import { inArray } from 'drizzle-orm'
|
||||||
import { Attraction } from '../types/attraction'
|
import { Attraction } from '../types/attraction'
|
||||||
import { ThemeparkSelect } from '../types/themepark'
|
import { ThemeparkSelect } from '../types/themepark'
|
||||||
|
import { AttractionImportError, BackgroundDatabaseError } from '../errors/background-error'
|
||||||
import asyncBatchJob from '../lib/async-batch-job'
|
import asyncBatchJob from '../lib/async-batch-job'
|
||||||
import fetchAttractions from '../lib/fetch-attractions'
|
import fetchAttractions from '../lib/fetch-attractions'
|
||||||
|
|
||||||
|
|
@ -24,7 +25,7 @@ async function getThemeparks(env: Env): Promise<ThemeparkAPI[]>{
|
||||||
return themeparks;
|
return themeparks;
|
||||||
}
|
}
|
||||||
catch(e){
|
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<A
|
||||||
return attractions;
|
return attractions;
|
||||||
}
|
}
|
||||||
catch(e){
|
catch(e){
|
||||||
throw new Error(`Failed to get attractions from database: ${e}`);
|
throw new BackgroundDatabaseError(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -86,7 +87,7 @@ async function importAttractionsByParks(env: Env, parks: ThemeparkAPI[]): Promis
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch(e){
|
catch(e){
|
||||||
throw new Error(`Failed to import attractions into database: ${e}`);
|
throw new AttractionImportError(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -99,7 +100,6 @@ async function importAttractionsByParks(env: Env, parks: ThemeparkAPI[]): Promis
|
||||||
* @param cron The cron statement specified to run the background jobs; used for batch size calculation
|
* @param cron The cron statement specified to run the background jobs; used for batch size calculation
|
||||||
*/
|
*/
|
||||||
export async function batchAttractionImport(env: Env, timestamp: number, cron: string): Promise<void>{
|
export async function batchAttractionImport(env: Env, timestamp: number, cron: string): Promise<void>{
|
||||||
try{
|
|
||||||
const themeparks = await getThemeparks(env); // all themeparks
|
const themeparks = await getThemeparks(env); // all themeparks
|
||||||
const executionHour = new Date(timestamp).getUTCHours(); // current hour, in which job is executed
|
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 executionTimes = getExecutionCountFromCron(cron, 1); // how often the job is executed
|
||||||
|
|
@ -116,10 +116,6 @@ export async function batchAttractionImport(env: Env, timestamp: number, cron: s
|
||||||
|
|
||||||
// import attractions from current time batch
|
// import attractions from current time batch
|
||||||
await importAttractionsByParks(env, batch);
|
await importAttractionsByParks(env, batch);
|
||||||
}
|
|
||||||
catch(e){
|
|
||||||
throw new Error(`Failed to split attraction import by time: ${e}`);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import { getDbEnv } from '../db/client'
|
import { getDbEnv } from '../db/client'
|
||||||
import { themepark } from '../db/schema'
|
import { themepark } from '../db/schema'
|
||||||
import { countryCodesDE } from '../lib/countries'
|
import { countryCodesDE } from '../lib/countries'
|
||||||
|
import { BackgroundFetchError, ThemeparkUpdateError } from '../errors/background-error'
|
||||||
import httpRequest from '../lib/http-request'
|
import httpRequest from '../lib/http-request'
|
||||||
import asyncBatchJob from '../lib/async-batch-job'
|
import asyncBatchJob from '../lib/async-batch-job'
|
||||||
|
|
||||||
|
|
@ -30,7 +31,7 @@ async function fetchThemeparks(
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
catch(e){
|
catch(e){
|
||||||
throw new Error(`Fetching themeparks failed: ${e}`);
|
throw new BackgroundFetchError(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -67,6 +68,6 @@ export async function updateThemeparkData(env: Env): Promise<void>{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch(e){
|
catch(e){
|
||||||
console.error(`Failed to update themepark data: ${e}`);
|
throw new ThemeparkUpdateError(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -4,7 +4,7 @@ import { UserSelect } from "../types/user";
|
||||||
import { user } from "../db/schema";
|
import { user } from "../db/schema";
|
||||||
import { like } from "drizzle-orm";
|
import { like } from "drizzle-orm";
|
||||||
import { DrizzleD1Database } from "drizzle-orm/d1";
|
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
|
* Returns the details of a user from the given context
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import { Hono, Context } from 'hono'
|
||||||
import { getDbContext } from '../db/client'
|
import { getDbContext } from '../db/client'
|
||||||
import { attractionNotification, notificationMethod } from '../db/schema'
|
import { attractionNotification, notificationMethod } from '../db/schema'
|
||||||
import { and, eq } from 'drizzle-orm'
|
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 { getUser } from '../lib/user-auth'
|
||||||
import { Message } from '../types/response'
|
import { Message } from '../types/response'
|
||||||
import { NotificationMethodSelect } from '../types/notification-method'
|
import { NotificationMethodSelect } from '../types/notification-method'
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import { getDbContext } from '../db/client'
|
||||||
import { themepark, attraction } from '../db/schema'
|
import { themepark, attraction } from '../db/schema'
|
||||||
import { responseCache } from '../lib/cache'
|
import { responseCache } from '../lib/cache'
|
||||||
import { eq } from 'drizzle-orm'
|
import { eq } from 'drizzle-orm'
|
||||||
import { DatabaseError } from '../types/error'
|
import { DatabaseError } from '../errors/http-error'
|
||||||
|
|
||||||
const app = new Hono()
|
const app = new Hono()
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue