Skip to content

Projects

URL: https://projects.heezy.infoLAN only Internal: http://192.168.1.15:30862 k8s: heezy namespace, deployment heezy-projects Source: heezy-containers/dockerfiles/heezy-projects/ Manifests: heezy-k8s/apps/heezy-projects/ Last Updated: 2026-08-14


What It Does

Tracks planned and in-flight projects: what it is, what it needs, what vendors quoted, what it actually cost. Flask, two server-rendered pages plus a JSON API.

Route Page
/ All projects, with rollup counters
/project/<id> One project: requirements, quotes, attachments

The index computes four figures in Python, not SQL: total projects, in-progress count, completions this calendar year, and the sums of actual_cost and estimated_cost across every project regardless of status. The spend figure is therefore lifetime, not year to date.

Deployed 2026-08-02.


Vocabulary

Three fixed lists in app.py, passed to the templates as dropdown options:

List Values
CATEGORIES home-improvement, tech-lab, vehicle, yard, other
STATUSES planning, in-progress, on-hold, completed
PRIORITIES low, medium, high

The database does not enforce any of them — the columns are plain TEXT with defaults other, planning and medium. An API client sending an unlisted value gets it stored.

Ordering on the index is priority DESC, updated_at DESC, which is a lexical sort on the priority text: medium > low > high. High-priority projects sort last.


Database

Its own projects database on big-boi, separate from the shared heezy database, using the same heezy_app credentials from OpenBao production/heezy/postgres/heezy-credentials.

Table Holds
projects Name, category, status, priority, description, notes, estimated/actual cost, three dates
project_requirements Free-text requirement lines
project_quotes Vendor, rep, amount, contact details, notes, optional attachment
project_tasks Checklist items
project_log Timestamped entries

Every child table is ON DELETE CASCADE against projects, so deleting a project takes its quotes and requirements with it.

project_tasks and project_log have no routes yet

Both tables ship in schema.sql and neither is read or written by app.py. They are schema laid down ahead of the feature, not something the UI exposes.


Quote attachments

POST /api/projects/<id>/quotes is multipart/form-data, not JSON — it is the only write endpoint that is. The optional attachment file is validated by extension against ALLOWED_EXTENSIONS (pdf, jpg, jpeg, png, gif, webp, heic, heif, bmp, tiff, tif) and rejected with a 400 otherwise.

Two names are stored: attachment_filename is the sanitised original for display, attachment_stored is a UUID plus extension and is the actual file on disk. Serving goes through GET /api/quotes/<id>/attachment, which looks the mimetype back up from the stored extension.

Deleting a quote removes the file before the row.

Property Value
PVC heezy-projects-data (5Gi, longhorn, RWO)
Mount /data
Attachments /data/attachments/<uuid>.<ext> (UPLOAD_DIR)

RWO plus strategy: Recreate caps this at one replica. Scaling up would leave the second pod unable to attach the volume.


API

Route Purpose
POST /api/projects Create, returns the new id
PUT /api/projects/<id> Full replace, bumps updated_at
DELETE /api/projects/<id> Deletes the project and everything cascaded from it
POST /api/projects/<id>/requirements, DELETE /api/requirements/<id> Requirements
POST /api/projects/<id>/quotes Multipart, optional attachment
DELETE /api/quotes/<id> Deletes the quote and its file
GET /api/quotes/<id>/attachment Serves the stored file
GET /api/health Liveness

PUT /api/projects/<id> writes every column from the payload. A partial body blanks the fields it omits; there is no PATCH.

No authentication. Same as Maintenance: the only control is that the name does not resolve off-LAN.


Access

projects.heezy.info is a dnsmasq override to the SWAG VIP and nothing else. No public DNS record, no Cloudflare tunnel hostname, no Access application. LAN clients need 192.168.1.29 as their resolver. See DNS Architecture.


CI

test-heezy-projects.yml runs pytest with --cov-fail-under=62. deploy-heezy-projects.yml builds, pushes to ECR and commits a .deploy-trigger to heezy-k8s.