From 59e83f47415ed746332140ba4640640f4d4e5bc4 Mon Sep 17 00:00:00 2001 From: Steffen Heger Date: Thu, 16 Oct 2025 19:08:24 +0200 Subject: [PATCH 1/3] api endpoint GET availability --- e2e/availability.test.ts | 36 ++++++++++- e2e/rideShare.test.ts | 8 +-- e2e/utils.ts | 5 ++ src/lib/server/getAvailability.ts | 58 +++++++++++++++++ src/routes/taxi/availability/+page.server.ts | 62 +++---------------- .../availability/api/availability/+server.ts | 30 ++++++++- 6 files changed, 137 insertions(+), 62 deletions(-) create mode 100644 src/lib/server/getAvailability.ts diff --git a/e2e/availability.test.ts b/e2e/availability.test.ts index 9cd6168f7..0807391a1 100644 --- a/e2e/availability.test.ts +++ b/e2e/availability.test.ts @@ -7,7 +7,8 @@ import { COMPANY1, moveMouse, offset, - dayString + dayString, + logout } from './utils'; test.describe.configure({ mode: 'serial' }); @@ -87,4 +88,37 @@ test('Request ride', async ({ page }) => { 'background-color', 'rgb(251, 146, 60)' ); + await logout(page); +}); + +test('Get availability', async ({ page }) => { + await login(page, TAXI_OWNER); + + const response = await page + .context() + .request.get(`/taxi/availability/api/availability?offset=${offset}&date=${dayString}`); + expect(response.status()).toBe(200); + + const responseBody = await response.json(); + expect(responseBody).toHaveProperty('tours'); + expect(responseBody).toHaveProperty('vehicles'); + expect(responseBody).toHaveProperty('utcDate'); + expect(responseBody).not.toHaveProperty('companyDataComplete'); + expect(responseBody).not.toHaveProperty('companyCoordinates'); + + const vehicles = responseBody['vehicles']; + expect(vehicles).toHaveLength(1); + expect(vehicles[0].availability).not.toHaveLength(0); + + const response2 = await page + .context() + .request.get(`/taxi/availability/api/availability?offset=NaN&date=${dayString}`); + expect(response2.status()).toBe(400); + + const response3 = await page + .context() + .request.get(`/taxi/availability/api/availability?offset=${offset}&date=noDate`); + expect(response3.status()).toBe(400); + + await logout(page); }); diff --git a/e2e/rideShare.test.ts b/e2e/rideShare.test.ts index 660cf68a2..2269c9684 100644 --- a/e2e/rideShare.test.ts +++ b/e2e/rideShare.test.ts @@ -5,7 +5,8 @@ import { login, RIDE_SHARE_CUSTOMER, execSQL, - UserCredentials + UserCredentials, + logout } from './utils'; import { sql } from 'kysely'; @@ -103,11 +104,6 @@ async function chooseFromTypeAhead( await suggestion.click(); } -async function logout(page: Page) { - await page.goto('/account/settings'); - await page.getByRole('button', { name: 'Abmelden' }).click(); -} - async function isFeedbackBannerVisible(page: Page, user: UserCredentials, xpct: boolean) { await login(page, user); await page.goto('/routing'); diff --git a/e2e/utils.ts b/e2e/utils.ts index 41d09b9ef..0e1cbd0ac 100644 --- a/e2e/utils.ts +++ b/e2e/utils.ts @@ -156,3 +156,8 @@ export async function moveMouse(page: Page, id: string) { const { x, y, width, height } = (await element.boundingBox())!; await page.mouse.move(x + width / 2, y + height / 2); } + +export async function logout(page: Page) { + await page.goto('/account/settings'); + await page.getByRole('button', { name: 'Abmelden' }).click(); +} diff --git a/src/lib/server/getAvailability.ts b/src/lib/server/getAvailability.ts new file mode 100644 index 000000000..f78880823 --- /dev/null +++ b/src/lib/server/getAvailability.ts @@ -0,0 +1,58 @@ +import { db } from '$lib/server/db'; +import { jsonArrayFrom } from 'kysely/helpers/postgres'; +import { getToursWithRequests } from '$lib/server/db/getTours.js'; + +export async function getAvailability( + utcDate: Date, + companyId: number +) { + const fromTime = new Date(utcDate); + fromTime.setHours(utcDate.getHours() - 1); + const toTime = new Date(utcDate); + toTime.setHours(utcDate.getHours() + 25); + + const vehicles = db + .selectFrom('vehicle') + .where('company', '=', companyId) + .selectAll() + .select((eb) => [ + jsonArrayFrom( + eb + .selectFrom('availability') + .whereRef('availability.vehicle', '=', 'vehicle.id') + .where('availability.startTime', '<', toTime.getTime()) + .where('availability.endTime', '>', fromTime.getTime()) + .select(['availability.id', 'availability.startTime', 'availability.endTime']) + .orderBy('availability.startTime') + ).as('availability') + ]) + .execute(); + + const tours = getToursWithRequests(false, companyId, [fromTime.getTime(), toTime.getTime()]); + + const company = await db + .selectFrom('company') + .where('id', '=', companyId) + .selectAll() + .executeTakeFirstOrThrow(); + + const companyDataComplete = + company.name !== null && + company.address !== null && + company.zone !== null && + company.lat !== null && + company.lng !== null; + + return { + tours: await tours, + vehicles: await vehicles, + utcDate, + companyDataComplete, + companyCoordinates: companyDataComplete + ? { + lat: company.lat!, + lng: company.lng! + } + : null + }; +} diff --git a/src/routes/taxi/availability/+page.server.ts b/src/routes/taxi/availability/+page.server.ts index 6735038d0..695524652 100644 --- a/src/routes/taxi/availability/+page.server.ts +++ b/src/routes/taxi/availability/+page.server.ts @@ -1,5 +1,4 @@ import { db } from '$lib/server/db'; -import { getToursWithRequests } from '$lib/server/db/getTours.js'; import { jsonArrayFrom } from 'kysely/helpers/postgres'; import type { Actions, RequestEvent } from './$types'; import { fail } from '@sveltejs/kit'; @@ -7,7 +6,9 @@ import { msg } from '$lib/msg'; import { readInt } from '$lib/server/util/readForm'; import { getPossibleInsertions } from '$lib/util/booking/getPossibleInsertions'; import { retry } from '$lib/server/db/retryQuery'; -import { LICENSE_PLATE_REGEX } from '$lib/constants'; +import { getAvailability } from '$lib/server/getAvailability.js'; + +const LICENSE_PLATE_REGEX = /^([A-ZÄÖÜ]{1,3})-([A-ZÄÖÜ]{1,2})-([0-9]{1,4})$/; export async function load(event: RequestEvent) { const companyId = event.locals.session?.companyId; @@ -15,62 +16,15 @@ export async function load(event: RequestEvent) { throw 'company not defined'; } - const url = event.url; - const localDateParam = url.searchParams.get('date'); - const timezoneOffset = url.searchParams.get('offset'); + const localDateParam = event.url.searchParams.get('date'); + const timezoneOffset = event.url.searchParams.get('offset'); + const utcDate = localDateParam && timezoneOffset ? new Date(new Date(localDateParam!).getTime() + Number(timezoneOffset) * 60 * 1000) : new Date(); - const fromTime = new Date(utcDate); - fromTime.setHours(utcDate.getHours() - 1); - const toTime = new Date(utcDate); - toTime.setHours(utcDate.getHours() + 25); - - const vehicles = db - .selectFrom('vehicle') - .where('company', '=', companyId) - .selectAll() - .select((eb) => [ - jsonArrayFrom( - eb - .selectFrom('availability') - .whereRef('availability.vehicle', '=', 'vehicle.id') - .where('availability.startTime', '<', toTime.getTime()) - .where('availability.endTime', '>', fromTime.getTime()) - .select(['availability.id', 'availability.startTime', 'availability.endTime']) - .orderBy('availability.startTime') - ).as('availability') - ]) - .execute(); - - const tours = getToursWithRequests(false, companyId, [fromTime.getTime(), toTime.getTime()]); - - const company = await db - .selectFrom('company') - .where('id', '=', companyId) - .selectAll() - .executeTakeFirstOrThrow(); - - const companyDataComplete = - company.name !== null && - company.address !== null && - company.zone !== null && - company.lat !== null && - company.lng !== null; - - return { - tours: await tours, - vehicles: await vehicles, - utcDate, - companyDataComplete, - companyCoordinates: companyDataComplete - ? { - lat: company.lat!, - lng: company.lng! - } - : null - }; + + return getAvailability(utcDate, companyId); } export const actions: Actions = { diff --git a/src/routes/taxi/availability/api/availability/+server.ts b/src/routes/taxi/availability/api/availability/+server.ts index a78f84997..3b93dde09 100644 --- a/src/routes/taxi/availability/api/availability/+server.ts +++ b/src/routes/taxi/availability/api/availability/+server.ts @@ -1,10 +1,12 @@ import { db, type Database } from '$lib/server/db'; import { Interval } from '$lib/util/interval'; -import { json } from '@sveltejs/kit'; +import { error, json } from '@sveltejs/kit'; import { type Insertable, type Selectable } from 'kysely'; import { getAlterableTimeframe } from '$lib/util/getAlterableTimeframe'; import { addAvailability } from '$lib/server/addAvailability'; import { retry } from '$lib/server/db/retryQuery'; +import { getAvailability } from '$lib/server/getAvailability.js'; +import { readInt } from '$lib/server/util/readForm'; type Availability = Selectable; type NewAvailability = Insertable; @@ -127,3 +129,29 @@ export const POST = async ({ locals, request }) => { await addAvailability(interval, companyId, vehicleId); return json({}); }; + +export const GET = async ({ locals, url }) => { + const companyId = locals.session?.companyId; + if (!companyId) { + throw 'no company'; + } + const timezoneOffset = readInt(url.searchParams.get('offset')); + if (isNaN(timezoneOffset)) { + error(400, { message: 'Invalid offset parameter' }); + } + const localDateParam = url.searchParams.get('date'); + if (!localDateParam) { + error(400, { message: 'Invalid date parameter' }); + } + const time = new Date(localDateParam).getTime() + if (isNaN(time)) { + error(400, { message: 'Invalid date parameter' }); + } + const utcDate = + localDateParam && timezoneOffset + ? new Date(time + timezoneOffset * 60 * 1000) + : new Date(); + + const { companyDataComplete, companyCoordinates, ...res } = await getAvailability(utcDate, companyId) + return json(res); +}; From 59b5907ffaa2dbde7afe6f6150294cce6807589f Mon Sep 17 00:00:00 2001 From: Steffen Heger Date: Fri, 17 Oct 2025 11:49:13 +0200 Subject: [PATCH 2/3] lint --- src/lib/server/getAvailability.ts | 11 ++++------- .../taxi/availability/api/availability/+server.ts | 12 +++++++----- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/src/lib/server/getAvailability.ts b/src/lib/server/getAvailability.ts index f78880823..c515b9eaf 100644 --- a/src/lib/server/getAvailability.ts +++ b/src/lib/server/getAvailability.ts @@ -2,10 +2,7 @@ import { db } from '$lib/server/db'; import { jsonArrayFrom } from 'kysely/helpers/postgres'; import { getToursWithRequests } from '$lib/server/db/getTours.js'; -export async function getAvailability( - utcDate: Date, - companyId: number -) { +export async function getAvailability(utcDate: Date, companyId: number) { const fromTime = new Date(utcDate); fromTime.setHours(utcDate.getHours() - 1); const toTime = new Date(utcDate); @@ -50,9 +47,9 @@ export async function getAvailability( companyDataComplete, companyCoordinates: companyDataComplete ? { - lat: company.lat!, - lng: company.lng! - } + lat: company.lat!, + lng: company.lng! + } : null }; } diff --git a/src/routes/taxi/availability/api/availability/+server.ts b/src/routes/taxi/availability/api/availability/+server.ts index 3b93dde09..c642f14e6 100644 --- a/src/routes/taxi/availability/api/availability/+server.ts +++ b/src/routes/taxi/availability/api/availability/+server.ts @@ -143,15 +143,17 @@ export const GET = async ({ locals, url }) => { if (!localDateParam) { error(400, { message: 'Invalid date parameter' }); } - const time = new Date(localDateParam).getTime() + const time = new Date(localDateParam).getTime(); if (isNaN(time)) { error(400, { message: 'Invalid date parameter' }); } const utcDate = - localDateParam && timezoneOffset - ? new Date(time + timezoneOffset * 60 * 1000) - : new Date(); + localDateParam && timezoneOffset ? new Date(time + timezoneOffset * 60 * 1000) : new Date(); - const { companyDataComplete, companyCoordinates, ...res } = await getAvailability(utcDate, companyId) + const { + companyDataComplete: _a, + companyCoordinates: _b, + ...res + } = await getAvailability(utcDate, companyId); return json(res); }; From 732bb350c15f092d3f54d85faf4ec28cd2f5d570 Mon Sep 17 00:00:00 2001 From: Steffen Heger Date: Mon, 20 Oct 2025 16:18:05 +0200 Subject: [PATCH 3/3] wip --- e2e/availability.test.ts | 2 +- src/routes/taxi/availability/api/availability/+server.ts | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/e2e/availability.test.ts b/e2e/availability.test.ts index 0807391a1..cdc077662 100644 --- a/e2e/availability.test.ts +++ b/e2e/availability.test.ts @@ -102,9 +102,9 @@ test('Get availability', async ({ page }) => { const responseBody = await response.json(); expect(responseBody).toHaveProperty('tours'); expect(responseBody).toHaveProperty('vehicles'); - expect(responseBody).toHaveProperty('utcDate'); expect(responseBody).not.toHaveProperty('companyDataComplete'); expect(responseBody).not.toHaveProperty('companyCoordinates'); + expect(responseBody).not.toHaveProperty('utcDate'); const vehicles = responseBody['vehicles']; expect(vehicles).toHaveLength(1); diff --git a/src/routes/taxi/availability/api/availability/+server.ts b/src/routes/taxi/availability/api/availability/+server.ts index c642f14e6..e6adcfd7c 100644 --- a/src/routes/taxi/availability/api/availability/+server.ts +++ b/src/routes/taxi/availability/api/availability/+server.ts @@ -147,12 +147,11 @@ export const GET = async ({ locals, url }) => { if (isNaN(time)) { error(400, { message: 'Invalid date parameter' }); } - const utcDate = - localDateParam && timezoneOffset ? new Date(time + timezoneOffset * 60 * 1000) : new Date(); - + const utcDate = new Date(time + timezoneOffset * 60 * 1000); const { companyDataComplete: _a, companyCoordinates: _b, + utcDate: _c, ...res } = await getAvailability(utcDate, companyId); return json(res);