diff --git a/api/src/db/schema.ts b/api/src/db/schema.ts index 297783b..90614c1 100644 --- a/api/src/db/schema.ts +++ b/api/src/db/schema.ts @@ -1,5 +1,7 @@ -import { integer, text, sqliteTable } from "drizzle-orm/sqlite-core"; +import { integer, text, sqliteTable, sqliteView } from "drizzle-orm/sqlite-core"; +import { eq, sql } from "drizzle-orm"; +// Tables export const attraction = sqliteTable('attraction', { id: integer().primaryKey({ autoIncrement: true }), name: text().notNull(), @@ -23,7 +25,7 @@ export const logbook = sqliteTable('logbook', { realWaittime: integer() }) -export const notificationMethod = sqliteTable('notification', { +export const notificationMethod = sqliteTable('notification_method', { id: integer().primaryKey({ autoIncrement: true }), webhookUrl: text().notNull(), shownName: text().notNull(), @@ -42,4 +44,24 @@ export const user = sqliteTable('user', { id: integer().primaryKey({ autoIncrement: true }), username: text().notNull(), isActive: integer({ mode: 'boolean' }).default(false) -}) \ No newline at end of file +}) + +// Views +export const subscribedThemeparks = sqliteView('subscribed_themeparks').as((qb) => + qb.selectDistinct({ + apiName: sql`themepark.api_name`.as('api_name') + }).from(attractionNotification) + .innerJoin(attraction, eq(attractionNotification.attractionId, attraction.id)) + .innerJoin(themepark, eq(attraction.themeparkId, themepark.id)) +); + +export const attractionSubscriptions = sqliteView('attraction_subscriptions').as((qb) => + qb.selectDistinct({ + attractionApiCode: sql`attraction.api_code`.as('attraction_api_code'), + themeparkApiName: sql`themepark.api_name`.as('themepark_api_name'), + webhookUrl: sql`notification_method.webhook_url`.as('webhook_url') + }).from(attractionNotification) + .innerJoin(attraction, eq(attractionNotification.attractionId, attraction.id)) + .innerJoin(themepark, eq(attraction.themeparkId, themepark.id)) + .innerJoin(notificationMethod, eq(attractionNotification.notificationMethodId, notificationMethod.id)) +); \ No newline at end of file diff --git a/api/src/jobs/update-attraction-list.ts b/api/src/jobs/update-attraction-list.ts index a97f8c7..0a9c38b 100644 --- a/api/src/jobs/update-attraction-list.ts +++ b/api/src/jobs/update-attraction-list.ts @@ -1,62 +1,22 @@ import { getDbEnv } from '../db/client' import { attraction, themepark } from '../db/schema' import { inArray } from 'drizzle-orm' -import httpRequest from '../lib/http-request' +import { Attraction } from '../types/attraction' +import { ThemeparkSelect } from '../types/themepark' import asyncBatchJob from '../lib/async-batch-job' +import fetchAttractions from '../lib/fetch-attractions' -interface AttractionImport { - code: string, - name: string, -} - -interface AttractionType { - name: string, - apiCode: string, - themeparkId: number -} - -interface Themepark { - apiName: string, - id: number -} - -/** - * Fetching the attractions from a specified park - * @param park API Code for request themepark - * @param endpoint Endpoint where to fetch data from (default: https://api.wartezeiten.app/v1/parks) - * @param lang Language used for API request - * @returns Interface with attraction code & name - */ -async function fetchAttractions( - park: string, - endpoint: string = "https://api.wartezeiten.app/v1/parks", - lang: string = 'de' -): Promise{ - try{ - const headers = { - 'language':lang, - 'park':park - }; - - const result = await httpRequest(endpoint, { - headers: headers - }); - return result; - } - catch(e){ - throw new Error(`Failed to fetch attractions: ${e}`); - } -} +type ThemeparkAPI = Pick; /** * Return an object of all themeparks saved in the database * @param env DB Connection * @returns Object of themeparks with api name & id from internal DB */ -async function getThemeparks(env: Env): Promise{ +async function getThemeparks(env: Env): Promise{ try{ const db = getDbEnv(env); - const themeparks: Themepark[] = await db.select({ + const themeparks: ThemeparkAPI[] = await db.select({ apiName: themepark.apiName, id: themepark.id }).from(themepark); @@ -74,13 +34,13 @@ async function getThemeparks(env: Env): Promise{ * @param parks Object of themeparks to get attractions from * @returns Object of attractions */ -async function getAttractionsByParks(env: Env, parks: Themepark[]): Promise{ +async function getAttractionsByParks(env: Env, parks: ThemeparkAPI[]): Promise{ try{ const db = getDbEnv(env); const parkIds: number[] = parks.map(p => p.id); - const attractions: AttractionType[] = await db.select({ + const attractions: Attraction[] = await db.select({ name: attraction.name, apiCode: attraction.apiCode, themeparkId: attraction.themeparkId @@ -100,7 +60,7 @@ async function getAttractionsByParks(env: Env, parks: Themepark[]): Promise{ +async function importAttractionsByParks(env: Env, parks: ThemeparkAPI[]): Promise{ try{ const db = getDbEnv(env); diff --git a/api/src/lib/fetch-attractions.ts b/api/src/lib/fetch-attractions.ts new file mode 100644 index 0000000..c83e7e1 --- /dev/null +++ b/api/src/lib/fetch-attractions.ts @@ -0,0 +1,30 @@ +import { AttractionImport } from '../types/attraction' +import httpRequest from '../lib/http-request' + +/** + * Fetching the attractions from a specified park + * @param park API Code for request themepark + * @param endpoint Endpoint where to fetch data from (default: https://api.wartezeiten.app/v1/parks) + * @param lang Language used for API request + * @returns Interface with attraction code & name + */ +export default async function fetchAttractions( + park: string, + endpoint: string = "https://api.wartezeiten.app/v1/waitingtimes", + lang: string = 'de' +): Promise{ + try{ + const headers = { + 'language':lang, + 'park':park + }; + + const result = await httpRequest(endpoint, { + headers: headers + }); + return result; + } + catch(e){ + throw new Error(`Failed to fetch attractions: ${e}`); + } +} \ No newline at end of file diff --git a/api/src/types/attractionNotification.ts b/api/src/types/attraction-notification.ts similarity index 100% rename from api/src/types/attractionNotification.ts rename to api/src/types/attraction-notification.ts diff --git a/api/src/types/attraction-subscriptions.ts b/api/src/types/attraction-subscriptions.ts new file mode 100644 index 0000000..a5e6b70 --- /dev/null +++ b/api/src/types/attraction-subscriptions.ts @@ -0,0 +1,3 @@ +import { attractionSubscriptions } from '../db/schema'; + +export type AttractionSubscription = typeof attractionSubscriptions.$inferSelect; \ No newline at end of file diff --git a/api/src/types/attraction.ts b/api/src/types/attraction.ts index 11c4438..0d76170 100644 --- a/api/src/types/attraction.ts +++ b/api/src/types/attraction.ts @@ -2,4 +2,21 @@ import { type InferSelectModel, type InferInsertModel } from 'drizzle-orm'; import { attraction } from '../db/schema'; export type Attraction = InferInsertModel -export type AttractionSelect = InferSelectModel \ No newline at end of file +export type AttractionSelect = InferSelectModel + +// API Response format +export interface AttractionImport { + code: string, + name: string, + waitingtime: number, + status: "opened" | "virtualqueue" | "maintenance" | "closedice" | "closedweather" | "closed" +} + +// Waittime comparison +export interface AttractionChanges { + apiCode: string, + name: string, + waittime: number, + hasChanged: boolean, + increased: boolean +} \ No newline at end of file diff --git a/api/src/types/notificationMethod.ts b/api/src/types/notification-method.ts similarity index 100% rename from api/src/types/notificationMethod.ts rename to api/src/types/notification-method.ts diff --git a/api/src/types/subscribed-themeparks.ts b/api/src/types/subscribed-themeparks.ts new file mode 100644 index 0000000..085c524 --- /dev/null +++ b/api/src/types/subscribed-themeparks.ts @@ -0,0 +1,3 @@ +import { subscribedThemeparks } from '../db/schema'; + +export type SubscribedThemeparks = typeof subscribedThemeparks.$inferSelect; \ No newline at end of file