From b3382b7a4c4f4725dfb51c653d54245084c9c8a4 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 6 Feb 2026 22:03:50 +0000 Subject: [PATCH 1/2] Remove debug try-catch from shifts/start endpoint https://claude.ai/code/session_01Rf47gNAgM7jDstC4bvffzw --- src/routes/api/shifts/start/+server.ts | 108 ++++++++++++------------- 1 file changed, 51 insertions(+), 57 deletions(-) diff --git a/src/routes/api/shifts/start/+server.ts b/src/routes/api/shifts/start/+server.ts index 62584aa..a22aba7 100644 --- a/src/routes/api/shifts/start/+server.ts +++ b/src/routes/api/shifts/start/+server.ts @@ -4,74 +4,68 @@ import { operators, shifts } from '$lib/server/db/schema'; import { eq, and, isNull } from 'drizzle-orm'; import { verifyPasscode, checkRateLimit, clearRateLimit, logAuthFailure, createOperatorSession, rehashIfPlaintext } from '$lib/server/auth'; import { dev } from '$app/environment'; -import { logger } from '$lib/server/logger'; import type { RequestHandler } from './$types'; export const POST: RequestHandler = async ({ request, cookies, getClientAddress }) => { - try { - const clientIp = getClientAddress(); - const rateCheck = await checkRateLimit(`shift-start:${clientIp}`); - if (!rateCheck.allowed) { - return json( - { error: 'Too many login attempts. Try again later.' }, - { status: 429, headers: { 'Retry-After': String(rateCheck.retryAfterSeconds) } } - ); - } + const clientIp = getClientAddress(); + const rateCheck = await checkRateLimit(`shift-start:${clientIp}`); + if (!rateCheck.allowed) { + return json( + { error: 'Too many login attempts. Try again later.' }, + { status: 429, headers: { 'Retry-After': String(rateCheck.retryAfterSeconds) } } + ); + } - const { operatorId, passcode } = await request.json(); + const { operatorId, passcode } = await request.json(); - if (!operatorId || !passcode) { - return json({ error: 'Operator ID and passcode required' }, { status: 400 }); - } + if (!operatorId || !passcode) { + return json({ error: 'Operator ID and passcode required' }, { status: 400 }); + } - const [operator] = await db - .select() - .from(operators) - .where(and(eq(operators.id, operatorId), eq(operators.isActive, true))); + const [operator] = await db + .select() + .from(operators) + .where(and(eq(operators.id, operatorId), eq(operators.isActive, true))); - if (!operator) { - logAuthFailure('/api/shifts/start', String(operatorId), clientIp); - return json({ error: 'Invalid credentials' }, { status: 401 }); - } + if (!operator) { + logAuthFailure('/api/shifts/start', String(operatorId), clientIp); + return json({ error: 'Invalid credentials' }, { status: 401 }); + } - const valid = await verifyPasscode(passcode, operator.passcode); - if (!valid) { - logAuthFailure('/api/shifts/start', String(operatorId), clientIp); - return json({ error: 'Invalid credentials' }, { status: 401 }); - } + const valid = await verifyPasscode(passcode, operator.passcode); + if (!valid) { + logAuthFailure('/api/shifts/start', String(operatorId), clientIp); + return json({ error: 'Invalid credentials' }, { status: 401 }); + } - await clearRateLimit(`shift-start:${clientIp}`); - rehashIfPlaintext('operators', operatorId, operator.passcode).catch(() => {}); + await clearRateLimit(`shift-start:${clientIp}`); + rehashIfPlaintext('operators', operatorId, operator.passcode).catch(() => {}); - const [existingShift] = await db - .select() - .from(shifts) - .where(and(eq(shifts.operatorId, operatorId), isNull(shifts.endedAt))); + const [existingShift] = await db + .select() + .from(shifts) + .where(and(eq(shifts.operatorId, operatorId), isNull(shifts.endedAt))); - let shift = existingShift; - if (!existingShift) { - const [newShift] = await db - .insert(shifts) - .values({ - operatorId - }) - .returning(); - shift = newShift; - } + let shift = existingShift; + if (!existingShift) { + const [newShift] = await db + .insert(shifts) + .values({ + operatorId + }) + .returning(); + shift = newShift; + } - const sessionToken = await createOperatorSession(operatorId); - cookies.set('operatorSession', sessionToken, { - path: '/', - httpOnly: true, - sameSite: 'lax', - secure: !dev, - maxAge: 60 * 60 * 24 - }); + const sessionToken = await createOperatorSession(operatorId); + cookies.set('operatorSession', sessionToken, { + path: '/', + httpOnly: true, + sameSite: 'lax', + secure: !dev, + maxAge: 60 * 60 * 24 + }); - const { passcode: _, ...safeOperator } = operator; - return json({ operator: safeOperator, shift }); - } catch (err) { - logger.error({ err, endpoint: '/api/shifts/start' }, 'shift_start_error'); - return json({ error: String(err) }, { status: 500 }); - } + const { passcode: _, ...safeOperator } = operator; + return json({ operator: safeOperator, shift }); }; From dd72886bba1af81dc60b0904e6f50a30d0666b40 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 6 Feb 2026 22:47:58 +0000 Subject: [PATCH 2/2] Add /home landing page and /privacy policy page for Google OAuth compliance Google requires a homepage URL (different from the app root) that shows the app name, and a separate privacy policy URL. These pages satisfy the OAuth consent screen branding requirements. https://claude.ai/code/session_01Rf47gNAgM7jDstC4bvffzw --- src/routes/home/+page.svelte | 86 +++++++++++++++++++ src/routes/privacy/+page.svelte | 148 ++++++++++++++++++++++++++++++++ 2 files changed, 234 insertions(+) create mode 100644 src/routes/home/+page.svelte create mode 100644 src/routes/privacy/+page.svelte diff --git a/src/routes/home/+page.svelte b/src/routes/home/+page.svelte new file mode 100644 index 0000000..da1f5e4 --- /dev/null +++ b/src/routes/home/+page.svelte @@ -0,0 +1,86 @@ + + Rental Manager + + +
+
+ point_of_sale +

Rental Manager

+

Equipment rental and point-of-sale management

+ +
+ + +
+ + diff --git a/src/routes/privacy/+page.svelte b/src/routes/privacy/+page.svelte new file mode 100644 index 0000000..7c9b27c --- /dev/null +++ b/src/routes/privacy/+page.svelte @@ -0,0 +1,148 @@ + + Privacy Policy - Rental Manager + + +
+
+ + arrow_back + Back to Home + + +

Privacy Policy

+

Last updated: February 2026

+ +
+

Overview

+

+ Rental Manager is an internal equipment rental and point-of-sale management system. + This policy describes how we handle data within the application. +

+
+ +
+

Data We Collect

+

+ The application stores data necessary for rental operations, including: +

+
    +
  • Operator names and authentication credentials
  • +
  • Guide names and availability status
  • +
  • Equipment inventory and rental records
  • +
  • Sales transaction records
  • +
  • Shift logs and operational data
  • +
+
+ +
+

Google API Usage

+

+ This application uses Google APIs (Sheets and Drive) solely to export shift reports. + We only access data you explicitly authorize and do not share it with third parties. + Our use of Google API data adheres to the + + Google API Services User Data Policy, including the Limited Use requirements. +

+
+ +
+

Data Storage

+

+ All data is stored on a secure server. We do not sell, share, or transfer data to + third parties beyond what is described above. +

+
+ +
+

Contact

+

+ For questions about this policy, contact the system administrator. +

+
+
+ + +
+ +