From f8baf5da5cc3ab3121c6514b60a1155a072bbc10 Mon Sep 17 00:00:00 2001 From: ivan-pelly Date: Fri, 17 Apr 2026 15:31:35 -0700 Subject: [PATCH] Docs update --- docs/lighthouse_desktop.png | Bin 0 -> 17130 bytes docs/lighthouse_mobile.png | Bin 0 -> 17912 bytes docs/technical.html | 2472 ++++++++++++++++++++++------------- 3 files changed, 1588 insertions(+), 884 deletions(-) create mode 100644 docs/lighthouse_desktop.png create mode 100644 docs/lighthouse_mobile.png diff --git a/docs/lighthouse_desktop.png b/docs/lighthouse_desktop.png new file mode 100644 index 0000000000000000000000000000000000000000..0f9991c3ea3b63883745df45604a4950c6856e70 GIT binary patch literal 17130 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
!~C*xVlq;> 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 literal 0 HcmV?d00001 diff --git a/docs/technical.html b/docs/technical.html index e40f6cc..6e7d616 100644 --- a/docs/technical.html +++ b/docs/technical.html @@ -1,246 +1,255 @@ + - + WIN Student Goal Tracker – Developer Documentation - + @@ -248,117 +257,498 @@ +
-
Primary Key
-
Foreign Key
- +
Primary Key +
+
+
Foreign Key +
+
+ + + Relationship
@@ -777,10 +1380,15 @@ JWT_EXPIRATION=3600

Appendix C – Architecture Overview

-

High-level diagrams of the system tiers, two-phase auth flow, data model, role hierarchy, deployment, and key design decisions.

+

High-level diagrams of the system tiers, two-phase auth flow, data model, role hierarchy, deployment, and key + design decisions.

- + + + + + Open full-page Architecture Overview @@ -836,7 +1444,9 @@ JWT_EXPIRATION=3600
- + + + HTTPS / REST + JWT Bearer — win.opelly.me → winapi.opelly.me
@@ -857,7 +1467,9 @@ JWT_EXPIRATION=3600
- + + + HTTP (internal Docker network)
@@ -907,7 +1519,9 @@ JWT_EXPIRATION=3600
- + + + Stored procedures via Dapper — TCP :3309 (Docker internal)
@@ -949,7 +1563,9 @@ JWT_EXPIRATION=3600
- + + + Outbound HTTP from API tier only
@@ -995,7 +1611,9 @@ JWT_EXPIRATION=3600
- + + +
@@ -1008,7 +1626,9 @@ JWT_EXPIRATION=3600
- + + +
@@ -1021,7 +1641,9 @@ JWT_EXPIRATION=3600
- + + +
@@ -1034,7 +1656,9 @@ JWT_EXPIRATION=3600
- + + +
@@ -1047,7 +1671,9 @@ JWT_EXPIRATION=3600
- + + +
@@ -1063,11 +1689,14 @@ JWT_EXPIRATION=3600
- Token storage: auth_jwt, auth_refresh_token, auth_session_token — all in localStorage. + Token storage: auth_jwt, auth_refresh_token, + auth_session_token — all in localStorage.  |  - 401 interceptor: attempts one silent refresh; if that fails, redirects to /login. + 401 interceptor: attempts one silent refresh; if that fails, redirects to + /login.  |  - Refresh token rotation: each use replaces the old token (tracked via replaced_by_token_id). + Refresh token rotation: each use replaces the old token (tracked via + replaced_by_token_id).
@@ -1106,7 +1735,8 @@ JWT_EXPIRATION=3600
-
user_program (junction)
+
user_program (junction)
PK id_user_program
FK id_user
@@ -1128,7 +1758,8 @@ JWT_EXPIRATION=3600
-
user_student (junction)
+
user_student (junction)
PK id_user_student
FK id_user
@@ -1171,7 +1802,8 @@ JWT_EXPIRATION=3600
-
progress_event_benchmark (junction)
+
progress_event_benchmark (junction)
FK id_progress_event
FK id_benchmark
@@ -1222,11 +1854,26 @@ JWT_EXPIRATION=3600
Role Tiers (highest → lowest)
-
super_admin  ·  full platform access
-
district_admin  ·  manages programs
-
program_admin  ·  manages users
-
teacher  ·  full student CRUD
-
paraeducator  ·  log events
+
+
super_admin  ·  full platform access
+
+
+
district_admin  ·  manages programs
+
+
+
program_admin  ·  manages users
+
+
+
teacher  ·  full student CRUD
+
+
+
paraeducator  ·  log events
+
@@ -1244,14 +1891,70 @@ JWT_EXPIRATION=3600 - Student — CreateAllowAllowAllowAllowDeny - Student — UpdateAllowAllowAllowMineDeny - Goal — CreateAllowAllowAllowMineDeny - Goal — UpdateAllowAllowAllowMineDeny - ProgressEvent — CreateAllowAllowAllowAllowMine - ProgressEvent — DeleteAllowAllowAllowMineDeny - Program — CreateAllowMineDenyDenyDeny - User — CreateAllowMineMineDenyDeny + + Student — Create + Allow + Allow + Allow + Allow + Deny + + + Student — Update + Allow + Allow + Allow + Mine + Deny + + + Goal — Create + Allow + Allow + Allow + Mine + Deny + + + Goal — Update + Allow + Allow + Allow + Mine + Deny + + + ProgressEvent — Create + Allow + Allow + Allow + Allow + Mine + + + ProgressEvent — Delete + Allow + Allow + Allow + Mine + Deny + + + Program — Create + Allow + Mine + Deny + Deny + Deny + + + User — Create + Allow + Mine + Mine + Deny + Deny +
@@ -1387,581 +2090,582 @@ JWT_EXPIRATION=3600
- + renderTables(); + drawRelationships(); + applyTransform(); + setTimeout(updateMinimap, 100); + window.addEventListener('resize', updateMinimap); + - + + \ No newline at end of file