mirror of https://github.com/docusealco/docuseal
parent
984d36aea2
commit
2665bc4ef6
File diff suppressed because it is too large
Load Diff
@ -1,645 +0,0 @@
|
||||
# API Design and Integration
|
||||
|
||||
## API Integration Strategy
|
||||
|
||||
**API Integration Strategy:** Extend existing DocuSeal API v1 with new cohort-specific endpoints under `/api/v1/cohorts/*`. All endpoints follow existing RESTful patterns, authentication mechanisms, and response formats.
|
||||
|
||||
**Authentication:** Reuse existing Devise + JWT authentication. No changes to auth flow required. New endpoints will require the same bearer token authentication as existing endpoints.
|
||||
|
||||
**Versioning:** No new API version required. New endpoints extend v1 following existing patterns. All new endpoints return consistent JSON response formats matching existing endpoints.
|
||||
|
||||
## New API Endpoints
|
||||
|
||||
### **Cohort Management Endpoints**
|
||||
|
||||
#### **Create Cohort**
|
||||
- **Method:** `POST`
|
||||
- **Endpoint:** `/api/v1/cohorts`
|
||||
- **Purpose:** Create new cohort with templates and configuration
|
||||
- **Integration:** Uses existing Template APIs for template management
|
||||
|
||||
**Request:**
|
||||
```json
|
||||
{
|
||||
"cohort": {
|
||||
"name": "Q1 2025 Learnership",
|
||||
"program_type": "learnership",
|
||||
"sponsor_email": "sponsor@company.com",
|
||||
"student_count": 50,
|
||||
"main_template_id": 123,
|
||||
"supporting_template_ids": [124, 125],
|
||||
"start_date": "2025-02-01",
|
||||
"end_date": "2025-07-31"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"id": 1,
|
||||
"name": "Q1 2025 Learnership",
|
||||
"state": "draft",
|
||||
"created_at": "2025-01-02T10:00:00Z",
|
||||
"links": {
|
||||
"self": "/api/v1/cohorts/1",
|
||||
"enrollments": "/api/v1/cohorts/1/enrollments"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### **List Cohorts**
|
||||
- **Method:** `GET`
|
||||
- **Endpoint:** `/api/v1/cohorts`
|
||||
- **Purpose:** Get paginated list of cohorts for current institution
|
||||
- **Integration:** Filters by current user's institution
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"data": [
|
||||
{
|
||||
"id": 1,
|
||||
"name": "Q1 2025 Learnership",
|
||||
"program_type": "learnership",
|
||||
"state": "active",
|
||||
"completion_percentage": 65,
|
||||
"student_count": 50,
|
||||
"completed_students": 32
|
||||
}
|
||||
],
|
||||
"meta": {
|
||||
"page": 1,
|
||||
"per_page": 20,
|
||||
"total": 1
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### **Get Cohort Details**
|
||||
- **Method:** `GET`
|
||||
- **Endpoint:** `/api/v1/cohorts/:id`
|
||||
- **Purpose:** Get detailed cohort information with enrollment status
|
||||
- **Integration:** Aggregates data from existing Submission APIs
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"id": 1,
|
||||
"name": "Q1 2025 Learnership",
|
||||
"program_type": "learnership",
|
||||
"state": "active",
|
||||
"sponsor_email": "sponsor@company.com",
|
||||
"admin_signed_at": "2025-01-02T10:30:00Z",
|
||||
"templates": {
|
||||
"main": { "id": 123, "name": "Learnership Agreement" },
|
||||
"supporting": [{ "id": 124, "name": "Code of Conduct" }]
|
||||
},
|
||||
"enrollments": {
|
||||
"waiting": 5,
|
||||
"in_progress": 13,
|
||||
"complete": 32
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### **Invite Students**
|
||||
- **Method:** `POST`
|
||||
- **Endpoint:** `/api/v1/cohorts/:id/invitations`
|
||||
- **Purpose:** Generate invite links or send email invitations
|
||||
- **Integration:** Uses existing email system and user creation
|
||||
|
||||
**Request:**
|
||||
```json
|
||||
{
|
||||
"students": [
|
||||
{ "email": "student1@example.com", "first_name": "John", "last_name": "Doe" },
|
||||
{ "email": "student2@example.com", "first_name": "Jane", "last_name": "Smith" }
|
||||
],
|
||||
"send_email": true
|
||||
}
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"invitations_sent": 2,
|
||||
"invite_links": [
|
||||
{ "email": "student1@example.com", "link": "https://flo.doc/invite/abc123" },
|
||||
{ "email": "student2@example.com", "link": "https://flo.doc/invite/def456" }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
#### **Export Cohort Data (FR23)**
|
||||
- **Method:** `GET`
|
||||
- **Endpoint:** `/api/v1/cohorts/:id/export`
|
||||
- **Purpose:** Export cohort data to Excel format
|
||||
- **Integration:** Uses existing rubyXL gem for Excel generation
|
||||
|
||||
**Query Parameters:**
|
||||
- `format=xlsx`
|
||||
- `include=student_demographics,program_details,sponsor_info`
|
||||
|
||||
**Response:** Excel file download with columns: cohort_name, student_name, student_surname, student_age, student_race, student_city, program_type, sponsor_company_name, disability_status, gender
|
||||
|
||||
## Web Portal Routes
|
||||
|
||||
### **Admin Portal Routes**
|
||||
|
||||
| Route | Method | Purpose | Authentication | Component |
|
||||
|-------|--------|---------|----------------|-----------|
|
||||
| `/cohorts/admin` | GET | Cohort dashboard | Devise + Role | `AdminPortal.vue` |
|
||||
| `/cohorts/admin/new` | GET | Create cohort wizard | Devise + Role | `CohortWizard.vue` |
|
||||
| `/cohorts/admin/:id` | GET | Cohort details | Devise + Role | `CohortDashboard.vue` |
|
||||
| `/cohorts/admin/:id/verify` | GET | Document verification | Devise + Role | `VerificationInterface.vue` |
|
||||
| `/cohorts/admin/:id/sponsors` | GET | Sponsor management | Devise + Role | `SponsorCoordinator.vue` |
|
||||
| `/cohorts/admin/:id/analytics` | GET | Analytics view | Devise + Role | `AnalyticsView.vue` |
|
||||
| `/cohorts/admin/:id/export` | GET | Excel export (FR23) | Devise + Role | `ExcelExport.vue` |
|
||||
| `/cohorts/admin/:id/invite` | POST | Student invitations | Devise + Role | API call |
|
||||
|
||||
### **Student Portal Routes**
|
||||
|
||||
| Route | Method | Purpose | Authentication | Component |
|
||||
|-------|--------|---------|----------------|-----------|
|
||||
| `/cohorts/student/:token` | GET | Portal entry (token) | Token-based | `StudentPortal.vue` |
|
||||
| `/cohorts/student/:token/welcome` | GET | Welcome screen | Token-based | `CohortWelcome.vue` |
|
||||
| `/cohorts/student/:token/upload` | GET | Document upload | Token-based | `DocumentUpload.vue` |
|
||||
| `/cohorts/student/:token/agreement` | GET | Main agreement | Token-based | `AgreementForm.vue` |
|
||||
| `/cohorts/student/:token/supporting` | GET | Supporting docs | Token-based | `AgreementForm.vue` |
|
||||
| `/cohorts/student/:token/status` | GET | Progress dashboard | Token-based | `StatusDashboard.vue` |
|
||||
| `/cohorts/student/:token/resubmit` | GET | Re-submission flow | Token-based | `ResubmissionFlow.vue` |
|
||||
|
||||
### **Sponsor Portal Routes**
|
||||
|
||||
| Route | Method | Purpose | Authentication | Component |
|
||||
|-------|--------|---------|----------------|-----------|
|
||||
| `/cohorts/sponsor/:token` | GET | Sponsor dashboard | Token-based | `SponsorPortal.vue` |
|
||||
| `/cohorts/sponsor/:token/overview` | GET | Cohort overview | Token-based | `SponsorDashboard.vue` |
|
||||
| `/cohorts/sponsor/:token/student/:student_id` | GET | Student review | Token-based | `StudentReview.vue` |
|
||||
| `/cohorts/sponsor/:token/bulk-sign` | POST | Bulk signing | Token-based | `BulkSigning.vue` |
|
||||
| `/cohorts/sponsor/:token/finalize` | POST | Cohort finalization | Token-based | `CohortFinalization.vue` |
|
||||
|
||||
### **Enrollment Management Endpoints**
|
||||
|
||||
#### **List Enrollments**
|
||||
- **Method:** `GET`
|
||||
- **Endpoint:** `/api/v1/cohorts/:id/enrollments`
|
||||
- **Purpose:** Get all student enrollments with status
|
||||
- **Integration:** Aggregates from CohortEnrollment + existing User/Submission data
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"data": [
|
||||
{
|
||||
"id": 1,
|
||||
"student": { "name": "John Doe", "email": "john@example.com" },
|
||||
"state": "complete",
|
||||
"verification_state": "verified",
|
||||
"documents": { "uploaded": 5, "signed": 3 },
|
||||
"created_at": "2025-01-01T10:00:00Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
#### **Verify Document**
|
||||
- **Method:** `POST`
|
||||
- **Endpoint:** `/api/v1/enrollments/:id/verify`
|
||||
- **Purpose:** Admin document verification (approve/reject)
|
||||
- **Integration:** Creates DocumentVerification records
|
||||
|
||||
**Request:**
|
||||
```json
|
||||
{
|
||||
"action": "reject",
|
||||
"document_type": "matric_certificate",
|
||||
"reason": "Certificate is not certified by SAQA"
|
||||
}
|
||||
```
|
||||
|
||||
### **Sponsor Endpoints**
|
||||
|
||||
#### **Get Sponsor Cohort Overview**
|
||||
- **Method:** `GET`
|
||||
- **Endpoint:** `/api/v1/sponsors/cohorts/:token`
|
||||
- **Purpose:** Sponsor access to cohort overview (token-based auth)
|
||||
- **Integration:** Validates token, checks all students complete
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"cohort": { "name": "Q1 2025 Learnership", "student_count": 50 },
|
||||
"students": [
|
||||
{ "id": 1, "name": "John Doe", "state": "complete", "signed": true }
|
||||
],
|
||||
"can_sign": true,
|
||||
"bulk_sign_available": true
|
||||
}
|
||||
```
|
||||
|
||||
#### **Bulk Sign**
|
||||
|
||||
**Request:**
|
||||
```json
|
||||
{
|
||||
"signature": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUg...",
|
||||
"initials": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUg...",
|
||||
"sign_all": true,
|
||||
"timestamp": "2025-01-02T15:30:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
**Success Response (200):**
|
||||
```json
|
||||
{
|
||||
"signed_count": 50,
|
||||
"failed_count": 0,
|
||||
"signatures_applied": [
|
||||
{
|
||||
"enrollment_id": 1,
|
||||
"submission_id": 100,
|
||||
"status": "signed",
|
||||
"signed_at": "2025-01-02T15:30:00Z"
|
||||
}
|
||||
],
|
||||
"cohort_finalized": true,
|
||||
"next_step": "Admin can now finalize cohort and download documents"
|
||||
}
|
||||
```
|
||||
|
||||
**Error Responses:**
|
||||
```json
|
||||
// 422 Validation Error
|
||||
{
|
||||
"error": {
|
||||
"code": "VALIDATION_ERROR",
|
||||
"message": "Signature data is invalid or corrupted",
|
||||
"timestamp": "2025-01-02T15:30:00Z"
|
||||
}
|
||||
}
|
||||
|
||||
// 403 Forbidden
|
||||
{
|
||||
"error": {
|
||||
"code": "STATE_ERROR",
|
||||
"message": "Cannot sign - some students are not ready",
|
||||
"details": {
|
||||
"ready": 32,
|
||||
"total": 50,
|
||||
"pending": 18
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Complete API Response Schemas
|
||||
|
||||
### **Cohort Endpoints**
|
||||
|
||||
**POST /api/v1/cohorts - Request:**
|
||||
```json
|
||||
{
|
||||
"cohort": {
|
||||
"name": "Q1 2025 Learnership",
|
||||
"program_type": "learnership",
|
||||
"sponsor_email": "sponsor@company.com",
|
||||
"student_count": 50,
|
||||
"main_template_id": 123,
|
||||
"supporting_template_ids": [124, 125],
|
||||
"start_date": "2025-02-01",
|
||||
"end_date": "2025-07-31"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**POST /api/v1/cohorts - Success Response (201):**
|
||||
```json
|
||||
{
|
||||
"id": 1,
|
||||
"uuid": "550e8400-e29b-41d4-a716-446655440000",
|
||||
"name": "Q1 2025 Learnership",
|
||||
"program_type": "learnership",
|
||||
"state": "draft",
|
||||
"sponsor_email": "sponsor@company.com",
|
||||
"student_count": 50,
|
||||
"main_template_id": 123,
|
||||
"supporting_template_ids": [124, 125],
|
||||
"start_date": "2025-02-01",
|
||||
"end_date": "2025-07-31",
|
||||
"admin_signed_at": null,
|
||||
"created_at": "2025-01-02T10:00:00Z",
|
||||
"updated_at": "2025-01-02T10:00:00Z",
|
||||
"links": {
|
||||
"self": "/api/v1/cohorts/1",
|
||||
"enrollments": "/api/v1/cohorts/1/enrollments",
|
||||
"invitations": "/api/v1/cohorts/1/invitations"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**POST /api/v1/cohorts - Error Responses:**
|
||||
```json
|
||||
// 422 Validation Error
|
||||
{
|
||||
"errors": {
|
||||
"name": ["can't be blank"],
|
||||
"sponsor_email": ["is invalid"],
|
||||
"main_template_id": ["must exist"]
|
||||
}
|
||||
}
|
||||
|
||||
// 403 Forbidden (wrong institution)
|
||||
{
|
||||
"error": {
|
||||
"code": "AUTHORIZATION_ERROR",
|
||||
"message": "Access denied"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**GET /api/v1/cohorts/:id - Success Response (200):**
|
||||
```json
|
||||
{
|
||||
"id": 1,
|
||||
"uuid": "550e8400-e29b-41d4-a716-446655440000",
|
||||
"name": "Q1 2025 Learnership",
|
||||
"program_type": "learnership",
|
||||
"state": "active",
|
||||
"sponsor_email": "sponsor@company.com",
|
||||
"student_count": 50,
|
||||
"admin_signed_at": "2025-01-02T10:30:00Z",
|
||||
"created_at": "2025-01-02T10:00:00Z",
|
||||
"updated_at": "2025-01-02T10:30:00Z",
|
||||
"templates": {
|
||||
"main": {
|
||||
"id": 123,
|
||||
"name": "Learnership Agreement",
|
||||
"uuid": "abc123..."
|
||||
},
|
||||
"supporting": [
|
||||
{
|
||||
"id": 124,
|
||||
"name": "Code of Conduct",
|
||||
"uuid": "def456..."
|
||||
}
|
||||
]
|
||||
},
|
||||
"enrollment_summary": {
|
||||
"total": 50,
|
||||
"waiting": 5,
|
||||
"in_progress": 13,
|
||||
"complete": 32,
|
||||
"rejected": 0
|
||||
},
|
||||
"completion_percentage": 64,
|
||||
"links": {
|
||||
"self": "/api/v1/cohorts/1",
|
||||
"enrollments": "/api/v1/cohorts/1/enrollments",
|
||||
"export": "/api/v1/cohorts/1/export"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**GET /api/v1/cohorts/:id/enrollments - Success Response (200):**
|
||||
```json
|
||||
{
|
||||
"data": [
|
||||
{
|
||||
"id": 1,
|
||||
"uuid": "550e8400-e29b-41d4-a716-446655440001",
|
||||
"student": {
|
||||
"id": 100,
|
||||
"name": "John Doe",
|
||||
"email": "john@example.com",
|
||||
"phone": "+27123456789"
|
||||
},
|
||||
"state": "complete",
|
||||
"verification_state": "verified",
|
||||
"rejection_reason": null,
|
||||
"student_data": {
|
||||
"age": 23,
|
||||
"race": "Black",
|
||||
"city": "Johannesburg",
|
||||
"gender": "Male",
|
||||
"disability": "None"
|
||||
},
|
||||
"documents": {
|
||||
"uploaded": 5,
|
||||
"signed": 3,
|
||||
"rejected": 0
|
||||
},
|
||||
"created_at": "2025-01-01T10:00:00Z",
|
||||
"updated_at": "2025-01-02T14:30:00Z",
|
||||
"links": {
|
||||
"self": "/api/v1/enrollments/1",
|
||||
"verify": "/api/v1/enrollments/1/verify"
|
||||
}
|
||||
}
|
||||
],
|
||||
"meta": {
|
||||
"page": 1,
|
||||
"per_page": 20,
|
||||
"total": 50,
|
||||
"filters": {
|
||||
"state": ["complete"],
|
||||
"verification_state": ["verified"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**POST /api/v1/cohorts/:id/invitations - Request:**
|
||||
```json
|
||||
{
|
||||
"students": [
|
||||
{
|
||||
"email": "student1@example.com",
|
||||
"first_name": "John",
|
||||
"last_name": "Doe",
|
||||
"phone": "+27123456789",
|
||||
"age": 23,
|
||||
"race": "Black",
|
||||
"city": "Johannesburg",
|
||||
"gender": "Male",
|
||||
"disability": "None"
|
||||
}
|
||||
],
|
||||
"send_email": true,
|
||||
"message": "Welcome to our Q1 2025 Learnership program!"
|
||||
}
|
||||
```
|
||||
|
||||
**POST /api/v1/cohorts/:id/invitations - Success Response (201):**
|
||||
```json
|
||||
{
|
||||
"invitations_sent": 1,
|
||||
"invite_links": [
|
||||
{
|
||||
"email": "student1@example.com",
|
||||
"token": "abc123def456",
|
||||
"link": "https://flo.doc/cohorts/student/abc123def456",
|
||||
"expires_at": "2025-02-01T10:00:00Z"
|
||||
}
|
||||
],
|
||||
"errors": []
|
||||
}
|
||||
```
|
||||
|
||||
**GET /api/v1/cohorts/:id/export - Query Parameters:**
|
||||
- `format=xlsx` (required)
|
||||
- `include=student_demographics,program_details,sponsor_info` (optional)
|
||||
|
||||
**GET /api/v1/cohorts/:id/export - Response:**
|
||||
- Returns Excel file (.xlsx) as binary download
|
||||
- **Headers:**
|
||||
```
|
||||
Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
|
||||
Content-Disposition: attachment; filename="cohort_1_export_20250102.xlsx"
|
||||
```
|
||||
|
||||
**Excel Columns:**
|
||||
```
|
||||
cohort_name | student_name | student_surname | student_age | student_race | student_city | program_type | sponsor_company_name | disability_status | gender
|
||||
```
|
||||
|
||||
### **Enrollment Endpoints**
|
||||
|
||||
**POST /api/v1/enrollments/:id/verify - Request:**
|
||||
```json
|
||||
{
|
||||
"action": "reject",
|
||||
"document_type": "matric_certificate",
|
||||
"reason": "Certificate is not certified by SAQA. Please provide SAQA verification letter.",
|
||||
"metadata": {
|
||||
"reviewed_by": "admin@institution.com",
|
||||
"review_notes": "Checked against SAQA database"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**POST /api/v1/enrollments/:id/verify - Success Response (200):**
|
||||
```json
|
||||
{
|
||||
"id": 1,
|
||||
"enrollment_id": 1,
|
||||
"action": "rejected",
|
||||
"document_type": "matric_certificate",
|
||||
"reason": "Certificate is not certified by SAQA. Please provide SAQA verification letter.",
|
||||
"admin_id": 50,
|
||||
"created_at": "2025-01-02T15:00:00Z",
|
||||
"metadata": {
|
||||
"reviewed_by": "admin@institution.com",
|
||||
"review_notes": "Checked against SAQA database"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**POST /api/v1/enrollments/:id/verify - Error Responses:**
|
||||
```json
|
||||
// 422 Invalid State Transition
|
||||
{
|
||||
"error": {
|
||||
"code": "STATE_ERROR",
|
||||
"message": "Cannot reject enrollment that is already complete"
|
||||
}
|
||||
}
|
||||
|
||||
// 404 Not Found
|
||||
{
|
||||
"error": {
|
||||
"code": "NOT_FOUND",
|
||||
"message": "Enrollment not found"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### **Sponsor Endpoints**
|
||||
|
||||
**GET /api/v1/sponsors/cohorts/:token - Success Response (200):**
|
||||
```json
|
||||
{
|
||||
"cohort": {
|
||||
"id": 1,
|
||||
"name": "Q1 2025 Learnership",
|
||||
"program_type": "learnership",
|
||||
"student_count": 50,
|
||||
"sponsor_email": "sponsor@company.com"
|
||||
},
|
||||
"students": [
|
||||
{
|
||||
"id": 1,
|
||||
"name": "John Doe",
|
||||
"email": "john@example.com",
|
||||
"state": "complete",
|
||||
"verification_state": "verified",
|
||||
"signed": true,
|
||||
"signed_at": "2025-01-02T10:00:00Z",
|
||||
"documents": {
|
||||
"main_agreement": {
|
||||
"id": 100,
|
||||
"status": "signed",
|
||||
"preview_url": "/api/v1/submissions/100/preview"
|
||||
},
|
||||
"supporting_docs": [
|
||||
{
|
||||
"id": 101,
|
||||
"name": "Code of Conduct",
|
||||
"status": "signed"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"summary": {
|
||||
"total": 50,
|
||||
"completed": 32,
|
||||
"pending": 18,
|
||||
"signed": 32
|
||||
},
|
||||
"can_sign": true,
|
||||
"bulk_sign_available": true,
|
||||
"token_expires_at": "2025-01-16T23:59:59Z"
|
||||
}
|
||||
```
|
||||
|
||||
**GET /api/v1/sponsors/cohorts/:token - Error Responses:**
|
||||
```json
|
||||
// 403 Forbidden (students not complete)
|
||||
{
|
||||
"error": {
|
||||
"code": "STATE_ERROR",
|
||||
"message": "All students must complete their submissions before sponsor access",
|
||||
"details": {
|
||||
"completed": 32,
|
||||
"total": 50,
|
||||
"remaining": 18
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 401 Unauthorized (invalid/expired token)
|
||||
{
|
||||
"error": {
|
||||
"code": "AUTHENTICATION_ERROR",
|
||||
"message": "Invalid or expired sponsor token"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### **Bulk Sign**
|
||||
- **Method:** `POST`
|
||||
- **Endpoint:** `/api/v1/sponsors/cohorts/:token/bulk-sign`
|
||||
- **Purpose:** Sign all student agreements at once
|
||||
- **Integration:** Uses existing submission signing APIs
|
||||
|
||||
**Request:**
|
||||
```json
|
||||
{
|
||||
"signature": "data:image/png;base64,...",
|
||||
"initials": "data:image/png;base64,..."
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
@ -0,0 +1,979 @@
|
||||
# API Design - FloDoc Architecture
|
||||
|
||||
**Document**: RESTful API Specifications
|
||||
**Version**: 1.0
|
||||
**Last Updated**: 2026-01-14
|
||||
|
||||
---
|
||||
|
||||
## 📡 API Overview
|
||||
|
||||
FloDoc provides a RESTful API for programmatic access to cohort management, student enrollment, and sponsor workflows. The API follows REST principles and uses JSON for request/response payloads.
|
||||
|
||||
**Base URL**: `/api/v1/`
|
||||
**Authentication**: Bearer token in Authorization header
|
||||
**Response Format**: JSON
|
||||
|
||||
---
|
||||
|
||||
## 🔐 Authentication
|
||||
|
||||
### JWT Token Authentication
|
||||
|
||||
All API requests must include an authentication token:
|
||||
|
||||
```http
|
||||
Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxfQ.signature
|
||||
```
|
||||
|
||||
### Token Generation
|
||||
|
||||
**Endpoint**: `POST /api/v1/auth/token`
|
||||
|
||||
**Request**:
|
||||
```json
|
||||
{
|
||||
"email": "admin@techpro.co.za",
|
||||
"password": "secure_password"
|
||||
}
|
||||
```
|
||||
|
||||
**Response (200 OK)**:
|
||||
```json
|
||||
{
|
||||
"token": "eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxfQ.signature",
|
||||
"expires_at": "2026-01-15T19:00:00Z",
|
||||
"user": {
|
||||
"id": 1,
|
||||
"email": "admin@techpro.co.za",
|
||||
"role": "tp_admin"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Error Response (401 Unauthorized)**:
|
||||
```json
|
||||
{
|
||||
"error": "Invalid credentials"
|
||||
}
|
||||
```
|
||||
|
||||
### Token Refresh
|
||||
|
||||
**Endpoint**: `POST /api/v1/auth/refresh`
|
||||
|
||||
**Headers**:
|
||||
```http
|
||||
Authorization: Bearer <old_token>
|
||||
```
|
||||
|
||||
**Response (200 OK)**:
|
||||
```json
|
||||
{
|
||||
"token": "new_token_here",
|
||||
"expires_at": "2026-01-16T19:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📚 API Endpoints
|
||||
|
||||
### 1. Cohorts Management
|
||||
|
||||
#### List Cohorts
|
||||
**Endpoint**: `GET /api/v1/cohorts`
|
||||
|
||||
**Authentication**: Required (TP Admin)
|
||||
|
||||
**Query Parameters**:
|
||||
- `status` (optional): Filter by status (`draft`, `active`, `completed`)
|
||||
- `page` (optional): Pagination page (default: 1)
|
||||
- `per_page` (optional): Items per page (default: 20)
|
||||
|
||||
**Request**:
|
||||
```http
|
||||
GET /api/v1/cohorts?status=active&page=1&per_page=10
|
||||
Authorization: Bearer <token>
|
||||
```
|
||||
|
||||
**Response (200 OK)**:
|
||||
```json
|
||||
{
|
||||
"data": [
|
||||
{
|
||||
"id": 1,
|
||||
"name": "2026 Q1 Learnership",
|
||||
"program_type": "learnership",
|
||||
"sponsor_email": "sponsor@company.co.za",
|
||||
"status": "active",
|
||||
"required_student_uploads": ["id_copy", "matric_certificate"],
|
||||
"cohort_metadata": {
|
||||
"start_date": "2026-02-01",
|
||||
"duration_months": 12,
|
||||
"stipend_amount": 3500
|
||||
},
|
||||
"tp_signed_at": "2026-01-14T10:00:00Z",
|
||||
"students_completed_at": null,
|
||||
"sponsor_completed_at": null,
|
||||
"finalized_at": null,
|
||||
"student_count": 15,
|
||||
"completed_count": 8,
|
||||
"created_at": "2026-01-10T08:00:00Z",
|
||||
"updated_at": "2026-01-14T10:00:00Z"
|
||||
}
|
||||
],
|
||||
"meta": {
|
||||
"current_page": 1,
|
||||
"total_pages": 3,
|
||||
"total_count": 25,
|
||||
"per_page": 10
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Error Response (401 Unauthorized)**:
|
||||
```json
|
||||
{
|
||||
"error": "Unauthorized"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### Create Cohort
|
||||
**Endpoint**: `POST /api/v1/cohorts`
|
||||
|
||||
**Authentication**: Required (TP Admin)
|
||||
|
||||
**Request Body**:
|
||||
```json
|
||||
{
|
||||
"name": "2026 Q2 Internship",
|
||||
"program_type": "internship",
|
||||
"sponsor_email": "hr@company.co.za",
|
||||
"template_id": 42,
|
||||
"required_student_uploads": ["id_copy", "cv", "tertiary_certificate"],
|
||||
"cohort_metadata": {
|
||||
"start_date": "2026-04-01",
|
||||
"duration_months": 6,
|
||||
"department": "Engineering"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Required Fields**:
|
||||
- `name` (string)
|
||||
- `program_type` (enum: `learnership`, `internship`, `candidacy`)
|
||||
- `sponsor_email` (string, valid email)
|
||||
- `template_id` (integer, must exist)
|
||||
|
||||
**Optional Fields**:
|
||||
- `required_student_uploads` (array of strings)
|
||||
- `cohort_metadata` (object)
|
||||
|
||||
**Response (201 Created)**:
|
||||
```json
|
||||
{
|
||||
"id": 2,
|
||||
"name": "2026 Q2 Internship",
|
||||
"program_type": "internship",
|
||||
"sponsor_email": "hr@company.co.za",
|
||||
"status": "draft",
|
||||
"required_student_uploads": ["id_copy", "cv", "tertiary_certificate"],
|
||||
"cohort_metadata": {
|
||||
"start_date": "2026-04-01",
|
||||
"duration_months": 6,
|
||||
"department": "Engineering"
|
||||
},
|
||||
"tp_signed_at": null,
|
||||
"students_completed_at": null,
|
||||
"sponsor_completed_at": null,
|
||||
"finalized_at": null,
|
||||
"created_at": "2026-01-14T19:00:00Z",
|
||||
"updated_at": "2026-01-14T19:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
**Error Response (422 Unprocessable Entity)**:
|
||||
```json
|
||||
{
|
||||
"errors": {
|
||||
"name": ["can't be blank"],
|
||||
"program_type": ["is not included in the list"],
|
||||
"sponsor_email": ["must be a valid email"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### Get Cohort Details
|
||||
**Endpoint**: `GET /api/v1/cohorts/:id`
|
||||
|
||||
**Authentication**: Required (TP Admin, must belong to same institution)
|
||||
|
||||
**Path Parameters**:
|
||||
- `id`: Cohort ID
|
||||
|
||||
**Request**:
|
||||
```http
|
||||
GET /api/v1/cohorts/1
|
||||
Authorization: Bearer <token>
|
||||
```
|
||||
|
||||
**Response (200 OK)**:
|
||||
```json
|
||||
{
|
||||
"id": 1,
|
||||
"name": "2026 Q1 Learnership",
|
||||
"program_type": "learnership",
|
||||
"sponsor_email": "sponsor@company.co.za",
|
||||
"status": "active",
|
||||
"required_student_uploads": ["id_copy", "matric_certificate"],
|
||||
"cohort_metadata": {
|
||||
"start_date": "2026-02-01",
|
||||
"duration_months": 12,
|
||||
"stipend_amount": 3500
|
||||
},
|
||||
"tp_signed_at": "2026-01-14T10:00:00Z",
|
||||
"students_completed_at": null,
|
||||
"sponsor_completed_at": null,
|
||||
"finalized_at": null,
|
||||
"template": {
|
||||
"id": 42,
|
||||
"name": "Standard Learnership Agreement"
|
||||
},
|
||||
"enrollments": [
|
||||
{
|
||||
"id": 101,
|
||||
"student_email": "john@example.com",
|
||||
"student_name": "John",
|
||||
"student_surname": "Doe",
|
||||
"status": "waiting",
|
||||
"role": "student",
|
||||
"uploaded_documents": {
|
||||
"id_copy": true,
|
||||
"matric_certificate": false
|
||||
},
|
||||
"completed_at": null
|
||||
}
|
||||
],
|
||||
"stats": {
|
||||
"total_students": 15,
|
||||
"completed": 8,
|
||||
"waiting": 5,
|
||||
"in_progress": 2
|
||||
},
|
||||
"created_at": "2026-01-10T08:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
**Error Response (404 Not Found)**:
|
||||
```json
|
||||
{
|
||||
"error": "Cohort not found"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### Update Cohort
|
||||
**Endpoint**: `PATCH /api/v1/cohorts/:id`
|
||||
|
||||
**Authentication**: Required (TP Admin)
|
||||
|
||||
**Request Body**:
|
||||
```json
|
||||
{
|
||||
"name": "Updated Cohort Name",
|
||||
"sponsor_email": "new.sponsor@company.co.za",
|
||||
"required_student_uploads": ["id_copy", "cv"]
|
||||
}
|
||||
```
|
||||
|
||||
**Response (200 OK)**:
|
||||
```json
|
||||
{
|
||||
"id": 1,
|
||||
"name": "Updated Cohort Name",
|
||||
"sponsor_email": "new.sponsor@company.co.za",
|
||||
"required_student_uploads": ["id_copy", "cv"],
|
||||
"updated_at": "2026-01-14T19:30:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
**Error Response (422 Unprocessable Entity)**:
|
||||
```json
|
||||
{
|
||||
"errors": {
|
||||
"sponsor_email": ["must be a valid email"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### Start Signing Phase
|
||||
**Endpoint**: `POST /api/v1/cohorts/:id/start_signing`
|
||||
|
||||
**Authentication**: Required (TP Admin)
|
||||
|
||||
**Description**: Transitions cohort from `draft` to `active` state. Allows students to enroll.
|
||||
|
||||
**Request**:
|
||||
```http
|
||||
POST /api/v1/cohorts/1/start_signing
|
||||
Authorization: Bearer <token>
|
||||
```
|
||||
|
||||
**Response (200 OK)**:
|
||||
```json
|
||||
{
|
||||
"id": 1,
|
||||
"status": "active",
|
||||
"tp_signed_at": "2026-01-14T19:30:00Z",
|
||||
"message": "Cohort is now active. Students can enroll."
|
||||
}
|
||||
```
|
||||
|
||||
**Error Responses**:
|
||||
- `400 Bad Request`: Cohort is not in draft state
|
||||
- `400 Bad Request`: No template associated
|
||||
- `403 Forbidden`: Insufficient permissions
|
||||
|
||||
```json
|
||||
{
|
||||
"error": "Cohort must be in draft state to start signing"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### Finalize Cohort
|
||||
**Endpoint**: `POST /api/v1/cohorts/:id/finalize`
|
||||
|
||||
**Authentication**: Required (TP Admin)
|
||||
|
||||
**Description**: Marks cohort as completed after sponsor signing. Generates final documents.
|
||||
|
||||
**Request**:
|
||||
```http
|
||||
POST /api/v1/cohorts/1/finalize
|
||||
Authorization: Bearer <token>
|
||||
```
|
||||
|
||||
**Response (200 OK)**:
|
||||
```json
|
||||
{
|
||||
"id": 1,
|
||||
"status": "completed",
|
||||
"finalized_at": "2026-01-14T19:45:00Z",
|
||||
"message": "Cohort finalized. All documents are ready for download."
|
||||
}
|
||||
```
|
||||
|
||||
**Error Responses**:
|
||||
- `400 Bad Request`: Sponsor hasn't completed signing
|
||||
- `400 Bad Request`: Students haven't completed
|
||||
|
||||
```json
|
||||
{
|
||||
"error": "Cannot finalize: sponsor signing incomplete"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2. Enrollments Management
|
||||
|
||||
#### List Cohort Enrollments
|
||||
**Endpoint**: `GET /api/v1/cohorts/:id/enrollments`
|
||||
|
||||
**Authentication**: Required (TP Admin)
|
||||
|
||||
**Query Parameters**:
|
||||
- `status` (optional): Filter by status
|
||||
- `role` (optional): Filter by role (`student`, `sponsor`)
|
||||
- `page` (optional): Pagination
|
||||
|
||||
**Request**:
|
||||
```http
|
||||
GET /api/v1/cohorts/1/enrollments?status=complete&role=student
|
||||
Authorization: Bearer <token>
|
||||
```
|
||||
|
||||
**Response (200 OK)**:
|
||||
```json
|
||||
{
|
||||
"data": [
|
||||
{
|
||||
"id": 101,
|
||||
"cohort_id": 1,
|
||||
"submission_id": 501,
|
||||
"student_email": "john@example.com",
|
||||
"student_name": "John",
|
||||
"student_surname": "Doe",
|
||||
"student_id": "STU2026001",
|
||||
"status": "complete",
|
||||
"role": "student",
|
||||
"uploaded_documents": {
|
||||
"id_copy": true,
|
||||
"matric_certificate": true,
|
||||
"cv": true
|
||||
},
|
||||
"values": {
|
||||
"full_name": "John Doe",
|
||||
"phone": "+27 82 123 4567"
|
||||
},
|
||||
"completed_at": "2026-01-14T15:00:00Z",
|
||||
"created_at": "2026-01-12T10:00:00Z"
|
||||
}
|
||||
],
|
||||
"meta": {
|
||||
"current_page": 1,
|
||||
"total_pages": 1,
|
||||
"total_count": 5
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### Create Enrollment (Bulk)
|
||||
**Endpoint**: `POST /api/v1/cohorts/:id/enrollments`
|
||||
|
||||
**Authentication**: Required (TP Admin)
|
||||
|
||||
**Description**: Creates multiple student enrollments at once. Sends invitation emails.
|
||||
|
||||
**Request Body**:
|
||||
```json
|
||||
{
|
||||
"students": [
|
||||
{
|
||||
"email": "john@example.com",
|
||||
"name": "John",
|
||||
"surname": "Doe",
|
||||
"student_id": "STU2026001"
|
||||
},
|
||||
{
|
||||
"email": "jane@example.com",
|
||||
"name": "Jane",
|
||||
"surname": "Smith",
|
||||
"student_id": "STU2026002"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**Response (201 Created)**:
|
||||
```json
|
||||
{
|
||||
"created": 2,
|
||||
"failed": 0,
|
||||
"enrollments": [
|
||||
{
|
||||
"id": 101,
|
||||
"student_email": "john@example.com",
|
||||
"status": "waiting",
|
||||
"token": "abc123xyz"
|
||||
},
|
||||
{
|
||||
"id": 102,
|
||||
"student_email": "jane@example.com",
|
||||
"status": "waiting",
|
||||
"token": "def456uvw"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**Error Response (207 Multi-Status)**:
|
||||
```json
|
||||
{
|
||||
"created": 1,
|
||||
"failed": 1,
|
||||
"errors": [
|
||||
{
|
||||
"email": "duplicate@example.com",
|
||||
"error": "Student already enrolled in this cohort"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### Get Enrollment Details
|
||||
**Endpoint**: `GET /api/v1/enrollments/:id`
|
||||
|
||||
**Authentication**: Required (TP Admin or Student with token)
|
||||
|
||||
**Request**:
|
||||
```http
|
||||
GET /api/v1/enrollments/101
|
||||
Authorization: Bearer <token>
|
||||
```
|
||||
|
||||
**Response (200 OK)**:
|
||||
```json
|
||||
{
|
||||
"id": 101,
|
||||
"cohort_id": 1,
|
||||
"submission_id": 501,
|
||||
"student_email": "john@example.com",
|
||||
"student_name": "John",
|
||||
"student_surname": "Doe",
|
||||
"status": "in_progress",
|
||||
"role": "student",
|
||||
"uploaded_documents": {
|
||||
"id_copy": true,
|
||||
"matric_certificate": false
|
||||
},
|
||||
"required_documents": ["id_copy", "matric_certificate", "cv"],
|
||||
"token": "abc123xyz",
|
||||
"token_expires_at": "2026-01-21T19:00:00Z",
|
||||
"created_at": "2026-01-12T10:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### Update Enrollment (Student Portal)
|
||||
**Endpoint**: `PATCH /api/v1/enrollments/:id`
|
||||
|
||||
**Authentication**: Token-based (ad-hoc)
|
||||
|
||||
**Description**: Student updates their enrollment, uploads documents, fills forms.
|
||||
|
||||
**Request Body**:
|
||||
```json
|
||||
{
|
||||
"token": "abc123xyz",
|
||||
"uploaded_documents": {
|
||||
"id_copy": true,
|
||||
"matric_certificate": true
|
||||
},
|
||||
"values": {
|
||||
"full_name": "John Doe",
|
||||
"phone": "+27 82 123 4567",
|
||||
"address": "123 Main St"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Response (200 OK)**:
|
||||
```json
|
||||
{
|
||||
"id": 101,
|
||||
"status": "in_progress",
|
||||
"uploaded_documents": {
|
||||
"id_copy": true,
|
||||
"matric_certificate": true
|
||||
},
|
||||
"values": {
|
||||
"full_name": "John Doe",
|
||||
"phone": "+27 82 123 4567",
|
||||
"address": "123 Main St"
|
||||
},
|
||||
"progress": "66%",
|
||||
"message": "Progress saved. Submit when complete."
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### Submit Enrollment
|
||||
**Endpoint**: `POST /api/v1/enrollments/:id/submit`
|
||||
|
||||
**Authentication**: Token-based (ad-hoc)
|
||||
|
||||
**Description**: Final submission of student enrollment.
|
||||
|
||||
**Request Body**:
|
||||
```json
|
||||
{
|
||||
"token": "abc123xyz"
|
||||
}
|
||||
```
|
||||
|
||||
**Response (200 OK)**:
|
||||
```json
|
||||
{
|
||||
"id": 101,
|
||||
"status": "complete",
|
||||
"completed_at": "2026-01-14T15:00:00Z",
|
||||
"message": "Enrollment submitted successfully. You will be notified of next steps."
|
||||
}
|
||||
```
|
||||
|
||||
**Error Response (400 Bad Request)**:
|
||||
```json
|
||||
{
|
||||
"error": "Missing required documents: matric_certificate"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3. Sponsor Portal
|
||||
|
||||
#### Get Sponsor Dashboard
|
||||
**Endpoint**: `GET /api/v1/sponsor/:token/dashboard`
|
||||
|
||||
**Authentication**: Token-based (ad-hoc)
|
||||
|
||||
**Path Parameters**:
|
||||
- `token`: Sponsor token from email
|
||||
|
||||
**Request**:
|
||||
```http
|
||||
GET /api/v1/sponsor/xyz789abc/dashboard
|
||||
```
|
||||
|
||||
**Response (200 OK)**:
|
||||
```json
|
||||
{
|
||||
"cohort": {
|
||||
"id": 1,
|
||||
"name": "2026 Q1 Learnership",
|
||||
"program_type": "learnership",
|
||||
"status": "active"
|
||||
},
|
||||
"stats": {
|
||||
"total_students": 15,
|
||||
"completed": 15,
|
||||
"pending": 0
|
||||
},
|
||||
"documents_ready": true,
|
||||
"can_sign": true,
|
||||
"token_expires_at": "2026-01-21T19:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### Bulk Sign Documents
|
||||
**Endpoint**: `POST /api/v1/sponsor/:token/sign`
|
||||
|
||||
**Authentication**: Token-based (ad-hoc)
|
||||
|
||||
**Description**: Sponsor signs once, applies to all student documents.
|
||||
|
||||
**Request Body**:
|
||||
```json
|
||||
{
|
||||
"signature": "John Smith",
|
||||
"agree_to_terms": true
|
||||
}
|
||||
```
|
||||
|
||||
**Response (200 OK)**:
|
||||
```json
|
||||
{
|
||||
"signed_count": 15,
|
||||
"cohort_id": 1,
|
||||
"status": "sponsor_completed",
|
||||
"message": "All documents signed successfully. TP has been notified."
|
||||
}
|
||||
```
|
||||
|
||||
**Error Response (400 Bad Request)**:
|
||||
```json
|
||||
{
|
||||
"error": "All students must complete before sponsor signing"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 4. Webhooks
|
||||
|
||||
#### Webhook Endpoint
|
||||
**Endpoint**: `POST /api/v1/webhooks`
|
||||
|
||||
**Authentication**: HMAC signature (optional but recommended)
|
||||
|
||||
**Description**: Receives webhook events for workflow state changes.
|
||||
|
||||
**Headers**:
|
||||
```http
|
||||
Content-Type: application/json
|
||||
X-Webhook-Signature: sha256=...
|
||||
```
|
||||
|
||||
**Request Body**:
|
||||
```json
|
||||
{
|
||||
"event": "submission.completed",
|
||||
"timestamp": "2026-01-14T15:00:00Z",
|
||||
"data": {
|
||||
"cohort_id": 1,
|
||||
"enrollment_id": 101,
|
||||
"student_email": "john@example.com"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Event Types**:
|
||||
- `cohort.created` - New cohort created
|
||||
- `cohort.activated` - Cohort moved to active
|
||||
- `enrollment.created` - New student enrollment
|
||||
- `enrollment.completed` - Student submitted
|
||||
- `sponsor.signed` - Sponsor completed signing
|
||||
- `cohort.finalized` - Cohort completed
|
||||
|
||||
**Response (200 OK)**:
|
||||
```json
|
||||
{
|
||||
"status": "received",
|
||||
"event_id": "evt_123456"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔄 Error Handling
|
||||
|
||||
### Standard Error Responses
|
||||
|
||||
**400 Bad Request**:
|
||||
```json
|
||||
{
|
||||
"error": "Invalid request parameters",
|
||||
"details": {
|
||||
"program_type": ["must be one of: learnership, internship, candidacy"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**401 Unauthorized**:
|
||||
```json
|
||||
{
|
||||
"error": "Authentication required",
|
||||
"code": "AUTH_REQUIRED"
|
||||
}
|
||||
```
|
||||
|
||||
**403 Forbidden**:
|
||||
```json
|
||||
{
|
||||
"error": "Insufficient permissions",
|
||||
"code": "PERMISSION_DENIED"
|
||||
}
|
||||
```
|
||||
|
||||
**404 Not Found**:
|
||||
```json
|
||||
{
|
||||
"error": "Resource not found",
|
||||
"code": "RESOURCE_NOT_FOUND"
|
||||
}
|
||||
```
|
||||
|
||||
**422 Unprocessable Entity**:
|
||||
```json
|
||||
{
|
||||
"error": "Validation failed",
|
||||
"errors": {
|
||||
"email": ["must be a valid email"],
|
||||
"name": ["can't be blank"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**500 Internal Server Error**:
|
||||
```json
|
||||
{
|
||||
"error": "Internal server error",
|
||||
"code": "SERVER_ERROR"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📊 Pagination
|
||||
|
||||
All list endpoints support cursor-based pagination:
|
||||
|
||||
**Query Parameters**:
|
||||
- `page`: Page number (default: 1)
|
||||
- `per_page`: Items per page (default: 20, max: 100)
|
||||
|
||||
**Response Structure**:
|
||||
```json
|
||||
{
|
||||
"data": [...],
|
||||
"meta": {
|
||||
"current_page": 1,
|
||||
"total_pages": 5,
|
||||
"total_count": 95,
|
||||
"per_page": 20,
|
||||
"next_page": 2,
|
||||
"prev_page": null
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Usage**:
|
||||
```http
|
||||
GET /api/v1/cohorts?page=2&per_page=10
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Rate Limiting
|
||||
|
||||
**Rate Limit**: 100 requests per minute per API key
|
||||
|
||||
**Headers**:
|
||||
```http
|
||||
X-RateLimit-Limit: 100
|
||||
X-RateLimit-Remaining: 95
|
||||
X-RateLimit-Reset: 1642186800
|
||||
```
|
||||
|
||||
**429 Too Many Requests**:
|
||||
```json
|
||||
{
|
||||
"error": "Rate limit exceeded",
|
||||
"retry_after": 45
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🧪 Testing the API
|
||||
|
||||
### Using cURL
|
||||
|
||||
**Create Cohort**:
|
||||
```bash
|
||||
curl -X POST https://api.flodoc.com/api/v1/cohorts \
|
||||
-H "Authorization: Bearer $TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"name": "Test Cohort",
|
||||
"program_type": "learnership",
|
||||
"sponsor_email": "test@example.com",
|
||||
"template_id": 1
|
||||
}'
|
||||
```
|
||||
|
||||
**Get Cohorts**:
|
||||
```bash
|
||||
curl -X GET "https://api.flodoc.com/api/v1/cohorts?status=active" \
|
||||
-H "Authorization: Bearer $TOKEN"
|
||||
```
|
||||
|
||||
**Student Enrollment**:
|
||||
```bash
|
||||
curl -X PATCH https://api.flodoc.com/api/v1/enrollments/101 \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"token": "abc123xyz",
|
||||
"values": {"full_name": "John Doe"}
|
||||
}'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔒 Security Best Practices
|
||||
|
||||
### 1. Token Security
|
||||
- Tokens expire after 7 days
|
||||
- Use HTTPS in production
|
||||
- Store tokens securely (not in localStorage for web)
|
||||
- Implement token refresh mechanism
|
||||
|
||||
### 2. Input Validation
|
||||
- Always validate on backend
|
||||
- Sanitize all inputs
|
||||
- Use strong parameters
|
||||
- Limit file uploads (size, type)
|
||||
|
||||
### 3. Rate Limiting
|
||||
- Implement per-user rate limits
|
||||
- Track API usage
|
||||
- Block abusive clients
|
||||
|
||||
### 4. Webhook Security
|
||||
- Verify HMAC signatures
|
||||
- Validate event payloads
|
||||
- Implement retry logic with exponential backoff
|
||||
- Log all webhook deliveries
|
||||
|
||||
### 5. CORS
|
||||
- Restrict origins in production
|
||||
- Use specific allowed methods
|
||||
- Implement preflight caching
|
||||
|
||||
---
|
||||
|
||||
## 📚 API Versioning
|
||||
|
||||
### Version Strategy
|
||||
- URL-based: `/api/v1/`
|
||||
- Future versions: `/api/v2/`
|
||||
- Backward compatibility maintained for 6 months
|
||||
- Deprecation headers for old versions
|
||||
|
||||
### Deprecation Headers
|
||||
```http
|
||||
Deprecation: true
|
||||
Sunset: Mon, 31 Dec 2026 23:59:59 GMT
|
||||
Link: </api/v2/cohorts>; rel="successor-version"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔄 Webhook Delivery
|
||||
|
||||
### Delivery Guarantees
|
||||
- At-least-once delivery
|
||||
- Exponential backoff retry (1m, 5m, 15m, 1h, 6h)
|
||||
- Max 5 retries
|
||||
- Dead letter queue for failures
|
||||
|
||||
### Retry Logic
|
||||
```ruby
|
||||
class WebhookDeliveryJob < ApplicationJob
|
||||
retry_on StandardError, wait: :exponentially_longer, attempts: 5
|
||||
|
||||
def perform(event)
|
||||
# Delivery logic
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📋 API Checklist
|
||||
|
||||
- [ ] Authentication required for all endpoints except auth
|
||||
- [ ] Proper HTTP status codes
|
||||
- [ ] Consistent JSON response format
|
||||
- [ ] Error handling with helpful messages
|
||||
- [ ] Pagination for list endpoints
|
||||
- [ ] Rate limiting implemented
|
||||
- [ ] Input validation on all endpoints
|
||||
- [ ] CORS configured
|
||||
- [ ] Webhook signature verification
|
||||
- [ ] API versioning strategy
|
||||
- [ ] Documentation complete
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Next Steps
|
||||
|
||||
1. **Implement API Controllers** - Start with cohorts endpoints
|
||||
2. **Add Authentication** - JWT token system
|
||||
3. **Write Request Specs** - Test all endpoints
|
||||
4. **Create API Documentation** - Auto-generate from specs
|
||||
5. **Test Integration** - Verify with real data
|
||||
|
||||
---
|
||||
|
||||
**Document Status**: ✅ Complete
|
||||
**Ready for**: API Implementation (Story 3.x)
|
||||
@ -1,104 +0,0 @@
|
||||
# Checklist Results Report
|
||||
|
||||
## Brownfield Architecture Validation
|
||||
|
||||
### ✅ **Integration Assessment**
|
||||
- [x] **Existing system analysis completed** - DocuSeal architecture fully understood
|
||||
- [x] **Integration points identified** - 15+ existing components mapped
|
||||
- [x] **Compatibility requirements defined** - API, DB, UI, performance constraints
|
||||
- [x] **Risk assessment performed** - Technical, integration, deployment risks documented
|
||||
|
||||
### ✅ **Technical Compatibility**
|
||||
- [x] **Ruby/Rails version compatibility** - Ruby 3.4.2, Rails 7.x maintained
|
||||
- [x] **Frontend framework compatibility** - Vue 3.3.2, Composition API for new components
|
||||
- [x] **Database compatibility** - Additive schema changes only, no modifications
|
||||
- [x] **External dependencies** - No new gems or npm packages required
|
||||
|
||||
### ✅ **Architecture Patterns**
|
||||
- [x] **Follows existing MVC pattern** - Rails conventions maintained
|
||||
- [x] **Service layer consistency** - New services in `lib/cohorts/` match `lib/submissions/` pattern
|
||||
- [x] **Component architecture** - Vue 3 Composition API matches existing patterns
|
||||
- [x] **API design consistency** - RESTful endpoints follow existing v1 patterns
|
||||
|
||||
### ✅ **Data Model Integration**
|
||||
- [x] **Foreign key relationships** - Links to existing User, Account, Template, Submission
|
||||
- [x] **No schema modifications** - Existing tables unchanged
|
||||
- [x] **Migration strategy** - Additive migrations with rollback capability
|
||||
- [x] **Backward compatibility** - 100% maintained
|
||||
|
||||
### ✅ **Security & Authentication**
|
||||
- [x] **Existing auth reuse** - Devise + JWT unchanged
|
||||
- [x] **Authorization extension** - Cancancan extended for cohort permissions
|
||||
- [x] **Data isolation** - Institution-based multi-tenancy enforced
|
||||
- [x] **Token security** - Sponsor access via secure tokens
|
||||
|
||||
### ✅ **Deployment & Operations**
|
||||
- [x] **Infrastructure compatibility** - No new services required
|
||||
- [x] **Deployment strategy** - Incremental, zero-downtime approach
|
||||
- [x] **Rollback plan** - Code and database rollback procedures defined
|
||||
- [x] **Monitoring integration** - Extends existing logging and metrics
|
||||
|
||||
### ✅ **Testing Strategy**
|
||||
- [x] **Test framework compatibility** - RSpec patterns maintained
|
||||
- [x] **Integration testing** - Existing + new feature verification
|
||||
- [x] **Regression testing** - Full existing test suite requirement
|
||||
- [x] **Coverage targets** - 80% minimum on new code
|
||||
|
||||
## Critical Architectural Decisions
|
||||
|
||||
1. **Technology Stack:** ✅ **No new technologies** - Leverages existing DocuSeal stack entirely
|
||||
2. **API Strategy:** ✅ **Extend v1** - No new API version required
|
||||
3. **Database Strategy:** ✅ **Additive only** - Zero modifications to existing schema
|
||||
4. **UI Approach:** ✅ **Custom design system** - TailwindCSS only (no DaisyUI for portals)
|
||||
5. **Authentication:** ✅ **Reuse existing** - Devise + JWT unchanged
|
||||
6. **Multi-tenancy:** ✅ **Institution model** - Extends existing Account concept
|
||||
|
||||
## Risk Mitigation Summary
|
||||
|
||||
| Risk | Mitigation | Status |
|
||||
|------|------------|--------|
|
||||
| Performance degradation | Pagination, lazy loading, background processing | ✅ Addressed |
|
||||
| State management complexity | Database transactions, optimistic locking | ✅ Addressed |
|
||||
| Integration conflicts | Thorough testing, feature flags | ✅ Addressed |
|
||||
| Authentication conflicts | Reuse existing auth, extend carefully | ✅ Addressed |
|
||||
| Database migration failures | Test on production-like data, rollback plan | ✅ Addressed |
|
||||
|
||||
## Architectural Decision Records (ADRs)
|
||||
|
||||
**ADR-001: Brownfield Enhancement Strategy**
|
||||
- **Decision:** Use additive-only approach with no modifications to existing DocuSeal schema or core logic
|
||||
- **Rationale:** Minimizes risk, enables rollback, maintains 100% backward compatibility
|
||||
- **Alternatives Considered:** Fork DocuSeal, modify core tables, microservices
|
||||
- **Consequences:** ✅ Zero downtime, easy rollback | ⚠️ Careful FK management required
|
||||
|
||||
**ADR-002: Custom UI Design System**
|
||||
- **Decision:** Use custom TailwindCSS design system (not DaisyUI) for new portals
|
||||
- **Rationale:** PRD requirement for custom UI/UX, better brand control, more flexibility
|
||||
- **Alternatives Considered:** Extend DaisyUI, use existing DaisyUI, new component library
|
||||
- **Consequences:** ✅ Tailored user experience | ⚠️ Additional CSS development time
|
||||
|
||||
**ADR-003: Token-Based Sponsor Access**
|
||||
- **Decision:** Use unique tokens (not JWT) for sponsor portal authentication
|
||||
- **Rationale:** Sponsors don't need existing accounts, simple email-based access, no session complexity
|
||||
- **Alternatives Considered:** JWT tokens, magic links, OAuth
|
||||
- **Consequences:** ✅ Simple sponsor onboarding | ⚠️ Token security considerations
|
||||
|
||||
**ADR-004: State Machine Pattern**
|
||||
- **Decision:** Use explicit state machine for cohort and enrollment states
|
||||
- **Rationale:** Complex workflow requires clear state definitions, prevents invalid transitions, provides audit trail
|
||||
- **Alternatives Considered:** Implicit state via flags, simple enum fields, external state engine
|
||||
- **Consequences:** ✅ Clear workflow logic | ⚠️ Additional code complexity
|
||||
|
||||
**ADR-005: Excel Export Technology**
|
||||
- **Decision:** Use rubyXL gem for FR23 Excel export functionality
|
||||
- **Rationale:** Existing gem in Gemfile, mature library, no external dependencies
|
||||
- **Alternatives Considered:** CSV export, Axlsx, external service
|
||||
- **Consequences:** ✅ Simple implementation | ⚠️ Memory usage for large exports
|
||||
|
||||
**ADR-006: Multi-Portal Architecture**
|
||||
- **Decision:** Three separate Vue applications (Admin, Student, Sponsor) with shared components
|
||||
- **Rationale:** Clear separation of concerns, role-specific UX, independent deployment
|
||||
- **Alternatives Considered:** Single SPA with routing, server-side rendering, separate repositories
|
||||
- **Consequences:** ✅ Clean architecture | ⚠️ Some code duplication
|
||||
|
||||
---
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,577 +0,0 @@
|
||||
# Component Architecture
|
||||
|
||||
## New Components
|
||||
|
||||
### **Cohort Management Service Layer**
|
||||
**Responsibility:** Business logic for cohort lifecycle, state transitions, and workflow orchestration
|
||||
|
||||
**Integration Points:**
|
||||
- Uses existing `Submission` and `Template` services for document workflows
|
||||
- Integrates with existing `EmailNotification` system for alerts
|
||||
- Leverages existing `WebhookDelivery` for external integrations
|
||||
|
||||
**Key Interfaces:**
|
||||
- `CohortWorkflowService` - Manages cohort state transitions
|
||||
- `EnrollmentService` - Handles student enrollment and document submission
|
||||
- `VerificationService` - Admin document verification workflow
|
||||
- `SponsorService` - Sponsor signing orchestration
|
||||
|
||||
**Dependencies:**
|
||||
- **Existing Components:** `SubmissionService`, `TemplateService`, `EmailService`, `WebhookService`
|
||||
- **New Components:** `CohortStateEngine`, `EnrollmentValidator`, `SponsorAccessManager`
|
||||
|
||||
**Technology Stack:** Ruby service objects in `lib/cohorts/`, following existing patterns in `lib/submissions/`
|
||||
|
||||
### **Admin Portal Vue Application**
|
||||
**Responsibility:** Cohort creation, management, verification, and analytics interface
|
||||
|
||||
**Integration Points:**
|
||||
- Embeds existing DocuSeal form builder for template creation
|
||||
- Uses existing API endpoints for document operations
|
||||
- Integrates with existing authentication system
|
||||
|
||||
**Key Interfaces:**
|
||||
- `CohortDashboard.vue` - Main admin dashboard
|
||||
- `CohortWizard.vue` - Multi-step cohort creation
|
||||
- `VerificationInterface.vue` - Document review/rejection
|
||||
- `SponsorCoordinator.vue` - Sponsor management
|
||||
- `AnalyticsView.vue` - Reporting and metrics
|
||||
- `ExcelExport.vue` - FR23 data export
|
||||
|
||||
**Dependencies:**
|
||||
- **Existing Components:** `TemplateBuilder` (embedded), `SubmissionPreview` (embedded)
|
||||
- **New Components:** `CohortList`, `EnrollmentTable`, `VerificationModal`
|
||||
|
||||
**Technology Stack:** Vue 3 Composition API, Custom TailwindCSS, Axios for API calls
|
||||
|
||||
### **Student Portal Vue Application**
|
||||
**Responsibility:** Student enrollment, document upload, and agreement completion interface
|
||||
|
||||
**Integration Points:**
|
||||
- Embeds existing DocuSeal signing form components
|
||||
- Uses existing file upload infrastructure
|
||||
- Integrates with existing authentication for student access
|
||||
|
||||
**Key Interfaces:**
|
||||
- `CohortWelcome.vue` - Portal entry and authentication
|
||||
- `DocumentUpload.vue` - File upload interface
|
||||
- `AgreementForm.vue` - Embedded DocuSeal form builder
|
||||
- `StatusDashboard.vue` - Progress tracking
|
||||
- `ResubmissionFlow.vue` - Rejection handling
|
||||
|
||||
**Dependencies:**
|
||||
- **Existing Components:** `SubmissionForm` (embedded), `FileDropzone` (embedded)
|
||||
- **New Components:** `CohortAccess`, `DocumentChecklist`, `ProgressTracker`
|
||||
|
||||
**Technology Stack:** Vue 3 Composition API, Custom TailwindCSS, Existing submission form components
|
||||
|
||||
### **Sponsor Portal Vue Application**
|
||||
**Responsibility:** Multi-student review, bulk signing, and cohort finalization interface
|
||||
|
||||
**Integration Points:**
|
||||
- Embeds existing DocuSeal signature components
|
||||
- Uses existing submission APIs for signing workflows
|
||||
- Integrates with existing authentication for sponsor access
|
||||
|
||||
**Key Interfaces:**
|
||||
- `SponsorDashboard.vue` - Cohort overview and student list
|
||||
- `StudentReview.vue` - Individual student document review
|
||||
- `BulkSigning.vue` - Mass signature operations
|
||||
- `CohortFinalization.vue` - Completion workflow
|
||||
|
||||
**Dependencies:**
|
||||
- **Existing Components:** `SignatureCapture` (embedded), `SubmissionViewer` (embedded)
|
||||
- **New Components:** `StudentProgressList`, `BulkSignControls`
|
||||
|
||||
**Technology Stack:** Vue 3 Composition API, Custom TailwindCSS, Existing signing components
|
||||
|
||||
### **State Management Engine**
|
||||
**Responsibility:** Orchestrate complex workflow states across all three portals
|
||||
|
||||
**Integration Points:**
|
||||
- Hooks into existing submission state machine
|
||||
- Manages cohort-level state transitions
|
||||
- Enforces workflow rules (sponsor access only after student completion)
|
||||
|
||||
**Key Interfaces:**
|
||||
- `CohortStateMachine` - State transition logic
|
||||
- `WorkflowEnforcer` - Business rule validation
|
||||
- `EventLogger` - Audit trail generation
|
||||
|
||||
**Dependencies:**
|
||||
- **Existing Components:** `SubmissionState` (extended)
|
||||
- **New Components:** `CohortStateTransitions`, `EnrollmentValidator`
|
||||
|
||||
**Technology Stack:** Ruby state machine pattern, ActiveRecord callbacks
|
||||
|
||||
## Component Interaction Diagram
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
subgraph "Existing DocuSeal System"
|
||||
Auth[Devise Authentication]
|
||||
API[RESTful API]
|
||||
Sub[Submission Engine]
|
||||
Temp[Template Builder]
|
||||
Store[Active Storage]
|
||||
Email[Email System]
|
||||
Webhook[Webhook Delivery]
|
||||
end
|
||||
|
||||
subgraph "New Cohort Management Layer"
|
||||
AdminPortal[Admin Portal Vue]
|
||||
StudentPortal[Student Portal Vue]
|
||||
SponsorPortal[Sponsor Portal Vue]
|
||||
CohortService[Cohort Management Services]
|
||||
StateEngine[State Management Engine]
|
||||
end
|
||||
|
||||
subgraph "New Data Models"
|
||||
Cohort[Cohort Model]
|
||||
Enrollment[CohortEnrollment Model]
|
||||
Institution[Institution Model]
|
||||
Sponsor[Sponsor Model]
|
||||
Verification[DocumentVerification Model]
|
||||
end
|
||||
|
||||
%% Integration Flows
|
||||
AdminPortal -->|Uses| API
|
||||
AdminPortal -->|Embeds| Temp
|
||||
StudentPortal -->|Uses| API
|
||||
StudentPortal -->|Embeds| Sub
|
||||
SponsorPortal -->|Uses| API
|
||||
SponsorPortal -->|Embeds| Sub
|
||||
|
||||
CohortService -->|Uses| Sub
|
||||
CohortService -->|Uses| Temp
|
||||
CohortService -->|Uses| Email
|
||||
CohortService -->|Uses| Webhook
|
||||
|
||||
StateEngine -->|Extends| Sub
|
||||
StateEngine -->|Manages| Cohort
|
||||
StateEngine -->|Manages| Enrollment
|
||||
|
||||
AdminPortal -->|Calls| CohortService
|
||||
StudentPortal -->|Calls| CohortService
|
||||
SponsorPortal -->|Calls| CohortService
|
||||
|
||||
CohortService -->|Stores| Cohort
|
||||
CohortService -->|Stores| Enrollment
|
||||
CohortService -->|Stores| Institution
|
||||
CohortService -->|Stores| Sponsor
|
||||
CohortService -->|Stores| Verification
|
||||
|
||||
Enrollment -->|Links| Sub
|
||||
Cohort -->|Uses| Temp
|
||||
Verification -->|Links| Sub
|
||||
|
||||
Auth -->|Validates| AdminPortal
|
||||
Auth -->|Validates| StudentPortal
|
||||
Auth -->|Validates| SponsorPortal
|
||||
```
|
||||
|
||||
## Component Props and Events Documentation
|
||||
|
||||
### **Admin Portal Components**
|
||||
|
||||
**CohortWizard.vue**
|
||||
```typescript
|
||||
// Props
|
||||
interface Props {
|
||||
institutionId: number
|
||||
availableTemplates: Template[] // Existing DocuSeal templates
|
||||
programTypes: ['learnership', 'internship', 'candidacy']
|
||||
}
|
||||
|
||||
// Events
|
||||
interface Emits {
|
||||
(e: 'created', cohort: Cohort): void
|
||||
(e: 'cancelled'): void
|
||||
(e: 'error', message: string): void
|
||||
}
|
||||
|
||||
// State
|
||||
interface State {
|
||||
step: 1 | 2 | 3 | 4
|
||||
formData: {
|
||||
name: string
|
||||
programType: string
|
||||
sponsorEmail: string
|
||||
studentCount: number
|
||||
mainTemplateId: number
|
||||
supportingTemplateIds: number[]
|
||||
startDate: string
|
||||
endDate: string
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**VerificationInterface.vue**
|
||||
```typescript
|
||||
// Props
|
||||
interface Props {
|
||||
cohortId: number
|
||||
enrollments: Enrollment[]
|
||||
verificationHistory: Verification[]
|
||||
}
|
||||
|
||||
// Events
|
||||
interface Emits {
|
||||
(e: 'verified', enrollmentId: number, action: 'approved' | 'rejected', reason?: string): void
|
||||
(e: 'bulkVerify', enrollmentIds: number[], action: 'approved' | 'rejected'): void
|
||||
}
|
||||
```
|
||||
|
||||
### **Student Portal Components**
|
||||
|
||||
**DocumentUpload.vue**
|
||||
```typescript
|
||||
// Props
|
||||
interface Props {
|
||||
requiredDocuments: string[] // ['matric', 'id', 'disability', 'qualifications', 'certificates']
|
||||
maxFileSize: number // 10MB
|
||||
allowedFormats: string[] // ['pdf', 'jpg', 'png']
|
||||
}
|
||||
|
||||
// Events
|
||||
interface Emits {
|
||||
(e: 'uploaded', documents: UploadedDocument[]): void
|
||||
(e: 'removed', documentId: number): void
|
||||
(e: 'error', errors: string[]): void
|
||||
}
|
||||
|
||||
// State
|
||||
interface State {
|
||||
uploadedFiles: File[]
|
||||
uploadProgress: Record<number, number>
|
||||
validationErrors: string[]
|
||||
}
|
||||
```
|
||||
|
||||
**AgreementForm.vue**
|
||||
```typescript
|
||||
// Props
|
||||
interface Props {
|
||||
templateId: number
|
||||
submissionId?: number // For existing submission
|
||||
readOnly?: boolean
|
||||
}
|
||||
|
||||
// Events
|
||||
interface Emits {
|
||||
(e: 'completed', submission: Submission): void
|
||||
(e: 'saved', submission: Submission): void
|
||||
(e: 'error', error: string): void
|
||||
}
|
||||
```
|
||||
|
||||
### **Sponsor Portal Components**
|
||||
|
||||
**BulkSigning.vue**
|
||||
```typescript
|
||||
// Props
|
||||
interface Props {
|
||||
cohortId: number
|
||||
studentCount: number
|
||||
completedCount: number
|
||||
signatureRequired: boolean
|
||||
initialsRequired: boolean
|
||||
}
|
||||
|
||||
// Events
|
||||
interface Emits {
|
||||
(e: 'bulkSigned', signatureData: SignatureData): void
|
||||
(e: 'individualSign', studentId: number, signatureData: SignatureData): void
|
||||
(e: 'error', error: string): void
|
||||
}
|
||||
|
||||
// State
|
||||
interface State {
|
||||
signatureCanvas: HTMLCanvasElement | null
|
||||
initialsCanvas: HTMLCanvasElement | null
|
||||
selectedStudents: number[]
|
||||
isDrawing: boolean
|
||||
}
|
||||
```
|
||||
|
||||
### **Shared Components**
|
||||
|
||||
**PortalNavigation.vue**
|
||||
```typescript
|
||||
// Props
|
||||
interface PortalNavigationProps {
|
||||
portal: 'admin' | 'student' | 'sponsor'
|
||||
user?: {
|
||||
name: string
|
||||
email: string
|
||||
role?: string
|
||||
}
|
||||
cohortName?: string
|
||||
}
|
||||
|
||||
// Events
|
||||
interface PortalNavigationEvents {
|
||||
(e: 'navigate', view: string): void
|
||||
(e: 'logout'): void
|
||||
(e: 'switchRole'): void // For admin users with multiple roles
|
||||
}
|
||||
```
|
||||
|
||||
**RoleSwitcher.vue**
|
||||
```typescript
|
||||
// Props
|
||||
interface RoleSwitcherProps {
|
||||
availableRoles: Array<{
|
||||
role: string
|
||||
portal: 'admin' | 'student' | 'sponsor'
|
||||
label: string
|
||||
}>
|
||||
currentRole: string
|
||||
}
|
||||
|
||||
// Events
|
||||
interface RoleSwitcherEvents {
|
||||
(e: 'roleChange', newRole: { role: string, portal: string }): void
|
||||
}
|
||||
```
|
||||
|
||||
**PortalNotifications.vue**
|
||||
```typescript
|
||||
// Props
|
||||
interface PortalNotificationsProps {
|
||||
notifications: Array<{
|
||||
id: number
|
||||
type: 'success' | 'error' | 'warning' | 'info'
|
||||
message: string
|
||||
timestamp: string
|
||||
read: boolean
|
||||
}>
|
||||
autoDismiss?: boolean
|
||||
dismissTime?: number // milliseconds
|
||||
}
|
||||
|
||||
// Events
|
||||
interface PortalNotificationsEvents {
|
||||
(e: 'dismiss', notificationId: number): void
|
||||
(e: 'markRead', notificationId: number): void
|
||||
}
|
||||
```
|
||||
|
||||
## UI Mockups and Wireframes Reference
|
||||
|
||||
### **Admin Portal Wireframes**
|
||||
|
||||
**Dashboard View:**
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ FloDoc Cohort Management - Institution Name │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ [Create Cohort] [Export Data] [Settings] [Logout] │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ Overview Statistics │
|
||||
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
|
||||
│ │ Active │ │ Completed │ │ Total │ │
|
||||
│ │ Cohorts: 5 │ │ Cohorts: 12 │ │ Students: 250│ │
|
||||
│ └─────────────┘ └─────────────┘ └─────────────┘ │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ Recent Cohorts │
|
||||
│ ┌──────────────────────────────────────────────────────┐ │
|
||||
│ │ Q1 2025 Learnership │ Active │ 32/50 Complete │ [View]│
|
||||
│ │ Q4 2024 Internship │ Draft │ 0/20 Complete │ [View]│
|
||||
│ │ Q3 2024 Candidacy │ Complete │ 45/45 Done │ [View]│
|
||||
│ └──────────────────────────────────────────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**Cohort Creation Wizard:**
|
||||
```
|
||||
Step 1: Basic Information
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Cohort Name: [Q1 2025 Learnership_______________] │
|
||||
│ Program Type: [Learnership ▼] [Internship] [Candidacy] │
|
||||
│ Student Count: [50___] Sponsor Email: [sponsor@company.com]│
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
|
||||
Step 2: Templates
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Main Agreement Template: [Select Template ▼] │
|
||||
│ Supporting Templates: │
|
||||
│ [✓] Code of Conduct [✓] Privacy Policy [ ] Other │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
|
||||
Step 3: Timeline
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Start Date: [2025-02-01] End Date: [2025-07-31] │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
|
||||
Step 4: Review & Create
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Summary: │
|
||||
│ • 50 students for Learnership program │
|
||||
│ • Main agreement: Learnership Agreement │
|
||||
│ • Supporting docs: Code of Conduct, Privacy Policy │
|
||||
│ • Timeline: Feb 1 - Jul 31, 2025 │
|
||||
│ │
|
||||
│ [Create Cohort] [Back] [Cancel] │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**Document Verification Interface:**
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Verification Queue - Q1 2025 Learnership │
|
||||
│ [Filter: Pending] [Sort: Date] [Bulk Actions] │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ Student: John Doe (john@example.com) │
|
||||
│ Documents: │
|
||||
│ ┌──────────────────────────────────────────────────────┐ │
|
||||
│ │ Matric Certificate: [Preview] [Approve] [Reject] │ │
|
||||
│ │ ID Document: [Preview] [Approve] [Reject] │ │
|
||||
│ │ Disability Doc: [Preview] [Approve] [Reject] │ │
|
||||
│ └──────────────────────────────────────────────────────┘ │
|
||||
│ Rejection Reason: [____________________________________] │
|
||||
│ [Submit Verification] │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### **Student Portal Wireframes**
|
||||
|
||||
**Welcome Screen:**
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Welcome to Q1 2025 Learnership Program │
|
||||
│ Institution: ABC Training Academy │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ Your Enrollment Process: │
|
||||
│ 1. Upload Required Documents │
|
||||
│ 2. Sign Program Agreement │
|
||||
│ 3. Sign Supporting Documents │
|
||||
│ 4. Wait for Admin Verification │
|
||||
│ 5. Sponsor Review & Signing │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ Required Documents: │
|
||||
│ • Matric Certificate │
|
||||
│ • ID Document │
|
||||
│ • Disability Documentation (if applicable) │
|
||||
│ • Tertiary Qualifications │
|
||||
│ • International Certificates (if applicable) │
|
||||
│ │
|
||||
│ [Start Enrollment] │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**Document Upload:**
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Upload Required Documents │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ Matric Certificate: [Drag files here or click to browse] │
|
||||
│ [Uploaded: matric.pdf ✓] │
|
||||
│ ID Document: [Drag files here or click to browse] │
|
||||
│ [Uploaded: id.pdf ✓] │
|
||||
│ Disability Doc: [Drag files here or click to browse] │
|
||||
│ [No file selected] │
|
||||
│ Qualifications: [Drag files here or click to browse] │
|
||||
│ [Uploaded: degree.pdf ✓] │
|
||||
│ Certificates: [Drag files here or click to browse] │
|
||||
│ [Uploaded: cert.pdf ✓] │
|
||||
│ │
|
||||
│ [Continue to Agreement] [Save Progress] │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**Status Dashboard:**
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Your Enrollment Status: Complete ✓ │
|
||||
│ Last Updated: 2025-01-02 14:30 │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ Progress: ████████████████████░░░░░░░░ 75% │
|
||||
│ │
|
||||
│ Documents: │
|
||||
│ ✓ Matric Certificate - Uploaded & Signed │
|
||||
│ ✓ ID Document - Uploaded & Signed │
|
||||
│ ✓ Disability Doc - Uploaded & Verified │
|
||||
│ ✓ Qualifications - Uploaded & Signed │
|
||||
│ ✓ Certificates - Uploaded & Signed │
|
||||
│ │
|
||||
│ Next Step: Waiting for Sponsor Review │
|
||||
│ Estimated Time: 2-3 business days │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### **Sponsor Portal Wireframes**
|
||||
|
||||
**Cohort Overview:**
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Sponsor Portal - Q1 2025 Learnership │
|
||||
│ Institution: ABC Training Academy │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ Cohort Summary: │
|
||||
│ • Total Students: 50 │
|
||||
│ • Ready for Signing: 50 │
|
||||
│ • Already Signed: 0 │
|
||||
│ • Status: All Complete ✓ │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ Student List │
|
||||
│ ┌──────────────────────────────────────────────────────┐ │
|
||||
│ │ John Doe │ john@example.com │ Ready │ [Review] │ │
|
||||
│ │ Jane Smith │ jane@example.com │ Ready │ [Review] │ │
|
||||
│ │ ... (48 more)│ │ │ │ │
|
||||
│ └──────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ [Bulk Sign All] [Sign Selected] [Export Summary] │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**Individual Student Review:**
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ John Doe - Document Review │
|
||||
│ [← Back to Overview] │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ Student Information: │
|
||||
│ Age: 23 | Race: Black | City: Johannesburg | Gender: Male │
|
||||
│ Disability: None │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ Documents for Review: │
|
||||
│ ┌──────────────────────────────────────────────────────┐ │
|
||||
│ │ Main Agreement: [View PDF] [Sign] │ │
|
||||
│ │ Code of Conduct: [View PDF] [Sign] │ │
|
||||
│ │ Privacy Policy: [View PDF] [Sign] │ │
|
||||
│ └──────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ Your Signature: [Canvas Area - Draw Here] │
|
||||
│ Your Initials: [Canvas Area - Draw Here] │
|
||||
│ │
|
||||
│ [Sign This Student] [Skip for Now] │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**Bulk Signing Interface:**
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Bulk Signing - Q1 2025 Learnership │
|
||||
│ 50 students ready for signing │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ Signature Application: │
|
||||
│ Apply to: [All 50 Students ▼] [Selected Students] │
|
||||
│ │
|
||||
│ Your Signature: │
|
||||
│ ┌──────────────────────────────────────────────────────┐ │
|
||||
│ │ [Canvas Area - Draw Your Signature] │ │
|
||||
│ │ [Clear] [Apply to All] │ │
|
||||
│ └──────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ Your Initials: │
|
||||
│ ┌──────────────────────────────────────────────────────┐ │
|
||||
│ │ [Canvas Area - Draw Your Initials] │ │
|
||||
│ │ [Clear] [Apply to All] │ │
|
||||
│ └──────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ [Sign All Documents] [Cancel] │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
@ -1,134 +0,0 @@
|
||||
# Data Models and Schema Changes
|
||||
|
||||
## New Data Models
|
||||
|
||||
### **Cohort Model**
|
||||
**Purpose:** Represents a training program cohort (learnership, internship, candidacy) managed by an institution. Contains program metadata, templates, and workflow state.
|
||||
|
||||
**Integration:** Links to existing `Account` (institution), `Template` (agreement templates), and manages `CohortEnrollment` records.
|
||||
|
||||
**Key Attributes:**
|
||||
- `name`: string - Cohort identifier (e.g., "Q1 2025 Learnership Program")
|
||||
- `program_type`: enum [learnership, internship, candidacy] - Fixed program types
|
||||
- `institution_id`: bigint - Foreign key to new `Institutions` table
|
||||
- `sponsor_email`: string - Email for sponsor notifications
|
||||
- `student_count`: integer - Expected number of students
|
||||
- `main_template_id`: bigint - Foreign key to existing `Template` (main agreement)
|
||||
- `supporting_templates`: jsonb - Array of supporting document template IDs
|
||||
- `admin_signed_at`: datetime - When admin signed main agreement
|
||||
- `state`: enum [draft, active, completed, cancelled] - Workflow state
|
||||
- `start_date`, `end_date`: datetime - Program timeline
|
||||
|
||||
**Relationships:**
|
||||
- **With Existing:** `Account` (institution), `Template` (agreement templates), `User` (admin creator)
|
||||
- **With New:** `CohortEnrollment` (has_many), `DocumentVerification` (has_many)
|
||||
|
||||
### **CohortEnrollment Model**
|
||||
**Purpose:** Represents a student's enrollment in a cohort, tracking their document submission progress and state through the workflow.
|
||||
|
||||
**Integration:** Links to existing `User` (student), `Submission` (document signing workflows), and manages verification state.
|
||||
|
||||
**Key Attributes:**
|
||||
- `cohort_id`: bigint - Foreign key to Cohort
|
||||
- `user_id`: bigint - Foreign key to existing User (student)
|
||||
- `submission_id`: bigint - Foreign key to existing Submission (main agreement)
|
||||
- `supporting_submission_ids`: jsonb - Array of submission IDs for supporting documents
|
||||
- `state`: enum [waiting, in_progress, complete] - Student workflow state
|
||||
- `document_verification_state`: enum [pending, verified, rejected] - Admin verification state
|
||||
- `rejection_reason`: text - Reason for document rejection
|
||||
- `student_data`: jsonb - Student demographics (age, race, city, gender, disability)
|
||||
- `uploaded_documents`: jsonb - Metadata about uploaded files (matric, ID, etc.)
|
||||
|
||||
**Relationships:**
|
||||
- **With Existing:** `User` (student), `Submission` (main agreement), `Template` (supporting docs)
|
||||
- **With New:** `Cohort` (belongs_to), `DocumentVerification` (has_many)
|
||||
|
||||
### **Institution Model**
|
||||
**Purpose:** Represents a private training institution, providing multi-tenancy for the cohort management system.
|
||||
|
||||
**Integration:** Extends existing `Account` concept but adds institution-specific metadata and relationships.
|
||||
|
||||
**Key Attributes:**
|
||||
- `account_id`: bigint - Foreign key to existing Account (for backward compatibility)
|
||||
- `name`: string - Institution name
|
||||
- `registration_number`: string - Industry registration number
|
||||
- `address`: text - Physical address
|
||||
- `contact_email`: string - Primary contact
|
||||
- `contact_phone`: string - Contact number
|
||||
- `super_admin_id`: bigint - Foreign key to User (institution super admin)
|
||||
- `settings`: jsonb - Institution-specific configurations
|
||||
|
||||
**Relationships:**
|
||||
- **With Existing:** `Account` (has_one), `User` (has_many admins)
|
||||
- **With New:** `Cohort` (has_many), `Sponsor` (has_many)
|
||||
|
||||
### **Sponsor Model**
|
||||
**Purpose:** Represents program sponsors (companies/organizations) who sign agreements for cohorts.
|
||||
|
||||
**Integration:** Independent model for sponsor management, linked to cohorts via email and approval workflow.
|
||||
|
||||
**Key Attributes:**
|
||||
- `company_name`: string - Sponsor organization name
|
||||
- `contact_email`: string - Primary contact email
|
||||
- `contact_name`: string - Contact person name
|
||||
- `contact_phone`: string - Contact number
|
||||
- `tax_number`: string - Tax/registration number
|
||||
- `institution_id`: bigint - Foreign key to Institution
|
||||
- `user_id`: bigint - Foreign key to User (if sponsor creates account)
|
||||
|
||||
**Relationships:**
|
||||
- **With Existing:** `User` (optional account), `Submission` (signing workflows)
|
||||
- **With New:** `Institution` (belongs_to), `Cohort` (referenced via email)
|
||||
|
||||
### **DocumentVerification Model**
|
||||
**Purpose:** Audit trail for admin document verification actions (approvals/rejections).
|
||||
|
||||
**Integration:** Links to `CohortEnrollment` and existing `User` (admin who performed verification).
|
||||
|
||||
**Key Attributes:**
|
||||
- `cohort_enrollment_id`: bigint - Foreign key to enrollment
|
||||
- `admin_id`: bigint - Foreign key to User (admin)
|
||||
- `document_type`: string - Type of document verified
|
||||
- `action`: enum [approved, rejected] - Verification decision
|
||||
- `reason`: text - Rejection reason (if rejected)
|
||||
- `metadata`: jsonb - Additional verification context
|
||||
|
||||
**Relationships:**
|
||||
- **With Existing:** `User` (admin), `Submission` (document reference)
|
||||
- **With New:** `CohortEnrollment` (belongs_to)
|
||||
|
||||
## Schema Integration Strategy
|
||||
|
||||
**Database Changes Required:**
|
||||
|
||||
**New Tables:**
|
||||
```sql
|
||||
cohorts
|
||||
cohort_enrollments
|
||||
institutions
|
||||
sponsors
|
||||
document_verifications
|
||||
```
|
||||
|
||||
**Modified Tables:** None (100% backward compatible)
|
||||
|
||||
**New Indexes:**
|
||||
- `cohorts.account_id` - Institution lookup
|
||||
- `cohort_enrollments.cohort_id, user_id` - Enrollment uniqueness
|
||||
- `cohort_enrollments.state` - Workflow state queries
|
||||
- `institutions.account_id` - Multi-tenancy isolation
|
||||
- `document_verifications.cohort_enrollment_id` - Audit trail queries
|
||||
|
||||
**Migration Strategy:**
|
||||
1. **Phase 1:** Create new tables with foreign keys (no data dependencies)
|
||||
2. **Phase 2:** Add indexes for performance
|
||||
3. **Phase 3:** Backfill any required default data
|
||||
4. **Rollback Plan:** Reverse migration order, preserve existing data
|
||||
|
||||
**Backward Compatibility:**
|
||||
- ✅ Existing tables unchanged
|
||||
- ✅ Existing relationships preserved
|
||||
- ✅ No breaking schema changes
|
||||
- ✅ Additive-only modifications
|
||||
|
||||
---
|
||||
@ -0,0 +1,632 @@
|
||||
# Data Models - FloDoc Architecture
|
||||
|
||||
**Document**: Database Schema & Data Models
|
||||
**Version**: 1.0
|
||||
**Last Updated**: 2026-01-14
|
||||
|
||||
---
|
||||
|
||||
## 📊 Database Overview
|
||||
|
||||
FloDoc extends the existing DocuSeal database schema with three new tables to support the 3-portal cohort management system. All new tables follow Rails conventions and include soft delete functionality.
|
||||
|
||||
---
|
||||
|
||||
## 🎯 New Tables (FloDoc Enhancement)
|
||||
|
||||
### 1. institutions
|
||||
|
||||
**Purpose**: Single training institution record (one per deployment)
|
||||
|
||||
```ruby
|
||||
create_table :institutions do |t|
|
||||
t.string :name, null: false
|
||||
t.string :email, null: false
|
||||
t.string :contact_person
|
||||
t.string :phone
|
||||
t.jsonb :settings, default: {}
|
||||
t.timestamps
|
||||
t.datetime :deleted_at
|
||||
end
|
||||
|
||||
# Indexes
|
||||
add_index :institutions, :name, unique: true
|
||||
add_index :institutions, :email, unique: true
|
||||
```
|
||||
|
||||
**Key Fields**:
|
||||
- `name`: Institution name (e.g., "TechPro Training Academy")
|
||||
- `email`: Official contact email
|
||||
- `contact_person`: Primary contact name
|
||||
- `phone`: Contact phone number
|
||||
- `settings`: JSONB for future configuration (logo, branding, etc.)
|
||||
- `deleted_at`: Soft delete timestamp
|
||||
|
||||
**Design Decisions**:
|
||||
- **Single Record**: Only one institution per deployment (not multi-tenant)
|
||||
- **No Account Link**: Independent of DocuSeal's `accounts` table
|
||||
- **Settings JSONB**: Flexible for future features without migrations
|
||||
|
||||
**Relationships**:
|
||||
```ruby
|
||||
class Institution < ApplicationRecord
|
||||
has_many :cohorts, dependent: :destroy
|
||||
has_many :cohort_enrollments, through: :cohorts
|
||||
|
||||
validates :name, presence: true, uniqueness: true
|
||||
validates :email, presence: true, uniqueness: true
|
||||
end
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2. cohorts
|
||||
|
||||
**Purpose**: Represents a training program cohort (maps to DocuSeal template)
|
||||
|
||||
```ruby
|
||||
create_table :cohorts do |t|
|
||||
t.references :institution, null: false, foreign_key: true
|
||||
t.references :template, null: false # Links to existing templates
|
||||
t.string :name, null: false
|
||||
t.string :program_type, null: false # learnership/internship/candidacy
|
||||
t.string :sponsor_email, null: false
|
||||
t.jsonb :required_student_uploads, default: [] # ['id', 'matric', 'tertiary']
|
||||
t.jsonb :cohort_metadata, default: {} # Additional cohort info
|
||||
t.string :status, default: 'draft' # draft/active/completed
|
||||
t.datetime :tp_signed_at # TP completed signing
|
||||
t.datetime :students_completed_at # All students completed
|
||||
t.datetime :sponsor_completed_at # Sponsor completed
|
||||
t.datetime :finalized_at # TP finalized review
|
||||
t.timestamps
|
||||
t.datetime :deleted_at
|
||||
end
|
||||
|
||||
# Indexes
|
||||
add_index :cohorts, [:institution_id, :status]
|
||||
add_index :cohorts, :template_id
|
||||
add_index :cohorts, :sponsor_email
|
||||
```
|
||||
|
||||
**Key Fields**:
|
||||
- `institution_id`: Foreign key to institutions
|
||||
- `template_id`: Foreign key to existing `templates` table
|
||||
- `name`: Cohort name (e.g., "2026 Q1 Learnership Program")
|
||||
- `program_type`: Type of training program
|
||||
- `learnership`: SETA-funded learnership
|
||||
- `internship`: Workplace internship
|
||||
- `candidacy`: Professional certification candidacy
|
||||
- `sponsor_email`: Email for sponsor notifications
|
||||
- `required_student_uploads`: Array of required documents
|
||||
- Example: `["id_copy", "matric_certificate", "tertiary_transcript"]`
|
||||
- `cohort_metadata`: JSONB for additional data
|
||||
- Example: `{"start_date": "2026-02-01", "duration_months": 12}`
|
||||
- `status`: Workflow state
|
||||
- `draft`: Being configured by TP
|
||||
- `active`: Students can enroll
|
||||
- `completed`: All phases done
|
||||
- `*_at` fields: Audit trail for workflow phases
|
||||
|
||||
**Workflow States**:
|
||||
```
|
||||
draft → active → [students_enroll] → [students_complete] → [tp_verifies] → [sponsor_signs] → [tp_finalizes] → completed
|
||||
```
|
||||
|
||||
**Relationships**:
|
||||
```ruby
|
||||
class Cohort < ApplicationRecord
|
||||
belongs_to :institution
|
||||
belongs_to :template # Existing DocuSeal model
|
||||
|
||||
has_many :cohort_enrollments, dependent: :destroy
|
||||
has_many :submissions, through: :cohort_enrollments
|
||||
|
||||
validates :name, :program_type, :sponsor_email, presence: true
|
||||
validates :status, inclusion: { in: %w[draft active completed] }
|
||||
|
||||
scope :active, -> { where(status: 'active') }
|
||||
scope :completed, -> { where(status: 'completed') }
|
||||
end
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3. cohort_enrollments
|
||||
|
||||
**Purpose**: Links students to cohorts with state tracking
|
||||
|
||||
```ruby
|
||||
create_table :cohort_enrollments do |t|
|
||||
t.references :cohort, null: false, foreign_key: true
|
||||
t.references :submission, null: false # Links to existing submissions
|
||||
t.string :student_email, null: false
|
||||
t.string :student_name
|
||||
t.string :student_surname
|
||||
t.string :student_id
|
||||
t.string :status, default: 'waiting' # waiting/in_progress/complete
|
||||
t.string :role, default: 'student' # student/sponsor
|
||||
t.jsonb :uploaded_documents, default: {} # Track required uploads
|
||||
t.jsonb :values, default: {} # Copy of submitter values
|
||||
t.datetime :completed_at
|
||||
t.timestamps
|
||||
t.datetime :deleted_at
|
||||
end
|
||||
|
||||
# Indexes
|
||||
add_index :cohort_enrollments, [:cohort_id, :status]
|
||||
add_index :cohort_enrollments, [:cohort_id, :student_email], unique: true
|
||||
add_index :cohort_enrollments, [:submission_id], unique: true
|
||||
```
|
||||
|
||||
**Key Fields**:
|
||||
- `cohort_id`: Foreign key to cohorts
|
||||
- `submission_id`: Foreign key to existing `submissions` table
|
||||
- `student_email`: Student's email (unique per cohort)
|
||||
- `student_name`: First name
|
||||
- `student_surname`: Last name
|
||||
- `student_id`: Student ID number (optional)
|
||||
- `status`: Enrollment state
|
||||
- `waiting`: Awaiting student action
|
||||
- `in_progress`: Student is filling forms
|
||||
- `complete`: Student submitted
|
||||
- `role`: Participant role
|
||||
- `student`: Student participant
|
||||
- `sponsor`: Sponsor participant (rare, usually one per cohort)
|
||||
- `uploaded_documents`: JSONB tracking required uploads
|
||||
- Example: `{"id_copy": true, "matric": false}`
|
||||
- `values`: JSONB copy of submitter values for quick access
|
||||
- Avoids joining to `submitters` table for simple queries
|
||||
- `completed_at`: When student finished
|
||||
|
||||
**Unique Constraints**:
|
||||
- One enrollment per student per cohort (`[cohort_id, student_email]`)
|
||||
- One enrollment per submission (`[submission_id]`)
|
||||
|
||||
**Relationships**:
|
||||
```ruby
|
||||
class CohortEnrollment < ApplicationRecord
|
||||
belongs_to :cohort
|
||||
belongs_to :submission # Existing DocuSeal model
|
||||
|
||||
validates :student_email, presence: true
|
||||
validates :status, inclusion: { in: %w[waiting in_progress complete] }
|
||||
validates :role, inclusion: { in: %w[student sponsor] }
|
||||
|
||||
scope :students, -> { where(role: 'student') }
|
||||
scope :sponsors, -> { where(role: 'sponsor') }
|
||||
scope :completed, -> { where(status: 'complete') }
|
||||
end
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🏗️ Existing DocuSeal Tables (Integration Points)
|
||||
|
||||
### templates (Existing)
|
||||
**Used by**: `cohorts.template_id`
|
||||
|
||||
```ruby
|
||||
# Existing schema (simplified)
|
||||
create_table :templates do |t|
|
||||
t.string :name
|
||||
t.string :status
|
||||
t.references :account
|
||||
# ... other fields
|
||||
end
|
||||
```
|
||||
|
||||
**Integration**: Cohorts reference templates for PDF generation
|
||||
|
||||
---
|
||||
|
||||
### submissions (Existing)
|
||||
**Used by**: `cohort_enrollments.submission_id`
|
||||
|
||||
```ruby
|
||||
# Existing schema (simplified)
|
||||
create_table :submissions do |t|
|
||||
t.references :template
|
||||
t.string :status
|
||||
t.jsonb :values
|
||||
# ... other fields
|
||||
end
|
||||
```
|
||||
|
||||
**Integration**: Enrollments track submission progress
|
||||
|
||||
---
|
||||
|
||||
### submitters (Existing)
|
||||
**Used by**: Workflow logic (not directly referenced)
|
||||
|
||||
```ruby
|
||||
# Existing schema (simplified)
|
||||
create_table :submitters do |t|
|
||||
t.references :submission
|
||||
t.string :email
|
||||
t.string :name
|
||||
t.string :status
|
||||
# ... other fields
|
||||
end
|
||||
```
|
||||
|
||||
**Integration**: Used for signing workflow, values copied to `cohort_enrollments.values`
|
||||
|
||||
---
|
||||
|
||||
## 🔗 Relationships Diagram
|
||||
|
||||
```
|
||||
┌─────────────────┐
|
||||
│ institutions │◄────┐
|
||||
│ (1 per dep) │ │
|
||||
└────────┬────────┘ │
|
||||
│ │
|
||||
1│ │
|
||||
│ │
|
||||
▼ │
|
||||
┌─────────────────┐ │
|
||||
│ cohorts │ │
|
||||
│ │ │
|
||||
└────────┬────────┘ │
|
||||
│ │
|
||||
1│ │
|
||||
│ │
|
||||
▼ │
|
||||
┌─────────────────┐ │
|
||||
│cohort_enrollments│ │
|
||||
│ │ │
|
||||
└────────┬────────┘ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
▼ │
|
||||
┌─────────────────┐ │
|
||||
│ submissions │─────┘
|
||||
│ (existing) │
|
||||
└────────┬────────┘
|
||||
│
|
||||
│
|
||||
▼
|
||||
┌─────────────────┐
|
||||
│ submitters │
|
||||
│ (existing) │
|
||||
└─────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎯 State Management
|
||||
|
||||
### Cohort Status Flow
|
||||
|
||||
```ruby
|
||||
class Cohort < ApplicationRecord
|
||||
def advance_to_active!
|
||||
update!(status: 'active')
|
||||
end
|
||||
|
||||
def mark_students_completed!
|
||||
update!(students_completed_at: Time.current)
|
||||
end
|
||||
|
||||
def mark_sponsor_completed!
|
||||
update!(sponsor_completed_at: Time.current)
|
||||
end
|
||||
|
||||
def finalize!
|
||||
update!(status: 'completed', finalized_at: Time.current)
|
||||
end
|
||||
|
||||
def can_be_signed_by_sponsor?
|
||||
students_completed_at.present? && tp_signed_at.present?
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
### Enrollment Status Flow
|
||||
|
||||
```ruby
|
||||
class CohortEnrollment < ApplicationRecord
|
||||
def start!
|
||||
update!(status: 'in_progress')
|
||||
end
|
||||
|
||||
def complete!
|
||||
update!(
|
||||
status: 'complete',
|
||||
completed_at: Time.current,
|
||||
values: submission.values # Copy for quick access
|
||||
)
|
||||
end
|
||||
|
||||
def incomplete_uploads
|
||||
required = cohort.required_student_uploads
|
||||
uploaded = uploaded_documents.keys
|
||||
required - uploaded
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔍 Query Patterns
|
||||
|
||||
### Get all students for a cohort
|
||||
```ruby
|
||||
cohort = Cohort.find(id)
|
||||
students = cohort.cohort_enrollments.students
|
||||
```
|
||||
|
||||
### Get pending enrollments
|
||||
```ruby
|
||||
pending = CohortEnrollment.where(status: ['waiting', 'in_progress'])
|
||||
```
|
||||
|
||||
### Get sponsor dashboard data
|
||||
```ruby
|
||||
cohort = Cohort.find(id)
|
||||
{
|
||||
total_students: cohort.cohort_enrollments.students.count,
|
||||
completed: cohort.cohort_enrollments.completed.count,
|
||||
pending: cohort.cohort_enrollments.where(status: 'waiting').count,
|
||||
documents_ready: cohort.tp_signed_at.present?
|
||||
}
|
||||
```
|
||||
|
||||
### Check if cohort is ready for sponsor
|
||||
```ruby
|
||||
cohort = Cohort.find(id)
|
||||
ready = cohort.students_completed_at.present? &&
|
||||
cohort.tp_signed_at.present? &&
|
||||
cohort.cohort_enrollments.students.any?
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📊 Data Integrity Rules
|
||||
|
||||
### Foreign Keys
|
||||
```ruby
|
||||
# All new tables have foreign keys
|
||||
add_foreign_key :cohorts, :institutions
|
||||
add_foreign_key :cohorts, :templates
|
||||
add_foreign_key :cohort_enrollments, :cohorts
|
||||
add_foreign_key :cohort_enrollments, :submissions
|
||||
```
|
||||
|
||||
### Validations
|
||||
```ruby
|
||||
# Institution
|
||||
validates :name, presence: true, uniqueness: true
|
||||
validates :email, presence: true, uniqueness: true
|
||||
|
||||
# Cohort
|
||||
validates :name, presence: true
|
||||
validates :program_type, inclusion: { in: %w[learnership internship candidacy] }
|
||||
validates :sponsor_email, presence: true
|
||||
validates :status, inclusion: { in: %w[draft active completed] }
|
||||
|
||||
# Enrollment
|
||||
validates :student_email, presence: true
|
||||
validates :status, inclusion: { in: %w[waiting in_progress complete] }
|
||||
validates :role, inclusion: { in: %w[student sponsor] }
|
||||
```
|
||||
|
||||
### Unique Constraints
|
||||
```ruby
|
||||
# One enrollment per student per cohort
|
||||
add_index :cohort_enrollments, [:cohort_id, :student_email], unique: true
|
||||
|
||||
# One enrollment per submission
|
||||
add_index :cohort_enrollments, [:submission_id], unique: true
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🗄️ Migration Strategy
|
||||
|
||||
### Phase 1: Create Tables
|
||||
```ruby
|
||||
class CreateFloDocTables < ActiveRecord::Migration[7.0]
|
||||
def change
|
||||
create_table :institutions do |t|
|
||||
# ... fields
|
||||
end
|
||||
|
||||
create_table :cohorts do |t|
|
||||
# ... fields
|
||||
end
|
||||
|
||||
create_table :cohort_enrollments do |t|
|
||||
# ... fields
|
||||
end
|
||||
|
||||
# Add indexes
|
||||
add_index :cohorts, [:institution_id, :status]
|
||||
# ... more indexes
|
||||
|
||||
# Add foreign keys
|
||||
add_foreign_key :cohorts, :institutions
|
||||
# ... more foreign keys
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
### Phase 2: Add Models
|
||||
```ruby
|
||||
# app/models/institution.rb
|
||||
class Institution < ApplicationRecord
|
||||
has_many :cohorts, dependent: :destroy
|
||||
# ...
|
||||
end
|
||||
|
||||
# app/models/cohort.rb
|
||||
class Cohort < ApplicationRecord
|
||||
belongs_to :institution
|
||||
belongs_to :template
|
||||
has_many :cohort_enrollments, dependent: :destroy
|
||||
# ...
|
||||
end
|
||||
|
||||
# app/models/cohort_enrollment.rb
|
||||
class CohortEnrollment < ApplicationRecord
|
||||
belongs_to :cohort
|
||||
belongs_to :submission
|
||||
# ...
|
||||
end
|
||||
```
|
||||
|
||||
### Rollback
|
||||
```bash
|
||||
# All migrations are reversible
|
||||
bin/rails db:rollback STEP=1
|
||||
# Tables are dropped, data is lost (intentional for MVP)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Performance Considerations
|
||||
|
||||
### Index Strategy
|
||||
```ruby
|
||||
# For cohort queries by status
|
||||
add_index :cohorts, [:institution_id, :status]
|
||||
|
||||
# For enrollment queries by cohort and status
|
||||
add_index :cohort_enrollments, [:cohort_id, :status]
|
||||
|
||||
# For student lookup (unique per cohort)
|
||||
add_index :cohort_enrollments, [:cohort_id, :student_email], unique: true
|
||||
|
||||
# For submission lookup (unique)
|
||||
add_index :cohort_enrollments, [:submission_id], unique: true
|
||||
```
|
||||
|
||||
### Query Optimization
|
||||
```ruby
|
||||
# Eager load associations to avoid N+1
|
||||
Cohort.includes(:institution, :cohort_enrollments).find(id)
|
||||
|
||||
# Use scopes for common queries
|
||||
cohort.cohort_enrollments.completed.count
|
||||
cohort.cohort_enrollments.students.pending
|
||||
```
|
||||
|
||||
### JSONB Usage
|
||||
```ruby
|
||||
# Store flexible data without schema changes
|
||||
cohort.update!(cohort_metadata: {
|
||||
start_date: '2026-02-01',
|
||||
duration_months: 12,
|
||||
funding_source: 'SETA'
|
||||
})
|
||||
|
||||
# Query JSONB fields
|
||||
Cohort.where("cohort_metadata->>'funding_source' = ?", 'SETA')
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔒 Security Considerations
|
||||
|
||||
### Data Isolation
|
||||
```ruby
|
||||
# All queries must filter by institution
|
||||
class Cohort < ApplicationRecord
|
||||
scope :for_institution, ->(institution_id) { where(institution_id: institution_id) }
|
||||
end
|
||||
|
||||
# In controllers
|
||||
@cohort = current_institution.cohorts.find(params[:id])
|
||||
```
|
||||
|
||||
### Email Encryption (Optional)
|
||||
```ruby
|
||||
# If policy requires, encrypt sensitive fields
|
||||
class CohortEnrollment < ApplicationRecord
|
||||
encrypts :student_email
|
||||
encrypts :student_name
|
||||
encrypts :student_surname
|
||||
end
|
||||
```
|
||||
|
||||
### Audit Trail
|
||||
```ruby
|
||||
# All tables have timestamps and soft deletes
|
||||
t.timestamps
|
||||
t.datetime :deleted_at
|
||||
|
||||
# Use paranoia gem or manual soft delete
|
||||
def soft_delete
|
||||
update!(deleted_at: Time.current)
|
||||
end
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📈 Sample Data
|
||||
|
||||
### Institution
|
||||
```ruby
|
||||
Institution.create!(
|
||||
name: "TechPro Training Academy",
|
||||
email: "admin@techpro.co.za",
|
||||
contact_person: "Jane Smith",
|
||||
phone: "+27 11 123 4567",
|
||||
settings: {
|
||||
logo_url: "/images/techpro-logo.png",
|
||||
primary_color: "#1e3a8a"
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
### Cohort
|
||||
```ruby
|
||||
Cohort.create!(
|
||||
institution: institution,
|
||||
template: template, # Existing DocuSeal template
|
||||
name: "2026 Q1 Software Learnership",
|
||||
program_type: "learnership",
|
||||
sponsor_email: "sponsor@company.co.za",
|
||||
required_student_uploads: ["id_copy", "matric_certificate", "cv"],
|
||||
cohort_metadata: {
|
||||
start_date: "2026-02-01",
|
||||
duration_months: 12,
|
||||
stipend_amount: 3500
|
||||
},
|
||||
status: "active"
|
||||
)
|
||||
```
|
||||
|
||||
### Enrollment
|
||||
```ruby
|
||||
CohortEnrollment.create!(
|
||||
cohort: cohort,
|
||||
submission: submission, # Existing DocuSeal submission
|
||||
student_email: "john.doe@example.com",
|
||||
student_name: "John",
|
||||
student_surname: "Doe",
|
||||
student_id: "STU2026001",
|
||||
status: "waiting",
|
||||
role: "student",
|
||||
uploaded_documents: {
|
||||
"id_copy": true,
|
||||
"matric_certificate": false
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Next Steps
|
||||
|
||||
1. **Implement Story 1.1**: Create migrations for these tables
|
||||
2. **Implement Story 1.2**: Create ActiveRecord models
|
||||
3. **Write Tests**: Verify schema and relationships
|
||||
4. **Test Integration**: Ensure existing DocuSeal tables work
|
||||
|
||||
---
|
||||
|
||||
**Document Status**: ✅ Complete
|
||||
**Ready for**: Story 1.1 implementation
|
||||
@ -1,59 +0,0 @@
|
||||
# Enhancement Scope and Integration Strategy
|
||||
|
||||
## Enhancement Overview
|
||||
|
||||
**Enhancement Type:** ✅ **Major Feature Addition** (3-Portal Cohort Management System)
|
||||
|
||||
**Scope:** Transform the single-portal DocuSeal platform into a specialized 3-portal cohort management system for South African private training institutions. The system will manage training cohorts (learnerships, internships, candidacies) through a coordinated workflow involving institution admins, students, and sponsors.
|
||||
|
||||
**Integration Impact:** ✅ **Significant Impact** (substantial existing code changes required)
|
||||
|
||||
## Integration Approach
|
||||
|
||||
**Code Integration Strategy:**
|
||||
- **Additive Approach:** All new functionality will be added as new models, controllers, and components without modifying existing DocuSeal core logic
|
||||
- **Extension Pattern:** Extend existing authentication and authorization to support new role types
|
||||
- **Service Layer:** Create new service objects in `lib/cohorts/` directory for cohort-specific business logic
|
||||
- **Event-Driven:** Leverage existing webhook infrastructure for cohort workflow notifications
|
||||
|
||||
**Database Integration:**
|
||||
- **New Tables:** Create 5 new tables (`cohorts`, `cohort_enrollments`, `institutions`, `sponsors`, `document_verifications`) with foreign keys to existing tables
|
||||
- **No Schema Modifications:** Existing tables remain unchanged, only new relationships added
|
||||
- **Migration Strategy:** Sequential migrations with rollback capability, tested on production-like data
|
||||
- **Data Integrity:** Use database transactions for cohort state transitions
|
||||
|
||||
**API Integration:**
|
||||
- **Endpoint Extension:** New endpoints under `/api/v1/cohorts/*` following existing RESTful patterns
|
||||
- **Authentication Reuse:** Leverage existing Devise + JWT authentication without modification
|
||||
- **Submission Integration:** Use existing submission APIs for document signing workflows
|
||||
- **Versioning:** No new API version needed, endpoints extend v1
|
||||
|
||||
**UI Integration:**
|
||||
- **Portal Architecture:** Three separate Vue-based portals (Admin, Student, Sponsor) with custom TailwindCSS design
|
||||
- **Component Reuse:** Embed existing DocuSeal form builder and signing components within new portal frameworks
|
||||
- **Navigation:** Role-based portal switching via new navigation layer
|
||||
- **Design System:** Custom TailwindCSS (replacing DaisyUI) for portals while maintaining mobile responsiveness
|
||||
|
||||
## Compatibility Requirements
|
||||
|
||||
**Existing API Compatibility:** ✅ **MAINTAINED**
|
||||
- All new endpoints follow existing DocuSeal API patterns
|
||||
- No breaking changes to existing public APIs
|
||||
- Existing authentication mechanisms remain unchanged
|
||||
|
||||
**Database Schema Compatibility:** ✅ **MAINTAINED**
|
||||
- New tables only, no modifications to existing tables
|
||||
- Foreign key relationships to existing tables (users, submissions, templates)
|
||||
- Backward compatibility through additive schema changes
|
||||
|
||||
**UI/UX Consistency:** ✅ **ADAPTED**
|
||||
- **Challenge:** PRD specifies custom UI/UX (not DaisyUI) for portals
|
||||
- **Solution:** Maintain mobile-first responsive principles, consistent interaction patterns, but allow custom design system
|
||||
- **Existing UI:** DocuSeal's existing DaisyUI interface remains unchanged for legacy features
|
||||
|
||||
**Performance Impact:** ✅ **ACCEPTABLE**
|
||||
- **Target:** Not exceed current memory usage by more than 20%
|
||||
- **Mitigation:** Pagination, lazy loading, background processing for large cohorts
|
||||
- **Monitoring:** Extend existing metrics to track cohort-specific performance
|
||||
|
||||
---
|
||||
@ -1,398 +0,0 @@
|
||||
# FloDoc Institution Management - Implementation Summary
|
||||
|
||||
## Overview
|
||||
|
||||
This document summarizes the complete implementation of the FloDoc Institution Management system following Winston's 4-layer security architecture.
|
||||
|
||||
## Phase 1: Database Layer ✅
|
||||
|
||||
### Migrations Created
|
||||
|
||||
1. **20250103000001** - Add institution_id to account_access
|
||||
- Added nullable institution_id column
|
||||
- Foreign key to institutions table
|
||||
- Foundation for data isolation
|
||||
|
||||
2. **20250103000002** - Create institutions table
|
||||
- Core institution model with account isolation
|
||||
- Super admin relationship
|
||||
- Settings JSONB for flexibility
|
||||
|
||||
3. **20250103000003** - Create cohort_admin_invitations table
|
||||
- Secure token storage (SHA-256)
|
||||
- Token preview for debugging
|
||||
- Expiration tracking
|
||||
|
||||
4. **20250103000004** - Update account_access roles
|
||||
- Added cohort_admin and cohort_super_admin roles
|
||||
- Role-based permissions
|
||||
|
||||
5. **20250103000005** - Backfill institution data
|
||||
- Migrates existing data safely
|
||||
- Creates default institutions
|
||||
- Makes institution_id non-nullable
|
||||
|
||||
6. **20250103000006** - Create security_events table
|
||||
- Audit trail for all security events
|
||||
- JSONB details for flexibility
|
||||
|
||||
## Phase 2: Model Layer ✅
|
||||
|
||||
### Core Models
|
||||
|
||||
**Institution Model** (`app/models/institution.rb`)
|
||||
- `scope :for_user(user)` - Critical security scope
|
||||
- `scope :managed_by(user)` - Super admin scope
|
||||
- `accessible_by?(user)` - Security check method
|
||||
- Relationships: cohorts, sponsors, account_accesses
|
||||
|
||||
**User Model Extensions** (`app/models/user.rb`)
|
||||
- `has_many :institutions` - Through account_accesses
|
||||
- `has_many :managed_institutions` - Super admin relationships
|
||||
- `can_access_institution?(institution)` - Security verification
|
||||
- `cohort_super_admin?` / `cohort_admin?` - Role checks
|
||||
|
||||
**AccountAccess Model** (`app/models/account_access.rb`)
|
||||
- `belongs_to :institution` - Critical for isolation
|
||||
- `enum role` - Includes new cohort roles
|
||||
- `validates :user_id, uniqueness: { scope: :institution_id }`
|
||||
- Scopes for efficient querying
|
||||
|
||||
**CohortAdminInvitation Model** (`app/models/cohort_admin_invitation.rb`)
|
||||
- `generate_token` - 512-bit secure tokens
|
||||
- `valid_token?(raw_token)` - Redis single-use enforcement
|
||||
- Rate limiting: max 5 per email
|
||||
- Expiration handling
|
||||
|
||||
**SecurityEvent Model** (`app/models/security_event.rb`)
|
||||
- `log(event_type, user, details)` - Central logging
|
||||
- Alert thresholds
|
||||
- Export capability (CSV)
|
||||
|
||||
## Phase 3: Security Core ✅
|
||||
|
||||
### Token System
|
||||
|
||||
**Cryptographic Security:**
|
||||
- Token generation: `SecureRandom.urlsafe_base64(64)` (512 bits)
|
||||
- Storage: SHA-256 hash only
|
||||
- Preview: First 8 chars + '...'
|
||||
- Single-use: Redis `SET key NX EX 86400`
|
||||
|
||||
**Redis Enforcement:**
|
||||
- Configuration: `config/initializers/redis.rb`
|
||||
- Atomic operations prevent race conditions
|
||||
- Automatic cleanup via `InvitationCleanupJob`
|
||||
|
||||
### Security Event Logging
|
||||
|
||||
**Event Types:**
|
||||
1. `unauthorized_institution_access` - Cross-institution attempts
|
||||
2. `insufficient_privileges` - Role violations
|
||||
3. `token_validation_failure` - Invalid token attempts
|
||||
4. `rate_limit_exceeded` - Too many invitations
|
||||
5. `invitation_accepted` - Successful acceptance
|
||||
6. `super_admin_demoted` - Role changes
|
||||
|
||||
**Alert Thresholds:**
|
||||
- >5 unauthorized/hour → Security alert
|
||||
- >20 token failures/hour → Potential attack
|
||||
- Any super_admin demotion → Immediate notification
|
||||
|
||||
## Phase 4: Controllers & Services ✅
|
||||
|
||||
### API Controllers
|
||||
|
||||
**InstitutionsController** (`api/v1/institutions_controller.rb`)
|
||||
- Layer 1: `Institution.for_user(current_user)`
|
||||
- Layer 2: CanCanCan abilities
|
||||
- Layer 3: `verify_institution_access` before_action
|
||||
- Layer 4: Strong parameters
|
||||
|
||||
**InvitationsController** (`api/v1/admin/invitations_controller.rb`)
|
||||
- Rate limiting: max 5 per email
|
||||
- Service-based business logic
|
||||
- Security event logging
|
||||
|
||||
**InvitationAcceptanceController** (`api/v1/admin/invitation_acceptance_controller.rb`)
|
||||
- Token validation with Redis
|
||||
- Email verification
|
||||
- Atomic AccountAccess creation
|
||||
|
||||
**SecurityEventsController** (`api/v1/admin/security_events_controller.rb`)
|
||||
- Export capability
|
||||
- Alert monitoring
|
||||
- Filtering and pagination
|
||||
|
||||
### Services
|
||||
|
||||
**InvitationService** (`app/services/invitation_service.rb`)
|
||||
- `create_invitation` - With rate limiting
|
||||
- `accept_invitation` - Redis single-use enforcement
|
||||
- `revoke_invitation` - Mark as used
|
||||
- `cleanup_expired` - Daily maintenance
|
||||
|
||||
### Jobs
|
||||
|
||||
**CohortAdminInvitationJob** - Async email delivery
|
||||
**SecurityAlertJob** - Critical security alerts
|
||||
**InvitationCleanupJob** - Daily cleanup
|
||||
|
||||
### Mailers
|
||||
|
||||
**CohortMailer** - Secure invitation emails
|
||||
- Never logs raw tokens
|
||||
- HTTPS URLs only
|
||||
- Token in email body, not URL params
|
||||
|
||||
## Phase 5: Frontend Components ✅
|
||||
|
||||
### Vue Components
|
||||
|
||||
**InstitutionWizard.vue**
|
||||
- Create/edit institution forms
|
||||
- Validation and error handling
|
||||
- Success feedback
|
||||
|
||||
**AdminInviteModal.vue**
|
||||
- Role selection with explanations
|
||||
- Rate limit warnings
|
||||
- Form validation
|
||||
|
||||
**InstitutionList.vue**
|
||||
- Institution cards with role badges
|
||||
- Loading and error states
|
||||
- Empty state handling
|
||||
|
||||
### API Client
|
||||
|
||||
**institutionClient.js**
|
||||
- All API methods
|
||||
- Error handling
|
||||
- Auth token management
|
||||
- Security event monitoring
|
||||
|
||||
## Phase 6: Routes ✅
|
||||
|
||||
### API Routes
|
||||
|
||||
```
|
||||
/api/v1/institutions
|
||||
GET / - List institutions
|
||||
GET /:id - Show institution
|
||||
POST / - Create institution
|
||||
PATCH /:id - Update institution
|
||||
DELETE /:id - Delete institution
|
||||
|
||||
/api/v1/admin/invitations
|
||||
GET / - List invitations
|
||||
POST / - Create invitation
|
||||
DELETE /:id - Revoke invitation
|
||||
|
||||
/api/v1/admin/invitation_acceptance
|
||||
POST / - Accept invitation
|
||||
GET /validate - Validate token
|
||||
|
||||
/api/v1/admin/security_events
|
||||
GET / - List events
|
||||
GET /export - Export CSV
|
||||
GET /alerts - Get alerts
|
||||
```
|
||||
|
||||
### Web Routes
|
||||
|
||||
```
|
||||
/cohorts/admin
|
||||
GET / - Dashboard
|
||||
GET /new - New institution form
|
||||
POST / - Create institution
|
||||
GET /:id - Institution details
|
||||
GET /:id/edit - Edit form
|
||||
PATCH /:id - Update institution
|
||||
GET /:id/invite - Invite form
|
||||
POST /:id/send_invitation - Send invitation
|
||||
```
|
||||
|
||||
## Security Architecture Summary
|
||||
|
||||
### 4-Layer Defense
|
||||
|
||||
**Layer 1: Database**
|
||||
- Foreign keys and constraints
|
||||
- Unique indexes
|
||||
- Non-nullable relationships
|
||||
- Scoped queries (`Institution.for_user`)
|
||||
|
||||
**Layer 2: Model**
|
||||
- Validations
|
||||
- Security methods (`can_access_institution?`)
|
||||
- Role enums
|
||||
- Association security
|
||||
|
||||
**Layer 3: Controller**
|
||||
- `verify_institution_access` before_action
|
||||
- CanCanCan authorization
|
||||
- Strong parameters
|
||||
- Security event logging
|
||||
|
||||
**Layer 4: UI**
|
||||
- Vue route guards
|
||||
- API client pre-validation
|
||||
- Role-based UI rendering
|
||||
- Context management
|
||||
|
||||
### Token Security
|
||||
|
||||
```
|
||||
Generation → Hash Storage → Redis Single-Use → Validation → Access Grant
|
||||
↓ ↓ ↓ ↓ ↓
|
||||
512-bit SHA-256 only Atomic SET NX Email match AccountAccess
|
||||
```
|
||||
|
||||
### Integration Compatibility
|
||||
|
||||
✅ **Existing DocuSeal systems preserved:**
|
||||
- Authentication (Devise + JWT)
|
||||
- Authorization (CanCanCan extended)
|
||||
- Account-level isolation
|
||||
- Template/submission workflows
|
||||
|
||||
✅ **Additive changes only:**
|
||||
- New models (institutions, invitations, security_events)
|
||||
- Extended User and AccountAccess
|
||||
- Additional API endpoints
|
||||
- New Vue components
|
||||
|
||||
## Testing Requirements
|
||||
|
||||
### Unit Tests (Phase 4)
|
||||
- Model scopes and validations
|
||||
- Token generation and validation
|
||||
- Rate limiting
|
||||
- Security event logging
|
||||
|
||||
### Request Tests (Phase 4)
|
||||
- Cross-institution access attempts
|
||||
- Role-based authorization
|
||||
- Token security scenarios
|
||||
- Rate limit enforcement
|
||||
|
||||
### Integration Tests (Phase 4)
|
||||
- Complete invitation flow
|
||||
- Concurrent access handling
|
||||
- Migration rollback scenarios
|
||||
|
||||
### Security Audit (Phase 4)
|
||||
- Penetration testing
|
||||
- Token analysis
|
||||
- Redis security verification
|
||||
- OWASP compliance check
|
||||
|
||||
## Deployment Checklist
|
||||
|
||||
### Pre-Production
|
||||
- [ ] Run migrations on staging
|
||||
- [ ] Test rollback procedure
|
||||
- [ ] Security audit review
|
||||
- [ ] Performance benchmarking
|
||||
- [ ] Redis infrastructure setup
|
||||
|
||||
### Production Monitoring
|
||||
- [ ] Security event dashboard
|
||||
- [ ] Alert system integration
|
||||
- [ ] Performance monitoring
|
||||
- [ ] Error tracking
|
||||
|
||||
### Rollback Plan
|
||||
- [ ] Database backup
|
||||
- [ ] Step-by-step rollback procedure
|
||||
- [ ] Emergency contact list
|
||||
- [ ] Incident response plan
|
||||
|
||||
## Files Created
|
||||
|
||||
### Database
|
||||
- 6 migration files
|
||||
- Rollback strategy document
|
||||
|
||||
### Models (5 files)
|
||||
- `app/models/institution.rb`
|
||||
- `app/models/cohort_admin_invitation.rb`
|
||||
- `app/models/security_event.rb`
|
||||
- `app/models/account_access.rb` (updated)
|
||||
- `app/models/user.rb` (updated)
|
||||
|
||||
### Services (1 file)
|
||||
- `app/services/invitation_service.rb`
|
||||
|
||||
### Controllers (4 files)
|
||||
- `api/v1/institutions_controller.rb`
|
||||
- `api/v1/admin/invitations_controller.rb`
|
||||
- `api/v1/admin/invitation_acceptance_controller.rb`
|
||||
- `api/v1/admin/security_events_controller.rb`
|
||||
- `cohorts/admin_controller.rb` (web)
|
||||
|
||||
### Jobs (3 files)
|
||||
- `app/jobs/cohort_admin_invitation_job.rb`
|
||||
- `app/jobs/security_alert_job.rb`
|
||||
- `app/jobs/invitation_cleanup_job.rb`
|
||||
|
||||
### Mailers (1 file)
|
||||
- `app/mailers/cohort_mailer.rb`
|
||||
|
||||
### Email Templates (2 files)
|
||||
- `app/views/cohort_mailer/admin_invitation.html.erb`
|
||||
- `app/views/cohort_mailer/admin_invitation.text.erb`
|
||||
|
||||
### Frontend (4 files)
|
||||
- `app/javascript/api/institutionClient.js`
|
||||
- `app/javascript/cohorts/admin/InstitutionWizard.vue`
|
||||
- `app/javascript/cohorts/admin/AdminInviteModal.vue`
|
||||
- `app/javascript/cohorts/admin/InstitutionList.vue`
|
||||
|
||||
### Configuration (1 file)
|
||||
- `config/initializers/redis.rb`
|
||||
|
||||
### Routes (1 file - updated)
|
||||
- `config/routes.rb`
|
||||
|
||||
### Documentation (2 files)
|
||||
- `docs/architecture/rollback-strategy.md`
|
||||
- `docs/architecture/implementation-summary.md`
|
||||
|
||||
## Next Steps
|
||||
|
||||
### Immediate (When Ruby is installed)
|
||||
1. Run `bundle install`
|
||||
2. Run `bin/rails db:migrate`
|
||||
3. Start Redis: `sudo systemctl start redis-server`
|
||||
4. Test in console: `bin/rails console`
|
||||
5. Run development server: `bin/rails server`
|
||||
|
||||
### Phase 4 Testing
|
||||
1. Create comprehensive test suite
|
||||
2. Run security penetration tests
|
||||
3. Performance benchmarking
|
||||
4. Migration rollback testing
|
||||
|
||||
### Production Readiness
|
||||
1. Security audit review
|
||||
2. Team training on 4-layer architecture
|
||||
3. Monitoring setup
|
||||
4. Incident response procedures
|
||||
|
||||
## Success Criteria
|
||||
|
||||
✅ **All Winston's requirements met:**
|
||||
- 4-layer security architecture implemented
|
||||
- Cryptographic token system with Redis
|
||||
- Comprehensive security event logging
|
||||
- Zero impact on existing DocuSeal features
|
||||
- Additive database changes only
|
||||
- Complete audit trail
|
||||
|
||||
✅ **Ready for Phase 4 testing and production deployment**
|
||||
|
||||
---
|
||||
|
||||
**Implementation Status:** ✅ **COMPLETE** (pending Ruby environment setup and Phase 4 testing)
|
||||
@ -1,107 +1,307 @@
|
||||
# FloDoc Brownfield Enhancement Architecture
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [FloDoc Brownfield Enhancement Architecture](#table-of-contents)
|
||||
- [Table of Contents](./table-of-contents.md)
|
||||
- [Introduction](./introduction.md)
|
||||
- [Introduction Content](./introduction.md#introduction-content)
|
||||
- [Existing Project Analysis](./introduction.md#existing-project-analysis)
|
||||
- [Current Project State](./introduction.md#current-project-state)
|
||||
- [Available Documentation](./introduction.md#available-documentation)
|
||||
- [Identified Constraints](./introduction.md#identified-constraints)
|
||||
- [Change Log](./introduction.md#change-log)
|
||||
- [Enhancement Scope and Integration Strategy](./enhancement-scope-and-integration-strategy.md)
|
||||
- [Enhancement Overview](./enhancement-scope-and-integration-strategy.md#enhancement-overview)
|
||||
- [Integration Approach](./enhancement-scope-and-integration-strategy.md#integration-approach)
|
||||
- [Compatibility Requirements](./enhancement-scope-and-integration-strategy.md#compatibility-requirements)
|
||||
# FloDoc Architecture Documentation
|
||||
|
||||
**Project**: FloDoc v3 - 3-Portal Cohort Management System
|
||||
**Version**: 1.0
|
||||
**Last Updated**: 2026-01-14
|
||||
**Status**: Complete
|
||||
|
||||
---
|
||||
|
||||
## 📚 Overview
|
||||
|
||||
This architecture documentation provides comprehensive technical guidance for the FloDoc enhancement project. The system transforms DocuSeal into a 3-portal cohort management platform for training institutions.
|
||||
|
||||
**System Architecture**: Brownfield Enhancement
|
||||
**Primary Goal**: Local Docker MVP for management demonstration
|
||||
**Deployment Strategy**: Option A - Local Docker Only (no production infrastructure)
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Architecture Principles
|
||||
|
||||
1. **Brownfield First**: Enhance existing DocuSeal without breaking functionality
|
||||
2. **Single Institution**: One institution per deployment (not multi-tenant)
|
||||
3. **Ad-hoc Access**: Students/sponsors don't need accounts
|
||||
4. **Security by Design**: POPIA compliance, token-based auth, audit trails
|
||||
5. **Performance**: <20% degradation from baseline
|
||||
6. **Developer Experience**: Clear patterns, comprehensive testing
|
||||
|
||||
---
|
||||
|
||||
## 📖 Documentation Structure
|
||||
|
||||
### Core Architecture (Start Here)
|
||||
1. **[Tech Stack](./tech-stack.md)** - Complete technology specifications
|
||||
2. **[Data Models](./data-models.md)** - Database schema and relationships
|
||||
3. **[Project Structure](./project-structure.md)** - File organization and conventions
|
||||
4. **[Source Tree](./source-tree.md)** - Complete file tree with explanations
|
||||
|
||||
### Implementation Guides
|
||||
5. **[Coding Standards](./coding-standards.md)** - Ruby, Vue, and testing conventions
|
||||
6. **[API Design](./api-design.md)** - RESTful API specifications and patterns
|
||||
7. **[Component Architecture](./component-architecture.md)** - Vue 3 component patterns
|
||||
8. **[State Management](./state-management.md)** - Pinia store architecture
|
||||
|
||||
### Security & Quality
|
||||
9. **[Security Architecture](./security.md)** - Authentication, authorization, data protection
|
||||
10. **[Testing Strategy](./testing-strategy.md)** - RSpec, Vue Test Utils, E2E testing
|
||||
11. **[Integration Patterns](./integration.md)** - 3-portal workflow integration
|
||||
|
||||
### Operations
|
||||
12. **[Infrastructure](./infrastructure.md)** - Docker Compose setup
|
||||
13. **[Deployment](./deployment.md)** - Local deployment procedures
|
||||
14. **[Rollback Strategy](./rollback.md)** - Safety procedures
|
||||
|
||||
---
|
||||
|
||||
## 🏗️ System Overview
|
||||
|
||||
### Three-Portal Architecture
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ TP Portal (Admin) │
|
||||
│ - Cohort Management │
|
||||
│ - Template Management │
|
||||
│ - Student Verification │
|
||||
│ - Sponsor Coordination │
|
||||
│ - Final Review & Export │
|
||||
└──────────────────────┬──────────────────────────────────────┘
|
||||
│
|
||||
┌─────────────┼─────────────┐
|
||||
│ │ │
|
||||
▼ ▼ ▼
|
||||
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
|
||||
│ Student │ │ Sponsor │ │ DocuSeal │
|
||||
│ Portal │ │ Portal │ │ (Core) │
|
||||
│ │ │ │ │ │
|
||||
│ - Upload │ │ - Bulk Sign │ │ - Templates │
|
||||
│ - Fill Forms │ │ - Progress │ │ - Submissions│
|
||||
│ - Submit │ │ - Download │ │ - Signing │
|
||||
└──────────────┘ └──────────────┘ └──────────────┘
|
||||
```
|
||||
|
||||
### Authentication Flow
|
||||
|
||||
1. **TP Portal**: Devise authentication (email/password + 2FA)
|
||||
2. **Student Portal**: Ad-hoc token-based access (no account creation)
|
||||
3. **Sponsor Portal**: Single email notification with token link
|
||||
|
||||
### Data Flow
|
||||
|
||||
```
|
||||
TP Creates Cohort
|
||||
↓
|
||||
Generates Template (DocuSeal)
|
||||
↓
|
||||
Students Receive Token Links
|
||||
↓
|
||||
Students Upload & Submit
|
||||
↓
|
||||
TP Verifies Submissions
|
||||
↓
|
||||
Sponsor Receives Bulk Signing Link
|
||||
↓
|
||||
Sponsor Signs Once (Auto-fills all)
|
||||
↓
|
||||
TP Finalizes & Exports
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔑 Key Design Decisions
|
||||
|
||||
### 1. Single Institution Model
|
||||
- **Rationale**: Training institutions operate independently
|
||||
- **Implementation**: One `institutions` table record per deployment
|
||||
- **Benefit**: Simplified access control, no multi-tenant complexity
|
||||
|
||||
### 2. Template-Cohort Mapping
|
||||
- **Rationale**: Leverage existing DocuSeal template infrastructure
|
||||
- **Implementation**: `cohorts.template_id` → `templates.id`
|
||||
- **Benefit**: Reuse existing PDF generation and signing logic
|
||||
|
||||
### 3. Submission-Cohort Enrollment Mapping
|
||||
- **Rationale**: Track student progress while reusing DocuSeal workflows
|
||||
- **Implementation**: `cohort_enrollments.submission_id` → `submissions.id`
|
||||
- **Benefit**: Existing notification and reminder system works
|
||||
|
||||
### 4. Ad-hoc Token Authentication
|
||||
- **Rationale**: Students/sponsors shouldn't need to create accounts
|
||||
- **Implementation**: JWT tokens with expiration, sent via email
|
||||
- **Benefit**: Lower friction, faster adoption
|
||||
|
||||
### 5. Single Email Rule for Sponsors
|
||||
- **Rationale**: Sponsors sign once for entire cohort
|
||||
- **Implementation**: Bulk signing interface with auto-fill
|
||||
- **Benefit**: Massive efficiency gain for sponsors
|
||||
|
||||
---
|
||||
|
||||
## 📊 Technology Stack Summary
|
||||
|
||||
| Layer | Technology | Version | Purpose |
|
||||
|-------|------------|---------|---------|
|
||||
| **Backend** | Ruby on Rails | 7.x | Core application logic |
|
||||
| **Database** | PostgreSQL | 14+ | Primary data store |
|
||||
| **Background Jobs** | Sidekiq | Latest | Async processing |
|
||||
| **Authentication** | Devise | 4.x | User auth + 2FA |
|
||||
| **Authorization** | Cancancan | 3.x | Role-based access |
|
||||
| **PDF Processing** | HexaPDF | 0.15+ | Generation & signing |
|
||||
| **Frontend** | Vue.js | 3.x | Portal interfaces |
|
||||
| **State Management** | Pinia | 2.x | Client-side state |
|
||||
| **Styling** | TailwindCSS | 3.4.17 | Design system |
|
||||
| **Build Tool** | Shakapacker | 8.x | Webpack wrapper |
|
||||
| **Container** | Docker Compose | Latest | Local development |
|
||||
|
||||
---
|
||||
|
||||
## 📊 Project Metrics
|
||||
|
||||
### Stories
|
||||
- **Total**: 32 stories
|
||||
- **In Scope**: 24 stories (Phases 1-7 + Stories 8.0, 8.0.1, 8.5)
|
||||
- **Deferred**: 8 stories (production infrastructure)
|
||||
- **Completed**: 0 (ready to start)
|
||||
|
||||
### Files Created
|
||||
- **Architecture Docs**: 14 files
|
||||
- **PRD Files**: 7 files (sharded)
|
||||
- **PO Documentation**: 3 files
|
||||
- **Total**: 24+ files
|
||||
|
||||
### Documentation Size
|
||||
- **Total**: ~100KB
|
||||
- **Architecture**: ~60KB
|
||||
- **PRD**: ~30KB
|
||||
- **PO Docs**: ~10KB
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Quick Reference
|
||||
|
||||
### Start Development
|
||||
```bash
|
||||
docker-compose up -d
|
||||
docker-compose exec app bundle exec rails db:setup
|
||||
```
|
||||
|
||||
### Run Tests
|
||||
```bash
|
||||
# Ruby
|
||||
docker-compose exec app bundle exec rspec
|
||||
|
||||
# JavaScript
|
||||
docker-compose exec app yarn test
|
||||
```
|
||||
|
||||
### View Documentation
|
||||
- **PRD**: `docs/prd.md` or `docs/prd/index.md`
|
||||
- **Architecture**: `docs/architecture/index.md` (this file)
|
||||
- **Stories**: `docs/prd/6-epic-details.md`
|
||||
|
||||
---
|
||||
|
||||
## 📖 Reading Path
|
||||
|
||||
### For Developers
|
||||
1. **Start**: `docs/architecture/tech-stack.md`
|
||||
2. **Learn**: `docs/architecture/data-models.md`
|
||||
3. **Code**: `docs/architecture/coding-standards.md`
|
||||
4. **Test**: `docs/architecture/testing-strategy.md`
|
||||
5. **Deploy**: `docs/architecture/infrastructure.md`
|
||||
|
||||
### For Architects
|
||||
1. **Overview**: `docs/architecture/index.md` (this file)
|
||||
2. **Models**: `docs/architecture/data-models.md`
|
||||
3. **API**: `docs/architecture/api-design.md`
|
||||
4. **Security**: `docs/architecture/security.md`
|
||||
5. **Structure**: `docs/architecture/project-structure.md`
|
||||
|
||||
### For Product Managers
|
||||
1. **PRD**: `docs/prd.md`
|
||||
2. **Stories**: `docs/prd/6-epic-details.md`
|
||||
3. **PO Report**: `docs/PO_Master_Validation_Report.md`
|
||||
4. **Plan**: `docs/po/plan-to-address-po-findings.md`
|
||||
|
||||
---
|
||||
|
||||
## 🔗 Quick Links
|
||||
|
||||
### Core Documents
|
||||
- [Tech Stack](./tech-stack.md)
|
||||
- [Existing Technology Stack](./tech-stack.md#existing-technology-stack)
|
||||
- [New Technology Additions](./tech-stack.md#new-technology-additions)
|
||||
- [Data Models and Schema Changes](./data-models-and-schema-changes.md)
|
||||
- [New Data Models](./data-models-and-schema-changes.md#new-data-models)
|
||||
- [Cohort Model](./data-models-and-schema-changes.md#cohort-model)
|
||||
- [CohortEnrollment Model](./data-models-and-schema-changes.md#cohortenrollment-model)
|
||||
- [Institution Model](./data-models-and-schema-changes.md#institution-model)
|
||||
- [Sponsor Model](./data-models-and-schema-changes.md#sponsor-model)
|
||||
- [DocumentVerification Model](./data-models-and-schema-changes.md#documentverification-model)
|
||||
- [Schema Integration Strategy](./data-models-and-schema-changes.md#schema-integration-strategy)
|
||||
- [Component Architecture](./component-architecture.md)
|
||||
- [New Components](./component-architecture.md#new-components)
|
||||
- [Cohort Management Service Layer](./component-architecture.md#cohort-management-service-layer)
|
||||
- [Admin Portal Vue Application](./component-architecture.md#admin-portal-vue-application)
|
||||
- [Student Portal Vue Application](./component-architecture.md#student-portal-vue-application)
|
||||
- [Sponsor Portal Vue Application](./component-architecture.md#sponsor-portal-vue-application)
|
||||
- [State Management Engine](./component-architecture.md#state-management-engine)
|
||||
- [Component Interaction Diagram](./component-architecture.md#component-interaction-diagram)
|
||||
- [Component Props and Events Documentation](./component-architecture.md#component-props-and-events-documentation)
|
||||
- [Admin Portal Components](./component-architecture.md#admin-portal-components)
|
||||
- [Student Portal Components](./component-architecture.md#student-portal-components)
|
||||
- [Sponsor Portal Components](./component-architecture.md#sponsor-portal-components)
|
||||
- [Shared Components](./component-architecture.md#shared-components)
|
||||
- [UI Mockups and Wireframes Reference](./component-architecture.md#ui-mockups-and-wireframes-reference)
|
||||
- [Admin Portal Wireframes](./component-architecture.md#admin-portal-wireframes)
|
||||
- [Student Portal Wireframes](./component-architecture.md#student-portal-wireframes)
|
||||
- [Sponsor Portal Wireframes](./component-architecture.md#sponsor-portal-wireframes)
|
||||
- [API Design and Integration](./api-design-and-integration.md)
|
||||
- [API Integration Strategy](./api-design-and-integration.md#api-integration-strategy)
|
||||
- [New API Endpoints](./api-design-and-integration.md#new-api-endpoints)
|
||||
- [Cohort Management Endpoints](./api-design-and-integration.md#cohort-management-endpoints)
|
||||
- [Create Cohort](./api-design-and-integration.md#create-cohort)
|
||||
- [List Cohorts](./api-design-and-integration.md#list-cohorts)
|
||||
- [Get Cohort Details](./api-design-and-integration.md#get-cohort-details)
|
||||
- [Invite Students](./api-design-and-integration.md#invite-students)
|
||||
- [Export Cohort Data (FR23)](./api-design-and-integration.md#export-cohort-data-fr23)
|
||||
- [Web Portal Routes](./api-design-and-integration.md#web-portal-routes)
|
||||
- [Admin Portal Routes](./api-design-and-integration.md#admin-portal-routes)
|
||||
- [Student Portal Routes](./api-design-and-integration.md#student-portal-routes)
|
||||
- [Sponsor Portal Routes](./api-design-and-integration.md#sponsor-portal-routes)
|
||||
- [Enrollment Management Endpoints](./api-design-and-integration.md#enrollment-management-endpoints)
|
||||
- [List Enrollments](./api-design-and-integration.md#list-enrollments)
|
||||
- [Verify Document](./api-design-and-integration.md#verify-document)
|
||||
- [Sponsor Endpoints](./api-design-and-integration.md#sponsor-endpoints)
|
||||
- [Get Sponsor Cohort Overview](./api-design-and-integration.md#get-sponsor-cohort-overview)
|
||||
- [Bulk Sign](./api-design-and-integration.md#bulk-sign)
|
||||
- [Complete API Response Schemas](./api-design-and-integration.md#complete-api-response-schemas)
|
||||
- [Cohort Endpoints](./api-design-and-integration.md#cohort-endpoints)
|
||||
- [Enrollment Endpoints](./api-design-and-integration.md#enrollment-endpoints)
|
||||
- [Sponsor Endpoints](./api-design-and-integration.md#sponsor-endpoints)
|
||||
- [Bulk Sign](./api-design-and-integration.md#bulk-sign)
|
||||
- [Source Tree](./source-tree.md)
|
||||
- [Existing Project Structure](./source-tree.md#existing-project-structure)
|
||||
- [New File Organization](./source-tree.md#new-file-organization)
|
||||
- [Integration Guidelines](./source-tree.md#integration-guidelines)
|
||||
- [Infrastructure and Deployment Integration](./infrastructure-and-deployment-integration.md)
|
||||
- [Existing Infrastructure](./infrastructure-and-deployment-integration.md#existing-infrastructure)
|
||||
- [Enhancement Deployment Strategy](./infrastructure-and-deployment-integration.md#enhancement-deployment-strategy)
|
||||
- [Rollback Strategy](./infrastructure-and-deployment-integration.md#rollback-strategy)
|
||||
- [Resource Sizing Recommendations](./infrastructure-and-deployment-integration.md#resource-sizing-recommendations)
|
||||
- [Coding Standards](./coding-standards.md)
|
||||
- [Existing Standards Compliance](./coding-standards.md#existing-standards-compliance)
|
||||
- [Enhancement-Specific Standards](./coding-standards.md#enhancement-specific-standards)
|
||||
- [Data Models](./data-models.md)
|
||||
- [Project Structure](./project-structure.md)
|
||||
- [API Design](./api-design.md)
|
||||
- [Security](./security.md)
|
||||
- [Testing Strategy](./testing-strategy.md)
|
||||
- [Integration with Existing Tests](./testing-strategy.md#integration-with-existing-tests)
|
||||
- [New Testing Requirements](./testing-strategy.md#new-testing-requirements)
|
||||
- [Unit Tests for New Components](./testing-strategy.md#unit-tests-for-new-components)
|
||||
- [Integration Tests](./testing-strategy.md#integration-tests)
|
||||
- [Regression Testing](./testing-strategy.md#regression-testing)
|
||||
- [Security Integration](./security-integration.md)
|
||||
- [Existing Security Measures](./security-integration.md#existing-security-measures)
|
||||
- [Enhancement Security Requirements](./security-integration.md#enhancement-security-requirements)
|
||||
- [Security Testing](./security-integration.md#security-testing)
|
||||
- [Checklist Results Report](./checklist-results-report.md)
|
||||
- [Brownfield Architecture Validation](./checklist-results-report.md#brownfield-architecture-validation)
|
||||
- [✅ Integration Assessment](./checklist-results-report.md#integration-assessment)
|
||||
- [✅ Technical Compatibility](./checklist-results-report.md#technical-compatibility)
|
||||
- [✅ Architecture Patterns](./checklist-results-report.md#architecture-patterns)
|
||||
- [✅ Data Model Integration](./checklist-results-report.md#data-model-integration)
|
||||
- [✅ Security & Authentication](./checklist-results-report.md#security-authentication)
|
||||
- [✅ Deployment & Operations](./checklist-results-report.md#deployment-operations)
|
||||
- [✅ Testing Strategy](./checklist-results-report.md#testing-strategy)
|
||||
- [Critical Architectural Decisions](./checklist-results-report.md#critical-architectural-decisions)
|
||||
- [Risk Mitigation Summary](./checklist-results-report.md#risk-mitigation-summary)
|
||||
- [Architectural Decision Records (ADRs)](./checklist-results-report.md#architectural-decision-records-adrs)
|
||||
- [Next Steps](./next-steps.md)
|
||||
- [Story Manager Handoff](./next-steps.md#story-manager-handoff)
|
||||
- [Developer Handoff](./next-steps.md#developer-handoff)
|
||||
- [Enhanced Documentation Summary](./next-steps.md#enhanced-documentation-summary)
|
||||
- [Infrastructure](./infrastructure.md)
|
||||
|
||||
### Related Documents
|
||||
- [PRD](../prd.md)
|
||||
- [PO Validation Report](../PO_Master_Validation_Report.md)
|
||||
- [PO Plan](../po/plan-to-address-po-findings.md)
|
||||
|
||||
---
|
||||
|
||||
## 📋 Status & Next Steps
|
||||
|
||||
### ✅ Completed
|
||||
- Architecture documentation created
|
||||
- All 14 architecture files written
|
||||
- PO validation issues addressed
|
||||
- PRD sharded for IDE support
|
||||
- Git commits completed
|
||||
- Branch merged to master
|
||||
|
||||
### 🎯 Next Actions
|
||||
1. **Review Architecture**: Read through key documents
|
||||
2. **Setup Local**: Run Docker Compose setup
|
||||
3. **Start Story 1.1**: Implement database schema
|
||||
4. **Write Tests**: Follow testing strategy
|
||||
5. **Iterate**: Follow enhanced IDE workflow
|
||||
|
||||
### 📞 Support
|
||||
For questions or clarifications:
|
||||
- Review specific architecture documents
|
||||
- Check PRD in `docs/prd.md`
|
||||
- Refer to stories in `docs/prd/6-epic-details.md`
|
||||
|
||||
---
|
||||
|
||||
## 🏆 Success Criteria
|
||||
|
||||
### Architecture Quality
|
||||
- ✅ Comprehensive coverage of all technical aspects
|
||||
- ✅ Clear examples and code snippets
|
||||
- ✅ Follows industry best practices
|
||||
- ✅ Addresses security from the start
|
||||
- ✅ Enables efficient development
|
||||
|
||||
### Developer Experience
|
||||
- ✅ Easy to find information
|
||||
- ✅ Clear implementation guidance
|
||||
- ✅ Complete testing strategy
|
||||
- ✅ Standardized conventions
|
||||
- ✅ Production-ready patterns
|
||||
|
||||
### Project Readiness
|
||||
- ✅ All documentation complete
|
||||
- ✅ Infrastructure ready
|
||||
- ✅ Security addressed
|
||||
- ✅ Quality gates defined
|
||||
- ✅ Ready for implementation
|
||||
|
||||
---
|
||||
|
||||
**Document Status**: ✅ Complete
|
||||
**Last Updated**: 2026-01-14
|
||||
**Next Review**: After Phase 1 Implementation
|
||||
|
||||
---
|
||||
|
||||
## 📝 Notes
|
||||
|
||||
This architecture documentation is **comprehensive and production-ready** for the Local Docker MVP. All documents follow industry standards and provide complete guidance for implementation.
|
||||
|
||||
**Key Achievement**: This documentation enables any developer to understand and implement FloDoc without needing to read the original DocuSeal codebase or external resources.
|
||||
@ -1,84 +0,0 @@
|
||||
# Infrastructure and Deployment Integration
|
||||
|
||||
## Existing Infrastructure
|
||||
|
||||
**Current Deployment:** Docker-based with Dockerfile and docker-compose.yml
|
||||
**Infrastructure Tools:** Docker, Sidekiq, Puma, Redis, PostgreSQL/MySQL
|
||||
**Environments:** Development (SQLite), Production (PostgreSQL/MySQL)
|
||||
|
||||
## Enhancement Deployment Strategy
|
||||
|
||||
**Deployment Approach:** Incremental feature addition to existing DocuSeal deployment
|
||||
- **Zero downtime:** Database migrations are additive only
|
||||
- **Feature flags:** Can disable cohort features if issues arise
|
||||
- **Rolling deployment:** Deploy new code alongside existing functionality
|
||||
|
||||
**Infrastructure Changes:** None required
|
||||
- ✅ No new services needed
|
||||
- ✅ No infrastructure configuration changes
|
||||
- ✅ Existing Docker setup sufficient
|
||||
- ✅ Redis already configured for Sidekiq
|
||||
|
||||
**Pipeline Integration:**
|
||||
- ✅ Existing CI/CD handles new Ruby code
|
||||
- ✅ Shakapacker bundles new Vue components automatically
|
||||
- ✅ Existing test suite extends with new tests
|
||||
- ✅ No changes to build process
|
||||
|
||||
## Rollback Strategy
|
||||
|
||||
**Rollback Method:** Standard git revert + database migration rollback
|
||||
- **Code rollback:** `git revert <commit-hash>` - Reverts to previous state
|
||||
- **Database rollback:** `bin/rails db:rollback STEP=5` - Rolls back last 5 migrations
|
||||
- **Asset rollback:** Previous assets remain cached in CDN
|
||||
|
||||
**Risk Mitigation:**
|
||||
1. **Database backups:** Before migrations run in production
|
||||
2. **Feature flags:** Can disable cohort routes if needed
|
||||
3. **Gradual rollout:** Deploy to staging first, then production
|
||||
4. **Monitoring:** Watch error rates and performance metrics
|
||||
|
||||
**Monitoring:**
|
||||
- Extend existing Rails logging with cohort events
|
||||
- Add cohort-specific metrics to existing monitoring
|
||||
- Use existing Sidekiq monitoring for new jobs
|
||||
- Track API response times for new endpoints
|
||||
|
||||
## Resource Sizing Recommendations
|
||||
|
||||
**Development Environment:**
|
||||
- **CPU:** 2 cores minimum
|
||||
- **RAM:** 4GB minimum (8GB recommended)
|
||||
- **Storage:** 10GB free space
|
||||
- **Database:** SQLite (file-based, no additional resources)
|
||||
|
||||
**Production Environment (Small Scale: 1-5 institutions, <1000 students):**
|
||||
- **Application Server:** 4 cores, 8GB RAM, 50GB SSD
|
||||
- **Database:** PostgreSQL 14+, 2GB RAM, 1 CPU core
|
||||
- **Redis:** 1GB RAM for Sidekiq
|
||||
- **Concurrent Users:** 50-100
|
||||
- **Background Workers:** 2 workers (1 core, 1GB RAM each)
|
||||
|
||||
**Production Environment (Medium Scale: 5-20 institutions, <5000 students):**
|
||||
- **Application Server:** 8 cores, 16GB RAM, 100GB SSD
|
||||
- **Database:** PostgreSQL 14+, 4GB RAM, 2 CPU cores
|
||||
- **Redis:** 2GB RAM
|
||||
- **Concurrent Users:** 200-400
|
||||
- **Background Workers:** 4 workers (2 cores, 2GB RAM each)
|
||||
|
||||
**Production Environment (Large Scale: 20+ institutions, 5000+ students):**
|
||||
- **Application Server:** 16 cores, 32GB RAM, 200GB SSD
|
||||
- **Database:** PostgreSQL 14+, 16GB RAM, 4 CPU cores (consider read replicas)
|
||||
- **Redis:** 4GB RAM
|
||||
- **Concurrent Users:** 500+
|
||||
- **Background Workers:** 8+ workers (2 cores, 4GB RAM each)
|
||||
|
||||
**Performance Targets:**
|
||||
- **Dashboard load:** < 2 seconds
|
||||
- **Cohort list (50 cohorts):** < 1 second
|
||||
- **Student list (100 students):** < 1.5 seconds
|
||||
- **Excel export (100 students):** < 5 seconds
|
||||
- **Document preview:** < 2 seconds
|
||||
- **Bulk signing (50 students):** < 60 seconds
|
||||
|
||||
---
|
||||
@ -0,0 +1,691 @@
|
||||
# Infrastructure - FloDoc Architecture
|
||||
|
||||
**Document**: Local Docker MVP Setup
|
||||
**Version**: 1.0
|
||||
**Last Updated**: 2026-01-14
|
||||
**Deployment Strategy**: Option A - Local Docker Only
|
||||
|
||||
---
|
||||
|
||||
## 🏗️ Infrastructure Overview
|
||||
|
||||
FloDoc uses Docker Compose for local development and demonstration. This provides a consistent, isolated environment that mirrors production without requiring production infrastructure.
|
||||
|
||||
**Architecture**:
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ Docker Host │
|
||||
│ │
|
||||
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
|
||||
│ │ Rails │ │ PostgreSQL │ │ Redis │ │
|
||||
│ │ App │ │ │ │ │ │
|
||||
│ └─────────────┘ └─────────────┘ └─────────────┘ │
|
||||
│ │ │ │ │
|
||||
│ └────────────────┼────────────────┘ │
|
||||
│ │ │
|
||||
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
|
||||
│ │ Minio │ │ MailHog │ │ Sidekiq │ │
|
||||
│ │ (S3) │ │ (Email) │ │ (Jobs) │ │
|
||||
│ └─────────────┘ └─────────────┘ └─────────────┘ │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🐳 Docker Compose Configuration
|
||||
|
||||
### docker-compose.yml
|
||||
|
||||
```yaml
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
# Rails Application
|
||||
app:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
ports:
|
||||
- "3000:3000"
|
||||
depends_on:
|
||||
- db
|
||||
- redis
|
||||
- minio
|
||||
environment:
|
||||
# Database
|
||||
DATABASE_URL: postgresql://postgres:password@db:5432/flo_doc_development
|
||||
|
||||
# Redis (Sidekiq)
|
||||
REDIS_URL: redis://redis:6379
|
||||
|
||||
# Storage (Minio)
|
||||
AWS_ACCESS_KEY_ID: minioadmin
|
||||
AWS_SECRET_ACCESS_KEY: minioadmin
|
||||
AWS_REGION: us-east-1
|
||||
AWS_ENDPOINT_URL: http://minio:9000
|
||||
AWS_BUCKET_NAME: flo-doc
|
||||
|
||||
# Email (MailHog)
|
||||
SMTP_ADDRESS: mailhog
|
||||
SMTP_PORT: 1025
|
||||
|
||||
# Rails
|
||||
RAILS_ENV: development
|
||||
RAILS_LOG_LEVEL: info
|
||||
RAILS_SERVE_STATIC_FILES: true
|
||||
|
||||
# Security
|
||||
SECRET_KEY_BASE: dev_secret_key_base_change_in_production
|
||||
JWT_SECRET_KEY: dev_jwt_secret_change_in_production
|
||||
|
||||
# Feature Flags
|
||||
FLODOC_MULTITENANT: "false"
|
||||
FLODOC_PRO: "false"
|
||||
|
||||
volumes:
|
||||
- .:/app
|
||||
- app_bundle:/usr/local/bundle
|
||||
- app_node_modules:/app/node_modules
|
||||
command: bundle exec foreman start -f Procfile.dev
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
|
||||
# PostgreSQL Database
|
||||
db:
|
||||
image: postgres:14-alpine
|
||||
ports:
|
||||
- "5432:5432"
|
||||
environment:
|
||||
POSTGRES_USER: postgres
|
||||
POSTGRES_PASSWORD: password
|
||||
POSTGRES_DB: flo_doc_development
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
- ./init.sql:/docker-entrypoint-initdb.d/init.sql:ro
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U postgres"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
# Redis (Sidekiq)
|
||||
redis:
|
||||
image: redis:7-alpine
|
||||
ports:
|
||||
- "6379:6379"
|
||||
volumes:
|
||||
- redis_data:/data
|
||||
command: redis-server --appendonly yes
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "ping"]
|
||||
interval: 10s
|
||||
timeout: 3s
|
||||
retries: 3
|
||||
|
||||
# Minio (S3-Compatible Storage)
|
||||
minio:
|
||||
image: minio/minio
|
||||
ports:
|
||||
- "9000:9000" # API
|
||||
- "9001:9001" # Console
|
||||
environment:
|
||||
MINIO_ROOT_USER: minioadmin
|
||||
MINIO_ROOT_PASSWORD: minioadmin
|
||||
volumes:
|
||||
- minio_data:/data
|
||||
command: server /data --console-address ":9001"
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
|
||||
# MailHog (Email Testing)
|
||||
mailhog:
|
||||
image: mailhog/mailhog
|
||||
ports:
|
||||
- "1025:1025" # SMTP
|
||||
- "8025:8025" # Web UI
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:8025"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
|
||||
# Sidekiq (Background Jobs)
|
||||
sidekiq:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
depends_on:
|
||||
- db
|
||||
- redis
|
||||
environment:
|
||||
DATABASE_URL: postgresql://postgres:password@db:5432/flo_doc_development
|
||||
REDIS_URL: redis://redis:6379
|
||||
RAILS_ENV: development
|
||||
AWS_ACCESS_KEY_ID: minioadmin
|
||||
AWS_SECRET_ACCESS_KEY: minioadmin
|
||||
AWS_ENDPOINT_URL: http://minio:9000
|
||||
AWS_BUCKET_NAME: flo-doc
|
||||
command: bundle exec sidekiq -C config/sidekiq.yml
|
||||
volumes:
|
||||
- .:/app
|
||||
- app_bundle:/usr/local/bundle
|
||||
restart: unless-stopped
|
||||
|
||||
volumes:
|
||||
postgres_data:
|
||||
redis_data:
|
||||
minio_data:
|
||||
app_bundle:
|
||||
app_node_modules:
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🐋 Dockerfile
|
||||
|
||||
```dockerfile
|
||||
# Dockerfile
|
||||
FROM ruby:3.2-slim
|
||||
|
||||
# Install system dependencies
|
||||
RUN apt-get update -qq && \
|
||||
apt-get install -y \
|
||||
build-essential \
|
||||
libpq-dev \
|
||||
libxml2-dev \
|
||||
libxslt1-dev \
|
||||
nodejs \
|
||||
npm \
|
||||
curl \
|
||||
git \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Install Yarn
|
||||
RUN npm install -g yarn
|
||||
|
||||
# Set working directory
|
||||
WORKDIR /app
|
||||
|
||||
# Install Ruby dependencies
|
||||
COPY Gemfile Gemfile.lock ./
|
||||
RUN bundle config set --local deployment 'true' && \
|
||||
bundle install --jobs 4 --retry 3
|
||||
|
||||
# Install Node dependencies
|
||||
COPY package.json yarn.lock ./
|
||||
RUN yarn install --frozen-lockfile
|
||||
|
||||
# Copy application code
|
||||
COPY . .
|
||||
|
||||
# Precompile assets (optional, can be done at runtime)
|
||||
# RUN bundle exec rails assets:precompile
|
||||
|
||||
# Expose port
|
||||
EXPOSE 3000
|
||||
|
||||
# Health check endpoint
|
||||
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
|
||||
CMD curl -f http://localhost:3000/health || exit 1
|
||||
|
||||
# Default command
|
||||
CMD ["bundle", "exec", "rails", "server", "-b", "0.0.0.0"]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📁 Project Structure for Docker
|
||||
|
||||
```
|
||||
floDoc-v3/
|
||||
├── Dockerfile
|
||||
├── docker-compose.yml
|
||||
├── Procfile.dev
|
||||
├── Gemfile
|
||||
├── package.json
|
||||
├── .env.example
|
||||
├── init.sql (optional)
|
||||
└── app/
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Quick Start Guide
|
||||
|
||||
### Prerequisites
|
||||
- Docker Desktop (or Docker Engine + Docker Compose)
|
||||
- Git
|
||||
- Terminal/Command Line
|
||||
|
||||
### Step 1: Clone & Setup
|
||||
```bash
|
||||
# Clone repository
|
||||
git clone <repository-url> floDoc-v3
|
||||
cd floDoc-v3
|
||||
|
||||
# Create environment file
|
||||
cp .env.example .env
|
||||
# Edit .env with your settings
|
||||
```
|
||||
|
||||
### Step 2: Start Services
|
||||
```bash
|
||||
# Build and start all services
|
||||
docker-compose up -d
|
||||
|
||||
# Or build without cache
|
||||
docker-compose up -d --build
|
||||
|
||||
# View logs
|
||||
docker-compose logs -f app
|
||||
```
|
||||
|
||||
### Step 3: Setup Database
|
||||
```bash
|
||||
# Create and migrate database
|
||||
docker-compose exec app bundle exec rails db:setup
|
||||
|
||||
# Or run migrations only
|
||||
docker-compose exec app bundle exec rails db:migrate
|
||||
|
||||
# Seed data (optional)
|
||||
docker-compose exec app bundle exec rails db:seed
|
||||
```
|
||||
|
||||
### Step 4: Verify Installation
|
||||
```bash
|
||||
# Check all services are healthy
|
||||
docker-compose ps
|
||||
|
||||
# Test application
|
||||
curl http://localhost:3000/health
|
||||
|
||||
# Open in browser
|
||||
open http://localhost:3000
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔧 Development Workflow
|
||||
|
||||
### Starting Development
|
||||
```bash
|
||||
# Start all services
|
||||
docker-compose up -d
|
||||
|
||||
# View real-time logs
|
||||
docker-compose logs -f app
|
||||
|
||||
# Run Rails console
|
||||
docker-compose exec app bundle exec rails console
|
||||
|
||||
# Run database console
|
||||
docker-compose exec db psql -U postgres -d flo_doc_development
|
||||
```
|
||||
|
||||
### Running Tests
|
||||
```bash
|
||||
# Ruby tests
|
||||
docker-compose exec app bundle exec rspec
|
||||
|
||||
# JavaScript tests
|
||||
docker-compose exec app yarn test
|
||||
|
||||
# With coverage
|
||||
docker-compose exec app bundle exec rspec --format documentation
|
||||
docker-compose exec app yarn test --coverage
|
||||
```
|
||||
|
||||
### Code Changes
|
||||
```bash
|
||||
# Edit files normally on your host machine
|
||||
# Changes are automatically reflected in container
|
||||
|
||||
# Restart Rails server if needed
|
||||
docker-compose restart app
|
||||
|
||||
# Rebuild specific service
|
||||
docker-compose up -d --build app
|
||||
```
|
||||
|
||||
### Stopping Services
|
||||
```bash
|
||||
# Stop all services
|
||||
docker-compose down
|
||||
|
||||
# Stop and remove volumes (WARNING: deletes data)
|
||||
docker-compose down -v
|
||||
|
||||
# Stop specific service
|
||||
docker-compose stop sidekiq
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📊 Service Access Points
|
||||
|
||||
| Service | Port | URL | Purpose |
|
||||
|---------|------|-----|---------|
|
||||
| Rails App | 3000 | http://localhost:3000 | Main application |
|
||||
| PostgreSQL | 5432 | localhost:5432 | Database |
|
||||
| Redis | 6379 | localhost:6379 | Sidekiq backend |
|
||||
| Minio API | 9000 | http://localhost:9000 | S3-compatible storage |
|
||||
| Minio Console | 9001 | http://localhost:9001 | Storage management |
|
||||
| MailHog SMTP | 1025 | localhost:1025 | Email testing |
|
||||
| MailHog Web | 8025 | http://localhost:8025 | Email inbox viewer |
|
||||
|
||||
---
|
||||
|
||||
## 🔍 Monitoring & Debugging
|
||||
|
||||
### View Logs
|
||||
```bash
|
||||
# All services
|
||||
docker-compose logs -f
|
||||
|
||||
# Specific service
|
||||
docker-compose logs -f app
|
||||
docker-compose logs -f sidekiq
|
||||
|
||||
# Follow and tail
|
||||
docker-compose logs -f --tail=100 app
|
||||
```
|
||||
|
||||
### Check Service Health
|
||||
```bash
|
||||
# All services
|
||||
docker-compose ps
|
||||
|
||||
# App health endpoint
|
||||
curl http://localhost:3000/health
|
||||
|
||||
# Database connectivity
|
||||
docker-compose exec app bundle exec rails runner "puts ActiveRecord::Base.connection.current_database"
|
||||
|
||||
# Redis connectivity
|
||||
docker-compose exec redis redis-cli ping
|
||||
```
|
||||
|
||||
### Access Containers
|
||||
```bash
|
||||
# Shell into app container
|
||||
docker-compose exec app bash
|
||||
|
||||
# Shell into database
|
||||
docker-compose exec db bash
|
||||
|
||||
# Shell into Redis
|
||||
docker-compose exec redis sh
|
||||
```
|
||||
|
||||
### Database Management
|
||||
```bash
|
||||
# Create database
|
||||
docker-compose exec app bundle exec rails db:create
|
||||
|
||||
# Reset database
|
||||
docker-compose exec app bundle exec rails db:reset
|
||||
|
||||
# Run specific migration
|
||||
docker-compose exec app bundle exec rails db:migrate:up VERSION=20260114000001
|
||||
|
||||
# Rollback
|
||||
docker-compose exec app bundle exec rails db:rollback
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎨 Email Testing
|
||||
|
||||
### Access MailHog Web UI
|
||||
1. Open http://localhost:8025
|
||||
2. Send emails from application
|
||||
3. View email content, headers, and attachments
|
||||
|
||||
### Test Email Flow
|
||||
```ruby
|
||||
# Rails console
|
||||
docker-compose exec app bundle exec rails console
|
||||
|
||||
# Send test email
|
||||
CohortMailer.activated(Cohort.first).deliver_now
|
||||
|
||||
# Check MailHog at http://localhost:8025
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 💾 Storage Management
|
||||
|
||||
### Access Minio Console
|
||||
1. Open http://localhost:9001
|
||||
2. Login: `minioadmin` / `minioadmin`
|
||||
3. View buckets and files
|
||||
|
||||
### Upload Files
|
||||
```ruby
|
||||
# In Rails console
|
||||
cohort = Cohort.first
|
||||
cohort.documents.attach(
|
||||
io: File.open('/path/to/file.pdf'),
|
||||
filename: 'document.pdf',
|
||||
content_type: 'application/pdf'
|
||||
)
|
||||
```
|
||||
|
||||
### View Uploaded Files
|
||||
```ruby
|
||||
# Get URL
|
||||
url = url_for(cohort.documents.first)
|
||||
puts url # Will be http://minio:9000/flo-doc/...
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔄 Background Jobs (Sidekiq)
|
||||
|
||||
### Monitor Sidekiq
|
||||
```bash
|
||||
# View Sidekiq logs
|
||||
docker-compose logs -f sidekiq
|
||||
|
||||
# Access Sidekiq Web UI (if mounted)
|
||||
# Add to routes.rb:
|
||||
# require 'sidekiq/web'
|
||||
# mount Sidekiq::Web => '/sidekiq'
|
||||
# Then visit http://localhost:3000/sidekiq
|
||||
```
|
||||
|
||||
### Test Background Jobs
|
||||
```ruby
|
||||
# Rails console
|
||||
docker-compose exec app bundle exec rails console
|
||||
|
||||
# Enqueue a job
|
||||
CohortMailer.activated(Cohort.first).deliver_later
|
||||
|
||||
# Check Sidekiq logs
|
||||
docker-compose logs -f sidekiq
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🛠️ Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
**1. Port Already in Use**
|
||||
```bash
|
||||
# Error: "Bind for 0.0.0.0:3000 failed: port is already allocated"
|
||||
|
||||
# Solution: Stop conflicting services
|
||||
sudo lsof -i :3000
|
||||
kill <PID>
|
||||
|
||||
# Or change port in docker-compose.yml
|
||||
ports:
|
||||
- "3001:3000" # Host:Container
|
||||
```
|
||||
|
||||
**2. Database Connection Failed**
|
||||
```bash
|
||||
# Error: "could not connect to server"
|
||||
|
||||
# Solution: Wait for DB to be ready
|
||||
docker-compose exec db pg_isready -U postgres
|
||||
|
||||
# Or restart DB
|
||||
docker-compose restart db
|
||||
```
|
||||
|
||||
**3. Bundle Install Fails**
|
||||
```bash
|
||||
# Error: "Gem::Ext::BuildError"
|
||||
|
||||
# Solution: Rebuild with cache cleared
|
||||
docker-compose down -v
|
||||
docker-compose build --no-cache
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
**4. Node Modules Missing**
|
||||
```bash
|
||||
# Error: "Cannot find module"
|
||||
|
||||
# Solution: Reinstall node modules
|
||||
docker-compose exec app yarn install
|
||||
```
|
||||
|
||||
**5. Assets Not Compiling**
|
||||
```bash
|
||||
# Precompile assets manually
|
||||
docker-compose exec app bundle exec rails assets:precompile
|
||||
docker-compose restart app
|
||||
```
|
||||
|
||||
### Reset Everything
|
||||
```bash
|
||||
# WARNING: This deletes all data
|
||||
docker-compose down -v
|
||||
docker-compose build --no-cache
|
||||
docker-compose up -d
|
||||
docker-compose exec app bundle exec rails db:setup
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📦 Production Considerations
|
||||
|
||||
### This is Local Docker MVP Only
|
||||
**DO NOT use this setup for production**
|
||||
|
||||
### Production Requirements (Deferred)
|
||||
- Managed database (RDS, Cloud SQL)
|
||||
- Managed Redis (ElastiCache, Memorystore)
|
||||
- Object storage (S3, GCS)
|
||||
- Load balancer
|
||||
- Auto-scaling
|
||||
- Monitoring (CloudWatch, Stackdriver)
|
||||
- Backup strategy
|
||||
- SSL certificates
|
||||
- Domain configuration
|
||||
|
||||
### When to Upgrade
|
||||
- Management validates MVP
|
||||
- Ready for production deployment
|
||||
- Need high availability
|
||||
- Require scaling beyond single instance
|
||||
- Need compliance certifications
|
||||
|
||||
---
|
||||
|
||||
## 🔒 Security Notes
|
||||
|
||||
### Local Development Only
|
||||
- Default credentials (minioadmin/password) are **NOT SECURE**
|
||||
- No SSL/TLS encryption
|
||||
- No firewall rules
|
||||
- Debug mode enabled
|
||||
|
||||
### Before Production
|
||||
- Change all default passwords
|
||||
- Enable SSL/TLS
|
||||
- Implement proper secrets management
|
||||
- Use environment-specific configs
|
||||
- Enable security headers
|
||||
- Implement rate limiting
|
||||
- Set up monitoring and alerting
|
||||
|
||||
---
|
||||
|
||||
## 📊 Performance Tuning
|
||||
|
||||
### Docker Resources
|
||||
**Recommended for development**:
|
||||
- CPU: 4+ cores
|
||||
- RAM: 8GB+ (4GB for Docker)
|
||||
- Disk: 50GB+
|
||||
|
||||
### Docker Desktop Settings
|
||||
1. Open Docker Desktop
|
||||
2. Go to Preferences → Resources
|
||||
3. Set:
|
||||
- CPUs: 4
|
||||
- Memory: 8GB
|
||||
- Disk image size: 50GB
|
||||
|
||||
### Optimize Build Speed
|
||||
```dockerfile
|
||||
# Use layer caching
|
||||
COPY Gemfile Gemfile.lock ./
|
||||
RUN bundle install
|
||||
COPY . . # This layer changes frequently
|
||||
|
||||
# Multi-stage builds (for production)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Next Steps
|
||||
|
||||
1. **Start Development**:
|
||||
```bash
|
||||
docker-compose up -d
|
||||
docker-compose exec app bundle exec rails db:setup
|
||||
```
|
||||
|
||||
2. **Verify Installation**:
|
||||
- Visit http://localhost:3000
|
||||
- Check MailHog at http://localhost:8025
|
||||
- Check Minio at http://localhost:9001
|
||||
|
||||
3. **Run First Story**:
|
||||
```bash
|
||||
docker-compose exec app bundle exec rails generate migration CreateFloDocTables
|
||||
```
|
||||
|
||||
4. **Write Tests**:
|
||||
```bash
|
||||
docker-compose exec app bundle exec rspec
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📚 Related Documents
|
||||
|
||||
- **Tech Stack**: `docs/architecture/tech-stack.md`
|
||||
- **Project Structure**: `docs/architecture/project-structure.md`
|
||||
- **Story 8.0**: Infrastructure setup story in PRD
|
||||
|
||||
---
|
||||
|
||||
**Document Status**: ✅ Complete
|
||||
**Deployment Strategy**: Local Docker MVP (Option A)
|
||||
**Ready for**: Development and Management Demo
|
||||
@ -1,54 +0,0 @@
|
||||
# Introduction
|
||||
|
||||
## Introduction Content
|
||||
|
||||
This document outlines the architectural approach for enhancing **DocuSeal** with the **3-Portal Cohort Management System** for training institutions. Its primary goal is to serve as the guiding architectural blueprint for AI-driven development of new features while ensuring seamless integration with the existing system.
|
||||
|
||||
**Relationship to Existing Architecture:**
|
||||
This document supplements existing DocuSeal architecture by defining how new cohort management components will integrate with current systems. Where conflicts arise between new and existing patterns, this document provides guidance on maintaining consistency while implementing enhancements.
|
||||
|
||||
## Existing Project Analysis
|
||||
|
||||
### Current Project State
|
||||
|
||||
**Primary Purpose:** DocuSeal is an open-source document filling and signing platform providing WYSIWYG PDF form building, multi-signer workflows, and secure digital document signing capabilities.
|
||||
|
||||
**Current Tech Stack:**
|
||||
- **Languages:** Ruby 3.4.2, JavaScript, Vue.js 3, HTML, CSS
|
||||
- **Frameworks:** Rails 7.x, Shakapacker 8.0, Vue 3.3.2, TailwindCSS 3.4.17, DaisyUI 3.9.4
|
||||
- **Database:** SQLite (development), PostgreSQL/MySQL (production via DATABASE_URL)
|
||||
- **Infrastructure:** Docker, Sidekiq for background jobs, Puma web server
|
||||
- **External Dependencies:** AWS S3, Google Cloud Storage, Azure Cloud (optional), SMTP for emails
|
||||
|
||||
**Architecture Style:** Monolithic Rails 7 application with Vue.js 3 frontend, following MVC pattern with service objects for complex business logic.
|
||||
|
||||
**Deployment Method:** Docker-based deployment with existing CI/CD pipeline, Shakapacker for asset compilation, Sidekiq workers for background processing.
|
||||
|
||||
### Available Documentation
|
||||
|
||||
- ✅ **API Documentation** - Complete RESTful API with examples in Node.js, Ruby, Python, PHP, Java, Go, C#, TypeScript, JavaScript
|
||||
- ✅ **Webhook Documentation** - Submission, form, and template webhooks with event types and payload schemas
|
||||
- ✅ **Embedding Documentation** - React, Vue, Angular, JavaScript form builders and signing forms
|
||||
- ⚠️ **Architecture Documentation** - **Created via this document** (previously missing)
|
||||
- ⚠️ **Coding Standards** - **To be documented** (previously missing)
|
||||
- ⚠️ **Source Tree Documentation** - **Created via this document** (previously missing)
|
||||
- ⚠️ **Technical Debt Documentation** - **To be analyzed** (previously missing)
|
||||
|
||||
### Identified Constraints
|
||||
|
||||
- **Multi-tenancy:** Current system supports single-account or multi-tenant mode via `Docuseal.multitenant?` flag
|
||||
- **Authentication:** Devise-based with 2FA support, JWT tokens for API access
|
||||
- **Authorization:** Cancancan with role-based access via `AccountAccess` model
|
||||
- **Storage:** Active Storage with multiple backend support (S3, GCS, Azure, local)
|
||||
- **PDF Processing:** HexaPDF for generation/signing, PDFium for rendering
|
||||
- **Background Jobs:** Sidekiq with Redis dependency
|
||||
- **UI Framework:** Vue 3 with Composition API, DaisyUI components
|
||||
- **Mobile Support:** Existing responsive design must be maintained
|
||||
|
||||
## Change Log
|
||||
|
||||
| Change | Date | Version | Description | Author |
|
||||
|--------|------|---------|-------------|--------|
|
||||
| Initial Architecture Creation | 2025-01-02 | v1.0 | Brownfield enhancement architecture for 3-portal cohort management | Winston (Architect) |
|
||||
|
||||
---
|
||||
@ -1,106 +0,0 @@
|
||||
# Next Steps
|
||||
|
||||
## Story Manager Handoff
|
||||
|
||||
**Reference Architecture:** This document provides complete architectural blueprint for 3-portal cohort management enhancement.
|
||||
|
||||
**Key Integration Requirements (Validated):**
|
||||
- **Authentication:** Extend existing Devise + JWT without modification
|
||||
- **Database:** Additive schema changes only, maintain 100% backward compatibility
|
||||
- **API:** Extend existing v1 endpoints, follow RESTful patterns
|
||||
- **UI:** Custom TailwindCSS design system for portals, mobile-first responsive
|
||||
- **PDF Processing:** Reuse existing HexaPDF and form builder components
|
||||
- **Email/Notifications:** Leverage existing DocuSeal email infrastructure
|
||||
- **Storage:** Use existing Active Storage with multi-backend support
|
||||
|
||||
**First Story to Implement:** **Story 1.1 - Institution and Admin Management**
|
||||
- **Why first:** Foundation for multi-tenancy, enables all subsequent stories
|
||||
- **Integration checkpoints:**
|
||||
1. Verify Institution model doesn't conflict with existing Account
|
||||
2. Test role-based permissions with existing Cancancan
|
||||
3. Ensure admin invitation uses existing Devise patterns
|
||||
4. Validate data isolation between institutions
|
||||
- **Success criteria:** Admin can create institution, invite other admins, manage permissions
|
||||
|
||||
**Implementation Sequencing:**
|
||||
1. **Story 1.1** → Institution & Admin Management (foundation)
|
||||
2. **Story 1.2** → Cohort Creation & Templates (builds on 1.1)
|
||||
3. **Story 1.3** → Student Enrollment (requires 1.2)
|
||||
4. **Story 1.4** → Admin Verification (parallel with 1.3)
|
||||
5. **Story 1.5** → Student Portal (requires 1.3)
|
||||
6. **Story 1.6** → Sponsor Portal (requires 1.5)
|
||||
7. **Story 1.7** → Admin Finalization (requires 1.6)
|
||||
8. **Story 1.8** → Notifications (can run parallel)
|
||||
9. **Story 1.9** → Dashboard & Analytics (requires all above)
|
||||
10. **Story 1.10** → State Management (refinement throughout)
|
||||
|
||||
## Developer Handoff
|
||||
|
||||
**Architecture Reference:** This document is the source of truth for all architectural decisions.
|
||||
|
||||
**Key Technical Constraints (Based on Real Project Analysis):**
|
||||
- **Ruby 3.4.2, Rails 7.x** - Maintain exact versions
|
||||
- **Vue 3.3.2, Composition API** - All new components use `<script setup>`
|
||||
- **TailwindCSS 3.4.17** - No DaisyUI for new portals
|
||||
- **SQLite dev, PostgreSQL/MySQL prod** - Test with both
|
||||
- **Sidekiq + Redis** - Required for background jobs
|
||||
- **HexaPDF** - Core document processing engine
|
||||
|
||||
**Integration Requirements (Validated with Real Code):**
|
||||
- **Models:** Follow existing patterns (strip_attributes, annotations, foreign keys)
|
||||
- **Controllers:** Use existing base controllers, follow naming conventions
|
||||
- **API:** Match existing response formats, error handling, pagination
|
||||
- **Vue:** Use existing API client patterns, component registration
|
||||
- **Jobs:** Follow existing Sidekiq job patterns, queue naming
|
||||
- **Tests:** Use existing factories, helpers, matchers
|
||||
|
||||
**Critical Verification Steps:**
|
||||
1. **Run existing test suite** - Must pass before any cohort changes
|
||||
2. **Test authentication flow** - Verify Devise + JWT works for new roles
|
||||
3. **Validate database migrations** - Test rollback on production-like data
|
||||
4. **Check performance** - Monitor response times with large cohorts
|
||||
5. **Verify mobile responsiveness** - Test all portals on mobile devices
|
||||
6. **Test existing workflows** - Ensure template creation, submission, signing still work
|
||||
|
||||
**Key Files to Reference:**
|
||||
- `app/models/user.rb` - Authentication patterns
|
||||
- `app/models/account.rb` - Multi-tenancy structure
|
||||
- `app/controllers/api/api_base_controller.rb` - API auth patterns
|
||||
- `lib/submissions/` - Business logic patterns
|
||||
- `app/javascript/template_builder/` - Form builder integration
|
||||
- `app/javascript/submission_form/` - Signing form patterns
|
||||
|
||||
**Rollback Checklist:**
|
||||
- [ ] Database backup before migrations
|
||||
- [ ] Feature flag for cohort routes
|
||||
- [ ] Monitor error rates post-deployment
|
||||
- [ ] Have git revert command ready
|
||||
- [ ] Test rollback procedure on staging
|
||||
|
||||
---
|
||||
|
||||
**Architecture Document Complete** ✅
|
||||
|
||||
This brownfield architecture provides a comprehensive blueprint for implementing the 3-portal cohort management system while maintaining 100% compatibility with existing DocuSeal functionality. All recommendations are based on actual codebase analysis and validated against real project constraints.
|
||||
|
||||
## Enhanced Documentation Summary
|
||||
|
||||
**All architect checklist gaps have been addressed:**
|
||||
|
||||
✅ **Route Tables** - Complete web portal routes for Admin, Student, and Sponsor portals
|
||||
✅ **Resource Sizing** - Detailed recommendations for development and production environments
|
||||
✅ **Architectural Decisions** - 6 ADRs documenting key technical choices and rationale
|
||||
✅ **API Response Schemas** - Complete request/response examples with error handling
|
||||
✅ **Component Props/Events** - TypeScript interfaces for all Vue components
|
||||
✅ **UI Mockups** - ASCII wireframes for all portal interfaces
|
||||
✅ **Error Handling** - Comprehensive error response patterns and codes
|
||||
|
||||
**Key Enhancements Added:**
|
||||
- **Web Portal Routes**: 17 routes across 3 portals with authentication and component mapping
|
||||
- **Performance Targets**: Specific response time goals for all major operations
|
||||
- **Decision Records**: Brownfield strategy, UI approach, auth patterns, state management
|
||||
- **Complete API Examples**: All endpoints with request/response schemas and error cases
|
||||
- **Component Specifications**: Props, events, and state for 10+ Vue components
|
||||
- **Visual Mockups**: ASCII wireframes showing exact UI layouts for all portals
|
||||
|
||||
**Ready for Implementation** 🚀
|
||||
@ -0,0 +1,924 @@
|
||||
# Project Structure - FloDoc Architecture
|
||||
|
||||
**Document**: File Organization & Conventions
|
||||
**Version**: 1.0
|
||||
**Last Updated**: 2026-01-14
|
||||
|
||||
---
|
||||
|
||||
## 📁 Root Directory Structure
|
||||
|
||||
```
|
||||
floDoc-v3/
|
||||
├── app/ # Rails application code
|
||||
├── config/ # Configuration files
|
||||
├── db/ # Database migrations and schema
|
||||
├── docs/ # Documentation
|
||||
│ ├── architecture/ # Architecture docs (this folder)
|
||||
│ ├── prd/ # Product requirements (sharded)
|
||||
│ ├── po/ # Product Owner validation
|
||||
│ ├── qa/ # QA assessments & gates
|
||||
│ └── stories/ # Developer story files
|
||||
├── lib/ # Library code
|
||||
├── spec/ # Tests
|
||||
├── app/javascript/ # Frontend code
|
||||
├── .bmad-core/ # BMAD workflow configuration
|
||||
├── docker-compose.yml # Local Docker setup
|
||||
└── Gemfile, package.json # Dependencies
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Application Directory Structure
|
||||
|
||||
### `app/models/` - Business Logic
|
||||
|
||||
```
|
||||
app/models/
|
||||
├── application_record.rb # Base model
|
||||
├── user.rb # Devise authentication
|
||||
├── account.rb # Multi-tenancy (existing)
|
||||
├── template.rb # Document templates (existing)
|
||||
├── submission.rb # Document workflows (existing)
|
||||
├── submitter.rb # Signers (existing)
|
||||
│
|
||||
├── # NEW FloDoc Models
|
||||
├── institution.rb # Single training institution
|
||||
├── cohort.rb # Training program cohort
|
||||
├── cohort_enrollment.rb # Student enrollment
|
||||
│
|
||||
├── # Concerns & Utilities
|
||||
├── feature_flag.rb # Feature flag system
|
||||
├── account_access.rb # Role-based access (existing)
|
||||
└── ability.rb # Cancancan abilities (existing)
|
||||
```
|
||||
|
||||
**Key Patterns**:
|
||||
- All models inherit from `ApplicationRecord`
|
||||
- Use `strip_attributes` for data cleaning
|
||||
- Include soft delete via `deleted_at` timestamp
|
||||
- Follow Rails naming conventions (singular, lowercase)
|
||||
|
||||
**Example Model**:
|
||||
```ruby
|
||||
# app/models/cohort.rb
|
||||
class Cohort < ApplicationRecord
|
||||
include SoftDeletable
|
||||
strip_attributes
|
||||
|
||||
belongs_to :institution
|
||||
belongs_to :template
|
||||
has_many :cohort_enrollments, dependent: :destroy
|
||||
|
||||
validates :name, :program_type, :sponsor_email, presence: true
|
||||
validates :status, inclusion: { in: %w[draft active completed] }
|
||||
|
||||
scope :active, -> { where(status: 'active') }
|
||||
end
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `app/controllers/` - Request Handling
|
||||
|
||||
```
|
||||
app/controllers/
|
||||
├── application_controller.rb # Base controller
|
||||
├── dashboard_controller.rb # TP dashboard (existing)
|
||||
│
|
||||
├── # NEW FloDoc Controllers
|
||||
├── tp/ # TP Portal namespace
|
||||
│ ├── base_controller.rb
|
||||
│ ├── cohorts_controller.rb
|
||||
│ ├── enrollments_controller.rb
|
||||
│ └── dashboard_controller.rb
|
||||
│
|
||||
├── student/ # Student Portal namespace
|
||||
│ ├── base_controller.rb
|
||||
│ ├── enrollment_controller.rb
|
||||
│ └── documents_controller.rb
|
||||
│
|
||||
├── sponsor/ # Sponsor Portal namespace
|
||||
│ ├── base_controller.rb
|
||||
│ ├── dashboard_controller.rb
|
||||
│ └── signing_controller.rb
|
||||
│
|
||||
├── # API Controllers
|
||||
├── api/
|
||||
│ ├── v1/
|
||||
│ │ ├── base_controller.rb
|
||||
│ │ ├── cohorts_controller.rb
|
||||
│ │ ├── enrollments_controller.rb
|
||||
│ │ └── webhooks_controller.rb
|
||||
│ └── v2/ # Future versions
|
||||
│
|
||||
├── # Settings & Admin
|
||||
├── settings/
|
||||
│ ├── profile_controller.rb
|
||||
│ └── security_controller.rb
|
||||
│
|
||||
└── # Existing DocuSeal Controllers
|
||||
├── templates_controller.rb
|
||||
├── submissions_controller.rb
|
||||
└── submitters_controller.rb
|
||||
```
|
||||
|
||||
**Controller Patterns**:
|
||||
|
||||
**TP Portal Controller**:
|
||||
```ruby
|
||||
# app/controllers/tp/cohorts_controller.rb
|
||||
class tp::CohortsController < ApplicationController
|
||||
before_action :authenticate_user!
|
||||
load_and_authorize_resource
|
||||
|
||||
def index
|
||||
@cohorts = current_institution.cohorts.order(created_at: :desc)
|
||||
end
|
||||
|
||||
def create
|
||||
@cohort = current_institution.cohorts.new(cohort_params)
|
||||
if @cohort.save
|
||||
redirect_to tp_cohort_path(@cohort), notice: 'Cohort created'
|
||||
else
|
||||
render :new
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def cohort_params
|
||||
params.require(:cohort).permit(:name, :program_type, :sponsor_email, :template_id)
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
**Ad-hoc Portal Controller**:
|
||||
```ruby
|
||||
# app/controllers/student/enrollment_controller.rb
|
||||
class Student::EnrollmentController < ApplicationController
|
||||
skip_before_action :authenticate_user!
|
||||
|
||||
def show
|
||||
@enrollment = CohortEnrollment.find_by!(token: params[:token])
|
||||
redirect_to root_path, alert: 'Invalid token' unless @enrollment.pending?
|
||||
end
|
||||
|
||||
def submit
|
||||
@enrollment = CohortEnrollment.find_by!(token: params[:token])
|
||||
# Process submission
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
**API Controller**:
|
||||
```ruby
|
||||
# app/api/v1/cohorts_controller.rb
|
||||
class Api::V1::CohortsController < Api::V1::BaseController
|
||||
def index
|
||||
@cohorts = current_institution.cohorts
|
||||
render json: @cohorts
|
||||
end
|
||||
|
||||
def create
|
||||
@cohort = current_institution.cohorts.new(cohort_params)
|
||||
if @cohort.save
|
||||
render json: @cohort, status: :created
|
||||
else
|
||||
render json: { errors: @cohort.errors }, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def cohort_params
|
||||
params.permit(:name, :program_type, :sponsor_email, :template_id)
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `app/javascript/` - Frontend Code
|
||||
|
||||
```
|
||||
app/javascript/
|
||||
├── application.js # Main entry point
|
||||
├── packs/ # Webpack packs
|
||||
│ └── application.js
|
||||
│
|
||||
├── # NEW FloDoc Portals
|
||||
├── tp/ # TP Portal (Admin)
|
||||
│ ├── index.js # Vue app entry
|
||||
│ ├── views/
|
||||
│ │ ├── Dashboard.vue
|
||||
│ │ ├── CohortList.vue
|
||||
│ │ ├── CohortCreate.vue
|
||||
│ │ ├── CohortDetail.vue
|
||||
│ │ ├── StudentManagement.vue
|
||||
│ │ └── SponsorPortal.vue
|
||||
│ ├── components/
|
||||
│ │ ├── CohortCard.vue
|
||||
│ │ ├── CohortForm.vue
|
||||
│ │ ├── StudentTable.vue
|
||||
│ │ └── StatusBadge.vue
|
||||
│ ├── stores/
|
||||
│ │ ├── cohortStore.js # Pinia store
|
||||
│ │ └── uiStore.js
|
||||
│ └── api/
|
||||
│ └── cohorts.js # API client
|
||||
│
|
||||
├── student/ # Student Portal
|
||||
│ ├── index.js
|
||||
│ ├── views/
|
||||
│ │ ├── Enrollment.vue
|
||||
│ │ ├── DocumentUpload.vue
|
||||
│ │ ├── FormFill.vue
|
||||
│ │ └── SubmissionStatus.vue
|
||||
│ ├── components/
|
||||
│ │ ├── UploadZone.vue
|
||||
│ │ ├── FormField.vue
|
||||
│ │ └── ProgressTracker.vue
|
||||
│ ├── stores/
|
||||
│ │ └── enrollmentStore.js
|
||||
│ └── api/
|
||||
│ └── enrollment.js
|
||||
│
|
||||
├── sponsor/ # Sponsor Portal
|
||||
│ ├── index.js
|
||||
│ ├── views/
|
||||
│ │ ├── Dashboard.vue
|
||||
│ │ ├── BulkSigning.vue
|
||||
│ │ ├── DocumentPreview.vue
|
||||
│ │ └── ProgressTracker.vue
|
||||
│ ├── components/
|
||||
│ │ ├── CohortSummary.vue
|
||||
│ │ ├── SigningTable.vue
|
||||
│ │ └── BulkSignButton.vue
|
||||
│ ├── stores/
|
||||
│ │ └── sponsorStore.js
|
||||
│ └── api/
|
||||
│ └── sponsor.js
|
||||
│
|
||||
├── # Shared Components
|
||||
├── components/
|
||||
│ ├── ui/
|
||||
│ │ ├── Button.vue
|
||||
│ │ ├── Modal.vue
|
||||
│ │ ├── Alert.vue
|
||||
│ │ └── LoadingSpinner.vue
|
||||
│ └── layout/
|
||||
│ ├── Header.vue
|
||||
│ ├── Sidebar.vue
|
||||
│ └── Footer.vue
|
||||
│
|
||||
├── # Existing DocuSeal UI
|
||||
├── template_builder/ # PDF form builder
|
||||
├── elements/ # Web components
|
||||
├── submission_form/ # Multi-step signing
|
||||
│
|
||||
└── # Utilities
|
||||
├── utils/
|
||||
│ ├── auth.js
|
||||
│ ├── api.js
|
||||
│ └── validators.js
|
||||
└── plugins/
|
||||
└── axios.js
|
||||
```
|
||||
|
||||
**Vue Component Pattern**:
|
||||
```vue
|
||||
<!-- app/javascript/tp/views/CohortList.vue -->
|
||||
<template>
|
||||
<div class="cohort-list">
|
||||
<Header title="Cohorts" />
|
||||
|
||||
<div v-if="loading">Loading...</div>
|
||||
|
||||
<div v-else>
|
||||
<CohortCard
|
||||
v-for="cohort in cohorts"
|
||||
:key="cohort.id"
|
||||
:cohort="cohort"
|
||||
@click="viewCohort(cohort.id)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { useCohortStore } from '@/tp/stores/cohortStore'
|
||||
import CohortCard from '@/tp/components/CohortCard.vue'
|
||||
|
||||
const cohortStore = useCohortStore()
|
||||
const loading = ref(true)
|
||||
|
||||
onMounted(async () => {
|
||||
await cohortStore.fetchCohorts()
|
||||
loading.value = false
|
||||
})
|
||||
|
||||
function viewCohort(id) {
|
||||
// Navigate to detail view
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.cohort-list {
|
||||
padding: 2rem;
|
||||
}
|
||||
</style>
|
||||
```
|
||||
|
||||
**Pinia Store Pattern**:
|
||||
```javascript
|
||||
// app/javascript/tp/stores/cohortStore.js
|
||||
import { defineStore } from 'pinia'
|
||||
import { CohortsAPI } from '@/tp/api/cohorts'
|
||||
|
||||
export const useCohortStore = defineStore('cohort', {
|
||||
state: () => ({
|
||||
cohorts: [],
|
||||
currentCohort: null,
|
||||
loading: false,
|
||||
error: null
|
||||
}),
|
||||
|
||||
actions: {
|
||||
async fetchCohorts() {
|
||||
this.loading = true
|
||||
this.error = null
|
||||
try {
|
||||
this.cohorts = await CohortsAPI.getAll()
|
||||
} catch (err) {
|
||||
this.error = err.message
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
},
|
||||
|
||||
async createCohort(data) {
|
||||
const cohort = await CohortsAPI.create(data)
|
||||
this.cohorts.unshift(cohort)
|
||||
return cohort
|
||||
},
|
||||
|
||||
async fetchCohort(id) {
|
||||
this.currentCohort = await CohortsAPI.get(id)
|
||||
}
|
||||
},
|
||||
|
||||
getters: {
|
||||
activeCohorts: (state) => state.cohorts.filter(c => c.status === 'active'),
|
||||
completedCohorts: (state) => state.cohorts.filter(c => c.status === 'completed')
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
**API Client Pattern**:
|
||||
```javascript
|
||||
// app/javascript/tp/api/cohorts.js
|
||||
import axios from 'axios'
|
||||
|
||||
export const CohortsAPI = {
|
||||
async getAll() {
|
||||
const response = await axios.get('/api/v1/cohorts')
|
||||
return response.data
|
||||
},
|
||||
|
||||
async get(id) {
|
||||
const response = await axios.get(`/api/v1/cohorts/${id}`)
|
||||
return response.data
|
||||
},
|
||||
|
||||
async create(data) {
|
||||
const response = await axios.post('/api/v1/cohorts', data)
|
||||
return response.data
|
||||
},
|
||||
|
||||
async update(id, data) {
|
||||
const response = await axios.patch(`/api/v1/cohorts/${id}`, data)
|
||||
return response.data
|
||||
},
|
||||
|
||||
async startSigning(id) {
|
||||
const response = await axios.post(`/api/v1/cohorts/${id}/start_signing`)
|
||||
return response.data
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `app/controllers/api/` - API Layer
|
||||
|
||||
```
|
||||
app/controllers/api/
|
||||
├── v1/
|
||||
│ ├── base_controller.rb # API authentication & versioning
|
||||
│ ├── cohorts_controller.rb # Cohort CRUD
|
||||
│ ├── enrollments_controller.rb # Enrollment management
|
||||
│ ├── students_controller.rb # Student portal API
|
||||
│ ├── sponsors_controller.rb # Sponsor portal API
|
||||
│ ├── webhooks_controller.rb # Webhook endpoints
|
||||
│ └── uploads_controller.rb # File uploads
|
||||
│
|
||||
└── v2/ # Future version
|
||||
└── base_controller.rb
|
||||
```
|
||||
|
||||
**API Base Controller**:
|
||||
```ruby
|
||||
# app/controllers/api/v1/base_controller.rb
|
||||
class Api::V1::BaseController < ActionController::API
|
||||
before_action :authenticate_api!
|
||||
rescue_from StandardError, with: :handle_error
|
||||
|
||||
private
|
||||
|
||||
def authenticate_api!
|
||||
token = request.headers['Authorization']&.split(' ')&.last
|
||||
@current_user = User.find_by(jwt_token: token)
|
||||
render json: { error: 'Unauthorized' }, status: :unauthorized unless @current_user
|
||||
end
|
||||
|
||||
def current_institution
|
||||
@current_user.institution
|
||||
end
|
||||
|
||||
def handle_error(exception)
|
||||
render json: { error: exception.message }, status: :internal_server_error
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `db/migrate/` - Database Migrations
|
||||
|
||||
```
|
||||
db/migrate/
|
||||
├── 20260114000001_create_flo_doc_tables.rb # Story 1.1
|
||||
├── 20260114000002_create_feature_flags.rb # Story 1.2
|
||||
├── 20260114000003_add_flo_doc_indexes.rb # Performance
|
||||
└── # Existing DocuSeal migrations
|
||||
```
|
||||
|
||||
**Migration Naming Convention**:
|
||||
- Timestamp format: `YYYYMMDDHHMMSS`
|
||||
- Descriptive name: `create_[table]_tables` or `add_[field]_to_[table]`
|
||||
- Group related changes
|
||||
|
||||
**Example Migration**:
|
||||
```ruby
|
||||
# db/migrate/20260114000001_create_flo_doc_tables.rb
|
||||
class CreateFloDocTables < ActiveRecord::Migration[7.0]
|
||||
def change
|
||||
create_table :institutions do |t|
|
||||
t.string :name, null: false
|
||||
t.string :email, null: false
|
||||
t.string :contact_person
|
||||
t.string :phone
|
||||
t.jsonb :settings, default: {}
|
||||
t.timestamps
|
||||
t.datetime :deleted_at
|
||||
end
|
||||
|
||||
add_index :institutions, :name, unique: true
|
||||
add_index :institutions, :email, unique: true
|
||||
|
||||
# ... more tables
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `spec/` - Test Suite
|
||||
|
||||
```
|
||||
spec/
|
||||
├── models/
|
||||
│ ├── institution_spec.rb
|
||||
│ ├── cohort_spec.rb
|
||||
│ └── cohort_enrollment_spec.rb
|
||||
│
|
||||
├── controllers/
|
||||
│ ├── tp/
|
||||
│ │ ├── cohorts_controller_spec.rb
|
||||
│ │ └── dashboard_controller_spec.rb
|
||||
│ ├── student/
|
||||
│ │ └── enrollment_controller_spec.rb
|
||||
│ └── api/
|
||||
│ └── v1/
|
||||
│ ├── cohorts_controller_spec.rb
|
||||
│ └── webhooks_controller_spec.rb
|
||||
│
|
||||
├── requests/
|
||||
│ └── api/
|
||||
│ └── v1/
|
||||
│ ├── cohorts_spec.rb
|
||||
│ └── webhooks_spec.rb
|
||||
│
|
||||
├── system/
|
||||
│ ├── tp_portal_spec.rb
|
||||
│ ├── student_portal_spec.rb
|
||||
│ └── sponsor_portal_spec.rb
|
||||
│
|
||||
├── migrations/
|
||||
│ └── create_flo_doc_tables_spec.rb
|
||||
│
|
||||
├── javascript/
|
||||
│ ├── tp/
|
||||
│ │ ├── views/
|
||||
│ │ │ └── CohortList.spec.js
|
||||
│ │ └── stores/
|
||||
│ │ └── cohortStore.spec.js
|
||||
│ └── student/
|
||||
│ └── stores/
|
||||
│ └── enrollmentStore.spec.js
|
||||
│
|
||||
├── factories/
|
||||
│ ├── institutions.rb
|
||||
│ ├── cohorts.rb
|
||||
│ └── cohort_enrollments.rb
|
||||
│
|
||||
└── support/
|
||||
├── database_cleaner.rb
|
||||
└── api_helpers.rb
|
||||
```
|
||||
|
||||
**Model Spec Example**:
|
||||
```ruby
|
||||
# spec/models/cohort_spec.rb
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe Cohort, type: :model do
|
||||
describe 'validations' do
|
||||
it { should validate_presence_of(:name) }
|
||||
it { should validate_presence_of(:program_type) }
|
||||
it { should validate_inclusion_of(:status).in_array(%w[draft active completed]) }
|
||||
end
|
||||
|
||||
describe 'associations' do
|
||||
it { should belong_to(:institution) }
|
||||
it { should belong_to(:template) }
|
||||
it { should have_many(:cohort_enrollments) }
|
||||
end
|
||||
|
||||
describe '#active?' do
|
||||
it 'returns true when status is active' do
|
||||
cohort = build(:cohort, status: 'active')
|
||||
expect(cohort.active?).to be true
|
||||
end
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
**Vue Component Spec Example**:
|
||||
```javascript
|
||||
// spec/javascript/tp/views/CohortList.spec.js
|
||||
import { mount, flushPromises } from '@vue/test-utils'
|
||||
import CohortList from '@/tp/views/CohortList.vue'
|
||||
import { createPinia, setActivePinia } from 'pinia'
|
||||
import { useCohortStore } from '@/tp/stores/cohortStore'
|
||||
|
||||
describe('CohortList', () => {
|
||||
beforeEach(() => {
|
||||
setActivePinia(createPinia())
|
||||
})
|
||||
|
||||
it('displays loading state', () => {
|
||||
const wrapper = mount(CohortList)
|
||||
expect(wrapper.text()).toContain('Loading')
|
||||
})
|
||||
|
||||
it('displays cohorts after loading', async () => {
|
||||
const wrapper = mount(CohortList)
|
||||
const store = useCohortStore()
|
||||
store.cohorts = [{ id: 1, name: 'Test Cohort' }]
|
||||
|
||||
await flushPromises()
|
||||
expect(wrapper.text()).toContain('Test Cohort')
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `lib/` - Utility Modules
|
||||
|
||||
```
|
||||
lib/
|
||||
├── # Business Logic Helpers
|
||||
├── submissions.rb # Submission workflows (existing)
|
||||
├── submitters.rb # Submitter logic (existing)
|
||||
├── cohorts.rb # Cohort workflows (NEW)
|
||||
├── enrollments.rb # Enrollment logic (NEW)
|
||||
│
|
||||
├── # PDF Processing
|
||||
├── pdf_utils.rb # PDF utilities
|
||||
├── pdfium.rb # PDF rendering
|
||||
│
|
||||
├── # Webhooks
|
||||
├── send_webhook_request.rb # Webhook delivery
|
||||
├── webhook_events.rb # Event definitions
|
||||
│
|
||||
├── # Token Management
|
||||
├── token_generator.rb # Secure token generation
|
||||
├── token_verifier.rb # Token validation
|
||||
│
|
||||
└── # Utilities
|
||||
├── load_active_storage_configs.rb
|
||||
└── feature_flag_loader.rb
|
||||
```
|
||||
|
||||
**Utility Module Example**:
|
||||
```ruby
|
||||
# lib/cohorts.rb
|
||||
module Cohorts
|
||||
module Workflow
|
||||
def self.advance_to_active(cohort)
|
||||
return false unless cohort.draft?
|
||||
|
||||
cohort.update!(status: 'active')
|
||||
CohortMailer.activated(cohort).deliver_later
|
||||
true
|
||||
end
|
||||
|
||||
def self.ready_for_sponsor?(cohort)
|
||||
cohort.students_completed_at.present? &&
|
||||
cohort.tp_signed_at.present? &&
|
||||
cohort.cohort_enrollments.students.any?
|
||||
end
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `config/` - Configuration
|
||||
|
||||
```
|
||||
config/
|
||||
├── application.rb # Rails config
|
||||
├── database.yml # Database config
|
||||
├── routes.rb # All routes
|
||||
├── storage.yml # Active Storage
|
||||
├── sidekiq.yml # Sidekiq config
|
||||
├── shakapacker.yml # Webpack config
|
||||
│
|
||||
├── # Initializers
|
||||
├── devise.rb # Devise config
|
||||
├── cors.rb # CORS settings
|
||||
├── active_storage.rb # Storage config
|
||||
│
|
||||
└── # Environments
|
||||
├── development.rb
|
||||
├── test.rb
|
||||
└── production.rb
|
||||
```
|
||||
|
||||
**Routes Configuration**:
|
||||
```ruby
|
||||
# config/routes.rb
|
||||
Rails.application.routes.draw do
|
||||
# Existing DocuSeal routes
|
||||
resources :templates
|
||||
resources :submissions
|
||||
|
||||
# TP Portal (authenticated)
|
||||
namespace :tp do
|
||||
root 'dashboard#index'
|
||||
resources :cohorts do
|
||||
member do
|
||||
post :start_signing
|
||||
post :finalize
|
||||
end
|
||||
resources :enrollments, only: [:index, :show]
|
||||
end
|
||||
resources :students, only: [:index, :show]
|
||||
resources :sponsors, only: [:index, :show]
|
||||
end
|
||||
|
||||
# Student Portal (ad-hoc tokens)
|
||||
scope module: :student do
|
||||
get '/enroll/:token', to: 'enrollment#show', as: :student_enroll
|
||||
post '/enroll/:token/submit', to: 'enrollment#submit'
|
||||
get '/status/:token', to: 'enrollment#status'
|
||||
end
|
||||
|
||||
# Sponsor Portal (ad-hoc tokens)
|
||||
scope module: :sponsor do
|
||||
get '/sponsor/:token', to: 'dashboard#show', as: :sponsor_dashboard
|
||||
post '/sponsor/:token/sign', to: 'signing#bulk_sign'
|
||||
end
|
||||
|
||||
# API v1
|
||||
namespace :api do
|
||||
namespace :v1 do
|
||||
resources :cohorts do
|
||||
member do
|
||||
post :start_signing
|
||||
end
|
||||
end
|
||||
resources :enrollments
|
||||
resources :students, only: [:show, :update]
|
||||
resources :sponsors, only: [:show]
|
||||
resources :webhooks, only: [:create]
|
||||
end
|
||||
end
|
||||
|
||||
# Devise (existing)
|
||||
devise_for :users
|
||||
end
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎯 File Naming Conventions
|
||||
|
||||
### Models
|
||||
- **Singular**: `cohort.rb`, not `cohorts.rb`
|
||||
- **Snake case**: `cohort_enrollment.rb`
|
||||
- **Table name**: Plural (Rails convention)
|
||||
|
||||
### Controllers
|
||||
- **Plural**: `cohorts_controller.rb`
|
||||
- **Namespaced**: `tp/cohorts_controller.rb`
|
||||
- **API versioned**: `api/v1/cohorts_controller.rb`
|
||||
|
||||
### Views
|
||||
- **Controller-based**: `app/views/tp/cohorts/`
|
||||
- **Template names**: `index.html.erb`, `show.html.erb`, `_form.html.erb`
|
||||
|
||||
### JavaScript
|
||||
- **Components**: PascalCase (`CohortCard.vue`)
|
||||
- **Stores**: camelCase (`cohortStore.js`)
|
||||
- **API**: PascalCase (`CohortsAPI`)
|
||||
- **Views**: PascalCase (`CohortList.vue`)
|
||||
|
||||
### Tests
|
||||
- **Models**: `model_name_spec.rb`
|
||||
- **Controllers**: `controller_name_spec.rb`
|
||||
- **Requests**: `request_name_spec.rb`
|
||||
- **System**: `feature_name_spec.rb`
|
||||
- **JavaScript**: `ComponentName.spec.js`
|
||||
|
||||
---
|
||||
|
||||
## 🔧 Configuration Files
|
||||
|
||||
### Gemfile
|
||||
```ruby
|
||||
# Core
|
||||
gem 'rails', '~> 7.0'
|
||||
|
||||
# Database
|
||||
gem 'pg', '~> 1.4'
|
||||
|
||||
# Authentication
|
||||
gem 'devise', '~> 4.8'
|
||||
gem 'devise-two-factor'
|
||||
gem 'cancancan', '~> 3.0'
|
||||
|
||||
# Background Jobs
|
||||
gem 'sidekiq', '~> 7.0'
|
||||
|
||||
# PDF
|
||||
gem 'hexapdf', '~> 0.15'
|
||||
|
||||
# API
|
||||
gem 'jbuilder'
|
||||
|
||||
# Security
|
||||
gem 'rack-attack'
|
||||
```
|
||||
|
||||
### package.json
|
||||
```json
|
||||
{
|
||||
"name": "flo-doc",
|
||||
"dependencies": {
|
||||
"vue": "^3.3.0",
|
||||
"pinia": "^2.1.0",
|
||||
"axios": "^1.6.0",
|
||||
"tailwindcss": "^3.4.17",
|
||||
"daisyui": "^3.9.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vue/test-utils": "^2.4.0",
|
||||
"vitest": "^1.0.0"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### docker-compose.yml
|
||||
```yaml
|
||||
version: '3.8'
|
||||
services:
|
||||
app:
|
||||
build: .
|
||||
ports:
|
||||
- "3000:3000"
|
||||
depends_on:
|
||||
- db
|
||||
- redis
|
||||
- minio
|
||||
environment:
|
||||
DATABASE_URL: postgresql://postgres:password@db:5432/flo_doc
|
||||
REDIS_URL: redis://redis:6379
|
||||
|
||||
db:
|
||||
image: postgres:14
|
||||
environment:
|
||||
POSTGRES_PASSWORD: password
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
|
||||
redis:
|
||||
image: redis:7-alpine
|
||||
|
||||
minio:
|
||||
image: minio/minio
|
||||
command: server /data
|
||||
ports:
|
||||
- "9000:9000"
|
||||
|
||||
mailhog:
|
||||
image: mailhog/mailhog
|
||||
ports:
|
||||
- "1025:1025"
|
||||
- "8025:8025"
|
||||
|
||||
volumes:
|
||||
postgres_data:
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📊 Source Tree Reference
|
||||
|
||||
For complete file tree with explanations, see: `docs/architecture/source-tree.md`
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Development Workflow
|
||||
|
||||
### Adding a New Model
|
||||
1. Create migration: `rails g migration CreateTableName`
|
||||
2. Create model: `rails g model TableName`
|
||||
3. Add associations & validations
|
||||
4. Write model specs
|
||||
5. Run migration: `rails db:migrate`
|
||||
|
||||
### Adding a New Controller
|
||||
1. Create controller: `rails g controller Namespace/Name`
|
||||
2. Add routes
|
||||
3. Add authentication/authorization
|
||||
4. Write controller specs
|
||||
5. Test manually
|
||||
|
||||
### Adding a New Vue Component
|
||||
1. Create component file in appropriate portal folder
|
||||
2. Add to view or register globally
|
||||
3. Write component spec
|
||||
4. Test in browser
|
||||
|
||||
### Running Tests
|
||||
```bash
|
||||
# Ruby tests
|
||||
bundle exec rspec spec/models/cohort_spec.rb
|
||||
|
||||
# JavaScript tests
|
||||
yarn test spec/javascript/tp/views/CohortList.spec.js
|
||||
|
||||
# All tests
|
||||
bundle exec rspec
|
||||
yarn test
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔍 Key Principles
|
||||
|
||||
1. **Convention Over Configuration**: Follow Rails and Vue conventions
|
||||
2. **Separation of Concerns**: Keep models, controllers, views separate
|
||||
3. **DRY**: Reuse code via concerns, mixins, and components
|
||||
4. **Testability**: Design for easy testing
|
||||
5. **Maintainability**: Clear structure, good naming, documentation
|
||||
|
||||
---
|
||||
|
||||
## 📚 Related Documents
|
||||
|
||||
- **Tech Stack**: `docs/architecture/tech-stack.md`
|
||||
- **Data Models**: `docs/architecture/data-models.md`
|
||||
- **Coding Standards**: `docs/architecture/coding-standards.md`
|
||||
- **Source Tree**: `docs/architecture/source-tree.md`
|
||||
|
||||
---
|
||||
|
||||
**Document Status**: ✅ Complete
|
||||
**Ready for**: Implementation
|
||||
@ -1,152 +0,0 @@
|
||||
# Rollback Strategy for Institution Management
|
||||
|
||||
## Overview
|
||||
|
||||
This document outlines the rollback procedure for the institution management migrations (20250103000001-20250103000006).
|
||||
|
||||
## Migration Sequence
|
||||
|
||||
1. `20250103000001` - Add institution_id to account_access (nullable)
|
||||
2. `20250103000002` - Create institutions table
|
||||
3. `20250103000003` - Create cohort_admin_invitations table
|
||||
4. `20250103000004` - Update account_access roles
|
||||
5. `20250103000005` - Backfill institution data (makes institution_id non-nullable)
|
||||
6. `20250103000006` - Create security_events table
|
||||
|
||||
## Rollback Procedure
|
||||
|
||||
### Step 1: Test Rollback on Staging/Development
|
||||
|
||||
```bash
|
||||
# Rollback last migration
|
||||
bin/rails db:rollback STEP=1
|
||||
|
||||
# Rollback all institution migrations
|
||||
bin/rails db:rollback STEP=6
|
||||
```
|
||||
|
||||
### Step 2: Production Rollback (If Needed)
|
||||
|
||||
**⚠️ CRITICAL: Always backup database before rollback**
|
||||
|
||||
```bash
|
||||
# 1. Create database backup
|
||||
pg_dump -Fc docuseal_production > docuseal_backup_$(date +%Y%m%d_%H%M%S).dump
|
||||
|
||||
# 2. Check current migration status
|
||||
bin/rails db:migrate:status
|
||||
|
||||
# 3. Rollback in reverse order
|
||||
bin/rails db:rollback STEP=1 # Rollback security_events
|
||||
bin/rails db:rollback STEP=1 # Rollback backfill
|
||||
bin/rails db:rollback STEP=1 # Rollback account_access roles
|
||||
bin/rails db:rollback STEP=1 # Rollback cohort_admin_invitations
|
||||
bin/rails db:rollback STEP=1 # Rollback institutions
|
||||
bin/rails db:rollback STEP=1 # Rollback institution_id addition
|
||||
|
||||
# 4. Verify rollback
|
||||
bin/rails db:migrate:status
|
||||
```
|
||||
|
||||
### Step 3: Data Safety Verification
|
||||
|
||||
**After rollback, verify:**
|
||||
|
||||
1. **Existing data intact:**
|
||||
```sql
|
||||
SELECT COUNT(*) FROM users;
|
||||
SELECT COUNT(*) FROM accounts;
|
||||
SELECT COUNT(*) FROM account_accesses;
|
||||
SELECT COUNT(*) FROM templates;
|
||||
SELECT COUNT(*) FROM submissions;
|
||||
```
|
||||
|
||||
2. **No orphaned records:**
|
||||
```sql
|
||||
-- Check for orphaned records
|
||||
SELECT * FROM account_accesses WHERE account_id NOT IN (SELECT id FROM accounts);
|
||||
SELECT * FROM users WHERE account_id NOT IN (SELECT id FROM accounts);
|
||||
```
|
||||
|
||||
3. **Existing functionality works:**
|
||||
- User login
|
||||
- Template creation
|
||||
- Submission workflows
|
||||
- API access
|
||||
|
||||
## Rollback Risks and Mitigations
|
||||
|
||||
### Risk 1: Data Loss
|
||||
**Impact:** High
|
||||
**Mitigation:**
|
||||
- Always backup before rollback
|
||||
- Test rollback on staging first
|
||||
- Verify data integrity after rollback
|
||||
|
||||
### Risk 2: Downtime
|
||||
**Impact:** Medium
|
||||
**Mitigation:**
|
||||
- Schedule rollback during maintenance window
|
||||
- Have rollback plan ready
|
||||
- Test procedure beforehand
|
||||
|
||||
### Risk 3: Application Errors
|
||||
**Impact:** High
|
||||
**Mitigation:**
|
||||
- Keep application version compatible with database schema
|
||||
- Have emergency rollback to previous app version ready
|
||||
- Monitor error logs during rollback
|
||||
|
||||
## Emergency Rollback
|
||||
|
||||
If critical issues arise during deployment:
|
||||
|
||||
1. **Immediate rollback:**
|
||||
```bash
|
||||
git revert HEAD
|
||||
bin/rails db:rollback STEP=6
|
||||
```
|
||||
|
||||
2. **Restore from backup if needed:**
|
||||
```bash
|
||||
pg_restore -d docuseal_production docuseal_backup_YYYYMMDD_HHMMSS.dump
|
||||
```
|
||||
|
||||
3. **Notify stakeholders** of rollback and reason
|
||||
|
||||
## Post-Rollback Verification
|
||||
|
||||
After rollback, verify these critical paths:
|
||||
|
||||
- [ ] User authentication works
|
||||
- [ ] Existing templates accessible
|
||||
- [ ] Submissions can be created
|
||||
- [ ] API endpoints return correct data
|
||||
- [ ] No database constraint violations
|
||||
- [ ] Email notifications work
|
||||
- [ ] Webhook delivery works
|
||||
|
||||
## Rollback Decision Matrix
|
||||
|
||||
**Rollback if:**
|
||||
- Data corruption detected
|
||||
- Critical security vulnerabilities found
|
||||
- Major performance degradation (>50%)
|
||||
- Application crashes on startup
|
||||
- Cannot fix issues within 2 hours
|
||||
|
||||
**Do NOT rollback if:**
|
||||
- Minor bugs that can be hotfixed
|
||||
- Performance issues within acceptable range (<10%)
|
||||
- UI/UX issues only
|
||||
- Non-critical feature failures
|
||||
|
||||
## Contact Information
|
||||
|
||||
**Emergency Contacts:**
|
||||
- Database Administrator: [To be filled]
|
||||
- DevOps Engineer: [To be filled]
|
||||
- Security Team: [To be filled]
|
||||
|
||||
**Rollback Window:** [To be scheduled]
|
||||
**Estimated Downtime:** 15-30 minutes
|
||||
@ -1,44 +0,0 @@
|
||||
# Security Integration
|
||||
|
||||
## Existing Security Measures
|
||||
|
||||
**Authentication:** Devise with database_authenticatable, 2FA support, JWT tokens
|
||||
**Authorization:** Cancancan with `Ability` class, role-based via `AccountAccess`
|
||||
**Data Protection:** Encrypted fields, secure file storage, CSRF protection
|
||||
**Security Tools:** Devise security extensions, input validation, secure headers
|
||||
|
||||
## Enhancement Security Requirements
|
||||
|
||||
**New Security Measures:**
|
||||
- **Token-based Sponsor Access:** Unique tokens for sponsor portal (not JWT)
|
||||
- **Institution Isolation:** Ensure strict data separation between institutions
|
||||
- **Role Validation:** Portal-specific role checks at controller level
|
||||
- **Document Access Control:** Verify enrollment ownership before document access
|
||||
- **Bulk Operation Limits:** Rate limiting for sponsor bulk signing
|
||||
|
||||
**Integration Points:**
|
||||
- **Authentication:** Extend existing Devise setup with cohort-specific roles
|
||||
- **Authorization:** Add cohort permissions to existing Cancancan abilities
|
||||
- **Data Protection:** Apply existing encryption to new sensitive fields
|
||||
- **Session Management:** Use existing session handling for portal access
|
||||
|
||||
**Compliance Requirements:**
|
||||
- **South African Regulations:** Electronic signature compliance (existing HexaPDF signatures)
|
||||
- **Data Privacy:** POPIA compliance for student personal data (existing GDPR patterns)
|
||||
- **Audit Trail:** Document verification actions logged (extends existing audit capabilities)
|
||||
|
||||
## Security Testing
|
||||
|
||||
**Existing Security Tests:** Devise security tests, API authentication tests
|
||||
**New Security Test Requirements:**
|
||||
- **Portal Access Control:** Test role-based portal access
|
||||
- **Institution Isolation:** Test cross-institution data access prevention
|
||||
- **Token Security:** Test sponsor token generation, expiration, reuse prevention
|
||||
- **Bulk Operation Security:** Test rate limiting and abuse prevention
|
||||
|
||||
**Penetration Testing:**
|
||||
- **Scope:** New cohort endpoints and portal authentication
|
||||
- **Focus:** Token-based sponsor access, institution isolation, bulk operations
|
||||
- **Tools:** Existing security scanning tools, OWASP ZAP for API testing
|
||||
|
||||
---
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,184 +0,0 @@
|
||||
# Source Tree
|
||||
|
||||
## Existing Project Structure
|
||||
|
||||
```
|
||||
floDoc-v3/
|
||||
├── app/
|
||||
│ ├── controllers/
|
||||
│ │ ├── api/ # RESTful API controllers
|
||||
│ │ │ ├── api_base_controller.rb
|
||||
│ │ │ ├── submissions_controller.rb
|
||||
│ │ │ ├── templates_controller.rb
|
||||
│ │ │ └── [15+ existing controllers]
|
||||
│ │ ├── [30+ existing controllers] # Dashboard, settings, etc.
|
||||
│ ├── models/
|
||||
│ │ ├── account.rb # Multi-tenancy root
|
||||
│ │ ├── user.rb # Devise auth + 2FA
|
||||
│ │ ├── template.rb # Document templates
|
||||
│ │ ├── submission.rb # Document workflows
|
||||
│ │ ├── submitter.rb # Signers/participants
|
||||
│ │ ├── account_access.rb # User permissions
|
||||
│ │ └── [15+ supporting models]
|
||||
│ ├── jobs/
|
||||
│ │ ├── process_submitter_completion_job.rb
|
||||
│ │ ├── send_submission_completed_webhook_request_job.rb
|
||||
│ │ └── [15+ existing jobs]
|
||||
│ ├── mailers/
|
||||
│ │ ├── application_mailer.rb
|
||||
│ │ ├── submitter_mailer.rb
|
||||
│ │ └── user_mailer.rb
|
||||
│ ├── javascript/
|
||||
│ │ ├── application.js # Vue 3 entry point
|
||||
│ │ ├── template_builder/ # PDF form builder (15+ Vue components)
|
||||
│ │ ├── submission_form/ # Signing interface (10+ Vue components)
|
||||
│ │ └── elements/ # Web Components (40+ custom elements)
|
||||
│ └── views/
|
||||
│ ├── mailers/ # Email templates
|
||||
│ └── shared/ # Common partials
|
||||
├── lib/
|
||||
│ ├── submissions/ # Core business logic
|
||||
│ │ ├── generate_result_attachments.rb
|
||||
│ │ ├── generate_combined_attachment.rb
|
||||
│ │ └── [10+ submission modules]
|
||||
│ ├── submitters/ # Submitter logic
|
||||
│ ├── templates/ # Template processing
|
||||
│ ├── pdf_utils.rb # HexaPDF wrapper
|
||||
│ ├── docuseal.rb # Global config
|
||||
│ ├── ability.rb # CanCanCan rules
|
||||
│ └── send_webhook_request.rb # Webhook delivery
|
||||
├── config/
|
||||
│ ├── routes.rb # All routes (200+ lines)
|
||||
│ ├── database.yml # DB config
|
||||
│ ├── storage.yml # Active Storage config
|
||||
│ ├── sidekiq.yml # Background job config
|
||||
│ └── shakapacker.yml # Webpack config
|
||||
├── db/
|
||||
│ ├── migrate/ # Existing migrations
|
||||
│ └── schema.rb # Current schema
|
||||
└── docs/
|
||||
├── prd.md # Product requirements
|
||||
└── architecture.md # This document
|
||||
```
|
||||
|
||||
## New File Organization
|
||||
|
||||
```
|
||||
floDoc-v3/
|
||||
├── app/
|
||||
│ ├── controllers/
|
||||
│ │ ├── api/
|
||||
│ │ │ ├── v1/
|
||||
│ │ │ │ ├── cohorts_controller.rb # NEW: Cohort API endpoints
|
||||
│ │ │ │ ├── enrollments_controller.rb # NEW: Enrollment API endpoints
|
||||
│ │ │ │ └── sponsors_controller.rb # NEW: Sponsor API endpoints
|
||||
│ │ ├── cohorts/ # NEW: Web controllers
|
||||
│ │ │ ├── admin_controller.rb # Admin portal web endpoints
|
||||
│ │ │ ├── student_controller.rb # Student portal web endpoints
|
||||
│ │ │ └── sponsor_controller.rb # Sponsor portal web endpoints
|
||||
│ ├── models/
|
||||
│ │ ├── cohort.rb # NEW: Cohort model
|
||||
│ │ ├── cohort_enrollment.rb # NEW: Enrollment model
|
||||
│ │ ├── institution.rb # NEW: Institution model
|
||||
│ │ ├── sponsor.rb # NEW: Sponsor model
|
||||
│ │ └── document_verification.rb # NEW: Verification model
|
||||
│ ├── jobs/
|
||||
│ │ ├── cohort_reminder_job.rb # NEW: Cohort reminders
|
||||
│ │ ├── cohort_completion_job.rb # NEW: Workflow completion
|
||||
│ │ └── excel_export_job.rb # NEW: FR23 Excel export
|
||||
│ ├── mailers/
|
||||
│ │ ├── cohort_mailer.rb # NEW: Cohort notifications
|
||||
│ │ └── sponsor_mailer.rb # NEW: Sponsor notifications
|
||||
│ ├── javascript/
|
||||
│ │ ├── cohorts/ # NEW: Cohort management
|
||||
│ │ │ ├── admin/ # Admin portal Vue app
|
||||
│ │ │ │ ├── AdminPortal.vue
|
||||
│ │ │ │ ├── CohortDashboard.vue
|
||||
│ │ │ │ ├── CohortWizard.vue
|
||||
│ │ │ │ ├── VerificationInterface.vue
|
||||
│ │ │ │ ├── SponsorCoordinator.vue
|
||||
│ │ │ │ ├── AnalyticsView.vue
|
||||
│ │ │ │ └── ExcelExport.vue
|
||||
│ │ │ ├── student/ # Student portal Vue app
|
||||
│ │ │ │ ├── StudentPortal.vue
|
||||
│ │ │ │ ├── CohortWelcome.vue
|
||||
│ │ │ │ ├── DocumentUpload.vue
|
||||
│ │ │ │ ├── AgreementForm.vue
|
||||
│ │ │ │ ├── StatusDashboard.vue
|
||||
│ │ │ │ └── ResubmissionFlow.vue
|
||||
│ │ │ └── sponsor/ # Sponsor portal Vue app
|
||||
│ │ │ ├── SponsorPortal.vue
|
||||
│ │ │ ├── SponsorDashboard.vue
|
||||
│ │ │ ├── StudentReview.vue
|
||||
│ │ │ ├── BulkSigning.vue
|
||||
│ │ │ └── CohortFinalization.vue
|
||||
│ │ └── shared/ # NEW: Shared portal components
|
||||
│ │ ├── PortalNavigation.vue
|
||||
│ │ ├── RoleSwitcher.vue
|
||||
│ │ └── PortalNotifications.vue
|
||||
│ └── views/
|
||||
│ ├── cohorts/
|
||||
│ │ ├── admin/
|
||||
│ │ │ ├── index.html.erb
|
||||
│ │ │ └── show.html.erb
|
||||
│ │ ├── student/
|
||||
│ │ │ ├── index.html.erb
|
||||
│ │ │ └── show.html.erb
|
||||
│ │ └── sponsor/
|
||||
│ │ ├── index.html.erb
|
||||
│ │ └── show.html.erb
|
||||
│ └── mailers/
|
||||
│ ├── cohort_mailer/
|
||||
│ │ ├── cohort_created.html.erb
|
||||
│ │ ├── student_invite.html.erb
|
||||
│ │ └── sponsor_access.html.erb
|
||||
│ └── sponsor_mailer/
|
||||
│ └── cohort_ready.html.erb
|
||||
├── lib/
|
||||
│ ├── cohorts/ # NEW: Cohort business logic
|
||||
│ │ ├── cohort_workflow_service.rb
|
||||
│ │ ├── enrollment_service.rb
|
||||
│ │ ├── verification_service.rb
|
||||
│ │ ├── sponsor_service.rb
|
||||
│ │ ├── cohort_state_engine.rb
|
||||
│ │ ├── enrollment_validator.rb
|
||||
│ │ ├── sponsor_access_manager.rb
|
||||
│ │ └── excel_export_service.rb
|
||||
│ └── templates/
|
||||
│ └── cohort_template_processor.rb # NEW: Cohort template extensions
|
||||
├── db/
|
||||
│ ├── migrate/
|
||||
│ │ ├── 20250102000001_create_institutions.rb
|
||||
│ │ ├── 20250102000002_create_cohorts.rb
|
||||
│ │ ├── 20250102000003_create_cohort_enrollments.rb
|
||||
│ │ ├── 20250102000004_create_sponsors.rb
|
||||
│ │ └── 20250102000005_create_document_verifications.rb
|
||||
│ └── schema.rb # UPDATED: New tables added
|
||||
├── config/
|
||||
│ └── routes.rb # UPDATED: New cohort routes
|
||||
└── docs/
|
||||
├── architecture.md # This document
|
||||
└── cohort-workflows.md # NEW: Workflow documentation
|
||||
```
|
||||
|
||||
## Integration Guidelines
|
||||
|
||||
**File Naming:**
|
||||
- **Models:** `cohort.rb`, `cohort_enrollment.rb` (snake_case, singular)
|
||||
- **Controllers:** `cohorts_controller.rb`, `admin_controller.rb` (plural for resources)
|
||||
- **Vue Components:** `CohortDashboard.vue`, `StudentPortal.vue` (PascalCase)
|
||||
- **Services:** `cohort_workflow_service.rb` (snake_case, descriptive)
|
||||
- **Jobs:** `cohort_reminder_job.rb` (snake_case, _job suffix)
|
||||
|
||||
**Folder Organization:**
|
||||
- **API Controllers:** `app/controllers/api/v1/cohorts/` (versioned, resource-based)
|
||||
- **Web Controllers:** `app/controllers/cohorts/` (portal-specific)
|
||||
- **Vue Apps:** `app/javascript/cohorts/{admin,student,sponsor}/` (portal separation)
|
||||
- **Services:** `lib/cohorts/` (business logic separation)
|
||||
|
||||
**Import/Export Patterns:**
|
||||
- **Ruby:** Follow existing patterns (service objects, concerns, modules)
|
||||
- **Vue:** Use ES6 imports, Composition API, existing API client patterns
|
||||
- **API:** Consistent JSON response format matching existing endpoints
|
||||
|
||||
---
|
||||
@ -1,16 +0,0 @@
|
||||
# Table of Contents
|
||||
1. [Introduction](#introduction)
|
||||
2. [Enhancement Scope and Integration Strategy](#enhancement-scope-and-integration-strategy)
|
||||
3. [Tech Stack](#tech-stack)
|
||||
4. [Data Models and Schema Changes](#data-models-and-schema-changes)
|
||||
5. [Component Architecture](#component-architecture)
|
||||
6. [API Design and Integration](#api-design-and-integration)
|
||||
7. [Source Tree](#source-tree)
|
||||
8. [Infrastructure and Deployment Integration](#infrastructure-and-deployment-integration)
|
||||
9. [Coding Standards](#coding-standards)
|
||||
10. [Testing Strategy](#testing-strategy)
|
||||
11. [Security Integration](#security-integration)
|
||||
12. [Checklist Results Report](#checklist-results-report)
|
||||
13. [Next Steps](#next-steps)
|
||||
|
||||
---
|
||||
@ -1,30 +1,348 @@
|
||||
# Tech Stack
|
||||
# Tech Stack - FloDoc Architecture
|
||||
|
||||
## Existing Technology Stack
|
||||
**Document**: Tech Stack Specification
|
||||
**Version**: 1.0
|
||||
**Last Updated**: 2026-01-14
|
||||
|
||||
| Category | Current Technology | Version | Usage in Enhancement | Notes |
|
||||
|----------|-------------------|---------|---------------------|--------|
|
||||
| **Backend Language** | Ruby | 3.4.2 | ✅ Core backend logic | Existing version maintained |
|
||||
| **Web Framework** | Rails | 7.x | ✅ Controllers, Models, Views | Existing patterns followed |
|
||||
| **Frontend Framework** | Vue.js | 3.3.2 | ✅ All three portals | Composition API for new components |
|
||||
| **CSS Framework** | TailwindCSS | 3.4.17 | ✅ Custom portal styling | Replacing DaisyUI for portals |
|
||||
| **UI Components** | DaisyUI | 3.9.4 | ⚠️ Legacy DocuSeal UI only | Not used in new portals |
|
||||
| **Build Tool** | Shakapacker | 8.0 | ✅ Asset compilation | Existing configuration maintained |
|
||||
| **Database** | PostgreSQL/MySQL/SQLite | Latest | ✅ New cohort tables | DATABASE_URL configuration |
|
||||
| **Background Jobs** | Sidekiq | Latest | ✅ Email notifications, reminders | Existing queue system |
|
||||
| **PDF Processing** | HexaPDF | Latest | ✅ Document generation/signing | Core DocuSeal capability |
|
||||
| **PDF Rendering** | PDFium | Latest | ✅ Document preview | Existing rendering engine |
|
||||
| **Authentication** | Devise | Latest | ✅ User auth + 2FA | Extended for new roles |
|
||||
| **Authorization** | Cancancan | Latest | ✅ Role-based access | Extended for cohort permissions |
|
||||
| **Storage** | Active Storage | Latest | ✅ Document storage | Existing multi-backend support |
|
||||
| **Job Queue** | Redis | Latest | ✅ Sidekiq backend | Required dependency |
|
||||
| **API Auth** | JWT | Latest | ✅ API token authentication | Existing mechanism |
|
||||
| **Email** | SMTP | Latest | ✅ Notifications | Existing infrastructure |
|
||||
---
|
||||
|
||||
## 🎯 Technology Overview
|
||||
|
||||
FloDoc is a brownfield Rails 7 application enhanced with Vue.js 3 frontend. The stack is chosen for stability, developer productivity, and seamless integration with existing DocuSeal functionality.
|
||||
|
||||
---
|
||||
|
||||
## 🔧 Backend Stack
|
||||
|
||||
### Ruby on Rails
|
||||
- **Version**: 7.x
|
||||
- **Purpose**: Core application framework
|
||||
- **Key Features**:
|
||||
- MVC architecture
|
||||
- Active Record ORM
|
||||
- Action Mailer for emails
|
||||
- Active Job for background processing
|
||||
- Built-in security features
|
||||
|
||||
### Database
|
||||
- **Primary**: PostgreSQL 14+
|
||||
- **Alternative**: MySQL 8+ or SQLite (for development)
|
||||
- **Configuration**: `DATABASE_URL` environment variable
|
||||
- **Key Tables**:
|
||||
- `institutions` - Single training institution
|
||||
- `cohorts` - Training program cohorts
|
||||
- `cohort_enrollments` - Student enrollments
|
||||
- `templates` (existing) - Document templates
|
||||
- `submissions` (existing) - Document workflows
|
||||
- `submitters` (existing) - Signers/participants
|
||||
|
||||
### Background Jobs
|
||||
- **Framework**: Sidekiq
|
||||
- **Backend**: Redis
|
||||
- **Queues**:
|
||||
- `default` - General tasks
|
||||
- `mailers` - Email delivery
|
||||
- `webhooks` - Webhook delivery
|
||||
- `pdf` - PDF generation
|
||||
- **Configuration**: `REDIS_URL` environment variable
|
||||
|
||||
### Authentication
|
||||
- **Gem**: Devise 4.x
|
||||
- **Modules**:
|
||||
- `database_authenticatable` - Password auth
|
||||
- `registerable` - User registration
|
||||
- `recoverable` - Password reset
|
||||
- `rememberable` - Remember me
|
||||
- `validatable` - Validations
|
||||
- `omniauthable` - OAuth support
|
||||
- `two_factor_authenticatable` - 2FA
|
||||
- **API Auth**: JWT tokens (custom implementation)
|
||||
|
||||
## New Technology Additions
|
||||
### Authorization
|
||||
- **Gem**: Cancancan 3.x
|
||||
- **Ability Class**: `app/models/ability.rb`
|
||||
- **Roles**: TP (admin), Student, Sponsor
|
||||
- **Access Control**: Role-based via `AccountAccess` model
|
||||
|
||||
**No new technologies required.** The enhancement leverages existing DocuSeal technology stack entirely. All new functionality will be implemented using current frameworks and libraries.
|
||||
### PDF Processing
|
||||
- **Generation**: HexaPDF 0.15+
|
||||
- PDF creation from templates
|
||||
- Form field rendering
|
||||
- Digital signatures
|
||||
- Signature verification
|
||||
- **Rendering**: PDFium
|
||||
- PDF preview
|
||||
- Document manipulation
|
||||
- Multi-page handling
|
||||
|
||||
**Rationale:** Brownfield enhancement should minimize technology changes to reduce risk and maintain compatibility. The existing stack provides all necessary capabilities for the 3-portal cohort management system.
|
||||
### Email Delivery
|
||||
- **SMTP**: Standard Rails Action Mailer
|
||||
- **Templates**: ERB in `app/views/mailers/`
|
||||
- **Async**: Sidekiq `mailers` queue
|
||||
- **Tracking**: `email_events` table
|
||||
|
||||
### Webhooks
|
||||
- **Delivery**: Custom `WebhookDeliveryJob`
|
||||
- **Events**: submission.created, submission.completed, etc.
|
||||
- **Retry**: Exponential backoff
|
||||
- **Tracking**: `webhook_events` table
|
||||
|
||||
---
|
||||
|
||||
## 🎨 Frontend Stack
|
||||
|
||||
### Vue.js
|
||||
- **Version**: 3.x with Composition API
|
||||
- **Build Tool**: Shakapacker 8.x (Webpack wrapper)
|
||||
- **Entry Point**: `app/javascript/application.js`
|
||||
- **Key Libraries**:
|
||||
- Vue Router (if needed for SPA sections)
|
||||
- Pinia for state management
|
||||
- Axios for HTTP requests
|
||||
|
||||
### State Management
|
||||
- **Framework**: Pinia 2.x
|
||||
- **Stores**:
|
||||
- `cohortStore` - Cohort management state
|
||||
- `submissionStore` - Submission workflow state
|
||||
- `authStore` - Authentication state
|
||||
- `uiStore` - UI state (modals, notifications)
|
||||
|
||||
### Styling
|
||||
- **Framework**: TailwindCSS 3.4.17
|
||||
- **Components**: DaisyUI 3.9.4
|
||||
- **Customization**: `tailwind.config.js`
|
||||
- **Design System**: Custom FloDoc branding (not DaisyUI defaults)
|
||||
|
||||
### Build & Development
|
||||
- **Tool**: Shakapacker 8.x
|
||||
- **Node**: 18+ recommended
|
||||
- **Yarn**: Package management
|
||||
- **Hot Reload**: Via Shakapacker dev server
|
||||
|
||||
### API Integration
|
||||
- **HTTP Client**: Axios or Fetch API
|
||||
- **Base URL**: `/api/v1/`
|
||||
- **Auth**: Bearer tokens in headers
|
||||
- **Response Format**: JSON
|
||||
|
||||
---
|
||||
|
||||
## 🔒 Security Stack
|
||||
|
||||
### Authentication
|
||||
- **Web Sessions**: Devise + Rails session store
|
||||
- **API Access**: JWT tokens
|
||||
- **Ad-hoc Links**: Short-lived tokens with email verification
|
||||
- **2FA**: Devise-two-factor for TP users
|
||||
|
||||
### Authorization
|
||||
- **Backend**: Cancancan abilities
|
||||
- **Frontend**: Route guards + UI visibility checks
|
||||
- **API**: Token-based scope validation
|
||||
|
||||
### Data Protection
|
||||
- **Encryption at Rest**:
|
||||
- Sensitive fields (emails) encrypted if policy requires
|
||||
- Database-level encryption available
|
||||
- **Input Validation**: Rails strong parameters + model validations
|
||||
- **XSS Prevention**: Vue template auto-escaping
|
||||
- **SQL Injection**: ActiveRecord parameterized queries
|
||||
|
||||
### Web Security
|
||||
- **CSRF**: Rails built-in protection
|
||||
- **CORS**: Configured for API endpoints
|
||||
- **HTTPS**: Enforced in production
|
||||
- **Security Headers**: Via Rails default + custom
|
||||
|
||||
---
|
||||
|
||||
## 🧪 Testing Stack
|
||||
|
||||
### Ruby Tests
|
||||
- **Framework**: RSpec 3.x
|
||||
- **Coverage**: SimpleCov
|
||||
- **Types**:
|
||||
- Model specs: `spec/models/`
|
||||
- Request specs: `spec/requests/`
|
||||
- System specs: `spec/system/`
|
||||
- Migration specs: `spec/migrations/`
|
||||
- Job specs: `spec/jobs/`
|
||||
|
||||
### JavaScript/Vue Tests
|
||||
- **Framework**: Vue Test Utils + Vitest/Jest
|
||||
- **Coverage**: Component unit tests
|
||||
- **Location**: `spec/javascript/`
|
||||
|
||||
### E2E Tests
|
||||
- **Framework**: Playwright or Cypress
|
||||
- **Scope**: Critical user journeys
|
||||
- **Scenarios**: 3-portal workflow
|
||||
|
||||
---
|
||||
|
||||
## 🐳 Infrastructure (Local Docker MVP)
|
||||
|
||||
### Docker Compose
|
||||
- **Services**:
|
||||
- `app` - Rails application
|
||||
- `db` - PostgreSQL
|
||||
- `redis` - Sidekiq backend
|
||||
- `minio` - S3-compatible storage
|
||||
- `mailhog` - Email testing
|
||||
- **Configuration**: `docker-compose.yml`
|
||||
- **Volumes**: Persistent data storage
|
||||
|
||||
### Storage
|
||||
- **Backend**: Active Storage
|
||||
- **Local**: Minio (S3-compatible)
|
||||
- **Configuration**: `config/storage.yml`
|
||||
- **Environment**: `AWS_*` variables for Minio
|
||||
|
||||
### Development Tools
|
||||
- **Linting**: RuboCop (Ruby), ESLint (JS)
|
||||
- **Formatting**: StandardRB, Prettier
|
||||
- **Debugging**: Byebug, Pry
|
||||
- **Console**: Rails console
|
||||
|
||||
---
|
||||
|
||||
## 📦 Dependencies Summary
|
||||
|
||||
### Gemfile (Backend)
|
||||
```ruby
|
||||
# Core
|
||||
gem 'rails', '~> 7.0'
|
||||
gem 'pg', '~> 1.4' # or 'mysql2', 'sqlite3'
|
||||
|
||||
# Authentication & Authorization
|
||||
gem 'devise', '~> 4.8'
|
||||
gem 'devise-two-factor'
|
||||
gem 'cancancan', '~> 3.0'
|
||||
gem 'jwt'
|
||||
|
||||
# Background Jobs
|
||||
gem 'sidekiq', '~> 7.0'
|
||||
gem 'redis', '~> 5.0'
|
||||
|
||||
# PDF Processing
|
||||
gem 'hexapdf', '~> 0.15'
|
||||
# PDFium via system library
|
||||
|
||||
# API
|
||||
gem 'jbuilder', '~> 2.11'
|
||||
|
||||
# Security
|
||||
gem 'rack-attack'
|
||||
|
||||
# File Uploads
|
||||
gem 'activestorage'
|
||||
```
|
||||
|
||||
### package.json (Frontend)
|
||||
```json
|
||||
{
|
||||
"dependencies": {
|
||||
"vue": "^3.3.0",
|
||||
"pinia": "^2.1.0",
|
||||
"axios": "^1.6.0",
|
||||
"tailwindcss": "^3.4.17",
|
||||
"daisyui": "^3.9.4"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔌 Environment Variables
|
||||
|
||||
### Required
|
||||
```bash
|
||||
# Database
|
||||
DATABASE_URL=postgresql://user:pass@localhost:5432/flo_doc
|
||||
|
||||
# Secrets
|
||||
SECRET_KEY_BASE=your_rails_secret
|
||||
|
||||
# Redis (Sidekiq)
|
||||
REDIS_URL=redis://localhost:6379
|
||||
|
||||
# Storage (Minio)
|
||||
AWS_ACCESS_KEY_ID=minioadmin
|
||||
AWS_SECRET_ACCESS_KEY=minioadmin
|
||||
AWS_REGION=us-east-1
|
||||
AWS_ENDPOINT_URL=http://localhost:9000
|
||||
AWS_BUCKET_NAME=flo-doc
|
||||
|
||||
# Email (Development)
|
||||
SMTP_ADDRESS=localhost
|
||||
SMTP_PORT=1025 # MailHog
|
||||
```
|
||||
|
||||
### Optional
|
||||
```bash
|
||||
# Feature Flags
|
||||
FLODOC_MULTITENANT=false
|
||||
FLODOC_PRO=false
|
||||
|
||||
# Webhooks
|
||||
WEBHOOK_SECRET=your_webhook_secret
|
||||
|
||||
# Security
|
||||
ENCRYPT_EMAILS=false
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Technology Justifications
|
||||
|
||||
### Why Rails 7?
|
||||
- **Brownfield**: DocuSeal is already Rails
|
||||
- **Convention**: Rapid development with established patterns
|
||||
- **Security**: Built-in protections
|
||||
- **Ecosystem**: Rich gem ecosystem
|
||||
|
||||
### Why Vue 3 + Pinia?
|
||||
- **Composition API**: Better TypeScript support
|
||||
- **Performance**: Virtual DOM optimization
|
||||
- **Ecosystem**: Strong community support
|
||||
- **Integration**: Works well with Rails via Shakapacker
|
||||
|
||||
### Why PostgreSQL?
|
||||
- **JSONB**: Perfect for flexible metadata (cohorts, uploads)
|
||||
- **Reliability**: Production-ready
|
||||
- **Performance**: Excellent for relational data
|
||||
- **Extensions**: Full-text search if needed
|
||||
|
||||
### Why Docker Compose?
|
||||
- **Consistency**: Same environment for all developers
|
||||
- **Simplicity**: Single command setup
|
||||
- **Isolation**: Services don't conflict
|
||||
- **MVP**: No production infrastructure needed
|
||||
|
||||
---
|
||||
|
||||
## 📊 Performance Targets
|
||||
|
||||
| Metric | Baseline (DocuSeal) | FloDoc Target | Max Degradation |
|
||||
|--------|---------------------|---------------|-----------------|
|
||||
| Page Load | 1.0s | 1.2s | +20% |
|
||||
| PDF Generation | 2.0s | 2.4s | +20% |
|
||||
| DB Query (complex) | 100ms | 120ms | +20% |
|
||||
| Sidekiq Job | 500ms | 600ms | +20% |
|
||||
|
||||
**NFR1**: All performance metrics must stay within 20% of baseline
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Next Steps
|
||||
|
||||
1. **Setup Local Environment** → Follow `docs/architecture/infrastructure.md`
|
||||
2. **Review Data Models** → Study `docs/architecture/data-models.md`
|
||||
3. **Read Coding Standards** → Follow `docs/architecture/coding-standards.md`
|
||||
4. **Start Story 1.1** → Database schema extension
|
||||
|
||||
---
|
||||
|
||||
**Document Status**: ✅ Complete
|
||||
**Review Date**: After Phase 1 Implementation
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Loading…
Reference in new issue