diff --git a/README.md b/README.md index ded9be1..3413738 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,50 @@ # themepark-assistant A tool for improving your trips to themeparks - once developed -## Testing -Send request +## Repo structure +- /api: API implementation + - ./: config files + - /src: API Code + - /db: Database client, schema & migrations + - /errors: Error types + - index.ts: Exporter for all error classes + - /jobs: Background tasks + - /lib: Reusable functions + - /routes: API Endpoints + - /types: Data type definitions + - index.ts: Entrypoint for API Requests & background tasks on Cloudflare Workers + +## Development +### Run enviromnment +Run worker locally (without remote d1 access, scheduled tasks not available) +```bash +npx wrangler dev +``` + +Run worker locally (without remote d1 access, scheduled tasks available) +```bash +npx wrangler dev --test-scheduled +``` + +Run worker locally (with remote connection to d1, scheduled tasks available) +```bash +npx wrangler dev --remote --test-scheduled +``` + +### Requests +Send request with bearer authentication ```bash curl -H "Authorization: Bearer insecure-token" http://127.0.0.1:8787/notification/list ``` -## Update cloudflare d1 db +Run request with cron expression (for executing background tasks) +```bash +curl "http://localhost:8787/__scheduled?cron=*+*+*+*+*" +``` + +### Drizzle DB migrations +Update cloudflare d1 db DB scheme is defined in typescript apply changes @@ -21,35 +57,25 @@ export sql statements instead of running migration npx drizzle-kit export --config=drizzle-dev.config.ts ``` -## SQLite / D1 +### Useful sql statements for SQLite / D1 Delete view ```sql DROP VIEW IF EXISTS attraction_subscriptions; ``` -## Cloudflare workers tricks +### Cloudflare workers tricks If types are missing, run: ```bash npx wrangler types ``` -## Testing cronjobs -Run worker locally (without remote d1 access) -```bash -npx wrangler dev --test-scheduled -``` - -Run worker locally (with remote connection to d1) -```bash -npx wrangler dev --remote --test-scheduled -``` - -Run curl request with cron expression -```bash -curl "http://localhost:8787/__scheduled?cron=*+*+*+*+*" -``` - -## Authentication endpoints +## Authentication endpoints (auth.js) - /auth/signin -> Login - /auth/signout -> Logout -- /auth/callback/github -> Callback for GitHub OAuth config \ No newline at end of file +- /auth/callback/github -> Callback for GitHub OAuth config + +## Contributing +TBD + +## License +TBD \ No newline at end of file diff --git a/api/src/errors/background-error.ts b/api/src/errors/background-error.ts index 338a5ad..d05207f 100644 --- a/api/src/errors/background-error.ts +++ b/api/src/errors/background-error.ts @@ -1,15 +1,8 @@ // Error classes for background jobs +import { BaseError } from "./base-error"; // 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; - } -} +export class BackgroundJobError extends BaseError{} // Errors based on class BackgroundJobError export class BackgroundDatabaseError extends BackgroundJobError{ @@ -18,12 +11,6 @@ export class BackgroundDatabaseError extends BackgroundJobError{ } } -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); diff --git a/api/src/errors/base-error.ts b/api/src/errors/base-error.ts new file mode 100644 index 0000000..3e17ce4 --- /dev/null +++ b/api/src/errors/base-error.ts @@ -0,0 +1,10 @@ +// Base for new error domains/classes +export class BaseError extends Error{ + cause?: unknown; + + constructor(message: string, cause?: unknown){ + super(message); + this.name = this.constructor.name; + this.cause = cause; + } +} \ No newline at end of file diff --git a/api/src/errors/index.ts b/api/src/errors/index.ts new file mode 100644 index 0000000..4eaca3f --- /dev/null +++ b/api/src/errors/index.ts @@ -0,0 +1,3 @@ +export * from './background-error' +export * from './http-error' +export * from './lib-error' \ No newline at end of file diff --git a/api/src/errors/lib-error.ts b/api/src/errors/lib-error.ts new file mode 100644 index 0000000..2cad4da --- /dev/null +++ b/api/src/errors/lib-error.ts @@ -0,0 +1,26 @@ +// Errors in custom libs +import { BaseError } from "./base-error"; + +export class LibError extends BaseError{} + +export class FetchError extends LibError{ + constructor(cause?: unknown, source?: string){ + super( + source + ? `Fetching data from ${source} failed` + : 'Fetching data failed', + cause); + } +} + +export class BatchExecutionError extends LibError{ + constructor(cause?: unknown){ + super('Batched execution failed.', cause); + } +} + +export class HTTPError extends LibError{ + constructor(errorCode: number, cause?: unknown){ + super(`Received HTTP error code: ${errorCode}`, cause); + } +} \ No newline at end of file diff --git a/api/src/jobs/send-notifications.ts b/api/src/jobs/send-notifications.ts index af7c610..11fa89a 100644 --- a/api/src/jobs/send-notifications.ts +++ b/api/src/jobs/send-notifications.ts @@ -3,7 +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 { KVParseError, SendNotificationError } from "../errors"; import httpRequest from "../lib/http-request"; import fetchAttractions from "../lib/fetch-attractions"; diff --git a/api/src/jobs/update-attraction-list.ts b/api/src/jobs/update-attraction-list.ts index eddfeba..0fed46b 100644 --- a/api/src/jobs/update-attraction-list.ts +++ b/api/src/jobs/update-attraction-list.ts @@ -3,7 +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 { AttractionImportError, BackgroundDatabaseError } from '../errors' import asyncBatchJob from '../lib/async-batch-job' import fetchAttractions from '../lib/fetch-attractions' diff --git a/api/src/jobs/update-themepark-data.ts b/api/src/jobs/update-themepark-data.ts index daec8de..44d8ca9 100644 --- a/api/src/jobs/update-themepark-data.ts +++ b/api/src/jobs/update-themepark-data.ts @@ -1,7 +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 { FetchError, ThemeparkUpdateError } from '../errors' import httpRequest from '../lib/http-request' import asyncBatchJob from '../lib/async-batch-job' @@ -31,7 +31,7 @@ async function fetchThemeparks( return result; } catch(e){ - throw new BackgroundFetchError(e); + throw new FetchError(e); } } diff --git a/api/src/lib/async-batch-job.ts b/api/src/lib/async-batch-job.ts index f41818c..a9e84ca 100644 --- a/api/src/lib/async-batch-job.ts +++ b/api/src/lib/async-batch-job.ts @@ -1,3 +1,5 @@ +import { BatchExecutionError } from "../errors"; + /** * Run any async operation in (multiple) batches * @param data Array to split into batches @@ -12,6 +14,6 @@ export default async function asyncBatchJob(data: T[], batchSize: number = 20 } } catch(e){ - throw new Error(`Batch execution failed: ${e}`); + throw new BatchExecutionError(e); } } \ No newline at end of file diff --git a/api/src/lib/fetch-attractions.ts b/api/src/lib/fetch-attractions.ts index c83e7e1..53a3950 100644 --- a/api/src/lib/fetch-attractions.ts +++ b/api/src/lib/fetch-attractions.ts @@ -1,5 +1,6 @@ import { AttractionImport } from '../types/attraction' import httpRequest from '../lib/http-request' +import { FetchError } from '../errors'; /** * Fetching the attractions from a specified park @@ -25,6 +26,6 @@ export default async function fetchAttractions( return result; } catch(e){ - throw new Error(`Failed to fetch attractions: ${e}`); + throw new FetchError(e, endpoint); } } \ No newline at end of file diff --git a/api/src/lib/http-request.ts b/api/src/lib/http-request.ts index 47267e2..fdc0aea 100644 --- a/api/src/lib/http-request.ts +++ b/api/src/lib/http-request.ts @@ -1,3 +1,5 @@ +import { HTTPError } from "../errors"; + /** * Executes a HTTP Request with option for custom header & body parameters * @param endpoint Request endpoint @@ -26,7 +28,7 @@ export default async function httpRequest( if (!response.ok){ - throw new Error(`HTTP error! Status: ${response.status}`); + throw new HTTPError(response.status); } if(response.status === 204){ diff --git a/api/src/lib/user-auth.ts b/api/src/lib/user-auth.ts index f71b586..550be54 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 "../errors/http-error"; +import { MissingMailError, UserInactiveError, DatabaseError } from "../errors"; /** * 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 291d429..bf59d49 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 '../errors/http-error' +import { DatabaseError, InvalidParameter, MissingParameter } from '../errors' 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 c1b4d03..7bd89b9 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 '../errors/http-error' +import { DatabaseError } from '../errors' const app = new Hono()