Skip to content

Repository files navigation

NeedyPet (Nuxt rebuild)

Mobile-first rebuild of NeedyPet — a warm, pastel pet-care app where owners and caretakers share a pet's daily care tasks. This is the Nuxt 4 successor to the legacy Vue/Vite + Express + MongoDB app (kept read-only next door as reference).

Stack: Nuxt 4 · Vue 3 · TypeScript · Tailwind CSS v4 · Nuxt server routes · Drizzle ORM · SQLite (bun:sqlite locally, libSQL/Turso-ready for production) · Bun. Bun-native and Web-standard throughout: bun:sqlite drives the database, Bun.password (argon2id) hashes passwords, Bun.write/Bun.file handle uploads, Web Crypto powers tokens and R2 request signing, and the test suite runs under bun --bun vitest — no node:crypto, no Node runtime required.

Supported browsers: Chrome/Edge 111+, Firefox 121+, Safari and iOS 16.4+, plus the Android and Samsung Internet equivalents. The matrix lives in the browserslist field of package.json and drives the Lightning CSS build targets (docs/architecture.md).

Features

  • Cookie-session auth (nuxt-auth-utils) with email confirmation, revocable sessions, password reset, account deletion with cascades, and durable rate limiting on auth and password-verifying endpoints (docs/security-model.md)
  • Pets with preset portraits or an uploaded photo (chosen when creating or editing a pet, magic-byte validated, local disk in dev or Cloudflare R2 in production, behind one storage abstraction — see docs/deployment.md)
  • Daily care tasks (needs): max 10/day, pause/resume, lazy day rollover in the owner's timezone; the dashboard shows each pet's done/total progress for today
  • Care records: full and partial logs with auto-completion, manual time-of-day, edit/delete with an owner/caretaker permission matrix
  • Caretaker management and caretaker self-removal
  • Care diary (paginated history) and a weekly stats page (streak, per-day counts, per-category totals)
  • Opt-in daily email digest of unfinished care tasks, sent per user on their own local evening via a secret-guarded cron endpoint (docs/deployment.md)
  • Legacy JSON bundle importer (docs/migration.md)
  • Installable PWA: web manifest + Workbox service worker (auto-updating), maskable icons, and an offline fallback page — API responses are never cached (docs/deployment.md)
  • English + Finnish UI (vue-i18n), language stored on the user profile and switchable from Profile — see Internationalization below

Environment variables

Variable Purpose
NUXT_SESSION_PASSWORD 32+ char secret sealing the session cookie (required)
NUXT_SITE_URL canonical HTTPS origin for emailed links (required in production)
NUXT_RATE_LIMIT_TRUST_PROXY true only behind the documented single trusted edge proxy (default false)
NUXT_DB_FILE local SQLite path (default .data/needypet.sqlite)
NUXT_DB_URL set in production (a libsql:// Turso URL) → uses the remote libSQL DB (docs/deployment.md)
NUXT_DB_AUTH_TOKEN Turso auth token, required alongside a libsql:// URL
NUXT_MAILER_PROVIDER / NUXT_MAILER_API_KEY / NUXT_MAILER_FROM production requires resend + key + sender; an unset provider uses console mail only in dev
NUXT_UPLOADS_PROVIDER local (default) or r2 (Cloudflare R2) for pet photos (docs/deployment.md)
NUXT_UPLOADS_DIR pet photo directory for the local storage provider (default .data/uploads)
NUXT_UPLOADS_R2_ENDPOINT / NUXT_UPLOADS_R2_ACCESS_KEY_ID / NUXT_UPLOADS_R2_SECRET_ACCESS_KEY / NUXT_UPLOADS_R2_BUCKET / NUXT_UPLOADS_R2_PUBLIC_BASE_URL Cloudflare R2 config (required when the provider is r2)
NUXT_DIGEST_SECRET secret guarding the daily-digest cron endpoint; empty = disabled
NUXT_DIGEST_HOUR local hour (0–23) each user must reach before their digest sends (default 18)

Full production setup (build, migrations, storage, digest cron) lives in docs/deployment.md.

Layout

  • app/ — pages, layouts, components (mobile-first, bottom navigation)
  • server/api/ — session-cookie-authenticated API routes
  • server/db/ — Drizzle schema (schema.sqlite.ts), SQLite migrations, DB client (index.ts, with a Turso/libSQL seam), seed, legacy importer
  • shared/ — domain types, date/measurement/stats/pet-image utilities, zod schemas used by both client and server
  • tests/unit/ — pure-function vitest tests for the shared utilities; tests/integration/ — endpoint tests that drive the built server over HTTP (permission matrix, rollover, record recompute, auth and profile flows)
  • public/ — static assets and PWA icons (favicon.ico, pwa-192x192.png, pwa-512x512.png, maskable-512x512.png, apple-touch-icon.png); the manifest and service worker are generated at build time by @vite-pwa/nuxt
  • docs/architecture.md (system map and invariants), domain-model.md (entities, permissions, date rules), security-model.md (auth and threat model), testing-strategy.md, deployment.md (production environment, database, storage, digest cron, PWA), migration.md (legacy import contract) and decisions/ (architecture decision records)
  • tasks/backlog.md (roadmap) and implementation-order.md

Internationalization

The UI ships in English (default) and Finnish, using vue-i18n directly as a Nuxt plugin (app/plugins/i18n.ts) rather than @nuxtjs/i18n — this is an auth app with no need for per-locale routing or SEO, and no language ever appears in the URL. Messages live in app/i18n/en.ts and app/i18n/fi.ts (namespaced: common, nav, auth, pets, needs, records, caretakers, profile, stats, offline, errors); the Finnish copy is a transcreation, keeping the warm 🐾 tone rather than a literal translation. Finnish plural rules and named interpolation are handled by vue-i18n (e.g. task counts, care-team announcements).

  • The active language is stored on the user's profile (users.locale, default 'en') and changed from the Profile page. There is no browser-language autodetect.
  • The SSR i18n plugin reads the locale from the session (cached on the session payload), so the server and client render the same language with no hydration-time flicker. Signed-out pages (landing / login / register) are always English.
  • <html lang> and the in-app date/weekday labels (formatted via Temporal's locale-aware toLocaleString) follow the active locale; the PWA manifest lang stays en (it is generated once at build time). The daily digest email is localized to the recipient's locale; confirmation/reset emails stay English.
  • A unit test (tests/unit/i18n.spec.ts) enforces en/fi key parity, so a new key added to one locale but not the other fails the suite.

Domain rules worth knowing

  • Date-only values (birthday, need dateFor) are YYYY-MM-DD strings on the pet owner's local day and are compared as strings — never shifted through a browser timezone. Care record dates are full UTC timestamps with the acting user's IANA timezone stored for audit.
  • Every need and care record has exactly one measurement: duration (1–1440 minutes) or quantity (1–100,000 ml/g). A record must match its parent need's measurement type, so that type is fixed once the need is created. Completion is derived from the records, so editing a target recomputes it.
  • Owners control everything; caretakers see only their assigned pets and can view, log, and edit/delete only their own records. Rolled-over (archived) days are frozen.
  • Pet images are the presets dog, cat or bunny (anything unknown coerces to cat) or an uploaded JPEG/PNG/WebP photo.

Releases

Packages

Contributors

Languages