mirror of
https://github.com/michivonah/themepark-assistant.git
synced 2025-12-22 22:16:29 +01:00
Compare commits
3 commits
04ae271e1e
...
60a75f7894
| Author | SHA1 | Date | |
|---|---|---|---|
| 60a75f7894 | |||
| 6bb5037eae | |||
| 6b61bd0df3 |
14 changed files with 105 additions and 48 deletions
72
README.md
72
README.md
|
|
@ -1,14 +1,50 @@
|
||||||
# themepark-assistant
|
# themepark-assistant
|
||||||
A tool for improving your trips to themeparks - once developed
|
A tool for improving your trips to themeparks - once developed
|
||||||
|
|
||||||
## Testing
|
## Repo structure
|
||||||
Send request
|
- /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
|
```bash
|
||||||
curl -H "Authorization: Bearer insecure-token" http://127.0.0.1:8787/notification/list
|
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
|
DB scheme is defined in typescript
|
||||||
|
|
||||||
apply changes
|
apply changes
|
||||||
|
|
@ -21,35 +57,25 @@ export sql statements instead of running migration
|
||||||
npx drizzle-kit export --config=drizzle-dev.config.ts
|
npx drizzle-kit export --config=drizzle-dev.config.ts
|
||||||
```
|
```
|
||||||
|
|
||||||
## SQLite / D1
|
### Useful sql statements for SQLite / D1
|
||||||
Delete view
|
Delete view
|
||||||
```sql
|
```sql
|
||||||
DROP VIEW IF EXISTS attraction_subscriptions;
|
DROP VIEW IF EXISTS attraction_subscriptions;
|
||||||
```
|
```
|
||||||
|
|
||||||
## Cloudflare workers tricks
|
### Cloudflare workers tricks
|
||||||
If types are missing, run:
|
If types are missing, run:
|
||||||
```bash
|
```bash
|
||||||
npx wrangler types
|
npx wrangler types
|
||||||
```
|
```
|
||||||
|
|
||||||
## Testing cronjobs
|
## Authentication endpoints (auth.js)
|
||||||
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
|
|
||||||
- /auth/signin -> Login
|
- /auth/signin -> Login
|
||||||
- /auth/signout -> Logout
|
- /auth/signout -> Logout
|
||||||
- /auth/callback/github -> Callback for GitHub OAuth config
|
- /auth/callback/github -> Callback for GitHub OAuth config
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
TBD
|
||||||
|
|
||||||
|
## License
|
||||||
|
TBD
|
||||||
|
|
@ -1,15 +1,8 @@
|
||||||
// Error classes for background jobs
|
// Error classes for background jobs
|
||||||
|
import { BaseError } from "./base-error";
|
||||||
|
|
||||||
// Class for custom background errors
|
// Class for custom background errors
|
||||||
export class BackgroundJobError extends Error{
|
export class BackgroundJobError extends BaseError{}
|
||||||
cause?: unknown;
|
|
||||||
|
|
||||||
constructor(message: string, cause?: unknown){
|
|
||||||
super(message);
|
|
||||||
this.name = this.constructor.name;
|
|
||||||
this.cause = cause;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Errors based on class BackgroundJobError
|
// Errors based on class BackgroundJobError
|
||||||
export class BackgroundDatabaseError extends 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{
|
export class AttractionImportError extends BackgroundJobError{
|
||||||
constructor(cause?: unknown){
|
constructor(cause?: unknown){
|
||||||
super('Failed to import attractions into database.', cause);
|
super('Failed to import attractions into database.', cause);
|
||||||
|
|
|
||||||
10
api/src/errors/base-error.ts
Normal file
10
api/src/errors/base-error.ts
Normal file
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
3
api/src/errors/index.ts
Normal file
3
api/src/errors/index.ts
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
export * from './background-error'
|
||||||
|
export * from './http-error'
|
||||||
|
export * from './lib-error'
|
||||||
26
api/src/errors/lib-error.ts
Normal file
26
api/src/errors/lib-error.ts
Normal file
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -3,7 +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 { KVParseError, SendNotificationError } from "../errors";
|
||||||
import httpRequest from "../lib/http-request";
|
import httpRequest from "../lib/http-request";
|
||||||
import fetchAttractions from "../lib/fetch-attractions";
|
import fetchAttractions from "../lib/fetch-attractions";
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,7 +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 { AttractionImportError, BackgroundDatabaseError } from '../errors'
|
||||||
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'
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +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 { FetchError, ThemeparkUpdateError } from '../errors'
|
||||||
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'
|
||||||
|
|
||||||
|
|
@ -31,7 +31,7 @@ async function fetchThemeparks(
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
catch(e){
|
catch(e){
|
||||||
throw new BackgroundFetchError(e);
|
throw new FetchError(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
import { BatchExecutionError } from "../errors";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Run any async operation in (multiple) batches
|
* Run any async operation in (multiple) batches
|
||||||
* @param data Array to split into batches
|
* @param data Array to split into batches
|
||||||
|
|
@ -12,6 +14,6 @@ export default async function asyncBatchJob<T>(data: T[], batchSize: number = 20
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch(e){
|
catch(e){
|
||||||
throw new Error(`Batch execution failed: ${e}`);
|
throw new BatchExecutionError(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import { AttractionImport } from '../types/attraction'
|
import { AttractionImport } from '../types/attraction'
|
||||||
import httpRequest from '../lib/http-request'
|
import httpRequest from '../lib/http-request'
|
||||||
|
import { FetchError } from '../errors';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetching the attractions from a specified park
|
* Fetching the attractions from a specified park
|
||||||
|
|
@ -25,6 +26,6 @@ export default async function fetchAttractions(
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
catch(e){
|
catch(e){
|
||||||
throw new Error(`Failed to fetch attractions: ${e}`);
|
throw new FetchError(e, endpoint);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
import { HTTPError } from "../errors";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Executes a HTTP Request with option for custom header & body parameters
|
* Executes a HTTP Request with option for custom header & body parameters
|
||||||
* @param endpoint Request endpoint
|
* @param endpoint Request endpoint
|
||||||
|
|
@ -26,7 +28,7 @@ export default async function httpRequest<TResponse, TBody = undefined>(
|
||||||
|
|
||||||
|
|
||||||
if (!response.ok){
|
if (!response.ok){
|
||||||
throw new Error(`HTTP error! Status: ${response.status}`);
|
throw new HTTPError(response.status);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(response.status === 204){
|
if(response.status === 204){
|
||||||
|
|
|
||||||
|
|
@ -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 "../errors/http-error";
|
import { MissingMailError, UserInactiveError, DatabaseError } from "../errors";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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 '../errors/http-error'
|
import { DatabaseError, InvalidParameter, MissingParameter } from '../errors'
|
||||||
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 '../errors/http-error'
|
import { DatabaseError } from '../errors'
|
||||||
|
|
||||||
const app = new Hono()
|
const app = new Hono()
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue