Commit Graph

114 Commits

Author SHA1 Message Date
Ryan Moon
05f926c0dc fix: remove unused imports and dead code to clear ESLint errors 2026-04-01 20:34:56 -05:00
Ryan Moon
a73c2de26e feat: add frontend nginx image and update build workflow for both images 2026-04-01 20:32:34 -05:00
Ryan Moon
0f8aff9426 fix: resolve ESLint errors — remove unused imports and dead code 2026-04-01 20:18:13 -05:00
Ryan Moon
c3de66e554 fix: skip build workflow on version bump commits 2026-04-01 20:05:50 -05:00
Ryan Moon
7987818ae7 fix: use node script for version bump instead of npm version 2026-04-01 19:57:09 -05:00
Ryan Moon
c2b1073fef feat: add CI/CD pipeline, production Dockerfile, and deployment architecture
- Add production Dockerfile with bun build --compile, multi-stage Alpine build
- Add .dockerignore
- Swap bcrypt -> bcryptjs (pure JS, no native addons)
- Add programmatic migrations on startup via drizzle migrator
- Add /v1/version endpoint with APP_VERSION baked in at build time
- Add .gitea/workflows/ci.yml (lint + test with postgres/valkey services)
- Add .gitea/workflows/build.yml (version bump, build, push to registry)
- Update CLAUDE.md and docs/architecture.md to remove multi-tenancy
- Add docs/deployment.md covering DOKS + ArgoCD architecture

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-01 19:50:37 -05:00
Ryan Moon
ffef4c8727 Remove infra folder — moved to lunarfront-infra repo 2026-03-31 08:11:37 -05:00
Ryan Moon
d18d431bd0 Add terraform lock file 2026-03-31 06:12:44 -05:00
Ryan Moon
41b6f076cb Remove terraform provider binaries from git, add .gitignore 2026-03-31 06:12:25 -05:00
Ryan Moon
fe3c7646d6 Add infra setup: Terraform for DO droplet + Cloudflare DNS, Ansible roles for Gitea, Vaultwarden, and Gitea runner 2026-03-31 06:08:21 -05:00
Ryan Moon
bde3ad64fd Fix code review items: atomic qty increment, unit updatedAt, suppliers/all endpoint, SKU unique index 2026-03-31 05:08:01 -05:00
Ryan Moon
5f5ba9e4a2 Build inventory frontend and stock management features
- Full inventory UI: product list with search/filter, product detail with
  tabs (details, units, suppliers, stock receipts, price history)
- Product filters: category, type (serialized/rental/repair), low stock,
  active/inactive — all server-side with URL-synced state
- Product-supplier junction: link products to multiple suppliers with
  preferred flag, joined supplier details in UI
- Stock receipts: record incoming stock with supplier, qty, cost per unit,
  invoice number; auto-increments qty_on_hand for non-serialized products
- Price history tab on product detail page
- categories/all endpoint to avoid pagination limit on dropdown fetches
- categoryId filter on product list endpoint
- Repair parts and additional inventory items in music store seed data
- isDualUseRepair corrected: instruments set to false, strings/parts true
- Product-supplier links and stock receipts in seed data
- Price history seed data simulating cost increases over past year
- 37 API tests covering categories, suppliers, products, units,
  product-suppliers, and stock receipts
- alert-dialog and checkbox UI components
- sync-and-deploy.sh script for rsync + remote deploy
2026-03-30 20:12:07 -05:00
Ryan Moon
ec09e319ed Update accounting planning doc v2 — fix AP/safe accounts, add periods, tax rates, gift cards, consignment, posting service 2026-03-30 19:41:23 -05:00
Ryan Moon
89b412374a Expand POS planning — discounts, returns, cash management, training mode, customer display, quick keys 2026-03-30 19:15:13 -05:00
Ryan Moon
07f199b69d Add PIN unlock flow to POS frontend planning 2026-03-30 19:12:17 -05:00
Ryan Moon
ae3c85fee0 Add frontend strategy planning doc — admin, POS, floor app 2026-03-30 19:08:16 -05:00
Ryan Moon
5ad27bc196 Add lessons module, rate cycles, EC2 deploy scripts, and help content
- Lessons module: lesson types, instructors, schedule slots, enrollments,
  sessions (list + week grid view), lesson plans, grading scales, templates
- Rate cycles: replace monthly_rate with billing_interval + billing_unit on
  enrollments; add weekly/monthly/quarterly rate presets to lesson types and
  schedule slots with auto-fill on enrollment form
- Member detail page: tabbed layout for details, identity documents, enrollments
- Sessions week view: custom 7-column grid replacing react-big-calendar
- Music store seed: instructors, lesson types, slots, enrollments, sessions,
  grading scale, lesson plan template
- Scrollbar styling: themed to match sidebar/app palette
- deploy/: EC2 setup and redeploy scripts, nginx config, systemd service
- Help: add Lessons category (overview, types, instructors, slots, enrollments,
  sessions, plans/grading); collapsible sidebar with independent scroll;
  remove POS/accounting references from docs
2026-03-30 18:52:57 -05:00
Ryan Moon
7680a73d88 Add Phase 8: lesson plan templates with deep-copy instantiation
- New tables: lesson_plan_template, lesson_plan_template_section, lesson_plan_template_item
- skill_level enum: beginner, intermediate, advanced, all_levels
- Templates are reusable curriculum definitions independent of any member/enrollment
- POST /lesson-plan-templates/:id/create-plan deep-copies the template into a member plan
- Instantiation uses template name as default plan title, accepts custom title override
- Instantiation deactivates any existing active plan on the enrollment (one-active rule)
- Plan items are independent copies — renaming the template does not affect existing plans
- 11 new integration tests
2026-03-30 10:37:30 -05:00
Ryan Moon
2cc8f24535 Add Phase 7: grade history and session-plan item linking
- New tables: lesson_plan_item_grade_history (append-only), lesson_session_plan_item
- Grading an item updates current_grade_value and creates immutable history record
- Grading a not_started item auto-transitions it to in_progress
- Linking items to a session also auto-transitions not_started items
- Link operation is idempotent — re-linking same items produces no duplicates
- Endpoints: POST/GET /lesson-plan-items/:id/grades, GET /lesson-plan-items/:id/grade-history
- Endpoints: POST/GET /lesson-sessions/:id/plan-items
- 8 new integration tests
2026-03-30 10:33:21 -05:00
Ryan Moon
5cd2d05983 Add Phase 4b: instructor blocked dates, store closures, and substitute instructors
- New tables: instructor_blocked_date, store_closure (migration 0034)
- substitute_instructor_id column added to lesson_session
- Session generation skips blocked instructor dates and store closure periods
- Substitute assignment validates sub is not blocked and has no conflicting slot
- Routes: POST/GET/DELETE /instructors/:id/blocked-dates, POST/GET/DELETE /store-closures
- 15 new integration tests covering blocked dates, store closures, and sub validation
2026-03-30 10:29:13 -05:00
Ryan Moon
aae5a022a8 Add lessons Phase 6: lesson plans with curriculum tracking
Structured lesson plans with nested sections and items per enrollment.
Deep create in one request, one-active-per-enrollment constraint,
auto-set startedDate/masteredDate on status transitions, progress %
calculation (skipped items excluded). 8 new tests (84 total).
2026-03-30 09:40:41 -05:00
Ryan Moon
31f661ff4f Add lessons Phase 5: grading scales with nested levels
Custom grading scales with ordered levels (value, label, numeric score,
color). Supports one-default-per-store constraint, deep create with
nested levels, lookup endpoint for dropdowns, and search/pagination.
12 new tests (76 total lessons tests).
2026-03-30 09:36:48 -05:00
Ryan Moon
73360cd478 Add lessons Phase 4: lesson sessions with hybrid calendar generation
Individual lesson occurrences generated from schedule slot patterns.
Idempotent session generation with configurable rolling window.
Post-lesson notes workflow with auto-set notesCompletedAt. Status
tracking (scheduled/attended/missed/makeup/cancelled) and date/time
filtering. 13 new tests (64 total lessons tests).
2026-03-30 09:29:03 -05:00
Ryan Moon
93405af3b2 Add lessons Phase 3: enrollments with capacity and time conflict checks
Links members to schedule slots via enrollments. Enforces max_students
capacity on slots and prevents members from double-booking the same
day/time. Supports status transitions and filtering. 11 new tests
(51 total lessons tests).
2026-03-30 09:23:43 -05:00
Ryan Moon
f777ce5184 Add lessons Phase 2: schedule slots with conflict detection
Recurring weekly time slots linking instructors to lesson types.
Includes day/time overlap detection, instructor and day-of-week
filtering, and 17 new integration tests (40 total lessons tests).
2026-03-30 09:20:03 -05:00
Ryan Moon
5dbe837c08 Add lessons domain Phase 1: instructor and lesson type entities
Foundation tables for the lessons module with full CRUD, pagination,
search, and sorting. Includes migration, Drizzle schema, Zod validation,
services, routes, and 23 integration tests.
2026-03-30 09:17:32 -05:00
Ryan Moon
145eb0efce Improve sidebar: collapsible, grouped sections, scrollable nav, pinned footer
- Sidebar collapses to icon-only mode with toggle button
- Nav items grouped into sections (Customers, Repairs, Storage, Admin)
- Each section is independently collapsible
- Middle nav area scrolls when items overflow
- Help, profile, and sign out pinned to bottom
2026-03-30 08:52:23 -05:00
Ryan Moon
7176c1471e Add music store seed preset and repair data reset script
- music-store-seed.ts: 52 templates covering strings, brass, woodwinds,
  guitar, plus music-specific tickets and a school band batch
- reset-repairs.ts: clears all repair data for switching between presets
- New scripts: bun run db:seed-music, bun run db:seed-reset-repairs
2026-03-30 08:52:09 -05:00
Ryan Moon
701e15ea6d Remove DB-dependent unit tests, use api-tests for integration testing
All tests in __tests__/ were hitting the database directly. Integration
testing is handled by the api-tests/ suite instead.
2026-03-30 08:52:01 -05:00
Ryan Moon
9400828f62 Rename Forte to LunarFront, generalize for any small business
Rebrand from Forte (music-store-specific) to LunarFront (any small business):
- Package namespace @forte/* → @lunarfront/*
- Database forte/forte_test → lunarfront/lunarfront_test
- Docker containers, volumes, connection strings
- UI branding, localStorage keys, test emails
- All documentation and planning docs

Generalize music-specific terminology:
- instrumentDescription → itemDescription
- instrumentCount → itemCount
- instrumentType → itemCategory (on service templates)
- New migration 0027_generalize_terminology for column renames
- Seed data updated with generic examples
- RBAC descriptions updated
2026-03-30 08:51:54 -05:00
Ryan Moon
535446696c Include accessLevel in vault categories list response 2026-03-30 07:36:23 -05:00
Ryan Moon
a51f1f5141 Add vault category permissions dialog with role/user management 2026-03-30 07:23:23 -05:00
Ryan Moon
4438188362 Fix vault dialog autofill: prevent browser password manager from overriding dark theme colors 2026-03-30 07:18:58 -05:00
Ryan Moon
1510133074 Seed default roles on startup alongside permissions 2026-03-30 07:16:25 -05:00
Ryan Moon
328b4a1f7b Fix dev seed for single-company schema, sync RBAC on startup
- Remove all company_id references from dev-seed.ts (removed in 0021)
- seedPermissions now syncs role-permission assignments for system roles
  when new permissions are added (e.g., vault.view assigned to admin)
- Fix enum migration: use text cast workaround for PostgreSQL's
  "unsafe use of new enum value" error on fresh DB creation
2026-03-30 07:10:20 -05:00
Ryan Moon
e346e072b8 Add module management system for enabling/disabling features
Stores can enable/disable feature modules from Settings. When disabled,
nav links are hidden and API routes return 403. Designed as the
foundation for future license-based gating (licensed + enabled flags).

Core modules (Accounts, Members, Users, Roles, Settings) are always on.

- module_config table with slug, name, description, licensed, enabled
- In-memory cache for fast per-request module checks
- requireModule middleware wraps route groups in main.ts
- Settings page Modules card with toggle switches
- Sidebar hides nav links for disabled modules
- Default modules seeded: inventory, pos, repairs, rentals, lessons,
  files, vault, email, reports
2026-03-30 06:52:27 -05:00
Ryan Moon
1f9297f533 Add vault secret manager frontend UI
Three-state page: not initialized → locked → unlocked.
Any user with vault.view can unlock (for store opening).
Admins can lock and change master password.

- Two-panel layout: categories on left, entries on right
- Entry reveal button shows decrypted value for 30s with copy
- Add/edit/delete entries and categories
- KeyRound icon in sidebar navigation
2026-03-30 06:17:58 -05:00
Ryan Moon
7246587955 Add vault secret manager backend with AES-256-GCM encryption
Secrets are encrypted at rest in the database. The derived encryption
key is held in memory only — on reboot, an authorized user must enter
the master password to unlock. Admins can also manually lock the vault.

- vault_config, vault_category, vault_category_permission, vault_entry tables
- AES-256-GCM encryption with PBKDF2-derived key + per-entry IV
- Master password initialize/unlock/lock/change lifecycle
- Category CRUD with role/user permission model (view/edit/admin)
- Entry CRUD with reveal endpoint (POST to avoid caching)
- Secret values never returned in list/detail responses
- vault.view/edit/admin RBAC permissions seeded
- 19 API integration tests covering full lifecycle
2026-03-30 06:11:33 -05:00
Ryan Moon
748ea59c80 Harden storage permissions and WebDAV security
Permission service:
- Add hasAccess() with explicit minLevel param, deprecate canAccess()
- Cycle protection + depth limit (50) on all parent traversal
- Pick highest access level across multiple roles (was using first match)
- isPublic only grants view on directly requested folder, not inherited
- Sanitize file extension from content-type
- Clean up orphaned traverse perms when removing permissions
- Add getPermissionById() for authz checks on permission deletion

Storage routes:
- All write ops require edit via hasAccess() — traverse can no longer
  create folders, upload files, rename, toggle isPublic, or delete
- Permission delete requires admin access on the folder
- Permission list requires admin access on the folder
- Folder children listing filtered by user access
- File search results filtered by user access (was returning all)
- Signed URL requires view (was using canAccess which allows traverse)

WebDAV:
- 100MB upload size limit (was unbounded — OOM risk)
- PROPFIND root filters folders by user access (was listing all)
- COPY uses hasAccess('view') not canAccess (traverse bypass)
- All writes use hasAccess('edit') consistently
- MKCOL at root requires files.delete permission
- Lock ownership enforced on UNLOCK (was allowing any user)
- Lock conflict check on LOCK (423 if locked by another user)
- Lock enforcement on PUT and DELETE (423 if locked by another)
- Max 100 locks per user, periodic expired lock cleanup
- Path traversal protection: reject .. and null bytes in segments
- Brute-force protection: 10 failed attempts per IP, 5min lockout
2026-03-29 18:21:19 -05:00
Ryan Moon
f998b16a3f Add traverse access level for folder navigation without file access
When a permission is set on a nested folder, traverse is automatically
granted on all ancestor folders so users can navigate to it. Traverse
only shows subfolders in listings — files are hidden. This prevents
orphaned permissions where a user has access to a nested folder but
can't reach it.

Hierarchy: traverse < view < edit < admin
2026-03-29 18:04:24 -05:00
Ryan Moon
51ca2ca683 Add folder permissions UI and WebDAV protocol support
Permissions UI:
- FolderPermissionsDialog component with public/private toggle,
  role/user permission management, and access level badges
- Integrated into file manager toolbar (visible for folder admins)
- Backend returns accessLevel in folder detail endpoint

WebDAV server:
- Full WebDAV protocol at /webdav/ with Basic Auth (existing credentials)
- PROPFIND, GET, PUT, DELETE, MKCOL, COPY, MOVE, LOCK/UNLOCK support
- Permission-checked against existing folder access model
- In-memory lock stubs for Windows client compatibility
- 22 API integration tests covering all operations

Also fixes canAccess to check folder creator (was missing).
2026-03-29 17:38:57 -05:00
Ryan Moon
cbbf2713a1 Enlarge logo upload area to 256x128 for better preview 2026-03-29 16:39:36 -05:00
Ryan Moon
1002117610 Show store logo in sidebar with Amplified by Forte branding
Sidebar header now loads the store logo from the files API and
displays it scaled to fit. Below the logo: "Amplified by Forte"
in subtle text. Falls back to store name as text if no logo is
uploaded. Logo fetched via authenticated request with blob URL
and proper cleanup.
2026-03-29 16:32:18 -05:00
Ryan Moon
f9bf1c9bff Add rectangular logo upload to settings, support SVG content type
Settings page now shows a rectangular upload area for the store logo
instead of circular avatar. Uses authenticated image fetching with
blob URL cleanup. Accepts SVG in addition to JPEG/PNG/WebP. SVG
added to file serve content type map. Simplified to single logo
image (used on PDFs, sidebar, and login).
2026-03-29 16:27:02 -05:00
Ryan Moon
8d75586f8b Add store logo and app icon uploads to settings page
AvatarUpload component now supports custom category and placeholder
icon props. Settings page shows two upload circles: Store Logo (for
PDFs/invoices, uses ImageIcon placeholder) and App Icon (for sidebar/
login, uses Building placeholder). Added 'company' to allowed file
entity types.
2026-03-29 16:14:08 -05:00
Ryan Moon
653fff6ce2 Add store settings page with location management
Company table gains address and logo_file_id columns. New store
settings API: GET/PATCH /store for company info, full CRUD for
/locations. Settings page shows store name, phone, email, address,
timezone with inline edit. Location cards with add/edit/delete.
Settings link in admin sidebar. Fixes leftover company_id on
location table and seed files.
2026-03-29 15:56:02 -05:00
Ryan Moon
0f6cc104d2 Add shared file storage with folder tree, permissions, and file manager UI
New document hub for centralized file storage — replaces scattered
drives and USB sticks for non-technical SMBs. Three new tables:
storage_folder (nested hierarchy), storage_folder_permission (role
and user-level access control), storage_file.

Backend: folder CRUD with nested paths, file upload/download via
signed URLs, permission checks (view/edit/admin with inheritance
from parent folders), public/private toggle, breadcrumb navigation,
file search.

Frontend: two-panel file manager — collapsible folder tree on left,
icon grid view on right. Folder icons by type, file size display,
upload button, context menu for download/delete. Breadcrumb nav.
Files sidebar link added.
2026-03-29 15:31:20 -05:00
Ryan Moon
d36c6f7135 Remove multi-tenant company_id scoping from entire codebase
Drop company_id column from all 22 domain tables via migration.
Remove companyId from JWT payload, auth plugins, all service method
signatures (~215 occurrences), all route handlers (~105 occurrences),
test runner, test suites, and frontend auth store/types.

The company table stays as store settings (name, timezone). Tenant
isolation in a SaaS deployment would be at the database level (one
DB per customer) not the application level.

All 107 API tests pass. Zero TSC errors across all packages.
2026-03-29 14:58:33 -05:00
Ryan Moon
55f8591cf1 Add dev seed data, batch status progress bar, pre-fill account from batch
Dev seed script creates 8 accounts, 8 members, 16 repair templates,
6 repair tickets in various statuses, and a school batch with 5
instruments. Run with bun run db:seed-dev.

Batch detail page now has a status progress bar matching the ticket
detail pattern. Add Repair from batch pre-fills the account and
contact name. New repair form reads accountId and contactName from
search params when linked from a batch.
2026-03-29 14:04:02 -05:00
Ryan Moon
f7e78bec84 Improve repair batches: account search, add repair button, batch PDF
Batch create form now uses searchable account picker (same as ticket
intake) instead of a dropdown. Batch detail shows + Add Repair button
that creates a ticket pre-linked to the batch. Repair count and total
estimate auto-calculated from linked tickets. Batch PDF generates a
summary with all repairs, statuses, estimates, and grand total. Added
Mark Delivered button for completed batches. New repair form shows
batch badge when linked from a batch.
2026-03-29 13:38:25 -05:00