docs: create app wiki
parent
6a0ee1c92e
commit
06f4b3d139
123
Admin-and-Operations-Guide.md
Normal file
123
Admin-and-Operations-Guide.md
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
# Admin and Operations Guide
|
||||||
|
|
||||||
|
This page is for system admins and operators.
|
||||||
|
|
||||||
|
## System Admin Panel
|
||||||
|
|
||||||
|
The `/admin` page is available to users with the `system_admin` role.
|
||||||
|
|
||||||
|
The admin panel includes:
|
||||||
|
|
||||||
|
- User management.
|
||||||
|
- Global store management.
|
||||||
|
|
||||||
|
Household owners/admins manage household-scoped members, invite links, stores, locations, zones, and catalog items from **Manage**. System admins manage cross-household/global records from **Admin**.
|
||||||
|
|
||||||
|
## Runtime Layout
|
||||||
|
|
||||||
|
Local and production runtime pieces:
|
||||||
|
|
||||||
|
- Backend API: Express, default port `5000`.
|
||||||
|
- Frontend: Vite/React, Docker-mapped dev port `3010`, direct Vite default `5173`.
|
||||||
|
- Database: external Postgres, not a container.
|
||||||
|
- Dev compose: `docker-compose.dev.yml`.
|
||||||
|
- Production compose: `docker-compose.yml`.
|
||||||
|
|
||||||
|
## Environment Variables
|
||||||
|
|
||||||
|
Do not commit real `.env`, `backend.env`, or `frontend.env` values.
|
||||||
|
|
||||||
|
Important backend variables:
|
||||||
|
|
||||||
|
| Variable | Purpose |
|
||||||
|
| --- | --- |
|
||||||
|
| `DATABASE_URL` | Preferred Postgres connection string for backend and root migration scripts. |
|
||||||
|
| `DB_USER`, `DB_PASS`, `DB_HOST`, `DB_PORT`, `DB_NAME` | Backend fallback database settings when `DATABASE_URL` is absent. |
|
||||||
|
| `JWT_SECRET` | Required for token-compatible auth paths. |
|
||||||
|
| `ALLOWED_ORIGINS` | Comma-separated allowed frontend origins for CORS. |
|
||||||
|
| `SESSION_COOKIE_NAME` | Optional session cookie name. |
|
||||||
|
| `SESSION_TTL_DAYS` | Optional session lifetime in days. |
|
||||||
|
|
||||||
|
Important frontend variables:
|
||||||
|
|
||||||
|
| Variable | Purpose |
|
||||||
|
| --- | --- |
|
||||||
|
| `VITE_API_URL` | Frontend API base URL. Defaults to the local backend URL. |
|
||||||
|
| `VITE_ALLOWED_HOSTS` | Optional Vite host allowlist. |
|
||||||
|
|
||||||
|
## Local Operations
|
||||||
|
|
||||||
|
Install:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm ci
|
||||||
|
npm --prefix backend ci
|
||||||
|
npm --prefix frontend ci
|
||||||
|
```
|
||||||
|
|
||||||
|
Run with Docker:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose -f docker-compose.dev.yml up
|
||||||
|
```
|
||||||
|
|
||||||
|
Run in separate terminals:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run dev:backend
|
||||||
|
npm run dev:frontend
|
||||||
|
```
|
||||||
|
|
||||||
|
Health checks:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl http://127.0.0.1:5000/
|
||||||
|
curl http://127.0.0.1:5000/config
|
||||||
|
```
|
||||||
|
|
||||||
|
## Deployment
|
||||||
|
|
||||||
|
Gitea Actions build and deploy this app.
|
||||||
|
|
||||||
|
- Pushes to `main` build and tag images as `latest`.
|
||||||
|
- Pushes to `main-new` build and tag images as `main-new`.
|
||||||
|
- Workflows install dependencies, run reliability checks, build backend/frontend images, push images to the configured registry, then deploy through SSH.
|
||||||
|
|
||||||
|
Deployment secrets are stored in Gitea Actions secrets. Do not print or paste them into logs.
|
||||||
|
|
||||||
|
## Reliability Gate
|
||||||
|
|
||||||
|
The workflow currently runs:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run audit
|
||||||
|
npm run lint
|
||||||
|
npm run typecheck
|
||||||
|
npm test
|
||||||
|
npm run db:migrate:stale:check
|
||||||
|
npm run build:backend
|
||||||
|
npm run build:frontend
|
||||||
|
```
|
||||||
|
|
||||||
|
Use the same commands locally before pushing risky changes.
|
||||||
|
|
||||||
|
## Operational Safety
|
||||||
|
|
||||||
|
- Do not run migrations against production or shared databases unless that is the explicit operator task.
|
||||||
|
- Do not add a Postgres container; the app expects an external database.
|
||||||
|
- Do not log secrets, tokens, session cookies, DB URLs, receipt bytes, or full invite codes.
|
||||||
|
- Invite code logs/audit entries may include last 4 characters only.
|
||||||
|
- Backend/API changes in Docker dev usually require rebuilding/restarting the backend service.
|
||||||
|
|
||||||
|
Useful dev backend restart:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose -f docker-compose.dev.yml up -d --build backend
|
||||||
|
```
|
||||||
|
|
||||||
|
Useful env/CORS reload:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose -f docker-compose.dev.yml up -d --force-recreate --no-deps backend
|
||||||
|
```
|
||||||
|
|
||||||
182
Architecture-and-API.md
Normal file
182
Architecture-and-API.md
Normal file
@ -0,0 +1,182 @@
|
|||||||
|
# Architecture and API
|
||||||
|
|
||||||
|
This page summarizes the current app architecture and active API shape.
|
||||||
|
|
||||||
|
## High-Level Flow
|
||||||
|
|
||||||
|
```text
|
||||||
|
React/Vite SPA
|
||||||
|
-> frontend/src/api shared Axios client
|
||||||
|
-> Express route
|
||||||
|
-> controller
|
||||||
|
-> middleware/services/models
|
||||||
|
-> external Postgres
|
||||||
|
```
|
||||||
|
|
||||||
|
The active grocery flow is:
|
||||||
|
|
||||||
|
```text
|
||||||
|
User -> Household -> Store Location -> Zones -> List Items / Available Items
|
||||||
|
```
|
||||||
|
|
||||||
|
## Frontend
|
||||||
|
|
||||||
|
Routes:
|
||||||
|
|
||||||
|
| Route | Page |
|
||||||
|
| --- | --- |
|
||||||
|
| `/login` | Login |
|
||||||
|
| `/register` | Register |
|
||||||
|
| `/invite/:token` | Invite link summary and acceptance |
|
||||||
|
| `/` | Grocery list |
|
||||||
|
| `/manage` | Household and store management |
|
||||||
|
| `/settings` | User settings |
|
||||||
|
| `/admin` | System admin panel |
|
||||||
|
|
||||||
|
Core providers:
|
||||||
|
|
||||||
|
- `AuthProvider`: login state.
|
||||||
|
- `HouseholdProvider`: user households and active household.
|
||||||
|
- `StoreProvider`: household stores, active store/location, and zones.
|
||||||
|
- `UploadQueueProvider`: queued image uploads and retry/discard behavior.
|
||||||
|
- `ActionToastProvider`: user-visible action outcomes.
|
||||||
|
- `SettingsProvider`: per-user local settings.
|
||||||
|
|
||||||
|
## Backend
|
||||||
|
|
||||||
|
Route families:
|
||||||
|
|
||||||
|
| Prefix | Purpose |
|
||||||
|
| --- | --- |
|
||||||
|
| `/auth` | Register, login, logout. |
|
||||||
|
| `/users` | Current user profile, password change, username availability. |
|
||||||
|
| `/admin` | System admin user operations. |
|
||||||
|
| `/config` | Public frontend config such as image limits. |
|
||||||
|
| `/households` | Households, members, stores, locations, zones, lists, and available items. |
|
||||||
|
| `/stores` | Global store records and legacy household store helpers. |
|
||||||
|
| `/api/groups` | Invite links, join requests, and join policy. |
|
||||||
|
| `/api/invite-links` | Public invite link summary and authenticated acceptance. |
|
||||||
|
| `/list` | Legacy/global list routes; prefer scoped household/location routes for new work. |
|
||||||
|
|
||||||
|
## Active Scoped Endpoints
|
||||||
|
|
||||||
|
Households:
|
||||||
|
|
||||||
|
```text
|
||||||
|
GET /households
|
||||||
|
POST /households
|
||||||
|
GET /households/:householdId
|
||||||
|
PATCH /households/:householdId
|
||||||
|
DELETE /households/:householdId
|
||||||
|
GET /households/:householdId/members
|
||||||
|
PATCH /households/:householdId/members/:userId/role
|
||||||
|
DELETE /households/:householdId/members/:userId
|
||||||
|
```
|
||||||
|
|
||||||
|
Stores, locations, and zones:
|
||||||
|
|
||||||
|
```text
|
||||||
|
GET /households/:householdId/stores
|
||||||
|
POST /households/:householdId/stores
|
||||||
|
PATCH /households/:householdId/stores/:householdStoreId
|
||||||
|
DELETE /households/:householdId/stores/:householdStoreId
|
||||||
|
POST /households/:householdId/stores/:householdStoreId/locations
|
||||||
|
PATCH /households/:householdId/locations/:locationId
|
||||||
|
DELETE /households/:householdId/locations/:locationId
|
||||||
|
PATCH /households/:householdId/locations/:locationId/default
|
||||||
|
GET /households/:householdId/locations/:locationId/zones
|
||||||
|
POST /households/:householdId/locations/:locationId/zones
|
||||||
|
PATCH /households/:householdId/locations/:locationId/zones/:zoneId
|
||||||
|
DELETE /households/:householdId/locations/:locationId/zones/:zoneId
|
||||||
|
```
|
||||||
|
|
||||||
|
Location-scoped list:
|
||||||
|
|
||||||
|
```text
|
||||||
|
GET /households/:householdId/locations/:locationId/list
|
||||||
|
GET /households/:householdId/locations/:locationId/list/item
|
||||||
|
POST /households/:householdId/locations/:locationId/list/add
|
||||||
|
PUT /households/:householdId/locations/:locationId/list/item
|
||||||
|
PATCH /households/:householdId/locations/:locationId/list/item
|
||||||
|
DELETE /households/:householdId/locations/:locationId/list/item
|
||||||
|
GET /households/:householdId/locations/:locationId/list/suggestions
|
||||||
|
GET /households/:householdId/locations/:locationId/list/recent
|
||||||
|
GET /households/:householdId/locations/:locationId/list/classification
|
||||||
|
POST /households/:householdId/locations/:locationId/list/classification
|
||||||
|
POST /households/:householdId/locations/:locationId/list/update-image
|
||||||
|
```
|
||||||
|
|
||||||
|
Available items:
|
||||||
|
|
||||||
|
```text
|
||||||
|
GET /households/:householdId/locations/:locationId/available-items
|
||||||
|
POST /households/:householdId/locations/:locationId/available-items
|
||||||
|
PATCH /households/:householdId/locations/:locationId/available-items/:itemId
|
||||||
|
DELETE /households/:householdId/locations/:locationId/available-items/:itemId
|
||||||
|
POST /households/:householdId/locations/:locationId/available-items/import-current
|
||||||
|
```
|
||||||
|
|
||||||
|
Invite links:
|
||||||
|
|
||||||
|
```text
|
||||||
|
GET /api/groups/invites
|
||||||
|
POST /api/groups/invites
|
||||||
|
GET /api/groups/join-requests
|
||||||
|
POST /api/groups/join-requests/decision
|
||||||
|
POST /api/groups/invites/revoke
|
||||||
|
POST /api/groups/invites/revive
|
||||||
|
POST /api/groups/invites/delete
|
||||||
|
GET /api/groups/join-policy
|
||||||
|
POST /api/groups/join-policy
|
||||||
|
GET /api/invite-links/:token
|
||||||
|
POST /api/invite-links/:token
|
||||||
|
```
|
||||||
|
|
||||||
|
Group invite management uses a `X-Group-Id` header from the frontend wrapper when the user manages multiple groups.
|
||||||
|
|
||||||
|
## Auth And Authorization
|
||||||
|
|
||||||
|
Authentication:
|
||||||
|
|
||||||
|
- Login validates a lowercase username and password.
|
||||||
|
- Login creates a DB session and sets an HttpOnly cookie.
|
||||||
|
- Login also returns a JWT-compatible token used by existing client code.
|
||||||
|
- Protected routes accept valid session or token credentials.
|
||||||
|
|
||||||
|
Authorization:
|
||||||
|
|
||||||
|
- System admin routes require the `system_admin` role.
|
||||||
|
- Household routes require membership in the target household.
|
||||||
|
- Household admin routes require owner/admin membership.
|
||||||
|
- Location/list routes also verify access to the selected store location.
|
||||||
|
|
||||||
|
## Request IDs And Errors
|
||||||
|
|
||||||
|
Every API response includes a request ID and sends it as the `X-Request-Id` response header.
|
||||||
|
|
||||||
|
Error responses are normalized to:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"error": {
|
||||||
|
"code": "bad_request",
|
||||||
|
"message": "Readable message"
|
||||||
|
},
|
||||||
|
"request_id": "generated-request-id"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Frontend API wrappers unwrap `{ data, request_id }` success payloads when needed.
|
||||||
|
|
||||||
|
## Image Handling
|
||||||
|
|
||||||
|
Image uploads use Multer and Sharp.
|
||||||
|
|
||||||
|
Configuration is exposed by `/config` and includes:
|
||||||
|
|
||||||
|
- Maximum image file size.
|
||||||
|
- Maximum image dimension.
|
||||||
|
- Image quality.
|
||||||
|
|
||||||
|
Frontend uploads use `UploadQueueContext`, which persists queued jobs and lets users retry failed uploads.
|
||||||
|
|
||||||
103
Database-and-Migrations.md
Normal file
103
Database-and-Migrations.md
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
# Database and Migrations
|
||||||
|
|
||||||
|
Fiddy uses an external on-prem Postgres database. Do not add or assume a database container.
|
||||||
|
|
||||||
|
## Canonical Migration Directory
|
||||||
|
|
||||||
|
Canonical migrations live in:
|
||||||
|
|
||||||
|
```text
|
||||||
|
packages/db/migrations
|
||||||
|
```
|
||||||
|
|
||||||
|
Legacy SQL under `backend/migrations` is reference-only. Do not add new canonical migrations there.
|
||||||
|
|
||||||
|
## Required Environment
|
||||||
|
|
||||||
|
Migration commands need:
|
||||||
|
|
||||||
|
- `DATABASE_URL` set in the shell.
|
||||||
|
- `psql` installed and available on `PATH`.
|
||||||
|
- The command run from the repo root.
|
||||||
|
|
||||||
|
Do not print or commit the value of `DATABASE_URL`.
|
||||||
|
|
||||||
|
## Commands
|
||||||
|
|
||||||
|
Show status:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run db:migrate:status
|
||||||
|
```
|
||||||
|
|
||||||
|
Create a migration:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run db:migrate:new -- <migration-name>
|
||||||
|
```
|
||||||
|
|
||||||
|
Apply pending migrations:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run db:migrate
|
||||||
|
```
|
||||||
|
|
||||||
|
Verify clean state:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run db:migrate:verify
|
||||||
|
```
|
||||||
|
|
||||||
|
Check stale legacy SQL:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run db:migrate:stale:check
|
||||||
|
```
|
||||||
|
|
||||||
|
Update stale tracking:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run db:migrate:stale
|
||||||
|
```
|
||||||
|
|
||||||
|
## Operator Flow
|
||||||
|
|
||||||
|
1. Confirm which database `DATABASE_URL` targets.
|
||||||
|
2. Run `npm run db:migrate:status`.
|
||||||
|
3. Create a new migration file if the code change needs schema changes.
|
||||||
|
4. Review the SQL.
|
||||||
|
5. Apply pending migrations only when the operator intends to change that database.
|
||||||
|
6. Run `npm run db:migrate:verify`.
|
||||||
|
7. Run the relevant app tests/builds.
|
||||||
|
|
||||||
|
## Tracking Table
|
||||||
|
|
||||||
|
Applied migrations are recorded in:
|
||||||
|
|
||||||
|
```text
|
||||||
|
schema_migrations(filename text unique, applied_at timestamptz)
|
||||||
|
```
|
||||||
|
|
||||||
|
Migrations apply in lexicographic filename order.
|
||||||
|
|
||||||
|
## Current Data Areas
|
||||||
|
|
||||||
|
The current app stores data for:
|
||||||
|
|
||||||
|
- Users and DB-backed sessions.
|
||||||
|
- Households and household members.
|
||||||
|
- Invite links, join requests, join policy, and audit events.
|
||||||
|
- Stores, household stores, locations, and zones.
|
||||||
|
- Location-scoped list items.
|
||||||
|
- Available/catalog items.
|
||||||
|
- Item images and metadata.
|
||||||
|
- Item classification and notes.
|
||||||
|
|
||||||
|
## Safety Rules
|
||||||
|
|
||||||
|
- Never run migrations against production/shared data unless explicitly asked.
|
||||||
|
- Never edit applied migration history manually unless performing a planned database repair.
|
||||||
|
- Do not log secrets, DB URLs, tokens, cookies, receipt bytes, or full invite codes.
|
||||||
|
- Invite-code logging may include last 4 characters only.
|
||||||
|
- Add tests for API behavior that depends on a new schema contract.
|
||||||
|
|
||||||
65
Home.md
Normal file
65
Home.md
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
# Fiddy Wiki
|
||||||
|
|
||||||
|
Fiddy is a collaborative grocery list app for households. It lets people keep a shared shopping list, organize items by store location and zone, add product images, track recently bought items, and manage who can access each household.
|
||||||
|
|
||||||
|
This wiki is written for two audiences:
|
||||||
|
|
||||||
|
- Users who shop with the app and manage households.
|
||||||
|
- Programmers and operators who run, maintain, or extend the app.
|
||||||
|
|
||||||
|
## Start Here
|
||||||
|
|
||||||
|
| Audience | Page | Use it for |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| Users | [User Guide](User-Guide) | Sign in, create or join a household, use the grocery list, upload images, and adjust settings. |
|
||||||
|
| Household owners/admins | [Households, Roles, and Invites](Households-Roles-and-Invites) | Manage members, invite links, join policies, and household roles. |
|
||||||
|
| Store/list managers | [Stores, Zones, and Catalog Items](Stores-Zones-and-Catalog-Items) | Add store locations, choose defaults, organize zones, and maintain reusable item suggestions. |
|
||||||
|
| Operators | [Admin and Operations Guide](Admin-and-Operations-Guide) | Manage system users, deployment, environment variables, and health checks. |
|
||||||
|
| Programmers | [Programmer Guide](Programmer-Guide) | Set up the repo, run the app locally, follow coding conventions, and verify changes. |
|
||||||
|
| Programmers | [Architecture and API](Architecture-and-API) | Understand the Express API, React client, auth, request IDs, and active route families. |
|
||||||
|
| Programmers/operators | [Database and Migrations](Database-and-Migrations) | Work with the external Postgres database and canonical migration workflow. |
|
||||||
|
| Everyone | [Troubleshooting](Troubleshooting) | Fix common login, household, store, image upload, CORS, and deployment issues. |
|
||||||
|
|
||||||
|
## What The App Does
|
||||||
|
|
||||||
|
Fiddy supports:
|
||||||
|
|
||||||
|
- Email/password accounts with DB-backed sessions and an HttpOnly session cookie.
|
||||||
|
- Multiple households per user.
|
||||||
|
- Household roles: owner, admin, and member.
|
||||||
|
- Invite links with join policies for closed, automatic, or approval-based joining.
|
||||||
|
- Store locations per household, including default locations.
|
||||||
|
- Ordered store zones so the list can match the shopping path.
|
||||||
|
- Location-scoped grocery lists with quantities, notes, classification, and images.
|
||||||
|
- Recently bought items for the last 24 hours.
|
||||||
|
- Search and suggestions from the household/location catalog.
|
||||||
|
- User settings for theme, list display, recent item count, confirmation behavior, haptics, and debug mode.
|
||||||
|
- System administration for users and global store records.
|
||||||
|
- Gitea Actions based build/deploy workflows.
|
||||||
|
|
||||||
|
## Current Stack
|
||||||
|
|
||||||
|
- Frontend: React 19 and Vite in `frontend/`.
|
||||||
|
- Backend: Express 5 CommonJS API in `backend/`.
|
||||||
|
- Database: external on-prem Postgres.
|
||||||
|
- Package manager: npm.
|
||||||
|
- Migrations: canonical SQL files in `packages/db/migrations`.
|
||||||
|
- CI/CD: Gitea Actions in `.gitea/workflows`.
|
||||||
|
|
||||||
|
## Important Concepts
|
||||||
|
|
||||||
|
The active app flow is household and location scoped:
|
||||||
|
|
||||||
|
1. A user signs in.
|
||||||
|
2. The user creates or joins a household.
|
||||||
|
3. The user selects a household store location.
|
||||||
|
4. The grocery list, item suggestions, zones, images, and recently bought items are scoped to that household and location.
|
||||||
|
|
||||||
|
Programmers should prefer the current scoped API routes under:
|
||||||
|
|
||||||
|
```text
|
||||||
|
/households/:householdId/locations/:locationId/...
|
||||||
|
```
|
||||||
|
|
||||||
|
Some legacy global list routes still exist for compatibility, but new work should follow the household/location model unless the task explicitly says otherwise.
|
||||||
|
|
||||||
82
Households-Roles-and-Invites.md
Normal file
82
Households-Roles-and-Invites.md
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
# Households, Roles, and Invites
|
||||||
|
|
||||||
|
Households are the main collaboration boundary in Fiddy. Lists, stores, locations, zones, catalog items, and membership are scoped to a household.
|
||||||
|
|
||||||
|
## Household Roles
|
||||||
|
|
||||||
|
| Role | Can shop | Manage household | Manage stores/zones | Manage invites | Manage member roles |
|
||||||
|
| --- | --- | --- | --- | --- | --- |
|
||||||
|
| Owner | Yes | Yes | Yes | Yes | Yes, including ownership transfer |
|
||||||
|
| Admin | Yes | Yes | Yes | Yes | Yes, except protected owner actions |
|
||||||
|
| Member | Yes | No | Limited/no admin actions | No | No |
|
||||||
|
|
||||||
|
Rules enforced by the backend:
|
||||||
|
|
||||||
|
- Only household members can access a household.
|
||||||
|
- Owner/admin actions require a household owner or admin role.
|
||||||
|
- A member cannot promote themselves.
|
||||||
|
- Owner role changes are protected.
|
||||||
|
- Server-side authorization is required; client UI checks are only convenience.
|
||||||
|
|
||||||
|
## Create A Household
|
||||||
|
|
||||||
|
1. Sign in.
|
||||||
|
2. Use the household create flow.
|
||||||
|
3. Enter a household name.
|
||||||
|
4. The creator becomes the household owner.
|
||||||
|
|
||||||
|
## Rename Or Delete A Household
|
||||||
|
|
||||||
|
Open **Manage > Household**.
|
||||||
|
|
||||||
|
Owners/admins can rename a household. Owners/admins can also delete a household, which removes that household's lists and related data. Treat deletion as permanent.
|
||||||
|
|
||||||
|
## Invite Links
|
||||||
|
|
||||||
|
Owners/admins can create invite links from **Manage > Household > Invite Links**.
|
||||||
|
|
||||||
|
Invite link behavior:
|
||||||
|
|
||||||
|
- Links have a time-to-live from 1 to 7 days.
|
||||||
|
- Link settings are immutable after creation.
|
||||||
|
- Expired links are retained and can be revived.
|
||||||
|
- Reviving a link resets its expiration and creates a new audit event.
|
||||||
|
- Single-use links are deleted after successful use.
|
||||||
|
- Single-use does not bypass approval-required joining.
|
||||||
|
- Logs and audit records must never expose full invite codes; use last 4 characters only.
|
||||||
|
|
||||||
|
When copying or sharing invite links, send them only to people who should join the household.
|
||||||
|
|
||||||
|
## Join Policy
|
||||||
|
|
||||||
|
Owners/admins choose how invite link joins work:
|
||||||
|
|
||||||
|
| Policy | Meaning |
|
||||||
|
| --- | --- |
|
||||||
|
| Not accepting | New joins are closed. Existing members continue to use the household. |
|
||||||
|
| Auto accept | A valid invite link adds the user automatically. |
|
||||||
|
| Approval required | A valid invite link creates a pending join request that an owner/admin must approve. |
|
||||||
|
|
||||||
|
## Join Requests
|
||||||
|
|
||||||
|
When approval is required:
|
||||||
|
|
||||||
|
1. A user opens an invite link.
|
||||||
|
2. The app creates a pending join request.
|
||||||
|
3. Owners/admins review pending requests from the household management screen.
|
||||||
|
4. Owners/admins approve or deny each request.
|
||||||
|
|
||||||
|
Approved users become household members.
|
||||||
|
|
||||||
|
## Member Management
|
||||||
|
|
||||||
|
Owners/admins can:
|
||||||
|
|
||||||
|
- View members.
|
||||||
|
- Promote members to admin.
|
||||||
|
- Demote admins to member.
|
||||||
|
- Transfer ownership where allowed.
|
||||||
|
- Remove members.
|
||||||
|
|
||||||
|
Members can leave a household. If a user belongs to multiple households, they can switch to another household after leaving.
|
||||||
|
|
||||||
132
Programmer-Guide.md
Normal file
132
Programmer-Guide.md
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
# Programmer Guide
|
||||||
|
|
||||||
|
This page is for people changing the code.
|
||||||
|
|
||||||
|
## Stack
|
||||||
|
|
||||||
|
- Backend: Express 5, CommonJS, Node.js.
|
||||||
|
- Frontend: React 19, Vite, partial TypeScript.
|
||||||
|
- Database: external Postgres through `pg`.
|
||||||
|
- Tests: Jest/Supertest for backend, Playwright for e2e.
|
||||||
|
- Package manager: npm.
|
||||||
|
|
||||||
|
Use Node.js 20.19+ or 22.12+ for frontend/Vite commands.
|
||||||
|
|
||||||
|
## Repository Map
|
||||||
|
|
||||||
|
Key paths:
|
||||||
|
|
||||||
|
| Path | Purpose |
|
||||||
|
| --- | --- |
|
||||||
|
| `backend/app.js` | Express middleware, CORS, route mounting, and error handler. |
|
||||||
|
| `backend/routes` | Route registration. Keep these thin. |
|
||||||
|
| `backend/controllers` | Request parsing and response handling. |
|
||||||
|
| `backend/models` | Database query modules. |
|
||||||
|
| `backend/services` | Domain service logic. |
|
||||||
|
| `backend/middleware` | Auth, RBAC, household access, request IDs, rate limits, image processing. |
|
||||||
|
| `frontend/src/api` | Axios API wrappers. Use these instead of direct component fetches. |
|
||||||
|
| `frontend/src/context` | Auth, household, store, settings, toast, and upload queue state. |
|
||||||
|
| `frontend/src/pages` | Route-level screens. |
|
||||||
|
| `frontend/src/components` | Shared and feature UI components. |
|
||||||
|
| `packages/db/migrations` | Canonical SQL migrations. |
|
||||||
|
| `.gitea/workflows` | Gitea Actions workflows. |
|
||||||
|
|
||||||
|
## Setup
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm ci
|
||||||
|
npm --prefix backend ci
|
||||||
|
npm --prefix frontend ci
|
||||||
|
```
|
||||||
|
|
||||||
|
Copy env examples as needed, but never commit real values:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
backend/.env.example -> backend/.env
|
||||||
|
frontend/.env.example -> frontend/.env
|
||||||
|
```
|
||||||
|
|
||||||
|
Root migration scripts read `DATABASE_URL` from the shell environment.
|
||||||
|
|
||||||
|
## Run Locally
|
||||||
|
|
||||||
|
Docker dev stack:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose -f docker-compose.dev.yml up
|
||||||
|
```
|
||||||
|
|
||||||
|
Separate terminals:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run dev:backend
|
||||||
|
npm run dev:frontend
|
||||||
|
```
|
||||||
|
|
||||||
|
Default endpoints:
|
||||||
|
|
||||||
|
- Backend: `http://localhost:5000`
|
||||||
|
- Frontend through Docker compose: `http://localhost:3010`
|
||||||
|
- Frontend direct Vite: `http://localhost:5173`
|
||||||
|
|
||||||
|
## Coding Conventions
|
||||||
|
|
||||||
|
- Preserve the current Express/Vite stack.
|
||||||
|
- Keep route files thin.
|
||||||
|
- Put DB-heavy logic in models/services.
|
||||||
|
- Enforce RBAC and household access server-side.
|
||||||
|
- Keep frontend network calls in `frontend/src/api`.
|
||||||
|
- Use the shared Axios client so credentials are sent consistently.
|
||||||
|
- Use context/hooks for UI state.
|
||||||
|
- Frontend DB-mutating actions should show toast or bubble outcome notifications.
|
||||||
|
- Progress notifications should reuse `UploadQueueContext` and `UploadToaster`.
|
||||||
|
- Do not introduce cron, workers, polling daemons, or background job frameworks.
|
||||||
|
- Do not add production dependencies unless the task clearly requires one and there is no practical existing option.
|
||||||
|
|
||||||
|
## Auth Notes
|
||||||
|
|
||||||
|
The current login flow returns a token for compatibility and also creates a DB-backed session. The backend sets an HttpOnly session cookie. The Axios client sends credentials and also attaches the stored token when present.
|
||||||
|
|
||||||
|
When changing auth:
|
||||||
|
|
||||||
|
- Keep sessions DB-backed.
|
||||||
|
- Keep session cookies HttpOnly.
|
||||||
|
- Do not log tokens or cookies.
|
||||||
|
- Preserve redirect behavior for expired sessions.
|
||||||
|
|
||||||
|
## Verification
|
||||||
|
|
||||||
|
Choose the narrowest useful checks first, then broaden when risk warrants it.
|
||||||
|
|
||||||
|
Common commands:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm test
|
||||||
|
npm run lint
|
||||||
|
npm run typecheck
|
||||||
|
npm run audit
|
||||||
|
npm run build:backend
|
||||||
|
npm run build:frontend
|
||||||
|
npm run build
|
||||||
|
npm run test:e2e
|
||||||
|
```
|
||||||
|
|
||||||
|
Migration checks:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run db:migrate:status
|
||||||
|
npm run db:migrate:verify
|
||||||
|
npm run db:migrate:stale:check
|
||||||
|
```
|
||||||
|
|
||||||
|
Do not run `npm run db:migrate` unless an operator explicitly intends to apply migrations to the selected external database.
|
||||||
|
|
||||||
|
## Tests To Add
|
||||||
|
|
||||||
|
Add or update tests when behavior changes:
|
||||||
|
|
||||||
|
- API behavior: Jest/Supertest in `backend/tests`.
|
||||||
|
- Authz, membership, invalid input, and not-a-member cases: include negative coverage.
|
||||||
|
- UI behavior: focused Playwright tests in `frontend/tests`.
|
||||||
|
- Pure docs changes: validate Markdown/content where practical.
|
||||||
|
|
||||||
75
Stores-Zones-and-Catalog-Items.md
Normal file
75
Stores-Zones-and-Catalog-Items.md
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
# Stores, Zones, and Catalog Items
|
||||||
|
|
||||||
|
Store management controls how grocery lists are organized for a household.
|
||||||
|
|
||||||
|
## Store Model
|
||||||
|
|
||||||
|
Fiddy separates:
|
||||||
|
|
||||||
|
- Global store records: system-level store templates.
|
||||||
|
- Household-owned stores: stores configured for a specific household.
|
||||||
|
- Locations: concrete shopping locations under a household store.
|
||||||
|
- Zones: ordered areas inside a location.
|
||||||
|
- Available items: location-specific catalog items used for suggestions and defaults.
|
||||||
|
|
||||||
|
The active shopping list is scoped to a household and one location.
|
||||||
|
|
||||||
|
## Add A Household Store And Location
|
||||||
|
|
||||||
|
Open **Manage > Stores**.
|
||||||
|
|
||||||
|
Owners/admins can:
|
||||||
|
|
||||||
|
1. Add a household store.
|
||||||
|
2. Add one or more locations for that store.
|
||||||
|
3. Rename stores or locations.
|
||||||
|
4. Set the default shopping location.
|
||||||
|
5. Remove stores or locations when they are no longer needed.
|
||||||
|
|
||||||
|
The main list uses the active/default location when showing list items and suggestions.
|
||||||
|
|
||||||
|
## Manage Zones
|
||||||
|
|
||||||
|
Zones represent store areas such as produce, dairy, freezer, household, or checkout.
|
||||||
|
|
||||||
|
Owners/admins can:
|
||||||
|
|
||||||
|
- Add zones.
|
||||||
|
- Rename zones.
|
||||||
|
- Set zone order.
|
||||||
|
- Delete zones.
|
||||||
|
|
||||||
|
Users see list items grouped by the active location's zones. Zone order should match the route people normally walk through the store.
|
||||||
|
|
||||||
|
## Manage Available Items
|
||||||
|
|
||||||
|
Available items are reusable household/location catalog entries. They power suggestions and default details when users add groceries.
|
||||||
|
|
||||||
|
From **Manage > Stores > Manage Items**, owners/admins can:
|
||||||
|
|
||||||
|
- Search the catalog.
|
||||||
|
- Add catalog items.
|
||||||
|
- Edit item names, defaults, notes, classification, and images.
|
||||||
|
- Delete catalog items.
|
||||||
|
- Import current list items into the catalog.
|
||||||
|
|
||||||
|
Members can use catalog-backed suggestions while adding items. Admin-only catalog deletion is enforced by the backend.
|
||||||
|
|
||||||
|
## Good Catalog Hygiene
|
||||||
|
|
||||||
|
Use stable, simple item names:
|
||||||
|
|
||||||
|
- Prefer `milk` over `need milk`.
|
||||||
|
- Prefer `eggs` over `12 eggs` because quantity is tracked separately.
|
||||||
|
- Use notes for brand, size, or special instructions.
|
||||||
|
- Keep zones broad enough to be useful while shopping.
|
||||||
|
|
||||||
|
## When A Store Is Missing
|
||||||
|
|
||||||
|
If the list says no stores are found:
|
||||||
|
|
||||||
|
1. Confirm the correct household is selected.
|
||||||
|
2. Ask an owner/admin to open **Manage > Stores**.
|
||||||
|
3. Add a store and at least one location.
|
||||||
|
4. Set the location as default if it should open automatically.
|
||||||
|
|
||||||
148
Troubleshooting.md
Normal file
148
Troubleshooting.md
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
# Troubleshooting
|
||||||
|
|
||||||
|
Use this page for common user, admin, and developer problems.
|
||||||
|
|
||||||
|
## I Cannot Log In
|
||||||
|
|
||||||
|
Check:
|
||||||
|
|
||||||
|
- Username is lowercase or can be lowercased by the app.
|
||||||
|
- Password is correct.
|
||||||
|
- Backend is running.
|
||||||
|
- `JWT_SECRET` is configured.
|
||||||
|
- The browser can reach the backend API URL.
|
||||||
|
- Cookies are not blocked for the app origin.
|
||||||
|
|
||||||
|
For developers, inspect the response status and `request_id`. Do not log session cookies or tokens.
|
||||||
|
|
||||||
|
## I Am Logged Out Unexpectedly
|
||||||
|
|
||||||
|
Possible causes:
|
||||||
|
|
||||||
|
- Session expired.
|
||||||
|
- Session row was deleted.
|
||||||
|
- Backend `SESSION_COOKIE_NAME` changed.
|
||||||
|
- Browser cookie storage was cleared.
|
||||||
|
- Frontend and backend origins changed and cookies no longer match CORS/cookie rules.
|
||||||
|
|
||||||
|
Log in again after confirming the backend is healthy.
|
||||||
|
|
||||||
|
## I Have No Household
|
||||||
|
|
||||||
|
Create a household or open an invite link. If invite links fail:
|
||||||
|
|
||||||
|
- Confirm the link has not expired.
|
||||||
|
- Confirm the household join policy accepts new joins.
|
||||||
|
- If approval is required, wait for an owner/admin to approve the request.
|
||||||
|
|
||||||
|
## I See No Stores
|
||||||
|
|
||||||
|
This means the active household has no usable store location.
|
||||||
|
|
||||||
|
1. Confirm the active household is correct.
|
||||||
|
2. Ask an owner/admin to open **Manage > Stores**.
|
||||||
|
3. Add a store and location.
|
||||||
|
4. Set a default location if needed.
|
||||||
|
|
||||||
|
## Suggestions Or Zones Look Wrong
|
||||||
|
|
||||||
|
Check:
|
||||||
|
|
||||||
|
- Active household.
|
||||||
|
- Active store location.
|
||||||
|
- Location zones in **Manage > Stores**.
|
||||||
|
- Available items in **Manage > Stores > Manage Items**.
|
||||||
|
- Whether current list items should be imported into the catalog.
|
||||||
|
|
||||||
|
## Image Upload Failed
|
||||||
|
|
||||||
|
Check:
|
||||||
|
|
||||||
|
- Browser network status.
|
||||||
|
- File type and size.
|
||||||
|
- `/config` image limit values.
|
||||||
|
- Upload toaster state.
|
||||||
|
- Retry the queued upload.
|
||||||
|
|
||||||
|
Developers should check the backend image middleware and the request ID from the failed response. Do not log image bytes.
|
||||||
|
|
||||||
|
## CORS Error
|
||||||
|
|
||||||
|
Backend CORS uses `ALLOWED_ORIGINS`.
|
||||||
|
|
||||||
|
Add the exact frontend origin, for example:
|
||||||
|
|
||||||
|
```text
|
||||||
|
http://localhost:3010
|
||||||
|
http://127.0.0.1:3010
|
||||||
|
http://localhost:5173
|
||||||
|
```
|
||||||
|
|
||||||
|
After backend env/CORS changes in Docker dev, recreate the backend service:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose -f docker-compose.dev.yml up -d --force-recreate --no-deps backend
|
||||||
|
```
|
||||||
|
|
||||||
|
Then verify:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl http://127.0.0.1:5000/
|
||||||
|
curl http://127.0.0.1:5000/config
|
||||||
|
```
|
||||||
|
|
||||||
|
## Migration Or Store Management Is Unavailable
|
||||||
|
|
||||||
|
Some store/catalog features require the latest database schema.
|
||||||
|
|
||||||
|
Check:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run db:migrate:status
|
||||||
|
npm run db:migrate:verify
|
||||||
|
```
|
||||||
|
|
||||||
|
Only run `npm run db:migrate` when the operator intends to apply migrations to the selected external Postgres database.
|
||||||
|
|
||||||
|
## Gitea Actions Failed
|
||||||
|
|
||||||
|
Check the failing job:
|
||||||
|
|
||||||
|
- Install dependencies.
|
||||||
|
- Audit.
|
||||||
|
- Lint.
|
||||||
|
- Typecheck.
|
||||||
|
- Backend tests.
|
||||||
|
- Migration stale check.
|
||||||
|
- Backend build.
|
||||||
|
- Frontend build.
|
||||||
|
- Docker image build/push.
|
||||||
|
- SSH deploy.
|
||||||
|
|
||||||
|
For deployment failures, verify Gitea secrets exist and are current. Do not print secret values in logs.
|
||||||
|
|
||||||
|
## API Returned 401 Or 403
|
||||||
|
|
||||||
|
401 means the request is not authenticated. 403 means the authenticated user lacks the required role or membership.
|
||||||
|
|
||||||
|
Check:
|
||||||
|
|
||||||
|
- User is logged in.
|
||||||
|
- Session cookie is present.
|
||||||
|
- Token in local storage is current.
|
||||||
|
- User belongs to the target household.
|
||||||
|
- User has owner/admin role for management actions.
|
||||||
|
- System admin user has the `system_admin` role for `/admin`.
|
||||||
|
|
||||||
|
## API Returned 500
|
||||||
|
|
||||||
|
Use the `request_id` in the response and backend logs to trace the failure.
|
||||||
|
|
||||||
|
Common causes:
|
||||||
|
|
||||||
|
- Missing environment variable.
|
||||||
|
- Database connection failure.
|
||||||
|
- Pending migration.
|
||||||
|
- Invalid data shape not handled by the route/controller.
|
||||||
|
- Image processing failure.
|
||||||
|
|
||||||
101
User-Guide.md
Normal file
101
User-Guide.md
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
# User Guide
|
||||||
|
|
||||||
|
This guide explains the normal shopping workflow.
|
||||||
|
|
||||||
|
## Sign In Or Register
|
||||||
|
|
||||||
|
1. Open the app in a browser.
|
||||||
|
2. Use **Register** to create an account, or **Login** if you already have one.
|
||||||
|
3. Usernames are normalized to lowercase.
|
||||||
|
4. Passwords must be at least 8 characters.
|
||||||
|
|
||||||
|
The app keeps you signed in with a secure session cookie. If your session expires, the app redirects you back to login.
|
||||||
|
|
||||||
|
## Create Or Join A Household
|
||||||
|
|
||||||
|
A household is the group that owns lists, stores, locations, invite links, and members.
|
||||||
|
|
||||||
|
If you have no household:
|
||||||
|
|
||||||
|
1. Open the household prompt from the main list or manage area.
|
||||||
|
2. Choose **Create Household** to start a new household.
|
||||||
|
3. Choose **Join Household** if someone sent you an invite link.
|
||||||
|
|
||||||
|
If you belong to multiple households, use the household switcher in the navigation bar. The app remembers the active household in your browser.
|
||||||
|
|
||||||
|
## Choose A Store Location
|
||||||
|
|
||||||
|
The grocery list is scoped to a household store location.
|
||||||
|
|
||||||
|
1. Use the store tabs near the top of the grocery list.
|
||||||
|
2. Pick the location you are shopping at.
|
||||||
|
3. If there are no stores, ask a household owner/admin to add one from **Manage > Stores**.
|
||||||
|
|
||||||
|
The app remembers the active store location for each household.
|
||||||
|
|
||||||
|
## Add Grocery Items
|
||||||
|
|
||||||
|
On the main list:
|
||||||
|
|
||||||
|
1. Type an item name.
|
||||||
|
2. Set the quantity.
|
||||||
|
3. Choose **Add** or **Create + Add**.
|
||||||
|
4. If the item already exists, confirm whether to add to the existing quantity.
|
||||||
|
5. Optionally add item details, classification, notes, or an image when prompted.
|
||||||
|
|
||||||
|
Suggestions appear from the household/location catalog and previous items.
|
||||||
|
|
||||||
|
## Mark Items Bought
|
||||||
|
|
||||||
|
Use the item action to mark an item as bought. Depending on your settings, the app may ask you to confirm before buying.
|
||||||
|
|
||||||
|
Bought items move out of the active list and can appear in **Recently Bought (24HR)**. This section helps recover accidental purchases or review what was just picked up.
|
||||||
|
|
||||||
|
## Search The List
|
||||||
|
|
||||||
|
Use **Search list** to filter visible list items. Search works on the current household/location list.
|
||||||
|
|
||||||
|
When search is active:
|
||||||
|
|
||||||
|
- Matching items remain visible.
|
||||||
|
- Empty zone sections are hidden.
|
||||||
|
- Collapsed zones are expanded for easier scanning.
|
||||||
|
|
||||||
|
## Images
|
||||||
|
|
||||||
|
Items can have product images. Images are uploaded through the app and processed by the backend.
|
||||||
|
|
||||||
|
If the network drops during an image upload:
|
||||||
|
|
||||||
|
- The upload queue keeps track of the failed upload.
|
||||||
|
- The upload toaster shows progress, success, or failure.
|
||||||
|
- You can retry or discard a failed upload.
|
||||||
|
|
||||||
|
Do not upload sensitive photos or receipt images unless the feature you are using explicitly expects them.
|
||||||
|
|
||||||
|
## Settings
|
||||||
|
|
||||||
|
Open **Settings** from the user menu.
|
||||||
|
|
||||||
|
Available settings include:
|
||||||
|
|
||||||
|
- Theme: light, dark, or auto.
|
||||||
|
- Recently bought visibility.
|
||||||
|
- Number of recently bought items to show.
|
||||||
|
- Whether recently bought starts collapsed.
|
||||||
|
- Confirm before marking bought.
|
||||||
|
- Auto reload interval.
|
||||||
|
- Haptic feedback.
|
||||||
|
- Debug mode.
|
||||||
|
|
||||||
|
Settings are stored in your browser for your username.
|
||||||
|
|
||||||
|
## Permissions You May See
|
||||||
|
|
||||||
|
Fiddy has two role layers:
|
||||||
|
|
||||||
|
- System role: normal users generally use the app; system admins can access the admin panel.
|
||||||
|
- Household role: owner, admin, or member within each household.
|
||||||
|
|
||||||
|
Household owners/admins can manage members, invite links, stores, locations, zones, and catalog defaults. Members can use the list and participate in household shopping.
|
||||||
|
|
||||||
Loading…
Reference in New Issue
Block a user