Souls
Animal Registration & Care Platform
A multi-portal platform for registering animals, managing medical records, tracking ownership transfers, and coordinating adoptions and lost-and-found reports.
About the Project
Souls is a four-portal web platform for animal registration and care: a public website, a member (guardian) dashboard, a workspace dashboard for vets and shelters, and an admin panel backed by a single NestJS API.
Animal owners registering pets, veterinary clinics and shelters managing records, and platform administrators overseeing transfers, adoptions, and lost-and-found cases.
To give every registered animal a persistent digital identity — covering medical history, ownership chain, vaccinations, and adoption or loss events — accessible across all stakeholder types through role-separated interfaces.
What I Built
Designed and built the NestJS API: 18 feature modules, Prisma schemas across 12 files, JWT + session auth with Google OAuth, BullMQ queue workers, and DigitalOcean Spaces file storage.
Modelled the MongoDB schema using Prisma with split schema files covering animals, medical records, vaccinations, transfers, adoptions, lost-and-found reports, and a multi-channel notification system.
Built four separate React apps: the TanStack Router public website, and three Vite + React Router dashboards (member, workspace, admin) each with Tailwind, Radix UI, and React Query.
Configured CORS for four deployed Vercel frontends, PM2 ecosystem for production NestJS, DigitalOcean Spaces CDN for uploaded files, and Redis with graceful degradation when unavailable.
What It Can Do
Animal registration with unique UID, photo gallery, breed/type/color classification, tag types, and behavior profiles
Medical record system supporting check-ups, vaccinations, surgeries, lab tests, dental visits, and structured medication schedules with prescription details
Ownership transfer workflow tracking full chain of custody with type (ownership, custody, adoption, surrender) and document uploads
Multi-channel notification delivery via BullMQ queues with per-user preference resolution, quiet-hours scheduling, and idempotency-keyed delivery tracking
Engineering Deep Dive
Click any card to read the full technical detail.
Challenges & Trade-offs
The real decisions behind the architecture.
Reference data (animal types, breeds, colors, tag types) needed fast validation on every animal write without a DB query per field.
Cache all active reference IDs into Redis sets and hashes at startup with in-memory fallback Sets/Maps. Validation is a single Redis SISMEMBER or HGET call.
Cache can drift after admin updates. A refreshAllCache() method exists to force a reload, but automated invalidation is not wired up — a stale write window exists until manual or restart-triggered refresh.
Notification delivery needs retry logic, scheduling, quiet-hours delay, and delivery tracking without blocking the HTTP request.
Use BullMQ with exponential backoff (5 attempts, 3s base delay) per delivery record. Delivery records hold idempotency keys and processing leases. A 60-second reconcile loop re-enqueues stuck deliveries.
Requires Redis in production. The code enforces this: Redis unavailability throws a FATAL error in production but degrades gracefully in development, disabling queue features.
Four frontends need CORS access to a single API while preventing arbitrary origins.
CORS is configured with an explicit allowlist of four Vercel deployment URLs plus four LAN IPs for local development. Credentials are enabled.
New frontend deployments require a backend code change to add the origin. There is no runtime config or environment-variable-driven allowlist.
Redis may not be available in development environments, but the notification and animal-create queues depend on it.
RedisConnectionService uses lazyConnect and catches the initial connection failure. isAvailable is set to false and propagated to all queue/worker services via guard checks in onModuleInit. Features degrade silently in dev; in production a FATAL error is thrown.
The app boots and serves all non-queue endpoints in dev without Redis. Queue-dependent features are simply no-ops. In production, a missing Redis blocks startup rather than causing silent data loss.
Animal creation involves multiple file uploads (photo + gallery + documents), storage to CDN, and several DB writes that must be atomic or rolled back.
An animal-create BullMQ queue job carries pre-uploaded filenames alongside the full DTO. The worker handles DB writes with the uploaded CDN URLs. Temp files on disk are deleted in a finally block regardless of upload success or failure.
File uploads are separated from DB writes; upload failures clean up temp files without leaving orphaned DB records.
Notification quiet-hours logic must handle overnight ranges (e.g. 22:00–06:00) and respect per-notification priority (CRITICAL bypasses quiet hours).
The preference resolver calculates minutesSinceMidnight in the system timezone, checks isOvernight = start > end, and computes the delay-until datetime. CRITICAL priority notifications skip the quiet-hours check entirely.
Quiet-hours logic is self-contained in a single service and handles both same-day and overnight window ranges.
System Design
What Makes It Fast
admin KPI endpoint runs 10 concurrent count queries in a single await
Reference data validation via in-memory Set/Map lookups or single Redis commands instead of DB queries per field on every animal write
BullMQ workers use concurrency: 3 for email and concurrency: 1 for SMS and push to avoid overwhelming external providers
DigitalOcean Spaces CDN serves uploaded files; storage keys use a nanoid + timestamp suffix to avoid cache collisions
Security Model
JWT access tokens (7-day expiry) and refresh tokens (30-day expiry) with separate secrets; a third reset token secret (15-min expiry) for password reset flows
Session sliding-window expiry: the AuthGuard checks lastActive against a per-user configurable sessionTimeout and rejects expired sessions before the JWT itself expires
Passwords hashed with bcrypt using a configurable salt rounds from environment; Google OAuth users get a nanoid(32) random password they never know
CSRF protection via csrf-csrf and Helmet for HTTP security headers; throttling via @nestjs/throttler; raw body middleware scoped specifically to the /webhook path for Stripe signature verification
How It Ships
Screenshots




