- TypeScript 46.1%
- JavaScript 36.5%
- CSS 17.2%
- HTML 0.2%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
| client | ||
| server | ||
| .gitignore | ||
| config.example.ini | ||
| package-lock.json | ||
| package.json | ||
| README.md | ||
Amy’s Pet Buddy Services
A client-only Angular dashboard with a Node.js Fastify API and persistent NeDB storage. Plain HTML templates and CSS, no UI component library or SSR.
Run
Requires Node.js 22.22.3+, 24.15+, or 26+.
npm install
npm start
Open http://localhost:4200. Angular forwards /api requests to Fastify at http://localhost:3000. Client and server watch file changes; no manual build is required. To run only the server, use npm run dev -w server.
The API uses the maintained NeDB fork @seald-io/nedb. Its embedded database needs no separate database service. Records are stored in data/dashboard.db, which is excluded from Git. Start only one server process per database file. Back up the data directory with the server stopped.
Server environment variables: PORT (default 3000), HOST (default 0.0.0.0), and DATA_DIR (default ./data, resolved relative to the project root). Relative database paths always resolve from the project root. Update client/proxy.conf.json if you change the API port.
Dashboard behavior
Navigation contains Overview and Bookkeeping. The former standalone Bookings, Pets, and Clients pages have been removed; the overview retains its schedule and familiar faces. Existing records and APIs remain available.
Bookkeeping supports adding, editing, and deleting manual income and expense entries, with a month selector, all-time view, type filters, and search. Income, expense, and net totals cover the selected period independently of search/type filters. All amounts are AUD and stored as positive integer cents; the direction determines income or expense. Bookings do not automatically create financial entries.
- Bookings, pets, clients, and reminders load from the API.
- Creating a booking adds its pet and client when needed. Existing names are matched after trimming and ignoring case, within the same client for pets.
- Status changes and reminder checkboxes are saved before client state changes. Failed requests retain input and show an error.
- Pets and clients remain saved independently of bookings; removing a booking does not remove its pet or client.
- A fresh database starts empty. Your first booking adds a client and pet.
- Existing
data/bookings.jsondata is imported once without modifying the original file. Invalid legacy data stops startup so it can be repaired without silently losing records. - Browser reminders from the previous version are imported on the first successful connection and then removed from local storage. Repeated imports do not duplicate reminders or overwrite their saved completion status.
The layout starts with phone screens, bottom navigation, booking cards, large touch targets, and 16px form inputs. Columns and a sidebar are added on wider screens. Brand colours are taken from https://amyspetbuddys.com.au/styles.css: lilac #c79afc, purple #a875d8, and lavender #e6ccff, #f5e6ff, #faf5ff. Darker purple supports readable controls.
API
All request and response bodies use JSON, except successful deletes (204, empty body). Errors return { "message": "..." } with 400 for validation, 404 for missing records, and 409 for duplicate or linked records. Unknown fields are rejected. Dates use YYYY-MM-DD; times use 24-hour HH:mm.
| Resource | Endpoints | Fields |
|---|---|---|
| Health | GET /api/health |
Server and database readiness |
| Bookings | GET/POST /api/bookings, GET/PATCH/DELETE /api/bookings/:id |
service, date, time; create with petId or pet + owner. Patch accepts petId, service, date, time, status. |
| Clients | GET/POST /api/clients, GET/PATCH/DELETE /api/clients/:id |
name required on create; optional email, phone |
| Pets | GET/POST /api/pets, GET/PATCH/DELETE /api/pets/:id |
name, clientId required on create; optional emoji, color |
| Reminders | GET/POST /api/tasks, GET/PATCH/DELETE /api/tasks/:id |
title required on create; optional note, done |
| Reminder migration | POST /api/tasks/import |
{ "tasks": [{ "title": "...", "note": "...", "done": false }] } |
| Bookkeeping | GET/POST /api/transactions, GET/PATCH/DELETE /api/transactions/:id |
Required on create: direction (income or expense), description, category, date, amountCents (integer, 1–999999999). Optional note. |
Booking lists accept optional date, status, petId, and search query parameters. Services: Dog walking, Pet sitting, Home visit, Grooming. Statuses: Upcoming, In progress, Completed. Pet colours: peach, sage, lavender, yellow.
Booking responses include pet and parent names, IDs, emoji, and colour. Client lists include their pets. Pet lists include parent names and most recent service. Client names are unique ignoring case; pet names are unique within a client. Delete bookings before deleting their pet, and delete pets before deleting their client. Pets with bookings cannot be reassigned to a different client.
Example booking request:
{
"pet": "Milo",
"owner": "Olivia Martin",
"service": "Dog walking",
"date": "2026-11-01",
"time": "09:00"
}
The dashboard currently exposes booking creation/status changes and reminder creation/completion. Other CRUD operations are available through the API. Authentication is not configured.
Checks
npm run check checks Angular templates, TypeScript, and server syntax without building. npm test exercises validation, CRUD relationships, concurrent requests, NeDB restart persistence, and legacy migration in temporary directories.
Private configuration and repository
The project root is one Git repository containing both client/ and server/. Local runtime data and credentials are excluded by .gitignore:
project/
.git/
.gitignore
config.ini # private configuration, ignored
config.example.ini # safe empty template, suitable for Git
data/ # NeDB files and local backups, ignored
dashboard.db
client/
server/
Put your Square values in the [square] section of root config.ini: environment (sandbox or production), applicationId, accessToken, locationId, and webhookSignatureKey. The server reads this file; it is never imported into the Angular client or returned by an API. Square transfer imports use the Payouts API; payment creation is not implemented.
After cloning, copy config.example.ini to config.ini and fill in your private values. Missing configuration uses defaults with empty Square credentials. Restart the server after editing configuration. Database files from the previous server/data/ location have been moved to data/; the server now uses the root location.
Environment variables override configuration: PORT, HOST, DATA_DIR, SQUARE_ENVIRONMENT, SQUARE_APPLICATION_ID, SQUARE_ACCESS_TOKEN, SQUARE_LOCATION_ID, and SQUARE_WEBHOOK_SIGNATURE_KEY. Environment variables are read directly; .env files are ignored by Git but are not automatically loaded.
No commits or remote have been created. Before committing, git status should show source files and config.example.ini, but never config.ini or data/.
Configuration uses INI sections and key = value pairs. Lines beginning with ; are comments. Wrap values containing ; or # in double quotes. See config.example.ini for the complete layout:
[server]
host = 0.0.0.0
port = 3000
[database]
directory = ./data
[square]
environment = sandbox
applicationId =
accessToken =
locationId =
webhookSignatureKey =
Import Square transfers
Bookkeeping has an Import Square transfers button and a list of saved transfers. It reads the last 12 months of payouts from Square's Money/Transfers data, following every pagination cursor. Configure the [square] section in private config.ini:
environment = productionfor real Square data, orsandboxfor test data.accessTokenmust havePAYOUTS_READaccess.locationIdselects a location; if empty, Square uses the seller's default location.
The server makes read-only Square requests. Credentials never enter the browser. Imports preserve the payout ID, signed amount, currency, arrival date, status, and environment in NeDB. Re-importing skips unchanged transfers and updates changed records, including statuses that move from paid to failed. Sandbox and production records have separate identities. Older saved records outside the latest 12-month import window are retained.
Transfers are stored separately from manual bookkeeping income/expenses, so importing them does not count sales twice. No payments or bank transfers are initiated. The page remains limited to the requested button and saved-transfer list.
Endpoints: GET /api/square/transfers and POST /api/square/transfers/import. The import returns counts for imported, updated, unchanged, and total. Square API version is pinned to 2026-09-16. Upstream pagination completes before records are saved; failed requests can be retried without duplicating already saved transfers.
Reference: https://developer.squareup.com/reference/square/payouts-api/ListPayouts
CommBank CSV imports
Open Bookkeeping → CommBank transactions → Upload CSV. The tab uses a dark, date-grouped transaction list based on the supplied screenshot, with green deposit amounts, withdrawal amounts, search, and deposit/withdrawal filters.
The actual CommBank export has four columns in this order: Date,Amount,Description,Balance, with no header row. A matching header row is also accepted. Positive signed amounts are deposits; negative amounts are withdrawals; zero-value rows are skipped. This includes transfers, direct credits/debits, and card debits according to their signed amounts. Dates use DD/MM/YYYY. Quoted descriptions, signed balances, thousands separators, UTF-8 BOM, and trailing empty columns are supported. Maximum upload size is 2 MB. Invalid eligible rows reject the whole upload before anything is saved. The previous five-column Date,Description,Debit,Credit,Balance format remains supported with its original description-based filtering.
Imported records are kept in NeDB separately from Square transfers and manual transactions. Duplicate identities use the normalized posted date, description, signed amount in cents, and running balance in cents. The running balance preserves separate same-day transactions with otherwise identical details. A unique database index and serialized batch imports prevent matching transactions being inserted again across overlapping files, simultaneous uploads, or server restarts. This importer is for the single CommBank account represented by the supplied CSV, which contains no account or bank transaction ID.
Endpoints: GET /api/commbank/transactions and POST /api/commbank/transactions/import with { "csv": "CSV contents" }. Imports return imported, duplicates, skipped, and rows counts. The supplied actual CSV and example screenshot are excluded from Git because they contain financial information. Verification of the example uses a temporary database; it is not automatically imported into the live account.
Each CommBank deposit has a Mark as income toggle. Selected deposits show ✓ Actual income; press again to remove the mark. Use the Actual income filter to show only marked deposits. Marks are saved in NeDB and survive refreshes, server restarts, and re-imports. Withdrawals cannot be marked as income. New and previously unclassified deposits default to unmarked. PATCH /api/commbank/transactions/:id accepts { "actualIncome": true } or false; this changes classification only and does not create a second transaction.