[TOC]
# InfoX
# DB
## Users
* id
* email
* lastname
* firstname
* nickname
* public
* if false than only `nickname`, `teacher` and number of solved problems are visible
* if true than all names, `school`, `coins` and location are visible; also problem history is visible
* password
* password must not be changed without email request
* author
* admin
* messages
* teacher
* avatar
* school
* county
* locality
* coins
* created_at
* updated_at
## Problems
* id
* author_id
* approved
* approved_before
* title
* source
* abstract
* full
* tips
* subchapter_id
* labels
* csv string with label ids
* folder
* metadata
* proposer_code
* rejection
* submitted
* should be incremented for each new user that sends a solution
* correct
* should be incremented for each new correct solution [from a user that didn't solve it before]
* created_at
* updated_at
## Chapters
* id
* year
* chapter
* subchapter
* created_at
* updated_at
## LabelsProblemRelationship (TODO: change name)
* id
* problem_id
* label_id
* even though we have `labels` in `problems`, for filtering problems after labels it's better to have this table
## Messages
* id
* sender_id
* type
* may be `"private"`, `"class"` or `"problem"`
* receiver_id
* is `0` when it's not a message between users
* class_id
* is `0` when it's not a message in class
* problem_id
* is `0` when it's not a message on problem
* text
* seen
* true if seen by receiver, false otherwise
* created_at
* updated_at
## Solutions
* id
* user_id
* problem_id
* request_output
* points
* details
* source
* result
* created_at
* updated_at
## Transactions
* id
* user_id
* problem_id
* transaction_type
* can be `"test"` or `"solutions"`
* test_id
* is `0` if `transaction_type="solutions"`
* created_at
* updated_at
## Users_Temp
* id
* password
* code
* created_at
* updated_at
## Classes
* id
* owner_id
* collaborators
* @Cosmin, thoughts?
* I think a CSV string with all collaborators it's better when searching the class after id and getting all *admins* for the class in one search
* below, in the students table, we also have a field for collaborator
* name
* school
* useful for filtering public classes
* public
* if `true` than class is visible on user profile and in all classes
* password
* if not 0 then class needs password to enter
* approve_only
* if true than each user that wants to join needs to be approved first by the owner
* created_at
* updated_at
## Students
* id
* class_id
* student_id
* approved
* owner
* collaborator
* if true he has management rights on the class (except on the owner)
* seen_timestamp
* the last time `student_id` accesed messages for `class_id`
* created_at
* updated_at
## Homeworks
* id
* class_id
* homework_name
* due_time
* created_at
* updated_at
## Homework_Student_Relation
* id
* homework_id
* student_id
* problem_id / problems
* fie id-ul problemei, fie CSV cu toate problemele
* probabil mergem pe id
* created_at
* updated_at
## Blacklists
* id
* user_id
* blocked_user_id
* created_at
* updated_at
## Reports
* id
* user_id
* reported_message_id
* reason
* seen
* created_at
* updated_at
## Tickets
* id
* user_id
* text
* answer
* closed
* created_at
* updated_at
# Backend
* All requests should return `success:true` or `success:false` besides Status Code
## JWT Token
* is never refreshed without explicit login
* is refreshed only when buying (once and irrevocable) `author` / `messages` or user becomes (once and irrevocable) `teacher`
* Fields:
* `userId`
* `teacher`
* `admin`
* `author`
* `messages`
## Frontend Route
### `GET /api`
* Serves the frontend application
## Middlewares
### JWTMiddleware
* Receives the bearer token in the `Authentication Header`, validates it
* If validation fails returns `401 Unauthorized`
* Otherwise attaches the JWT data to Context
### AdminMidlleware
* **Returns** `403 Forbidden` if `JWT.admin` is `false`
### RequestValidatorMiddleware
* Optional
* Should receive a `Schema`
* Validates _POST to conform to the schema
* If validation fails, returns `400 Bad Request`
## Auth Route
### `POST /api/auth/oauth`
* Logins with OAuth
* If email does not exist registers new user
* **Returns** `200 Ok` and JWT Token
### `POST /api/auth/register`
* **Schema**:
* `email`
* `password`
* If `email` is not in `users` registers new entry in `users_temp` and sends confirmation email to `email`
* **Returns** `201 Created` no matter what (except for internal server error)
### `GET /api/auth/confirm?token={token}`
* **Schema**:
* `token`
* If `token` is in `users_temp`, moves row in `users`
* **Returns** `202 Accepted` if everything ok or `404 Not Found` ("Link may have expired" on frontend)
### `POST /api/auth/login`
* **Schema**:
* `email`
* `password`
* If `email` in `users` verifies password
* **Returns** either `200 Ok` and JWT Token or `400 Bad Request` ("Invalid credentials" on frontend)
### `POST /api/auth/reset`
* **Schema**:
* `email`
* If `email` is Google, send email prompting connection with Google
* Otherwise sends if `email` in `users` creates new entry in `users_temp` and sends recovery email
* **Returns** `200 Ok` regardless
### `POST /api/auth/change_password`
* **Schema**:
* `token`
* `password`
* If `token` in `users_temp`, deletes entry and moves `password` to `users`
* **Returns** either `200 Ok` or `404 Not Found` ("Link may have expired" on frontend)
## Users Route
* Each Route requires `JWTMiddleware`
* Responses could be saved in a Dictionary along with timestamp of request
### `GET /api/users/profile`
* Takes `JWT.userId` and searches for user profile
* User profile consists of:
* user row from DB
* number of solved problems
* problems authored by `userId` (are got using `/api/authors/{authorId}`)
* **Returns** `200 Ok` and user profile
* Advice:
* could be called only once
* can be updated with a `SetInterval` or when timestamp is older
### `GET /api/users/profile/{userId}`
* Another user profile consists of:
* public data (`id`, `nickname`, `teacher`, number of solved problems and problems authored by `userId`)
* private data if allowed
* Private data is public if `JWT.userId` is `admin` or `JWT.userId` is `owner` or `collaborator` of a class where `userId` is
* **Returns** `200 Ok` and profile of `userId`, or `404 Not Found` if `userId` is not in `users`
### `POST /api/users/profile`
* **Schema**:
* `lastname`
* `firstname`
* `nickname`
* `public`
* `teacher`
* `avatar`
* `school`
* `county`
* `locality`
* Updates the above data in `users` for `id = JWT.userId`
* **Returns** `200 Ok`
### `GET /api/users/problems`
* Gets all problems tried and their score for `JWT.userId`
* **Returns** `200 Ok` and problems
* Advice:
* Could be called only once
* can be updated when new code is submitted, or when timestamp is older
### `GET /api/users/problems/{userId}`
* Gets all problems tried and their score for `userId` if `userId` is `public`
* `userId` is `public` to `admin`, `owner` or `collaborator` in classes where `userId` is
* **Returns** `200 Ok` and problems, `403 Forbidden` if not `public` or `404 Not Found` if `userId` not in `users`
* Advice:
* Request may be saved in dictionary
* Older requests may be deleted.
### `GET /api/users/classes`
* Gets all classes for `JWT.userId` (rows in `students` where `student_id = JWT.userId`)
* **Returns** `200 Ok` and classes
* Advice:
* could be called only once
* can be updated when new class is joined or created, or when timestamp is older
## Problems Route
* Each Route requires `JWTMiddleware`
* Responses could be saved in a Dictionary along with timestamp of request
### `GET /api/problems/chapters/{yearId}`
* **Returns** `200 Ok` and all chapters for `{yearId}`
* Advice:
* `/api/problems/chapters/9`, `/api/problems/chapters/10` and `/api/problems/chapters/11` are to be called only once
### `GET /api/problems/problems/{subchapterId}`
* Gets all problems in `subchapterId`
* **Returns** `200 Ok` and problems, or `404 Not Found`
* Advice:
* `/api/problems/problems/{subchapterId}` is to be called only once, then be saved in a dictionary
### `GET /api/problems/{problemId}`
* Gets `problemId` row from `problems`
* **Returns** `200 Ok` and problem, or `404 Not Found`
* Advice:
* `/api/problems/{problemId}` could be used only once, then be saved in a dictionary along with timestamp
* to avoid overloading the user's memory, older timestamps may be deleted (or not)
### `GET /api/problems/hard`
* Gets 50 problems which have the lowest `correct` / `submitted` ratio and `JWT.userId` hasn't solved
* **Returns** `200 Ok` and problems
* Advice:
* `/api/problems/hard` is to be called only once
* it can be updated when timestamp is older than 1 hour or more.
### `GET /api/problems/search/{searchString}`
* Gets all problems that may be `searchString`
* **Returns** `200 Ok` and problems, or `404 Not Found` if no problem matches `searchString`
* Advice:
* search can be saved in a dictionary
* searches older than 10 minutes can be deleted
### `POST /api/problems/searchString`
* **Schema**:
* ?? (filters with labels, chapter, abstract, author, ...)
* Advanced search
* **Returns** `200 Ok` and problems, or `404 Not Found`
* Needs not to be cached
## Tests Route
* Each Route requires `JWTMiddleware`
### `GET /api/tests/{problemId}`
* Gets all example tests for `problemId`
* Requires `JWT.userId` to have bought tests for `problemId`, to be `admin` or `author` of `problemId` to get all tests
* **Returns** `200 Ok` and test, or `404 Not Found` if `problemId` not in `problems`
* Advice:
* Data may be attached in problem dictionary on `problemId` row
## Solutions Route
* Each Route requires `JWTMiddleware`
### `POST /api/solutions`
* **Schema**:
* code
* problemId
* Adds new entry in `solutions`
* **Returns** `200 Ok`
### `GET /api/solutions/{solutionId}`
* Gets solution with `solutionId` if `solutionId` is owned by user, user is teacher or collaborator in a class in which solution owner is or user has bought solutions for the problem where `solutionId` was send
* **Returns** `200 Ok`, `403 Forbidden` or `404 Not Found`
### `GET /api/solutions/problem/{problemId}`
* Gets all solutions to `problemId` owned by `JWT.userId`
* **Returns** `200 Ok`
### `GET /api/solutions/bought/{problemId}`
* Gets all solutions to `problemId` which have 100 points only if user bought them
* **Returns** `200 Ok`, `403 Forbidden` or `404 Not Found`
### (Admin only) `POST /api/solutions/{solutionId}`
* Optional
* **Schema**:
* points
* details
* Requires `AdminMiddleware` to be passed
* It can be used by admins to change points for problems which have `if (input) then output` sintax
* **Returns** `200 Ok`
## Compiler Route
* Used only by compiler
### `POST /api/compiler/get_source`
* could be old model or be made again
* returns uncompiled problems to compiler
### `POST /api/compiler/save_result`
* could be old model or be made again
* gets compiler results and saves them
* Advice:
* the part which replaces tests should not be done here. Either way, compiler result is in `result`, so test replacement can be done somewhere else
## Authors Route
* Each Route requires `JWTMiddleware`
### `GET /api/authors/{authorId}`
* Gets all problems authored by `authorId`
* **Returns** `200 Ok` and problems or `404 Not Found`
### `GET /api/authors/problems`
* Gets all problems authored by `JWT.userId` if `JWT.author`
* **Returns** `200 Ok` and problems, `403 Forbidden` or `404 Not Found`
### `GET /api/authors/problems/{problemId}`
* Gets problem with `problemId` if problem author is `JWT.userId` or `JWT.admin`
* **Returns** `200 Ok` and problems, `403 Forbidden` or `404 Not Found`
### `POST /api/authors/problems/{problemId}`
* **Schema**:
* TODO: de scris schema
* Updates data for `problemId` if `problemId` is owned by `JWT.userId` or `JWT.admin`
* **Returns** `200 Ok` or `403 Forbidden`
### `POST /api/authors/new_problem`
* **Schema**: is empty
* Creates new field in `problems` if `JWT.author`
* **Returns** `200 Ok` and `problemId` or `403 Forbidden`
### `POST /api/authors/finalize/{problemId}`
* **Schema**: is empty
* Send problem to approval if problem author is `JWT.userId` or `JWT.admin`
* **Returns** `200 Ok` or `403 Forbidden`
### `POST /api/authors/clone/{problemId}`
* **Schema**: is empty
* Copies problem with `problemId` if problem author is `JWT.userId` or `JWT.admin`
* **Returns** `200 Ok` or `403 Forbidden`
### `GET /api/authors/labels`
* Gets all labels from `labels`
* **Returns** `200 Ok` and labels
### `POST /api/authors/labels`
* **Schema**:
* label
* Creates new entry in `labels` with the new label, stripped of any html tags
* **Returns** `200 Ok` or `403 Forbidden` if not `JWT.author`
### (Admin only) `POST /api/authors/labels/{labelId}`
* **Schema**:
* label
* Updates label name if `JWT.admin`
* **Returns** `200 Ok`
### `GET /api/authors/export/{problemId}`
* Optional
* Makes archive (or json file) with problem data and returns it if `problemId` is owned by `JWT.userId` or `JWT.admin`
* **Returns** `200 Ok` and archive or `403 Forbidden`
### (Admin only) `GET /api/authors/export`
* Partiallly done
* Creates json file with all the problems in DB
* Should be accesible only to admins and then it should allow file download
* **Returns** `200 Ok` and File
## Messages Route
* Each Route requires `JWTMiddleware`
### `GET /api/messages/problem/{problemId}`
* Gets messages in `messages` which have `problem_id = problemId`
* **Returns** `200 Ok` and messages
### `GET /api/messages/class/{classId}`
* Gets messages in `messages` which have `class_id = classId` and `JWT.userId` is in `classId`
* Updates `seen_timestamp` in `classId` for `JWT.userId`
* **Returns** `200 Ok` and messages
### `GET /api/messages/user/{userId}`
* Gets messages in `messages` which have `sender_id = JWT.userId` and `receiver_id = userId` or `sender_id = userId` and `receiver_id = JWT.userId`
* Updates `seen = true` for messages in which `receiver_id = JWT.userId`
* **Returns** `200 Ok` and messages
### `GET /api/messages/new`
* Gets ids of classes that have messages after `seen_timestamp` for `studentsId = JWT.userId` and ids of senders for messages that have `receiver_id = JWT.userId AND seen = false`
* **Returns** `200 Ok` and the 2 pairs of ids or `404 Not Found`
* Advice:
* use `SetTimeout` to call this route once per minute then add the data in an array locally
* once client acceses the ids, remove them from array
### `POST /api/messages`
* **Schema**:
* message
* problemId (0 if not problem message)
* classId (0 if not class message)
* receiverId (0 if not direct message)
* Verifies that `JWT.messages`
* If `receiverId != 0` then checks that `JWT.userId` is not a `blockedUserId` for `userId = receiverId`
* Admins may forcefully send messages
* **Returns** `200 Ok` or `403 Forbidden` (for `JWT.messages = false`) or `418 I'm a teapot` if blocked
### `POST /api/messages/block/{userId}`
* **Schema**: is empty
* Adds userId to blacklist
* **Returns** `200 Ok`
### `POST /api/messages/report/{messageId}`
* **Schema**:
* reason
* Creates new entry in `reports` with `user_id = JWT.userId, reported_message_id = messageId, reason = reason, seen = false`
* **Returns** `200 Ok`
### `DELETE /api/messages/{messageId}`
* Deletes from `messages WHERE id = messageId` if `JWT.admin` or `JWT.userId` is owner or collaborator in class where `messageId` was sent
* **Returns** `204 No Content` or `403 Forbidden`
### (Admin only) `GET /api/messages`
* Optional
* Alternatives:
1. gets last 100 problem messages
2. gets last 100 problem messages which have STOP WORDS (...) or have CODE WORDS (if, while, for, cout, cin, =, ++)
3. @Cosmin
* **Returns** `200 Ok` and messages for admin, otherwise `403 Forbidden`
## Classes Route
* Each Route requires `JWTMiddleware`
### `GET /api/classes`
* Gets all classes in which `JWT.userId` is approved
* **Returns** `200 Ok` and classes
### `GET /api/classes/{classId}`
* Gets `classId`, students in `classId` and homeworks if `JWT.userId` in `classId` and `approved`
* **Returns** `200 Ok` and students
### `POST /api/classes/{classId}`
* **Schema**:
* name
* school
* public
* password
* approve_only
* Updates class only if `owner = true OR collaborator = true` in `students WHERE class_id = classId AND student_id = JWT.userId`
* **Returns** `200 Ok` or `403 Forbidden`
### `DELETE /api/classes/{classId}`
* Deletes class with `classId` if `owner_id = JWT.userId`
* Deletes cascade everything related to `classId`
* **Returns** `204 No Content` or `403 Forbidden`
### `POST /api/classes/create`
* **Schema**:
* name
* school
* public
* password
* approve_only
* Creates new entry in `classes` and `students` with the current user if `JWT.teacher`
* **Returns** `200 Ok` or `403 Forbidden`
### `POST /api/classes/join`
* **Schema**:
* class_id
* password (may be "")
* Adds `student_id = JWT.userId` in students for `class_id` if `password = password`
* `approved` is true if `class_id` is `public`, `false` otherwise
* **Returns** `200 Ok` or `400 Bad Request`
### `GET /api/classes/all`
* Gets all public classes
* **Returns** `200 Ok` and classes
### `GET /api/classes/join_requests/{classId}`
* Checks that `classId` is owned by `JWT.userId` or `JWT.userId` is collaborator in `classId`
* Gets all `student_id` in `students WHERE class_id = classId AND approved = false`
* **Returns** `200 Ok` and students or `403 Forbidden`
### `GET /api/classes/students/{classId}`
* Checks that `JWT.userId` is student in `classId`
* Gets all students in `classId`
* **Returns** `200 Ok` and students or `403 Forbidden`
### `POST /api/classes/students/{classId}`
* **Schema**:
* student_id
* approve (= true)
* collaborator (= false)
* delete (= false)
* If `JWT.userId` is owner or collaborator, it may approve student, make collaborator, delete student
* Owners can remove and delete colaborators
* Collaborators cannot remove or delete owners and collaborators
* **Returns** `200 Ok` or `403 Forbidden`
### `POST /api/classes/leave/{classId}`
* **Schema**: is empty
* `JWT.userId` leaves `classId` and related rows from `students` and `homeworks` are deleted
* **Returns** `200 Ok` regardless (checks if student is in class are useless)
### `GET /api/classes/homework`
* Gets all homeworks where `student_id = JWT.userId`
* **Returns** `200 Ok`
### `GET /api/classes/homework/{classId}`
* Gets all homeworks where `student_id = JWT.userId AND class_id = classId`
* **Returns** `200 Ok` or `404 Not Found` (403 Forbidden for when the student is not in `classId` is useless)
### `POST /api/classes/homework/{classId}`
* **Schema**:
* Array:
* student_id
* problem_id / problems
* Creates or updates row for `classId` and `student_id` with homework if `JWT.userId` is owner or collaborator in `classId`
* **Returns** `200 Ok` or `403 Forbidden`
### `POST /api/classes/homework/student`
* **Schema**:
* student_id
* class_id
* If `JWT.userId` is owner or collaborator in `class_id`, gets student homework in `class_id`
* **Returns** `200 Ok` and homework or `403 Forbidden`
## Coins Route
* Each Route requires `JWTMiddleware`
### `GET /api/coins`
* Gets `coins FROM users WHERE id = JWT.userId`
* **Returns** `200 Ok` and coins
### (Admin Only) `POST /api/coins/{userId}`
* Optional
* **Schema**:
* coins
* updates `coins` for `userId` if JWT.admin
* **Returns** `200 Ok` or `403 Forbidden`
### `POST /api/coins/buy_tests`
* **Schema**:
* problem_id
* test_id
* Buys `test_id` from `problem_id` if `test_id` exists and enough `coins`
* **Returns** `200 Ok` or `404 Not Found` (not `problem_id` or `test_id`) or `418 I'm a teapot` (not enough coins)
### `POST /api/coins/buy_solutions/{problemId}`
* **Schema**: is empty
* Buys solutions for `problemId` if enough `coins`
* **Returns** `200 Ok` or `404 Not Found` (not `problem_id`) or `418 I'm a teapot` (not enough coins)
### `POST /api/coins/buy_author`
* **Schema**: is empty
* Buys `author` for `JWT.userId`
* **Returns** `200 Ok` or `418 I'm a teapot` (not enough coins)
### `POST /api/coins/buy_messages`
* **Schema**: is empty
* Buys `messages` for `JWT.userId`
* **Returns** `200 Ok` or `418 I'm a teapot` (not enough coins)
## Admin Route
* Each Route requires `JWTMiddleware`
* Each Route requires `AdminMiddleware`, except for `POST /api/admin/tickets`
### `POST /api/admin/tickets`
* **Schema**:
* text
* Adds new entry in tickets
* **Returns** `200 Ok`
### (Admin Only) `GET /api/admin/tickets`
### (Admin Only) `GET /api/admin/tickets/{ticketId}`
### (Admin Only) `GET /api/admin/reports`
### (Admin Only) `GET /api/admin/reports/{reportId}`
### (Admin Only) `GET /api/admin/users`
### (Admin Only) `GET /api/admin/users/{userId}`
### (Admin Only) `POST /api/admin/users/{userId}`
### (Admin Only) `POST /api/admin/users/login_as/{userId}`
### (Admin Only) `GET /api/admin/authors`
### (Admin Only) `GET /api/admin/authors/{userId}`
### (Admin Only) `GET /api/admin/problems/unpublished`
### (Admin Only) `GET /api/admin/problems/unapproved`
### (Admin Only) `GET /api/admin/problems/approved`
### (Admin Only) `GET /api/admin/problems/{problemId}`
### (Admin Only) `POST /api/admin/problems/approve/{problemId}`
### (Admin Only) `POST /api/admin/problems/reject/{problemId}`
# Frontend
A single page app. It can use a frontend framework (ex. React) or use vanilla js to manage page history and requests
Framework advantage: application flow needs not to be implemented
Framework disadvantage: needs to be learned
Vanilla JS disadvantage: may raise problems when designing the frontend architecture and the application flow; may be hard to follow
Vanilla JS advantage: lightweight, can be (easily ?? hardly) learned, better control over the application
# Observations
* We could use the google SMTP mail service instead of mxhost mail service for visibility
* The password should be changed only using an email address.
* We should have a Mail Manager for sending emails
* Each table should have a Repository class which queries each table
* Operations with coins should be done server side. Coins should be updated on client after each operation
* JWT should not be refreshed
* We should use a File Manager to work with test files. Metadata and proposer code should not be added to files anymore
* For `county` and `locality` in `users` we could use a drop-down with choices
* We could also use a drop down with schools and universities to help filter classes from a school
* Hide ids and passwords in a configuration file which should not be on github
# TODOS
* A logger should write data / erors in files during development. It could be useful even on frontend
* Assert that:
* All problems have a metadata in DB and do not have a metadata file (changes are required on compiler before deleting metadata.json)
* All problems have proposer_code in DB and not in a file
* All test files have the same format. Delete all other formats
* We do not need to create 1120.json and 1120.txt (files with the source code and compiler analysis). They are already in the DB. To create routes that get these files from database
* Use `submitted` and `correct` in `problems`. Update them in the database with their real values first.