Skip to content

Commit bf1ef4c

Browse files
committed
Implemented Thesis defense list (draft).
1 parent 4784aba commit bf1ef4c

26 files changed

Lines changed: 1030 additions & 2 deletions

AGENTS.md

Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
# CAPS Application Structure
2+
3+
This document describes the structure of the CAPS (Curriculum and Proposal System) application, which is a CakePHP-based system for managing study proposals and form submissions.
4+
5+
## Overview
6+
7+
CAPS is a web application that allows students to submit study proposals and for secretaries to evaluate them. It also handles submission of generic forms. The application has two main interfaces:
8+
- User-facing interface built with React
9+
- Administration interface built with CakePHP's classical MVC pattern
10+
11+
## Project Structure
12+
13+
```
14+
.
15+
├── backend/ # CakePHP backend application
16+
│ ├── src/ # Source code
17+
│ │ ├── Controller/ # MVC Controllers
18+
│ │ ├── Model/ # MVC Models (Entities and Tables)
19+
│ │ ├── View/ # MVC Views
20+
│ │ ├── Form/ # Form classes
21+
│ │ ├── Authentication/ # Authentication components
22+
│ │ ├── Command/ # CLI commands
23+
│ │ └── Utility/ # Utility classes
24+
│ ├── templates/ # View templates
25+
│ ├── config/ # Configuration files
26+
│ ├── webroot/ # Web root directory
27+
│ └── tests/ # Test files
28+
├── frontend/ # React frontend application
29+
│ ├── src/ # Source code
30+
│ │ ├── components/ # React components
31+
│ │ ├── controllers/ # Controller logic
32+
│ │ ├── modules/ # Utility modules
33+
│ │ └── ...
34+
│ ├── scss/ # Stylesheets
35+
│ └── ...
36+
├── docker/ # Docker configuration
37+
├── scripts/ # Utility scripts
38+
└── docs/ # Documentation
39+
40+
## Backend Components
41+
42+
### Controllers
43+
The backend uses CakePHP's MVC pattern with controllers handling HTTP requests and responses. Key controllers include:
44+
- Api/v1/ - API controllers for the APIs (used by React frontend)
45+
- ProposalsController.php - Controller for study proposal management
46+
- FormsController.php - Controller for form submissions
47+
- UsersController.php - Controller for user management
48+
- DegreesController.php - Controller for degree management
49+
- CurriculaController.php - Controller for curriculum management
50+
- ExamsController.php - Controller for exam management
51+
52+
### Models
53+
The application uses CakePHP's ORM with entities and tables:
54+
- Entities: Represent individual records (User, Proposal, Form, Exam, etc.)
55+
- Tables: Handle database operations for entities
56+
- Behaviors: Additional functionality for models
57+
58+
### Forms
59+
Form classes are used for validation and data handling:
60+
- FormsFilterForm.php - Filter forms for data retrieval
61+
- Various entity-specific forms
62+
63+
### Authentication
64+
- AdminTokenAuthenticator.php - Authentication for admin tokens
65+
- UnipiAuthenticate.php - Authentication for UNIPI LDAP
66+
67+
### Commands
68+
- GrantAdminCommand.php - CLI command for granting admin privileges
69+
70+
## Frontend Components
71+
72+
The frontend is built with React and includes:
73+
- Components for UI elements (Attachment, ProposalEdit, Dashboard, etc.)
74+
- Controllers for managing application logic
75+
- Modules for utility functions (API calls, date handling, etc.)
76+
- SCSS stylesheets for styling
77+
78+
## Data Models
79+
80+
The application manages several key entities:
81+
- Users: Student and administrator accounts
82+
- Proposals: Study plan proposals submitted by students
83+
- Forms: Generic form submissions
84+
- Degrees: Academic degrees
85+
- Curricula: Academic curricula
86+
- Exams: Academic exams
87+
- Groups: Exam groups
88+
- Attachments: File attachments
89+
- Documents: Document storage
90+
- Settings: Application settings
91+
- Logs: System logs
92+
93+
## API Endpoints
94+
95+
The application exposes RESTful API endpoints through the Api/v1/ controllers:
96+
- /api/v1/forms - Manage form submissions
97+
- /api/v1/proposals - Manage study proposals
98+
- /api/v1/degrees - Manage academic degrees
99+
- /api/v1/curricula - Manage curricula
100+
- /api/v1/exams - Manage exams
101+
- /api/v1/users - Manage users
102+
- /api/v1/documents - Manage documents
103+
- /api/v1/form-templates - Manage form templates
104+
- /api/v1/groups - Manage exam groups
105+
- /api/v1/logs - Manage system logs
106+
107+
## API Authentication
108+
109+
The application supports administrative token authentication. If the instance configures `CAPS_ADMIN_TOKEN` and `CAPS_ADMIN_TOKEN_USER`, all APIs can be invoked with administrative privileges by passing the token in the HTTP header `Authorization`:
110+
111+
```http
112+
Authorization: Bearer <CAPS_ADMIN_TOKEN>
113+
```
114+
115+
`CAPS_ADMIN_TOKEN_USER` must be the username of an existing user in the database. The user is used to attribute operations, logs, and records created; administrative privileges are applied only to the token-authenticated request. The token is not accepted in the query string and must be used exclusively via HTTPS.
116+
117+
## API Methods
118+
119+
API controllers in `backend/src/Controller/Api/v1/` support HTTP methods GET, POST, PATCH, and DELETE. Not all endpoints are implemented yet. Examples include:
120+
* `GET /proposals/?user_id=996&limit=10&offset=20` returns a paginated version of proposals
121+
* `DELETE /proposals/1800` deletes the proposal with id=1800
122+
* `GET /forms` returns all forms in the system
123+
* `POST /forms` creates a new form based on the JSON payload passed (not implemented)
124+
* `PATCH /forms/45` updates a form based on the new JSON payload passed
125+
126+
Most PUT and POST methods are not yet implemented.
127+
128+
## API Response Format
129+
130+
Data is returned inside a `data` field of the JSON response, which also contains `code` and `message` fields - respectively the HTTP code (also returned as HTTP code by the method) and an optional success or error message.
131+
132+
Examples:
133+
```
134+
GET /proposals/?user=996&_limit=1
135+
136+
{
137+
"data": [
138+
{
139+
"id": 1846,
140+
"user_id": 996,
141+
"modified": "2022-02-24T08:41:46+00:00",
142+
"curriculum_id": 95,
143+
"state": "submitted",
144+
"submitted_date": "2022-02-24T08:41:46+00:00",
145+
"approved_date": null,
146+
"auths": [],
147+
"attachments": [],
148+
"curriculum": {
149+
[...]
150+
}
151+
}
152+
],
153+
"code": 200,
154+
"message": "OK"
155+
}
156+
```
157+
158+
```
159+
DELETE /api/v1/forms/23
160+
161+
{
162+
"data": null,
163+
"code": 200,
164+
"message": "OK"
165+
}
166+
```
167+
168+
```
169+
GET /api/v1/proposals/1823
170+
171+
{
172+
"data": null,
173+
"code": 404,
174+
"message": "Proposal not found"
175+
}
176+
```
177+
178+
## Configuration
179+
180+
The application uses CakePHP's configuration system with:
181+
- config/app.php - Main application configuration
182+
- config/bootstrap.php - Bootstrap configuration
183+
- Various migration files for database schema management
184+
185+
## Database Schema
186+
187+
The application uses a relational database with the following main entities:
188+
- Attachment [attachments]
189+
- ChosenExam [chosen_exams]
190+
- ChosenFreeChoiceExam [chosen_free_choice_exams]
191+
- CompulsoryExam [compulsory_exams]
192+
- CompulsoryGroup [compulsory_groups]
193+
- Curriculum [curricula]
194+
- Degree [degrees]
195+
- Documents
196+
- Exam [exams]
197+
- FreeChoiceExam [free_choice_exams]
198+
- Form [forms]
199+
- FormAttachment [form_attachments]
200+
- FormTemplate [form_templates]
201+
- Group [groups]
202+
- ProposalAuth [proposal_auths]
203+
- Proposal
204+
- Settings [settings]
205+
- Tag [tags]
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
use Migrations\AbstractMigration;
5+
use Phinx\Db\Adapter\MysqlAdapter;
6+
7+
class CreateDegreeSessionsAndThesisDefenses extends AbstractMigration
8+
{
9+
public function change(): void
10+
{
11+
$sessions = $this->table('degree_sessions');
12+
$sessions
13+
->addColumn('degree_id', 'integer', ['null' => false])
14+
->addColumn('name', 'string', ['limit' => 255, 'null' => false])
15+
->addColumn('start_date', 'date', ['null' => false])
16+
->addColumn('created', 'datetime', ['null' => true])
17+
->addColumn('modified', 'datetime', ['null' => true])
18+
->addForeignKey('degree_id', 'degrees', 'id', ['delete' => 'RESTRICT'])
19+
->addIndex(['degree_id', 'start_date'])
20+
->create();
21+
22+
$defenses = $this->table('thesis_defenses');
23+
$defenses
24+
->addColumn('degree_session_id', 'integer', ['null' => false])
25+
->addColumn('user_id', 'integer', ['null' => false])
26+
->addColumn('title', 'text', ['null' => false])
27+
->addColumn('state', 'string', ['limit' => 32, 'default' => 'submitted', 'null' => false])
28+
->addColumn('scheduled_at', 'datetime', ['null' => true])
29+
->addColumn('venue', 'string', ['limit' => 255, 'null' => true])
30+
->addColumn('submitted_at', 'datetime', ['null' => false])
31+
->addColumn('managed_at', 'datetime', ['null' => true])
32+
->addColumn('created', 'datetime', ['null' => true])
33+
->addColumn('modified', 'datetime', ['null' => true])
34+
->addForeignKey('degree_session_id', 'degree_sessions', 'id', ['delete' => 'RESTRICT'])
35+
->addForeignKey('user_id', 'users', 'id', ['delete' => 'RESTRICT'])
36+
->addIndex(['degree_session_id', 'user_id'], ['unique' => true])
37+
->addIndex(['state'])
38+
->create();
39+
40+
$advisors = $this->table('thesis_defense_advisors');
41+
$advisors
42+
->addColumn('thesis_defense_id', 'integer', ['null' => false])
43+
->addColumn('name', 'string', ['limit' => 255, 'null' => false])
44+
->addColumn('email', 'string', ['limit' => 255, 'null' => false])
45+
->addForeignKey('thesis_defense_id', 'thesis_defenses', 'id', ['delete' => 'CASCADE'])
46+
->create();
47+
48+
$attachments = $this->table('thesis_defense_attachments');
49+
$attachments
50+
->addColumn('thesis_defense_id', 'integer', ['null' => false])
51+
->addColumn('filename', 'string', ['limit' => 255, 'null' => false])
52+
->addColumn('mimetype', 'string', ['limit' => 255, 'null' => false])
53+
->addColumn('created', 'datetime', ['null' => true]);
54+
if ($this->getAdapter()->getConnection()->getAttribute(PDO::ATTR_DRIVER_NAME) === 'mysql') {
55+
$attachments->addColumn('data', 'blob', ['null' => false, 'limit' => MysqlAdapter::BLOB_LONG]);
56+
} else {
57+
$attachments->addColumn('data', 'blob', ['null' => false]);
58+
}
59+
$attachments
60+
->addForeignKey('thesis_defense_id', 'thesis_defenses', 'id', ['delete' => 'CASCADE'])
61+
->create();
62+
}
63+
}

backend/config/routes.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838
'Curricula', 'Degrees', 'Documents',
3939
'Exams', 'FormAttachments', 'Forms', 'FormTemplates',
4040
'Groups', 'Proposals', 'Users',
41-
'Dashboard', 'Logs', 'FormAuths'
41+
'Dashboard', 'Logs', 'FormAuths', 'ThesisDefenses'
4242
];
4343

4444
foreach ($api_controllers as $controller) {

backend/src/Controller/Api/v1/DashboardController.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ public function initialize(): void
1414
parent::initialize();
1515
$this->Proposals = TableRegistry::getTableLocator()->get('proposals');
1616
$this->Forms = TableRegistry::getTableLocator()->get('forms');
17+
$this->ThesisDefenses = TableRegistry::getTableLocator()->get('thesisDefenses');
1718
}
1819

1920
/**
@@ -64,6 +65,8 @@ public function dashboardData() {
6465

6566
function index() {
6667
$submitted_count = $this->Proposals->find()->where([ 'state' => 'submitted' ])->count();
68+
$submitted_thesis_defenses_count = $this->ThesisDefenses->find()
69+
->where(['state' => 'submitted'])->count();
6770
$months = 24;
6871

6972
$conn = $this->Proposals->getConnection();
@@ -90,6 +93,7 @@ function index() {
9093

9194
$data = [
9295
'submitted_count' => $submitted_count,
96+
'submitted_thesis_defenses_count' => $submitted_thesis_defenses_count,
9397
'proposal_comments' => $proposal_comments,
9498
'proposal_submission_counts' => $this->get_submission_counts($this->Proposals, 'submitted_date', $months),
9599
'proposal_approval_counts' => $this->get_submission_counts($this->Proposals, 'approved_date', $months),
@@ -99,4 +103,3 @@ function index() {
99103
$this->JSONResponse(ResponseCode::Ok, $data);
100104
}
101105
}
102-
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<?php
2+
namespace App\Controller\Api\v1;
3+
4+
class ThesisDefensesController extends RestController
5+
{
6+
public $allowedFilters = [
7+
'user_id' => Integer::class,
8+
'state' => ['type' => String::class, 'options' => ['submitted', 'approved', 'rejected']],
9+
'submitted_at' => Integer::class,
10+
];
11+
12+
public function index()
13+
{
14+
if (!$this->user['admin'] && (int)$this->request->getQuery('user_id') !== (int)$this->user['id']) {
15+
$this->JSONResponse(ResponseCode::Forbidden);
16+
return;
17+
}
18+
$query = $this->ThesisDefenses->find()->contain([
19+
'DegreeSessions', 'DegreeSessions.Degrees', 'ThesisDefenseAdvisors',
20+
]);
21+
$query = $this->applyFilters($query);
22+
$this->JSONResponse(ResponseCode::Ok, $query);
23+
}
24+
25+
public function get($id)
26+
{
27+
try {
28+
$defense = $this->ThesisDefenses->get($id, ['contain' => [
29+
'Users', 'DegreeSessions', 'DegreeSessions.Degrees',
30+
'ThesisDefenseAdvisors', 'ThesisDefenseAttachments',
31+
]]);
32+
} catch (\Exception $e) {
33+
$this->JSONResponse(ResponseCode::NotFound);
34+
return;
35+
}
36+
if (!$this->user['admin'] && $defense->user_id !== $this->user['id']) {
37+
$this->JSONResponse(ResponseCode::Forbidden);
38+
return;
39+
}
40+
foreach ($defense->thesis_defense_attachments as $attachment) {
41+
unset($attachment->data);
42+
}
43+
$this->JSONResponse(ResponseCode::Ok, $defense);
44+
}
45+
}

0 commit comments

Comments
 (0)