diff --git a/diagrams.md b/diagrams.md new file mode 100644 index 0000000..f34151c --- /dev/null +++ b/diagrams.md @@ -0,0 +1,234 @@ +# Time Track — Architecture Diagrams + +## System Architecture + +Time Track is a time-tracking web application with a SolidJS frontend and a Spring Boot backend. The client communicates with the server exclusively via a REST API, and authentication is handled through JWT tokens stored in HTTP-only cookies. + +```mermaid +graph TD + Browser["Browser\n(SolidJS + TanStack Router/Query)"] + Server["Spring Boot Server\n(REST API on :8080)"] + DB["H2 In-Memory Database"] + Vite["Vite Dev Server\n(:5173)"] + + Browser -->|"REST API calls (JSON)"| Server + Server -->|"HTTP-only JWT cookie"| Browser + Server --> DB + Browser -.->|"HMR in development"| Vite +``` + +--- + +## Data Model + +The core domain revolves around users who belong to organizations. Organizations own projects, and time entries are logged against projects. Each user has at most one active timer at a time. + +```mermaid +erDiagram + users { + UUID id PK + string name + string username + string password + } + + organizations { + UUID id PK + string name + } + + organization_members { + UUID id PK + UUID organization_id FK + UUID user_id FK + string role "OWNER | ADMINISTRATOR | MEMBER" + } + + projects { + UUID id PK + string name + UUID organization_id FK + } + + times { + UUID id PK + UUID project_id FK + datetime start_time + datetime end_time + } + + timers { + UUID id PK + UUID user_id FK + } + + timer_start_pauses { + UUID id PK + UUID timer_id FK + datetime started_at + datetime paused_at "null when running" + } + + users ||--o{ organization_members : "belongs to" + organizations ||--o{ organization_members : "has" + organizations ||--o{ projects : "owns" + projects ||--o{ times : "has" + users ||--o| timers : "has active" + timers ||--o{ timer_start_pauses : "tracks" +``` + +--- + +## REST API Endpoints + +The API is grouped by resource. All endpoints require a valid JWT cookie except sign-in and sign-up. + +```mermaid +graph LR + subgraph Auth ["/api — Auth"] + A1["POST /sign-ins"] + A2["POST /sign-ups"] + A3["POST /sign-outs"] + end + + subgraph Users ["/api/users — Users"] + U1["GET /current/id"] + U2["GET /{userId}/organizations"] + U3["POST /{userId}/organizations"] + U4["PUT /{userId}/organizations/{orgId}/name"] + U5["GET /{userId}/projects"] + U6["POST /{userId}/organizations/{orgId}/projects"] + U7["PUT /{userId}/projects/{projectId}"] + U8["GET /{userId}/times"] + U9["POST /{userId}/projects/{projectId}/times"] + U10["PUT /{userId}/times"] + U11["DELETE /{userId}/times"] + end + + subgraph Timer ["/api/users/{userId}/timer — Timer"] + T1["GET /"] + T2["POST /start"] + T3["POST /pause"] + T4["POST /stop"] + T5["DELETE /"] + T6["DELETE /entries/{entryId}"] + end + + subgraph Orgs ["/api/organizations — Organizations"] + O1["POST /"] + O2["GET /{orgId}"] + O3["DELETE /{orgId}"] + O4["GET /{orgId}/users"] + O5["POST /{orgId}/members/registrations"] + O6["DELETE /{orgId}/members/{userId}"] + O7["PUT /{orgId}/members/{userId}/role"] + end +``` + +--- + +## Timer State Machine + +The timer follows a clear lifecycle. When stopped, all start/pause intervals are converted into permanent time entries and the timer is deleted. + +```mermaid +stateDiagram-v2 + [*] --> Idle : no timer exists + + Idle --> Running : POST /timer/start\n(creates timer + first entry) + + Running --> Paused : POST /timer/pause\n(sets pausedAt on current entry) + + Paused --> Running : POST /timer/start\n(adds new start entry) + + Paused --> Idle : POST /timer/stop\n(converts entries → times, deletes timer) + + Running --> Idle : DELETE /timer\n(discard) + Paused --> Idle : DELETE /timer\n(discard) + + Paused --> Paused : DELETE /timer/entries/{id}\n(remove one interval) +``` + +--- + +## Authentication Flow + +Sign-in uses HTTP Basic Auth in the request header. On success the server returns a short-lived JWT in an HTTP-only `Strict` same-site cookie that the browser attaches automatically to every subsequent API call. + +```mermaid +sequenceDiagram + participant B as Browser + participant S as Spring Boot Server + + B->>S: POST /api/sign-ins\n Authorization: Basic + S->>S: Verify credentials & generate JWT + S-->>B: 200 OK\n Set-Cookie: jwt=...; HttpOnly; SameSite=Strict\n Body: userId + + Note over B,S: Subsequent authenticated requests + + B->>S: GET /api/users/{userId}/projects\n Cookie: jwt=... + S->>S: JwtAuthenticationFilter validates token + S-->>B: 200 OK — project list +``` + +--- + +## Frontend Route Structure + +The client uses TanStack Router with a file-based route tree. Protected routes are nested under `_app`, which handles authentication checks. Unauthenticated users are redirected to `/sign-in`. + +```mermaid +graph TD + Root["__root\n(layout + navigation)"] + + Root --> Index["/\n(redirect)"] + Root --> SignIn["/sign-in"] + Root --> SignUp["/sign-up"] + Root --> App["/_app\n(auth guard)"] + + App --> Timer["/_app/timer\nActive timer & controls"] + App --> Times["/_app/times\nTime entry history"] + App --> Projects["/_app/projects\nProject list"] + App --> Orgs["/_app/organizations\nOrganization list"] + App --> Settings["/_app/settings"] + + Orgs --> OrgNew["/organizations/new"] + Orgs --> OrgEdit["/organizations/:id/edit"] + Orgs --> OrgMembers["/organizations/:id/members"] + OrgMembers --> AddMember["/organizations/:id/members/add"] + + Projects --> ProjectNew["/projects/new"] + Projects --> ProjectEdit["/projects/:id.edit"] + + Times --> TimeNew["/times/new"] +``` + +--- + +## Organization Roles & Permissions + +Organizations support a three-tier role system that controls who can manage members and projects. + +```mermaid +graph LR + subgraph Actions + ViewOrg["View organization"] + ManageMembers["Add / remove members"] + ChangeRoles["Change member roles"] + DeleteOrg["Delete organization"] + ManageProjects["Create / edit projects"] + end + + MEMBER -->|can| ViewOrg + MEMBER -->|can| ManageProjects + + ADMINISTRATOR -->|can| ViewOrg + ADMINISTRATOR -->|can| ManageMembers + ADMINISTRATOR -->|can| ManageProjects + + OWNER -->|can| ViewOrg + OWNER -->|can| ManageMembers + OWNER -->|can| ChangeRoles + OWNER -->|can| DeleteOrg + OWNER -->|can| ManageProjects +```