---
# System prepended metadata

title: InfoX

---

[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.