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