JK
>ΦΟΡΤΩΣΗ // ΦΟΡΤΩΣΗ
ΑΡΧΙΚΟΠΟΙΗΣΗ ΠΡΟΒΟΛΗΣ
Πλατφόρμα SaaS κατασκευασμένη με Go, PostgreSQL, Redis και Docker που παρέχει διαγνωστικά μαθητών μέσω αυτοματοποιημένης μηχανής SQI.
Τα εκπαιδευτικά ινστιτούτα στερούνται αυτοματοποιημένων εργαλείων για τον έγκαιρο εντοπισμό μαθησιακών κενών, απαιτώντας παράλληλα ασφαλή επιτήρηση και κλιμακούμενη επεξεργασία.
| Τεχνολογία | Έκδοση | Σκοπός | Γιατί επιλέχθηκε |
|---|---|---|---|
| Go | 1.25.1 | Backend language | Strong concurrency, fast compilation |
| Gin | 1.12.0 | HTTP framework | High performance, mature middleware ecosystem |
| PostgreSQL | 12+ | Primary database | JSONB for flexible analysis storage, pg_trgm for fuzzy search |
| React | 19.2.4 | Frontend SPA | Modern hooks-based architecture |
| TypeScript | ~6.0.2 | Type-safe JS | Compile-time safety |
| Vite | 8.0.4 | Build tool/dev server | Fast HMR, ESM-native |
| Tailwind CSS | 4.2.2 | Styling | Utility-first, no CSS-in-JS runtime |
| Redis | 9.7.0 (go-redis) | Cache, queues, rate limiting | Optional; degrades gracefully without it |
| JWT (golang-jwt) | 5.3.1 | Auth tokens | Stateless auth |
| lib/pq | 1.12.3 | PostgreSQL driver | Mature, standard library |
| Μέθοδος | Διαδρομή | Σκοπός |
|---|---|---|
| POST | /auth/login | User login (admin/coach) |
| POST | /student/login | Student login (student_code) |
| POST | /student/assignments/:id/start | Start exam attempt |
| POST | /student/assignments/:id/submit | Submit exam answers |
| POST | /student/assignments/:id/autosave | Autosave answers |
| POST | /student/assignments/:id/video-chunk | Upload video chunk |
| GET | /admin/tests | List tests |
| POST | /admin/tests | Create test |
| POST | /admin/assignments | Create assignment |
| POST | /admin/sqi/compute | Compute SQI for attempt |
| POST | /admin/sqi/compute-batch | Batch SQI computation |
| GET | /admin/students/:id/sqi | Get student SQI metrics |
| GET | /super-admin/tenants | List tenants |
| POST | /super-admin/plans | Create subscription plan |
| GET | /health | Health check (PostgreSQL ping) |
| Στήλη | Τύπος | Σημειώσεις |
|---|---|---|
| idPK | SERIAL | Auto-generated primary key |
| name | VARCHAR(100) | Organization/institute name |
| created_at | TIMESTAMP | Default NOW() |
| suspended_at | TIMESTAMP | Null if active |
| plan_id | INT | FK to subscription_plans (reference only) |
Multi-tenant root entity. Every other table has tenant_id FK with CASCADE delete.
| Στήλη | Τύπος | Σημειώσεις |
|---|---|---|
| idPK | SERIAL | Auto-generated primary key |
| tenant_id | INT | FK → tenants CASCADE |
| VARCHAR(100) | UNIQUE NOT NULL | |
| password | TEXT | bcrypt hashed |
| role | VARCHAR(20) | CHECK super_admin/admin/coach |
Students do NOT have user accounts; they authenticate via student_code.
| Στήλη | Τύπος | Σημειώσεις |
|---|---|---|
| idPK | SERIAL | Auto-generated primary key |
| tenant_id | INT | FK → tenants CASCADE |
| coach_id | INT | FK → coaches CASCADE |
| student_code | VARCHAR(50) | Auth credential (no password) |
| name | VARCHAR(100) | Student display name |
| batch_id | INT | FK → batches (optional) |
Partial unique index on (tenant_id, student_code) WHERE deleted_at IS NULL.
| Στήλη | Τύπος | Σημειώσεις |
|---|---|---|
| idPK | SERIAL | Auto-generated primary key |
| tenant_id | INT | FK → tenants CASCADE |
| title | TEXT | Test title |
| subject_id | INT | FK → subjects |
| coach_id | INT | FK → coaches CASCADE |
| duration | INT | Duration in seconds |
| subject_name | VARCHAR(100) | Denormalized for SQI display |
subject_name denormalized in migration 6 to avoid JOIN for SQI.
| Στήλη | Τύπος | Σημειώσεις |
|---|---|---|
| idPK | SERIAL | Auto-generated primary key |
| test_id | INT | FK → tests CASCADE |
| question_text | TEXT | NOT NULL |
| correct_answer | CHAR(1) | CHECK A/B/C/D |
| marks | FLOAT | Positive marks |
| neg_marks | FLOAT | Negative marks |
| importance | VARCHAR(10) | high/medium/low |
| difficulty | CHAR(1) | E(asy)/M(edium)/H(ard) |
| expected_time | FLOAT | Expected time in seconds |
| concept_tag | VARCHAR(100) | Concept grouping |
MCQ questions with metadata for SQI weight computation.
| Στήλη | Τύπος | Σημειώσεις |
|---|---|---|
| idPK | SERIAL | Auto-generated primary key |
| attempt_id | INT | UNIQUE FK → attempts CASCADE |
| sqi_score | FLOAT | Overall SQI score |
| raw_score | FLOAT | Raw marks scored |
| analysis_json | JSONB | Full DiagnosticPayloadV2 |
| version | VARCHAR(10) | Default 'v2' |
SQI analysis stored as JSONB — schema evolves without migrations.
tenants → users/coaches/students/subjects/tests/batches (1:N, tenant_id FK CASCADE). coaches → students (1:N). tests → questions (1:N CASCADE). students → assignments → attempts → answer_logs → attempt_results. 22+ tables total across 18 migrations.
Απόφαση: Every table has tenant_id FK with CASCADE delete instead of schema-per-tenant
Λόγος: Simpler than schema-per-tenant; adequate for expected scale
Οφέλη: Single database, simple queries, easy cross-tenant analytics
Ανταλλάγματα: Must include tenant_id in every query; no physical data isolation
Απόφαση: Students login with student_code only, no password or user account
Λόγος: Students are numerous, short-lived, and don't need full user accounts
Οφέλη: Simplified onboarding, no password management
Ανταλλάγματα: Weaker auth (code-only), no email/password recovery
Απόφαση: SQI v2 engine has no DB dependencies; takes inputs, returns payload
Λόγος: Separation of analytics from data access; testable in isolation
Οφέλη: Unit testable, deterministic, reusable
Ανταλλάγματα: Requires data to be fetched and assembled before calling
Απόφαση: Store full DiagnosticPayloadV2 as JSONB in attempt_results
Λόγος: Schema evolves faster than migrations; analysis payload is read-heavy, write-once
Οφέλη: No migration needed for analysis schema changes; flexible querying
Ανταλλάγματα: No referential integrity on nested data; harder to query specific fields
Απόφαση: Redis is optional — in-process queue and in-memory rate limiter as fallbacks
Λόγος: Development simplicity; single-binary deployment possible
Οφέλη: Works without Redis in development; scales with Redis in production
Ανταλλάγματα: In-process queue loses jobs on restart; in-memory rate limiter isn't shared
Απόφαση: ffmpeg concat on first stream request, not on upload
Λόγος: Most videos are never watched; avoid wasted compute
Οφέλη: Saves storage and compute for unwatched recordings
Ανταλλάγματα: First viewer experiences delay; requires ffmpeg on server