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 }); }; 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. +

+
+
+ + +
+ +