diff --git a/motomate/.env.example b/motomate/.env.example index 2fcb40a..26ff210 100644 --- a/motomate/.env.example +++ b/motomate/.env.example @@ -35,8 +35,11 @@ PUBLIC_APP_URL=http://localhost:5173 PUBLIC_APP_ORIGINS=http://localhost:5173 PUBLIC_APP_NAME=MotoMate -# Scheduler -CRON_INTERVAL_HOURS=1 # workflow check interval in hours (default: 1) +# Demo mode (which will seed a read-only demo account on startup; don't use a pre-existing db!) +PUBLIC_DEMO_ENABLED=false + +# Scheduler for workflow/reminders +CRON_INTERVAL_HOURS=1 # Push notifications (VAPID) VAPID_PUBLIC_KEY= diff --git a/motomate/demo-seed.js b/motomate/demo-seed.js new file mode 100644 index 0000000..de79986 --- /dev/null +++ b/motomate/demo-seed.js @@ -0,0 +1,218 @@ +import Database from 'better-sqlite3'; +import { hash } from '@node-rs/argon2'; +import { randomBytes } from 'node:crypto'; + +const ARGON2_OPTS = { memoryCost: 19456, timeCost: 2, outputLen: 32, parallelism: 1 }; + +function id() { + return randomBytes(10).toString('hex'); +} + +export async function seedDemo() { + const url = process.env.DATABASE_URL ?? './data/motomate.db'; + const sqlite = new Database(url); + + try { + const existing = sqlite + .prepare('SELECT id FROM users WHERE email = ?') + .get('demo@motomate.local'); + + if (existing) { + console.log('[motomate] Demo seed: user already exists, skipping'); + return; + } + + const passwordHash = await hash('password123', ARGON2_OPTS); + const userId = id(); + const vehicleId = id(); + const oilTmplId = id(); + const chainTmplId = id(); + const tireTmplId = id(); + const oilTrackerId = id(); + const chainTrackerId = id(); + const tireTrackerId = id(); + + const settings = JSON.stringify({ + theme: 'system', + currency: 'EUR', + odometer_unit: 'km', + locale: 'en', + avatar_seed: id() + }); + + sqlite.transaction(() => { + sqlite + .prepare( + `INSERT INTO users (id, email, password_hash, onboarding_done, settings) + VALUES (?, ?, ?, 1, ?)` + ) + .run(userId, 'demo@motomate.local', passwordHash, settings); + + sqlite + .prepare( + `INSERT INTO vehicles (id, user_id, type, name, make, model, year, + current_odometer, current_measurement, current_measurement_unit, odometer_unit, meta) + VALUES (?, ?, 'motorcycle', 'Honda CB500F', 'Honda', 'CB500F', 2021, + 18400, 18400, 'km', 'km', '{"avatar_emoji":"๐Ÿ๏ธ"}')` + ) + .run(vehicleId, userId); + + // Task templates + sqlite + .prepare( + `INSERT INTO task_templates + (id, user_id, vehicle_id, name, category, description, interval_km, interval_measurement, interval_unit, interval_months, is_preset) + VALUES (?, ?, ?, 'Oil & Filter Change', 'oil', 'Engine oil and oil filter replacement', 10000, 10000, 'km', 12, 1)` + ) + .run(oilTmplId, userId, vehicleId); + + sqlite + .prepare( + `INSERT INTO task_templates + (id, user_id, vehicle_id, name, category, description, interval_km, interval_measurement, interval_unit, interval_months, is_preset) + VALUES (?, ?, ?, 'Chain Clean & Lube', 'chain', 'Clean and lubricate the chain', 500, 500, 'km', NULL, 1)` + ) + .run(chainTmplId, userId, vehicleId); + + sqlite + .prepare( + `INSERT INTO task_templates + (id, user_id, vehicle_id, name, category, description, interval_km, interval_measurement, interval_unit, interval_months, is_preset) + VALUES (?, ?, ?, 'Tire Pressure & Wear Check', 'tire', 'Check tyre pressure and inspect tread depth', NULL, NULL, NULL, 1, 1)` + ) + .run(tireTmplId, userId, vehicleId); + + // Active trackers + sqlite + .prepare( + `INSERT INTO active_trackers + (id, vehicle_id, template_id, last_done_at, last_done_odometer, last_done_measurement, + next_due_odometer, next_due_measurement, next_due_at, measurement_unit, status) + VALUES (?, ?, ?, '2025-04-10', 8000, 8000, 18000, 18000, '2026-04-10', 'km', 'overdue')` + ) + .run(oilTrackerId, vehicleId, oilTmplId); + + sqlite + .prepare( + `INSERT INTO active_trackers + (id, vehicle_id, template_id, last_done_at, last_done_odometer, last_done_measurement, + next_due_odometer, next_due_measurement, measurement_unit, status) + VALUES (?, ?, ?, '2026-04-15', 17950, 17950, 18450, 18450, 'km', 'due')` + ) + .run(chainTrackerId, vehicleId, chainTmplId); + + sqlite + .prepare( + `INSERT INTO active_trackers + (id, vehicle_id, template_id, last_done_at, next_due_at, status) + VALUES (?, ?, ?, '2026-05-05', '2026-06-05', 'ok')` + ) + .run(tireTrackerId, vehicleId, tireTmplId); + + // Service logs + const sl = sqlite.prepare( + `INSERT INTO service_logs + (id, vehicle_id, tracker_id, performed_at, odometer_at_service, measurement_at_service, measurement_unit, cost_cents, currency, notes) + VALUES (?, ?, ?, ?, ?, ?, 'km', ?, 'EUR', ?)` + ); + sl.run(id(), vehicleId, null, '2024-10-15', 3000, 3000, 4200, 'Oil & Filter Change'); + sl.run(id(), vehicleId, oilTrackerId, '2025-04-10', 8000, 8000, 4500, 'Oil & Filter Change'); + sl.run( + id(), + vehicleId, + chainTrackerId, + '2025-06-20', + 10500, + 10500, + 800, + 'Chain Clean & Lube' + ); + sl.run( + id(), + vehicleId, + chainTrackerId, + '2025-09-15', + 13200, + 13200, + 800, + 'Chain Clean & Lube' + ); + sl.run( + id(), + vehicleId, + chainTrackerId, + '2026-04-15', + 17950, + 17950, + 800, + 'Chain Clean & Lube' + ); + + // Workflow rules + const wr = sqlite.prepare( + `INSERT INTO workflow_rules (id, user_id, vehicle_id, name, description, trigger, actions, enabled) + VALUES (?, ?, ?, ?, ?, ?, ?, ?)` + ); + wr.run( + id(), + userId, + vehicleId, + 'Oil change overdue', + 'Notify when oil change is past due by 500 km', + JSON.stringify({ type: 'odometer_overdue', km_past: 500 }), + JSON.stringify({ + title: 'Oil change overdue', + body: 'Your Honda CB500F is due for an oil change.' + }), + 1 + ); + wr.run( + id(), + userId, + vehicleId, + 'Upcoming service reminder', + 'Notify 7 days before a tracker is due by date', + JSON.stringify({ type: 'date_upcoming', days_before: 7 }), + JSON.stringify({ + title: 'Service reminder', + body: 'A scheduled service is coming up in 7 days.' + }), + 1 + ); + wr.run( + id(), + userId, + null, + 'No odometer update', + 'Notify if no odometer reading logged in 30 days', + JSON.stringify({ type: 'no_odometer_update', days: 30 }), + JSON.stringify({ + title: 'No recent activity', + body: 'No odometer update logged in the last 30 days.' + }), + 0 + ); + + // Finance transactions + const ft = sqlite.prepare( + `INSERT INTO finance_transactions + (id, vehicle_id, user_id, category, amount_cents, currency, notes, performed_at) + VALUES (?, ?, ?, ?, ?, 'EUR', ?, ?)` + ); + ft.run(id(), vehicleId, userId, 'fuel', 6000, 'Fuel fill-up', '2026-02-10'); + ft.run( + id(), + vehicleId, + userId, + 'accessories', + 15000, + 'Handlebar grips & bar end weights', + '2025-11-05' + ); + })(); + + console.log('[motomate] Demo data seeded (demo@motomate.local / password123)'); + } finally { + sqlite.close(); + } +} diff --git a/motomate/server.js b/motomate/server.js index 63847f4..0ca7803 100644 --- a/motomate/server.js +++ b/motomate/server.js @@ -11,12 +11,16 @@ try { process.exit(1); } +if (process.env.PUBLIC_DEMO_ENABLED === 'true') { + const { seedDemo } = await import('./demo-seed.js'); + await seedDemo(); +} + const { handler } = await import('./build/handler.js'); const app = express(); -// Helmet CSP conflicts with SvelteKit's inline scripts, so we disable it. -// SvelteKit handles its own CSP via tags in app.html. +// Helmet CSP conflicts with SvelteKit's inline scripts, so we disable it. SvelteKit handles its own CSP via tags in app.html. app.use( helmet({ contentSecurityPolicy: false diff --git a/motomate/src/hooks.server.ts b/motomate/src/hooks.server.ts index 1b85953..6678af5 100644 --- a/motomate/src/hooks.server.ts +++ b/motomate/src/hooks.server.ts @@ -1,10 +1,13 @@ import { lucia } from '$lib/auth/index.js'; import type { Handle } from '@sveltejs/kit'; import { env } from '$env/dynamic/private'; +import { env as pubEnv } from '$env/dynamic/public'; import { initScheduler } from '$lib/server/scheduler.js'; initScheduler(); +let _demoSeeded = false; + function isOriginTrusted(origin: string | null, referer: string | null, url: string): boolean { const configuredOrigins = process.env.PUBLIC_APP_ORIGINS ? process.env.PUBLIC_APP_ORIGINS.split(',') @@ -102,6 +105,12 @@ function buildCorsHeaders(requestOrigin: string | null): Record } export const handle: Handle = async ({ event, resolve }) => { + if (!_demoSeeded && pubEnv.PUBLIC_DEMO_ENABLED === 'true') { + _demoSeeded = true; + const { seedDemo } = await import('$lib/db/demo-seed.js'); + await seedDemo(); + } + if (event.request.method === 'OPTIONS') { return new Response(null, { status: 204, @@ -109,6 +118,24 @@ export const handle: Handle = async ({ event, resolve }) => { }); } + if ( + pubEnv.PUBLIC_DEMO_ENABLED === 'true' && + event.request.method !== 'GET' && + event.request.method !== 'HEAD' && + !event.url.pathname.startsWith('/login') && + !event.url.pathname.startsWith('/register') && + !event.url.pathname.startsWith('/magic-link') && + event.url.pathname !== '/auth/logout' + ) { + if (event.request.headers.get('x-sveltekit-action') === 'true') { + return new Response(JSON.stringify({ type: 'success', status: 200 }), { + status: 200, + headers: { 'content-type': 'application/json' } + }); + } + return new Response(null, { status: 303, headers: { Location: event.url.pathname } }); + } + if (event.request.method !== 'GET' && event.request.method !== 'HEAD') { const origin = event.request.headers.get('origin'); const referer = event.request.headers.get('referer'); diff --git a/motomate/src/lib/db/demo-assets/dealer-service-1000km.pdf b/motomate/src/lib/db/demo-assets/dealer-service-1000km.pdf new file mode 100644 index 0000000..30bb018 Binary files /dev/null and b/motomate/src/lib/db/demo-assets/dealer-service-1000km.pdf differ diff --git a/motomate/src/lib/db/demo-assets/eifel-route.gpx b/motomate/src/lib/db/demo-assets/eifel-route.gpx new file mode 100644 index 0000000..beb6e25 --- /dev/null +++ b/motomate/src/lib/db/demo-assets/eifel-route.gpx @@ -0,0 +1,9469 @@ + + + + Eifel Route + + MotoMate + + Eifel Route + + + + 2026 + None + + + + + Eifel_Route_6-7 + + + 509 + + + 509 + + + 509.1 + + + 509.2 + + + 509.3 + + + 509.6 + + + 509.9 + + + 510.7 + + + 511.7 + + + 514.4 + + + 517.2 + + + 520.7 + + + 523.3 + + + 533.5 + + + 537.7 + + + 539 + + + 542.3 + + + 546 + + + 547.3 + + + 547.7 + + + 548.3 + + + 549.7 + + + 552.3 + + + 556.3 + + + 557.8 + + + 559.1 + + + 559.4 + + + 559.7 + + + 559.8 + + + 559.9 + + + 559.8 + + + 559.8 + + + 559.8 + + + 559.8 + + + 559.7 + + + 559.4 + + + 558.7 + + + 558.5 + + + 556 + + + 552.9 + + + 545.5 + + + 539.6 + + + 536.4 + + + 533 + + + 528.2 + + + 516.6 + + + 514.2 + + + 511.6 + + + 509.5 + + + 507.4 + + + 504 + + + 502.7 + + + 501.1 + + + 498.8 + + + 498.2 + + + 497.7 + + + 497.5 + + + 497.2 + + + 496.8 + + + 495.7 + + + 495.4 + + + 495.1 + + + 494.4 + + + 494 + + + 492.6 + + + 491.1 + + + 489.6 + + + 488.8 + + + 487.4 + + + 484.7 + + + 479.8 + + + 476.7 + + + 471.3 + + + 467.9 + + + 464.3 + + + 459.7 + + + 455.6 + + + 453.6 + + + 452 + + + 450.8 + + + 449.1 + + + 448.1 + + + 447.6 + + + 447 + + + 445.6 + + + 444.7 + + + 442.7 + + + 442.4 + + + 442.3 + + + 442 + + + 442 + + + 442 + + + 442 + + + 442.2 + + + 442.2 + + + 441.2 + + + 441 + + + 439 + + + 436.6 + + + 430.3 + + + 427.7 + + + 427.3 + + + 426.3 + + + 425.4 + + + 425 + + + 424.7 + + + 424.5 + + + 424.7 + + + 425.1 + + + 425.7 + + + 426.6 + + + 427.2 + + + 427.7 + + + 428.4 + + + 430.2 + + + 431.8 + + + 432.7 + + + 434.3 + + + 435.4 + + + 437.9 + + + 441.6 + + + 444.3 + + + 446.5 + + + 450.3 + + + 452.4 + + + 455.4 + + + 458.5 + + + 462 + + + 464.7 + + + 468.6 + + + 470.6 + + + 474.3 + + + 477.7 + + + 482.2 + + + 483.1 + + + 483.4 + + + 484.3 + + + 484.6 + + + 485.5 + + + 486.3 + + + 487.2 + + + 488 + + + 488.6 + + + 489 + + + 489.5 + + + 490.1 + + + 490.5 + + + 491.2 + + + 492.6 + + + 494 + + + 494.7 + + + 495.2 + + + 497.3 + + + 497.6 + + + 497.7 + + + 497.5 + + + 497.1 + + + 496.6 + + + 495.3 + + + 494.1 + + + 491.5 + + + 486.2 + + + 484.3 + + + 482.2 + + + 480 + + + 477.7 + + + 476.6 + + + 471.8 + + + 468.9 + + + 467.4 + + + 467.3 + + + 467.3 + + + 467.4 + + + 467.7 + + + 468.5 + + + 468.5 + + + 468.4 + + + 468 + + + 467.6 + + + 467 + + + 466.7 + + + 466 + + + 465.4 + + + 464.8 + + + 461.4 + + + 461.3 + + + 458 + + + 455.2 + + + 451.8 + + + 451.3 + + + 449.1 + + + 447.4 + + + 444.3 + + + 443.8 + + + 442.5 + + + 441.7 + + + 441.2 + + + 440.6 + + + 440.8 + + + 441.9 + + + 443.8 + + + 447.2 + + + 449.3 + + + 451.6 + + + 459.5 + + + 466.3 + + + 469.6 + + + 474.4 + + + 481.6 + + + 485.2 + + + 488.8 + + + 489.1 + + + 489.9 + + + 490.1 + + + 490.4 + + + 491.7 + + + 492.4 + + + 492.8 + + + 493.3 + + + 493.6 + + + 494.1 + + + 494.5 + + + 495 + + + 496.2 + + + 496.7 + + + 497.2 + + + 498.1 + + + 498.7 + + + 499.3 + + + 500.1 + + + 500.3 + + + 501.1 + + + 503.4 + + + 504.8 + + + 505.9 + + + 506.9 + + + 507.4 + + + 508.8 + + + 509.5 + + + 509.8 + + + 513.4 + + + 515.4 + + + 516.3 + + + 518.3 + + + 519 + + + 519.6 + + + 520.3 + + + 520.9 + + + 521.6 + + + 522.7 + + + 523.6 + + + 524 + + + 524.2 + + + 524.4 + + + 524 + + + 521.8 + + + 521.3 + + + 520.3 + + + 518.6 + + + 518.4 + + + 518.2 + + + 516.8 + + + 515.7 + + + 514.4 + + + 514.3 + + + 512.9 + + + 503 + + + 494.5 + + + 495.6 + + + 495.9 + + + 496.1 + + + 496.5 + + + 497 + + + 497.8 + + + 498.5 + + + 499.7 + + + 500 + + + 500.3 + + + 500.9 + + + 501.1 + + + 501.3 + + + 501.5 + + + 501.6 + + + 502.3 + + + 502.7 + + + 502.9 + + + 503 + + + 503 + + + 503 + + + 503.1 + + + 503 + + + 502.7 + + + 502.2 + + + 501.7 + + + 501.1 + + + 500.7 + + + 499.4 + + + 495.1 + + + 492.9 + + + 491.5 + + + 488.3 + + + 475.3 + + + 469.9 + + + 457.2 + + + 443.5 + + + 438.2 + + + 427.6 + + + 426.6 + + + 424.5 + + + 417.8 + + + 415.4 + + + 414 + + + 411.4 + + + 407.2 + + + 403.9 + + + 399.9 + + + 393.1 + + + 390.4 + + + 379.9 + + + 375.2 + + + 365 + + + 358.5 + + + 350.4 + + + 347.4 + + + 346.2 + + + 344.7 + + + 333 + + + 332.2 + + + 328.6 + + + 325.8 + + + 324 + + + 319.8 + + + 317.6 + + + 315.9 + + + 313.2 + + + 311.9 + + + 308.9 + + + 308.5 + + + 307.8 + + + 306.9 + + + 306.4 + + + 306 + + + 305.7 + + + 305.2 + + + 305 + + + 304.8 + + + 304.8 + + + 304.4 + + + 304 + + + 303.8 + + + 303.7 + + + 303.6 + + + 303.5 + + + 303.5 + + + 304.1 + + + 304.5 + + + 305.2 + + + 305.5 + + + 306 + + + 306.7 + + + 307.3 + + + 308.1 + + + 308.8 + + + 310.5 + + + 310.8 + + + 311.4 + + + 311.7 + + + 312.4 + + + 314.9 + + + 316 + + + 317 + + + 318.7 + + + 319.9 + + + 322 + + + 324.5 + + + 325.7 + + + 327.4 + + + 334.6 + + + 335.9 + + + 336.3 + + + 336.3 + + + 336.3 + + + 336.1 + + + 335.7 + + + 335.3 + + + 334.7 + + + 334.1 + + + 333.5 + + + 332.1 + + + 329.2 + + + 327.5 + + + 327 + + + 326 + + + 324.4 + + + 323.5 + + + 323.2 + + + 322.9 + + + 322.4 + + + 322.3 + + + 322 + + + 321.9 + + + 322 + + + 322.2 + + + 323 + + + 324.2 + + + 325.5 + + + 326.4 + + + 327.4 + + + 328.7 + + + 330.2 + + + 330.9 + + + 332.3 + + + 335.7 + + + 336.8 + + + 340.1 + + + 342.4 + + + 343 + + + 343.4 + + + 344.1 + + + 344.2 + + + 344.3 + + + 344.4 + + + 344.4 + + + 344.5 + + + 344.6 + + + 344.8 + + + 344.9 + + + 344.9 + + + 345.2 + + + 345.5 + + + 346.2 + + + 346.5 + + + 346.9 + + + 347.1 + + + 347.3 + + + 347.6 + + + 347.8 + + + 348.3 + + + 348.3 + + + 349.2 + + + 349.9 + + + 350.1 + + + 350.9 + + + 352.4 + + + 352.9 + + + 355.4 + + + 358.6 + + + 364.2 + + + 367.1 + + + 371.4 + + + 376.8 + + + 380.3 + + + 382.3 + + + 398.4 + + + 400.9 + + + 403.7 + + + 421.2 + + + 425.3 + + + 428.6 + + + 435.3 + + + 444.1 + + + 448.3 + + + 452.9 + + + 456.1 + + + 457.6 + + + 459.3 + + + 460.1 + + + 462.6 + + + 463.9 + + + 466.9 + + + 469.9 + + + 471.2 + + + 471.9 + + + 473.2 + + + 474 + + + 475.3 + + + 476 + + + 477 + + + 479.9 + + + 480.5 + + + 482.2 + + + 482.4 + + + 482.9 + + + 483.7 + + + 484 + + + 484.8 + + + 484.9 + + + 485 + + + 490.7 + + + 494.5 + + + 507.4 + + + 510 + + + 510.7 + + + 511.3 + + + 511.6 + + + 511.8 + + + 512 + + + 512 + + + 511.8 + + + 511.5 + + + 510.8 + + + 510.8 + + + 510.8 + + + 510.8 + + + 511 + + + 511.3 + + + 511.6 + + + 512.8 + + + 514.3 + + + 516 + + + 517.6 + + + 518.5 + + + 519.1 + + + 539.9 + + + 540.8 + + + 542 + + + 542.6 + + + 544.9 + + + 545 + + + 545.5 + + + 545.6 + + + 547 + + + 548.3 + + + 549.1 + + + 551.1 + + + 554.3 + + + 556.8 + + + 558.3 + + + 560.8 + + + 565.6 + + + 569.5 + + + 570.3 + + + 573.3 + + + 575.1 + + + 577.8 + + + 580.8 + + + 586.9 + + + 588.3 + + + 590.8 + + + 592.3 + + + 595.2 + + + 596 + + + 596.4 + + + 597.4 + + + 597.9 + + + 598.3 + + + 598.6 + + + 600.2 + + + 601.1 + + + 602.1 + + + 602.8 + + + 603.9 + + + 605.2 + + + 605.3 + + + 605.3 + + + 605.4 + + + 605.6 + + + 605.7 + + + 605.8 + + + 605.8 + + + 605.9 + + + 605.9 + + + 606.1 + + + 606.2 + + + 606.2 + + + 606.3 + + + 606.4 + + + 607.3 + + + 607.6 + + + 608 + + + 608 + + + 608.1 + + + 608.1 + + + 608.1 + + + 608.2 + + + 608.2 + + + 608.2 + + + 608.1 + + + 607.6 + + + 607.6 + + + 607.4 + + + 607.1 + + + 606.1 + + + 604.2 + + + 602.6 + + + 602.4 + + + 599.9 + + + 591.8 + + + 590.1 + + + 586.6 + + + 581.4 + + + 573 + + + 560.6 + + + 533.4 + + + 532.5 + + + 532.1 + + + 532.4 + + + 533.5 + + + 535.7 + + + 544.8 + + + 546.7 + + + 547.1 + + + 548 + + + 547.9 + + + 547.2 + + + 546.5 + + + 545.9 + + + 545.3 + + + 544.2 + + + 543.6 + + + 543.3 + + + 541.7 + + + 540.8 + + + 540.2 + + + 538.8 + + + 537.4 + + + 535.2 + + + 528.8 + + + 526.3 + + + 523.7 + + + 519.6 + + + 517.8 + + + 515.6 + + + 514.1 + + + 512.8 + + + 510.8 + + + 510.2 + + + 509.7 + + + 509.3 + + + 506.4 + + + 504.4 + + + 503 + + + 500.2 + + + 498.4 + + + 496.7 + + + 494.8 + + + 493.5 + + + 487.4 + + + 482.9 + + + 481.8 + + + 478.9 + + + 476.4 + + + 473.2 + + + 470.9 + + + 469.8 + + + 464.4 + + + 459.1 + + + 446.5 + + + 441.7 + + + 439.9 + + + 438.1 + + + 432.4 + + + 430.5 + + + 427.7 + + + 426.8 + + + 424.7 + + + 416.9 + + + 407.6 + + + 404.8 + + + 402.7 + + + 398.1 + + + 395.5 + + + 394.6 + + + 393.2 + + + 391.6 + + + 391 + + + 390.7 + + + 390.5 + + + 390.2 + + + 390 + + + 389.6 + + + 388.8 + + + 388.7 + + + 388.6 + + + 388.3 + + + 386.2 + + + 385.3 + + + 384.2 + + + 382.3 + + + 381.7 + + + 381.3 + + + 380.8 + + + 378.9 + + + 377.3 + + + 376.5 + + + 374.5 + + + 372.9 + + + 372.3 + + + 369.8 + + + 367.7 + + + 367.5 + + + 367.5 + + + 367.4 + + + 366 + + + 365.6 + + + 365.3 + + + 364.8 + + + 364.5 + + + 364.2 + + + 364.1 + + + 364.2 + + + 364.4 + + + 364.5 + + + 364.8 + + + 364.9 + + + 365.1 + + + 365.5 + + + 365.7 + + + 365.9 + + + 366 + + + 366 + + + 366.2 + + + 366.2 + + + 366 + + + 365.7 + + + 364.9 + + + 364.2 + + + 363.1 + + + 361.1 + + + 357.1 + + + 355.7 + + + 354.5 + + + 353.6 + + + 350.1 + + + 349.5 + + + 349.1 + + + 348.8 + + + 347.4 + + + 346.8 + + + 345.8 + + + 345.3 + + + 344.3 + + + 343.4 + + + 342.3 + + + 341.2 + + + 339.8 + + + 337.1 + + + 336.6 + + + 336.3 + + + 335.9 + + + 334.9 + + + 330.8 + + + 330.4 + + + 329.8 + + + 329.4 + + + 329.3 + + + 329.3 + + + 329.9 + + + 330.8 + + + 331.3 + + + 331.6 + + + 332.2 + + + 333.4 + + + 334.2 + + + 334.9 + + + 336.3 + + + 337.4 + + + 338.5 + + + 343.2 + + + 347.1 + + + 351.6 + + + 353.7 + + + 358 + + + 360.3 + + + 361.9 + + + 362.7 + + + 363.6 + + + 365.9 + + + 368.1 + + + 370.3 + + + 372.8 + + + 374.9 + + + 376.3 + + + 377.5 + + + 378.6 + + + 380.4 + + + 381.2 + + + 382.4 + + + 383 + + + 384.8 + + + 385.8 + + + 387.4 + + + 389.5 + + + 393.9 + + + 394.7 + + + 395.9 + + + 397.3 + + + 398.9 + + + 400.7 + + + 401.2 + + + 402.2 + + + 403.7 + + + 404.7 + + + 405.3 + + + 407.4 + + + 408.1 + + + 409 + + + 412.8 + + + 415.9 + + + 417.3 + + + 419.5 + + + 419.6 + + + 420.1 + + + 419.9 + + + 419.1 + + + 418.5 + + + 415.5 + + + 415 + + + 414.2 + + + 411.6 + + + 409.7 + + + 408 + + + 406.5 + + + 405.7 + + + 404.8 + + + 404.2 + + + 403.9 + + + 403.4 + + + 402.8 + + + 401.8 + + + 401.4 + + + 401 + + + 400.4 + + + 399.2 + + + 397.3 + + + 393.3 + + + 392.7 + + + 392.5 + + + 391.3 + + + 390.9 + + + 390.1 + + + 390 + + + 389.8 + + + 389.3 + + + 388.8 + + + 388.5 + + + 388.5 + + + 388.5 + + + 388.8 + + + 389.5 + + + 389.7 + + + 389.6 + + + 389.5 + + + 389 + + + 386.8 + + + 385.2 + + + 384.9 + + + 384.7 + + + 384 + + + 383.3 + + + 382.5 + + + 381.8 + + + 378.6 + + + 378 + + + 377.5 + + + 377 + + + 374.9 + + + 374.4 + + + 367 + + + 365.3 + + + 360.9 + + + 357.4 + + + 355 + + + 352.8 + + + 350.8 + + + 350.6 + + + 350.3 + + + 350.1 + + + 349.9 + + + 349.8 + + + 349.8 + + + 349.9 + + + 350.8 + + + 351.8 + + + 352.4 + + + 353.3 + + + 354.5 + + + 356.2 + + + 357.7 + + + 359.9 + + + 361.8 + + + 363.6 + + + 366.2 + + + 369.1 + + + 371.9 + + + 372.5 + + + 374.5 + + + 378 + + + 386.5 + + + 388.4 + + + 389 + + + 390.8 + + + 394.1 + + + 396.5 + + + 402.5 + + + 409.9 + + + 417.5 + + + 420.4 + + + 426.4 + + + 434.6 + + + 440.8 + + + 446.3 + + + 447.8 + + + 447.4 + + + 441.2 + + + 439 + + + 437.8 + + + 437.7 + + + 437.7 + + + 437.9 + + + 438.1 + + + 439.1 + + + 439.8 + + + 440.5 + + + 441.3 + + + 441.5 + + + 441.8 + + + 441.8 + + + 441.6 + + + 441.4 + + + 438.3 + + + 437.8 + + + 437 + + + 432.7 + + + 431.8 + + + 430.2 + + + 429.5 + + + 428.5 + + + 427.4 + + + 425.8 + + + 424.7 + + + 424.5 + + + 424.4 + + + 424.2 + + + 424.1 + + + 423.7 + + + 423.6 + + + 423.4 + + + 423.4 + + + 423.4 + + + 423.3 + + + 423.4 + + + 423.5 + + + 423.5 + + + 423.7 + + + 423.7 + + + 423.9 + + + 424.1 + + + 424.1 + + + 424.2 + + + 424.3 + + + 424.4 + + + 424.5 + + + 424.6 + + + 425.1 + + + 425.3 + + + 425.4 + + + 425.4 + + + 425.5 + + + 425.6 + + + 425.6 + + + 425.7 + + + 425.9 + + + 425.8 + + + 422.4 + + + 419.6 + + + 417.4 + + + 412.4 + + + 411.7 + + + 410.1 + + + 408.5 + + + 406.8 + + + 406.2 + + + 405.3 + + + 404.8 + + + 404.6 + + + 404.4 + + + 404.2 + + + 404.1 + + + 404.1 + + + 404.1 + + + 404.2 + + + 404.4 + + + 405.1 + + + 405.2 + + + 407.5 + + + 407.6 + + + 409.2 + + + 412.6 + + + 413.8 + + + 414.4 + + + 415.5 + + + 417.8 + + + 418.8 + + + 421.8 + + + 424.4 + + + 426.5 + + + 436 + + + 438 + + + 439.3 + + + 444.9 + + + 446.6 + + + 447 + + + 447.5 + + + 447.8 + + + 448 + + + 448 + + + 448.1 + + + 448.9 + + + 449.1 + + + 449.2 + + + 449.2 + + + 449.7 + + + 450.1 + + + 450.1 + + + 450.2 + + + 450.3 + + + 451.7 + + + 452.1 + + + 452.3 + + + 453.2 + + + 453.9 + + + 454.7 + + + 457.2 + + + 457.8 + + + 458.8 + + + 459.5 + + + 460.1 + + + 460.7 + + + 461.8 + + + 465.2 + + + 465.7 + + + 466.2 + + + 466.4 + + + 468.1 + + + 469.5 + + + 470 + + + 472.8 + + + 473.2 + + + 474.3 + + + 474.9 + + + 475.3 + + + 476.2 + + + 479.3 + + + 480.5 + + + 481.2 + + + 487.6 + + + 490.3 + + + 494.7 + + + 503 + + + 516.1 + + + 519.3 + + + 528.6 + + + 533.9 + + + 543 + + + 544.7 + + + 550.6 + + + 551.5 + + + 552.2 + + + 552.3 + + + 552.4 + + + 552.3 + + + 552.2 + + + 552.2 + + + 552.1 + + + 552.1 + + + 551.9 + + + 551.9 + + + 551.8 + + + 551.3 + + + 550.9 + + + 550.7 + + + 550.5 + + + 550.2 + + + 549.6 + + + 549.4 + + + 549.4 + + + 549.1 + + + 548.9 + + + 548.4 + + + 548.3 + + + 548.1 + + + 548.1 + + + 547.6 + + + 547.5 + + + 547.4 + + + 547 + + + 546.9 + + + 546.5 + + + 545 + + + 544.6 + + + 544 + + + 542.6 + + + 542 + + + 542.7 + + + 542.9 + + + 543.5 + + + 544.7 + + + 547.8 + + + 549.9 + + + 558.4 + + + 561 + + + 564.4 + + + 573.5 + + + 576.6 + + + 588.5 + + + 589 + + + 591 + + + 592.7 + + + 593.6 + + + 593.7 + + + 591.3 + + + 590.4 + + + 583.2 + + + 578.8 + + + 577 + + + 573.6 + + + 572.6 + + + 572.8 + + + 573.4 + + + 576.1 + + + 577.7 + + + 577.9 + + + 577.7 + + + 576.6 + + + 576 + + + 573.7 + + + 573.4 + + + 573 + + + 572.7 + + + 572.5 + + + 571.1 + + + 570.8 + + + 569.8 + + + 568.9 + + + 568.5 + + + 567.9 + + + 566.6 + + + 565.7 + + + 555.4 + + + 554.8 + + + 556.2 + + + 557.8 + + + 558.3 + + + 558.8 + + + 560.3 + + + 563 + + + 565.3 + + + 566.4 + + + 570 + + + 571.3 + + + 574.1 + + + 574.9 + + + 577 + + + 578 + + + 579.8 + + + 581.7 + + + 583 + + + 583.6 + + + 583.9 + + + 584.1 + + + 582.2 + + + 581.2 + + + 579.8 + + + 576.1 + + + 574.2 + + + 571.7 + + + 567.5 + + + 565.5 + + + 564 + + + 562.5 + + + 561.4 + + + 559.8 + + + 557.8 + + + 556.4 + + + 554 + + + 553 + + + 552 + + + 551.8 + + + 551.6 + + + 550.3 + + + 549.9 + + + 549.3 + + + 549.1 + + + 549 + + + 548.9 + + + 548.9 + + + 549.2 + + + 550.1 + + + 554.4 + + + 555 + + + 554.8 + + + 549.9 + + + 547.9 + + + 543.6 + + + 540.5 + + + 536.4 + + + 532.5 + + + 529.6 + + + 525.9 + + + 521.6 + + + 519.3 + + + 518.7 + + + 515.6 + + + 511 + + + 506.3 + + + 504.9 + + + 501.9 + + + 500.6 + + + 499.9 + + + 499 + + + 498.4 + + + 497.3 + + + 496.3 + + + 495.1 + + + 492.6 + + + 491.6 + + + 490.9 + + + 489.9 + + + 489.3 + + + 488.7 + + + 487.3 + + + 486.2 + + + 485.6 + + + 485.1 + + + 484 + + + 482.9 + + + 480 + + + 477.1 + + + 471.1 + + + 468.3 + + + 464.6 + + + 463.8 + + + 463.5 + + + 463.8 + + + 464.3 + + + 464.1 + + + 463.8 + + + 463.4 + + + 462.6 + + + 461.4 + + + 459.9 + + + 457.9 + + + 457.9 + + + 457.8 + + + 457.8 + + + 457.7 + + + 457.5 + + + 457.4 + + + 457.1 + + + 454.8 + + + 451.6 + + + 450.1 + + + 448.3 + + + 447.1 + + + 446.2 + + + 444.5 + + + 443 + + + 442.9 + + + 442.8 + + + 442.4 + + + 442 + + + 441.7 + + + 441.7 + + + 441.6 + + + 441.5 + + + 441.1 + + + 440.8 + + + 440.6 + + + 440 + + + 438.4 + + + 437.4 + + + 436.8 + + + 435.7 + + + 434.7 + + + 432.6 + + + 431.6 + + + 431 + + + 430.6 + + + 430.1 + + + 430.1 + + + 429.9 + + + 429.7 + + + 429.4 + + + 429.3 + + + 429.2 + + + 429.2 + + + 430 + + + 430.4 + + + 431.2 + + + 431.9 + + + 432.2 + + + 432.9 + + + 433.6 + + + 434.1 + + + 434.2 + + + 434.3 + + + 434.3 + + + 434.4 + + + 434.5 + + + 434.5 + + + 434.7 + + + 435 + + + 435.1 + + + 435.4 + + + 435.5 + + + 435.6 + + + 435.6 + + + 435.7 + + + 435.6 + + + 435.6 + + + 435.6 + + + 435.5 + + + 435.3 + + + 435.1 + + + 435.1 + + + 435.2 + + + 435.4 + + + 435.7 + + + 436.1 + + + 437.2 + + + 437.4 + + + 437.7 + + + 437.8 + + + 438.1 + + + 438.4 + + + 440.2 + + + 440.6 + + + 441.2 + + + 442.8 + + + 443.8 + + + 444.1 + + + 444.5 + + + 445.3 + + + 449.6 + + + 450.8 + + + 455.3 + + + 457.7 + + + 459.1 + + + 460.3 + + + 460.9 + + + 460.9 + + + 460.7 + + + 459.8 + + + 459.2 + + + 458.3 + + + 457.6 + + + 456.8 + + + 456.3 + + + 455 + + + 453.6 + + + 452.9 + + + 451.4 + + + 450.6 + + + 450.2 + + + 448.6 + + + 448.2 + + + 446.9 + + + 444.9 + + + 442.3 + + + 434.4 + + + 432.3 + + + 430.6 + + + 429.9 + + + 428.5 + + + 428 + + + 427.8 + + + 427.6 + + + 428.1 + + + 428.4 + + + 429 + + + 429.4 + + + 430.2 + + + 431.5 + + + 432.1 + + + 432.4 + + + 434.2 + + + 434.3 + + + 434.3 + + + 435 + + + 436.8 + + + 437.5 + + + 438 + + + 441.1 + + + 441.8 + + + 442.4 + + + 443 + + + 444.3 + + + 447.3 + + + 448.3 + + + 450.6 + + + 452.1 + + + 454.1 + + + 458.1 + + + 468.6 + + + 473.7 + + + 476.2 + + + 494.3 + + + 498 + + + 502.3 + + + 512.2 + + + 523.2 + + + 532.8 + + + 546.9 + + + 551.2 + + + 556.4 + + + 558.1 + + + 559.4 + + + 560.1 + + + 559.9 + + + 559.6 + + + 559 + + + 558.5 + + + 557.7 + + + 556.5 + + + 554 + + + 539.4 + + + 537.7 + + + 535.6 + + + 528.1 + + + 526.6 + + + 525.6 + + + 524.4 + + + 523.2 + + + 521 + + + 519 + + + 518.1 + + + 516.3 + + + 515.4 + + + 514.8 + + + 514.3 + + + 514.1 + + + 513.9 + + + 513.6 + + + 513.5 + + + 513.6 + + + 513.6 + + + 513.6 + + + 513.7 + + + 513.8 + + + 513.8 + + + 513.8 + + + 515.2 + + + 516 + + + 516.1 + + + 516.7 + + + 517.8 + + + 521.6 + + + 527.5 + + + 531.5 + + + 533.1 + + + 535.3 + + + 538 + + + 542.3 + + + 545.4 + + + 548.3 + + + 550.4 + + + 554 + + + 555.3 + + + 561 + + + 564.4 + + + 569.4 + + + 574.3 + + + 577.1 + + + 579.5 + + + 581.8 + + + 586.8 + + + 589.5 + + + 591.9 + + + 593.3 + + + 595.9 + + + 598.2 + + + 599.2 + + + 601.8 + + + 602.9 + + + 606 + + + 607.7 + + + 608.8 + + + 609.8 + + + 609.6 + + + 608.7 + + + 602.9 + + + 595.8 + + + 593.8 + + + 591.6 + + + 589.3 + + + 587.6 + + + 582.3 + + + 580.7 + + + 577.4 + + + 574.9 + + + 572.2 + + + 571.2 + + + 568.5 + + + 567.1 + + + 562.4 + + + 561.6 + + + 559 + + + 557.3 + + + 555.8 + + + 554.8 + + + 553.4 + + + 551.2 + + + 550.7 + + + 549.2 + + + 548.4 + + + 548 + + + 546.9 + + + 545.9 + + + 545.3 + + + 541.7 + + + 536.3 + + + 533.4 + + + 531.4 + + + 529.3 + + + 527.3 + + + 525.8 + + + 525.5 + + + 526 + + + 526.4 + + + 526.4 + + + 526.4 + + + 525.4 + + + 521.5 + + + 515.3 + + + 509 + + + 505.8 + + + 497.8 + + + 495.6 + + + 494 + + + 492.2 + + + 490.5 + + + 480.9 + + + 477.7 + + + 476.2 + + + 469.1 + + + 467 + + + 462.5 + + + 457.7 + + + 443.6 + + + 437.4 + + + 432.5 + + + 425.9 + + + 420.3 + + + 412.7 + + + 404.7 + + + 398.5 + + + 396.8 + + + 396.2 + + + 394.4 + + + 392.2 + + + 391.3 + + + 389.7 + + + 389.3 + + + 389 + + + 389 + + + 389.1 + + + 389.5 + + + 390.5 + + + 391.1 + + + 391.6 + + + 392.4 + + + 393.8 + + + 397 + + + 398.5 + + + 400.4 + + + 401.8 + + + 403.8 + + + 405.2 + + + 406.1 + + + 407.3 + + + 409.2 + + + 410.1 + + + 411.1 + + + 412.4 + + + 413.3 + + + 414 + + + 415.3 + + + 417.8 + + + 418.4 + + + 419 + + + 420.2 + + + 420.9 + + + 421.2 + + + 422.2 + + + 422.5 + + + 422 + + + 421.8 + + + 421.2 + + + 421 + + + 421 + + + 420.9 + + + 420.7 + + + 420.2 + + + 420 + + + 419.8 + + + 419.1 + + + 418.9 + + + 418.8 + + + 418.4 + + + 417.5 + + + 416.9 + + + 415 + + + 413.6 + + + 407.3 + + + 407.1 + + + 406.8 + + + 406.5 + + + 404.7 + + + 403.5 + + + 401 + + + 399.7 + + + 397.6 + + + 396.9 + + + 396.6 + + + 396.3 + + + 396 + + + 395.6 + + + 394.3 + + + 391.3 + + + 390.2 + + + 389.3 + + + 386.8 + + + 385.1 + + + 383.1 + + + 380.4 + + + 378.1 + + + 375.7 + + + 373.3 + + + 370.1 + + + 369.7 + + + 369.5 + + + 369.3 + + + 368.5 + + + 367.8 + + + 367.4 + + + 367.2 + + + 366.9 + + + 366.2 + + + 366.1 + + + 365.9 + + + 365.8 + + + 365.6 + + + 365.5 + + + 365.4 + + + 365.5 + + + 365.5 + + + 365.5 + + + 365.5 + + + 365.5 + + + 365.7 + + + 365.8 + + + 366 + + + 367.6 + + + 368.6 + + + 369.2 + + + 369.7 + + + 370.6 + + + 371.3 + + + 371.9 + + + 372.7 + + + 373.4 + + + 374.7 + + + 377.1 + + + 377.7 + + + 378.2 + + + 378.8 + + + 379 + + + 379.4 + + + 379.8 + + + 379.5 + + + 379.1 + + + 378.7 + + + 378.1 + + + 377.6 + + + 377.3 + + + 376.3 + + + 375.5 + + + 374.9 + + + 374.3 + + + 372.6 + + + 372.1 + + + 371.6 + + + 370.9 + + + 370.5 + + + 369.9 + + + 369.5 + + + 369.1 + + + 367.9 + + + 367.7 + + + 367.7 + + + 367.5 + + + 367.2 + + + 367.1 + + + 366.9 + + + 367 + + + 367 + + + 367 + + + 366.9 + + + 366.9 + + + 366.8 + + + 366.6 + + + 366.8 + + + 367.1 + + + 368.1 + + + 369.1 + + + 380.7 + + + 378.7 + + + 377.1 + + + 374.8 + + + 372.5 + + + 369.7 + + + 368 + + + 365.1 + + + 362.5 + + + 362 + + + 361.6 + + + 361.2 + + + 360.6 + + + 360.1 + + + 359.8 + + + 359.4 + + + 359.2 + + + 359 + + + 358.7 + + + 358.5 + + + 358.3 + + + 358.1 + + + 357.9 + + + 357.6 + + + 357.7 + + + 358.4 + + + 359.5 + + + 360.4 + + + 364.1 + + + 369.3 + + + 373 + + + 376.4 + + + 376.7 + + + 376.5 + + + 374.1 + + + 364.7 + + + 361.4 + + + 356.9 + + + 354.4 + + + 352.6 + + + 351.7 + + + 351.2 + + + 350.9 + + + 351.6 + + + 352.2 + + + 353.3 + + + 354.3 + + + 355.3 + + + 356.9 + + + 357.8 + + + 361.1 + + + 366.6 + + + 370.2 + + + 372.5 + + + 373.5 + + + 375.5 + + + 376.6 + + + 376.8 + + + 376.6 + + + 375 + + + 372.5 + + + 370.4 + + + 366.8 + + + 363.7 + + + 360.3 + + + 357.5 + + + 355.6 + + + 348.2 + + + 344.7 + + + 342.7 + + + 341.6 + + + 337.9 + + + 337.3 + + + 336.9 + + + 336.2 + + + 335.7 + + + 335.7 + + + 335.8 + + + 335.9 + + + 336.3 + + + 336.5 + + + 336.7 + + + 336.9 + + + 337.5 + + + 337.6 + + + 337.5 + + + 336.6 + + + 336.1 + + + 335.2 + + + 332.9 + + + 331.8 + + + 331.6 + + + 331.3 + + + 331.2 + + + 331.3 + + + 331.5 + + + 332.1 + + + 333 + + + 334.7 + + + 336 + + + 338.3 + + + 339.2 + + + 340.1 + + + 341.7 + + + 344.2 + + + 346.2 + + + 351.8 + + + 358.3 + + + 371.8 + + + 374.3 + + + 378.1 + + + 380 + + + 382.1 + + + 384.3 + + + 386.4 + + + 388.2 + + + 389.3 + + + 391.2 + + + 392.1 + + + 392.5 + + + 393.2 + + + 393.9 + + + 394.8 + + + 395.1 + + + 395.2 + + + 395.4 + + + 395.6 + + + 396.2 + + + 396.6 + + + 397 + + + 397.9 + + + 398.5 + + + 400 + + + 401.7 + + + 403.4 + + + 405.4 + + + 407.6 + + + 409.8 + + + 411.2 + + + 414.9 + + + 427.7 + + + 436.1 + + + 444.5 + + + 451.2 + + + 456.3 + + + 459.3 + + + 462.3 + + + 464.2 + + + 466.5 + + + 468.8 + + + 470.5 + + + 472.3 + + + 475.8 + + + 477.3 + + + 478.9 + + + 488.5 + + + 490.9 + + + 493.1 + + + 495.6 + + + 497.7 + + + 498.5 + + + 499.3 + + + 499.6 + + + 498.8 + + + 495.5 + + + 492.4 + + + 490 + + + 487.7 + + + 484.5 + + + 483.5 + + + 480 + + + 479.5 + + + 478.6 + + + 477.5 + + + 476.6 + + + 475.1 + + + 474.4 + + + 474.2 + + + 473.8 + + + 473.7 + + + 473.6 + + + 473.6 + + + 473.6 + + + 473.7 + + + 473.9 + + + 474.1 + + + 474.2 + + + 474.1 + + + 474.1 + + + 474.1 + + + 474.1 + + + 474 + + + 473.7 + + + 473.5 + + + 472.6 + + + 471.1 + + + 469.9 + + + 459.1 + + + 456.4 + + + 454.9 + + + 454.2 + + + 454.4 + + + 454.7 + + + 455 + + + 455.4 + + + 455.8 + + + 456 + + + 456.2 + + + 456.6 + + + 457.9 + + + 458.4 + + + 460.2 + + + 461 + + + 462.4 + + + 464 + + + 464.8 + + + 465.7 + + + 466.2 + + + 466.7 + + + 467.2 + + + 469 + + + 469.4 + + + 470.5 + + + 471.5 + + + 473.1 + + + 480.3 + + + 484.7 + + + 494.7 + + + 498.1 + + + 500.5 + + + 503.8 + + + 505.8 + + + 502.9 + + + 500.4 + + + 498.3 + + + 496.2 + + + 493.4 + + + 487.1 + + + 485.4 + + + 484.4 + + + 483.3 + + + 481.4 + + + 480.2 + + + 477.2 + + + 476 + + + 473.4 + + + 472 + + + 469.9 + + + 468.8 + + + 464.3 + + + 462.2 + + + 460.5 + + + 459.6 + + + 459.4 + + + 459.5 + + + 456.1 + + + 455.7 + + + 452.9 + + + 447.1 + + + 445.3 + + + 444.1 + + + 441.1 + + + 430.6 + + + 427.9 + + + 426.1 + + + 424.1 + + + 420.7 + + + 420.4 + + + 419 + + + 418.2 + + + 418 + + + 417.2 + + + 417.1 + + + 417.2 + + + 417.3 + + + 417.6 + + + 417.8 + + + 418.2 + + + 418.2 + + + 419.2 + + + 419.2 + + + 419.3 + + + 419.3 + + + 419 + + + 418.6 + + + 415.8 + + + 410.8 + + + 405.2 + + + 402.9 + + + 395.5 + + + 392.9 + + + 391.2 + + + 387.2 + + + 385.9 + + + 382.6 + + + 381.8 + + + 381.4 + + + 380.2 + + + 379.5 + + + 378.8 + + + 377.3 + + + 376.7 + + + 376.5 + + + 375.9 + + + 375 + + + 373.6 + + + 373.3 + + + 373.1 + + + 372.9 + + + 371.5 + + + 370.2 + + + 368.6 + + + 367.8 + + + 365.3 + + + 364.3 + + + 362.8 + + + 362.4 + + + 361.1 + + + 360.9 + + + 360.5 + + + 360 + + + 359.7 + + + 359.5 + + + 359 + + + 358.7 + + + 358.5 + + + 358.5 + + + 358.4 + + + 358.5 + + + 358.6 + + + 358.7 + + + 359.4 + + + 360.8 + + + 361.3 + + + 362.9 + + + 363.6 + + + 364.8 + + + 365.8 + + + 369 + + + 370.1 + + + 370.6 + + + 371.8 + + + 373.2 + + + 373.9 + + + 374.4 + + + 374.9 + + + 375.8 + + + 378.1 + + + 383.7 + + + 386.1 + + + 386.7 + + + 387.8 + + + 388.6 + + + 389 + + + 389.2 + + + 389.6 + + + 389.8 + + + 389.1 + + + 387.2 + + + 386.5 + + + 384.5 + + + 381.5 + + + 380.5 + + + 379.8 + + + 380 + + + 380.4 + + + 380.6 + + + 380.6 + + + 380.7 + + + 380.7 + + + 380.6 + + + 380.4 + + + 380.3 + + + 380.1 + + + 379.9 + + + 379.6 + + + 379.3 + + + 378.3 + + + 377.4 + + + 376.8 + + + 374.6 + + + 373.3 + + + 371.9 + + + 370.4 + + + 368.3 + + + 368 + + + 369.2 + + + 371.1 + + + 372.6 + + + 373.1 + + + 374.9 + + + 376.3 + + + 377.3 + + + 377.9 + + + 378.1 + + + 378.1 + + + 378 + + + 377.7 + + + 376.6 + + + 376.3 + + + 375.8 + + + 375.6 + + + 375.2 + + + 374.9 + + + 374.9 + + + 375 + + + 375 + + + 375.2 + + + 375.8 + + + 378.5 + + + 380.3 + + + 381.5 + + + 382.8 + + + 383.7 + + + 384.5 + + + 385.2 + + + 386.3 + + + 388.6 + + + 389.2 + + + 392 + + + 395.3 + + + 397.4 + + + 398.4 + + + 399.5 + + + 400.9 + + + 404.3 + + + 407.1 + + + 409.7 + + + 411.5 + + + 413.2 + + + 415.4 + + + 417.8 + + + 419.7 + + + 422 + + + 423.2 + + + 423.9 + + + 424.1 + + + 424.6 + + + 425 + + + 425.1 + + + 425 + + + 424.8 + + + 424 + + + 423.8 + + + 423.8 + + + 423.7 + + + 423.9 + + + 424.1 + + + 423.9 + + + 423.7 + + + 423.2 + + + 422.5 + + + 421.5 + + + 419.3 + + + 417.4 + + + 412.8 + + + 410.2 + + + 409 + + + 408.4 + + + 407.2 + + + 406.4 + + + 405.7 + + + 405.4 + + + 405.7 + + + 405.9 + + + 407.1 + + + 407.5 + + + 408.1 + + + 408.4 + + + 408.8 + + + 409.3 + + + 410.1 + + + 410.5 + + + 410.4 + + + 410 + + + 408.3 + + + 407.9 + + + 409 + + + 410.8 + + + 416.5 + + + 420.2 + + + 421.4 + + + 421.7 + + + 422.3 + + + 422.6 + + + 423 + + + 424.3 + + + 425.9 + + + 426.6 + + + 427.6 + + + 428.4 + + + 429.1 + + + 430.4 + + + 431 + + + 431.2 + + + 431.3 + + + 431.6 + + + 432.1 + + + 432.4 + + + 432.5 + + + 432.9 + + + 433.1 + + + 433.6 + + + 434.1 + + + 434.5 + + + 434.8 + + + 435.1 + + + 435.3 + + + 435.6 + + + 435.6 + + + 436 + + + 436.6 + + + 436.9 + + + 437.2 + + + 437.3 + + + 438.1 + + + 438.1 + + + 438.1 + + + 438.2 + + + 438.4 + + + 439.2 + + + 440.1 + + + 440.5 + + + 440.9 + + + 442.4 + + + 443.8 + + + 444.8 + + + 446 + + + 449 + + + 452 + + + 453.3 + + + 454.4 + + + 454.8 + + + 458.5 + + + 459.2 + + + 459.6 + + + 462.1 + + + 462.6 + + + 463.9 + + + 464.2 + + + 465.7 + + + 466.3 + + + 466.7 + + + 467.2 + + + 468.1 + + + 468.6 + + + 478.9 + + + 479.8 + + + 480.6 + + + 482.8 + + + 489.6 + + + 502.9 + + + 509.3 + + + 518.6 + + + 523 + + + 530.4 + + + 535.8 + + + 537.8 + + + 539.3 + + + 539.9 + + + 540.5 + + + 542.4 + + + 542.9 + + + 543.3 + + + 545.2 + + + 548.9 + + + 551.3 + + + 553 + + + 553 + + + 552.8 + + + 550.5 + + + 548.4 + + + 548.1 + + + 547.3 + + + 545.1 + + + 543.2 + + + 540.7 + + + 540.4 + + + 540.2 + + + 539.8 + + + 538.2 + + + 533.1 + + + 532.4 + + + 532.1 + + + 529.9 + + + 527.3 + + + 524.9 + + + 524 + + + 521.2 + + + 521 + + + 520 + + + 519.2 + + + 516.6 + + + 515.6 + + + 513.8 + + + 512.9 + + + 512.5 + + + 511.8 + + + 509.7 + + + 509.7 + + + 509.7 + + + 513.2 + + + 514 + + + 514.7 + + + 517 + + + 518.6 + + + 519.4 + + + 520.7 + + + 521.6 + + + 522.2 + + + 522.4 + + + 522.6 + + + 522.5 + + + 522.5 + + + 522.5 + + + 521.6 + + + 520.8 + + + 516.9 + + + 516.7 + + + 514.8 + + + 511.4 + + + 504 + + + 499.8 + + + 496.6 + + + 494.5 + + + 490.6 + + + 487.3 + + + 483.1 + + + 478.1 + + + 476.1 + + + 471.7 + + + 471 + + + 468.9 + + + 466.2 + + + 463 + + + 456.6 + + + 455.2 + + + 452.8 + + + 450.6 + + + 447.4 + + + 444.4 + + + 443.4 + + + 442.6 + + + 441.2 + + + 438.3 + + + 436.1 + + + 433.8 + + + 432.6 + + + 431.6 + + + 430.5 + + + 429.4 + + + 426.5 + + + 424.8 + + + 422.3 + + + 421.3 + + + 420.7 + + + 419.3 + + + 403.5 + + + 402.4 + + + 401.8 + + + 390.4 + + + 387.5 + + + 386.6 + + + 385.3 + + + 385 + + + 384.2 + + + 384 + + + 384 + + + 383.9 + + + 384.2 + + + 384.3 + + + 384.7 + + + 385 + + + 385.1 + + + 386.1 + + + 386.5 + + + 388.8 + + + 390.2 + + + 391 + + + 391.8 + + + 393 + + + 393.9 + + + 394.7 + + + 395.8 + + + 399.3 + + + 401.3 + + + 402.2 + + + 403.8 + + + 404.9 + + + 405.9 + + + 408.3 + + + 413 + + + 422.3 + + + 425.4 + + + 428.9 + + + 436.3 + + + 441.1 + + + 445.7 + + + 456.5 + + + 457.8 + + + 458.7 + + + 459.1 + + + 461.3 + + + 461.8 + + + 462 + + + 462.3 + + + 463.4 + + + 465.1 + + + 466 + + + 466.3 + + + 468.6 + + + 470.6 + + + 473.3 + + + 477.1 + + + 479.7 + + + 483.1 + + + 487.9 + + + 490 + + + 492.4 + + + 493.1 + + + 493.5 + + + 493.8 + + + 494.1 + + + 494.3 + + + 494.4 + + + 494 + + + 494 + + + 491.8 + + + 490.8 + + + 490.2 + + + 488.8 + + + 488.3 + + + 487.7 + + + 486.5 + + + 485.8 + + + 484.7 + + + 484.2 + + + 483.8 + + + 483.3 + + + 483.1 + + + 483 + + + 482.8 + + + 481.7 + + + 482.5 + + + 485.5 + + + 487.2 + + + 488.1 + + + 492.3 + + + 497.5 + + + 499.5 + + + 504.3 + + + 506.6 + + + 508.2 + + + 511.2 + + + 513.3 + + + 515.2 + + + 517.8 + + + 522.6 + + + 524.5 + + + 525.2 + + + 527 + + + 528.6 + + + 529.7 + + + 530.8 + + + 531.5 + + + 532.2 + + + 532.8 + + + 534.1 + + + 534.9 + + + 535.5 + + + 536.7 + + + 537.1 + + + 537.6 + + + 538.7 + + + 539.5 + + + 539.9 + + + 539.9 + + + 539.9 + + + 539.9 + + + 539.9 + + + 539.9 + + + 540 + + + 540 + + + 540.4 + + + 541.1 + + + 543.4 + + + 544.1 + + + 545.4 + + + 547.8 + + + 549.8 + + + 550.5 + + + 553.3 + + + 556.7 + + + 561.7 + + + 563.8 + + + 571.7 + + + 574.7 + + + 578.1 + + + 587.5 + + + 591 + + + 592.7 + + + 615.2 + + + 618.2 + + + 619 + + + 619.7 + + + 622.7 + + + 626.1 + + + 626.9 + + + 628 + + + 629.3 + + + 630 + + + 633.3 + + + 639.4 + + + 640.3 + + + 643.1 + + + 655.5 + + + 663.1 + + + 665.4 + + + 667 + + + 674.9 + + + 676 + + + 679.5 + + + 680 + + + 683.7 + + + 685 + + + 685 + + + 686.7 + + + 686.8 + + + 686.5 + + + 685.9 + + + 685.7 + + + 683.8 + + + 682.9 + + + 679.6 + + + 676.7 + + + 675.7 + + + 672.3 + + + 667.6 + + + 666.8 + + + 664.5 + + + 664.1 + + + 664.1 + + + 662.8 + + + 662.6 + + + 662.2 + + + 662.1 + + + 661.8 + + + 661.7 + + + 661.6 + + + 661.4 + + + 661.3 + + + 661 + + + 660.9 + + + 660.6 + + + 660.5 + + + 660.1 + + + 660.1 + + + 660 + + + 659.8 + + + 659.5 + + + 659.1 + + + 657.4 + + + 655.3 + + + 653.8 + + + 650.7 + + + 648.3 + + + 647.7 + + + 645.6 + + + 644.8 + + + 644.4 + + + 643.8 + + + 643 + + + 642 + + + 639.2 + + + 637.7 + + + 637.1 + + + 631.9 + + + 630.4 + + + 629.3 + + + 627.8 + + + 623.3 + + + 620.4 + + + 610.9 + + + 608.3 + + + 607.1 + + + 603.3 + + + 600.9 + + + 599.7 + + + 598.2 + + + 594.6 + + + 590.4 + + + 586.5 + + + 584.9 + + + 582.9 + + + 581.8 + + + 580.8 + + + 579.4 + + + 578.5 + + + 577 + + + 576.2 + + + 575 + + + 573.1 + + + 570.5 + + + 566.2 + + + 564.4 + + + 560.9 + + + 557.8 + + + 551.1 + + + 549.3 + + + 545.5 + + + 543.7 + + + 542.6 + + + 541.4 + + + 539 + + + 538.1 + + + 535.5 + + + 530.8 + + + 523.9 + + + 521.1 + + + 520.6 + + + 519 + + + 518.2 + + + 517.3 + + + 517 + + + 517.1 + + + 517.5 + + + 518.3 + + + 519.2 + + + 524.5 + + + 525.2 + + + 525.8 + + + 527.2 + + + 527.8 + + + 528.8 + + + 529.8 + + + 530.8 + + + 530.8 + + + 530.9 + + + 531.6 + + + 533.4 + + + 534.2 + + + 535.3 + + + 538.7 + + + 539.9 + + + 545.6 + + + 546.7 + + + 548.4 + + + 549.3 + + + 550 + + + 550.2 + + + 550.3 + + + 550.4 + + + 550.6 + + + 551.3 + + + 551.8 + + + 552.8 + + + 553.7 + + + 554.8 + + + 555.6 + + + 556 + + + 557 + + + 557.6 + + + 558.3 + + + 558.7 + + + 559.8 + + + 561.4 + + + 563.8 + + + 564.4 + + + 565.9 + + + 569.1 + + + 571.4 + + + 573.2 + + + 573.9 + + + 581.3 + + + 586.6 + + + 590.2 + + + 594.7 + + + 598.8 + + + 600.5 + + + 606.1 + + + 607.3 + + + 608.9 + + + 610.2 + + + 612.1 + + + 612.6 + + + 613.1 + + + 613.3 + + + 613.3 + + + 613.6 + + + 614.3 + + + 614.6 + + + 614.6 + + + 614.1 + + + 612.8 + + + 612.4 + + + 604.3 + + + 600.3 + + + 592.1 + + + 590.1 + + + 586.4 + + + 585.3 + + + 585.1 + + + 584.8 + + + 584.2 + + + 583.8 + + + 583 + + + 582.4 + + + 581.8 + + + 580.7 + + + 580.1 + + + 579.2 + + + 578 + + + 577.4 + + + 576.2 + + + 575.6 + + + 574.5 + + + 573.7 + + + 572.7 + + + 572.1 + + + 571.9 + + + 571.9 + + + 571.3 + + + 569.3 + + + 568 + + + 567 + + + 566.2 + + + 565.7 + + + 565.1 + + + 565.3 + + + 565.5 + + + 566.8 + + + 569.5 + + + 571 + + + 571.9 + + + 572.7 + + + 574.4 + + + 575.5 + + + 576 + + + 576.5 + + + 577.5 + + + 578.6 + + + 579.5 + + + 579.6 + + + 579.1 + + + 576.5 + + + 575.8 + + + 575.2 + + + 572.1 + + + 571.3 + + + 570.5 + + + 568.7 + + + 564.9 + + + 563.4 + + + 561.3 + + + 559.1 + + + 557.2 + + + 551.7 + + + 550.2 + + + 545.3 + + + 540.6 + + + 537.1 + + + 534.3 + + + 532.8 + + + 525.7 + + + 525 + + + 523.1 + + + 522.3 + + + 520.6 + + + 519.7 + + + 518.6 + + + 517 + + + 516 + + + 511.6 + + + 511.2 + + + 510.8 + + + 509.2 + + + 508.2 + + + 507.4 + + + 506.7 + + + 506.2 + + + 504.8 + + + 503.7 + + + 502.8 + + + 501.1 + + + 499.6 + + + 496.5 + + + 495.6 + + + 495.2 + + + 494.9 + + + 494.9 + + + 494.9 + + + 495.5 + + + 496.1 + + + 497.1 + + + 498.6 + + + 498.8 + + + 499.1 + + + 499.9 + + + 500.3 + + + 500.5 + + + 501.3 + + + 503.1 + + + 505.2 + + + 505.5 + + + 506 + + + 506.5 + + + 506.7 + + + 506.7 + + + 506.7 + + + 506.4 + + + 506.3 + + + 506.2 + + + 505.1 + + + 504.9 + + + 504.4 + + + 503.4 + + + 502.4 + + + 501.5 + + + 500.2 + + + 499.2 + + + 497 + + + 494.5 + + + 492.6 + + + 490.2 + + + 488.7 + + + 488.3 + + + 487.9 + + + 486.2 + + + 485.3 + + + 483.7 + + + 480 + + + 475.5 + + + 474.8 + + + 469.9 + + + 469.1 + + + 468.4 + + + 463.5 + + + 462.9 + + + 462.7 + + + 462.7 + + + 462 + + + 461.7 + + + 461.3 + + + 460.1 + + + 459.9 + + + 459.6 + + + 459.4 + + + 459.1 + + + 458.5 + + + 458 + + + 457.6 + + + 457.2 + + + 456.3 + + + 456 + + + 455.7 + + + 455.3 + + + 454.8 + + + 454.4 + + + 453.4 + + + 452.5 + + + 451.5 + + + 450.1 + + + 449.5 + + + 448.2 + + + 444.6 + + + 442.1 + + + 441.1 + + + 437.5 + + + 435.7 + + + 430.1 + + + 429.6 + + + 425.9 + + + 424.8 + + + 424.1 + + + 423.2 + + + 422.9 + + + 422.9 + + + 422.8 + + + 422.7 + + + 422.6 + + + 422.5 + + + 422.6 + + + 422.8 + + + 423 + + + 423.5 + + + 424 + + + 424.6 + + + 425.3 + + + 426.3 + + + 427.3 + + + 429.4 + + + 430.7 + + + 431.7 + + + 432.6 + + + 435.3 + + + 436.5 + + + 443.8 + + + 446.3 + + + 447.1 + + + 447.9 + + + 449.4 + + + 449.7 + + + 449.7 + + + 448.5 + + + 446.7 + + + 445.8 + + + 445.5 + + + 446.1 + + + 446.2 + + + 446.7 + + + 446.8 + + + 446.8 + + + 446.7 + + + 446.1 + + + 446.1 + + + 446.6 + + + 447.4 + + + 449.6 + + + 451.7 + + + 452.3 + + + 455.4 + + + 460 + + + 460.1 + + + 461.4 + + + 461.5 + + + 460.7 + + + 459.8 + + + 459.5 + + + 458.6 + + + 457.2 + + + 455.9 + + + 455.7 + + + 455.2 + + + 454.8 + + + 454.1 + + + 454.1 + + + 453.9 + + + 453.9 + + + 453.9 + + + 453.8 + + + 453.7 + + + 453.6 + + + 453.6 + + + 453.8 + + + 454 + + + 454.5 + + + 455.4 + + + 455.8 + + + 456.1 + + + 456.9 + + + 458.5 + + + 459.7 + + + 460.4 + + + 462.4 + + + 463.1 + + + 464.4 + + + 465.2 + + + 465.8 + + + 465.8 + + + 466.1 + + + 466.6 + + + 466.6 + + + 466.9 + + + 467.7 + + + 467.8 + + + 467.1 + + + 467 + + + 466.8 + + + 466.6 + + + 466.4 + + + 466.1 + + + 465.8 + + + 465.7 + + + 465.3 + + + 465 + + + 464.8 + + + 464.9 + + + 464.8 + + + 464.9 + + + 464.9 + + + 465 + + + 465.1 + + + 465.2 + + + 465.3 + + + 465.3 + + + 465.7 + + + 465.9 + + + 466 + + + 466.1 + + + 466.2 + + + 466.6 + + + 466.8 + + + 469.5 + + + 471.2 + + + 471.3 + + + 471.4 + + + 473.5 + + + 475 + + + 479.8 + + + 480.3 + + + 481.2 + + + 483.9 + + + 484.3 + + + 485.3 + + + 486.2 + + + 487.1 + + + 489.6 + + + 493 + + + 501.7 + + + 502.2 + + + 502.6 + + + 503.2 + + + 503.9 + + + 506.1 + + + 508.1 + + + 509.3 + + + 510.1 + + + 511 + + + 511.2 + + + 516.5 + + + 518.3 + + + 520.7 + + + 523.1 + + + 523.8 + + + 524.4 + + + 524.7 + + + 525.8 + + + 526.1 + + + 526.4 + + + 526.8 + + + 527.3 + + + 529.6 + + + 532.3 + + + 532.4 + + + 532.4 + + + 532.3 + + + 532.1 + + + 532.2 + + + 532.4 + + + 533.7 + + + 534 + + + 534.4 + + + 534.5 + + + 534.4 + + + 532.5 + + + 529.2 + + + 525.3 + + + 522.6 + + + 521.3 + + + 519.6 + + + 518 + + + 516.1 + + + 515.6 + + + 514.3 + + + 513.2 + + + 511.9 + + + 512.1 + + + 512.5 + + + 513.2 + + + 513.6 + + + 513.9 + + + 515 + + + 516 + + + 516.8 + + + 516.9 + + + 517.2 + + + 517.6 + + + 517.7 + + + 518.6 + + + 520.7 + + + 521 + + + 521.4 + + + 521.9 + + + 522.8 + + + 523.4 + + + 523.8 + + + 524.5 + + + 524.9 + + + 526.3 + + + 526.8 + + + 527.5 + + + 532.2 + + + 533.2 + + + 536.4 + + + 536.7 + + + 538.4 + + + 538.9 + + + 540.1 + + + 541.5 + + + 542.9 + + + 544.1 + + + 545.5 + + + 548.6 + + + 551.6 + + + 558.7 + + + 564.7 + + + 568.4 + + + 571.2 + + + 572.6 + + + 573 + + + 573.2 + + + 573.2 + + + 572.9 + + + 567.5 + + + 564 + + + 563.5 + + + 560.3 + + + 559.9 + + + 558.6 + + + 557.6 + + + 554.9 + + + 553.3 + + + 551.9 + + + 550.3 + + + 550.2 + + + 550.2 + + + 550.4 + + + 549 + + + 548.8 + + + 548.6 + + + 548.5 + + + 548.4 + + + 548.3 + + + 547.8 + + + 547.5 + + + 547.2 + + + 546.5 + + + 545.6 + + + 543.9 + + + 539.3 + + + 529.8 + + + 527.2 + + + 525.7 + + + 523.9 + + + 521.8 + + + 521.5 + + + 521 + + + 520.9 + + + 520.6 + + + 519.1 + + + 518.4 + + + 518.4 + + + + \ No newline at end of file diff --git a/motomate/src/lib/db/demo-seed.ts b/motomate/src/lib/db/demo-seed.ts new file mode 100644 index 0000000..8c1d38a --- /dev/null +++ b/motomate/src/lib/db/demo-seed.ts @@ -0,0 +1,421 @@ +import { mkdirSync, existsSync, copyFileSync } from 'node:fs'; +import { fileURLToPath } from 'node:url'; +import { join, dirname } from 'node:path'; +import { hash } from '@node-rs/argon2'; +import { eq, count } from 'drizzle-orm'; +import { db } from './index.js'; +import { getUserByEmail } from './repositories/users.js'; +import { + users, + vehicles, + task_templates, + active_trackers, + service_logs, + finance_transactions, + workflow_rules, + documents, + travels, + odometer_logs +} from './schema.js'; +import { generateId } from '../utils/id.js'; + +const ARGON2_OPTS = { memoryCost: 19456, timeCost: 2, outputLen: 32, parallelism: 1 }; + +const ASSETS_DIR = join(dirname(fileURLToPath(import.meta.url)), 'demo-assets'); + +function copyAssetToStorage(assetName: string, storageKey: string): boolean { + const storagePath = process.env.STORAGE_LOCAL_PATH ?? './uploads'; + const destPath = join(storagePath, storageKey); + if (existsSync(destPath)) return true; + try { + const destDir = dirname(destPath); + if (!existsSync(destDir)) mkdirSync(destDir, { recursive: true }); + copyFileSync(join(ASSETS_DIR, assetName), destPath); + return true; + } catch { + return false; + } +} + +async function patchDemoContent(userId: string, vehicleId: string): Promise { + // Finance: dealer 1000 km service + const [{ value: ftCount }] = await db + .select({ value: count() }) + .from(finance_transactions) + .where(eq(finance_transactions.vehicle_id, vehicleId)); + if (ftCount < 3) { + await db.insert(finance_transactions).values({ + id: generateId(), + vehicle_id: vehicleId, + user_id: userId, + category: 'maintenance', + amount_cents: 28500, + currency: 'EUR', + notes: '1000 km dealer service. Oil, filters, chain & brake inspection', + performed_at: '2022-05-20', + odometer_at_transaction: 1000, + measurement_at_transaction: 1000, + measurement_unit: 'km' + }); + } + + // Odometer logs + const [{ value: odoCount }] = await db + .select({ value: count() }) + .from(odometer_logs) + .where(eq(odometer_logs.vehicle_id, vehicleId)); + if (odoCount < 3) { + await db.insert(odometer_logs).values([ + { + id: generateId(), + vehicle_id: vehicleId, + user_id: userId, + odometer: 14200, + measurement: 14200, + measurement_unit: 'km', + kind: 'odometer', + recorded_at: '2025-01-15' + }, + { + id: generateId(), + vehicle_id: vehicleId, + user_id: userId, + odometer: 16800, + measurement: 16800, + measurement_unit: 'km', + kind: 'odometer', + recorded_at: '2025-07-10' + }, + { + id: generateId(), + vehicle_id: vehicleId, + user_id: userId, + odometer: 17900, + measurement: 17900, + measurement_unit: 'km', + remark: 'Spring prep', + kind: 'odometer', + recorded_at: '2026-03-01' + } + ]); + } + + // Always ensure asset files are present in uploads (idempotent & skips if file exists) + const pdfKey = 'demo/dealer-service-1000km.pdf'; + const gpxKey = 'demo/eifel-route.gpx'; + copyAssetToStorage('dealer-service-1000km.pdf', pdfKey); + copyAssetToStorage('eifel-route.gpx', gpxKey); + + // PDF document DB record + const [{ value: docCount }] = await db + .select({ value: count() }) + .from(documents) + .where(eq(documents.vehicle_id, vehicleId)); + if (docCount === 0) { + await db.insert(documents).values({ + id: generateId(), + vehicle_id: vehicleId, + user_id: userId, + name: 'dealer-service-invoice-1000km.pdf', + title: 'Dealer service invoice; 1000 km', + doc_type: 'service', + storage_key: pdfKey, + mime_type: 'application/pdf', + size_bytes: 66156 + }); + } + + // GPX travel DB records + const [{ value: travelCount }] = await db + .select({ value: count() }) + .from(travels) + .where(eq(travels.vehicle_id, vehicleId)); + if (travelCount === 0) { + const gpxDocId = generateId(); + await db.insert(documents).values({ + id: gpxDocId, + vehicle_id: vehicleId, + user_id: userId, + name: 'eifel-route.gpx', + title: 'Eifel Route, Germany', + doc_type: 'route', + storage_key: gpxKey, + mime_type: 'application/gpx+xml', + size_bytes: 384624 + }); + await db.insert(travels).values({ + id: generateId(), + vehicle_id: vehicleId, + user_id: userId, + title: 'Germany, Eifel', + start_date: '2025-06-14', + duration_days: 2, + remark: 'Weekend tour through the Eifel region', + total_expenses_cents: 18000, + currency: 'EUR', + gpx_document_ids: [gpxDocId, null] + }); + } +} + +async function patchWorkflowRules(userId: string, vehicleId: string): Promise { + const existing = await db.query.workflow_rules.findFirst({ + where: eq(workflow_rules.user_id, userId) + }); + if (existing) return; + + await db.insert(workflow_rules).values([ + { + id: generateId(), + user_id: userId, + vehicle_id: vehicleId, + name: 'Oil change overdue', + description: 'Notify when oil change is past due by 500 km', + trigger: { type: 'odometer_overdue', km_past: 500 }, + actions: { + title: 'Oil change overdue', + body: 'Your Honda CB500F is due for an oil change.' + }, + enabled: true + }, + { + id: generateId(), + user_id: userId, + vehicle_id: vehicleId, + name: 'Upcoming service reminder', + description: 'Notify 7 days before a tracker is due by date', + trigger: { type: 'date_upcoming', days_before: 7 }, + actions: { title: 'Service reminder', body: 'A scheduled service is coming up in 7 days.' }, + enabled: true + }, + { + id: generateId(), + user_id: userId, + vehicle_id: null, + name: 'No odometer update', + description: 'Notify if no odometer reading logged in 30 days', + trigger: { type: 'no_odometer_update', days: 30 }, + actions: { + title: 'No recent activity', + body: 'No odometer update logged in the last 30 days.' + }, + enabled: false + } + ]); +} + +export async function seedDemo(): Promise { + const existing = await getUserByEmail('demo@motomate.local'); + if (existing) { + const vehicle = await db.query.vehicles.findFirst({ + where: eq(vehicles.user_id, existing.id) + }); + if (vehicle) { + await patchWorkflowRules(existing.id, vehicle.id); + await patchDemoContent(existing.id, vehicle.id); + } + return; + } + + const passwordHash = await hash('password123', ARGON2_OPTS); + const userId = generateId(); + const vehicleId = generateId(); + const oilTmplId = generateId(); + const chainTmplId = generateId(); + const tireTmplId = generateId(); + const oilTrackerId = generateId(); + const chainTrackerId = generateId(); + const tireTrackerId = generateId(); + + await db.insert(users).values({ + id: userId, + email: 'demo@motomate.local', + password_hash: passwordHash, + onboarding_done: true, + settings: { + theme: 'system', + currency: 'EUR', + odometer_unit: 'km', + locale: 'en', + avatar_seed: generateId() + } + }); + + await db.insert(vehicles).values({ + id: vehicleId, + user_id: userId, + type: 'motorcycle', + name: 'Honda CB500F', + make: 'Honda', + model: 'CB500F', + year: 2021, + current_odometer: 18400, + current_measurement: 18400, + current_measurement_unit: 'km', + odometer_unit: 'km', + meta: { avatar_emoji: '๐Ÿ๏ธ' } + }); + + await db.insert(task_templates).values([ + { + id: oilTmplId, + user_id: userId, + vehicle_id: vehicleId, + name: 'Oil & Filter Change', + category: 'oil', + description: 'Engine oil and oil filter replacement', + interval_km: 10000, + interval_measurement: 10000, + interval_unit: 'km', + interval_months: 12, + is_preset: true + }, + { + id: chainTmplId, + user_id: userId, + vehicle_id: vehicleId, + name: 'Chain Clean & Lube', + category: 'chain', + description: 'Clean and lubricate the chain', + interval_km: 500, + interval_measurement: 500, + interval_unit: 'km', + is_preset: true + }, + { + id: tireTmplId, + user_id: userId, + vehicle_id: vehicleId, + name: 'Tire Pressure & Wear Check', + category: 'tire', + description: 'Check tyre pressure and inspect tread depth', + interval_months: 1, + is_preset: true + } + ]); + + await db.insert(active_trackers).values([ + { + id: oilTrackerId, + vehicle_id: vehicleId, + template_id: oilTmplId, + last_done_at: '2025-04-10', + last_done_odometer: 8000, + last_done_measurement: 8000, + next_due_odometer: 18000, + next_due_measurement: 18000, + next_due_at: '2026-04-10', + measurement_unit: 'km', + status: 'overdue' + }, + { + id: chainTrackerId, + vehicle_id: vehicleId, + template_id: chainTmplId, + last_done_at: '2026-04-15', + last_done_odometer: 17950, + last_done_measurement: 17950, + next_due_odometer: 18450, + next_due_measurement: 18450, + measurement_unit: 'km', + status: 'due' + }, + { + id: tireTrackerId, + vehicle_id: vehicleId, + template_id: tireTmplId, + last_done_at: '2026-05-05', + next_due_at: '2026-06-05', + status: 'ok' + } + ]); + + await db.insert(service_logs).values([ + { + id: generateId(), + vehicle_id: vehicleId, + performed_at: '2024-10-15', + odometer_at_service: 3000, + measurement_at_service: 3000, + measurement_unit: 'km', + cost_cents: 4200, + currency: 'EUR', + notes: 'Oil & Filter Change' + }, + { + id: generateId(), + vehicle_id: vehicleId, + tracker_id: oilTrackerId, + performed_at: '2025-04-10', + odometer_at_service: 8000, + measurement_at_service: 8000, + measurement_unit: 'km', + cost_cents: 4500, + currency: 'EUR', + notes: 'Oil & Filter Change' + }, + { + id: generateId(), + vehicle_id: vehicleId, + tracker_id: chainTrackerId, + performed_at: '2025-06-20', + odometer_at_service: 10500, + measurement_at_service: 10500, + measurement_unit: 'km', + cost_cents: 800, + currency: 'EUR', + notes: 'Chain Clean & Lube' + }, + { + id: generateId(), + vehicle_id: vehicleId, + tracker_id: chainTrackerId, + performed_at: '2025-09-15', + odometer_at_service: 13200, + measurement_at_service: 13200, + measurement_unit: 'km', + cost_cents: 800, + currency: 'EUR', + notes: 'Chain Clean & Lube' + }, + { + id: generateId(), + vehicle_id: vehicleId, + tracker_id: chainTrackerId, + performed_at: '2026-04-15', + odometer_at_service: 17950, + measurement_at_service: 17950, + measurement_unit: 'km', + cost_cents: 800, + currency: 'EUR', + notes: 'Chain Clean & Lube' + } + ]); + + await db.insert(finance_transactions).values([ + { + id: generateId(), + vehicle_id: vehicleId, + user_id: userId, + category: 'fuel', + amount_cents: 6000, + currency: 'EUR', + notes: 'Fuel fill-up', + performed_at: '2026-02-10' + }, + { + id: generateId(), + vehicle_id: vehicleId, + user_id: userId, + category: 'accessories', + amount_cents: 15000, + currency: 'EUR', + notes: 'Handlebar grips & bar end weights', + performed_at: '2025-11-05' + } + ]); + + await patchWorkflowRules(userId, vehicleId); + await patchDemoContent(userId, vehicleId); + console.log('[motomate] Demo data seeded (demo@motomate.local / password123)'); +} diff --git a/motomate/src/lib/utils/format.ts b/motomate/src/lib/utils/format.ts index 4ed466d..8bd0859 100644 --- a/motomate/src/lib/utils/format.ts +++ b/motomate/src/lib/utils/format.ts @@ -1,12 +1,8 @@ -/** - * Locale-aware formatting utilities. - * All functions accept a locale string (e.g. 'en', 'de', 'fr') from user settings. - */ - +/* Utility functions for formatting numbers, dates, currencies, etc. in a locale-aware way */ const _numFmtCache = new Map(); const _currFmtCache = new Map(); -/** Format an odometer or plain integer with thousand-separators. */ +/* Format odometer with thousand-seperators */ export function formatNumber(value: number, locale = 'en'): string { let fmt = _numFmtCache.get(locale); if (!fmt) { @@ -16,12 +12,12 @@ export function formatNumber(value: number, locale = 'en'): string { return fmt.format(value); } -/** Format a measurement reading with its stored unit token. */ +/* Format a measurement reading with unit token */ export function formatMeasurement(value: number, unit: string, locale = 'en'): string { return `${formatNumber(value, locale)} ${unit}`; } -/** Format cost stored in cents as a locale-aware currency string. */ +/* Format cost stored in cents as a locale-aware currency string */ export function formatCurrency(cents: number, currency = 'EUR', locale = 'en'): string { const key = `${locale}:${currency}`; let fmt = _currFmtCache.get(key); @@ -37,16 +33,16 @@ export function formatCurrency(cents: number, currency = 'EUR', locale = 'en'): return fmt.format(cents / 100); } -/** Format a YYYY-MM-DD string as a short date (e.g. "12 Jan" or "12 Jan '25"). */ +/* Format a date string as "5 Jan 2026", omitting the year if its the current yearr */ export function formatDateShort(dateStr: string, locale = 'en'): string { - const d = new Date(dateStr + 'T00:00:00'); // force local midnight parse + const d = new Date(dateStr + 'T00:00:00'); const now = new Date(); const currentYear = now.getFullYear(); const targetYear = d.getFullYear(); const nextJan1 = new Date(currentYear + 1, 0, 1); const monthsDiff = (d.getTime() - now.getTime()) / (30.44 * 24 * 60 * 60 * 1000); const includeYear = - targetYear > currentYear || d.getTime() > nextJan1.getTime() || monthsDiff > 6; + targetYear !== currentYear || d.getTime() > nextJan1.getTime() || monthsDiff > 6; const opts: Intl.DateTimeFormatOptions = { day: 'numeric', month: 'short', @@ -55,17 +51,13 @@ export function formatDateShort(dateStr: string, locale = 'en'): string { return d.toLocaleDateString(locale, opts); } -/** Format a YYYY-MM-DD string as a long date (e.g. "12 January 2025"). */ +/* Format a date string as "5 January 2026" */ export function formatDateLong(dateStr: string, locale = 'en'): string { const d = new Date(dateStr + 'T00:00:00'); return d.toLocaleDateString(locale, { day: 'numeric', month: 'long', year: 'numeric' }); } -/** - * Format a datetime string (SQLite `datetime('now')` returns "YYYY-MM-DD HH:MM:SS") - * as a short date + time, e.g. "5 Jan 2026, 14:30". - * Pass a timezone IANA string (e.g. "Europe/Amsterdam") to display in that zone. - */ +/* Format a datetime string as "5 Jan 2026, 14:30" */ export function formatDateTime(dateStr: string, locale = 'en', timezone?: string): string { // SQLite stores datetimes without 'T'; make ISO-compatible before parsing const iso = dateStr.includes('T') ? dateStr : dateStr.replace(' ', 'T'); @@ -82,7 +74,7 @@ export function formatDateTime(dateStr: string, locale = 'en', timezone?: string }); } -/** Format a YYYY-MM key as "Month Year" (e.g. "January 2025"). */ +/* Format a year-month string like "2024-01" as "January 2024" */ export function formatYearMonth(ym: string, locale = 'en'): string { const [y, m] = ym.split('-'); return new Date(+y, +m - 1).toLocaleDateString(locale, { month: 'long', year: 'numeric' }); diff --git a/motomate/src/routes/(app)/+layout.server.ts b/motomate/src/routes/(app)/+layout.server.ts index 5999ef4..9395d2a 100644 --- a/motomate/src/routes/(app)/+layout.server.ts +++ b/motomate/src/routes/(app)/+layout.server.ts @@ -1,4 +1,5 @@ import { redirect } from '@sveltejs/kit'; +import { env } from '$env/dynamic/public'; import type { LayoutServerLoad } from './$types'; import { getVehiclesByUser } from '$lib/db/repositories/vehicles.js'; import { getUnreadCount } from '$lib/workflow/channels/inapp.js'; @@ -25,5 +26,10 @@ export const load: LayoutServerLoad = async ({ locals, url }) => { const unreadCount = await getUnreadCount(locals.user.id); - return { user: locals.user, vehicles, unreadCount }; + return { + user: locals.user, + vehicles, + unreadCount, + demoMode: env.PUBLIC_DEMO_ENABLED === 'true' + }; }; diff --git a/motomate/src/routes/(app)/+layout.svelte b/motomate/src/routes/(app)/+layout.svelte index 9ab56f3..fe95697 100644 --- a/motomate/src/routes/(app)/+layout.svelte +++ b/motomate/src/routes/(app)/+layout.svelte @@ -25,7 +25,7 @@ let { children, data } = $props<{ children: import('svelte').Snippet; - data: { user: any; vehicles: NavVehicle[] }; + data: { user: any; vehicles: NavVehicle[]; demoMode?: boolean }; }>(); // Initialize i18n with user's locale preference @@ -220,6 +220,11 @@
+ {#if data.demoMode} +
+ Demo mode: changes are disabled · intended for demonstration purposes only. +
+ {/if}