Source Tree Analysis

Multi-part Cargo workspace. Two members (backend, frontend) plus a workspace-excluded license-tool crate at deploy/license/.

shiftit/
├── Cargo.toml                         # Workspace manifest (members: backend, frontend; excludes deploy/license)
├── Cargo.lock                         # Committed
├── CLAUDE.md                          # Project handover doc — read this first
├── .gitlab-ci.yml                     # CI: fmt, clippy, backend test (with PG service), trunk build, cargo build
├── .pre-commit-config.yaml            # fmt + per-crate clippy (backend stable, frontend nightly+wasm32)
├── .sqlx/                             # sqlx offline query cache (committed; CI sets SQLX_OFFLINE=true)
│
├── backend/                           # Part: backend (project_type_id=backend)
│   ├── Cargo.toml                     # Stable Rust; features { default = [], saas = [] }
│   ├── migrations/                    # 0001..0007 — applied at startup via sqlx::migrate!
│   └── src/
│       ├── main.rs                    # ENTRY — create_router(), connect_db(), license check_on_startup
│       ├── openapi.rs                 # utoipa::OpenApi derive — every #[utoipa::path] handler listed here
│       ├── auth/                      # JWT, JWKS, license, middleware
│       │   ├── claims.rs              # Decoded JWT claims shape
│       │   ├── jwks.rs                # JWKS fetcher + cache (Zitadel)
│       │   ├── license.rs             # ed25519 license check (saas-feature-gated)
│       │   └── middleware.rs          # auth_middleware → injects TenantContext
│       ├── shared/                    # Cross-domain primitives
│       │   ├── error.rs               # ApiError → axum response
│       │   ├── pagination.rs          # Page<T> envelope (unused so far)
│       │   └── tenant_context.rs      # TenantContext, UserRole, rls_transaction()
│       ├── tenants/                   # Tenant settings
│       │   ├── db.rs                  # Includes rls_blocks_cross_tenant_reads test
│       │   ├── model.rs               # TenantSettings DTO
│       │   └── routes.rs              # GET/PATCH /tenant/settings
│       ├── shifts/                    # Largest domain — roles, locations, users, events, shifts, slots, assignments
│       │   ├── hierarchy.rs           # Role-hierarchy walking
│       │   ├── nominatim.rs           # Server-side geocoding for Location.lat/lng
│       │   ├── model.rs               # All shift-domain DTOs (RoleDefinition, Location, Event, Shift, ShiftSlot, …)
│       │   ├── db/                    # One file per aggregate
│       │   │   ├── assignments.rs     # claim_slot, assign_slot (P1: claim_slot race)
│       │   │   ├── events.rs
│       │   │   ├── locations.rs
│       │   │   ├── roles.rs
│       │   │   ├── shifts.rs
│       │   │   └── users.rs
│       │   └── routes/                # One per aggregate, mirrors db/
│       ├── time_tracking/
│       │   ├── guard.rs               # require_time_tracking_enabled — returns 404 when disabled
│       │   ├── db.rs                  # record_time_entry (P1: double-credit on edit), payout transactions
│       │   ├── model.rs               # TimeEntry, OvertimeBalance, PayoutRecord
│       │   └── routes.rs
│       ├── forms/
│       │   ├── db.rs                  # CRUD + customer_forms_lookup_by_slug call
│       │   ├── model.rs               # CustomerForm, FormSubmission, EventDocument
│       │   ├── rate_limit.rs          # Process-local DashMap RateLimiter (P2: per-tenant override, Redis)
│       │   ├── routes.rs              # Admin + customer-facing forms; presigned S3 upload URL
│       │   └── validation.rs          # Field-against-spec validation
│       ├── notifications/
│       │   ├── db.rs
│       │   ├── model.rs               # Notification, PushSubscription, NotificationPayload
│       │   ├── push.rs                # web-push payload encryption + dispatch
│       │   ├── routes.rs              # In-app inbox + push subscription CRUD
│       │   └── service.rs             # create_and_dispatch — call site fan-out (P1: not yet wired into business logic)
│       ├── events/                    # Event-document-only routes (lives separately for tag grouping)
│       │   └── documents.rs
│       └── test_support.rs            # #[cfg(test)] helpers
│
├── frontend/                          # Part: frontend (project_type_id=web; Rust/Leptos WASM)
│   ├── Cargo.toml                     # edition 2024, leptos 0.8 nightly, wasm-bindgen, gloo-*, rust-ui crates
│   ├── rust-toolchain.toml            # Pinned nightly-2026-04-25 + wasm32 target
│   ├── Trunk.toml                     # tailwindcss = "4.1.7", proxy /api/* → 127.0.0.1:3010, port 8080
│   ├── index.html                     # Trunk asset pipeline entry
│   ├── style/tailwind.css             # Tailwind v4 input
│   ├── public/                        # Static assets — manifest, icons, service worker target
│   └── src/
│       ├── main.rs                    # wasm-bindgen entry
│       ├── lib.rs                     # Re-exports
│       ├── app.rs                     # ENTRY — App component, Router with all routes
│       ├── api/
│       │   ├── client.rs              # api_get/post/patch/delete — JWT bearer, /api/v1 base
│       │   └── types.rs               # All DTOs mirrored from backend
│       ├── auth/
│       │   └── context.rs             # provide_auth_context, get_token (from gloo-storage)
│       ├── ui/                        # rust-ui primitives + our Sidebar (MIT, see LICENSE.rust-ui)
│       ├── admin/                     # Surface 1 — /admin/* (10 routed pages)
│       │   ├── shell.rs               # AdminShell layout
│       │   ├── dashboard.rs, locations.rs, roles.rs, settings.rs
│       │   ├── employees/             # list.rs, detail.rs
│       │   ├── events/                # list.rs, detail.rs
│       │   ├── forms/                 # list.rs, builder.rs, submissions.rs
│       │   └── shifts/                # list.rs, calendar.rs (placeholder), detail.rs
│       ├── employee/                  # Surface 2 — /app/* (5 routed pages)
│       │   ├── shell.rs
│       │   ├── schedule.rs, available.rs, overtime.rs, notifications.rs
│       └── customer/                  # Surface 3 — /f/:slug (single route)
│           ├── form_page.rs
│           ├── form_renderer.rs
│           └── fields/                # text.rs, number.rs, date_field.rs, select.rs, location.rs
│
├── deploy/
│   ├── docker/
│   │   ├── backend.Dockerfile         # Distroless final image
│   │   ├── nginx.Dockerfile           # Alpine nginx serving frontend/dist
│   │   ├── nginx.conf
│   │   └── docker-compose.yml         # backend + nginx + anubis + zitadel + rustfs (P1: pin :latest tags)
│   └── license/                       # Standalone crate (workspace-excluded), ed25519 license signing CLI
│       ├── Cargo.toml
│       ├── Cargo.lock                 # Its own lockfile
│       └── src/
│
├── docs/                              # ← you are here. Project documentation root.
│   ├── follow-ups.md                  # Outstanding work, prioritised
│   ├── licensing.md
│   ├── deployment/                    # binary.md, docker-compose.md, nginx.md, zitadel-setup.md
│   └── superpowers/                   # Original (frozen) plans + design spec
│       ├── plans/                     # 01..09 implementation plans
│       └── specs/2026-05-08-shiftit-design.md
│
└── scripts/
    ├── run-backend.sh                 # cargo run with DEV_AUTH_* env for admin/employee/customer
    ├── mock-jwks.py                   # Tiny HTTP server: serves empty {"keys":[]} at /oauth/v2/keys
    └── seed-demo.sql                  # Idempotent demo data (Demo GmbH tenant, 3 users, roles, locations, etc.)

Critical entry points

Concern File

Backend bootstrap

backend/src/main.rs::mainconnect_db()JwksCache::new()create_router()axum::serve

Backend route registration

backend/src/main.rs::create_router (single source of truth for paths)

Backend OpenAPI catalog

backend/src/openapi.rs::ApiDoc — every annotated handler must also be listed here

Frontend bootstrap

frontend/src/main.rs → mounts App from frontend/src/app.rs

Frontend route registration

frontend/src/app.rs (single <Routes> block — all 16 routes)

Auth middleware

backend/src/auth/middleware.rs::auth_middleware

RLS tenant scope

backend/src/shared/tenant_context.rs::TenantContext::rls_transaction

Migrations

backend/migrations/0001..0007_*.sql

Multi-part interface

The two parts communicate exclusively over HTTP at /api/v1. There is no shared Rust code between them — the frontend redeclares DTO shapes in frontend/src/api/types.rs (intentionally minimal subsets, sometimes with #[serde(default)] for forward-compat). The interface is documented in integration-architecture.md.