A mobile application that helps patients with memory conditions (such as Alzheimer's and dementia) retain recognition of familiar people through interactive quizzes. Caregivers upload photos and audio of family members and friends, while patients take guided recognition quizzes on a paired device. The system tracks performance over time and provides analytics to caregivers.
| Layer | Technologies |
|---|---|
| Backend | Node.js, TypeScript, NestJS 11, Express |
| Database | PostgreSQL 17, Prisma 6 ORM |
| Authentication | JWT with server-side sessions, Passport, bcrypt, Google OAuth, Apple Sign-In |
| Nodemailer (password reset codes) | |
| Mobile App | Expo ~54, React Native 0.81, React 19, expo-router 6 |
| UI | React Native Paper, custom adaptive components, @expo/vector-icons |
| Device APIs | Camera, Image Picker, Secure Store, Clipboard, QR Code generation |
memorylane/
├── backend/server/
│ ├── src/
│ │ ├── auth/ # Authentication module
│ │ │ ├── auth.controller.ts # All auth & account routes
│ │ │ ├── auth.service.ts # Auth, profile, sessions, deletion, delegation logic
│ │ │ ├── auth.module.ts # Module wiring (JWT, Passport)
│ │ │ ├── jwt.strategy.ts # Passport JWT strategy with DB session check
│ │ │ └── jwt-auth.guard.ts # Reusable auth guard
│ │ ├── patient/ # Patient module
│ │ │ ├── patient.controller.ts # Patient CRUD, pairing, care team routes
│ │ │ ├── patient.service.ts # Patient join/pairing/care team logic
│ │ │ ├── dashboard/
│ │ │ │ └── dashboard.service.ts # Caregiver overview aggregation
│ │ │ ├── management/
│ │ │ │ ├── management.service.ts # Primary delegation & patient deletion
│ │ │ │ └── management.service.spec.ts # Unit tests with Prisma mock
│ │ │ ├── dto/ # Patient-specific DTOs
│ │ │ └── encryption.util.ts # Patient data encryption helpers
│ │ ├── dto/ # Shared DTOs
│ │ │ ├── signup.dto.ts
│ │ │ ├── login.dto.ts
│ │ │ ├── social-login.dto.ts
│ │ │ ├── update-profile.dto.ts
│ │ │ ├── change-password.dto.ts
│ │ │ ├── change-email.dto.ts
│ │ │ ├── forgot-password.dto.ts
│ │ │ └── reset-password.dto.ts
│ │ ├── prisma/ # Prisma client module
│ │ ├── app.module.ts
│ │ └── main.ts
│ ├── prisma/
│ │ ├── schema.prisma
│ │ └── migrations/ # 13 migration files
│ ├── test/
│ │ └── app.e2e-spec.ts # E2E tests for auth endpoints
│ ├── Dockerfile
│ └── package.json
│
├── frontend/
│ ├── app/ # Expo Router screens
│ │ ├── _layout.tsx # Root stack layout
│ │ ├── index.tsx # Landing / welcome screen
│ │ ├── login.tsx # Login (email + Google + Apple)
│ │ ├── signup.tsx # Registration
│ │ ├── forgot-password.tsx # Forgot password flow
│ │ ├── reset-password.tsx # Reset password with code
│ │ ├── dashboard.tsx # Caregiver dashboard
│ │ ├── account.tsx # Account settings
│ │ ├── add-patient.tsx # Create a new patient
│ │ ├── join-patient.tsx # Join as a patient device
│ │ ├── join-space.tsx # Join an existing care space
│ │ ├── (caregiver-tabs)/ # Caregiver tab navigator
│ │ │ ├── patients.tsx # Patient list
│ │ │ ├── inbox.tsx # Delegation & role request inbox
│ │ │ └── analytics.tsx # Performance analytics
│ │ └── (patient-tabs)/ # Patient tab navigator
│ │ ├── quiz.tsx # Memory recognition quiz
│ │ └── relive.tsx # Media gallery / relive memories
│ ├── src/
│ │ ├── components/ # Reusable UI components
│ │ │ ├── AdaptiveButton.tsx
│ │ │ ├── AdaptiveInput.tsx
│ │ │ ├── AdaptiveCard.tsx
│ │ │ ├── AdaptiveBadge.tsx
│ │ │ ├── AppIcon.tsx
│ │ │ ├── M3BottomSheet.tsx
│ │ │ ├── M3Dialog.tsx
│ │ │ └── M3TabBar.tsx
│ │ ├── theme/
│ │ │ ├── colors.ts # Design system color tokens
│ │ │ └── typography.ts # Font family definitions
│ │ ├── config/
│ │ │ └── api.ts # API base URL configuration
│ │ └── utils/
│ │ └── auth.ts # Token & user info persistence
│ ├── plugins/
│ │ └── withAndroidGradleJvm.js # Custom Expo config plugin
│ ├── app.json
│ └── package.json
│
└── README.md
| Model | Purpose |
|---|---|
| Caregiver | User accounts with email, password, avatar, and account status |
| Patient | Individuals taking memory quizzes (encrypted name/surname, join code, device pairing) |
| PatientCaregiver | Many-to-many link with primary/secondary role tracking |
| Media | Photos and audio of people the patient should recognize, with decoy names for quizzes |
| QuizSession | A quiz sitting by a patient |
| QuizAttempt | Individual question results (correct/incorrect, tap count, response time) |
| AuthSession | JWT session tracking with device labels (supports server-side revocation) |
| PasswordHistory | Last 5 password hashes to prevent reuse |
| PasswordResetRequest | Time-limited 6-digit reset codes |
| DelegationRequest | Primary caregiver handover during account deletion |
| RoleRequest | Secondary-to-primary role upgrade requests |
| Notification | In-app notifications for delegation, role changes, and team events |
| AnalyticsSnapshot | Daily aggregated quiz performance stats |
| Method | Route | Auth | Description |
|---|---|---|---|
| POST | /auth/signup |
No | Register a new caregiver |
| POST | /auth/login |
No | Email/password login |
| POST | /auth/social-login |
No | Google or Apple OAuth login |
| POST | /auth/forgot-password |
No | Request a password reset code |
| POST | /auth/reset-password |
No | Reset password with code |
| POST | /auth/logout |
Yes | Revoke current session |
| POST | /auth/restore-account |
No | Restore a deactivated account |
| Method | Route | Description |
|---|---|---|
| GET | /auth/me |
Get current user profile |
| PATCH | /auth/profile |
Update name, surname, avatar |
| PATCH | /auth/change-password |
Change password (with history check) |
| PATCH | /auth/change-email |
Change email address |
| Method | Route | Description |
|---|---|---|
| GET | /auth/sessions |
List active sessions |
| DELETE | /auth/sessions/:id |
Revoke a specific session |
| DELETE | /auth/sessions/others |
Revoke all other sessions |
| Method | Route | Description |
|---|---|---|
| POST | /auth/request-deletion |
Start deletion (triggers delegation flow if primary) |
| POST | /auth/confirm-deletion |
Confirm deletion after delegations are resolved |
| POST | /auth/cancel-deletion |
Cancel pending deletion |
| GET | /auth/deletion-status |
Check delegation progress |
| DELETE | /auth/account |
Immediate hard delete (no dependents) |
| Method | Route | Description |
|---|---|---|
| POST | /auth/delegate-patient |
Send/resend delegation to a secondary |
| GET | /auth/delegation-requests/incoming |
View incoming delegation requests |
| POST | /auth/delegation-requests/:id/accept |
Accept a delegation |
| POST | /auth/delegation-requests/:id/decline |
Decline a delegation |
| POST | /auth/delegation-requests/:id/resend |
Resend a delegation request |
| POST | /auth/role-requests |
Request primary role for a patient |
| GET | /auth/role-requests/incoming |
View incoming role requests |
| GET | /auth/role-requests/pending-by-me |
View your pending role requests |
| POST | /auth/role-requests/:id/approve |
Approve a role request |
| POST | /auth/role-requests/:id/decline |
Decline a role request |
| Method | Route | Description |
|---|---|---|
| GET | /auth/notifications |
List all notifications |
| DELETE | /auth/notifications/:id |
Delete a notification |
| PATCH | /auth/notifications/mark-all-read |
Mark all as read |
| Method | Route | Description |
|---|---|---|
| POST | /patients |
Create a new patient |
| POST | /patients/join |
Patient device joins via code |
| POST | /patients/join-as-caregiver |
Caregiver joins a patient's care team |
| GET | /patients/my-list |
List all patients for current caregiver |
| GET | /patients/:id/paired-status |
Check if patient device is paired |
| PATCH | /patients/:id/unpair |
Unpair a patient device |
| PATCH | /patients/:id |
Update patient details |
| GET | /patients/:id/caregivers |
List caregivers for a patient |
| PATCH | /patients/:id/delegate-primary |
Transfer primary role |
| DELETE | /patients/:id/caregivers/:caregiverId |
Remove a caregiver from care team |
| DELETE | /patients/:id/leave |
Leave a patient's care team |
| DELETE | /patients/:id |
Delete a patient |
For caregiver-uploaded media used by quiz generation and the patient "Relive" tab, we chose the Balanced Academic-Grade approach:
- Private object storage + signed URLs for file transport/access
- Server-side envelope encryption (unique data key per file, wrapped by a master key)
- Pseudonymous media references in the database to avoid direct caregiver/patient linkage from media rows alone
This project needs stronger protection than a "single key for all files", but must still be deliverable within a bachelor course timeline. Option 2 gives a credible security architecture that is realistic, explainable in an academic report, and feasible to implement without building full end-to-end key exchange/recovery.
Pros
- Very fast to build and demo
- Minimal infrastructure and code complexity
Cons
- If that one key leaks, all media can be decrypted
- Weak tenant isolation and weaker "DB leak" story
- Less aligned with modern production patterns
Pros
- Stronger blast-radius control (per-file keying)
- Better fit for "DB leaked but media still protected" threat model
- Closer to industry architecture while still course-feasible
- Good balance of implementation effort and security value
Cons
- More moving parts than Option 1 (key wrapping, signed upload/download workflow)
- Requires careful metadata and access-control design
Pros
- Highest confidentiality (server never sees plaintext)
Cons
- Significant complexity for key distribution, key recovery, and multi-device support
- High delivery risk for a course project timeline
Our objective is to reduce impact from:
- Database exfiltration
- Object storage bucket exposure
- Unauthorized direct media URL access
Design principles:
- Store media files encrypted at rest with per-file data keys
- Keep media identifiers pseudonymous and non-guessable
- Minimize direct caregiver/patient identifiers in media tables
- Issue only short-lived signed URLs for reads/writes
- Enforce caregiver authorization before any URL issuance
The backend uses Prisma ORM and currently avoids raw SQL query construction. To keep SQL injection risk low:
- Continue using Prisma typed query APIs by default
- Avoid raw SQL unless absolutely required
- If raw SQL is ever needed, use parameterized Prisma raw APIs only
- Keep DTO validation (
class-validator) and authorization checks strict for all upload-related endpoints
- Node.js v20+
- PostgreSQL 17
- npm
- Xcode (for iOS development builds)
- Android Studio (for Android development builds)
-
Install dependencies:
cd backend/server npm install -
Configure environment variables:
Copy
backend/server/.env.exampletobackend/server/.envand fill in:DATABASE_URL=postgresql://postgres:postgres@localhost:5432/memorylane JWT_SECRET=your-secure-random-secret-here PORT=3000 # Google OAuth (from https://console.cloud.google.com/apis/credentials) GOOGLE_WEB_CLIENT_ID=your-web-client-id.apps.googleusercontent.com GOOGLE_IOS_CLIENT_ID=your-ios-client-id.apps.googleusercontent.com GOOGLE_ANDROID_CLIENT_ID=your-android-client-id.apps.googleusercontent.com # Apple Sign-In APPLE_BUNDLE_ID=com.memorylane.app # SMTP (optional — falls back to console in development only) SMTP_HOST=smtp.example.com SMTP_PORT=587 SMTP_USER=you@example.com SMTP_PASS=your-smtp-password
Note: Set
NODE_ENV=productionin any deployed environment. Without SMTP configured in production, password reset codes will not be sent and will not appear in logs. -
Set up the database:
createdb -U postgres memorylane cd backend/server npx prisma migrate deploy -
Start the dev server:
npm run start:dev
The API will be available at
http://localhost:3000.
-
Install dependencies:
cd frontend npm install -
Configure environment variables:
Copy
frontend/.env.exampletofrontend/.env.localand set as needed:# Physical device: use your machine's LAN IP # EXPO_PUBLIC_API_BASE_URL=http://192.168.1.X:3000 # Google Sign-In EXPO_PUBLIC_GOOGLE_WEB_CLIENT_ID=your-web-client-id.apps.googleusercontent.com EXPO_PUBLIC_GOOGLE_IOS_CLIENT_ID=your-ios-client-id.apps.googleusercontent.com
-
Run with Expo Go (limited — Google/Apple sign-in unavailable):
npx expo start
-
Run with development build (Google Sign-In, Apple Sign-In, push notifications):
Configure Google OAuth in
frontend/.env.localandbackend/server/.env, then:npx expo prebuild --clean npx expo run:ios # or npx expo run:android npm start # Metro — use the dev client, not Expo Go
Push notifications: Run
npm run eas:initinfrontend/, setEXPO_PUBLIC_EAS_PROJECT_IDin.env.local, configure APNs/FCM withnpm run eas:credentials, and test on a physical device (simulators do not receive remote push on iOS).
| Command | Description |
|---|---|
npm run start:dev |
Start in watch mode |
npm run build |
Compile TypeScript |
npm run start:prod |
Run compiled output |
npm run lint |
Lint and auto-fix |
npm run test |
Run unit tests |
npm run test:e2e |
Run end-to-end tests |
The backend has two test suites:
- Unit tests (
npm run test) —management.service.spec.tstestsdelegatePrimaryRoleanddeletePatientwith a Prisma mock. - E2E tests (
npm run test:e2e) —test/app.e2e-spec.tsboots the full NestJS app and tests real auth endpoints (bad credentials → 401, missing fields → 400, weak password → 400).
- Caregiver accounts with email/password, Google Sign-In (iOS + Android), and Apple Sign-In (iOS)
- Patient profiles with encrypted personal data, QR-code-based device pairing, and join codes
- Care teams with primary/secondary caregiver roles, delegation, and role transfer workflows
- Memory quizzes with photo + audio recognition, multiple-choice with decoy names, and performance tracking
- Analytics with daily accuracy snapshots and response time metrics
- Account lifecycle with soft deletion, 10-day grace period, delegation-based handover, and account restoration
- Session management with per-device tracking and remote revocation
- Password security with history-based reuse prevention and email-based reset codes
- In-app notifications for delegation events, role changes, and team updates