From 587456017459a0ecc5edb3bfb7a9a564c1d8fb34 Mon Sep 17 00:00:00 2001 From: Carlo M Date: Mon, 21 Oct 2024 06:42:24 -0600 Subject: [PATCH 01/72] Tweaked CICD, removed husky, removed deployment --- .dockerignore | 4 ---- .github/workflows/ci.yml | 5 ----- .github/workflows/deploy.yml | 21 ------------------ .husky/pre-commit | 4 ---- DB_SETUP.md | 18 ---------------- Dockerfile | 36 ------------------------------- fly.toml | 42 ------------------------------------ package.json | 2 -- 8 files changed, 132 deletions(-) delete mode 100644 .dockerignore delete mode 100644 .github/workflows/deploy.yml delete mode 100644 .husky/pre-commit delete mode 100644 DB_SETUP.md delete mode 100644 Dockerfile delete mode 100644 fly.toml diff --git a/.dockerignore b/.dockerignore deleted file mode 100644 index 79297ed5..00000000 --- a/.dockerignore +++ /dev/null @@ -1,4 +0,0 @@ -Dockerfile -.dockerignore -node_modules -.git diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 56cddc0a..1e2ff854 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,9 +1,5 @@ name: CI on: [push, pull_request] -env: - DATABASE_URL: ${{ secrets.DATABASE_URL }} - JWT_TOKEN: ${{ secrets.JWT_TOKEN }} - JWT_EXPIRY: ${{ secrets.JWT_EXPIRY }} jobs: test: runs-on: ubuntu-latest @@ -14,4 +10,3 @@ jobs: node-version: 'lts/*' - run: npm ci - run: npx eslint src - - run: npx prisma migrate reset --force --skip-seed diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml deleted file mode 100644 index 471052aa..00000000 --- a/.github/workflows/deploy.yml +++ /dev/null @@ -1,21 +0,0 @@ -name: Fly Deploy -on: - push: - branches: - - main -env: - FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }} - DATABASE_URL: ${{ secrets.DATABASE_URL }} - JWT_SECRET: ${{ secrets.JWT_SECRET }} - JWT_EXPIRY: ${{ secrets.JWT_EXPIRY }} -jobs: - deploy: - name: Deploy app to fly.io - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - uses: superfly/flyctl-actions/setup-flyctl@master - - run: echo DATABASE_URL=$DATABASE_URL >> .env - - run: echo JWT_SECRET=$JWT_SECRET >> .env - - run: echo JWT_EXPIRY=$JWT_EXPIRY >> .env - - run: flyctl deploy --remote-only diff --git a/.husky/pre-commit b/.husky/pre-commit deleted file mode 100644 index a41979b3..00000000 --- a/.husky/pre-commit +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/sh -. "$(dirname "$0")/_/husky.sh" - -npx eslint src \ No newline at end of file diff --git a/DB_SETUP.md b/DB_SETUP.md deleted file mode 100644 index 5a966b81..00000000 --- a/DB_SETUP.md +++ /dev/null @@ -1,18 +0,0 @@ -## Creating your databases with ElephantSQL - -1. [Sign in to ElephantSQL](https://customer.elephantsql.com/login) using your GitHub account - - If it asks you to create a team, create one with any name - -2. When logged in, click the green *Create New Instance* button in the top right -![](./assets/db-setup/1.PNG) - -3. Enter a name for your new database instance and choose the *Tiny Turtle (Free)* plan. **This will be your PRIMARY database** -![](./assets/db-setup/2.PNG) - -4. Repeat the same steps to create a second database, this time give it the same name but add `-shadow` to the end. **This will be your SHADOW database** - -5. In your shadow instance: - - click the *Browser* menu item on the left side of the screen - - in the SQL Browser text input, enter `CREATE SCHEMA shadow;` - - click the *Execute* button -![](./assets/db-setup/3.PNG) \ No newline at end of file diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index b0c8ba77..00000000 --- a/Dockerfile +++ /dev/null @@ -1,36 +0,0 @@ -FROM debian:bullseye as builder - -ARG NODE_VERSION=18.12.1 - -RUN apt-get update; apt install -y curl -RUN curl https://get.volta.sh | bash -ENV VOLTA_HOME /root/.volta -ENV PATH /root/.volta/bin:$PATH -RUN volta install node@${NODE_VERSION} - -####################################################################### - -RUN mkdir /app -WORKDIR /app - -# NPM will not install any package listed in "devDependencies" when NODE_ENV is set to "production", -# to install all modules: "npm install --production=false". -# Ref: https://docs.npmjs.com/cli/v9/commands/npm-install#description - -ENV NODE_ENV production - -COPY . . - -RUN npm install -FROM debian:bullseye - -LABEL fly_launch_runtime="nodejs" - -COPY --from=builder /root/.volta /root/.volta -COPY --from=builder /app /app - -WORKDIR /app -ENV NODE_ENV production -ENV PATH /root/.volta/bin:$PATH - -CMD [ "npm", "run", "start" ] diff --git a/fly.toml b/fly.toml deleted file mode 100644 index eac058e7..00000000 --- a/fly.toml +++ /dev/null @@ -1,42 +0,0 @@ -# fly.toml file generated for team-dev-backend-api on 2022-11-30T11:30:34Z - -app = "team-dev-backend-api" -kill_signal = "SIGINT" -kill_timeout = 5 -processes = [] - -[env] - PORT = "8080" - -[experimental] - allowed_public_ports = [] - auto_rollback = true - -[deploy] - release_command = "npx prisma migrate deploy" - -[[services]] - http_checks = [] - internal_port = 8080 - processes = ["app"] - protocol = "tcp" - script_checks = [] - [services.concurrency] - hard_limit = 25 - soft_limit = 20 - type = "connections" - - [[services.ports]] - force_https = true - handlers = ["http"] - port = 80 - - [[services.ports]] - handlers = ["tls", "http"] - port = 443 - - [[services.tcp_checks]] - grace_period = "1s" - interval = "15s" - restart_limit = 0 - timeout = "2s" diff --git a/package.json b/package.json index 41e19e5e..4efc3e40 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,6 @@ "scripts": { "start": "node src/index.js", "dev": "nodemon src/index.js", - "prepare": "husky install", "db-reset": "prisma migrate reset" }, "prisma": { @@ -33,7 +32,6 @@ "eslint-plugin-node": "^11.1.0", "eslint-plugin-prettier": "^4.0.0", "eslint-plugin-promise": "^5.1.0", - "husky": "^7.0.4", "nodemon": "^2.0.15", "prettier": "^2.6.2", "prisma": "^3.12.0" From 2326cca4000c002f8a9a536c2f6480944a88a8d9 Mon Sep 17 00:00:00 2001 From: Muzea001 Date: Mon, 28 Oct 2024 15:48:10 +0100 Subject: [PATCH 02/72] initialCommit --- .env.example | 5 ----- src/controllers/user.js | 6 ++++++ 2 files changed, 6 insertions(+), 5 deletions(-) delete mode 100644 .env.example diff --git a/.env.example b/.env.example deleted file mode 100644 index 932b9f1e..00000000 --- a/.env.example +++ /dev/null @@ -1,5 +0,0 @@ -PORT=4000 -DATABASE_URL="?schema=prisma" -SHADOW_DATABASE_URL="?schema=shadow" -JWT_SECRET="somesecurestring" -JWT_EXPIRY="24h" \ No newline at end of file diff --git a/src/controllers/user.js b/src/controllers/user.js index 40ff0f1c..24388420 100644 --- a/src/controllers/user.js +++ b/src/controllers/user.js @@ -1,6 +1,7 @@ import User from '../domain/user.js' import { sendDataResponse, sendMessageResponse } from '../utils/responses.js' +/* CREATES A NEW USER */ export const create = async (req, res) => { const userToCreate = await User.fromJson(req.body) @@ -19,6 +20,7 @@ export const create = async (req, res) => { } } +/* GETS A USER BY ID */ export const getById = async (req, res) => { const id = parseInt(req.params.id) @@ -35,6 +37,7 @@ export const getById = async (req, res) => { } } +/* GETS ALL USERS */ export const getAll = async (req, res) => { // eslint-disable-next-line camelcase const { first_name: firstName } = req.query @@ -55,6 +58,7 @@ export const getAll = async (req, res) => { return sendDataResponse(res, 200, { users: formattedUsers }) } +/* Updates a user by ID */ export const updateById = async (req, res) => { const { cohort_id: cohortId } = req.body @@ -65,3 +69,5 @@ export const updateById = async (req, res) => { return sendDataResponse(res, 201, { user: { cohort_id: cohortId } }) } + +/* Test Commit statement */ From ef9a86078702612391bce4a43894bc1633f7b3a2 Mon Sep 17 00:00:00 2001 From: tvaltn Date: Mon, 28 Oct 2024 15:49:25 +0100 Subject: [PATCH 03/72] test commit --- src/controllers/user.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/controllers/user.js b/src/controllers/user.js index 40ff0f1c..95d752b7 100644 --- a/src/controllers/user.js +++ b/src/controllers/user.js @@ -1,6 +1,6 @@ import User from '../domain/user.js' import { sendDataResponse, sendMessageResponse } from '../utils/responses.js' - +// Test commit export const create = async (req, res) => { const userToCreate = await User.fromJson(req.body) From b9d122d356d20a1d51c9e1f59297c1f9f98d3099 Mon Sep 17 00:00:00 2001 From: Ingeborg Brommeland Austeid Date: Mon, 28 Oct 2024 15:51:47 +0100 Subject: [PATCH 04/72] first commit --- src/controllers/user.js | 136 ++++++++++++++++++++-------------------- 1 file changed, 69 insertions(+), 67 deletions(-) diff --git a/src/controllers/user.js b/src/controllers/user.js index 40ff0f1c..944ab9eb 100644 --- a/src/controllers/user.js +++ b/src/controllers/user.js @@ -1,67 +1,69 @@ -import User from '../domain/user.js' -import { sendDataResponse, sendMessageResponse } from '../utils/responses.js' - -export const create = async (req, res) => { - const userToCreate = await User.fromJson(req.body) - - try { - const existingUser = await User.findByEmail(userToCreate.email) - - if (existingUser) { - return sendDataResponse(res, 400, { email: 'Email already in use' }) - } - - const createdUser = await userToCreate.save() - - return sendDataResponse(res, 201, createdUser) - } catch (error) { - return sendMessageResponse(res, 500, 'Unable to create new user') - } -} - -export const getById = async (req, res) => { - const id = parseInt(req.params.id) - - try { - const foundUser = await User.findById(id) - - if (!foundUser) { - return sendDataResponse(res, 404, { id: 'User not found' }) - } - - return sendDataResponse(res, 200, foundUser) - } catch (e) { - return sendMessageResponse(res, 500, 'Unable to get user') - } -} - -export const getAll = async (req, res) => { - // eslint-disable-next-line camelcase - const { first_name: firstName } = req.query - - let foundUsers - - if (firstName) { - foundUsers = await User.findManyByFirstName(firstName) - } else { - foundUsers = await User.findAll() - } - - const formattedUsers = foundUsers.map((user) => { - return { - ...user.toJSON().user - } - }) - - return sendDataResponse(res, 200, { users: formattedUsers }) -} - -export const updateById = async (req, res) => { - const { cohort_id: cohortId } = req.body - - if (!cohortId) { - return sendDataResponse(res, 400, { cohort_id: 'Cohort ID is required' }) - } - - return sendDataResponse(res, 201, { user: { cohort_id: cohortId } }) -} +import User from '../domain/user.js' +import { sendDataResponse, sendMessageResponse } from '../utils/responses.js' + +export const create = async (req, res) => { + const userToCreate = await User.fromJson(req.body) + + // Add password validation + + try { + const existingUser = await User.findByEmail(userToCreate.email) + + if (existingUser) { + return sendDataResponse(res, 400, { email: 'Email already in use' }) + } + + const createdUser = await userToCreate.save() + + return sendDataResponse(res, 201, createdUser) + } catch (error) { + return sendMessageResponse(res, 500, 'Unable to create new user') + } +} + +export const getById = async (req, res) => { + const id = parseInt(req.params.id) + + try { + const foundUser = await User.findById(id) + + if (!foundUser) { + return sendDataResponse(res, 404, { id: 'User not found' }) + } + + return sendDataResponse(res, 200, foundUser) + } catch (e) { + return sendMessageResponse(res, 500, 'Unable to get user') + } +} + +export const getAll = async (req, res) => { + // eslint-disable-next-line camelcase + const { first_name: firstName } = req.query + + let foundUsers + + if (firstName) { + foundUsers = await User.findManyByFirstName(firstName) + } else { + foundUsers = await User.findAll() + } + + const formattedUsers = foundUsers.map((user) => { + return { + ...user.toJSON().user + } + }) + + return sendDataResponse(res, 200, { users: formattedUsers }) +} + +export const updateById = async (req, res) => { + const { cohort_id: cohortId } = req.body + + if (!cohortId) { + return sendDataResponse(res, 400, { cohort_id: 'Cohort ID is required' }) + } + + return sendDataResponse(res, 201, { user: { cohort_id: cohortId } }) +} From 354ac4544335d0bf3172cd672d6f56e422ccae91 Mon Sep 17 00:00:00 2001 From: Tuva Aarseth Date: Mon, 28 Oct 2024 15:59:50 +0100 Subject: [PATCH 05/72] First commit --- src/controllers/cohort.js | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/controllers/cohort.js b/src/controllers/cohort.js index cc39365b..818f14d4 100644 --- a/src/controllers/cohort.js +++ b/src/controllers/cohort.js @@ -1,12 +1,12 @@ -import { createCohort } from '../domain/cohort.js' -import { sendDataResponse, sendMessageResponse } from '../utils/responses.js' - -export const create = async (req, res) => { - try { - const createdCohort = await createCohort() - - return sendDataResponse(res, 201, createdCohort) - } catch (e) { - return sendMessageResponse(res, 500, 'Unable to create cohort') - } -} +import { createCohort } from '../domain/cohort.js' +import { sendDataResponse, sendMessageResponse } from '../utils/responses.js' + +export const create = async (req, res) => { + try { + const createdCohort = await createCohort() + + return sendDataResponse(res, 201, createdCohort) + } catch (e) { + return sendMessageResponse(res, 500, 'Unable to create cohort') + } +} From f5d0718b6823a0c9c044b37c8107021489afb821 Mon Sep 17 00:00:00 2001 From: tvaltn Date: Tue, 29 Oct 2024 09:33:15 +0100 Subject: [PATCH 06/72] removed test --- src/controllers/user.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/controllers/user.js b/src/controllers/user.js index 95d752b7..40ff0f1c 100644 --- a/src/controllers/user.js +++ b/src/controllers/user.js @@ -1,6 +1,6 @@ import User from '../domain/user.js' import { sendDataResponse, sendMessageResponse } from '../utils/responses.js' -// Test commit + export const create = async (req, res) => { const userToCreate = await User.fromJson(req.body) From f0cc81577efa09e3c08b4340aacbc2df43608f64 Mon Sep 17 00:00:00 2001 From: Ingeborg Brommeland Austeid Date: Tue, 29 Oct 2024 09:38:06 +0100 Subject: [PATCH 07/72] adds password validation --- src/controllers/user.js | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/src/controllers/user.js b/src/controllers/user.js index 944ab9eb..69298ba5 100644 --- a/src/controllers/user.js +++ b/src/controllers/user.js @@ -1,10 +1,34 @@ import User from '../domain/user.js' import { sendDataResponse, sendMessageResponse } from '../utils/responses.js' +const validatePassword = (password) => { + const minLength = 8 + const hasUpperCase = /[A-Z]/.test(password) + const hasNumber = /\d/.test(password) + const hasSpecialChar = /[!@#$%^&*(),.?":{}|<>]/.test(password) + + if (password.length < minLength) { + return 'Password must be at least 8 characters long' + } + if (!hasUpperCase) { + return 'Password must contain at least one uppercase letter' + } + if (!hasNumber) { + return 'Password must contain at least one number' + } + if (!hasSpecialChar) { + return 'Password must contain at least one special character' + } + return null +} + export const create = async (req, res) => { - const userToCreate = await User.fromJson(req.body) + const passwordError = validatePassword(req.body.password) + if (passwordError) { + return sendDataResponse(res, 400, { password: passwordError }) + } - // Add password validation + const userToCreate = await User.fromJson(req.body) try { const existingUser = await User.findByEmail(userToCreate.email) From b1e5ec30bd62bcaf6225fcf4b60b0a20b0ff1c4b Mon Sep 17 00:00:00 2001 From: Tuva Aarseth Date: Tue, 29 Oct 2024 09:52:35 +0100 Subject: [PATCH 08/72] Updated openapi.yml with profileImage --- docs/openapi.yml | 1084 +++++++++++++++++++++++----------------------- 1 file changed, 547 insertions(+), 537 deletions(-) diff --git a/docs/openapi.yml b/docs/openapi.yml index 5f2a05f2..d4deb04d 100644 --- a/docs/openapi.yml +++ b/docs/openapi.yml @@ -1,537 +1,547 @@ -openapi: 3.0.3 -info: - title: Team Dev Server API - description: |- - version: 1.0 - -servers: - - url: http://localhost:4000/ -tags: - - name: user - - name: post - - name: cohort - - name: log -paths: - /users: - post: - tags: - - user - summary: Create user - description: Create new user - operationId: createUser - requestBody: - description: User registration details - content: - application/json: - schema: - $ref: '#/components/schemas/CreateUser' - responses: - '201': - description: Created - content: - application/json: - schema: - $ref: '#/components/schemas/CreatedUser' - get: - tags: - - user - summary: Get all users by first name if provided - description: '' - operationId: getAllUsers - security: - - bearerAuth: [] - parameters: - - name: firstName - in: query - description: Search all users by first name if provided (case-sensitive and exact string matches only) - schema: - type: string - responses: - '200': - description: successful operation - content: - application/json: - schema: - $ref: '#/components/schemas/AllUsers' - '400': - description: fail - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - '404': - description: fail - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - /login: - post: - tags: - - user - summary: Localhost Login - description: '' - operationId: loginUser - requestBody: - description: User login information - content: - application/json: - schema: - $ref: '#/components/schemas/login' - responses: - '200': - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/loginRes' - - '400': - description: Invalid username/password supplied - - /users/{id}: - get: - tags: - - user - summary: Get user by user id - description: '' - operationId: getUserByID - security: - - bearerAuth: [] - parameters: - - name: id - in: path - description: 'The name that needs to be fetched. Use user1 for testing. ' - required: true - schema: - type: string - responses: - '200': - description: successful operation - content: - application/json: - schema: - type: object - properties: - status: - type: string - data: - $ref: '#/components/schemas/User' - - '400': - description: fail - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - '404': - description: fail - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - patch: - tags: - - user - summary: Update a user - description: Only users with a TEACHER role can update the cohortId or role. Users with Students role can only update their own details. - operationId: userUpdate - security: - - bearerAuth: [] - parameters: - - name: id - in: path - description: 'The user id that needs to be updated' - required: true - schema: - type: string - requestBody: - description: The profile info - content: - application/json: - schema: - $ref: '#/components/schemas/UpdateUser' - responses: - '201': - description: Successful operation - content: - application/json: - schema: - $ref: '#/components/schemas/CreatedUser' - '401': - description: fail - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - /posts: - post: - tags: - - post - summary: Create post - description: This can only be done by the logged in user. - operationId: createPost - security: - - bearerAuth: [] - requestBody: - description: Created post object - content: - application/json: - schema: - type: object - properties: - content: - type: string - responses: - 201: - description: success - content: - application/json: - schema: - $ref: '#/components/schemas/Post' - 400: - description: fail - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - get: - tags: - - post - summary: Get all posts - description: get all posts - operationId: getPosts - security: - - bearerAuth: [] - responses: - '200': - description: Successful operation - content: - application/json: - schema: - $ref: '#/components/schemas/Posts' - '401': - description: fail - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - /logs: - post: - tags: - - log - summary: Create delivery log - description: This can only be done by an authorised teacher user. - operationId: createLog - security: - - bearerAuth: [] - requestBody: - description: Created log object - content: - application/json: - schema: - type: object - properties: - date: - type: string - cohortId: - type: integer - lines: - type: array - items: - type: object - properties: - content: - type: string - responses: - 201: - description: success - content: - application/json: - schema: - $ref: '#/components/schemas/Log' - '401': - description: Unauthorised - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - /cohorts: - post: - tags: - - cohort - summary: Create a cohort - description: This can only be done by the logged in user with role TEACHER. - operationId: createCohort - security: - - bearerAuth: [] - responses: - 201: - description: success - content: - application/json: - schema: - type: object - properties: - status: - type: string - data: - properties: - cohort: - $ref: '#/components/schemas/Cohort' - 400: - description: fail - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - -components: - securitySchemes: - bearerAuth: - type: http - scheme: bearer - bearerFormat: JWT - schemas: - Post: - type: object - properties: - status: - type: string - data: - properties: - post: - properties: - id: - type: integer - content: - type: string - - Cohort: - type: object - properties: - id: - type: integer - createdAt: - type: string - format: string - updatedAt: - type: string - format: string - - AllUsers: - type: object - properties: - status: - type: string - data: - type: object - properties: - users: - type: array - items: - $ref: '#/components/schemas/User' - - User: - type: object - properties: - id: - type: integer - email: - type: string - role: - type: string - cohortId: - type: integer - firstName: - type: string - lastName: - type: string - bio: - type: string - githubUrl: - type: string - - CreateUser: - type: object - properties: - firstName: - type: string - lastName: - type: string - email: - type: string - bio: - type: string - githubUrl: - type: string - password: - type: string - - UpdateUser: - type: object - properties: - email: - type: string - password: - type: string - cohortId: - type: integer - role: - type: string - firstName: - type: string - lastName: - type: string - bio: - type: string - githubUrl: - type: string - - Posts: - type: object - properties: - status: - type: string - data: - type: object - properties: - posts: - type: array - items: - type: object - properties: - id: - type: integer - content: - type: string - createdAt: - type: string - format: string - updatedAt: - type: string - format: string - author: - type: object - properties: - id: - type: integer - cohortId: - type: integer - role: - type: string - firstName: - type: string - lastName: - type: string - bio: - type: string - githubUrl: - type: string - profileImageUrl: - type: string - - CreatedUser: - type: object - properties: - status: - type: string - example: success - data: - properties: - user: - properties: - id: - type: integer - email: - type: string - cohortId: - type: integer - role: - type: string - firstName: - type: string - lastName: - type: string - bio: - type: string - githubUrl: - type: string - login: - type: object - properties: - email: - type: string - password: - type: string - - loginRes: - type: object - properties: - status: - type: string - data: - properties: - token: - type: string - user: - properties: - id: - type: integer - email: - type: string - cohortId: - type: integer - role: - type: string - firstName: - type: string - lastName: - type: string - bio: - type: string - githubUrl: - type: string - Error: - type: object - properties: - status: - type: string - data: - properties: - error: - type: string - - Log: - type: object - properties: - status: - type: string - data: - properties: - log: - properties: - id: - type: integer - cohortId: - type: integer - date: - type: string - author: - type: object - properties: - id: - type: integer - firstName: - type: string - lastName: - type: string - lines: - type: array - items: - type: object - properties: - id: - type: integer - content: - type: string +openapi: 3.0.3 +info: + title: Team Dev Server API + description: |- + version: "1.0" + +servers: + - url: "http://localhost:4000/" +tags: + - name: user + - name: post + - name: cohort + - name: log +paths: + /users: + post: + tags: + - user + summary: Create user + description: Create new user + operationId: createUser + requestBody: + description: User registration details + content: + application/json: + schema: + $ref: '#/components/schemas/CreateUser' + responses: + '201': + description: Created + content: + application/json: + schema: + $ref: '#/components/schemas/CreatedUser' + get: + tags: + - user + summary: Get all users by first name if provided + description: '' + operationId: getAllUsers + security: + - bearerAuth: [] + parameters: + - name: firstName + in: query + description: Search all users by first name if provided (case-sensitive and exact string matches only) + schema: + type: string + responses: + '200': + description: successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/AllUsers' + '400': + description: fail + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '404': + description: fail + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + /login: + post: + tags: + - user + summary: Localhost Login + description: '' + operationId: loginUser + requestBody: + description: User login information + content: + application/json: + schema: + $ref: '#/components/schemas/login' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/loginRes' + + '400': + description: Invalid username/password supplied + + /users/{id}: + get: + tags: + - user + summary: Get user by user id + description: '' + operationId: getUserByID + security: + - bearerAuth: [] + parameters: + - name: id + in: path + description: 'The name that needs to be fetched. Use user1 for testing. ' + required: true + schema: + type: string + responses: + '200': + description: successful operation + content: + application/json: + schema: + type: object + properties: + status: + type: string + data: + $ref: '#/components/schemas/User' + + '400': + description: fail + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '404': + description: fail + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + patch: + tags: + - user + summary: Update a user + description: Only users with a TEACHER role can update the cohortId or role. Users with Students role can only update their own details. + operationId: userUpdate + security: + - bearerAuth: [] + parameters: + - name: id + in: path + description: 'The user id that needs to be updated' + required: true + schema: + type: string + requestBody: + description: The profile info + content: + application/json: + schema: + $ref: '#/components/schemas/UpdateUser' + responses: + '201': + description: Successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/CreatedUser' + '401': + description: fail + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + /posts: + post: + tags: + - post + summary: Create post + description: This can only be done by the logged in user. + operationId: createPost + security: + - bearerAuth: [] + requestBody: + description: Created post object + content: + application/json: + schema: + type: object + properties: + content: + type: string + responses: + 201: + description: success + content: + application/json: + schema: + $ref: '#/components/schemas/Post' + 400: + description: fail + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + get: + tags: + - post + summary: Get all posts + description: get all posts + operationId: getPosts + security: + - bearerAuth: [] + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/Posts' + '401': + description: fail + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + /logs: + post: + tags: + - log + summary: Create delivery log + description: This can only be done by an authorised teacher user. + operationId: createLog + security: + - bearerAuth: [] + requestBody: + description: Created log object + content: + application/json: + schema: + type: object + properties: + date: + type: string + cohortId: + type: integer + lines: + type: array + items: + type: object + properties: + content: + type: string + responses: + 201: + description: success + content: + application/json: + schema: + $ref: '#/components/schemas/Log' + '401': + description: Unauthorised + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + /cohorts: + post: + tags: + - cohort + summary: Create a cohort + description: This can only be done by the logged in user with role TEACHER. + operationId: createCohort + security: + - bearerAuth: [] + responses: + 201: + description: success + content: + application/json: + schema: + type: object + properties: + status: + type: string + data: + properties: + cohort: + $ref: '#/components/schemas/Cohort' + 400: + description: fail + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + +components: + securitySchemes: + bearerAuth: + type: http + scheme: bearer + bearerFormat: JWT + schemas: + Post: + type: object + properties: + status: + type: string + data: + properties: + post: + properties: + id: + type: integer + content: + type: string + + Cohort: + type: object + properties: + id: + type: integer + createdAt: + type: string + format: string + updatedAt: + type: string + format: string + + AllUsers: + type: object + properties: + status: + type: string + data: + type: object + properties: + users: + type: array + items: + $ref: '#/components/schemas/User' + + User: + type: object + properties: + id: + type: integer + email: + type: string + role: + type: string + cohortId: + type: integer + firstName: + type: string + lastName: + type: string + bio: + type: string + githubUrl: + type: string + profileImage: + type: string + + CreateUser: + type: object + properties: + firstName: + type: string + lastName: + type: string + email: + type: string + bio: + type: string + githubUrl: + type: string + password: + type: string + profileImage: + type: string + + UpdateUser: + type: object + properties: + email: + type: string + password: + type: string + cohortId: + type: integer + role: + type: string + firstName: + type: string + lastName: + type: string + bio: + type: string + githubUrl: + type: string + profileImage: + type: string + + Posts: + type: object + properties: + status: + type: string + data: + type: object + properties: + posts: + type: array + items: + type: object + properties: + id: + type: integer + content: + type: string + createdAt: + type: string + format: string + updatedAt: + type: string + format: string + author: + type: object + properties: + id: + type: integer + cohortId: + type: integer + role: + type: string + firstName: + type: string + lastName: + type: string + bio: + type: string + githubUrl: + type: string + profileImage: + type: string + + CreatedUser: + type: object + properties: + status: + type: string + example: success + data: + properties: + user: + properties: + id: + type: integer + email: + type: string + cohortId: + type: integer + role: + type: string + firstName: + type: string + lastName: + type: string + bio: + type: string + githubUrl: + type: string + profileImage: + type: string + login: + type: object + properties: + email: + type: string + password: + type: string + + loginRes: + type: object + properties: + status: + type: string + data: + properties: + token: + type: string + user: + properties: + id: + type: integer + email: + type: string + cohortId: + type: integer + role: + type: string + firstName: + type: string + lastName: + type: string + bio: + type: string + githubUrl: + type: string + profileImage: + type: string + Error: + type: object + properties: + status: + type: string + data: + properties: + error: + type: string + + Log: + type: object + properties: + status: + type: string + data: + properties: + log: + properties: + id: + type: integer + cohortId: + type: integer + date: + type: string + author: + type: object + properties: + id: + type: integer + firstName: + type: string + lastName: + type: string + lines: + type: array + items: + type: object + properties: + id: + type: integer + content: + type: string From 2034c1368810359ca749dd6b35c73e46a48b9678 Mon Sep 17 00:00:00 2001 From: Tuva Aarseth Date: Tue, 29 Oct 2024 10:10:46 +0100 Subject: [PATCH 09/72] Added profileImage to prisma schema and seed.js --- .../migration.sql | 2 + .../migration.sql | 1 + prisma/schema.prisma | 139 ++++++------ prisma/seed.js | 203 +++++++++--------- 4 files changed, 177 insertions(+), 168 deletions(-) create mode 100644 prisma/migrations/20241029090014_add_profile_image/migration.sql create mode 100644 prisma/migrations/20241029090826_edited_profile_image/migration.sql diff --git a/prisma/migrations/20241029090014_add_profile_image/migration.sql b/prisma/migrations/20241029090014_add_profile_image/migration.sql new file mode 100644 index 00000000..b47489b4 --- /dev/null +++ b/prisma/migrations/20241029090014_add_profile_image/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "Profile" ADD COLUMN "profileImage" TEXT; diff --git a/prisma/migrations/20241029090826_edited_profile_image/migration.sql b/prisma/migrations/20241029090826_edited_profile_image/migration.sql new file mode 100644 index 00000000..af5102c8 --- /dev/null +++ b/prisma/migrations/20241029090826_edited_profile_image/migration.sql @@ -0,0 +1 @@ +-- This is an empty migration. \ No newline at end of file diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 72ec5632..3a3b150f 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -1,69 +1,70 @@ -// This is your Prisma schema file, -// learn more about it in the docs: https://pris.ly/d/prisma-schema - -generator client { - provider = "prisma-client-js" -} - -datasource db { - provider = "postgresql" - url = env("DATABASE_URL") - shadowDatabaseUrl = env("SHADOW_DATABASE_URL") -} - -enum Role { - STUDENT - TEACHER -} - -model User { - id Int @id @default(autoincrement()) - email String @unique - password String - role Role @default(STUDENT) - profile Profile? - cohortId Int? - cohort Cohort? @relation(fields: [cohortId], references: [id]) - posts Post[] - deliveryLogs DeliveryLog[] -} - -model Profile { - id Int @id @default(autoincrement()) - userId Int @unique - user User @relation(fields: [userId], references: [id]) - firstName String - lastName String - bio String? - githubUrl String? -} - -model Cohort { - id Int @id @default(autoincrement()) - users User[] - deliveryLogs DeliveryLog[] -} - -model Post { - id Int @id @default(autoincrement()) - content String - userId Int - user User @relation(fields: [userId], references: [id]) -} - -model DeliveryLog { - id Int @id @default(autoincrement()) - date DateTime - userId Int - user User @relation(fields: [userId], references: [id]) - cohortId Int - cohort Cohort @relation(fields: [cohortId], references: [id]) - lines DeliveryLogLine[] -} - -model DeliveryLogLine { - id Int @id @default(autoincrement()) - content String - logId Int - log DeliveryLog @relation(fields: [logId], references: [id]) -} +// This is your Prisma schema file, +// learn more about it in the docs: https://pris.ly/d/prisma-schema + +generator client { + provider = "prisma-client-js" +} + +datasource db { + provider = "postgresql" + url = env("DATABASE_URL") + shadowDatabaseUrl = env("SHADOW_DATABASE_URL") +} + +enum Role { + STUDENT + TEACHER +} + +model User { + id Int @id @default(autoincrement()) + email String @unique + password String + role Role @default(STUDENT) + profile Profile? + cohortId Int? + cohort Cohort? @relation(fields: [cohortId], references: [id]) + posts Post[] + deliveryLogs DeliveryLog[] +} + +model Profile { + id Int @id @default(autoincrement()) + userId Int @unique + user User @relation(fields: [userId], references: [id]) + firstName String + lastName String + bio String? + githubUrl String? + profileImage String? +} + +model Cohort { + id Int @id @default(autoincrement()) + users User[] + deliveryLogs DeliveryLog[] +} + +model Post { + id Int @id @default(autoincrement()) + content String + userId Int + user User @relation(fields: [userId], references: [id]) +} + +model DeliveryLog { + id Int @id @default(autoincrement()) + date DateTime + userId Int + user User @relation(fields: [userId], references: [id]) + cohortId Int + cohort Cohort @relation(fields: [cohortId], references: [id]) + lines DeliveryLogLine[] +} + +model DeliveryLogLine { + id Int @id @default(autoincrement()) + content String + logId Int + log DeliveryLog @relation(fields: [logId], references: [id]) +} diff --git a/prisma/seed.js b/prisma/seed.js index 21684795..7892170a 100644 --- a/prisma/seed.js +++ b/prisma/seed.js @@ -1,99 +1,104 @@ -import { PrismaClient } from '@prisma/client' -import bcrypt from 'bcrypt' -const prisma = new PrismaClient() - -async function seed() { - const cohort = await createCohort() - - const student = await createUser( - 'student@test.com', - 'Testpassword1!', - cohort.id, - 'Joe', - 'Bloggs', - 'Hello, world!', - 'student1' - ) - const teacher = await createUser( - 'teacher@test.com', - 'Testpassword1!', - null, - 'Rick', - 'Sanchez', - 'Hello there!', - 'teacher1', - 'TEACHER' - ) - - await createPost(student.id, 'My first post!') - await createPost(teacher.id, 'Hello, students') - - process.exit(0) -} - -async function createPost(userId, content) { - const post = await prisma.post.create({ - data: { - userId, - content - }, - include: { - user: true - } - }) - - console.info('Post created', post) - - return post -} - -async function createCohort() { - const cohort = await prisma.cohort.create({ - data: {} - }) - - console.info('Cohort created', cohort) - - return cohort -} - -async function createUser( - email, - password, - cohortId, - firstName, - lastName, - bio, - githubUrl, - role = 'STUDENT' -) { - const user = await prisma.user.create({ - data: { - email, - password: await bcrypt.hash(password, 8), - role, - cohortId, - profile: { - create: { - firstName, - lastName, - bio, - githubUrl - } - } - }, - include: { - profile: true - } - }) - - console.info(`${role} created`, user) - - return user -} - -seed().catch(async (e) => { - console.error(e) - await prisma.$disconnect() - process.exit(1) -}) +import { PrismaClient } from '@prisma/client' +import bcrypt from 'bcrypt' +const prisma = new PrismaClient() + +async function seed() { + const cohort = await createCohort() + + const student = await createUser( + 'student@test.com', + 'Testpassword1!', + cohort.id, + 'Joe', + 'Bloggs', + 'Hello, world!', + 'student1', + null, + 'STUDENT' + ) + const teacher = await createUser( + 'teacher@test.com', + 'Testpassword1!', + null, + 'Rick', + 'Sanchez', + 'Hello there!', + 'teacher1', + null, + 'TEACHER' + ) + + await createPost(student.id, 'My first post!') + await createPost(teacher.id, 'Hello, students') + + process.exit(0) +} + +async function createPost(userId, content) { + const post = await prisma.post.create({ + data: { + userId, + content + }, + include: { + user: true + } + }) + + console.info('Post created', post) + + return post +} + +async function createCohort() { + const cohort = await prisma.cohort.create({ + data: {} + }) + + console.info('Cohort created', cohort) + + return cohort +} + +async function createUser( + email, + password, + cohortId, + firstName, + lastName, + bio, + githubUrl, + profileImage, + role = 'STUDENT' +) { + const user = await prisma.user.create({ + data: { + email, + password: await bcrypt.hash(password, 8), + role, + cohortId, + profile: { + create: { + firstName, + lastName, + bio, + githubUrl, + profileImage + } + } + }, + include: { + profile: true + } + }) + + console.info(`${role} created`, user) + + return user +} + +seed().catch(async (e) => { + console.error(e) + await prisma.$disconnect() + process.exit(1) +}) From 9c21e2b1b877a20b840b7ce71752297cf2b4ce40 Mon Sep 17 00:00:00 2001 From: Tuva Aarseth Date: Tue, 29 Oct 2024 10:11:38 +0100 Subject: [PATCH 10/72] Removed failed migration --- .../migrations/20241029090826_edited_profile_image/migration.sql | 1 - 1 file changed, 1 deletion(-) delete mode 100644 prisma/migrations/20241029090826_edited_profile_image/migration.sql diff --git a/prisma/migrations/20241029090826_edited_profile_image/migration.sql b/prisma/migrations/20241029090826_edited_profile_image/migration.sql deleted file mode 100644 index af5102c8..00000000 --- a/prisma/migrations/20241029090826_edited_profile_image/migration.sql +++ /dev/null @@ -1 +0,0 @@ --- This is an empty migration. \ No newline at end of file From 3f8c12ce72b17f43ca7a0a3b11e8f5fa9cd3ca54 Mon Sep 17 00:00:00 2001 From: tvaltn Date: Tue, 29 Oct 2024 10:51:57 +0100 Subject: [PATCH 11/72] added user patch functionality --- src/controllers/user.js | 33 ++++++++++++++++++++++++++++----- src/domain/user.js | 33 ++++++++++++++++++++++++++++++++- 2 files changed, 60 insertions(+), 6 deletions(-) diff --git a/src/controllers/user.js b/src/controllers/user.js index 40ff0f1c..a177bee5 100644 --- a/src/controllers/user.js +++ b/src/controllers/user.js @@ -57,11 +57,34 @@ export const getAll = async (req, res) => { } export const updateById = async (req, res) => { - const { cohort_id: cohortId } = req.body - - if (!cohortId) { - return sendDataResponse(res, 400, { cohort_id: 'Cohort ID is required' }) + const id = parseInt(req.params.id) + const userToUpdate = await User.fromJson(req.body) + userToUpdate.id = id + + // Update cohortId and role to userToUpdate (only if logged in role is Teacher) + // otherwise, keep the existing values of the user + if (req.user.role === 'TEACHER') { + userToUpdate.cohortId = parseInt(req.body.cohortId) + const roleId = parseInt(req.body.role) + if (roleId === 1) { + userToUpdate.role = 'STUDENT' + } else if (roleId === 2) { + userToUpdate.role = 'TEACHER' + } + } else { + const existingUser = await User.findById(id) + userToUpdate.cohortId = existingUser.cohortId + userToUpdate.role = existingUser.role } - return sendDataResponse(res, 201, { user: { cohort_id: cohortId } }) + try { + if (!userToUpdate.cohortId) { + return sendDataResponse(res, 400, { cohort_id: userToUpdate }) + } + const updatedUser = await userToUpdate.update() + + return sendDataResponse(res, 201, updatedUser) + } catch (error) { + return sendMessageResponse(res, 500, 'Unable to update user') + } } diff --git a/src/domain/user.js b/src/domain/user.js index fd7734c7..be0b437f 100644 --- a/src/domain/user.js +++ b/src/domain/user.js @@ -7,7 +7,7 @@ export default class User { * take as inputs, what types they return, and other useful information that JS doesn't have built in * @tutorial https://www.valentinog.com/blog/jsdoc * - * @param { { id: int, cohortId: int, email: string, profile: { firstName: string, lastName: string, bio: string, githubUrl: string } } } user + * @param { { id: int, cohortId: int, email: string, role: string, profile: { firstName: string, lastName: string, bio: string, githubUrl: string } } } user * @returns {User} */ static fromDb(user) { @@ -118,6 +118,37 @@ export default class User { return User.fromDb(createdUser) } + /** + * @returns {User} + * A user instance containing the updated user data + */ + async update() { + const updatedUser = await dbClient.user.update({ + where: { + id: this.id + }, + data: { + email: this.email, + password: this.passwordHash, + role: this.role, + cohortId: this.cohortId, + profile: { + update: { + firstName: this.firstName, + lastName: this.lastName, + bio: this.bio, + githubUrl: this.githubUrl + } + } + }, + include: { + profile: true + } + }) + + return User.fromDb(updatedUser) + } + static async findByEmail(email) { return User._findByUnique('email', email) } From dc3132b00bc867eabcc890f248c80ade2b356a9c Mon Sep 17 00:00:00 2001 From: Muzea001 Date: Tue, 29 Oct 2024 11:26:57 +0100 Subject: [PATCH 12/72] added relevant fields, changed class structure to match newly created fields --- docs/openapi.yml | 87 +++++++++++++++++-- .../migration.sql | 6 ++ prisma/schema.prisma | 5 ++ prisma/seed.js | 32 +++++-- src/controllers/user.js | 6 +- src/domain/user.js | 54 ++++++++++-- 6 files changed, 167 insertions(+), 23 deletions(-) create mode 100644 prisma/migrations/20241029082651_add_profile_fields/migration.sql diff --git a/docs/openapi.yml b/docs/openapi.yml index 5f2a05f2..d4b9b148 100644 --- a/docs/openapi.yml +++ b/docs/openapi.yml @@ -85,10 +85,8 @@ paths: application/json: schema: $ref: '#/components/schemas/loginRes' - '400': description: Invalid username/password supplied - /users/{id}: get: tags: @@ -117,7 +115,6 @@ paths: type: string data: $ref: '#/components/schemas/User' - '400': description: fail content: @@ -314,10 +311,10 @@ components: type: integer createdAt: type: string - format: string + format: date-time updatedAt: type: string - format: string + format: date-time AllUsers: type: object @@ -351,6 +348,18 @@ components: type: string githubUrl: type: string + username: + type: string + mobile: + type: string + specialism: + type: string + startDate: + type: string + format: date-time + endDate: + type: string + format: date-time CreateUser: type: object @@ -365,6 +374,18 @@ components: type: string githubUrl: type: string + username: + type: string + mobile: + type: string + specialism: + type: string + startDate: + type: string + format: date-time + endDate: + type: string + format: date-time password: type: string @@ -387,6 +408,18 @@ components: type: string githubUrl: type: string + username: + type: string + mobile: + type: string + specialism: + type: string + startDate: + type: string + format: date-time + endDate: + type: string + format: date-time Posts: type: object @@ -407,10 +440,10 @@ components: type: string createdAt: type: string - format: string + format: date-time updatedAt: type: string - format: string + format: date-time author: type: object properties: @@ -428,6 +461,18 @@ components: type: string githubUrl: type: string + username: + type: string + mobile: + type: string + specialism: + type: string + startDate: + type: string + format: date-time + endDate: + type: string + format: date-time profileImageUrl: type: string @@ -457,6 +502,19 @@ components: type: string githubUrl: type: string + username: + type: string + mobile: + type: string + specialism: + type: string + startDate: + type: string + format: date-time + endDate: + type: string + format: date-time + login: type: object properties: @@ -492,6 +550,19 @@ components: type: string githubUrl: type: string + username: + type: string + mobile: + type: string + specialism: + type: string + startDate: + type: string + format: date-time + endDate: + type: string + format: date-time + Error: type: object properties: @@ -534,4 +605,4 @@ components: id: type: integer content: - type: string + type: string \ No newline at end of file diff --git a/prisma/migrations/20241029082651_add_profile_fields/migration.sql b/prisma/migrations/20241029082651_add_profile_fields/migration.sql new file mode 100644 index 00000000..4ca11f21 --- /dev/null +++ b/prisma/migrations/20241029082651_add_profile_fields/migration.sql @@ -0,0 +1,6 @@ +-- AlterTable +ALTER TABLE "Profile" ADD COLUMN "endDate" TIMESTAMP(3), +ADD COLUMN "mobile" TEXT NOT NULL DEFAULT E'', +ADD COLUMN "specialism" TEXT NOT NULL DEFAULT E'', +ADD COLUMN "startDate" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, +ADD COLUMN "username" TEXT NOT NULL DEFAULT E''; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 72ec5632..372a6029 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -36,6 +36,11 @@ model Profile { lastName String bio String? githubUrl String? + username String @default("") + mobile String @default("") + specialism String @default("") + startDate DateTime @default(now()) + endDate DateTime? } model Cohort { diff --git a/prisma/seed.js b/prisma/seed.js index 21684795..a42ec86b 100644 --- a/prisma/seed.js +++ b/prisma/seed.js @@ -12,7 +12,12 @@ async function seed() { 'Joe', 'Bloggs', 'Hello, world!', - 'student1' + 'student1@github.com', + 'student1', + '123-456-7890', // mobile + 'Software Engineering', // specialism + new Date('2023-01-01'), // startDate + new Date('2023-12-31') // endDate ) const teacher = await createUser( 'teacher@test.com', @@ -21,7 +26,12 @@ async function seed() { 'Rick', 'Sanchez', 'Hello there!', + 'teacher1@git.com', 'teacher1', + '987-654-3210', + 'Teaching', + new Date('2022-01-01'), + new Date('2022-12-31'), 'TEACHER' ) @@ -58,14 +68,19 @@ async function createCohort() { } async function createUser( - email, - password, - cohortId, firstName, lastName, + email, bio, githubUrl, - role = 'STUDENT' + username, + mobile, + specialism, + startDate, + endDate, + password, + role = 'STUDENT', + cohortId = null ) { const user = await prisma.user.create({ data: { @@ -78,7 +93,12 @@ async function createUser( firstName, lastName, bio, - githubUrl + githubUrl, + username, + mobile, + specialism, + startDate: new Date(startDate), + endDate: new Date(endDate) } } }, diff --git a/src/controllers/user.js b/src/controllers/user.js index 24388420..43742c07 100644 --- a/src/controllers/user.js +++ b/src/controllers/user.js @@ -3,9 +3,9 @@ import { sendDataResponse, sendMessageResponse } from '../utils/responses.js' /* CREATES A NEW USER */ export const create = async (req, res) => { - const userToCreate = await User.fromJson(req.body) - try { + const userToCreate = await User.fromJson(req.body) + const existingUser = await User.findByEmail(userToCreate.email) if (existingUser) { @@ -16,10 +16,10 @@ export const create = async (req, res) => { return sendDataResponse(res, 201, createdUser) } catch (error) { + console.error('Error creating user:', error) return sendMessageResponse(res, 500, 'Unable to create new user') } } - /* GETS A USER BY ID */ export const getById = async (req, res) => { const id = parseInt(req.params.id) diff --git a/src/domain/user.js b/src/domain/user.js index fd7734c7..34ab4b7f 100644 --- a/src/domain/user.js +++ b/src/domain/user.js @@ -7,7 +7,7 @@ export default class User { * take as inputs, what types they return, and other useful information that JS doesn't have built in * @tutorial https://www.valentinog.com/blog/jsdoc * - * @param { { id: int, cohortId: int, email: string, profile: { firstName: string, lastName: string, bio: string, githubUrl: string } } } user + * @param { { id: int, cohortId: int, email: string, profile: { firstName: string, lastName: string, bio: string, githubUrl: string, username:string, mobile } } } user * @returns {User} */ static fromDb(user) { @@ -19,6 +19,11 @@ export default class User { user.email, user.profile?.bio, user.profile?.githubUrl, + user.profile?.username, + user.profile?.mobile, + user.profile?.specialism, + user.profile?.startDate, + user.profile?.endDate, user.password, user.role ) @@ -26,7 +31,19 @@ export default class User { static async fromJson(json) { // eslint-disable-next-line camelcase - const { firstName, lastName, email, biography, githubUrl, password } = json + const { + firstName, + lastName, + email, + bio, + githubUrl, + username, + mobile, + specialism, + startDate, + endDate, + password + } = json const passwordHash = await bcrypt.hash(password, 8) @@ -36,8 +53,13 @@ export default class User { firstName, lastName, email, - biography, + bio, githubUrl, + username, + mobile, + specialism, + startDate, + endDate, passwordHash ) } @@ -50,6 +72,11 @@ export default class User { email, bio, githubUrl, + username, + mobile, + specialism, + startDate, + endDate, passwordHash = null, role = 'STUDENT' ) { @@ -60,6 +87,11 @@ export default class User { this.email = email this.bio = bio this.githubUrl = githubUrl + this.username = username + this.mobile = mobile + this.specialism = specialism + this.startDate = startDate + this.endDate = endDate this.passwordHash = passwordHash this.role = role } @@ -73,8 +105,13 @@ export default class User { firstName: this.firstName, lastName: this.lastName, email: this.email, - biography: this.bio, - githubUrl: this.githubUrl + bio: this.bio, + githubUrl: this.githubUrl, + username: this.username, + mobile: this.mobile, + specialism: this.specialism, + startDate: this.startDate, + endDate: this.endDate } } } @@ -104,7 +141,12 @@ export default class User { firstName: this.firstName, lastName: this.lastName, bio: this.bio, - githubUrl: this.githubUrl + githubUrl: this.githubUrl, + username: this.username, + mobile: this.mobile, + specialism: this.specialism, + startDate: this.startDate, + endDate: this.endDate } } } From dbc91b2bdcc7b5d25a9888b3114c5e2e82ec0e39 Mon Sep 17 00:00:00 2001 From: Ingeborg Brommeland Austeid Date: Tue, 29 Oct 2024 11:27:07 +0100 Subject: [PATCH 13/72] adds validateUser function in middleware\user.js with the password validation --- src/controllers/user.js | 52 ++++++++++++----------------------------- src/middleware/user.js | 31 ++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 37 deletions(-) create mode 100644 src/middleware/user.js diff --git a/src/controllers/user.js b/src/controllers/user.js index 69298ba5..9101e852 100644 --- a/src/controllers/user.js +++ b/src/controllers/user.js @@ -1,49 +1,27 @@ import User from '../domain/user.js' +import { validateUser } from '../middleware/user.js' import { sendDataResponse, sendMessageResponse } from '../utils/responses.js' -const validatePassword = (password) => { - const minLength = 8 - const hasUpperCase = /[A-Z]/.test(password) - const hasNumber = /\d/.test(password) - const hasSpecialChar = /[!@#$%^&*(),.?":{}|<>]/.test(password) +export const create = [ + validateUser, + async (req, res) => { + const userToCreate = await User.fromJson(req.body) - if (password.length < minLength) { - return 'Password must be at least 8 characters long' - } - if (!hasUpperCase) { - return 'Password must contain at least one uppercase letter' - } - if (!hasNumber) { - return 'Password must contain at least one number' - } - if (!hasSpecialChar) { - return 'Password must contain at least one special character' - } - return null -} + try { + const existingUser = await User.findByEmail(userToCreate.email) -export const create = async (req, res) => { - const passwordError = validatePassword(req.body.password) - if (passwordError) { - return sendDataResponse(res, 400, { password: passwordError }) - } + if (existingUser) { + return sendDataResponse(res, 400, { email: 'Email already in use' }) + } - const userToCreate = await User.fromJson(req.body) + const createdUser = await userToCreate.save() - try { - const existingUser = await User.findByEmail(userToCreate.email) - - if (existingUser) { - return sendDataResponse(res, 400, { email: 'Email already in use' }) + return sendDataResponse(res, 201, createdUser) + } catch (error) { + return sendMessageResponse(res, 500, 'Unable to create new user') } - - const createdUser = await userToCreate.save() - - return sendDataResponse(res, 201, createdUser) - } catch (error) { - return sendMessageResponse(res, 500, 'Unable to create new user') } -} +] export const getById = async (req, res) => { const id = parseInt(req.params.id) diff --git a/src/middleware/user.js b/src/middleware/user.js new file mode 100644 index 00000000..756db248 --- /dev/null +++ b/src/middleware/user.js @@ -0,0 +1,31 @@ +import { sendDataResponse } from '../utils/responses.js' + +export async function validateUser(req, res, next) { + const validatePassword = (password) => { + const minLength = 8 + const hasUpperCase = /[A-Z]/.test(password) + const hasNumber = /\d/.test(password) + const hasSpecialChar = /[!@#$%^&*(),.?":{}|<>]/.test(password) + + if (password.length < minLength) { + return 'Password must be at least 8 characters long' + } + if (!hasUpperCase) { + return 'Password must contain at least one uppercase letter' + } + if (!hasNumber) { + return 'Password must contain at least one number' + } + if (!hasSpecialChar) { + return 'Password must contain at least one special character' + } + return null + } + + const passwordError = validatePassword(req.body.password) + if (passwordError) { + return sendDataResponse(res, 400, { password: passwordError }) + } + + next() +} From 04b4a9af77fabf37a3d0bcebb0254a3365bad635 Mon Sep 17 00:00:00 2001 From: Ingeborg Brommeland Austeid Date: Tue, 29 Oct 2024 11:46:49 +0100 Subject: [PATCH 14/72] adds in routes\user.js instead of in controllers\user.js --- src/controllers/user.js | 28 ++++++++++++---------------- src/routes/user.js | 37 ++++++++++++++++++++++--------------- 2 files changed, 34 insertions(+), 31 deletions(-) diff --git a/src/controllers/user.js b/src/controllers/user.js index 9101e852..5829b8ba 100644 --- a/src/controllers/user.js +++ b/src/controllers/user.js @@ -1,27 +1,23 @@ import User from '../domain/user.js' -import { validateUser } from '../middleware/user.js' import { sendDataResponse, sendMessageResponse } from '../utils/responses.js' -export const create = [ - validateUser, - async (req, res) => { - const userToCreate = await User.fromJson(req.body) +export const create = async (req, res) => { + const userToCreate = await User.fromJson(req.body) - try { - const existingUser = await User.findByEmail(userToCreate.email) + try { + const existingUser = await User.findByEmail(userToCreate.email) - if (existingUser) { - return sendDataResponse(res, 400, { email: 'Email already in use' }) - } + if (existingUser) { + return sendDataResponse(res, 400, { email: 'Email already in use' }) + } - const createdUser = await userToCreate.save() + const createdUser = await userToCreate.save() - return sendDataResponse(res, 201, createdUser) - } catch (error) { - return sendMessageResponse(res, 500, 'Unable to create new user') - } + return sendDataResponse(res, 201, createdUser) + } catch (error) { + return sendMessageResponse(res, 500, 'Unable to create new user') } -] +} export const getById = async (req, res) => { const id = parseInt(req.params.id) diff --git a/src/routes/user.js b/src/routes/user.js index 9f63d162..c17edb74 100644 --- a/src/routes/user.js +++ b/src/routes/user.js @@ -1,15 +1,22 @@ -import { Router } from 'express' -import { create, getById, getAll, updateById } from '../controllers/user.js' -import { - validateAuthentication, - validateTeacherRole -} from '../middleware/auth.js' - -const router = Router() - -router.post('/', create) -router.get('/', validateAuthentication, getAll) -router.get('/:id', validateAuthentication, getById) -router.patch('/:id', validateAuthentication, validateTeacherRole, updateById) - -export default router +import { Router } from 'express' +import { create, getById, getAll, updateById } from '../controllers/user.js' +import { + validateAuthentication, + validateTeacherRole +} from '../middleware/auth.js' +import { validateUser } from '../middleware/user.js' + +const router = Router() + +router.post('/', validateUser, create) +router.get('/', validateAuthentication, getAll) +router.get('/:id', validateAuthentication, getById) +router.patch( + '/:id', + validateAuthentication, + validateUser, + validateTeacherRole, + updateById +) + +export default router From 6aa6abdc950c3914d531f0b63b57f40c9028a020 Mon Sep 17 00:00:00 2001 From: tvaltn Date: Tue, 29 Oct 2024 11:54:24 +0100 Subject: [PATCH 15/72] added logged in user routing --- src/controllers/user.js | 3 ++- src/middleware/auth.js | 18 ++++++++++++++++++ src/routes/user.js | 11 +++++++++-- 3 files changed, 29 insertions(+), 3 deletions(-) diff --git a/src/controllers/user.js b/src/controllers/user.js index a177bee5..437d8a40 100644 --- a/src/controllers/user.js +++ b/src/controllers/user.js @@ -79,12 +79,13 @@ export const updateById = async (req, res) => { try { if (!userToUpdate.cohortId) { - return sendDataResponse(res, 400, { cohort_id: userToUpdate }) + return sendDataResponse(res, 400, { cohort_id: 'Cohort ID is required' }) } const updatedUser = await userToUpdate.update() return sendDataResponse(res, 201, updatedUser) } catch (error) { + console.log(error) return sendMessageResponse(res, 500, 'Unable to update user') } } diff --git a/src/middleware/auth.js b/src/middleware/auth.js index baffff47..1e3f6f10 100644 --- a/src/middleware/auth.js +++ b/src/middleware/auth.js @@ -4,6 +4,9 @@ import jwt from 'jsonwebtoken' import User from '../domain/user.js' export async function validateTeacherRole(req, res, next) { + if (res.locals.skipTeacherValidation) { + return next() + } if (!req.user) { return sendMessageResponse(res, 500, 'Unable to verify user') } @@ -17,6 +20,21 @@ export async function validateTeacherRole(req, res, next) { next() } +// Function that checks if the currently logged in user is the same +// one that is being requested, if so, we skip the teacher validation +export async function validateLoggedInUser(req, res, next) { + if (!req.user) { + return sendMessageResponse(res, 500, 'Unable to verify user') + } + + if (req.user.id !== parseInt(req.params.id)) { + return next() + } + + res.locals.skipTeacherValidation = true + next() +} + export async function validateAuthentication(req, res, next) { const header = req.header('authorization') diff --git a/src/routes/user.js b/src/routes/user.js index 9f63d162..371a669d 100644 --- a/src/routes/user.js +++ b/src/routes/user.js @@ -2,7 +2,8 @@ import { Router } from 'express' import { create, getById, getAll, updateById } from '../controllers/user.js' import { validateAuthentication, - validateTeacherRole + validateTeacherRole, + validateLoggedInUser } from '../middleware/auth.js' const router = Router() @@ -10,6 +11,12 @@ const router = Router() router.post('/', create) router.get('/', validateAuthentication, getAll) router.get('/:id', validateAuthentication, getById) -router.patch('/:id', validateAuthentication, validateTeacherRole, updateById) +router.patch( + '/:id', + validateAuthentication, + validateLoggedInUser, + validateTeacherRole, + updateById +) export default router From 647caa2c56eb7290ee76e3a7711072e19acad6b4 Mon Sep 17 00:00:00 2001 From: Tuva Aarseth Date: Tue, 29 Oct 2024 11:54:54 +0100 Subject: [PATCH 16/72] Added profileImage to user.js domain --- src/domain/user.js | 362 +++++++++++++++++++++++---------------------- 1 file changed, 189 insertions(+), 173 deletions(-) diff --git a/src/domain/user.js b/src/domain/user.js index fd7734c7..b8605698 100644 --- a/src/domain/user.js +++ b/src/domain/user.js @@ -1,173 +1,189 @@ -import dbClient from '../utils/dbClient.js' -import bcrypt from 'bcrypt' - -export default class User { - /** - * This is JSDoc - a way for us to tell other developers what types functions/methods - * take as inputs, what types they return, and other useful information that JS doesn't have built in - * @tutorial https://www.valentinog.com/blog/jsdoc - * - * @param { { id: int, cohortId: int, email: string, profile: { firstName: string, lastName: string, bio: string, githubUrl: string } } } user - * @returns {User} - */ - static fromDb(user) { - return new User( - user.id, - user.cohortId, - user.profile?.firstName, - user.profile?.lastName, - user.email, - user.profile?.bio, - user.profile?.githubUrl, - user.password, - user.role - ) - } - - static async fromJson(json) { - // eslint-disable-next-line camelcase - const { firstName, lastName, email, biography, githubUrl, password } = json - - const passwordHash = await bcrypt.hash(password, 8) - - return new User( - null, - null, - firstName, - lastName, - email, - biography, - githubUrl, - passwordHash - ) - } - - constructor( - id, - cohortId, - firstName, - lastName, - email, - bio, - githubUrl, - passwordHash = null, - role = 'STUDENT' - ) { - this.id = id - this.cohortId = cohortId - this.firstName = firstName - this.lastName = lastName - this.email = email - this.bio = bio - this.githubUrl = githubUrl - this.passwordHash = passwordHash - this.role = role - } - - toJSON() { - return { - user: { - id: this.id, - cohort_id: this.cohortId, - role: this.role, - firstName: this.firstName, - lastName: this.lastName, - email: this.email, - biography: this.bio, - githubUrl: this.githubUrl - } - } - } - - /** - * @returns {User} - * A user instance containing an ID, representing the user data created in the database - */ - async save() { - const data = { - email: this.email, - password: this.passwordHash, - role: this.role - } - - if (this.cohortId) { - data.cohort = { - connectOrCreate: { - id: this.cohortId - } - } - } - - if (this.firstName && this.lastName) { - data.profile = { - create: { - firstName: this.firstName, - lastName: this.lastName, - bio: this.bio, - githubUrl: this.githubUrl - } - } - } - const createdUser = await dbClient.user.create({ - data, - include: { - profile: true - } - }) - - return User.fromDb(createdUser) - } - - static async findByEmail(email) { - return User._findByUnique('email', email) - } - - static async findById(id) { - return User._findByUnique('id', id) - } - - static async findManyByFirstName(firstName) { - return User._findMany('firstName', firstName) - } - - static async findAll() { - return User._findMany() - } - - static async _findByUnique(key, value) { - const foundUser = await dbClient.user.findUnique({ - where: { - [key]: value - }, - include: { - profile: true - } - }) - - if (foundUser) { - return User.fromDb(foundUser) - } - - return null - } - - static async _findMany(key, value) { - const query = { - include: { - profile: true - } - } - - if (key !== undefined && value !== undefined) { - query.where = { - profile: { - [key]: value - } - } - } - - const foundUsers = await dbClient.user.findMany(query) - - return foundUsers.map((user) => User.fromDb(user)) - } -} +import dbClient from '../utils/dbClient.js' +import bcrypt from 'bcrypt' + +export default class User { + /** + * This is JSDoc - a way for us to tell other developers what types functions/methods + * take as inputs, what types they return, and other useful information that JS doesn't have built in + * @tutorial https://www.valentinog.com/blog/jsdoc + * + * @param { { id: int, cohortId: int, email: string, profile: { firstName: string, lastName: string, bio: string, githubUrl: string, profileImage: string } } } user + * @returns {User} + */ + static fromDb(user) { + console.log(user) + return new User( + user.id, + user.cohortId, + user.profile?.firstName, + user.profile?.lastName, + user.email, + user.profile?.bio, + user.profile?.githubUrl, + user.password, + user.profile?.profileImage, + user.role + ) + } + + static async fromJson(json) { + // eslint-disable-next-line camelcase + const { + firstName, + lastName, + email, + biography, + githubUrl, + password, + profileImage + } = json + + const passwordHash = await bcrypt.hash(password, 8) + + return new User( + null, + null, + firstName, + lastName, + email, + biography, + githubUrl, + passwordHash, + profileImage + ) + } + + constructor( + id, + cohortId, + firstName, + lastName, + email, + bio, + githubUrl, + passwordHash = null, + profileImage = null, + role = 'STUDENT' + ) { + this.id = id + this.cohortId = cohortId + this.firstName = firstName + this.lastName = lastName + this.email = email + this.bio = bio + this.githubUrl = githubUrl + this.passwordHash = passwordHash + this.role = role + this.profileImage = profileImage + } + + toJSON() { + console.log(this) + return { + user: { + id: this.id, + cohort_id: this.cohortId, + role: this.role, + firstName: this.firstName, + lastName: this.lastName, + email: this.email, + biography: this.bio, + githubUrl: this.githubUrl, + profileImage: this.profileImage + } + } + } + + /** + * @returns {User} + * A user instance containing an ID, representing the user data created in the database + */ + async save() { + const data = { + email: this.email, + password: this.passwordHash, + role: this.role + } + + if (this.cohortId) { + data.cohort = { + connectOrCreate: { + id: this.cohortId + } + } + } + + if (this.firstName && this.lastName) { + data.profile = { + create: { + firstName: this.firstName, + lastName: this.lastName, + bio: this.bio, + githubUrl: this.githubUrl, + profileImage: this.profileImage + } + } + } + const createdUser = await dbClient.user.create({ + data, + include: { + profile: true + } + }) + + return User.fromDb(createdUser) + } + + static async findByEmail(email) { + return User._findByUnique('email', email) + } + + static async findById(id) { + return User._findByUnique('id', id) + } + + static async findManyByFirstName(firstName) { + return User._findMany('firstName', firstName) + } + + static async findAll() { + return User._findMany() + } + + static async _findByUnique(key, value) { + const foundUser = await dbClient.user.findUnique({ + where: { + [key]: value + }, + include: { + profile: true + } + }) + + if (foundUser) { + return User.fromDb(foundUser) + } + + return null + } + + static async _findMany(key, value) { + const query = { + include: { + profile: true + } + } + + if (key !== undefined && value !== undefined) { + query.where = { + profile: { + [key]: value + } + } + } + + const foundUsers = await dbClient.user.findMany(query) + + return foundUsers.map((user) => User.fromDb(user)) + } +} From 86ef6bbfcff4370f9792daebf32b3b56746df78d Mon Sep 17 00:00:00 2001 From: tvaltn Date: Tue, 29 Oct 2024 12:03:16 +0100 Subject: [PATCH 17/72] fixed role format from request body --- src/controllers/user.js | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/controllers/user.js b/src/controllers/user.js index 437d8a40..0333a7e8 100644 --- a/src/controllers/user.js +++ b/src/controllers/user.js @@ -65,12 +65,7 @@ export const updateById = async (req, res) => { // otherwise, keep the existing values of the user if (req.user.role === 'TEACHER') { userToUpdate.cohortId = parseInt(req.body.cohortId) - const roleId = parseInt(req.body.role) - if (roleId === 1) { - userToUpdate.role = 'STUDENT' - } else if (roleId === 2) { - userToUpdate.role = 'TEACHER' - } + userToUpdate.role = req.body.role } else { const existingUser = await User.findById(id) userToUpdate.cohortId = existingUser.cohortId From b56e095e7c9430373e19a8d9b267ef0836d183a4 Mon Sep 17 00:00:00 2001 From: Muzea001 Date: Tue, 29 Oct 2024 13:28:02 +0100 Subject: [PATCH 18/72] fixed order of seeding data --- prisma/seed.js | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/prisma/seed.js b/prisma/seed.js index a42ec86b..de67ec44 100644 --- a/prisma/seed.js +++ b/prisma/seed.js @@ -7,7 +7,6 @@ async function seed() { const student = await createUser( 'student@test.com', - 'Testpassword1!', cohort.id, 'Joe', 'Bloggs', @@ -17,11 +16,12 @@ async function seed() { '123-456-7890', // mobile 'Software Engineering', // specialism new Date('2023-01-01'), // startDate - new Date('2023-12-31') // endDate + new Date('2023-12-31'), + 'STUDENT', + 'Testpassword1!' ) const teacher = await createUser( 'teacher@test.com', - 'Testpassword1!', null, 'Rick', 'Sanchez', @@ -32,7 +32,8 @@ async function seed() { 'Teaching', new Date('2022-01-01'), new Date('2022-12-31'), - 'TEACHER' + 'TEACHER', + 'Testpassword1!' ) await createPost(student.id, 'My first post!') @@ -68,9 +69,10 @@ async function createCohort() { } async function createUser( + email, + cohortId, firstName, lastName, - email, bio, githubUrl, username, @@ -78,9 +80,8 @@ async function createUser( specialism, startDate, endDate, - password, role = 'STUDENT', - cohortId = null + password ) { const user = await prisma.user.create({ data: { From 52145ad1f8948905046eddb46fb4851f0bb369f6 Mon Sep 17 00:00:00 2001 From: Ingeborg Brommeland Austeid Date: Tue, 29 Oct 2024 13:28:59 +0100 Subject: [PATCH 19/72] adds 400 response to /users in openapi.yaml --- docs/openapi.yml | 1080 +++++++++++++++++++++++----------------------- 1 file changed, 543 insertions(+), 537 deletions(-) diff --git a/docs/openapi.yml b/docs/openapi.yml index 5f2a05f2..8dd81e7b 100644 --- a/docs/openapi.yml +++ b/docs/openapi.yml @@ -1,537 +1,543 @@ -openapi: 3.0.3 -info: - title: Team Dev Server API - description: |- - version: 1.0 - -servers: - - url: http://localhost:4000/ -tags: - - name: user - - name: post - - name: cohort - - name: log -paths: - /users: - post: - tags: - - user - summary: Create user - description: Create new user - operationId: createUser - requestBody: - description: User registration details - content: - application/json: - schema: - $ref: '#/components/schemas/CreateUser' - responses: - '201': - description: Created - content: - application/json: - schema: - $ref: '#/components/schemas/CreatedUser' - get: - tags: - - user - summary: Get all users by first name if provided - description: '' - operationId: getAllUsers - security: - - bearerAuth: [] - parameters: - - name: firstName - in: query - description: Search all users by first name if provided (case-sensitive and exact string matches only) - schema: - type: string - responses: - '200': - description: successful operation - content: - application/json: - schema: - $ref: '#/components/schemas/AllUsers' - '400': - description: fail - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - '404': - description: fail - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - /login: - post: - tags: - - user - summary: Localhost Login - description: '' - operationId: loginUser - requestBody: - description: User login information - content: - application/json: - schema: - $ref: '#/components/schemas/login' - responses: - '200': - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/loginRes' - - '400': - description: Invalid username/password supplied - - /users/{id}: - get: - tags: - - user - summary: Get user by user id - description: '' - operationId: getUserByID - security: - - bearerAuth: [] - parameters: - - name: id - in: path - description: 'The name that needs to be fetched. Use user1 for testing. ' - required: true - schema: - type: string - responses: - '200': - description: successful operation - content: - application/json: - schema: - type: object - properties: - status: - type: string - data: - $ref: '#/components/schemas/User' - - '400': - description: fail - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - '404': - description: fail - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - patch: - tags: - - user - summary: Update a user - description: Only users with a TEACHER role can update the cohortId or role. Users with Students role can only update their own details. - operationId: userUpdate - security: - - bearerAuth: [] - parameters: - - name: id - in: path - description: 'The user id that needs to be updated' - required: true - schema: - type: string - requestBody: - description: The profile info - content: - application/json: - schema: - $ref: '#/components/schemas/UpdateUser' - responses: - '201': - description: Successful operation - content: - application/json: - schema: - $ref: '#/components/schemas/CreatedUser' - '401': - description: fail - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - /posts: - post: - tags: - - post - summary: Create post - description: This can only be done by the logged in user. - operationId: createPost - security: - - bearerAuth: [] - requestBody: - description: Created post object - content: - application/json: - schema: - type: object - properties: - content: - type: string - responses: - 201: - description: success - content: - application/json: - schema: - $ref: '#/components/schemas/Post' - 400: - description: fail - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - get: - tags: - - post - summary: Get all posts - description: get all posts - operationId: getPosts - security: - - bearerAuth: [] - responses: - '200': - description: Successful operation - content: - application/json: - schema: - $ref: '#/components/schemas/Posts' - '401': - description: fail - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - /logs: - post: - tags: - - log - summary: Create delivery log - description: This can only be done by an authorised teacher user. - operationId: createLog - security: - - bearerAuth: [] - requestBody: - description: Created log object - content: - application/json: - schema: - type: object - properties: - date: - type: string - cohortId: - type: integer - lines: - type: array - items: - type: object - properties: - content: - type: string - responses: - 201: - description: success - content: - application/json: - schema: - $ref: '#/components/schemas/Log' - '401': - description: Unauthorised - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - /cohorts: - post: - tags: - - cohort - summary: Create a cohort - description: This can only be done by the logged in user with role TEACHER. - operationId: createCohort - security: - - bearerAuth: [] - responses: - 201: - description: success - content: - application/json: - schema: - type: object - properties: - status: - type: string - data: - properties: - cohort: - $ref: '#/components/schemas/Cohort' - 400: - description: fail - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - -components: - securitySchemes: - bearerAuth: - type: http - scheme: bearer - bearerFormat: JWT - schemas: - Post: - type: object - properties: - status: - type: string - data: - properties: - post: - properties: - id: - type: integer - content: - type: string - - Cohort: - type: object - properties: - id: - type: integer - createdAt: - type: string - format: string - updatedAt: - type: string - format: string - - AllUsers: - type: object - properties: - status: - type: string - data: - type: object - properties: - users: - type: array - items: - $ref: '#/components/schemas/User' - - User: - type: object - properties: - id: - type: integer - email: - type: string - role: - type: string - cohortId: - type: integer - firstName: - type: string - lastName: - type: string - bio: - type: string - githubUrl: - type: string - - CreateUser: - type: object - properties: - firstName: - type: string - lastName: - type: string - email: - type: string - bio: - type: string - githubUrl: - type: string - password: - type: string - - UpdateUser: - type: object - properties: - email: - type: string - password: - type: string - cohortId: - type: integer - role: - type: string - firstName: - type: string - lastName: - type: string - bio: - type: string - githubUrl: - type: string - - Posts: - type: object - properties: - status: - type: string - data: - type: object - properties: - posts: - type: array - items: - type: object - properties: - id: - type: integer - content: - type: string - createdAt: - type: string - format: string - updatedAt: - type: string - format: string - author: - type: object - properties: - id: - type: integer - cohortId: - type: integer - role: - type: string - firstName: - type: string - lastName: - type: string - bio: - type: string - githubUrl: - type: string - profileImageUrl: - type: string - - CreatedUser: - type: object - properties: - status: - type: string - example: success - data: - properties: - user: - properties: - id: - type: integer - email: - type: string - cohortId: - type: integer - role: - type: string - firstName: - type: string - lastName: - type: string - bio: - type: string - githubUrl: - type: string - login: - type: object - properties: - email: - type: string - password: - type: string - - loginRes: - type: object - properties: - status: - type: string - data: - properties: - token: - type: string - user: - properties: - id: - type: integer - email: - type: string - cohortId: - type: integer - role: - type: string - firstName: - type: string - lastName: - type: string - bio: - type: string - githubUrl: - type: string - Error: - type: object - properties: - status: - type: string - data: - properties: - error: - type: string - - Log: - type: object - properties: - status: - type: string - data: - properties: - log: - properties: - id: - type: integer - cohortId: - type: integer - date: - type: string - author: - type: object - properties: - id: - type: integer - firstName: - type: string - lastName: - type: string - lines: - type: array - items: - type: object - properties: - id: - type: integer - content: - type: string +openapi: 3.0.3 +info: + title: Team Dev Server API + description: |- + version: 1.0 + +servers: + - url: http://localhost:4000/ +tags: + - name: user + - name: post + - name: cohort + - name: log +paths: + /users: + post: + tags: + - user + summary: Create user + description: Create new user + operationId: createUser + requestBody: + description: User registration details + content: + application/json: + schema: + $ref: '#/components/schemas/CreateUser' + responses: + '201': + description: Created + content: + application/json: + schema: + $ref: '#/components/schemas/CreatedUser' + '400': + description: Invalid email/password supplied + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + get: + tags: + - user + summary: Get all users by first name if provided + description: '' + operationId: getAllUsers + security: + - bearerAuth: [] + parameters: + - name: firstName + in: query + description: Search all users by first name if provided (case-sensitive and exact string matches only) + schema: + type: string + responses: + '200': + description: successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/AllUsers' + '400': + description: fail + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '404': + description: fail + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + /login: + post: + tags: + - user + summary: Localhost Login + description: '' + operationId: loginUser + requestBody: + description: User login information + content: + application/json: + schema: + $ref: '#/components/schemas/login' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/loginRes' + + '400': + description: Invalid username/password supplied + + /users/{id}: + get: + tags: + - user + summary: Get user by user id + description: '' + operationId: getUserByID + security: + - bearerAuth: [] + parameters: + - name: id + in: path + description: 'The name that needs to be fetched. Use user1 for testing. ' + required: true + schema: + type: string + responses: + '200': + description: successful operation + content: + application/json: + schema: + type: object + properties: + status: + type: string + data: + $ref: '#/components/schemas/User' + + '400': + description: fail + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '404': + description: fail + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + patch: + tags: + - user + summary: Update a user + description: Only users with a TEACHER role can update the cohortId or role. Users with Students role can only update their own details. + operationId: userUpdate + security: + - bearerAuth: [] + parameters: + - name: id + in: path + description: 'The user id that needs to be updated' + required: true + schema: + type: string + requestBody: + description: The profile info + content: + application/json: + schema: + $ref: '#/components/schemas/UpdateUser' + responses: + '201': + description: Successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/CreatedUser' + '401': + description: fail + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + /posts: + post: + tags: + - post + summary: Create post + description: This can only be done by the logged in user. + operationId: createPost + security: + - bearerAuth: [] + requestBody: + description: Created post object + content: + application/json: + schema: + type: object + properties: + content: + type: string + responses: + 201: + description: success + content: + application/json: + schema: + $ref: '#/components/schemas/Post' + 400: + description: fail + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + get: + tags: + - post + summary: Get all posts + description: get all posts + operationId: getPosts + security: + - bearerAuth: [] + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/Posts' + '401': + description: fail + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + /logs: + post: + tags: + - log + summary: Create delivery log + description: This can only be done by an authorised teacher user. + operationId: createLog + security: + - bearerAuth: [] + requestBody: + description: Created log object + content: + application/json: + schema: + type: object + properties: + date: + type: string + cohortId: + type: integer + lines: + type: array + items: + type: object + properties: + content: + type: string + responses: + 201: + description: success + content: + application/json: + schema: + $ref: '#/components/schemas/Log' + '401': + description: Unauthorised + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + /cohorts: + post: + tags: + - cohort + summary: Create a cohort + description: This can only be done by the logged in user with role TEACHER. + operationId: createCohort + security: + - bearerAuth: [] + responses: + 201: + description: success + content: + application/json: + schema: + type: object + properties: + status: + type: string + data: + properties: + cohort: + $ref: '#/components/schemas/Cohort' + 400: + description: fail + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + +components: + securitySchemes: + bearerAuth: + type: http + scheme: bearer + bearerFormat: JWT + schemas: + Post: + type: object + properties: + status: + type: string + data: + properties: + post: + properties: + id: + type: integer + content: + type: string + + Cohort: + type: object + properties: + id: + type: integer + createdAt: + type: string + format: string + updatedAt: + type: string + format: string + + AllUsers: + type: object + properties: + status: + type: string + data: + type: object + properties: + users: + type: array + items: + $ref: '#/components/schemas/User' + + User: + type: object + properties: + id: + type: integer + email: + type: string + role: + type: string + cohortId: + type: integer + firstName: + type: string + lastName: + type: string + bio: + type: string + githubUrl: + type: string + + CreateUser: + type: object + properties: + firstName: + type: string + lastName: + type: string + email: + type: string + bio: + type: string + githubUrl: + type: string + password: + type: string + + UpdateUser: + type: object + properties: + email: + type: string + password: + type: string + cohortId: + type: integer + role: + type: string + firstName: + type: string + lastName: + type: string + bio: + type: string + githubUrl: + type: string + + Posts: + type: object + properties: + status: + type: string + data: + type: object + properties: + posts: + type: array + items: + type: object + properties: + id: + type: integer + content: + type: string + createdAt: + type: string + format: string + updatedAt: + type: string + format: string + author: + type: object + properties: + id: + type: integer + cohortId: + type: integer + role: + type: string + firstName: + type: string + lastName: + type: string + bio: + type: string + githubUrl: + type: string + profileImageUrl: + type: string + + CreatedUser: + type: object + properties: + status: + type: string + example: success + data: + properties: + user: + properties: + id: + type: integer + email: + type: string + cohortId: + type: integer + role: + type: string + firstName: + type: string + lastName: + type: string + bio: + type: string + githubUrl: + type: string + login: + type: object + properties: + email: + type: string + password: + type: string + + loginRes: + type: object + properties: + status: + type: string + data: + properties: + token: + type: string + user: + properties: + id: + type: integer + email: + type: string + cohortId: + type: integer + role: + type: string + firstName: + type: string + lastName: + type: string + bio: + type: string + githubUrl: + type: string + Error: + type: object + properties: + status: + type: string + data: + properties: + error: + type: string + + Log: + type: object + properties: + status: + type: string + data: + properties: + log: + properties: + id: + type: integer + cohortId: + type: integer + date: + type: string + author: + type: object + properties: + id: + type: integer + firstName: + type: string + lastName: + type: string + lines: + type: array + items: + type: object + properties: + id: + type: integer + content: + type: string From 300711867e0e9bcf460fbdd124894f752a584a26 Mon Sep 17 00:00:00 2001 From: Ingeborg Brommeland Austeid Date: Tue, 29 Oct 2024 13:54:40 +0100 Subject: [PATCH 20/72] adds updated openapi.yml with 400 response to patch in /users --- docs/openapi.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/openapi.yml b/docs/openapi.yml index 8dd81e7b..50533123 100644 --- a/docs/openapi.yml +++ b/docs/openapi.yml @@ -170,6 +170,12 @@ paths: application/json: schema: $ref: '#/components/schemas/Error' + '400': + description: Invalid email/password supplied + content: + application/json: + schema: + $ref: '#/components/schemas/Error' /posts: post: tags: From d4ac66c92b22b864f04fd1a6a365cf5aff2c46ba Mon Sep 17 00:00:00 2001 From: tvaltn Date: Tue, 29 Oct 2024 14:41:08 +0100 Subject: [PATCH 21/72] moved request modification to middleware --- src/controllers/user.js | 15 ++++----------- src/middleware/auth.js | 14 +++++++++++--- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/src/controllers/user.js b/src/controllers/user.js index 0333a7e8..9e7d3ec7 100644 --- a/src/controllers/user.js +++ b/src/controllers/user.js @@ -59,18 +59,11 @@ export const getAll = async (req, res) => { export const updateById = async (req, res) => { const id = parseInt(req.params.id) const userToUpdate = await User.fromJson(req.body) - userToUpdate.id = id - // Update cohortId and role to userToUpdate (only if logged in role is Teacher) - // otherwise, keep the existing values of the user - if (req.user.role === 'TEACHER') { - userToUpdate.cohortId = parseInt(req.body.cohortId) - userToUpdate.role = req.body.role - } else { - const existingUser = await User.findById(id) - userToUpdate.cohortId = existingUser.cohortId - userToUpdate.role = existingUser.role - } + // Add id, cohortId and role (could be done in the domain) + userToUpdate.id = id + userToUpdate.cohortId = req.body.cohortId + userToUpdate.role = req.body.role try { if (!userToUpdate.cohortId) { diff --git a/src/middleware/auth.js b/src/middleware/auth.js index 1e3f6f10..2d4f505b 100644 --- a/src/middleware/auth.js +++ b/src/middleware/auth.js @@ -27,11 +27,19 @@ export async function validateLoggedInUser(req, res, next) { return sendMessageResponse(res, 500, 'Unable to verify user') } - if (req.user.id !== parseInt(req.params.id)) { - return next() + if (req.user.id === parseInt(req.params.id)) { + // Skip teacher validation if the user is updating their own profile + res.locals.skipTeacherValidation = true + + // Overwrite the request body with pre-existing values for cohortId and role, + // if the logged in user is a STUDENT + if (req.user.role === 'STUDENT') { + const existingUser = await User.findById(parseInt(req.params.id)) + req.body.cohortId = existingUser.cohortId + req.body.role = existingUser.role + } } - res.locals.skipTeacherValidation = true next() } From b624f5a3874685000c4d21de889bb089508f8ec0 Mon Sep 17 00:00:00 2001 From: tvaltn Date: Tue, 29 Oct 2024 15:36:27 +0100 Subject: [PATCH 22/72] added new user fields to update endpoint --- src/controllers/user.js | 2 ++ src/domain/user.js | 7 ++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/controllers/user.js b/src/controllers/user.js index 173f6e9c..72421118 100644 --- a/src/controllers/user.js +++ b/src/controllers/user.js @@ -69,6 +69,8 @@ export const updateById = async (req, res) => { userToUpdate.cohortId = req.body.cohortId userToUpdate.role = req.body.role + console.log(userToUpdate) + try { if (!userToUpdate.cohortId) { return sendDataResponse(res, 400, { cohort_id: 'Cohort ID is required' }) diff --git a/src/domain/user.js b/src/domain/user.js index 634842b7..21af5fe1 100644 --- a/src/domain/user.js +++ b/src/domain/user.js @@ -179,7 +179,12 @@ export default class User { firstName: this.firstName, lastName: this.lastName, bio: this.bio, - githubUrl: this.githubUrl + githubUrl: this.githubUrl, + username: this.username, + mobile: this.mobile, + specialism: this.specialism, + startDate: this.startDate, + endDate: this.endDate } } }, From 7d9aced26c504398e488f5f421b4bf636b8cae33 Mon Sep 17 00:00:00 2001 From: TMajlu Date: Tue, 29 Oct 2024 15:54:52 +0100 Subject: [PATCH 23/72] Added email-validation in the middleware/user.js file. --- src/middleware/user.js | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/middleware/user.js b/src/middleware/user.js index 756db248..07accf30 100644 --- a/src/middleware/user.js +++ b/src/middleware/user.js @@ -1,6 +1,24 @@ import { sendDataResponse } from '../utils/responses.js' export async function validateUser(req, res, next) { + const validateEmail = (email) => { + if ( + email.length < 7 || + email.indexOf('@') <= 0 || + email.slice(-4) !== '.com' || + (email.match(/@/g) || []).length > 1 || + email.charAt(email.length - 5) === '@' + ) { + return 'Email is invalid' + } + return null + } + + const emailError = validateEmail(req.body.email) + if (emailError) { + return sendDataResponse(res, 400, { email: emailError }) + } + const validatePassword = (password) => { const minLength = 8 const hasUpperCase = /[A-Z]/.test(password) From fd865b337c1f039353a4f79ee0cf21ee83f2809d Mon Sep 17 00:00:00 2001 From: Tuva Aarseth Date: Wed, 30 Oct 2024 09:40:36 +0100 Subject: [PATCH 24/72] Resolved merge conflict --- prisma/migrations/20241030083020_new_user_values/migration.sql | 1 + prisma/seed.js | 2 ++ 2 files changed, 3 insertions(+) create mode 100644 prisma/migrations/20241030083020_new_user_values/migration.sql diff --git a/prisma/migrations/20241030083020_new_user_values/migration.sql b/prisma/migrations/20241030083020_new_user_values/migration.sql new file mode 100644 index 00000000..af5102c8 --- /dev/null +++ b/prisma/migrations/20241030083020_new_user_values/migration.sql @@ -0,0 +1 @@ +-- This is an empty migration. \ No newline at end of file diff --git a/prisma/seed.js b/prisma/seed.js index d869c088..0d22470c 100644 --- a/prisma/seed.js +++ b/prisma/seed.js @@ -17,6 +17,7 @@ async function seed() { 'Software Engineering', // specialism new Date('2023-01-01'), // startDate new Date('2023-12-31'), + null, 'STUDENT', 'Testpassword1!' ) @@ -32,6 +33,7 @@ async function seed() { 'Teaching', new Date('2022-01-01'), new Date('2022-12-31'), + null, 'TEACHER', 'Testpassword1!' ) From b5b49ca4d06644eff4261f9400c5d3909d9e7033 Mon Sep 17 00:00:00 2001 From: Muzea001 Date: Wed, 30 Oct 2024 10:50:34 +0100 Subject: [PATCH 25/72] created endpoint for creating comment, issues with edit and delete --- docs/openapi.yml | 140 ++++++++++++++-- .../migration.sql | 15 ++ prisma/schema.prisma | 11 ++ prisma/seed.js | 25 ++- src/controllers/comment.js | 74 +++++++++ src/domain/comment.js | 153 ++++++++++++++++++ src/routes/comment.js | 11 ++ src/server.js | 2 + 8 files changed, 417 insertions(+), 14 deletions(-) create mode 100644 prisma/migrations/20241029124851_added_comments_table/migration.sql create mode 100644 src/controllers/comment.js create mode 100644 src/domain/comment.js create mode 100644 src/routes/comment.js diff --git a/docs/openapi.yml b/docs/openapi.yml index 5f2a05f2..2ad406a6 100644 --- a/docs/openapi.yml +++ b/docs/openapi.yml @@ -11,6 +11,7 @@ tags: - name: post - name: cohort - name: log + - name: comment paths: /users: post: @@ -85,10 +86,8 @@ paths: application/json: schema: $ref: '#/components/schemas/loginRes' - '400': description: Invalid username/password supplied - /users/{id}: get: tags: @@ -117,7 +116,6 @@ paths: type: string data: $ref: '#/components/schemas/User' - '400': description: fail content: @@ -285,6 +283,103 @@ paths: application/json: schema: $ref: '#/components/schemas/Error' + /comments: + post: + tags: + - comment + summary: Create a comment + description: Create a new comment on a post + operationId: createComment + security: + - bearerAuth: [] + requestBody: + description: Comment details + content: + application/json: + schema: + $ref: '#/components/schemas/CreateComment' + responses: + '201': + description: Created + content: + application/json: + schema: + $ref: '#/components/schemas/Comment' + '400': + description: Invalid input + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + /comments/{commentId}: + patch: + tags: + - comment + summary: Update a comment + description: Update an existing comment + operationId: updateComment + security: + - bearerAuth: [] + parameters: + - name: commentId + in: path + description: The ID of the comment to update + required: true + schema: + type: integer + requestBody: + description: Updated comment details + content: + application/json: + schema: + $ref: '#/components/schemas/UpdateComment' + responses: + '200': + description: Updated + content: + application/json: + schema: + $ref: '#/components/schemas/Comment' + '400': + description: Invalid input + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '404': + description: Comment not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + delete: + tags: + - comment + summary: Delete a comment + description: Delete an existing comment + operationId: deleteComment + security: + - bearerAuth: [] + parameters: + - name: commentId + in: path + description: The ID of the comment to delete + required: true + schema: + type: integer + responses: + '200': + description: Deleted + content: + application/json: + schema: + $ref: '#/components/schemas/Success' + '404': + description: Comment not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error' components: securitySchemes: @@ -306,7 +401,6 @@ components: type: integer content: type: string - Cohort: type: object properties: @@ -318,7 +412,6 @@ components: updatedAt: type: string format: string - AllUsers: type: object properties: @@ -331,7 +424,6 @@ components: type: array items: $ref: '#/components/schemas/User' - User: type: object properties: @@ -351,7 +443,6 @@ components: type: string githubUrl: type: string - CreateUser: type: object properties: @@ -367,7 +458,6 @@ components: type: string password: type: string - UpdateUser: type: object properties: @@ -387,7 +477,6 @@ components: type: string githubUrl: type: string - Posts: type: object properties: @@ -430,7 +519,6 @@ components: type: string profileImageUrl: type: string - CreatedUser: type: object properties: @@ -464,7 +552,6 @@ components: type: string password: type: string - loginRes: type: object properties: @@ -501,7 +588,6 @@ components: properties: error: type: string - Log: type: object properties: @@ -535,3 +621,33 @@ components: type: integer content: type: string + Comment: + type: object + properties: + id: + type: integer + content: + type: string + userId: + type: integer + postId: + type: integer + CreateComment: + type: object + properties: + content: + type: string + postId: + type: integer + userId: + type: integer + UpdateComment: + type: object + properties: + content: + type: string + Success: + type: object + properties: + message: + type: string \ No newline at end of file diff --git a/prisma/migrations/20241029124851_added_comments_table/migration.sql b/prisma/migrations/20241029124851_added_comments_table/migration.sql new file mode 100644 index 00000000..1c5829f7 --- /dev/null +++ b/prisma/migrations/20241029124851_added_comments_table/migration.sql @@ -0,0 +1,15 @@ +-- CreateTable +CREATE TABLE "Comment" ( + "id" SERIAL NOT NULL, + "content" TEXT NOT NULL, + "userId" INTEGER NOT NULL, + "postId" INTEGER NOT NULL, + + CONSTRAINT "Comment_pkey" PRIMARY KEY ("id") +); + +-- AddForeignKey +ALTER TABLE "Comment" ADD CONSTRAINT "Comment_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Comment" ADD CONSTRAINT "Comment_postId_fkey" FOREIGN KEY ("postId") REFERENCES "Post"("id") ON DELETE RESTRICT ON UPDATE CASCADE; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 72ec5632..ffb5c543 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -26,6 +26,7 @@ model User { cohort Cohort? @relation(fields: [cohortId], references: [id]) posts Post[] deliveryLogs DeliveryLog[] + comments Comment[] } model Profile { @@ -49,6 +50,16 @@ model Post { content String userId Int user User @relation(fields: [userId], references: [id]) + comments Comment[] +} + +model Comment { + id Int @id @default(autoincrement()) + content String + userId Int + user User @relation(fields: [userId], references: [id]) + postId Int + post Post @relation(fields: [postId], references: [id]) } model DeliveryLog { diff --git a/prisma/seed.js b/prisma/seed.js index 21684795..a2879087 100644 --- a/prisma/seed.js +++ b/prisma/seed.js @@ -25,8 +25,11 @@ async function seed() { 'TEACHER' ) - await createPost(student.id, 'My first post!') - await createPost(teacher.id, 'Hello, students') + const post1 = await createPost(student.id, 'My first post!') + const post2 = await createPost(teacher.id, 'Hello, students') + await createComment(student.id, post1.id, 'Nice post!') + await createComment(teacher.id, post1.id, 'Thank you!') + await createComment(student.id, post2.id, 'Hello, teacher!') process.exit(0) } @@ -47,6 +50,24 @@ async function createPost(userId, content) { return post } +async function createComment(userId, postId, content) { + const comment = await prisma.comment.create({ + data: { + userId, + postId, + content + }, + include: { + user: true, + post: true + } + }) + + console.info('Comment created', comment) + + return comment +} + async function createCohort() { const cohort = await prisma.cohort.create({ data: {} diff --git a/src/controllers/comment.js b/src/controllers/comment.js new file mode 100644 index 00000000..23f8adb9 --- /dev/null +++ b/src/controllers/comment.js @@ -0,0 +1,74 @@ +import { sendDataResponse, sendMessageResponse } from '../utils/responses.js' +import Comment from '../domain/comment.js' + +// Create a new comment +export const create = async (req, res) => { + const { content, postId, userId } = req.body + + if (!content || !postId || !userId) { + return sendDataResponse(res, 400, { + error: 'Must provide content, postId, and userId' + }) + } + + try { + const comment = new Comment({ + content, + userId, + postId + }) + const createdComment = await comment.save() + + // Fetch the created comment from the database + const fetchedComment = await Comment.findById(createdComment.id) + if (!fetchedComment) { + return sendDataResponse(res, 404, { id: 'Comment not found' }) + } + + return sendDataResponse(res, 201, fetchedComment) + } catch (error) { + console.error('Error creating comment:', error) + return sendMessageResponse(res, 500, 'Unable to create comment') + } +} + +// Update an existing comment +export const update = async (req, res) => { + const { content } = req.body + const { commentId } = req.params + + if (!content) { + return sendDataResponse(res, 400, { error: 'Must provide content' }) + } + + try { + const comment = await Comment.findById(commentId) + + if (!comment) { + return sendDataResponse(res, 404, { error: 'Comment not found' }) + } + + const updatedComment = await comment.update(content) + return sendDataResponse(res, 200, { comment: updatedComment }) + } catch (error) { + return sendDataResponse(res, 500, { error: 'Internal Server Error' }) + } +} + +// Delete an existing comment +export const remove = async (req, res) => { + const { commentId } = req.params + + try { + const comment = await Comment.findById(commentId) + + if (!comment) { + return res.status(404).json({ error: 'Comment not found' }) + } + + await comment.delete(); + return res.status(200).json({ message: 'Comment deleted successfully' }) + } catch (error) { + return res.status(500).json({ error: 'Internal Server Error' }) + } +} diff --git a/src/domain/comment.js b/src/domain/comment.js new file mode 100644 index 00000000..dd247197 --- /dev/null +++ b/src/domain/comment.js @@ -0,0 +1,153 @@ +import dbClient from '../utils/dbClient.js' + +export default class Comment { + /** + * Converts a database comment object to a Comment instance + * @param { { id: int, content: string, userId: int, postId: int, user: object, post: object } } comment + * @returns {Comment} + */ + static fromDb(comment) { + return new Comment( + comment.id, + comment.content, + comment.userId, + comment.postId + ) + } + + constructor({ id, content, userId, postId }) { + this.id = id + this.content = content + this.userId = userId + this.postId = postId + } + + toJSON() { + return { + comment: { + id: this.id, + content: this.content, + userId: this.userId, + postId: this.postId, + user: this.user, + post: this.post + } + } + } + + /** + * Saves the comment to the database + * @returns {Comment} + */ + async save() { + try { + const createdComment = await dbClient.comment.create({ + data: { + content: this.content, + userId: this.userId, + postId: this.postId + }, + include: { + user: true, + post: true + } + }) + + return Comment.fromDb(createdComment) + } catch (error) { + console.error('Error saving comment:', error) + throw new Error('Error saving comment') + } + } + + async update(content) { + try { + const updatedComment = await dbClient.comment.update({ + where: { id: this.id }, + data: { content }, + include: { + user: true, + post: true + } + }) + + return Comment.fromDb(updatedComment) + } catch (error) { + console.error('Error updating comment:', error) + throw new Error('Error updating comment') + } + } + + static async _findByUnique(field, value) { + if (!value) { + throw new Error(`${field} is required to find a comment`); + } + + try { + const comment = await dbClient.comment.findUnique({ + where: { [field]: parseInt(value, 10) }, + include: { + user: true, + post: true + } + }) + + if (!comment) { + throw new Error('Comment not found'); + } + + return Comment.fromDb(comment) + } catch (error) { + console.error(`Error finding comment by ${field}:`, error); + throw new Error(`Error finding comment by ${field}`); + } + } + + /** + * Deletes the comment from the database + * @returns {boolean} + */ + async delete() { + try { + await dbClient.comment.delete({ + where: { id: this.id } + }) + + return true + } catch (error) { + console.error('Error deleting comment:', error) + throw new Error('Error deleting comment') + } + } + + /** + * Finds a comment by its ID + * @param {int} id + * @returns {Comment} + */ + static async findById(id) { + return Comment._findByUnique('id', id) + } + + /** + * Finds comments by post ID + * @param {int} postId + * @returns {Comment[]} + */ + static async findByPostId(postId) { + try { + const foundComments = await dbClient.comment.findMany({ + where: { postId }, + include: { + user: true, + post: true + } + }) + + return foundComments.map((comment) => Comment.fromDb(comment)) + } catch (error) { + console.error('Error finding comments by post ID:', error) + throw new Error('Error finding comments by post ID') + } + } +} diff --git a/src/routes/comment.js b/src/routes/comment.js new file mode 100644 index 00000000..2e6b8948 --- /dev/null +++ b/src/routes/comment.js @@ -0,0 +1,11 @@ +import { Router } from 'express' +import { create, remove, update } from '../controllers/comment.js' +import { validateAuthentication } from '../middleware/auth.js' + +const router = Router() + +router.post('/', validateAuthentication, create) +router.patch('/:id', validateAuthentication, update) +router.delete('/:id', validateAuthentication, remove) + +export default router diff --git a/src/server.js b/src/server.js index a3f67eeb..2f89075e 100644 --- a/src/server.js +++ b/src/server.js @@ -7,6 +7,7 @@ import cors from 'cors' import userRouter from './routes/user.js' import postRouter from './routes/post.js' import authRouter from './routes/auth.js' +import commentRouter from './routes/comment.js' import cohortRouter from './routes/cohort.js' import deliveryLogRouter from './routes/deliveryLog.js' @@ -24,6 +25,7 @@ app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerDoc)) app.use('/users', userRouter) app.use('/posts', postRouter) app.use('/cohorts', cohortRouter) +app.use('/comments', commentRouter) app.use('/logs', deliveryLogRouter) app.use('/', authRouter) From 339e5c1b6139182aecb48e81c20ef98826d880ff Mon Sep 17 00:00:00 2001 From: eyvmal Date: Tue, 29 Oct 2024 12:40:20 +0100 Subject: [PATCH 26/72] copypaste progress from issue 5 --- docs/openapi.yml | 53 +++++++++++++++++++++++++++++++++++++++++ src/controllers/post.js | 22 +++++++++++++++++ src/domain/post.js | 40 +++++++++++++++++++++++++++++++ src/routes/post.js | 3 ++- 4 files changed, 117 insertions(+), 1 deletion(-) create mode 100644 src/domain/post.js diff --git a/docs/openapi.yml b/docs/openapi.yml index 27d5a6f4..417c15fc 100644 --- a/docs/openapi.yml +++ b/docs/openapi.yml @@ -225,6 +225,53 @@ paths: application/json: schema: $ref: '#/components/schemas/Error' + /posts/{id}: + patch: + tags: + - post + summary: Patch a post by id + description: patch a post + operationId: updatePostById + security: + - bearerAuth: [] + parameters: + - name: id + in: path + description: 'The post id that needs to be updated' + required: true + schema: + type: integer + requestBody: + description: The post description + content: + application/json: + schema: + $ref: '#/components/schemas/UpdatePost' + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/Posts' + '401': + description: Unauthorised + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '404': + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '500': + description: Server error + content: + application/json: + schema: + $ref: '#/components/schemas/Error' /logs: post: tags: @@ -494,6 +541,12 @@ components: profileImage: type: string + UpdatePost: + type: object + properties: + content: + type: string + CreatedUser: type: object properties: diff --git a/src/controllers/post.js b/src/controllers/post.js index 7b168039..959ea83a 100644 --- a/src/controllers/post.js +++ b/src/controllers/post.js @@ -1,4 +1,5 @@ import { sendDataResponse } from '../utils/responses.js' +import { getPostById, updateContentById } from '../domain/post.js' export const create = async (req, res) => { const { content } = req.body @@ -26,3 +27,24 @@ export const getAll = async (req, res) => { ] }) } + +export const updateById = async (req, res) => { + const { id } = req.params + const { content } = req.body + + try { + const post = await getPostById(Number(id)) + if (post) { + const updatedPost = await updateContentById(Number(id), content) + return sendDataResponse(res, 200, { post: updatedPost }) + } else { + return sendDataResponse(res, 404, { + content: `Post with id ${id} not found` + }) + } + } catch (error) { + return sendDataResponse(res, 500, { + content: 'Internal server error' + }) + } +} diff --git a/src/domain/post.js b/src/domain/post.js new file mode 100644 index 00000000..940c6d7e --- /dev/null +++ b/src/domain/post.js @@ -0,0 +1,40 @@ +import dbClient from '../utils/dbClient.js' + +export class Post { + constructor(id = null) { + this.id = id + } + + toJSON() { + return { + post: { + id: this.id + } + } + } +} + +export async function getPostById(id) { + try { + const post = await dbClient.post.findUnique({ + where: { id: id } + }) + return post + } catch (error) { + console.error('Error fetching post by ID:', error) + return null + } +} + +export async function updateContentById(id, content) { + try { + const post = await dbClient.post.update({ + where: { id: id }, + data: { content: content } + }) + return post + } catch (error) { + console.error('Error updating post content by ID:', error) + return null + } +} diff --git a/src/routes/post.js b/src/routes/post.js index a7fbbfb3..9fa2cfee 100644 --- a/src/routes/post.js +++ b/src/routes/post.js @@ -1,10 +1,11 @@ import { Router } from 'express' -import { create, getAll } from '../controllers/post.js' +import { create, getAll, updateById } from '../controllers/post.js' import { validateAuthentication } from '../middleware/auth.js' const router = Router() router.post('/', validateAuthentication, create) router.get('/', validateAuthentication, getAll) +router.patch('/:id', validateAuthentication, updateById) export default router From 7a9f548a6298d480a77222dfca3a5932e909281a Mon Sep 17 00:00:00 2001 From: eyvmal Date: Tue, 29 Oct 2024 12:43:32 +0100 Subject: [PATCH 27/72] modify getAll to fetch from database --- src/controllers/post.js | 23 ++++++++--------------- src/domain/post.js | 10 ++++++++++ 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/src/controllers/post.js b/src/controllers/post.js index 959ea83a..e60e4c9c 100644 --- a/src/controllers/post.js +++ b/src/controllers/post.js @@ -1,5 +1,5 @@ import { sendDataResponse } from '../utils/responses.js' -import { getPostById, updateContentById } from '../domain/post.js' +import { getAllPosts, getPostById, updateContentById } from '../domain/post.js' export const create = async (req, res) => { const { content } = req.body @@ -12,20 +12,13 @@ export const create = async (req, res) => { } export const getAll = async (req, res) => { - return sendDataResponse(res, 200, { - posts: [ - { - id: 1, - content: 'Hello world!', - author: { ...req.user } - }, - { - id: 2, - content: 'Hello from the void!', - author: { ...req.user } - } - ] - }) + const posts = await getAllPosts() + if (!posts) { + return sendDataResponse(res, 500, { + content: 'Internal server error' + }) + } + return sendDataResponse(res, 200, { posts }) } export const updateById = async (req, res) => { diff --git a/src/domain/post.js b/src/domain/post.js index 940c6d7e..720efba2 100644 --- a/src/domain/post.js +++ b/src/domain/post.js @@ -14,6 +14,16 @@ export class Post { } } +export async function getAllPosts() { + try { + const posts = await dbClient.post.findMany() + return posts + } catch (error) { + console.error('Error fetching all posts:', error) + return null + } +} + export async function getPostById(id) { try { const post = await dbClient.post.findUnique({ From a3eb2afe3009f92b4eb6276ef650f90c094daaea Mon Sep 17 00:00:00 2001 From: eyvmal Date: Tue, 29 Oct 2024 12:48:49 +0100 Subject: [PATCH 28/72] add getById endpoint --- docs/openapi.yml | 40 ++++++++++++++++++++++++++++++++++++++++ src/controllers/post.js | 13 +++++++++++++ src/routes/post.js | 3 ++- 3 files changed, 55 insertions(+), 1 deletion(-) diff --git a/docs/openapi.yml b/docs/openapi.yml index 417c15fc..9e460167 100644 --- a/docs/openapi.yml +++ b/docs/openapi.yml @@ -226,6 +226,46 @@ paths: schema: $ref: '#/components/schemas/Error' /posts/{id}: + get: + tags: + - post + summary: Get a post by id + description: get a post + operationId: getPostById + security: + - bearerAuth: [] + parameters: + - name: id + in: path + description: 'The post id that needs to be updated' + required: true + schema: + type: integer + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/Posts' + '401': + description: Unauthorised + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '404': + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '500': + description: Server error + content: + application/json: + schema: + $ref: '#/components/schemas/Error' patch: tags: - post diff --git a/src/controllers/post.js b/src/controllers/post.js index e60e4c9c..2be25adc 100644 --- a/src/controllers/post.js +++ b/src/controllers/post.js @@ -21,6 +21,19 @@ export const getAll = async (req, res) => { return sendDataResponse(res, 200, { posts }) } +export const getById = async (req, res) => { + const { id } = req.params + const post = await getPostById(Number(id)) + + if (!post) { + return sendDataResponse(res, 404, { + content: `Post with id ${id} not found` + }) + } + + return sendDataResponse(res, 200, { post }) +} + export const updateById = async (req, res) => { const { id } = req.params const { content } = req.body diff --git a/src/routes/post.js b/src/routes/post.js index 9fa2cfee..6b84c714 100644 --- a/src/routes/post.js +++ b/src/routes/post.js @@ -1,11 +1,12 @@ import { Router } from 'express' -import { create, getAll, updateById } from '../controllers/post.js' +import { create, getAll, updateById, getById } from '../controllers/post.js' import { validateAuthentication } from '../middleware/auth.js' const router = Router() router.post('/', validateAuthentication, create) router.get('/', validateAuthentication, getAll) +router.get('/:id', validateAuthentication, getById) router.patch('/:id', validateAuthentication, updateById) export default router From cf1c1e4c9260566c7cd9132d70042400799f915d Mon Sep 17 00:00:00 2001 From: eyvmal Date: Tue, 29 Oct 2024 12:54:21 +0100 Subject: [PATCH 29/72] add delete endpoint --- docs/openapi.yml | 40 ++++++++++++++++++++++++++++++++++++++++ src/controllers/post.js | 27 ++++++++++++++++++++++++++- src/domain/post.js | 12 ++++++++++++ src/routes/post.js | 9 ++++++++- 4 files changed, 86 insertions(+), 2 deletions(-) diff --git a/docs/openapi.yml b/docs/openapi.yml index 9e460167..0aeee602 100644 --- a/docs/openapi.yml +++ b/docs/openapi.yml @@ -312,6 +312,46 @@ paths: application/json: schema: $ref: '#/components/schemas/Error' + delete: + tags: + - post + summary: Delete a post by id + description: delete a post + operationId: deletePostById + security: + - bearerAuth: [] + parameters: + - name: id + in: path + description: 'The post id that needs to be updated' + required: true + schema: + type: integer + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/Posts' + '401': + description: Unauthorised + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '404': + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '500': + description: Server error + content: + application/json: + schema: + $ref: '#/components/schemas/Error' /logs: post: tags: diff --git a/src/controllers/post.js b/src/controllers/post.js index 2be25adc..f6e24ebe 100644 --- a/src/controllers/post.js +++ b/src/controllers/post.js @@ -1,5 +1,10 @@ import { sendDataResponse } from '../utils/responses.js' -import { getAllPosts, getPostById, updateContentById } from '../domain/post.js' +import { + getAllPosts, + getPostById, + updateContentById, + deletePostById +} from '../domain/post.js' export const create = async (req, res) => { const { content } = req.body @@ -54,3 +59,23 @@ export const updateById = async (req, res) => { }) } } + +export const deleteById = async (req, res) => { + const { id } = req.params + + try { + const post = await getPostById(Number(id)) + if (post) { + const deletedPost = await deletePostById(Number(id)) + return sendDataResponse(res, 200, { post: deletedPost }) + } else { + return sendDataResponse(res, 404, { + content: `Post with id ${id} not found` + }) + } + } catch (error) { + return sendDataResponse(res, 500, { + content: 'Internal server error' + }) + } +} diff --git a/src/domain/post.js b/src/domain/post.js index 720efba2..b9bb32a1 100644 --- a/src/domain/post.js +++ b/src/domain/post.js @@ -48,3 +48,15 @@ export async function updateContentById(id, content) { return null } } + +export async function deletePostById(id) { + try { + const post = await dbClient.post.delete({ + where: { id: id } + }) + return post + } catch (error) { + console.error('Error deleting post by ID:', error) + return null + } +} diff --git a/src/routes/post.js b/src/routes/post.js index 6b84c714..38f2b81b 100644 --- a/src/routes/post.js +++ b/src/routes/post.js @@ -1,5 +1,11 @@ import { Router } from 'express' -import { create, getAll, updateById, getById } from '../controllers/post.js' +import { + create, + getAll, + updateById, + getById, + deleteById +} from '../controllers/post.js' import { validateAuthentication } from '../middleware/auth.js' const router = Router() @@ -8,5 +14,6 @@ router.post('/', validateAuthentication, create) router.get('/', validateAuthentication, getAll) router.get('/:id', validateAuthentication, getById) router.patch('/:id', validateAuthentication, updateById) +router.delete('/:id', validateAuthentication, deleteById) export default router From 699b286db15e2c815d64aa2092668982a60a9e3b Mon Sep 17 00:00:00 2001 From: eyvmal Date: Tue, 29 Oct 2024 13:20:25 +0100 Subject: [PATCH 30/72] update create post method to connect to database --- docs/openapi.yml | 2 ++ src/controllers/post.js | 16 ++++++++++++++-- src/domain/post.js | 12 ++++++++++++ 3 files changed, 28 insertions(+), 2 deletions(-) diff --git a/docs/openapi.yml b/docs/openapi.yml index 0aeee602..ed8373fa 100644 --- a/docs/openapi.yml +++ b/docs/openapi.yml @@ -191,6 +191,8 @@ paths: properties: content: type: string + userid: + type: integer responses: 201: description: success diff --git a/src/controllers/post.js b/src/controllers/post.js index f6e24ebe..aa997ac1 100644 --- a/src/controllers/post.js +++ b/src/controllers/post.js @@ -1,19 +1,31 @@ import { sendDataResponse } from '../utils/responses.js' import { + createPost, getAllPosts, getPostById, updateContentById, deletePostById } from '../domain/post.js' +import User from '../domain/user.js' export const create = async (req, res) => { - const { content } = req.body + const { content, userid } = req.body + const user = await User.findById(userid) if (!content) { return sendDataResponse(res, 400, { content: 'Must provide content' }) } - return sendDataResponse(res, 201, { post: { id: 1, content } }) + try { + const post = await createPost(content, user) + if (post) { + return sendDataResponse(res, 201, { post }) + } else { + return sendDataResponse(res, 500, { content: 'Failed to create post' }) + } + } catch (error) { + return sendDataResponse(res, 500, { content: 'Internal server error' }) + } } export const getAll = async (req, res) => { diff --git a/src/domain/post.js b/src/domain/post.js index b9bb32a1..baaa3a9b 100644 --- a/src/domain/post.js +++ b/src/domain/post.js @@ -14,6 +14,18 @@ export class Post { } } +export async function createPost(content, user) { + try { + const post = await dbClient.post.create({ + data: { content: content, userId: user.id } + }) + return post + } catch (error) { + console.error('Error creating post:', error) + return null + } +} + export async function getAllPosts() { try { const posts = await dbClient.post.findMany() From 7e32f23ab0b1be3170389e4f756b697c8695aafb Mon Sep 17 00:00:00 2001 From: eyvmal Date: Tue, 29 Oct 2024 13:49:42 +0100 Subject: [PATCH 31/72] reformat domain- and controller-file of post to match the user-files structure --- src/controllers/post.js | 22 ++++++--------- src/domain/post.js | 60 ++++++++++++----------------------------- 2 files changed, 25 insertions(+), 57 deletions(-) diff --git a/src/controllers/post.js b/src/controllers/post.js index aa997ac1..0dc93868 100644 --- a/src/controllers/post.js +++ b/src/controllers/post.js @@ -1,11 +1,5 @@ import { sendDataResponse } from '../utils/responses.js' -import { - createPost, - getAllPosts, - getPostById, - updateContentById, - deletePostById -} from '../domain/post.js' +import Post from '../domain/post.js' import User from '../domain/user.js' export const create = async (req, res) => { @@ -17,7 +11,7 @@ export const create = async (req, res) => { } try { - const post = await createPost(content, user) + const post = await Post.createPost(content, user) if (post) { return sendDataResponse(res, 201, { post }) } else { @@ -29,7 +23,7 @@ export const create = async (req, res) => { } export const getAll = async (req, res) => { - const posts = await getAllPosts() + const posts = await Post.getAllPosts() if (!posts) { return sendDataResponse(res, 500, { content: 'Internal server error' @@ -40,7 +34,7 @@ export const getAll = async (req, res) => { export const getById = async (req, res) => { const { id } = req.params - const post = await getPostById(Number(id)) + const post = await Post.getPostById(Number(id)) if (!post) { return sendDataResponse(res, 404, { @@ -56,9 +50,9 @@ export const updateById = async (req, res) => { const { content } = req.body try { - const post = await getPostById(Number(id)) + const post = await Post.getPostById(Number(id)) if (post) { - const updatedPost = await updateContentById(Number(id), content) + const updatedPost = await Post.updateContentById(Number(id), content) return sendDataResponse(res, 200, { post: updatedPost }) } else { return sendDataResponse(res, 404, { @@ -76,9 +70,9 @@ export const deleteById = async (req, res) => { const { id } = req.params try { - const post = await getPostById(Number(id)) + const post = await Post.getPostById(Number(id)) if (post) { - const deletedPost = await deletePostById(Number(id)) + const deletedPost = await Post.deletePostById(Number(id)) return sendDataResponse(res, 200, { post: deletedPost }) } else { return sendDataResponse(res, 404, { diff --git a/src/domain/post.js b/src/domain/post.js index baaa3a9b..b69aa4d7 100644 --- a/src/domain/post.js +++ b/src/domain/post.js @@ -1,6 +1,6 @@ import dbClient from '../utils/dbClient.js' -export class Post { +export default class Post { constructor(id = null) { this.id = id } @@ -8,67 +8,41 @@ export class Post { toJSON() { return { post: { - id: this.id + id: this.id, + content: this.content, + userId: this.userId } } } -} -export async function createPost(content, user) { - try { - const post = await dbClient.post.create({ + static async createPost(content, user) { + return dbClient.post.create({ data: { content: content, userId: user.id } }) - return post - } catch (error) { - console.error('Error creating post:', error) - return null } -} -export async function getAllPosts() { - try { - const posts = await dbClient.post.findMany() - return posts - } catch (error) { - console.error('Error fetching all posts:', error) - return null + static async getAllPosts() { + return dbClient.post.findMany() } -} -export async function getPostById(id) { - try { - const post = await dbClient.post.findUnique({ - where: { id: id } + static async getPostById(id) { + return dbClient.post.findUnique({ + where: { + id: id + } }) - return post - } catch (error) { - console.error('Error fetching post by ID:', error) - return null } -} -export async function updateContentById(id, content) { - try { - const post = await dbClient.post.update({ + static async updateContentById(id, content) { + return dbClient.post.update({ where: { id: id }, data: { content: content } }) - return post - } catch (error) { - console.error('Error updating post content by ID:', error) - return null } -} -export async function deletePostById(id) { - try { - const post = await dbClient.post.delete({ + static async deletePostById(id) { + return dbClient.post.delete({ where: { id: id } }) - return post - } catch (error) { - console.error('Error deleting post by ID:', error) - return null } } From ddad8049e58696d4256d4b27451007e4e717519f Mon Sep 17 00:00:00 2001 From: eyvmal Date: Wed, 30 Oct 2024 10:03:39 +0100 Subject: [PATCH 32/72] remove userid from create endpoint --- docs/openapi.yml | 20 +++++++++++++++----- src/controllers/post.js | 4 ++-- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/docs/openapi.yml b/docs/openapi.yml index ed8373fa..9a064a1d 100644 --- a/docs/openapi.yml +++ b/docs/openapi.yml @@ -191,8 +191,6 @@ paths: properties: content: type: string - userid: - type: integer responses: 201: description: success @@ -200,8 +198,14 @@ paths: application/json: schema: $ref: '#/components/schemas/Post' - 400: - description: fail + 401: + description: Unauthorised + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '500': + description: Internal server error content: application/json: schema: @@ -222,7 +226,13 @@ paths: schema: $ref: '#/components/schemas/Posts' '401': - description: fail + description: Unauthorised + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '500': + description: Internal server error content: application/json: schema: diff --git a/src/controllers/post.js b/src/controllers/post.js index 0dc93868..6fab24e7 100644 --- a/src/controllers/post.js +++ b/src/controllers/post.js @@ -3,8 +3,8 @@ import Post from '../domain/post.js' import User from '../domain/user.js' export const create = async (req, res) => { - const { content, userid } = req.body - const user = await User.findById(userid) + const { content } = req.body + const user = await User.findById(req.user.id) if (!content) { return sendDataResponse(res, 400, { content: 'Must provide content' }) From 46814e40540fd344ea7550c5ed07816b113db0a2 Mon Sep 17 00:00:00 2001 From: eyvmal Date: Wed, 30 Oct 2024 11:40:24 +0100 Subject: [PATCH 33/72] add more fields to return on post calls --- src/domain/post.js | 37 ++++++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/src/domain/post.js b/src/domain/post.js index b69aa4d7..94a0e6a3 100644 --- a/src/domain/post.js +++ b/src/domain/post.js @@ -22,17 +22,52 @@ export default class Post { } static async getAllPosts() { - return dbClient.post.findMany() + return dbClient.post.findMany({ + include: { + user: { + select: { + id: true, + role: true, + cohortId: true, + profile: { + select: { + firstName: true, + lastName: true, + specialism: true + } + } + } + } + } + }) } static async getPostById(id) { return dbClient.post.findUnique({ where: { id: id + }, + include: { + user: { + select: { + id: true, + role: true, + cohortId: true, + profile: { + select: { + firstName: true, + lastName: true, + specialism: true + } + } + } + } } }) } + // firstName lastName rolle specialism + static async updateContentById(id, content) { return dbClient.post.update({ where: { id: id }, From fb2053e4ce9a3d471d99a84d3a0e79ade4600824 Mon Sep 17 00:00:00 2001 From: eyvmal Date: Wed, 30 Oct 2024 14:04:51 +0100 Subject: [PATCH 34/72] update post model in database and return value of post in endpoints --- .../migration.sql | 3 ++ prisma/schema.prisma | 2 + src/controllers/post.js | 47 ++++++++++++++++++- src/domain/post.js | 26 ++-------- 4 files changed, 54 insertions(+), 24 deletions(-) create mode 100644 prisma/migrations/20241030125841_add_created_updated_at_columns_to_post/migration.sql diff --git a/prisma/migrations/20241030125841_add_created_updated_at_columns_to_post/migration.sql b/prisma/migrations/20241030125841_add_created_updated_at_columns_to_post/migration.sql new file mode 100644 index 00000000..d19aa2d3 --- /dev/null +++ b/prisma/migrations/20241030125841_add_created_updated_at_columns_to_post/migration.sql @@ -0,0 +1,3 @@ +-- AlterTable +ALTER TABLE "Post" ADD COLUMN "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, +ADD COLUMN "updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 7ccee408..1f331713 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -55,6 +55,8 @@ model Post { content String userId Int user User @relation(fields: [userId], references: [id]) + createdAt DateTime @default(now()) + updatedAt DateTime @default(now()) } model DeliveryLog { diff --git a/src/controllers/post.js b/src/controllers/post.js index 6fab24e7..09fcaee6 100644 --- a/src/controllers/post.js +++ b/src/controllers/post.js @@ -29,7 +29,28 @@ export const getAll = async (req, res) => { content: 'Internal server error' }) } - return sendDataResponse(res, 200, { posts }) + const formattedPosts = posts.map((post) => ({ + id: post.id, + content: post.content, + createdAt: post.createdAt, + updatedAt: post.updatedAt, + author: { + id: post.user.id, + cohortId: post.user.cohortId, + role: post.user.role, + firstName: post.user.profile.firstName, + lastName: post.user.profile.lastName, + bio: post.user.profile.bio, + githubUrl: post.user.profile.githubUrl, + username: post.user.profile.username, + mobile: post.user.profile.mobile, + specialism: post.user.profile.specialism, + startDate: post.user.profile.startDate, + endDate: post.user.profile.endDate, + profileImage: post.user.profile.profileImage + } + })) + return sendDataResponse(res, 200, { formattedPosts }) } export const getById = async (req, res) => { @@ -42,7 +63,29 @@ export const getById = async (req, res) => { }) } - return sendDataResponse(res, 200, { post }) + const formattedPost = { + id: post.id, + content: post.content, + createdAt: post.createdAt, + updatedAt: post.updatedAt, + author: { + id: post.user.id, + cohortId: post.user.cohortId, + role: post.user.role, + firstName: post.user.profile.firstName, + lastName: post.user.profile.lastName, + bio: post.user.profile.bio, + githubUrl: post.user.profile.githubUrl, + username: post.user.profile.username, + mobile: post.user.profile.mobile, + specialism: post.user.profile.specialism, + startDate: post.user.profile.startDate, + endDate: post.user.profile.endDate, + profileImage: post.user.profile.profileImage + } + } + + return sendDataResponse(res, 200, { post: formattedPost }) } export const updateById = async (req, res) => { diff --git a/src/domain/post.js b/src/domain/post.js index 94a0e6a3..21b0d308 100644 --- a/src/domain/post.js +++ b/src/domain/post.js @@ -25,17 +25,8 @@ export default class Post { return dbClient.post.findMany({ include: { user: { - select: { - id: true, - role: true, - cohortId: true, - profile: { - select: { - firstName: true, - lastName: true, - specialism: true - } - } + include: { + profile: true } } } @@ -49,17 +40,8 @@ export default class Post { }, include: { user: { - select: { - id: true, - role: true, - cohortId: true, - profile: { - select: { - firstName: true, - lastName: true, - specialism: true - } - } + include: { + profile: true } } } From f18bc19f9fffdf99c2687becbffbcf6c7676db87 Mon Sep 17 00:00:00 2001 From: eyvmal Date: Wed, 30 Oct 2024 14:30:17 +0100 Subject: [PATCH 35/72] reformat toJSON and names --- src/controllers/post.js | 53 ++++-------------------------- src/domain/post.js | 71 ++++++++++++++++++++++++++++++----------- 2 files changed, 59 insertions(+), 65 deletions(-) diff --git a/src/controllers/post.js b/src/controllers/post.js index 09fcaee6..4500634e 100644 --- a/src/controllers/post.js +++ b/src/controllers/post.js @@ -23,34 +23,15 @@ export const create = async (req, res) => { } export const getAll = async (req, res) => { - const posts = await Post.getAllPosts() - if (!posts) { + const postsUnformatted = await Post.getAllPosts() + if (!postsUnformatted) { return sendDataResponse(res, 500, { content: 'Internal server error' }) } - const formattedPosts = posts.map((post) => ({ - id: post.id, - content: post.content, - createdAt: post.createdAt, - updatedAt: post.updatedAt, - author: { - id: post.user.id, - cohortId: post.user.cohortId, - role: post.user.role, - firstName: post.user.profile.firstName, - lastName: post.user.profile.lastName, - bio: post.user.profile.bio, - githubUrl: post.user.profile.githubUrl, - username: post.user.profile.username, - mobile: post.user.profile.mobile, - specialism: post.user.profile.specialism, - startDate: post.user.profile.startDate, - endDate: post.user.profile.endDate, - profileImage: post.user.profile.profileImage - } - })) - return sendDataResponse(res, 200, { formattedPosts }) + + const posts = postsUnformatted.map((post) => post.toJSON()) + return sendDataResponse(res, 200, { posts }) } export const getById = async (req, res) => { @@ -63,29 +44,7 @@ export const getById = async (req, res) => { }) } - const formattedPost = { - id: post.id, - content: post.content, - createdAt: post.createdAt, - updatedAt: post.updatedAt, - author: { - id: post.user.id, - cohortId: post.user.cohortId, - role: post.user.role, - firstName: post.user.profile.firstName, - lastName: post.user.profile.lastName, - bio: post.user.profile.bio, - githubUrl: post.user.profile.githubUrl, - username: post.user.profile.username, - mobile: post.user.profile.mobile, - specialism: post.user.profile.specialism, - startDate: post.user.profile.startDate, - endDate: post.user.profile.endDate, - profileImage: post.user.profile.profileImage - } - } - - return sendDataResponse(res, 200, { post: formattedPost }) + return sendDataResponse(res, 200, { post: post.toJSON() }) } export const updateById = async (req, res) => { diff --git a/src/domain/post.js b/src/domain/post.js index 21b0d308..0f992f10 100644 --- a/src/domain/post.js +++ b/src/domain/post.js @@ -1,16 +1,40 @@ import dbClient from '../utils/dbClient.js' export default class Post { - constructor(id = null) { + constructor( + id = null, + content = '', + user = null, + createdAt = null, + updatedAt = null + ) { this.id = id + this.content = content + this.user = user + this.createdAt = createdAt + this.updatedAt = updatedAt } toJSON() { return { - post: { - id: this.id, - content: this.content, - userId: this.userId + id: this.id, + content: this.content, + createdAt: this.createdAt, + updatedAt: this.updatedAt, + author: { + id: this.user.id, + cohortId: this.user.cohortId, + role: this.user.role, + firstName: this.user.profile.firstName, + lastName: this.user.profile.lastName, + bio: this.user.profile.bio, + githubUrl: this.user.profile.githubUrl, + username: this.user.profile.username, + mobile: this.user.profile.mobile, + specialism: this.user.profile.specialism, + startDate: this.user.profile.startDate, + endDate: this.user.profile.endDate, + profileImage: this.user.profile.profileImage } } } @@ -22,34 +46,45 @@ export default class Post { } static async getAllPosts() { - return dbClient.post.findMany({ + const posts = await dbClient.post.findMany({ include: { user: { - include: { - profile: true - } + include: { profile: true } } } }) + return posts.map( + (post) => + new Post( + post.id, + post.content, + post.user, + post.createdAt, + post.updatedAt + ) + ) } static async getPostById(id) { - return dbClient.post.findUnique({ - where: { - id: id - }, + const post = await dbClient.post.findUnique({ + where: { id }, include: { user: { - include: { - profile: true - } + include: { profile: true } } } }) + return post + ? new Post( + post.id, + post.content, + post.user, + post.createdAt, + post.updatedAt + ) + : null } - // firstName lastName rolle specialism - static async updateContentById(id, content) { return dbClient.post.update({ where: { id: id }, From cf16dbb4289d9bd06e1365bd8211890276c56d46 Mon Sep 17 00:00:00 2001 From: tvaltn Date: Wed, 30 Oct 2024 14:48:52 +0100 Subject: [PATCH 36/72] added default values for create --- src/domain/user.js | 29 +++++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/src/domain/user.js b/src/domain/user.js index 51b2fc42..045cf601 100644 --- a/src/domain/user.js +++ b/src/domain/user.js @@ -70,7 +70,7 @@ export default class User { constructor( id, - cohortId, + cohortId = 1, firstName, lastName, email, @@ -79,8 +79,8 @@ export default class User { username, mobile, specialism, - startDate, - endDate, + startDate = new Date('2023-01-01'), + endDate = new Date('2023-06-30'), passwordHash = null, profileImage = null, role = 'STUDENT' @@ -132,16 +132,18 @@ export default class User { const data = { email: this.email, password: this.passwordHash, - role: this.role + role: this.role, + cohortId: 1 } - if (this.cohortId) { + // This will break the code currently, needs a fix if you want to include cohort as its own table + /* if (this.cohortId) { data.cohort = { connectOrCreate: { id: this.cohortId } } - } + } */ if (this.firstName && this.lastName) { data.profile = { @@ -158,6 +160,21 @@ export default class User { endDate: this.endDate } } + } else { + data.profile = { + create: { + firstName: '', + lastName: '', + bio: this.bio, + githubUrl: this.githubUrl, + profileImage: this.profileImage, + username: this.username, + mobile: this.mobile, + specialism: this.specialism, + startDate: this.startDate, + endDate: this.endDate + } + } } const createdUser = await dbClient.user.create({ data, From 7b98fe7556b998a7935090546bbd927bd3e949ef Mon Sep 17 00:00:00 2001 From: eyvmal Date: Tue, 29 Oct 2024 14:46:42 +0100 Subject: [PATCH 37/72] add a middleware validation for post ownership or teacher role --- src/middleware/post.js | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 src/middleware/post.js diff --git a/src/middleware/post.js b/src/middleware/post.js new file mode 100644 index 00000000..90c1d934 --- /dev/null +++ b/src/middleware/post.js @@ -0,0 +1,26 @@ +import Post from '../models/Post.js' + +export async function validatePostOwnership(req, res, next) { + const { id: postId } = req.params + const userId = req.user.id + const userRole = req.user.role + + try { + const post = await Post.findById(postId) + if (!post) { + return res.status(404).json({ message: 'Post not found' }) + } + + if (post.userId === userId || userRole === 'TEACHER') { + return next() + } + + return res + .status(403) + .json({ message: 'Forbidden: Not authorized to modify this post' }) + } catch (error) { + return res + .status(500) + .json({ message: 'Server error', error: error.message }) + } +} From 220cc102e82f88dd282c5c463c696a3c230ba2ad Mon Sep 17 00:00:00 2001 From: tvaltn Date: Wed, 30 Oct 2024 14:52:34 +0100 Subject: [PATCH 38/72] added specialism --- src/domain/user.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/domain/user.js b/src/domain/user.js index 045cf601..7b69bfcb 100644 --- a/src/domain/user.js +++ b/src/domain/user.js @@ -78,7 +78,7 @@ export default class User { githubUrl, username, mobile, - specialism, + specialism = 'Software Developer', startDate = new Date('2023-01-01'), endDate = new Date('2023-06-30'), passwordHash = null, From bbef4514b36d5719c5d70df96cb3fde22f32fefa Mon Sep 17 00:00:00 2001 From: tvaltn Date: Wed, 30 Oct 2024 14:53:42 +0100 Subject: [PATCH 39/72] change cohortId to use default value from constr --- src/domain/user.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/domain/user.js b/src/domain/user.js index 7b69bfcb..af99e60f 100644 --- a/src/domain/user.js +++ b/src/domain/user.js @@ -133,7 +133,7 @@ export default class User { email: this.email, password: this.passwordHash, role: this.role, - cohortId: 1 + cohortId: this.cohortId } // This will break the code currently, needs a fix if you want to include cohort as its own table From af83b97ef4c5bce8a037f275f5ff2f12fee471a9 Mon Sep 17 00:00:00 2001 From: eyvmal Date: Wed, 30 Oct 2024 15:19:14 +0100 Subject: [PATCH 40/72] add validation on user/teacher for patch and delete --- src/middleware/post.js | 20 ++++++++------------ src/routes/post.js | 5 +++-- 2 files changed, 11 insertions(+), 14 deletions(-) diff --git a/src/middleware/post.js b/src/middleware/post.js index 90c1d934..8f110acf 100644 --- a/src/middleware/post.js +++ b/src/middleware/post.js @@ -1,26 +1,22 @@ -import Post from '../models/Post.js' +import { sendDataResponse } from '../utils/responses.js' +import Post from '../domain/post.js' export async function validatePostOwnership(req, res, next) { const { id: postId } = req.params - const userId = req.user.id - const userRole = req.user.role + const { id: userId, role: userRole } = req.user try { - const post = await Post.findById(postId) + const post = await Post.getPostById(Number(postId)) if (!post) { - return res.status(404).json({ message: 'Post not found' }) + return sendDataResponse(res, 404, { content: 'Post not found' }) } - if (post.userId === userId || userRole === 'TEACHER') { + if (post.user.id === userId || userRole === 'TEACHER') { return next() } - return res - .status(403) - .json({ message: 'Forbidden: Not authorized to modify this post' }) + return sendDataResponse(res, 401, { content: 'Unauthorized' }) } catch (error) { - return res - .status(500) - .json({ message: 'Server error', error: error.message }) + return sendDataResponse(res, 500, { content: 'Internal server error' }) } } diff --git a/src/routes/post.js b/src/routes/post.js index 38f2b81b..bc9ef6e6 100644 --- a/src/routes/post.js +++ b/src/routes/post.js @@ -7,13 +7,14 @@ import { deleteById } from '../controllers/post.js' import { validateAuthentication } from '../middleware/auth.js' +import { validatePostOwnership } from '../middleware/post.js' const router = Router() router.post('/', validateAuthentication, create) router.get('/', validateAuthentication, getAll) router.get('/:id', validateAuthentication, getById) -router.patch('/:id', validateAuthentication, updateById) -router.delete('/:id', validateAuthentication, deleteById) +router.patch('/:id', validateAuthentication, validatePostOwnership, updateById) +router.delete('/:id', validateAuthentication, validatePostOwnership, deleteById) export default router From 15eaf7ef4d560096fc39cd8ef6907f219e7f1a0e Mon Sep 17 00:00:00 2001 From: tvaltn Date: Wed, 30 Oct 2024 15:19:33 +0100 Subject: [PATCH 41/72] fixed cohortId being null --- src/domain/user.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/domain/user.js b/src/domain/user.js index af99e60f..0fdc4dee 100644 --- a/src/domain/user.js +++ b/src/domain/user.js @@ -34,6 +34,7 @@ export default class User { static async fromJson(json) { // eslint-disable-next-line camelcase const { + cohortId, firstName, lastName, email, @@ -52,7 +53,7 @@ export default class User { return new User( null, - null, + cohortId, firstName, lastName, email, From c621fd7cdbbbf48e076fd38f4a19cbc9b17331d1 Mon Sep 17 00:00:00 2001 From: tvaltn Date: Wed, 30 Oct 2024 16:20:54 +0100 Subject: [PATCH 42/72] basic validation --- docs/openapi.yml | 153 ++--------------------------------------- src/middleware/user.js | 35 ++++++++++ src/routes/user.js | 3 +- 3 files changed, 41 insertions(+), 150 deletions(-) diff --git a/docs/openapi.yml b/docs/openapi.yml index 9a064a1d..ce1c2649 100644 --- a/docs/openapi.yml +++ b/docs/openapi.yml @@ -168,7 +168,7 @@ paths: schema: $ref: '#/components/schemas/Error' '400': - description: Invalid email/password supplied + description: Invalid email/password/profile information supplied content: application/json: schema: @@ -198,14 +198,8 @@ paths: application/json: schema: $ref: '#/components/schemas/Post' - 401: - description: Unauthorised - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - '500': - description: Internal server error + 400: + description: fail content: application/json: schema: @@ -226,140 +220,7 @@ paths: schema: $ref: '#/components/schemas/Posts' '401': - description: Unauthorised - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - '500': - description: Internal server error - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - /posts/{id}: - get: - tags: - - post - summary: Get a post by id - description: get a post - operationId: getPostById - security: - - bearerAuth: [] - parameters: - - name: id - in: path - description: 'The post id that needs to be updated' - required: true - schema: - type: integer - responses: - '200': - description: Successful operation - content: - application/json: - schema: - $ref: '#/components/schemas/Posts' - '401': - description: Unauthorised - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - '404': - description: Not Found - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - '500': - description: Server error - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - patch: - tags: - - post - summary: Patch a post by id - description: patch a post - operationId: updatePostById - security: - - bearerAuth: [] - parameters: - - name: id - in: path - description: 'The post id that needs to be updated' - required: true - schema: - type: integer - requestBody: - description: The post description - content: - application/json: - schema: - $ref: '#/components/schemas/UpdatePost' - responses: - '200': - description: Successful operation - content: - application/json: - schema: - $ref: '#/components/schemas/Posts' - '401': - description: Unauthorised - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - '404': - description: Not Found - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - '500': - description: Server error - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - delete: - tags: - - post - summary: Delete a post by id - description: delete a post - operationId: deletePostById - security: - - bearerAuth: [] - parameters: - - name: id - in: path - description: 'The post id that needs to be updated' - required: true - schema: - type: integer - responses: - '200': - description: Successful operation - content: - application/json: - schema: - $ref: '#/components/schemas/Posts' - '401': - description: Unauthorised - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - '404': - description: Not Found - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - '500': - description: Server error + description: fail content: application/json: schema: @@ -633,12 +494,6 @@ components: profileImage: type: string - UpdatePost: - type: object - properties: - content: - type: string - CreatedUser: type: object properties: diff --git a/src/middleware/user.js b/src/middleware/user.js index 07accf30..9ece6c35 100644 --- a/src/middleware/user.js +++ b/src/middleware/user.js @@ -47,3 +47,38 @@ export async function validateUser(req, res, next) { next() } + +export async function validateProfile(req, res, next) { + if (!req.body.firstName) { + return sendDataResponse(res, 400, { firstName: 'First name is required' }) + } + if (!req.body.lastName) { + return sendDataResponse(res, 400, { lastName: 'Last name is required' }) + } + if (!req.body.username) { + return sendDataResponse(res, 400, { username: 'Username is required' }) + } + if (!req.body.githubUrl) { + return sendDataResponse(res, 400, { githubUrl: 'Github URL is required' }) + } + if (!req.body.mobile) { + return sendDataResponse(res, 400, { mobile: 'Mobile is required' }) + } + if (!req.body.specialism) { + return sendDataResponse(res, 400, { specialism: 'Specialism is required' }) + } + if (!req.body.startDate) { + return sendDataResponse(res, 400, { startDate: 'Start date is required' }) + } + if (!req.body.endDate) { + return sendDataResponse(res, 400, { endDate: 'End date is required' }) + } + if (!req.body.role && req.user.role === 'TEACHER') { + return sendDataResponse(res, 400, { role: 'Role is required' }) + } + if (!req.body.cohortId && req.user.role === 'TEACHER') { + return sendDataResponse(res, 400, { cohortId: 'Cohort ID is required' }) + } + + next() +} diff --git a/src/routes/user.js b/src/routes/user.js index 90287bdf..c14b5fbe 100644 --- a/src/routes/user.js +++ b/src/routes/user.js @@ -5,7 +5,7 @@ import { validateTeacherRole, validateLoggedInUser } from '../middleware/auth.js' -import { validateUser } from '../middleware/user.js' +import { validateProfile, validateUser } from '../middleware/user.js' const router = Router() @@ -16,6 +16,7 @@ router.patch( '/:id', validateAuthentication, validateUser, + validateProfile, validateLoggedInUser, validateTeacherRole, updateById From c00b8f1c3fa044e8f58f51f35e739f11687842ce Mon Sep 17 00:00:00 2001 From: eyvmal Date: Thu, 31 Oct 2024 09:17:30 +0100 Subject: [PATCH 43/72] update prisma cohort model/seeding and migrate --- .../migration.sql | 10 ++++++++++ prisma/schema.prisma | 7 +++++-- prisma/seed.js | 16 +++++++++++----- 3 files changed, 26 insertions(+), 7 deletions(-) create mode 100644 prisma/migrations/20241031081436_add_cohort_and_seeding/migration.sql diff --git a/prisma/migrations/20241031081436_add_cohort_and_seeding/migration.sql b/prisma/migrations/20241031081436_add_cohort_and_seeding/migration.sql new file mode 100644 index 00000000..4f3056d0 --- /dev/null +++ b/prisma/migrations/20241031081436_add_cohort_and_seeding/migration.sql @@ -0,0 +1,10 @@ +/* + Warnings: + + - Added the required column `cohortName` to the `Cohort` table without a default value. This is not possible if the table is not empty. + +*/ +-- AlterTable +ALTER TABLE "Cohort" ADD COLUMN "cohortName" TEXT NOT NULL, +ADD COLUMN "endDate" TIMESTAMP(3), +ADD COLUMN "startDate" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 1f331713..1bf869e7 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -46,8 +46,11 @@ model Profile { model Cohort { id Int @id @default(autoincrement()) - users User[] - deliveryLogs DeliveryLog[] + cohortName String + startDate DateTime @default(now()) + endDate DateTime? + students User[] + deliveryLogs DeliveryLog[] } model Post { diff --git a/prisma/seed.js b/prisma/seed.js index 0d22470c..3f73a989 100644 --- a/prisma/seed.js +++ b/prisma/seed.js @@ -3,7 +3,11 @@ import bcrypt from 'bcrypt' const prisma = new PrismaClient() async function seed() { - const cohort = await createCohort() + const cohort = await createCohort( + 'Boolean 2024', + new Date('2024-08-08'), + new Date('2024-11-01') + ) const student = await createUser( 'student@test.com', @@ -60,13 +64,15 @@ async function createPost(userId, content) { return post } -async function createCohort() { +async function createCohort(cohortName, startDate, endDate) { const cohort = await prisma.cohort.create({ - data: {} + data: { + cohortName, + startDate, + endDate + } }) - console.info('Cohort created', cohort) - return cohort } From 35fa6fddd2ff0d793a6d07980846991c3c764592 Mon Sep 17 00:00:00 2001 From: tvaltn Date: Thu, 31 Oct 2024 09:39:38 +0100 Subject: [PATCH 44/72] changed openapi.yml --- docs/openapi.yml | 153 +++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 149 insertions(+), 4 deletions(-) diff --git a/docs/openapi.yml b/docs/openapi.yml index ce1c2649..4d89ecf5 100644 --- a/docs/openapi.yml +++ b/docs/openapi.yml @@ -198,8 +198,14 @@ paths: application/json: schema: $ref: '#/components/schemas/Post' - 400: - description: fail + 401: + description: Unauthorised + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '500': + description: Internal server error content: application/json: schema: @@ -220,7 +226,140 @@ paths: schema: $ref: '#/components/schemas/Posts' '401': - description: fail + description: Unauthorised + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '500': + description: Internal server error + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + /posts/{id}: + get: + tags: + - post + summary: Get a post by id + description: get a post + operationId: getPostById + security: + - bearerAuth: [] + parameters: + - name: id + in: path + description: 'The post id that needs to be updated' + required: true + schema: + type: integer + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/Posts' + '401': + description: Unauthorised + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '404': + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '500': + description: Server error + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + patch: + tags: + - post + summary: Patch a post by id + description: patch a post + operationId: updatePostById + security: + - bearerAuth: [] + parameters: + - name: id + in: path + description: 'The post id that needs to be updated' + required: true + schema: + type: integer + requestBody: + description: The post description + content: + application/json: + schema: + $ref: '#/components/schemas/UpdatePost' + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/Posts' + '401': + description: Unauthorised + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '404': + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '500': + description: Server error + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + delete: + tags: + - post + summary: Delete a post by id + description: delete a post + operationId: deletePostById + security: + - bearerAuth: [] + parameters: + - name: id + in: path + description: 'The post id that needs to be updated' + required: true + schema: + type: integer + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/Posts' + '401': + description: Unauthorised + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '404': + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '500': + description: Server error content: application/json: schema: @@ -494,6 +633,12 @@ components: profileImage: type: string + UpdatePost: + type: object + properties: + content: + type: string + CreatedUser: type: object properties: @@ -627,4 +772,4 @@ components: id: type: integer content: - type: string + type: string \ No newline at end of file From 14eeaf966f06e66e683fc74305677e7c8974f517 Mon Sep 17 00:00:00 2001 From: Muzea001 Date: Thu, 31 Oct 2024 10:38:46 +0100 Subject: [PATCH 45/72] Fixed comments --- docs/openapi.yml | 411 ++++++++++++++++++----------------------------- 1 file changed, 152 insertions(+), 259 deletions(-) diff --git a/docs/openapi.yml b/docs/openapi.yml index 9a064a1d..479fb119 100644 --- a/docs/openapi.yml +++ b/docs/openapi.yml @@ -2,15 +2,16 @@ openapi: 3.0.3 info: title: Team Dev Server API description: |- - version: "1.0" + version: 1.0 servers: - - url: "http://localhost:4000/" + - url: http://localhost:4000/ tags: - name: user - name: post - name: cohort - name: log + - name: comment paths: /users: post: @@ -32,12 +33,6 @@ paths: application/json: schema: $ref: '#/components/schemas/CreatedUser' - '400': - description: Invalid email/password supplied - content: - application/json: - schema: - $ref: '#/components/schemas/Error' get: tags: - user @@ -167,12 +162,6 @@ paths: application/json: schema: $ref: '#/components/schemas/Error' - '400': - description: Invalid email/password supplied - content: - application/json: - schema: - $ref: '#/components/schemas/Error' /posts: post: tags: @@ -198,14 +187,8 @@ paths: application/json: schema: $ref: '#/components/schemas/Post' - 401: - description: Unauthorised - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - '500': - description: Internal server error + 400: + description: fail content: application/json: schema: @@ -226,209 +209,183 @@ paths: schema: $ref: '#/components/schemas/Posts' '401': - description: Unauthorised - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - '500': - description: Internal server error + description: fail content: application/json: schema: $ref: '#/components/schemas/Error' - /posts/{id}: - get: + /logs: + post: tags: - - post - summary: Get a post by id - description: get a post - operationId: getPostById + - log + summary: Create delivery log + description: This can only be done by an authorised teacher user. + operationId: createLog security: - bearerAuth: [] - parameters: - - name: id - in: path - description: 'The post id that needs to be updated' - required: true - schema: - type: integer + requestBody: + description: Created log object + content: + application/json: + schema: + type: object + properties: + date: + type: string + cohortId: + type: integer + lines: + type: array + items: + type: object + properties: + content: + type: string responses: - '200': - description: Successful operation + 201: + description: success content: application/json: schema: - $ref: '#/components/schemas/Posts' + $ref: '#/components/schemas/Log' '401': description: Unauthorised content: application/json: schema: $ref: '#/components/schemas/Error' - '404': - description: Not Found + /cohorts: + post: + tags: + - cohort + summary: Create a cohort + description: This can only be done by the logged in user with role TEACHER. + operationId: createCohort + security: + - bearerAuth: [] + responses: + 201: + description: success content: application/json: schema: - $ref: '#/components/schemas/Error' - '500': - description: Server error + type: object + properties: + status: + type: string + data: + properties: + cohort: + $ref: '#/components/schemas/Cohort' + 400: + description: fail content: application/json: schema: $ref: '#/components/schemas/Error' - patch: + /comments: + post: tags: - - post - summary: Patch a post by id - description: patch a post - operationId: updatePostById + - comment + summary: Create a comment + description: Create a new comment on a post + operationId: createComment security: - bearerAuth: [] - parameters: - - name: id - in: path - description: 'The post id that needs to be updated' - required: true - schema: - type: integer requestBody: - description: The post description + description: Comment details content: application/json: schema: - $ref: '#/components/schemas/UpdatePost' + $ref: '#/components/schemas/CreateComment' responses: - '200': - description: Successful operation - content: - application/json: - schema: - $ref: '#/components/schemas/Posts' - '401': - description: Unauthorised - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - '404': - description: Not Found + '201': + description: Created content: application/json: schema: - $ref: '#/components/schemas/Error' - '500': - description: Server error + $ref: '#/components/schemas/Comment' + '400': + description: Invalid input content: application/json: schema: $ref: '#/components/schemas/Error' - delete: + /comments/{commentId}: + patch: tags: - - post - summary: Delete a post by id - description: delete a post - operationId: deletePostById + - comment + summary: Update a comment + description: Update an existing comment + operationId: updateComment security: - bearerAuth: [] parameters: - - name: id + - name: commentId in: path - description: 'The post id that needs to be updated' + description: The ID of the comment to update required: true schema: type: integer - responses: - '200': - description: Successful operation - content: - application/json: - schema: - $ref: '#/components/schemas/Posts' - '401': - description: Unauthorised - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - '404': - description: Not Found - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - '500': - description: Server error - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - /logs: - post: - tags: - - log - summary: Create delivery log - description: This can only be done by an authorised teacher user. - operationId: createLog - security: - - bearerAuth: [] requestBody: - description: Created log object + description: Updated comment details + required: true content: application/json: schema: type: object properties: - date: + content: type: string - cohortId: + userId: type: integer - lines: - type: array - items: - type: object - properties: - content: - type: string + required: + - content + - userId + responses: - 201: - description: success + '200': + description: Updated content: application/json: schema: - $ref: '#/components/schemas/Log' - '401': - description: Unauthorised + $ref: '#/components/schemas/Comment' + '400': + description: Invalid input content: application/json: schema: $ref: '#/components/schemas/Error' - /cohorts: - post: + '404': + description: Comment not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + delete: tags: - - cohort - summary: Create a cohort - description: This can only be done by the logged in user with role TEACHER. - operationId: createCohort + - comment + summary: Delete a comment + description: Delete an existing comment + operationId: deleteComment security: - bearerAuth: [] + parameters: + - name: commentId + in: path + description: The ID of the comment to delete + required: true + schema: + type: integer responses: - 201: - description: success + '200': + description: Deleted content: application/json: schema: - type: object - properties: - status: - type: string - data: - properties: - cohort: - $ref: '#/components/schemas/Cohort' - 400: - description: fail + $ref: '#/components/schemas/Success' + '404': + description: Comment not found content: application/json: schema: @@ -454,7 +411,6 @@ components: type: integer content: type: string - Cohort: type: object properties: @@ -462,11 +418,10 @@ components: type: integer createdAt: type: string - format: date-time + format: string updatedAt: type: string - format: date-time - + format: string AllUsers: type: object properties: @@ -479,7 +434,6 @@ components: type: array items: $ref: '#/components/schemas/User' - User: type: object properties: @@ -499,21 +453,6 @@ components: type: string githubUrl: type: string - username: - type: string - mobile: - type: string - specialism: - type: string - startDate: - type: string - format: date-time - endDate: - type: string - format: date-time - profileImage: - type: string - CreateUser: type: object properties: @@ -527,23 +466,8 @@ components: type: string githubUrl: type: string - username: - type: string - mobile: - type: string - specialism: - type: string - startDate: - type: string - format: date-time - endDate: - type: string - format: date-time password: type: string - profileImage: - type: string - UpdateUser: type: object properties: @@ -563,21 +487,6 @@ components: type: string githubUrl: type: string - username: - type: string - mobile: - type: string - specialism: - type: string - startDate: - type: string - format: date-time - endDate: - type: string - format: date-time - profileImage: - type: string - Posts: type: object properties: @@ -597,10 +506,10 @@ components: type: string createdAt: type: string - format: date-time + format: string updatedAt: type: string - format: date-time + format: string author: type: object properties: @@ -618,27 +527,8 @@ components: type: string githubUrl: type: string - username: - type: string - mobile: - type: string - specialism: + profileImageUrl: type: string - startDate: - type: string - format: date-time - endDate: - type: string - format: date-time - profileImage: - type: string - - UpdatePost: - type: object - properties: - content: - type: string - CreatedUser: type: object properties: @@ -665,21 +555,6 @@ components: type: string githubUrl: type: string - username: - type: string - mobile: - type: string - specialism: - type: string - startDate: - type: string - format: date-time - endDate: - type: string - format: date-time - profileImage: - type: string - login: type: object properties: @@ -687,7 +562,6 @@ components: type: string password: type: string - loginRes: type: object properties: @@ -715,21 +589,6 @@ components: type: string githubUrl: type: string - username: - type: string - mobile: - type: string - specialism: - type: string - startDate: - type: string - format: date-time - endDate: - type: string - format: date-time - profileImage: - type: string - Error: type: object properties: @@ -739,7 +598,6 @@ components: properties: error: type: string - Log: type: object properties: @@ -773,3 +631,38 @@ components: type: integer content: type: string + Comment: + type: object + properties: + id: + type: integer + content: + type: string + userId: + type: integer + postId: + type: integer + CreateComment: + type: object + properties: + content: + type: string + postId: + type: integer + userId: + type: integer + UpdateComment: + type: object + properties: + content: + type: string + userId: + type: integer + required: + - content + - userId + Success: + type: object + properties: + message: + type: string \ No newline at end of file From ccec3cb8baff48cc38c4e5b43d7998d6a420e041 Mon Sep 17 00:00:00 2001 From: tvaltn Date: Thu, 31 Oct 2024 10:51:47 +0100 Subject: [PATCH 46/72] skips password validation if no password provided on patch --- src/domain/user.js | 48 ++++++++++++++++++++++++------------------ src/middleware/auth.js | 5 +++++ src/middleware/user.js | 4 ++++ src/routes/user.js | 2 +- 4 files changed, 38 insertions(+), 21 deletions(-) diff --git a/src/domain/user.js b/src/domain/user.js index 0fdc4dee..5ece88ab 100644 --- a/src/domain/user.js +++ b/src/domain/user.js @@ -49,7 +49,10 @@ export default class User { profileImage } = json - const passwordHash = await bcrypt.hash(password, 8) + let passwordHash = null + if (password) { + passwordHash = await bcrypt.hash(password, 8) + } return new User( null, @@ -192,29 +195,34 @@ export default class User { * A user instance containing the updated user data */ async update() { + const data = { + email: this.email, + role: this.role, + cohortId: this.cohortId, + profile: { + update: { + firstName: this.firstName, + lastName: this.lastName, + bio: this.bio, + githubUrl: this.githubUrl, + username: this.username, + mobile: this.mobile, + specialism: this.specialism, + startDate: this.startDate, + endDate: this.endDate + } + } + } + + if (this.passwordHash) { + data.password = this.passwordHash + } + const updatedUser = await dbClient.user.update({ where: { id: this.id }, - data: { - email: this.email, - password: this.passwordHash, - role: this.role, - cohortId: this.cohortId, - profile: { - update: { - firstName: this.firstName, - lastName: this.lastName, - bio: this.bio, - githubUrl: this.githubUrl, - username: this.username, - mobile: this.mobile, - specialism: this.specialism, - startDate: this.startDate, - endDate: this.endDate - } - } - }, + data, include: { profile: true } diff --git a/src/middleware/auth.js b/src/middleware/auth.js index 2d4f505b..449ba393 100644 --- a/src/middleware/auth.js +++ b/src/middleware/auth.js @@ -40,6 +40,11 @@ export async function validateLoggedInUser(req, res, next) { } } + // If no password was supplied, skip the password validation + if (!req.body.password) { + res.locals.skipPasswordValidation = true + } + next() } diff --git a/src/middleware/user.js b/src/middleware/user.js index 9ece6c35..5658c201 100644 --- a/src/middleware/user.js +++ b/src/middleware/user.js @@ -19,6 +19,10 @@ export async function validateUser(req, res, next) { return sendDataResponse(res, 400, { email: emailError }) } + if (res.locals.skipPasswordValidation) { + return next() + } + const validatePassword = (password) => { const minLength = 8 const hasUpperCase = /[A-Z]/.test(password) diff --git a/src/routes/user.js b/src/routes/user.js index c14b5fbe..5f616892 100644 --- a/src/routes/user.js +++ b/src/routes/user.js @@ -15,9 +15,9 @@ router.get('/:id', validateAuthentication, getById) router.patch( '/:id', validateAuthentication, + validateLoggedInUser, validateUser, validateProfile, - validateLoggedInUser, validateTeacherRole, updateById ) From 744be751a9ea3780af8e070f719a4a01a850aa1c Mon Sep 17 00:00:00 2001 From: JHalvor Date: Thu, 31 Oct 2024 10:58:25 +0100 Subject: [PATCH 47/72] removed duplicate createComment function declaration in seed.js --- prisma/seed.js | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/prisma/seed.js b/prisma/seed.js index bb7b47c0..26e90042 100644 --- a/prisma/seed.js +++ b/prisma/seed.js @@ -85,24 +85,6 @@ async function createComment(userId, postId, content) { return comment } -async function createComment(userId, postId, content) { - const comment = await prisma.comment.create({ - data: { - userId, - postId, - content - }, - include: { - user: true, - post: true - } - }) - - console.info('Comment created', comment) - - return comment -} - async function createCohort(cohortName, startDate, endDate) { const cohort = await prisma.cohort.create({ data: { From df722b23d6ee423a56230ed866944151c170db17 Mon Sep 17 00:00:00 2001 From: eyvmal Date: Thu, 31 Oct 2024 10:11:44 +0100 Subject: [PATCH 48/72] add constructor, fromJson, fromDb, toJson, save, and update --- src/domain/cohort.js | 73 +++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 69 insertions(+), 4 deletions(-) diff --git a/src/domain/cohort.js b/src/domain/cohort.js index abdda73b..4b1d7a2c 100644 --- a/src/domain/cohort.js +++ b/src/domain/cohort.js @@ -1,27 +1,92 @@ import dbClient from '../utils/dbClient.js' +import User from './user.js' /** * Create a new Cohort in the database * @returns {Cohort} */ -export async function createCohort() { +export async function createCohort(cohortName, startDate, endDate = null) { const createdCohort = await dbClient.cohort.create({ - data: {} + data: { + cohortName, + startDate, + endDate + } }) return new Cohort(createdCohort.id) } export class Cohort { - constructor(id = null) { + static fromDb(cohort) { + return new Cohort( + cohort.id, + cohort.cohortName, + cohort.startDate, + cohort.endDate, + cohort.students.map((student) => User.fromDb(student)) + ) + } + + static async fromJson(json) { + const { cohortId, cohortName, startDate, endDate, students } = json + return new Cohort( + cohortId, + cohortName, + startDate, + endDate, + students.map((student) => User.fromJson(student)) + ) + } + + constructor(id, cohortName, startDate, endDate = null, students = []) { this.id = id + this.cohortName = cohortName + this.startDate = startDate + this.endDate = endDate + this.students = students } toJSON() { return { cohort: { - id: this.id + id: this.id, + cohortName: this.cohortName, + startDate: this.startDate, + endDate: this.endDate, + students: this.students.map((student) => student.toJSON()) } } } + + async save() { + const data = { + cohortName: this.cohortName, + startDate: this.startDate + } + + if (this.endDate) { + data.endDate = this.endDate + } else { + data.endDate = null + } + + const createdCohort = await dbClient.cohort.create({ data }) + return Cohort.fromDb(createdCohort) + } + + async update() { + const updatedCohort = await dbClient.cohort.update({ + where: { + id: this.id + }, + data: { + cohortName: this.cohortName, + startDate: this.startDate, + endDate: this.endDate + } + }) + + return Cohort.fromDb(updatedCohort) + } } From c3bbef0d788d91a188541dcf1b8e042ef0fa88a2 Mon Sep 17 00:00:00 2001 From: eyvmal Date: Thu, 31 Oct 2024 10:43:03 +0100 Subject: [PATCH 49/72] remove default create function --- src/domain/cohort.js | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/src/domain/cohort.js b/src/domain/cohort.js index 4b1d7a2c..72de6471 100644 --- a/src/domain/cohort.js +++ b/src/domain/cohort.js @@ -1,22 +1,6 @@ import dbClient from '../utils/dbClient.js' import User from './user.js' -/** - * Create a new Cohort in the database - * @returns {Cohort} - */ -export async function createCohort(cohortName, startDate, endDate = null) { - const createdCohort = await dbClient.cohort.create({ - data: { - cohortName, - startDate, - endDate - } - }) - - return new Cohort(createdCohort.id) -} - export class Cohort { static fromDb(cohort) { return new Cohort( From b2988c3a7d79216dbca1ec5679d964c09a6e6722 Mon Sep 17 00:00:00 2001 From: eyvmal Date: Thu, 31 Oct 2024 11:24:39 +0100 Subject: [PATCH 50/72] make class export default --- src/domain/cohort.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/domain/cohort.js b/src/domain/cohort.js index 72de6471..45012bdd 100644 --- a/src/domain/cohort.js +++ b/src/domain/cohort.js @@ -1,7 +1,7 @@ import dbClient from '../utils/dbClient.js' import User from './user.js' -export class Cohort { +export default class Cohort { static fromDb(cohort) { return new Cohort( cohort.id, From 68c1eb5ff60ef0d56f42d4ddc97deb92d0b70910 Mon Sep 17 00:00:00 2001 From: eyvmal Date: Thu, 31 Oct 2024 11:48:31 +0100 Subject: [PATCH 51/72] fixed import --- src/controllers/cohort.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/controllers/cohort.js b/src/controllers/cohort.js index 818f14d4..1db92ff7 100644 --- a/src/controllers/cohort.js +++ b/src/controllers/cohort.js @@ -1,9 +1,9 @@ -import { createCohort } from '../domain/cohort.js' import { sendDataResponse, sendMessageResponse } from '../utils/responses.js' +import Cohort from '../domain/cohort.js' export const create = async (req, res) => { try { - const createdCohort = await createCohort() + const createdCohort = await Cohort.save() return sendDataResponse(res, 201, createdCohort) } catch (e) { From a9cb655401d97728ffb3c3c6a5f2b73733165253 Mon Sep 17 00:00:00 2001 From: eyvmal Date: Thu, 31 Oct 2024 10:39:30 +0100 Subject: [PATCH 52/72] add function for getAll and getById --- src/controllers/cohort.js | 20 ++++++++++++++++++++ src/routes/cohort.js | 4 +++- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/src/controllers/cohort.js b/src/controllers/cohort.js index 1db92ff7..13d57d7a 100644 --- a/src/controllers/cohort.js +++ b/src/controllers/cohort.js @@ -10,3 +10,23 @@ export const create = async (req, res) => { return sendMessageResponse(res, 500, 'Unable to create cohort') } } + +export const getAll = async (req, res) => { + const cohorts = await Cohort.getAllCohorts() + if (!cohorts) { + return sendMessageResponse(res, 500, 'Internal server error') + } + + return sendDataResponse(res, 200, cohorts) +} + +export const getById = async (req, res) => { + const { id } = req.params + const cohort = await Cohort.getCohortById(Number(id)) + + if (!cohort) { + return sendMessageResponse(res, 404, `Cohort with id ${id} not found`) + } + + return sendDataResponse(res, 200, cohort) +} diff --git a/src/routes/cohort.js b/src/routes/cohort.js index 3cc7813d..ac5d7b63 100644 --- a/src/routes/cohort.js +++ b/src/routes/cohort.js @@ -1,5 +1,5 @@ import { Router } from 'express' -import { create } from '../controllers/cohort.js' +import { create, getAll, getById } from '../controllers/cohort.js' import { validateAuthentication, validateTeacherRole @@ -8,5 +8,7 @@ import { const router = Router() router.post('/', validateAuthentication, validateTeacherRole, create) +router.get('/', validateAuthentication, getAll) +router.get('/:id', validateAuthentication, getById) export default router From 3cf9d6b33a887b5f0942ffe476eecd2fe1426698 Mon Sep 17 00:00:00 2001 From: eyvmal Date: Thu, 31 Oct 2024 11:21:53 +0100 Subject: [PATCH 53/72] update swagger docs --- docs/openapi.yml | 96 ++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 92 insertions(+), 4 deletions(-) diff --git a/docs/openapi.yml b/docs/openapi.yml index 4d89ecf5..835cfc0e 100644 --- a/docs/openapi.yml +++ b/docs/openapi.yml @@ -2,10 +2,10 @@ openapi: 3.0.3 info: title: Team Dev Server API description: |- - version: "1.0" + version: '1.0' servers: - - url: "http://localhost:4000/" + - url: 'http://localhost:4000/' tags: - name: user - name: post @@ -433,6 +433,88 @@ paths: application/json: schema: $ref: '#/components/schemas/Error' + get: + tags: + - cohort + summary: Get all cohorts + description: This can only be done by the logged in user with role TEACHER. + operationId: getAllCohorts + security: + - bearerAuth: [] + responses: + '201': + description: success + content: + application/json: + schema: + type: object + properties: + status: + type: string + data: + properties: + cohort: + $ref: '#/components/schemas/Cohort' + '401': + description: fail + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '500': + description: Server error + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + /cohorts/{id}: + get: + tags: + - cohort + summary: Get a cohort by id + description: This can only be done by the logged in user with role TEACHER. + operationId: getCohortById + security: + - bearerAuth: [] + parameters: + - name: id + in: path + description: 'The cohort id' + required: true + schema: + type: integer + responses: + '201': + description: success + content: + application/json: + schema: + type: object + properties: + status: + type: string + data: + properties: + cohort: + $ref: '#/components/schemas/Cohort' + '401': + description: Unautorized + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '404': + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '500': + description: Server error + content: + application/json: + schema: + $ref: '#/components/schemas/Error' components: securitySchemes: @@ -460,12 +542,18 @@ components: properties: id: type: integer + cohortName: + type: string createdAt: type: string format: date-time updatedAt: type: string format: date-time + users: + type: array + items: + $ref: '#/components/schemas/User' AllUsers: type: object @@ -729,7 +817,7 @@ components: format: date-time profileImage: type: string - + Error: type: object properties: @@ -772,4 +860,4 @@ components: id: type: integer content: - type: string \ No newline at end of file + type: string From 1615e8e28de13e0c3aa41cb0502f04ac0c5e18dc Mon Sep 17 00:00:00 2001 From: eyvmal Date: Thu, 31 Oct 2024 11:23:17 +0100 Subject: [PATCH 54/72] add db operations for get endpoints --- src/domain/cohort.js | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/domain/cohort.js b/src/domain/cohort.js index 45012bdd..5b254883 100644 --- a/src/domain/cohort.js +++ b/src/domain/cohort.js @@ -73,4 +73,23 @@ export default class Cohort { return Cohort.fromDb(updatedCohort) } + + static async getAllCohorts() { + const cohorts = await dbClient.cohort.findMany({ + include: { + students: true + } + }) + return cohorts.map((cohort) => Cohort.fromDb(cohort)) + } + + static async getCohortById(id) { + const cohort = await dbClient.cohort.findUnique({ + where: { id }, + include: { + students: true + } + }) + return cohort ? Cohort.fromDb(cohort) : null + } } From 4f59128e1e0e3c35ebeb2a9386c01ecc23911a56 Mon Sep 17 00:00:00 2001 From: eyvmal Date: Thu, 31 Oct 2024 11:40:34 +0100 Subject: [PATCH 55/72] update description of get endpoints --- docs/openapi.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/docs/openapi.yml b/docs/openapi.yml index 835cfc0e..9c9d18fa 100644 --- a/docs/openapi.yml +++ b/docs/openapi.yml @@ -437,7 +437,6 @@ paths: tags: - cohort summary: Get all cohorts - description: This can only be done by the logged in user with role TEACHER. operationId: getAllCohorts security: - bearerAuth: [] @@ -472,7 +471,6 @@ paths: tags: - cohort summary: Get a cohort by id - description: This can only be done by the logged in user with role TEACHER. operationId: getCohortById security: - bearerAuth: [] From d953db616faaaa420bde820c135d388988c5da3f Mon Sep 17 00:00:00 2001 From: tvaltn Date: Thu, 31 Oct 2024 12:45:23 +0100 Subject: [PATCH 56/72] added profileImage to patch --- src/domain/user.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/domain/user.js b/src/domain/user.js index 5ece88ab..48db2e5d 100644 --- a/src/domain/user.js +++ b/src/domain/user.js @@ -205,6 +205,7 @@ export default class User { lastName: this.lastName, bio: this.bio, githubUrl: this.githubUrl, + profileImage: this.profileImage, username: this.username, mobile: this.mobile, specialism: this.specialism, From 09cb074c26beb6983c170745e148f277af99c33a Mon Sep 17 00:00:00 2001 From: Muzea001 Date: Thu, 31 Oct 2024 13:27:29 +0100 Subject: [PATCH 57/72] Finished all endpoints for comments --- docs/openapi_BACKUP_1958.yml | 681 +++++++++++++++++++++++++++++++++++ docs/openapi_BASE_1958.yml | 681 +++++++++++++++++++++++++++++++++++ docs/openapi_LOCAL_1958.yml | 668 ++++++++++++++++++++++++++++++++++ docs/openapi_REMOTE_1958.yml | 674 ++++++++++++++++++++++++++++++++++ prisma/schema.prisma | 3 +- prisma/seed_BACKUP_1958.js | 155 ++++++++ prisma/seed_BASE_1958.js | 155 ++++++++ prisma/seed_LOCAL_1958.js | 151 ++++++++ prisma/seed_REMOTE_1958.js | 151 ++++++++ src/controllers/comment.js | 50 ++- src/domain/comment.js | 170 +++++---- src/routes/comment.js | 4 +- 12 files changed, 3440 insertions(+), 103 deletions(-) create mode 100644 docs/openapi_BACKUP_1958.yml create mode 100644 docs/openapi_BASE_1958.yml create mode 100644 docs/openapi_LOCAL_1958.yml create mode 100644 docs/openapi_REMOTE_1958.yml create mode 100644 prisma/seed_BACKUP_1958.js create mode 100644 prisma/seed_BASE_1958.js create mode 100644 prisma/seed_LOCAL_1958.js create mode 100644 prisma/seed_REMOTE_1958.js diff --git a/docs/openapi_BACKUP_1958.yml b/docs/openapi_BACKUP_1958.yml new file mode 100644 index 00000000..6fae389c --- /dev/null +++ b/docs/openapi_BACKUP_1958.yml @@ -0,0 +1,681 @@ +openapi: 3.0.3 +info: + title: Team Dev Server API + description: |- + version: 1.0 + +servers: + - url: http://localhost:4000/ +tags: + - name: user + - name: post + - name: cohort + - name: log + - name: comment +paths: + /users: + post: + tags: + - user + summary: Create user + description: Create new user + operationId: createUser + requestBody: + description: User registration details + content: + application/json: + schema: + $ref: '#/components/schemas/CreateUser' + responses: + '201': + description: Created + content: + application/json: + schema: + $ref: '#/components/schemas/CreatedUser' + get: + tags: + - user + summary: Get all users by first name if provided + description: '' + operationId: getAllUsers + security: + - bearerAuth: [] + parameters: + - name: firstName + in: query + description: Search all users by first name if provided (case-sensitive and exact string matches only) + schema: + type: string + responses: + '200': + description: successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/AllUsers' + '400': + description: fail + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '404': + description: fail + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + /login: + post: + tags: + - user + summary: Localhost Login + description: '' + operationId: loginUser + requestBody: + description: User login information + content: + application/json: + schema: + $ref: '#/components/schemas/login' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/loginRes' + '400': + description: Invalid username/password supplied + /users/{id}: + get: + tags: + - user + summary: Get user by user id + description: '' + operationId: getUserByID + security: + - bearerAuth: [] + parameters: + - name: id + in: path + description: 'The name that needs to be fetched. Use user1 for testing. ' + required: true + schema: + type: string + responses: + '200': + description: successful operation + content: + application/json: + schema: + type: object + properties: + status: + type: string + data: + $ref: '#/components/schemas/User' + '400': + description: fail + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '404': + description: fail + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + patch: + tags: + - user + summary: Update a user + description: Only users with a TEACHER role can update the cohortId or role. Users with Students role can only update their own details. + operationId: userUpdate + security: + - bearerAuth: [] + parameters: + - name: id + in: path + description: 'The user id that needs to be updated' + required: true + schema: + type: string + requestBody: + description: The profile info + content: + application/json: + schema: + $ref: '#/components/schemas/UpdateUser' + responses: + '201': + description: Successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/CreatedUser' + '401': + description: fail + content: + application/json: + schema: + $ref: '#/components/schemas/Error' +<<<<<<< HEAD +======= + '400': + description: Invalid email/password/profile information supplied + content: + application/json: + schema: + $ref: '#/components/schemas/Error' +>>>>>>> 744be751a9ea3780af8e070f719a4a01a850aa1c + /posts: + post: + tags: + - post + summary: Create post + description: This can only be done by the logged in user. + operationId: createPost + security: + - bearerAuth: [] + requestBody: + description: Created post object + content: + application/json: + schema: + type: object + properties: + content: + type: string + responses: + 201: + description: success + content: + application/json: + schema: + $ref: '#/components/schemas/Post' + 400: + description: fail + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + get: + tags: + - post + summary: Get all posts + description: get all posts + operationId: getPosts + security: + - bearerAuth: [] + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/Posts' + '401': + description: fail + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + /logs: + post: + tags: + - log + summary: Create delivery log + description: This can only be done by an authorised teacher user. + operationId: createLog + security: + - bearerAuth: [] + requestBody: + description: Created log object + content: + application/json: + schema: + type: object + properties: + date: + type: string + cohortId: + type: integer + lines: + type: array + items: + type: object + properties: + content: + type: string + responses: + 201: + description: success + content: + application/json: + schema: + $ref: '#/components/schemas/Log' + '401': + description: Unauthorised + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + /cohorts: + post: + tags: + - cohort + summary: Create a cohort + description: This can only be done by the logged in user with role TEACHER. + operationId: createCohort + security: + - bearerAuth: [] + responses: + 201: + description: success + content: + application/json: + schema: + type: object + properties: + status: + type: string + data: + properties: + cohort: + $ref: '#/components/schemas/Cohort' + 400: + description: fail + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + /comments: + post: + tags: + - comment + summary: Create a comment + description: Create a new comment on a post + operationId: createComment + security: + - bearerAuth: [] + requestBody: + description: Comment details + content: + application/json: + schema: + $ref: '#/components/schemas/CreateComment' + responses: + '201': + description: Created + content: + application/json: + schema: + $ref: '#/components/schemas/Comment' + '400': + description: Invalid input + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + /comments/{commentId}: + patch: + tags: + - comment + summary: Update a comment + description: Update an existing comment + operationId: updateComment + security: + - bearerAuth: [] + parameters: + - name: commentId + in: path + description: The ID of the comment to update + required: true + schema: + type: integer + requestBody: + description: Updated comment details + required: true + content: + application/json: + schema: + type: object + properties: + content: + type: string + userId: + type: integer + required: + - content + - userId + + responses: + '200': + description: Updated + content: + application/json: + schema: + $ref: '#/components/schemas/Comment' + '400': + description: Invalid input + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '404': + description: Comment not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + delete: + tags: + - comment + summary: Delete a comment + description: Delete an existing comment + operationId: deleteComment + security: + - bearerAuth: [] + parameters: + - name: commentId + in: path + description: The ID of the comment to delete + required: true + schema: + type: integer + responses: + '200': + description: Deleted + content: + application/json: + schema: + $ref: '#/components/schemas/Success' + '404': + description: Comment not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + +components: + securitySchemes: + bearerAuth: + type: http + scheme: bearer + bearerFormat: JWT + schemas: + Post: + type: object + properties: + status: + type: string + data: + properties: + post: + properties: + id: + type: integer + content: + type: string + Cohort: + type: object + properties: + id: + type: integer + createdAt: + type: string + format: string + updatedAt: + type: string + format: string + AllUsers: + type: object + properties: + status: + type: string + data: + type: object + properties: + users: + type: array + items: + $ref: '#/components/schemas/User' + User: + type: object + properties: + id: + type: integer + email: + type: string + role: + type: string + cohortId: + type: integer + firstName: + type: string + lastName: + type: string + bio: + type: string + githubUrl: + type: string + CreateUser: + type: object + properties: + firstName: + type: string + lastName: + type: string + email: + type: string + bio: + type: string + githubUrl: + type: string + password: + type: string + UpdateUser: + type: object + properties: + email: + type: string + password: + type: string + cohortId: + type: integer + role: + type: string + firstName: + type: string + lastName: + type: string + bio: + type: string + githubUrl: + type: string + Posts: + type: object + properties: + status: + type: string + data: + type: object + properties: + posts: + type: array + items: + type: object + properties: + id: + type: integer + content: + type: string + createdAt: + type: string + format: string + updatedAt: + type: string + format: string + author: + type: object + properties: + id: + type: integer + cohortId: + type: integer + role: + type: string + firstName: + type: string + lastName: + type: string + bio: + type: string + githubUrl: + type: string + profileImageUrl: + type: string + CreatedUser: + type: object + properties: + status: + type: string + example: success + data: + properties: + user: + properties: + id: + type: integer + email: + type: string + cohortId: + type: integer + role: + type: string + firstName: + type: string + lastName: + type: string + bio: + type: string + githubUrl: + type: string + login: + type: object + properties: + email: + type: string + password: + type: string + loginRes: + type: object + properties: + status: + type: string + data: + properties: + token: + type: string + user: + properties: + id: + type: integer + email: + type: string + cohortId: + type: integer + role: + type: string + firstName: + type: string + lastName: + type: string + bio: + type: string + githubUrl: + type: string + Error: + type: object + properties: + status: + type: string + data: + properties: + error: + type: string + Log: + type: object + properties: + status: + type: string + data: + properties: + log: + properties: + id: + type: integer + cohortId: + type: integer + date: + type: string + author: + type: object + properties: + id: + type: integer + firstName: + type: string + lastName: + type: string + lines: + type: array + items: + type: object + properties: + id: + type: integer + content: + type: string + Comment: + type: object + properties: + id: + type: integer + content: + type: string + userId: + type: integer + postId: + type: integer + CreateComment: + type: object + properties: + content: + type: string + postId: + type: integer + userId: + type: integer + UpdateComment: + type: object + properties: + content: + type: string + userId: + type: integer + required: + - content + - userId + Success: + type: object + properties: + message: +<<<<<<< HEAD + type: string +======= + type: string +>>>>>>> 744be751a9ea3780af8e070f719a4a01a850aa1c diff --git a/docs/openapi_BASE_1958.yml b/docs/openapi_BASE_1958.yml new file mode 100644 index 00000000..9b1389fa --- /dev/null +++ b/docs/openapi_BASE_1958.yml @@ -0,0 +1,681 @@ +openapi: 3.0.3 +info: + title: Team Dev Server API + description: |- + version: 1.0 + +servers: + - url: http://localhost:4000/ +tags: + - name: user + - name: post + - name: cohort + - name: log + - name: comment +paths: + /users: + post: + tags: + - user + summary: Create user + description: Create new user + operationId: createUser + requestBody: + description: User registration details + content: + application/json: + schema: + $ref: '#/components/schemas/CreateUser' + responses: + '201': + description: Created + content: + application/json: + schema: + $ref: '#/components/schemas/CreatedUser' + get: + tags: + - user + summary: Get all users by first name if provided + description: '' + operationId: getAllUsers + security: + - bearerAuth: [] + parameters: + - name: firstName + in: query + description: Search all users by first name if provided (case-sensitive and exact string matches only) + schema: + type: string + responses: + '200': + description: successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/AllUsers' + '400': + description: fail + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '404': + description: fail + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + /login: + post: + tags: + - user + summary: Localhost Login + description: '' + operationId: loginUser + requestBody: + description: User login information + content: + application/json: + schema: + $ref: '#/components/schemas/login' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/loginRes' + '400': + description: Invalid username/password supplied + /users/{id}: + get: + tags: + - user + summary: Get user by user id + description: '' + operationId: getUserByID + security: + - bearerAuth: [] + parameters: + - name: id + in: path + description: 'The name that needs to be fetched. Use user1 for testing. ' + required: true + schema: + type: string + responses: + '200': + description: successful operation + content: + application/json: + schema: + type: object + properties: + status: + type: string + data: + $ref: '#/components/schemas/User' + '400': + description: fail + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '404': + description: fail + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + patch: + tags: + - user + summary: Update a user + description: Only users with a TEACHER role can update the cohortId or role. Users with Students role can only update their own details. + operationId: userUpdate + security: + - bearerAuth: [] + parameters: + - name: id + in: path + description: 'The user id that needs to be updated' + required: true + schema: + type: string + requestBody: + description: The profile info + content: + application/json: + schema: + $ref: '#/components/schemas/UpdateUser' + responses: + '201': + description: Successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/CreatedUser' + '401': + description: fail + content: + application/json: + schema: + $ref: '#/components/schemas/Error' +<<<<<<<<< Temporary merge branch 1 + '400': + description: Invalid email/password/profile information supplied + content: + application/json: + schema: + $ref: '#/components/schemas/Error' +========= +>>>>>>>>> Temporary merge branch 2 + /posts: + post: + tags: + - post + summary: Create post + description: This can only be done by the logged in user. + operationId: createPost + security: + - bearerAuth: [] + requestBody: + description: Created post object + content: + application/json: + schema: + type: object + properties: + content: + type: string + responses: + 201: + description: success + content: + application/json: + schema: + $ref: '#/components/schemas/Post' + 400: + description: fail + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + get: + tags: + - post + summary: Get all posts + description: get all posts + operationId: getPosts + security: + - bearerAuth: [] + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/Posts' + '401': + description: fail + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + /logs: + post: + tags: + - log + summary: Create delivery log + description: This can only be done by an authorised teacher user. + operationId: createLog + security: + - bearerAuth: [] + requestBody: + description: Created log object + content: + application/json: + schema: + type: object + properties: + date: + type: string + cohortId: + type: integer + lines: + type: array + items: + type: object + properties: + content: + type: string + responses: + 201: + description: success + content: + application/json: + schema: + $ref: '#/components/schemas/Log' + '401': + description: Unauthorised + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + /cohorts: + post: + tags: + - cohort + summary: Create a cohort + description: This can only be done by the logged in user with role TEACHER. + operationId: createCohort + security: + - bearerAuth: [] + responses: + 201: + description: success + content: + application/json: + schema: + type: object + properties: + status: + type: string + data: + properties: + cohort: + $ref: '#/components/schemas/Cohort' + 400: + description: fail + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + /comments: + post: + tags: + - comment + summary: Create a comment + description: Create a new comment on a post + operationId: createComment + security: + - bearerAuth: [] + requestBody: + description: Comment details + content: + application/json: + schema: + $ref: '#/components/schemas/CreateComment' + responses: + '201': + description: Created + content: + application/json: + schema: + $ref: '#/components/schemas/Comment' + '400': + description: Invalid input + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + /comments/{commentId}: + patch: + tags: + - comment + summary: Update a comment + description: Update an existing comment + operationId: updateComment + security: + - bearerAuth: [] + parameters: + - name: commentId + in: path + description: The ID of the comment to update + required: true + schema: + type: integer + requestBody: + description: Updated comment details + required: true + content: + application/json: + schema: + type: object + properties: + content: + type: string + userId: + type: integer + required: + - content + - userId + + responses: + '200': + description: Updated + content: + application/json: + schema: + $ref: '#/components/schemas/Comment' + '400': + description: Invalid input + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '404': + description: Comment not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + delete: + tags: + - comment + summary: Delete a comment + description: Delete an existing comment + operationId: deleteComment + security: + - bearerAuth: [] + parameters: + - name: commentId + in: path + description: The ID of the comment to delete + required: true + schema: + type: integer + responses: + '200': + description: Deleted + content: + application/json: + schema: + $ref: '#/components/schemas/Success' + '404': + description: Comment not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + +components: + securitySchemes: + bearerAuth: + type: http + scheme: bearer + bearerFormat: JWT + schemas: + Post: + type: object + properties: + status: + type: string + data: + properties: + post: + properties: + id: + type: integer + content: + type: string + Cohort: + type: object + properties: + id: + type: integer + createdAt: + type: string + format: string + updatedAt: + type: string + format: string + AllUsers: + type: object + properties: + status: + type: string + data: + type: object + properties: + users: + type: array + items: + $ref: '#/components/schemas/User' + User: + type: object + properties: + id: + type: integer + email: + type: string + role: + type: string + cohortId: + type: integer + firstName: + type: string + lastName: + type: string + bio: + type: string + githubUrl: + type: string + CreateUser: + type: object + properties: + firstName: + type: string + lastName: + type: string + email: + type: string + bio: + type: string + githubUrl: + type: string + password: + type: string + UpdateUser: + type: object + properties: + email: + type: string + password: + type: string + cohortId: + type: integer + role: + type: string + firstName: + type: string + lastName: + type: string + bio: + type: string + githubUrl: + type: string + Posts: + type: object + properties: + status: + type: string + data: + type: object + properties: + posts: + type: array + items: + type: object + properties: + id: + type: integer + content: + type: string + createdAt: + type: string + format: string + updatedAt: + type: string + format: string + author: + type: object + properties: + id: + type: integer + cohortId: + type: integer + role: + type: string + firstName: + type: string + lastName: + type: string + bio: + type: string + githubUrl: + type: string + profileImageUrl: + type: string + CreatedUser: + type: object + properties: + status: + type: string + example: success + data: + properties: + user: + properties: + id: + type: integer + email: + type: string + cohortId: + type: integer + role: + type: string + firstName: + type: string + lastName: + type: string + bio: + type: string + githubUrl: + type: string + login: + type: object + properties: + email: + type: string + password: + type: string + loginRes: + type: object + properties: + status: + type: string + data: + properties: + token: + type: string + user: + properties: + id: + type: integer + email: + type: string + cohortId: + type: integer + role: + type: string + firstName: + type: string + lastName: + type: string + bio: + type: string + githubUrl: + type: string + Error: + type: object + properties: + status: + type: string + data: + properties: + error: + type: string + Log: + type: object + properties: + status: + type: string + data: + properties: + log: + properties: + id: + type: integer + cohortId: + type: integer + date: + type: string + author: + type: object + properties: + id: + type: integer + firstName: + type: string + lastName: + type: string + lines: + type: array + items: + type: object + properties: + id: + type: integer + content: +<<<<<<<<< Temporary merge branch 1 + type: string +========= + type: string + Comment: + type: object + properties: + id: + type: integer + content: + type: string + userId: + type: integer + postId: + type: integer + CreateComment: + type: object + properties: + content: + type: string + postId: + type: integer + userId: + type: integer + UpdateComment: + type: object + properties: + content: + type: string + userId: + type: integer + required: + - content + - userId + Success: + type: object + properties: + message: + type: string +>>>>>>>>> Temporary merge branch 2 diff --git a/docs/openapi_LOCAL_1958.yml b/docs/openapi_LOCAL_1958.yml new file mode 100644 index 00000000..479fb119 --- /dev/null +++ b/docs/openapi_LOCAL_1958.yml @@ -0,0 +1,668 @@ +openapi: 3.0.3 +info: + title: Team Dev Server API + description: |- + version: 1.0 + +servers: + - url: http://localhost:4000/ +tags: + - name: user + - name: post + - name: cohort + - name: log + - name: comment +paths: + /users: + post: + tags: + - user + summary: Create user + description: Create new user + operationId: createUser + requestBody: + description: User registration details + content: + application/json: + schema: + $ref: '#/components/schemas/CreateUser' + responses: + '201': + description: Created + content: + application/json: + schema: + $ref: '#/components/schemas/CreatedUser' + get: + tags: + - user + summary: Get all users by first name if provided + description: '' + operationId: getAllUsers + security: + - bearerAuth: [] + parameters: + - name: firstName + in: query + description: Search all users by first name if provided (case-sensitive and exact string matches only) + schema: + type: string + responses: + '200': + description: successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/AllUsers' + '400': + description: fail + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '404': + description: fail + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + /login: + post: + tags: + - user + summary: Localhost Login + description: '' + operationId: loginUser + requestBody: + description: User login information + content: + application/json: + schema: + $ref: '#/components/schemas/login' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/loginRes' + '400': + description: Invalid username/password supplied + /users/{id}: + get: + tags: + - user + summary: Get user by user id + description: '' + operationId: getUserByID + security: + - bearerAuth: [] + parameters: + - name: id + in: path + description: 'The name that needs to be fetched. Use user1 for testing. ' + required: true + schema: + type: string + responses: + '200': + description: successful operation + content: + application/json: + schema: + type: object + properties: + status: + type: string + data: + $ref: '#/components/schemas/User' + '400': + description: fail + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '404': + description: fail + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + patch: + tags: + - user + summary: Update a user + description: Only users with a TEACHER role can update the cohortId or role. Users with Students role can only update their own details. + operationId: userUpdate + security: + - bearerAuth: [] + parameters: + - name: id + in: path + description: 'The user id that needs to be updated' + required: true + schema: + type: string + requestBody: + description: The profile info + content: + application/json: + schema: + $ref: '#/components/schemas/UpdateUser' + responses: + '201': + description: Successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/CreatedUser' + '401': + description: fail + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + /posts: + post: + tags: + - post + summary: Create post + description: This can only be done by the logged in user. + operationId: createPost + security: + - bearerAuth: [] + requestBody: + description: Created post object + content: + application/json: + schema: + type: object + properties: + content: + type: string + responses: + 201: + description: success + content: + application/json: + schema: + $ref: '#/components/schemas/Post' + 400: + description: fail + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + get: + tags: + - post + summary: Get all posts + description: get all posts + operationId: getPosts + security: + - bearerAuth: [] + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/Posts' + '401': + description: fail + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + /logs: + post: + tags: + - log + summary: Create delivery log + description: This can only be done by an authorised teacher user. + operationId: createLog + security: + - bearerAuth: [] + requestBody: + description: Created log object + content: + application/json: + schema: + type: object + properties: + date: + type: string + cohortId: + type: integer + lines: + type: array + items: + type: object + properties: + content: + type: string + responses: + 201: + description: success + content: + application/json: + schema: + $ref: '#/components/schemas/Log' + '401': + description: Unauthorised + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + /cohorts: + post: + tags: + - cohort + summary: Create a cohort + description: This can only be done by the logged in user with role TEACHER. + operationId: createCohort + security: + - bearerAuth: [] + responses: + 201: + description: success + content: + application/json: + schema: + type: object + properties: + status: + type: string + data: + properties: + cohort: + $ref: '#/components/schemas/Cohort' + 400: + description: fail + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + /comments: + post: + tags: + - comment + summary: Create a comment + description: Create a new comment on a post + operationId: createComment + security: + - bearerAuth: [] + requestBody: + description: Comment details + content: + application/json: + schema: + $ref: '#/components/schemas/CreateComment' + responses: + '201': + description: Created + content: + application/json: + schema: + $ref: '#/components/schemas/Comment' + '400': + description: Invalid input + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + /comments/{commentId}: + patch: + tags: + - comment + summary: Update a comment + description: Update an existing comment + operationId: updateComment + security: + - bearerAuth: [] + parameters: + - name: commentId + in: path + description: The ID of the comment to update + required: true + schema: + type: integer + requestBody: + description: Updated comment details + required: true + content: + application/json: + schema: + type: object + properties: + content: + type: string + userId: + type: integer + required: + - content + - userId + + responses: + '200': + description: Updated + content: + application/json: + schema: + $ref: '#/components/schemas/Comment' + '400': + description: Invalid input + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '404': + description: Comment not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + delete: + tags: + - comment + summary: Delete a comment + description: Delete an existing comment + operationId: deleteComment + security: + - bearerAuth: [] + parameters: + - name: commentId + in: path + description: The ID of the comment to delete + required: true + schema: + type: integer + responses: + '200': + description: Deleted + content: + application/json: + schema: + $ref: '#/components/schemas/Success' + '404': + description: Comment not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + +components: + securitySchemes: + bearerAuth: + type: http + scheme: bearer + bearerFormat: JWT + schemas: + Post: + type: object + properties: + status: + type: string + data: + properties: + post: + properties: + id: + type: integer + content: + type: string + Cohort: + type: object + properties: + id: + type: integer + createdAt: + type: string + format: string + updatedAt: + type: string + format: string + AllUsers: + type: object + properties: + status: + type: string + data: + type: object + properties: + users: + type: array + items: + $ref: '#/components/schemas/User' + User: + type: object + properties: + id: + type: integer + email: + type: string + role: + type: string + cohortId: + type: integer + firstName: + type: string + lastName: + type: string + bio: + type: string + githubUrl: + type: string + CreateUser: + type: object + properties: + firstName: + type: string + lastName: + type: string + email: + type: string + bio: + type: string + githubUrl: + type: string + password: + type: string + UpdateUser: + type: object + properties: + email: + type: string + password: + type: string + cohortId: + type: integer + role: + type: string + firstName: + type: string + lastName: + type: string + bio: + type: string + githubUrl: + type: string + Posts: + type: object + properties: + status: + type: string + data: + type: object + properties: + posts: + type: array + items: + type: object + properties: + id: + type: integer + content: + type: string + createdAt: + type: string + format: string + updatedAt: + type: string + format: string + author: + type: object + properties: + id: + type: integer + cohortId: + type: integer + role: + type: string + firstName: + type: string + lastName: + type: string + bio: + type: string + githubUrl: + type: string + profileImageUrl: + type: string + CreatedUser: + type: object + properties: + status: + type: string + example: success + data: + properties: + user: + properties: + id: + type: integer + email: + type: string + cohortId: + type: integer + role: + type: string + firstName: + type: string + lastName: + type: string + bio: + type: string + githubUrl: + type: string + login: + type: object + properties: + email: + type: string + password: + type: string + loginRes: + type: object + properties: + status: + type: string + data: + properties: + token: + type: string + user: + properties: + id: + type: integer + email: + type: string + cohortId: + type: integer + role: + type: string + firstName: + type: string + lastName: + type: string + bio: + type: string + githubUrl: + type: string + Error: + type: object + properties: + status: + type: string + data: + properties: + error: + type: string + Log: + type: object + properties: + status: + type: string + data: + properties: + log: + properties: + id: + type: integer + cohortId: + type: integer + date: + type: string + author: + type: object + properties: + id: + type: integer + firstName: + type: string + lastName: + type: string + lines: + type: array + items: + type: object + properties: + id: + type: integer + content: + type: string + Comment: + type: object + properties: + id: + type: integer + content: + type: string + userId: + type: integer + postId: + type: integer + CreateComment: + type: object + properties: + content: + type: string + postId: + type: integer + userId: + type: integer + UpdateComment: + type: object + properties: + content: + type: string + userId: + type: integer + required: + - content + - userId + Success: + type: object + properties: + message: + type: string \ No newline at end of file diff --git a/docs/openapi_REMOTE_1958.yml b/docs/openapi_REMOTE_1958.yml new file mode 100644 index 00000000..9dee0c00 --- /dev/null +++ b/docs/openapi_REMOTE_1958.yml @@ -0,0 +1,674 @@ +openapi: 3.0.3 +info: + title: Team Dev Server API + description: |- + version: 1.0 + +servers: + - url: http://localhost:4000/ +tags: + - name: user + - name: post + - name: cohort + - name: log + - name: comment +paths: + /users: + post: + tags: + - user + summary: Create user + description: Create new user + operationId: createUser + requestBody: + description: User registration details + content: + application/json: + schema: + $ref: '#/components/schemas/CreateUser' + responses: + '201': + description: Created + content: + application/json: + schema: + $ref: '#/components/schemas/CreatedUser' + get: + tags: + - user + summary: Get all users by first name if provided + description: '' + operationId: getAllUsers + security: + - bearerAuth: [] + parameters: + - name: firstName + in: query + description: Search all users by first name if provided (case-sensitive and exact string matches only) + schema: + type: string + responses: + '200': + description: successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/AllUsers' + '400': + description: fail + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '404': + description: fail + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + /login: + post: + tags: + - user + summary: Localhost Login + description: '' + operationId: loginUser + requestBody: + description: User login information + content: + application/json: + schema: + $ref: '#/components/schemas/login' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/loginRes' + '400': + description: Invalid username/password supplied + /users/{id}: + get: + tags: + - user + summary: Get user by user id + description: '' + operationId: getUserByID + security: + - bearerAuth: [] + parameters: + - name: id + in: path + description: 'The name that needs to be fetched. Use user1 for testing. ' + required: true + schema: + type: string + responses: + '200': + description: successful operation + content: + application/json: + schema: + type: object + properties: + status: + type: string + data: + $ref: '#/components/schemas/User' + '400': + description: fail + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '404': + description: fail + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + patch: + tags: + - user + summary: Update a user + description: Only users with a TEACHER role can update the cohortId or role. Users with Students role can only update their own details. + operationId: userUpdate + security: + - bearerAuth: [] + parameters: + - name: id + in: path + description: 'The user id that needs to be updated' + required: true + schema: + type: string + requestBody: + description: The profile info + content: + application/json: + schema: + $ref: '#/components/schemas/UpdateUser' + responses: + '201': + description: Successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/CreatedUser' + '401': + description: fail + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '400': + description: Invalid email/password/profile information supplied + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + /posts: + post: + tags: + - post + summary: Create post + description: This can only be done by the logged in user. + operationId: createPost + security: + - bearerAuth: [] + requestBody: + description: Created post object + content: + application/json: + schema: + type: object + properties: + content: + type: string + responses: + 201: + description: success + content: + application/json: + schema: + $ref: '#/components/schemas/Post' + 400: + description: fail + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + get: + tags: + - post + summary: Get all posts + description: get all posts + operationId: getPosts + security: + - bearerAuth: [] + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/Posts' + '401': + description: fail + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + /logs: + post: + tags: + - log + summary: Create delivery log + description: This can only be done by an authorised teacher user. + operationId: createLog + security: + - bearerAuth: [] + requestBody: + description: Created log object + content: + application/json: + schema: + type: object + properties: + date: + type: string + cohortId: + type: integer + lines: + type: array + items: + type: object + properties: + content: + type: string + responses: + 201: + description: success + content: + application/json: + schema: + $ref: '#/components/schemas/Log' + '401': + description: Unauthorised + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + /cohorts: + post: + tags: + - cohort + summary: Create a cohort + description: This can only be done by the logged in user with role TEACHER. + operationId: createCohort + security: + - bearerAuth: [] + responses: + 201: + description: success + content: + application/json: + schema: + type: object + properties: + status: + type: string + data: + properties: + cohort: + $ref: '#/components/schemas/Cohort' + 400: + description: fail + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + /comments: + post: + tags: + - comment + summary: Create a comment + description: Create a new comment on a post + operationId: createComment + security: + - bearerAuth: [] + requestBody: + description: Comment details + content: + application/json: + schema: + $ref: '#/components/schemas/CreateComment' + responses: + '201': + description: Created + content: + application/json: + schema: + $ref: '#/components/schemas/Comment' + '400': + description: Invalid input + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + /comments/{commentId}: + patch: + tags: + - comment + summary: Update a comment + description: Update an existing comment + operationId: updateComment + security: + - bearerAuth: [] + parameters: + - name: commentId + in: path + description: The ID of the comment to update + required: true + schema: + type: integer + requestBody: + description: Updated comment details + required: true + content: + application/json: + schema: + type: object + properties: + content: + type: string + userId: + type: integer + required: + - content + - userId + + responses: + '200': + description: Updated + content: + application/json: + schema: + $ref: '#/components/schemas/Comment' + '400': + description: Invalid input + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '404': + description: Comment not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + delete: + tags: + - comment + summary: Delete a comment + description: Delete an existing comment + operationId: deleteComment + security: + - bearerAuth: [] + parameters: + - name: commentId + in: path + description: The ID of the comment to delete + required: true + schema: + type: integer + responses: + '200': + description: Deleted + content: + application/json: + schema: + $ref: '#/components/schemas/Success' + '404': + description: Comment not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + +components: + securitySchemes: + bearerAuth: + type: http + scheme: bearer + bearerFormat: JWT + schemas: + Post: + type: object + properties: + status: + type: string + data: + properties: + post: + properties: + id: + type: integer + content: + type: string + Cohort: + type: object + properties: + id: + type: integer + createdAt: + type: string + format: string + updatedAt: + type: string + format: string + AllUsers: + type: object + properties: + status: + type: string + data: + type: object + properties: + users: + type: array + items: + $ref: '#/components/schemas/User' + User: + type: object + properties: + id: + type: integer + email: + type: string + role: + type: string + cohortId: + type: integer + firstName: + type: string + lastName: + type: string + bio: + type: string + githubUrl: + type: string + CreateUser: + type: object + properties: + firstName: + type: string + lastName: + type: string + email: + type: string + bio: + type: string + githubUrl: + type: string + password: + type: string + UpdateUser: + type: object + properties: + email: + type: string + password: + type: string + cohortId: + type: integer + role: + type: string + firstName: + type: string + lastName: + type: string + bio: + type: string + githubUrl: + type: string + Posts: + type: object + properties: + status: + type: string + data: + type: object + properties: + posts: + type: array + items: + type: object + properties: + id: + type: integer + content: + type: string + createdAt: + type: string + format: string + updatedAt: + type: string + format: string + author: + type: object + properties: + id: + type: integer + cohortId: + type: integer + role: + type: string + firstName: + type: string + lastName: + type: string + bio: + type: string + githubUrl: + type: string + profileImageUrl: + type: string + CreatedUser: + type: object + properties: + status: + type: string + example: success + data: + properties: + user: + properties: + id: + type: integer + email: + type: string + cohortId: + type: integer + role: + type: string + firstName: + type: string + lastName: + type: string + bio: + type: string + githubUrl: + type: string + login: + type: object + properties: + email: + type: string + password: + type: string + loginRes: + type: object + properties: + status: + type: string + data: + properties: + token: + type: string + user: + properties: + id: + type: integer + email: + type: string + cohortId: + type: integer + role: + type: string + firstName: + type: string + lastName: + type: string + bio: + type: string + githubUrl: + type: string + Error: + type: object + properties: + status: + type: string + data: + properties: + error: + type: string + Log: + type: object + properties: + status: + type: string + data: + properties: + log: + properties: + id: + type: integer + cohortId: + type: integer + date: + type: string + author: + type: object + properties: + id: + type: integer + firstName: + type: string + lastName: + type: string + lines: + type: array + items: + type: object + properties: + id: + type: integer + content: + type: string + Comment: + type: object + properties: + id: + type: integer + content: + type: string + userId: + type: integer + postId: + type: integer + CreateComment: + type: object + properties: + content: + type: string + postId: + type: integer + userId: + type: integer + UpdateComment: + type: object + properties: + content: + type: string + userId: + type: integer + required: + - content + - userId + Success: + type: object + properties: + message: + type: string diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 7e97a25b..fbb96839 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -66,8 +66,7 @@ model Comment { user User @relation(fields: [userId], references: [id]) postId Int post Post @relation(fields: [postId], references: [id]) - createdAt DateTime @default(now()) - updatedAt DateTime @default(now()) + } model DeliveryLog { diff --git a/prisma/seed_BACKUP_1958.js b/prisma/seed_BACKUP_1958.js new file mode 100644 index 00000000..a1bdf9e3 --- /dev/null +++ b/prisma/seed_BACKUP_1958.js @@ -0,0 +1,155 @@ +import { PrismaClient } from '@prisma/client' +import bcrypt from 'bcrypt' +const prisma = new PrismaClient() + +async function seed() { + const cohort = await createCohort( + 'Boolean 2024', + new Date('2024-08-08'), + new Date('2024-11-01') + ) + + const student = await createUser( + 'student@test.com', + cohort.id, + 'Joe', + 'Bloggs', + 'Hello, world!', + 'student1@github.com', + 'student1', + '123-456-7890', // mobile + 'Software Engineering', // specialism + new Date('2023-01-01'), // startDate + new Date('2023-12-31'), + null, + 'STUDENT', + 'Testpassword1!' + ) + const teacher = await createUser( + 'teacher@test.com', + null, + 'Rick', + 'Sanchez', + 'Hello there!', + 'teacher1@git.com', + 'teacher1', + '987-654-3210', + 'Teaching', + new Date('2022-01-01'), + new Date('2022-12-31'), + null, + 'TEACHER', + 'Testpassword1!' + ) + + const post1 = await createPost(student.id, 'My first post!') + const post2 = await createPost(teacher.id, 'Hello, students') + await createComment(student.id, post1.id, 'Nice post!') + await createComment(teacher.id, post1.id, 'Thank you!') + await createComment(student.id, post2.id, 'Hello, teacher!') + + process.exit(0) +} + +async function createPost(userId, content) { + const post = await prisma.post.create({ + data: { + userId, + content + }, + include: { + user: true + } + }) + + console.info('Post created', post) + + return post +} + +async function createComment(userId, postId, content) { + const comment = await prisma.comment.create({ + data: { + userId, + postId, + content + }, + include: { + user: true, + post: true + } + }) + + console.info('Comment created', comment) + + return comment +} + +<<<<<<< HEAD +async function createCohort() { +======= +async function createCohort(cohortName, startDate, endDate) { +>>>>>>> 744be751a9ea3780af8e070f719a4a01a850aa1c + const cohort = await prisma.cohort.create({ + data: { + cohortName, + startDate, + endDate + } + }) + console.info('Cohort created', cohort) + return cohort +} + +async function createUser( + email, + cohortId, + firstName, + lastName, + bio, + githubUrl, + username, + mobile, + specialism, + startDate, + endDate, + profileImage, + role = 'STUDENT', + password +) { + const user = await prisma.user.create({ + data: { + email, + password: await bcrypt.hash(password, 8), + role, + cohortId, + profile: { + create: { + firstName, + lastName, + bio, + githubUrl, + profileImage, + username, + mobile, + specialism, + startDate: new Date(startDate), + endDate: new Date(endDate) + } + } + }, + include: { + profile: true + } + }) + + console.info(`${role} created`, user) + + return user +} + +seed().catch(async (e) => { + console.error(e) + await prisma.$disconnect() + process.exit(1) +}) diff --git a/prisma/seed_BASE_1958.js b/prisma/seed_BASE_1958.js new file mode 100644 index 00000000..3d003e98 --- /dev/null +++ b/prisma/seed_BASE_1958.js @@ -0,0 +1,155 @@ +import { PrismaClient } from '@prisma/client' +import bcrypt from 'bcrypt' +const prisma = new PrismaClient() + +async function seed() { + const cohort = await createCohort( + 'Boolean 2024', + new Date('2024-08-08'), + new Date('2024-11-01') + ) + + const student = await createUser( + 'student@test.com', + cohort.id, + 'Joe', + 'Bloggs', + 'Hello, world!', + 'student1@github.com', + 'student1', + '123-456-7890', // mobile + 'Software Engineering', // specialism + new Date('2023-01-01'), // startDate + new Date('2023-12-31'), + null, + 'STUDENT', + 'Testpassword1!' + ) + const teacher = await createUser( + 'teacher@test.com', + null, + 'Rick', + 'Sanchez', + 'Hello there!', + 'teacher1@git.com', + 'teacher1', + '987-654-3210', + 'Teaching', + new Date('2022-01-01'), + new Date('2022-12-31'), + null, + 'TEACHER', + 'Testpassword1!' + ) + + const post1 = await createPost(student.id, 'My first post!') + const post2 = await createPost(teacher.id, 'Hello, students') + await createComment(student.id, post1.id, 'Nice post!') + await createComment(teacher.id, post1.id, 'Thank you!') + await createComment(student.id, post2.id, 'Hello, teacher!') + + process.exit(0) +} + +async function createPost(userId, content) { + const post = await prisma.post.create({ + data: { + userId, + content + }, + include: { + user: true + } + }) + + console.info('Post created', post) + + return post +} + +<<<<<<<<< Temporary merge branch 1 +async function createCohort(cohortName, startDate, endDate) { +========= +async function createComment(userId, postId, content) { + const comment = await prisma.comment.create({ + data: { + userId, + postId, + content + }, + include: { + user: true, + post: true + } + }) + + console.info('Comment created', comment) + + return comment +} + +async function createCohort() { +>>>>>>>>> Temporary merge branch 2 + const cohort = await prisma.cohort.create({ + data: { + cohortName, + startDate, + endDate + } + }) + console.info('Cohort created', cohort) + return cohort +} + +async function createUser( + email, + cohortId, + firstName, + lastName, + bio, + githubUrl, + username, + mobile, + specialism, + startDate, + endDate, + profileImage, + role = 'STUDENT', + password +) { + const user = await prisma.user.create({ + data: { + email, + password: await bcrypt.hash(password, 8), + role, + cohortId, + profile: { + create: { + firstName, + lastName, + bio, + githubUrl, + profileImage, + username, + mobile, + specialism, + startDate: new Date(startDate), + endDate: new Date(endDate) + } + } + }, + include: { + profile: true + } + }) + + console.info(`${role} created`, user) + + return user +} + +seed().catch(async (e) => { + console.error(e) + await prisma.$disconnect() + process.exit(1) +}) diff --git a/prisma/seed_LOCAL_1958.js b/prisma/seed_LOCAL_1958.js new file mode 100644 index 00000000..95a209a6 --- /dev/null +++ b/prisma/seed_LOCAL_1958.js @@ -0,0 +1,151 @@ +import { PrismaClient } from '@prisma/client' +import bcrypt from 'bcrypt' +const prisma = new PrismaClient() + +async function seed() { + const cohort = await createCohort( + 'Boolean 2024', + new Date('2024-08-08'), + new Date('2024-11-01') + ) + + const student = await createUser( + 'student@test.com', + cohort.id, + 'Joe', + 'Bloggs', + 'Hello, world!', + 'student1@github.com', + 'student1', + '123-456-7890', // mobile + 'Software Engineering', // specialism + new Date('2023-01-01'), // startDate + new Date('2023-12-31'), + null, + 'STUDENT', + 'Testpassword1!' + ) + const teacher = await createUser( + 'teacher@test.com', + null, + 'Rick', + 'Sanchez', + 'Hello there!', + 'teacher1@git.com', + 'teacher1', + '987-654-3210', + 'Teaching', + new Date('2022-01-01'), + new Date('2022-12-31'), + null, + 'TEACHER', + 'Testpassword1!' + ) + + const post1 = await createPost(student.id, 'My first post!') + const post2 = await createPost(teacher.id, 'Hello, students') + await createComment(student.id, post1.id, 'Nice post!') + await createComment(teacher.id, post1.id, 'Thank you!') + await createComment(student.id, post2.id, 'Hello, teacher!') + + process.exit(0) +} + +async function createPost(userId, content) { + const post = await prisma.post.create({ + data: { + userId, + content + }, + include: { + user: true + } + }) + + console.info('Post created', post) + + return post +} + +async function createComment(userId, postId, content) { + const comment = await prisma.comment.create({ + data: { + userId, + postId, + content + }, + include: { + user: true, + post: true + } + }) + + console.info('Comment created', comment) + + return comment +} + +async function createCohort() { + const cohort = await prisma.cohort.create({ + data: { + cohortName, + startDate, + endDate + } + }) + console.info('Cohort created', cohort) + return cohort +} + +async function createUser( + email, + cohortId, + firstName, + lastName, + bio, + githubUrl, + username, + mobile, + specialism, + startDate, + endDate, + profileImage, + role = 'STUDENT', + password +) { + const user = await prisma.user.create({ + data: { + email, + password: await bcrypt.hash(password, 8), + role, + cohortId, + profile: { + create: { + firstName, + lastName, + bio, + githubUrl, + profileImage, + username, + mobile, + specialism, + startDate: new Date(startDate), + endDate: new Date(endDate) + } + } + }, + include: { + profile: true + } + }) + + console.info(`${role} created`, user) + + return user +} + +seed().catch(async (e) => { + console.error(e) + await prisma.$disconnect() + process.exit(1) +}) diff --git a/prisma/seed_REMOTE_1958.js b/prisma/seed_REMOTE_1958.js new file mode 100644 index 00000000..26e90042 --- /dev/null +++ b/prisma/seed_REMOTE_1958.js @@ -0,0 +1,151 @@ +import { PrismaClient } from '@prisma/client' +import bcrypt from 'bcrypt' +const prisma = new PrismaClient() + +async function seed() { + const cohort = await createCohort( + 'Boolean 2024', + new Date('2024-08-08'), + new Date('2024-11-01') + ) + + const student = await createUser( + 'student@test.com', + cohort.id, + 'Joe', + 'Bloggs', + 'Hello, world!', + 'student1@github.com', + 'student1', + '123-456-7890', // mobile + 'Software Engineering', // specialism + new Date('2023-01-01'), // startDate + new Date('2023-12-31'), + null, + 'STUDENT', + 'Testpassword1!' + ) + const teacher = await createUser( + 'teacher@test.com', + null, + 'Rick', + 'Sanchez', + 'Hello there!', + 'teacher1@git.com', + 'teacher1', + '987-654-3210', + 'Teaching', + new Date('2022-01-01'), + new Date('2022-12-31'), + null, + 'TEACHER', + 'Testpassword1!' + ) + + const post1 = await createPost(student.id, 'My first post!') + const post2 = await createPost(teacher.id, 'Hello, students') + await createComment(student.id, post1.id, 'Nice post!') + await createComment(teacher.id, post1.id, 'Thank you!') + await createComment(student.id, post2.id, 'Hello, teacher!') + + process.exit(0) +} + +async function createPost(userId, content) { + const post = await prisma.post.create({ + data: { + userId, + content + }, + include: { + user: true + } + }) + + console.info('Post created', post) + + return post +} + +async function createComment(userId, postId, content) { + const comment = await prisma.comment.create({ + data: { + userId, + postId, + content + }, + include: { + user: true, + post: true + } + }) + + console.info('Comment created', comment) + + return comment +} + +async function createCohort(cohortName, startDate, endDate) { + const cohort = await prisma.cohort.create({ + data: { + cohortName, + startDate, + endDate + } + }) + console.info('Cohort created', cohort) + return cohort +} + +async function createUser( + email, + cohortId, + firstName, + lastName, + bio, + githubUrl, + username, + mobile, + specialism, + startDate, + endDate, + profileImage, + role = 'STUDENT', + password +) { + const user = await prisma.user.create({ + data: { + email, + password: await bcrypt.hash(password, 8), + role, + cohortId, + profile: { + create: { + firstName, + lastName, + bio, + githubUrl, + profileImage, + username, + mobile, + specialism, + startDate: new Date(startDate), + endDate: new Date(endDate) + } + } + }, + include: { + profile: true + } + }) + + console.info(`${role} created`, user) + + return user +} + +seed().catch(async (e) => { + console.error(e) + await prisma.$disconnect() + process.exit(1) +}) diff --git a/src/controllers/comment.js b/src/controllers/comment.js index 23f8adb9..d12b7079 100644 --- a/src/controllers/comment.js +++ b/src/controllers/comment.js @@ -1,31 +1,25 @@ import { sendDataResponse, sendMessageResponse } from '../utils/responses.js' import Comment from '../domain/comment.js' +import User from '../domain/user.js' // Create a new comment export const create = async (req, res) => { - const { content, postId, userId } = req.body + const { content, postId } = req.body + const user = await User.findById(req.user.id) - if (!content || !postId || !userId) { + if (!content || !postId) { return sendDataResponse(res, 400, { error: 'Must provide content, postId, and userId' }) } try { - const comment = new Comment({ - content, - userId, - postId - }) - const createdComment = await comment.save() - - // Fetch the created comment from the database - const fetchedComment = await Comment.findById(createdComment.id) - if (!fetchedComment) { + const createdComment = await Comment.createComment(content, user, postId) + if (!createdComment) { return sendDataResponse(res, 404, { id: 'Comment not found' }) } - return sendDataResponse(res, 201, fetchedComment) + return sendDataResponse(res, 201, createdComment) } catch (error) { console.error('Error creating comment:', error) return sendMessageResponse(res, 500, 'Unable to create comment') @@ -34,41 +28,57 @@ export const create = async (req, res) => { // Update an existing comment export const update = async (req, res) => { - const { content } = req.body - const { commentId } = req.params + const { content, userId } = req.body + const { id } = req.params + const commentIdInt = parseInt(id, 10) if (!content) { return sendDataResponse(res, 400, { error: 'Must provide content' }) } + if (isNaN(commentIdInt)) { + return sendDataResponse(res, 400, { error: 'Invalid comment ID' }) + } + try { - const comment = await Comment.findById(commentId) + const comment = await Comment.getCommentById(commentIdInt) if (!comment) { return sendDataResponse(res, 404, { error: 'Comment not found' }) } - const updatedComment = await comment.update(content) + const updatedComment = await Comment.updateContentById( + commentIdInt, + content, + userId + ) return sendDataResponse(res, 200, { comment: updatedComment }) } catch (error) { + console.error('Error updating comment:', error) return sendDataResponse(res, 500, { error: 'Internal Server Error' }) } } // Delete an existing comment export const remove = async (req, res) => { - const { commentId } = req.params + const { id } = req.params + const commentIdInt = parseInt(id, 10) + + if (isNaN(commentIdInt)) { + return sendDataResponse(res, 400, { error: 'Invalid comment ID' }) + } try { - const comment = await Comment.findById(commentId) + const comment = await Comment.getCommentById(commentIdInt) if (!comment) { return res.status(404).json({ error: 'Comment not found' }) } - await comment.delete(); + await Comment.deleteCommentById(commentIdInt) return res.status(200).json({ message: 'Comment deleted successfully' }) } catch (error) { + console.error('Error deleting comment:', error) return res.status(500).json({ error: 'Internal Server Error' }) } } diff --git a/src/domain/comment.js b/src/domain/comment.js index ab7813b1..171005a3 100644 --- a/src/domain/comment.js +++ b/src/domain/comment.js @@ -7,18 +7,28 @@ export default class Comment { * @returns {Comment} */ static fromDb(comment) { - return new Comment({ - id: comment.id, - content: comment.content, - userId: comment.userId, - postId: comment.postId - }) + return new Comment( + comment.id, + comment.content, + comment.userId, + comment.postId, + comment.post + ) } - constructor({ id, content, userId, postId }) { + constructor({ + id = null, + content, + user = null, + post = null, + userId, + postId + }) { this.id = id this.content = content this.userId = userId + this.user = user + this.post = post this.postId = postId } @@ -37,87 +47,66 @@ export default class Comment { /** * Saves the comment to the database + * @param {string} content + * @param {object} user + * @param {int} postId * @returns {Comment} */ - async save() { - try { - const createdComment = await dbClient.comment.create({ - data: { - content: this.content, - userId: this.userId, - postId: this.postId - }, - include: { - user: true, - post: true - } - }) - - return Comment.fromDb(createdComment) - } catch (error) { - console.error('Error saving comment:', error) - throw new Error('Error saving comment') - } - } - - async update(content) { - try { - const updatedComment = await dbClient.comment.update({ - where: { id: this.id }, - data: { content }, - include: { - user: true, - post: true - } - }) - - return Comment.fromDb(updatedComment) - } catch (error) { - console.error('Error updating comment:', error) - throw new Error('Error updating comment') - } + static async createComment(content, user, postId) { + return dbClient.Comment.create({ + data: { + content, + userId: user.id, + postId + } + }) } - static async _findByUnique(field, value) { - if (!value) { - throw new Error(`${field} is required to find a comment`); - } - - try { - const comment = await dbClient.comment.findUnique({ - where: { [field]: parseInt(value, 10) }, - include: { - user: true, - post: true - } - }) - - if (!comment) { - throw new Error('Comment not found'); + /** + * Gets a comment by its ID + * @param {int} id + * @returns {Comment} + */ + static async getCommentById(id) { + const comment = await dbClient.comment.findUnique({ + where: { id }, + include: { + user: true, + post: true } - - return Comment.fromDb(comment) - } catch (error) { - console.error(`Error finding comment by ${field}:`, error) - throw new Error(`Error finding comment by ${field}`) - } + }) + return comment + ? new Comment({ + id: comment.id, + content: comment.content, + userId: comment.userId, + postId: comment.postId + }) + : null } /** - * Deletes the comment from the database - * @returns {boolean} + * Updates the content of a comment by its ID + * @param {int} id + * @param {string} content + * @returns {Comment} */ - async delete() { - try { - await dbClient.comment.delete({ - where: { id: this.id } - }) + static async updateContentById(id, content, userId) { + return dbClient.comment.update({ + where: { id }, + data: { content, userId } + }) + } - return true - } catch (error) { - console.error('Error deleting comment:', error) - throw new Error('Error deleting comment') - } + /** + * Deletes a comment by its ID + * @param {int} id + * @returns {Comment} + */ + static async deleteCommentById(id) { + return dbClient.comment.delete({ + where: { id } + }) } /** @@ -126,7 +115,30 @@ export default class Comment { * @returns {Comment} */ static async findById(id) { - return Comment._findByUnique('id', id) + console.log(id) + if (!id || isNaN(id)) { + throw new Error('Invalid comment ID') + } + const comment = await dbClient.comment.findUnique({ + where: { id: parseInt(id, 10) }, + include: { + user: { + include: { profile: true } + }, + post: true + } + }) + + return comment + ? new Comment({ + id: comment.id, + content: comment.content, + userId: comment.userId, + postId: comment.postId, + user: comment.user, + post: comment.post + }) + : null } /** diff --git a/src/routes/comment.js b/src/routes/comment.js index 0790ca62..2e6b8948 100644 --- a/src/routes/comment.js +++ b/src/routes/comment.js @@ -5,7 +5,7 @@ import { validateAuthentication } from '../middleware/auth.js' const router = Router() router.post('/', validateAuthentication, create) -router.patch('/:commentid', validateAuthentication, update) -router.delete('/:commentid', validateAuthentication, remove) +router.patch('/:id', validateAuthentication, update) +router.delete('/:id', validateAuthentication, remove) export default router From 807e592f572600af72159300597d615d3146ba58 Mon Sep 17 00:00:00 2001 From: Muzea001 Date: Thu, 31 Oct 2024 13:28:40 +0100 Subject: [PATCH 58/72] Deleted all dupliacte files --- docs/openapi_BACKUP_1958.yml | 681 ----------------------------------- docs/openapi_BASE_1958.yml | 681 ----------------------------------- docs/openapi_LOCAL_1958.yml | 668 ---------------------------------- docs/openapi_REMOTE_1958.yml | 674 ---------------------------------- prisma/seed_BACKUP_1958.js | 155 -------- prisma/seed_BASE_1958.js | 155 -------- prisma/seed_LOCAL_1958.js | 151 -------- prisma/seed_REMOTE_1958.js | 151 -------- 8 files changed, 3316 deletions(-) delete mode 100644 docs/openapi_BACKUP_1958.yml delete mode 100644 docs/openapi_BASE_1958.yml delete mode 100644 docs/openapi_LOCAL_1958.yml delete mode 100644 docs/openapi_REMOTE_1958.yml delete mode 100644 prisma/seed_BACKUP_1958.js delete mode 100644 prisma/seed_BASE_1958.js delete mode 100644 prisma/seed_LOCAL_1958.js delete mode 100644 prisma/seed_REMOTE_1958.js diff --git a/docs/openapi_BACKUP_1958.yml b/docs/openapi_BACKUP_1958.yml deleted file mode 100644 index 6fae389c..00000000 --- a/docs/openapi_BACKUP_1958.yml +++ /dev/null @@ -1,681 +0,0 @@ -openapi: 3.0.3 -info: - title: Team Dev Server API - description: |- - version: 1.0 - -servers: - - url: http://localhost:4000/ -tags: - - name: user - - name: post - - name: cohort - - name: log - - name: comment -paths: - /users: - post: - tags: - - user - summary: Create user - description: Create new user - operationId: createUser - requestBody: - description: User registration details - content: - application/json: - schema: - $ref: '#/components/schemas/CreateUser' - responses: - '201': - description: Created - content: - application/json: - schema: - $ref: '#/components/schemas/CreatedUser' - get: - tags: - - user - summary: Get all users by first name if provided - description: '' - operationId: getAllUsers - security: - - bearerAuth: [] - parameters: - - name: firstName - in: query - description: Search all users by first name if provided (case-sensitive and exact string matches only) - schema: - type: string - responses: - '200': - description: successful operation - content: - application/json: - schema: - $ref: '#/components/schemas/AllUsers' - '400': - description: fail - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - '404': - description: fail - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - /login: - post: - tags: - - user - summary: Localhost Login - description: '' - operationId: loginUser - requestBody: - description: User login information - content: - application/json: - schema: - $ref: '#/components/schemas/login' - responses: - '200': - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/loginRes' - '400': - description: Invalid username/password supplied - /users/{id}: - get: - tags: - - user - summary: Get user by user id - description: '' - operationId: getUserByID - security: - - bearerAuth: [] - parameters: - - name: id - in: path - description: 'The name that needs to be fetched. Use user1 for testing. ' - required: true - schema: - type: string - responses: - '200': - description: successful operation - content: - application/json: - schema: - type: object - properties: - status: - type: string - data: - $ref: '#/components/schemas/User' - '400': - description: fail - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - '404': - description: fail - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - patch: - tags: - - user - summary: Update a user - description: Only users with a TEACHER role can update the cohortId or role. Users with Students role can only update their own details. - operationId: userUpdate - security: - - bearerAuth: [] - parameters: - - name: id - in: path - description: 'The user id that needs to be updated' - required: true - schema: - type: string - requestBody: - description: The profile info - content: - application/json: - schema: - $ref: '#/components/schemas/UpdateUser' - responses: - '201': - description: Successful operation - content: - application/json: - schema: - $ref: '#/components/schemas/CreatedUser' - '401': - description: fail - content: - application/json: - schema: - $ref: '#/components/schemas/Error' -<<<<<<< HEAD -======= - '400': - description: Invalid email/password/profile information supplied - content: - application/json: - schema: - $ref: '#/components/schemas/Error' ->>>>>>> 744be751a9ea3780af8e070f719a4a01a850aa1c - /posts: - post: - tags: - - post - summary: Create post - description: This can only be done by the logged in user. - operationId: createPost - security: - - bearerAuth: [] - requestBody: - description: Created post object - content: - application/json: - schema: - type: object - properties: - content: - type: string - responses: - 201: - description: success - content: - application/json: - schema: - $ref: '#/components/schemas/Post' - 400: - description: fail - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - get: - tags: - - post - summary: Get all posts - description: get all posts - operationId: getPosts - security: - - bearerAuth: [] - responses: - '200': - description: Successful operation - content: - application/json: - schema: - $ref: '#/components/schemas/Posts' - '401': - description: fail - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - /logs: - post: - tags: - - log - summary: Create delivery log - description: This can only be done by an authorised teacher user. - operationId: createLog - security: - - bearerAuth: [] - requestBody: - description: Created log object - content: - application/json: - schema: - type: object - properties: - date: - type: string - cohortId: - type: integer - lines: - type: array - items: - type: object - properties: - content: - type: string - responses: - 201: - description: success - content: - application/json: - schema: - $ref: '#/components/schemas/Log' - '401': - description: Unauthorised - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - /cohorts: - post: - tags: - - cohort - summary: Create a cohort - description: This can only be done by the logged in user with role TEACHER. - operationId: createCohort - security: - - bearerAuth: [] - responses: - 201: - description: success - content: - application/json: - schema: - type: object - properties: - status: - type: string - data: - properties: - cohort: - $ref: '#/components/schemas/Cohort' - 400: - description: fail - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - /comments: - post: - tags: - - comment - summary: Create a comment - description: Create a new comment on a post - operationId: createComment - security: - - bearerAuth: [] - requestBody: - description: Comment details - content: - application/json: - schema: - $ref: '#/components/schemas/CreateComment' - responses: - '201': - description: Created - content: - application/json: - schema: - $ref: '#/components/schemas/Comment' - '400': - description: Invalid input - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - /comments/{commentId}: - patch: - tags: - - comment - summary: Update a comment - description: Update an existing comment - operationId: updateComment - security: - - bearerAuth: [] - parameters: - - name: commentId - in: path - description: The ID of the comment to update - required: true - schema: - type: integer - requestBody: - description: Updated comment details - required: true - content: - application/json: - schema: - type: object - properties: - content: - type: string - userId: - type: integer - required: - - content - - userId - - responses: - '200': - description: Updated - content: - application/json: - schema: - $ref: '#/components/schemas/Comment' - '400': - description: Invalid input - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - '404': - description: Comment not found - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - delete: - tags: - - comment - summary: Delete a comment - description: Delete an existing comment - operationId: deleteComment - security: - - bearerAuth: [] - parameters: - - name: commentId - in: path - description: The ID of the comment to delete - required: true - schema: - type: integer - responses: - '200': - description: Deleted - content: - application/json: - schema: - $ref: '#/components/schemas/Success' - '404': - description: Comment not found - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - -components: - securitySchemes: - bearerAuth: - type: http - scheme: bearer - bearerFormat: JWT - schemas: - Post: - type: object - properties: - status: - type: string - data: - properties: - post: - properties: - id: - type: integer - content: - type: string - Cohort: - type: object - properties: - id: - type: integer - createdAt: - type: string - format: string - updatedAt: - type: string - format: string - AllUsers: - type: object - properties: - status: - type: string - data: - type: object - properties: - users: - type: array - items: - $ref: '#/components/schemas/User' - User: - type: object - properties: - id: - type: integer - email: - type: string - role: - type: string - cohortId: - type: integer - firstName: - type: string - lastName: - type: string - bio: - type: string - githubUrl: - type: string - CreateUser: - type: object - properties: - firstName: - type: string - lastName: - type: string - email: - type: string - bio: - type: string - githubUrl: - type: string - password: - type: string - UpdateUser: - type: object - properties: - email: - type: string - password: - type: string - cohortId: - type: integer - role: - type: string - firstName: - type: string - lastName: - type: string - bio: - type: string - githubUrl: - type: string - Posts: - type: object - properties: - status: - type: string - data: - type: object - properties: - posts: - type: array - items: - type: object - properties: - id: - type: integer - content: - type: string - createdAt: - type: string - format: string - updatedAt: - type: string - format: string - author: - type: object - properties: - id: - type: integer - cohortId: - type: integer - role: - type: string - firstName: - type: string - lastName: - type: string - bio: - type: string - githubUrl: - type: string - profileImageUrl: - type: string - CreatedUser: - type: object - properties: - status: - type: string - example: success - data: - properties: - user: - properties: - id: - type: integer - email: - type: string - cohortId: - type: integer - role: - type: string - firstName: - type: string - lastName: - type: string - bio: - type: string - githubUrl: - type: string - login: - type: object - properties: - email: - type: string - password: - type: string - loginRes: - type: object - properties: - status: - type: string - data: - properties: - token: - type: string - user: - properties: - id: - type: integer - email: - type: string - cohortId: - type: integer - role: - type: string - firstName: - type: string - lastName: - type: string - bio: - type: string - githubUrl: - type: string - Error: - type: object - properties: - status: - type: string - data: - properties: - error: - type: string - Log: - type: object - properties: - status: - type: string - data: - properties: - log: - properties: - id: - type: integer - cohortId: - type: integer - date: - type: string - author: - type: object - properties: - id: - type: integer - firstName: - type: string - lastName: - type: string - lines: - type: array - items: - type: object - properties: - id: - type: integer - content: - type: string - Comment: - type: object - properties: - id: - type: integer - content: - type: string - userId: - type: integer - postId: - type: integer - CreateComment: - type: object - properties: - content: - type: string - postId: - type: integer - userId: - type: integer - UpdateComment: - type: object - properties: - content: - type: string - userId: - type: integer - required: - - content - - userId - Success: - type: object - properties: - message: -<<<<<<< HEAD - type: string -======= - type: string ->>>>>>> 744be751a9ea3780af8e070f719a4a01a850aa1c diff --git a/docs/openapi_BASE_1958.yml b/docs/openapi_BASE_1958.yml deleted file mode 100644 index 9b1389fa..00000000 --- a/docs/openapi_BASE_1958.yml +++ /dev/null @@ -1,681 +0,0 @@ -openapi: 3.0.3 -info: - title: Team Dev Server API - description: |- - version: 1.0 - -servers: - - url: http://localhost:4000/ -tags: - - name: user - - name: post - - name: cohort - - name: log - - name: comment -paths: - /users: - post: - tags: - - user - summary: Create user - description: Create new user - operationId: createUser - requestBody: - description: User registration details - content: - application/json: - schema: - $ref: '#/components/schemas/CreateUser' - responses: - '201': - description: Created - content: - application/json: - schema: - $ref: '#/components/schemas/CreatedUser' - get: - tags: - - user - summary: Get all users by first name if provided - description: '' - operationId: getAllUsers - security: - - bearerAuth: [] - parameters: - - name: firstName - in: query - description: Search all users by first name if provided (case-sensitive and exact string matches only) - schema: - type: string - responses: - '200': - description: successful operation - content: - application/json: - schema: - $ref: '#/components/schemas/AllUsers' - '400': - description: fail - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - '404': - description: fail - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - /login: - post: - tags: - - user - summary: Localhost Login - description: '' - operationId: loginUser - requestBody: - description: User login information - content: - application/json: - schema: - $ref: '#/components/schemas/login' - responses: - '200': - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/loginRes' - '400': - description: Invalid username/password supplied - /users/{id}: - get: - tags: - - user - summary: Get user by user id - description: '' - operationId: getUserByID - security: - - bearerAuth: [] - parameters: - - name: id - in: path - description: 'The name that needs to be fetched. Use user1 for testing. ' - required: true - schema: - type: string - responses: - '200': - description: successful operation - content: - application/json: - schema: - type: object - properties: - status: - type: string - data: - $ref: '#/components/schemas/User' - '400': - description: fail - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - '404': - description: fail - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - patch: - tags: - - user - summary: Update a user - description: Only users with a TEACHER role can update the cohortId or role. Users with Students role can only update their own details. - operationId: userUpdate - security: - - bearerAuth: [] - parameters: - - name: id - in: path - description: 'The user id that needs to be updated' - required: true - schema: - type: string - requestBody: - description: The profile info - content: - application/json: - schema: - $ref: '#/components/schemas/UpdateUser' - responses: - '201': - description: Successful operation - content: - application/json: - schema: - $ref: '#/components/schemas/CreatedUser' - '401': - description: fail - content: - application/json: - schema: - $ref: '#/components/schemas/Error' -<<<<<<<<< Temporary merge branch 1 - '400': - description: Invalid email/password/profile information supplied - content: - application/json: - schema: - $ref: '#/components/schemas/Error' -========= ->>>>>>>>> Temporary merge branch 2 - /posts: - post: - tags: - - post - summary: Create post - description: This can only be done by the logged in user. - operationId: createPost - security: - - bearerAuth: [] - requestBody: - description: Created post object - content: - application/json: - schema: - type: object - properties: - content: - type: string - responses: - 201: - description: success - content: - application/json: - schema: - $ref: '#/components/schemas/Post' - 400: - description: fail - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - get: - tags: - - post - summary: Get all posts - description: get all posts - operationId: getPosts - security: - - bearerAuth: [] - responses: - '200': - description: Successful operation - content: - application/json: - schema: - $ref: '#/components/schemas/Posts' - '401': - description: fail - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - /logs: - post: - tags: - - log - summary: Create delivery log - description: This can only be done by an authorised teacher user. - operationId: createLog - security: - - bearerAuth: [] - requestBody: - description: Created log object - content: - application/json: - schema: - type: object - properties: - date: - type: string - cohortId: - type: integer - lines: - type: array - items: - type: object - properties: - content: - type: string - responses: - 201: - description: success - content: - application/json: - schema: - $ref: '#/components/schemas/Log' - '401': - description: Unauthorised - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - /cohorts: - post: - tags: - - cohort - summary: Create a cohort - description: This can only be done by the logged in user with role TEACHER. - operationId: createCohort - security: - - bearerAuth: [] - responses: - 201: - description: success - content: - application/json: - schema: - type: object - properties: - status: - type: string - data: - properties: - cohort: - $ref: '#/components/schemas/Cohort' - 400: - description: fail - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - /comments: - post: - tags: - - comment - summary: Create a comment - description: Create a new comment on a post - operationId: createComment - security: - - bearerAuth: [] - requestBody: - description: Comment details - content: - application/json: - schema: - $ref: '#/components/schemas/CreateComment' - responses: - '201': - description: Created - content: - application/json: - schema: - $ref: '#/components/schemas/Comment' - '400': - description: Invalid input - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - /comments/{commentId}: - patch: - tags: - - comment - summary: Update a comment - description: Update an existing comment - operationId: updateComment - security: - - bearerAuth: [] - parameters: - - name: commentId - in: path - description: The ID of the comment to update - required: true - schema: - type: integer - requestBody: - description: Updated comment details - required: true - content: - application/json: - schema: - type: object - properties: - content: - type: string - userId: - type: integer - required: - - content - - userId - - responses: - '200': - description: Updated - content: - application/json: - schema: - $ref: '#/components/schemas/Comment' - '400': - description: Invalid input - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - '404': - description: Comment not found - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - delete: - tags: - - comment - summary: Delete a comment - description: Delete an existing comment - operationId: deleteComment - security: - - bearerAuth: [] - parameters: - - name: commentId - in: path - description: The ID of the comment to delete - required: true - schema: - type: integer - responses: - '200': - description: Deleted - content: - application/json: - schema: - $ref: '#/components/schemas/Success' - '404': - description: Comment not found - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - -components: - securitySchemes: - bearerAuth: - type: http - scheme: bearer - bearerFormat: JWT - schemas: - Post: - type: object - properties: - status: - type: string - data: - properties: - post: - properties: - id: - type: integer - content: - type: string - Cohort: - type: object - properties: - id: - type: integer - createdAt: - type: string - format: string - updatedAt: - type: string - format: string - AllUsers: - type: object - properties: - status: - type: string - data: - type: object - properties: - users: - type: array - items: - $ref: '#/components/schemas/User' - User: - type: object - properties: - id: - type: integer - email: - type: string - role: - type: string - cohortId: - type: integer - firstName: - type: string - lastName: - type: string - bio: - type: string - githubUrl: - type: string - CreateUser: - type: object - properties: - firstName: - type: string - lastName: - type: string - email: - type: string - bio: - type: string - githubUrl: - type: string - password: - type: string - UpdateUser: - type: object - properties: - email: - type: string - password: - type: string - cohortId: - type: integer - role: - type: string - firstName: - type: string - lastName: - type: string - bio: - type: string - githubUrl: - type: string - Posts: - type: object - properties: - status: - type: string - data: - type: object - properties: - posts: - type: array - items: - type: object - properties: - id: - type: integer - content: - type: string - createdAt: - type: string - format: string - updatedAt: - type: string - format: string - author: - type: object - properties: - id: - type: integer - cohortId: - type: integer - role: - type: string - firstName: - type: string - lastName: - type: string - bio: - type: string - githubUrl: - type: string - profileImageUrl: - type: string - CreatedUser: - type: object - properties: - status: - type: string - example: success - data: - properties: - user: - properties: - id: - type: integer - email: - type: string - cohortId: - type: integer - role: - type: string - firstName: - type: string - lastName: - type: string - bio: - type: string - githubUrl: - type: string - login: - type: object - properties: - email: - type: string - password: - type: string - loginRes: - type: object - properties: - status: - type: string - data: - properties: - token: - type: string - user: - properties: - id: - type: integer - email: - type: string - cohortId: - type: integer - role: - type: string - firstName: - type: string - lastName: - type: string - bio: - type: string - githubUrl: - type: string - Error: - type: object - properties: - status: - type: string - data: - properties: - error: - type: string - Log: - type: object - properties: - status: - type: string - data: - properties: - log: - properties: - id: - type: integer - cohortId: - type: integer - date: - type: string - author: - type: object - properties: - id: - type: integer - firstName: - type: string - lastName: - type: string - lines: - type: array - items: - type: object - properties: - id: - type: integer - content: -<<<<<<<<< Temporary merge branch 1 - type: string -========= - type: string - Comment: - type: object - properties: - id: - type: integer - content: - type: string - userId: - type: integer - postId: - type: integer - CreateComment: - type: object - properties: - content: - type: string - postId: - type: integer - userId: - type: integer - UpdateComment: - type: object - properties: - content: - type: string - userId: - type: integer - required: - - content - - userId - Success: - type: object - properties: - message: - type: string ->>>>>>>>> Temporary merge branch 2 diff --git a/docs/openapi_LOCAL_1958.yml b/docs/openapi_LOCAL_1958.yml deleted file mode 100644 index 479fb119..00000000 --- a/docs/openapi_LOCAL_1958.yml +++ /dev/null @@ -1,668 +0,0 @@ -openapi: 3.0.3 -info: - title: Team Dev Server API - description: |- - version: 1.0 - -servers: - - url: http://localhost:4000/ -tags: - - name: user - - name: post - - name: cohort - - name: log - - name: comment -paths: - /users: - post: - tags: - - user - summary: Create user - description: Create new user - operationId: createUser - requestBody: - description: User registration details - content: - application/json: - schema: - $ref: '#/components/schemas/CreateUser' - responses: - '201': - description: Created - content: - application/json: - schema: - $ref: '#/components/schemas/CreatedUser' - get: - tags: - - user - summary: Get all users by first name if provided - description: '' - operationId: getAllUsers - security: - - bearerAuth: [] - parameters: - - name: firstName - in: query - description: Search all users by first name if provided (case-sensitive and exact string matches only) - schema: - type: string - responses: - '200': - description: successful operation - content: - application/json: - schema: - $ref: '#/components/schemas/AllUsers' - '400': - description: fail - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - '404': - description: fail - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - /login: - post: - tags: - - user - summary: Localhost Login - description: '' - operationId: loginUser - requestBody: - description: User login information - content: - application/json: - schema: - $ref: '#/components/schemas/login' - responses: - '200': - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/loginRes' - '400': - description: Invalid username/password supplied - /users/{id}: - get: - tags: - - user - summary: Get user by user id - description: '' - operationId: getUserByID - security: - - bearerAuth: [] - parameters: - - name: id - in: path - description: 'The name that needs to be fetched. Use user1 for testing. ' - required: true - schema: - type: string - responses: - '200': - description: successful operation - content: - application/json: - schema: - type: object - properties: - status: - type: string - data: - $ref: '#/components/schemas/User' - '400': - description: fail - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - '404': - description: fail - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - patch: - tags: - - user - summary: Update a user - description: Only users with a TEACHER role can update the cohortId or role. Users with Students role can only update their own details. - operationId: userUpdate - security: - - bearerAuth: [] - parameters: - - name: id - in: path - description: 'The user id that needs to be updated' - required: true - schema: - type: string - requestBody: - description: The profile info - content: - application/json: - schema: - $ref: '#/components/schemas/UpdateUser' - responses: - '201': - description: Successful operation - content: - application/json: - schema: - $ref: '#/components/schemas/CreatedUser' - '401': - description: fail - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - /posts: - post: - tags: - - post - summary: Create post - description: This can only be done by the logged in user. - operationId: createPost - security: - - bearerAuth: [] - requestBody: - description: Created post object - content: - application/json: - schema: - type: object - properties: - content: - type: string - responses: - 201: - description: success - content: - application/json: - schema: - $ref: '#/components/schemas/Post' - 400: - description: fail - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - get: - tags: - - post - summary: Get all posts - description: get all posts - operationId: getPosts - security: - - bearerAuth: [] - responses: - '200': - description: Successful operation - content: - application/json: - schema: - $ref: '#/components/schemas/Posts' - '401': - description: fail - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - /logs: - post: - tags: - - log - summary: Create delivery log - description: This can only be done by an authorised teacher user. - operationId: createLog - security: - - bearerAuth: [] - requestBody: - description: Created log object - content: - application/json: - schema: - type: object - properties: - date: - type: string - cohortId: - type: integer - lines: - type: array - items: - type: object - properties: - content: - type: string - responses: - 201: - description: success - content: - application/json: - schema: - $ref: '#/components/schemas/Log' - '401': - description: Unauthorised - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - /cohorts: - post: - tags: - - cohort - summary: Create a cohort - description: This can only be done by the logged in user with role TEACHER. - operationId: createCohort - security: - - bearerAuth: [] - responses: - 201: - description: success - content: - application/json: - schema: - type: object - properties: - status: - type: string - data: - properties: - cohort: - $ref: '#/components/schemas/Cohort' - 400: - description: fail - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - /comments: - post: - tags: - - comment - summary: Create a comment - description: Create a new comment on a post - operationId: createComment - security: - - bearerAuth: [] - requestBody: - description: Comment details - content: - application/json: - schema: - $ref: '#/components/schemas/CreateComment' - responses: - '201': - description: Created - content: - application/json: - schema: - $ref: '#/components/schemas/Comment' - '400': - description: Invalid input - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - /comments/{commentId}: - patch: - tags: - - comment - summary: Update a comment - description: Update an existing comment - operationId: updateComment - security: - - bearerAuth: [] - parameters: - - name: commentId - in: path - description: The ID of the comment to update - required: true - schema: - type: integer - requestBody: - description: Updated comment details - required: true - content: - application/json: - schema: - type: object - properties: - content: - type: string - userId: - type: integer - required: - - content - - userId - - responses: - '200': - description: Updated - content: - application/json: - schema: - $ref: '#/components/schemas/Comment' - '400': - description: Invalid input - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - '404': - description: Comment not found - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - delete: - tags: - - comment - summary: Delete a comment - description: Delete an existing comment - operationId: deleteComment - security: - - bearerAuth: [] - parameters: - - name: commentId - in: path - description: The ID of the comment to delete - required: true - schema: - type: integer - responses: - '200': - description: Deleted - content: - application/json: - schema: - $ref: '#/components/schemas/Success' - '404': - description: Comment not found - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - -components: - securitySchemes: - bearerAuth: - type: http - scheme: bearer - bearerFormat: JWT - schemas: - Post: - type: object - properties: - status: - type: string - data: - properties: - post: - properties: - id: - type: integer - content: - type: string - Cohort: - type: object - properties: - id: - type: integer - createdAt: - type: string - format: string - updatedAt: - type: string - format: string - AllUsers: - type: object - properties: - status: - type: string - data: - type: object - properties: - users: - type: array - items: - $ref: '#/components/schemas/User' - User: - type: object - properties: - id: - type: integer - email: - type: string - role: - type: string - cohortId: - type: integer - firstName: - type: string - lastName: - type: string - bio: - type: string - githubUrl: - type: string - CreateUser: - type: object - properties: - firstName: - type: string - lastName: - type: string - email: - type: string - bio: - type: string - githubUrl: - type: string - password: - type: string - UpdateUser: - type: object - properties: - email: - type: string - password: - type: string - cohortId: - type: integer - role: - type: string - firstName: - type: string - lastName: - type: string - bio: - type: string - githubUrl: - type: string - Posts: - type: object - properties: - status: - type: string - data: - type: object - properties: - posts: - type: array - items: - type: object - properties: - id: - type: integer - content: - type: string - createdAt: - type: string - format: string - updatedAt: - type: string - format: string - author: - type: object - properties: - id: - type: integer - cohortId: - type: integer - role: - type: string - firstName: - type: string - lastName: - type: string - bio: - type: string - githubUrl: - type: string - profileImageUrl: - type: string - CreatedUser: - type: object - properties: - status: - type: string - example: success - data: - properties: - user: - properties: - id: - type: integer - email: - type: string - cohortId: - type: integer - role: - type: string - firstName: - type: string - lastName: - type: string - bio: - type: string - githubUrl: - type: string - login: - type: object - properties: - email: - type: string - password: - type: string - loginRes: - type: object - properties: - status: - type: string - data: - properties: - token: - type: string - user: - properties: - id: - type: integer - email: - type: string - cohortId: - type: integer - role: - type: string - firstName: - type: string - lastName: - type: string - bio: - type: string - githubUrl: - type: string - Error: - type: object - properties: - status: - type: string - data: - properties: - error: - type: string - Log: - type: object - properties: - status: - type: string - data: - properties: - log: - properties: - id: - type: integer - cohortId: - type: integer - date: - type: string - author: - type: object - properties: - id: - type: integer - firstName: - type: string - lastName: - type: string - lines: - type: array - items: - type: object - properties: - id: - type: integer - content: - type: string - Comment: - type: object - properties: - id: - type: integer - content: - type: string - userId: - type: integer - postId: - type: integer - CreateComment: - type: object - properties: - content: - type: string - postId: - type: integer - userId: - type: integer - UpdateComment: - type: object - properties: - content: - type: string - userId: - type: integer - required: - - content - - userId - Success: - type: object - properties: - message: - type: string \ No newline at end of file diff --git a/docs/openapi_REMOTE_1958.yml b/docs/openapi_REMOTE_1958.yml deleted file mode 100644 index 9dee0c00..00000000 --- a/docs/openapi_REMOTE_1958.yml +++ /dev/null @@ -1,674 +0,0 @@ -openapi: 3.0.3 -info: - title: Team Dev Server API - description: |- - version: 1.0 - -servers: - - url: http://localhost:4000/ -tags: - - name: user - - name: post - - name: cohort - - name: log - - name: comment -paths: - /users: - post: - tags: - - user - summary: Create user - description: Create new user - operationId: createUser - requestBody: - description: User registration details - content: - application/json: - schema: - $ref: '#/components/schemas/CreateUser' - responses: - '201': - description: Created - content: - application/json: - schema: - $ref: '#/components/schemas/CreatedUser' - get: - tags: - - user - summary: Get all users by first name if provided - description: '' - operationId: getAllUsers - security: - - bearerAuth: [] - parameters: - - name: firstName - in: query - description: Search all users by first name if provided (case-sensitive and exact string matches only) - schema: - type: string - responses: - '200': - description: successful operation - content: - application/json: - schema: - $ref: '#/components/schemas/AllUsers' - '400': - description: fail - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - '404': - description: fail - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - /login: - post: - tags: - - user - summary: Localhost Login - description: '' - operationId: loginUser - requestBody: - description: User login information - content: - application/json: - schema: - $ref: '#/components/schemas/login' - responses: - '200': - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/loginRes' - '400': - description: Invalid username/password supplied - /users/{id}: - get: - tags: - - user - summary: Get user by user id - description: '' - operationId: getUserByID - security: - - bearerAuth: [] - parameters: - - name: id - in: path - description: 'The name that needs to be fetched. Use user1 for testing. ' - required: true - schema: - type: string - responses: - '200': - description: successful operation - content: - application/json: - schema: - type: object - properties: - status: - type: string - data: - $ref: '#/components/schemas/User' - '400': - description: fail - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - '404': - description: fail - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - patch: - tags: - - user - summary: Update a user - description: Only users with a TEACHER role can update the cohortId or role. Users with Students role can only update their own details. - operationId: userUpdate - security: - - bearerAuth: [] - parameters: - - name: id - in: path - description: 'The user id that needs to be updated' - required: true - schema: - type: string - requestBody: - description: The profile info - content: - application/json: - schema: - $ref: '#/components/schemas/UpdateUser' - responses: - '201': - description: Successful operation - content: - application/json: - schema: - $ref: '#/components/schemas/CreatedUser' - '401': - description: fail - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - '400': - description: Invalid email/password/profile information supplied - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - /posts: - post: - tags: - - post - summary: Create post - description: This can only be done by the logged in user. - operationId: createPost - security: - - bearerAuth: [] - requestBody: - description: Created post object - content: - application/json: - schema: - type: object - properties: - content: - type: string - responses: - 201: - description: success - content: - application/json: - schema: - $ref: '#/components/schemas/Post' - 400: - description: fail - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - get: - tags: - - post - summary: Get all posts - description: get all posts - operationId: getPosts - security: - - bearerAuth: [] - responses: - '200': - description: Successful operation - content: - application/json: - schema: - $ref: '#/components/schemas/Posts' - '401': - description: fail - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - /logs: - post: - tags: - - log - summary: Create delivery log - description: This can only be done by an authorised teacher user. - operationId: createLog - security: - - bearerAuth: [] - requestBody: - description: Created log object - content: - application/json: - schema: - type: object - properties: - date: - type: string - cohortId: - type: integer - lines: - type: array - items: - type: object - properties: - content: - type: string - responses: - 201: - description: success - content: - application/json: - schema: - $ref: '#/components/schemas/Log' - '401': - description: Unauthorised - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - /cohorts: - post: - tags: - - cohort - summary: Create a cohort - description: This can only be done by the logged in user with role TEACHER. - operationId: createCohort - security: - - bearerAuth: [] - responses: - 201: - description: success - content: - application/json: - schema: - type: object - properties: - status: - type: string - data: - properties: - cohort: - $ref: '#/components/schemas/Cohort' - 400: - description: fail - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - /comments: - post: - tags: - - comment - summary: Create a comment - description: Create a new comment on a post - operationId: createComment - security: - - bearerAuth: [] - requestBody: - description: Comment details - content: - application/json: - schema: - $ref: '#/components/schemas/CreateComment' - responses: - '201': - description: Created - content: - application/json: - schema: - $ref: '#/components/schemas/Comment' - '400': - description: Invalid input - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - /comments/{commentId}: - patch: - tags: - - comment - summary: Update a comment - description: Update an existing comment - operationId: updateComment - security: - - bearerAuth: [] - parameters: - - name: commentId - in: path - description: The ID of the comment to update - required: true - schema: - type: integer - requestBody: - description: Updated comment details - required: true - content: - application/json: - schema: - type: object - properties: - content: - type: string - userId: - type: integer - required: - - content - - userId - - responses: - '200': - description: Updated - content: - application/json: - schema: - $ref: '#/components/schemas/Comment' - '400': - description: Invalid input - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - '404': - description: Comment not found - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - delete: - tags: - - comment - summary: Delete a comment - description: Delete an existing comment - operationId: deleteComment - security: - - bearerAuth: [] - parameters: - - name: commentId - in: path - description: The ID of the comment to delete - required: true - schema: - type: integer - responses: - '200': - description: Deleted - content: - application/json: - schema: - $ref: '#/components/schemas/Success' - '404': - description: Comment not found - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - -components: - securitySchemes: - bearerAuth: - type: http - scheme: bearer - bearerFormat: JWT - schemas: - Post: - type: object - properties: - status: - type: string - data: - properties: - post: - properties: - id: - type: integer - content: - type: string - Cohort: - type: object - properties: - id: - type: integer - createdAt: - type: string - format: string - updatedAt: - type: string - format: string - AllUsers: - type: object - properties: - status: - type: string - data: - type: object - properties: - users: - type: array - items: - $ref: '#/components/schemas/User' - User: - type: object - properties: - id: - type: integer - email: - type: string - role: - type: string - cohortId: - type: integer - firstName: - type: string - lastName: - type: string - bio: - type: string - githubUrl: - type: string - CreateUser: - type: object - properties: - firstName: - type: string - lastName: - type: string - email: - type: string - bio: - type: string - githubUrl: - type: string - password: - type: string - UpdateUser: - type: object - properties: - email: - type: string - password: - type: string - cohortId: - type: integer - role: - type: string - firstName: - type: string - lastName: - type: string - bio: - type: string - githubUrl: - type: string - Posts: - type: object - properties: - status: - type: string - data: - type: object - properties: - posts: - type: array - items: - type: object - properties: - id: - type: integer - content: - type: string - createdAt: - type: string - format: string - updatedAt: - type: string - format: string - author: - type: object - properties: - id: - type: integer - cohortId: - type: integer - role: - type: string - firstName: - type: string - lastName: - type: string - bio: - type: string - githubUrl: - type: string - profileImageUrl: - type: string - CreatedUser: - type: object - properties: - status: - type: string - example: success - data: - properties: - user: - properties: - id: - type: integer - email: - type: string - cohortId: - type: integer - role: - type: string - firstName: - type: string - lastName: - type: string - bio: - type: string - githubUrl: - type: string - login: - type: object - properties: - email: - type: string - password: - type: string - loginRes: - type: object - properties: - status: - type: string - data: - properties: - token: - type: string - user: - properties: - id: - type: integer - email: - type: string - cohortId: - type: integer - role: - type: string - firstName: - type: string - lastName: - type: string - bio: - type: string - githubUrl: - type: string - Error: - type: object - properties: - status: - type: string - data: - properties: - error: - type: string - Log: - type: object - properties: - status: - type: string - data: - properties: - log: - properties: - id: - type: integer - cohortId: - type: integer - date: - type: string - author: - type: object - properties: - id: - type: integer - firstName: - type: string - lastName: - type: string - lines: - type: array - items: - type: object - properties: - id: - type: integer - content: - type: string - Comment: - type: object - properties: - id: - type: integer - content: - type: string - userId: - type: integer - postId: - type: integer - CreateComment: - type: object - properties: - content: - type: string - postId: - type: integer - userId: - type: integer - UpdateComment: - type: object - properties: - content: - type: string - userId: - type: integer - required: - - content - - userId - Success: - type: object - properties: - message: - type: string diff --git a/prisma/seed_BACKUP_1958.js b/prisma/seed_BACKUP_1958.js deleted file mode 100644 index a1bdf9e3..00000000 --- a/prisma/seed_BACKUP_1958.js +++ /dev/null @@ -1,155 +0,0 @@ -import { PrismaClient } from '@prisma/client' -import bcrypt from 'bcrypt' -const prisma = new PrismaClient() - -async function seed() { - const cohort = await createCohort( - 'Boolean 2024', - new Date('2024-08-08'), - new Date('2024-11-01') - ) - - const student = await createUser( - 'student@test.com', - cohort.id, - 'Joe', - 'Bloggs', - 'Hello, world!', - 'student1@github.com', - 'student1', - '123-456-7890', // mobile - 'Software Engineering', // specialism - new Date('2023-01-01'), // startDate - new Date('2023-12-31'), - null, - 'STUDENT', - 'Testpassword1!' - ) - const teacher = await createUser( - 'teacher@test.com', - null, - 'Rick', - 'Sanchez', - 'Hello there!', - 'teacher1@git.com', - 'teacher1', - '987-654-3210', - 'Teaching', - new Date('2022-01-01'), - new Date('2022-12-31'), - null, - 'TEACHER', - 'Testpassword1!' - ) - - const post1 = await createPost(student.id, 'My first post!') - const post2 = await createPost(teacher.id, 'Hello, students') - await createComment(student.id, post1.id, 'Nice post!') - await createComment(teacher.id, post1.id, 'Thank you!') - await createComment(student.id, post2.id, 'Hello, teacher!') - - process.exit(0) -} - -async function createPost(userId, content) { - const post = await prisma.post.create({ - data: { - userId, - content - }, - include: { - user: true - } - }) - - console.info('Post created', post) - - return post -} - -async function createComment(userId, postId, content) { - const comment = await prisma.comment.create({ - data: { - userId, - postId, - content - }, - include: { - user: true, - post: true - } - }) - - console.info('Comment created', comment) - - return comment -} - -<<<<<<< HEAD -async function createCohort() { -======= -async function createCohort(cohortName, startDate, endDate) { ->>>>>>> 744be751a9ea3780af8e070f719a4a01a850aa1c - const cohort = await prisma.cohort.create({ - data: { - cohortName, - startDate, - endDate - } - }) - console.info('Cohort created', cohort) - return cohort -} - -async function createUser( - email, - cohortId, - firstName, - lastName, - bio, - githubUrl, - username, - mobile, - specialism, - startDate, - endDate, - profileImage, - role = 'STUDENT', - password -) { - const user = await prisma.user.create({ - data: { - email, - password: await bcrypt.hash(password, 8), - role, - cohortId, - profile: { - create: { - firstName, - lastName, - bio, - githubUrl, - profileImage, - username, - mobile, - specialism, - startDate: new Date(startDate), - endDate: new Date(endDate) - } - } - }, - include: { - profile: true - } - }) - - console.info(`${role} created`, user) - - return user -} - -seed().catch(async (e) => { - console.error(e) - await prisma.$disconnect() - process.exit(1) -}) diff --git a/prisma/seed_BASE_1958.js b/prisma/seed_BASE_1958.js deleted file mode 100644 index 3d003e98..00000000 --- a/prisma/seed_BASE_1958.js +++ /dev/null @@ -1,155 +0,0 @@ -import { PrismaClient } from '@prisma/client' -import bcrypt from 'bcrypt' -const prisma = new PrismaClient() - -async function seed() { - const cohort = await createCohort( - 'Boolean 2024', - new Date('2024-08-08'), - new Date('2024-11-01') - ) - - const student = await createUser( - 'student@test.com', - cohort.id, - 'Joe', - 'Bloggs', - 'Hello, world!', - 'student1@github.com', - 'student1', - '123-456-7890', // mobile - 'Software Engineering', // specialism - new Date('2023-01-01'), // startDate - new Date('2023-12-31'), - null, - 'STUDENT', - 'Testpassword1!' - ) - const teacher = await createUser( - 'teacher@test.com', - null, - 'Rick', - 'Sanchez', - 'Hello there!', - 'teacher1@git.com', - 'teacher1', - '987-654-3210', - 'Teaching', - new Date('2022-01-01'), - new Date('2022-12-31'), - null, - 'TEACHER', - 'Testpassword1!' - ) - - const post1 = await createPost(student.id, 'My first post!') - const post2 = await createPost(teacher.id, 'Hello, students') - await createComment(student.id, post1.id, 'Nice post!') - await createComment(teacher.id, post1.id, 'Thank you!') - await createComment(student.id, post2.id, 'Hello, teacher!') - - process.exit(0) -} - -async function createPost(userId, content) { - const post = await prisma.post.create({ - data: { - userId, - content - }, - include: { - user: true - } - }) - - console.info('Post created', post) - - return post -} - -<<<<<<<<< Temporary merge branch 1 -async function createCohort(cohortName, startDate, endDate) { -========= -async function createComment(userId, postId, content) { - const comment = await prisma.comment.create({ - data: { - userId, - postId, - content - }, - include: { - user: true, - post: true - } - }) - - console.info('Comment created', comment) - - return comment -} - -async function createCohort() { ->>>>>>>>> Temporary merge branch 2 - const cohort = await prisma.cohort.create({ - data: { - cohortName, - startDate, - endDate - } - }) - console.info('Cohort created', cohort) - return cohort -} - -async function createUser( - email, - cohortId, - firstName, - lastName, - bio, - githubUrl, - username, - mobile, - specialism, - startDate, - endDate, - profileImage, - role = 'STUDENT', - password -) { - const user = await prisma.user.create({ - data: { - email, - password: await bcrypt.hash(password, 8), - role, - cohortId, - profile: { - create: { - firstName, - lastName, - bio, - githubUrl, - profileImage, - username, - mobile, - specialism, - startDate: new Date(startDate), - endDate: new Date(endDate) - } - } - }, - include: { - profile: true - } - }) - - console.info(`${role} created`, user) - - return user -} - -seed().catch(async (e) => { - console.error(e) - await prisma.$disconnect() - process.exit(1) -}) diff --git a/prisma/seed_LOCAL_1958.js b/prisma/seed_LOCAL_1958.js deleted file mode 100644 index 95a209a6..00000000 --- a/prisma/seed_LOCAL_1958.js +++ /dev/null @@ -1,151 +0,0 @@ -import { PrismaClient } from '@prisma/client' -import bcrypt from 'bcrypt' -const prisma = new PrismaClient() - -async function seed() { - const cohort = await createCohort( - 'Boolean 2024', - new Date('2024-08-08'), - new Date('2024-11-01') - ) - - const student = await createUser( - 'student@test.com', - cohort.id, - 'Joe', - 'Bloggs', - 'Hello, world!', - 'student1@github.com', - 'student1', - '123-456-7890', // mobile - 'Software Engineering', // specialism - new Date('2023-01-01'), // startDate - new Date('2023-12-31'), - null, - 'STUDENT', - 'Testpassword1!' - ) - const teacher = await createUser( - 'teacher@test.com', - null, - 'Rick', - 'Sanchez', - 'Hello there!', - 'teacher1@git.com', - 'teacher1', - '987-654-3210', - 'Teaching', - new Date('2022-01-01'), - new Date('2022-12-31'), - null, - 'TEACHER', - 'Testpassword1!' - ) - - const post1 = await createPost(student.id, 'My first post!') - const post2 = await createPost(teacher.id, 'Hello, students') - await createComment(student.id, post1.id, 'Nice post!') - await createComment(teacher.id, post1.id, 'Thank you!') - await createComment(student.id, post2.id, 'Hello, teacher!') - - process.exit(0) -} - -async function createPost(userId, content) { - const post = await prisma.post.create({ - data: { - userId, - content - }, - include: { - user: true - } - }) - - console.info('Post created', post) - - return post -} - -async function createComment(userId, postId, content) { - const comment = await prisma.comment.create({ - data: { - userId, - postId, - content - }, - include: { - user: true, - post: true - } - }) - - console.info('Comment created', comment) - - return comment -} - -async function createCohort() { - const cohort = await prisma.cohort.create({ - data: { - cohortName, - startDate, - endDate - } - }) - console.info('Cohort created', cohort) - return cohort -} - -async function createUser( - email, - cohortId, - firstName, - lastName, - bio, - githubUrl, - username, - mobile, - specialism, - startDate, - endDate, - profileImage, - role = 'STUDENT', - password -) { - const user = await prisma.user.create({ - data: { - email, - password: await bcrypt.hash(password, 8), - role, - cohortId, - profile: { - create: { - firstName, - lastName, - bio, - githubUrl, - profileImage, - username, - mobile, - specialism, - startDate: new Date(startDate), - endDate: new Date(endDate) - } - } - }, - include: { - profile: true - } - }) - - console.info(`${role} created`, user) - - return user -} - -seed().catch(async (e) => { - console.error(e) - await prisma.$disconnect() - process.exit(1) -}) diff --git a/prisma/seed_REMOTE_1958.js b/prisma/seed_REMOTE_1958.js deleted file mode 100644 index 26e90042..00000000 --- a/prisma/seed_REMOTE_1958.js +++ /dev/null @@ -1,151 +0,0 @@ -import { PrismaClient } from '@prisma/client' -import bcrypt from 'bcrypt' -const prisma = new PrismaClient() - -async function seed() { - const cohort = await createCohort( - 'Boolean 2024', - new Date('2024-08-08'), - new Date('2024-11-01') - ) - - const student = await createUser( - 'student@test.com', - cohort.id, - 'Joe', - 'Bloggs', - 'Hello, world!', - 'student1@github.com', - 'student1', - '123-456-7890', // mobile - 'Software Engineering', // specialism - new Date('2023-01-01'), // startDate - new Date('2023-12-31'), - null, - 'STUDENT', - 'Testpassword1!' - ) - const teacher = await createUser( - 'teacher@test.com', - null, - 'Rick', - 'Sanchez', - 'Hello there!', - 'teacher1@git.com', - 'teacher1', - '987-654-3210', - 'Teaching', - new Date('2022-01-01'), - new Date('2022-12-31'), - null, - 'TEACHER', - 'Testpassword1!' - ) - - const post1 = await createPost(student.id, 'My first post!') - const post2 = await createPost(teacher.id, 'Hello, students') - await createComment(student.id, post1.id, 'Nice post!') - await createComment(teacher.id, post1.id, 'Thank you!') - await createComment(student.id, post2.id, 'Hello, teacher!') - - process.exit(0) -} - -async function createPost(userId, content) { - const post = await prisma.post.create({ - data: { - userId, - content - }, - include: { - user: true - } - }) - - console.info('Post created', post) - - return post -} - -async function createComment(userId, postId, content) { - const comment = await prisma.comment.create({ - data: { - userId, - postId, - content - }, - include: { - user: true, - post: true - } - }) - - console.info('Comment created', comment) - - return comment -} - -async function createCohort(cohortName, startDate, endDate) { - const cohort = await prisma.cohort.create({ - data: { - cohortName, - startDate, - endDate - } - }) - console.info('Cohort created', cohort) - return cohort -} - -async function createUser( - email, - cohortId, - firstName, - lastName, - bio, - githubUrl, - username, - mobile, - specialism, - startDate, - endDate, - profileImage, - role = 'STUDENT', - password -) { - const user = await prisma.user.create({ - data: { - email, - password: await bcrypt.hash(password, 8), - role, - cohortId, - profile: { - create: { - firstName, - lastName, - bio, - githubUrl, - profileImage, - username, - mobile, - specialism, - startDate: new Date(startDate), - endDate: new Date(endDate) - } - } - }, - include: { - profile: true - } - }) - - console.info(`${role} created`, user) - - return user -} - -seed().catch(async (e) => { - console.error(e) - await prisma.$disconnect() - process.exit(1) -}) From fd28ddc0ce4b264b7f6d6664a86b7678568fe9cc Mon Sep 17 00:00:00 2001 From: Muzea001 Date: Thu, 31 Oct 2024 13:43:41 +0100 Subject: [PATCH 59/72] fixed openapi.yml file --- docs/openapi.yml | 246 +++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 236 insertions(+), 10 deletions(-) diff --git a/docs/openapi.yml b/docs/openapi.yml index 6eb580e5..b4503921 100644 --- a/docs/openapi.yml +++ b/docs/openapi.yml @@ -11,7 +11,6 @@ tags: - name: post - name: cohort - name: log - - name: comment paths: /users: post: @@ -33,6 +32,12 @@ paths: application/json: schema: $ref: '#/components/schemas/CreatedUser' + '400': + description: Invalid email/password supplied + content: + application/json: + schema: + $ref: '#/components/schemas/Error' get: tags: - user @@ -193,8 +198,14 @@ paths: application/json: schema: $ref: '#/components/schemas/Post' - 400: - description: fail + 401: + description: Unauthorised + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '500': + description: Internal server error content: application/json: schema: @@ -215,7 +226,140 @@ paths: schema: $ref: '#/components/schemas/Posts' '401': - description: fail + description: Unauthorised + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '500': + description: Internal server error + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + /posts/{id}: + get: + tags: + - post + summary: Get a post by id + description: get a post + operationId: getPostById + security: + - bearerAuth: [] + parameters: + - name: id + in: path + description: 'The post id that needs to be updated' + required: true + schema: + type: integer + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/Posts' + '401': + description: Unauthorised + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '404': + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '500': + description: Server error + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + patch: + tags: + - post + summary: Patch a post by id + description: patch a post + operationId: updatePostById + security: + - bearerAuth: [] + parameters: + - name: id + in: path + description: 'The post id that needs to be updated' + required: true + schema: + type: integer + requestBody: + description: The post description + content: + application/json: + schema: + $ref: '#/components/schemas/UpdatePost' + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/Posts' + '401': + description: Unauthorised + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '404': + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '500': + description: Server error + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + delete: + tags: + - post + summary: Delete a post by id + description: delete a post + operationId: deletePostById + security: + - bearerAuth: [] + parameters: + - name: id + in: path + description: 'The post id that needs to be updated' + required: true + schema: + type: integer + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/Posts' + '401': + description: Unauthorised + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '404': + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '500': + description: Server error content: application/json: schema: @@ -476,7 +620,6 @@ paths: application/json: schema: $ref: '#/components/schemas/Error' - components: securitySchemes: bearerAuth: @@ -497,6 +640,7 @@ components: type: integer content: type: string + Cohort: type: object properties: @@ -506,7 +650,7 @@ components: type: string createdAt: type: string - format: string + format: date-time updatedAt: type: string format: date-time @@ -527,6 +671,7 @@ components: type: array items: $ref: '#/components/schemas/User' + User: type: object properties: @@ -546,6 +691,21 @@ components: type: string githubUrl: type: string + username: + type: string + mobile: + type: string + specialism: + type: string + startDate: + type: string + format: date-time + endDate: + type: string + format: date-time + profileImage: + type: string + CreateUser: type: object properties: @@ -559,8 +719,23 @@ components: type: string githubUrl: type: string + username: + type: string + mobile: + type: string + specialism: + type: string + startDate: + type: string + format: date-time + endDate: + type: string + format: date-time password: type: string + profileImage: + type: string + UpdateUser: type: object properties: @@ -580,6 +755,21 @@ components: type: string githubUrl: type: string + username: + type: string + mobile: + type: string + specialism: + type: string + startDate: + type: string + format: date-time + endDate: + type: string + format: date-time + profileImage: + type: string + Posts: type: object properties: @@ -599,10 +789,10 @@ components: type: string createdAt: type: string - format: string + format: date-time updatedAt: type: string - format: string + format: date-time author: type: object properties: @@ -620,8 +810,27 @@ components: type: string githubUrl: type: string - profileImageUrl: + username: + type: string + mobile: + type: string + specialism: + type: string + startDate: + type: string + format: date-time + endDate: + type: string + format: date-time + profileImage: type: string + + UpdatePost: + type: object + properties: + content: + type: string + CreatedUser: type: object properties: @@ -648,6 +857,21 @@ components: type: string githubUrl: type: string + username: + type: string + mobile: + type: string + specialism: + type: string + startDate: + type: string + format: date-time + endDate: + type: string + format: date-time + profileImage: + type: string + login: type: object properties: @@ -655,6 +879,7 @@ components: type: string password: type: string + loginRes: type: object properties: @@ -706,6 +931,7 @@ components: properties: error: type: string + Log: type: object properties: @@ -773,4 +999,4 @@ components: type: object properties: message: - type: string \ No newline at end of file + type: string From 6083755d112f93c0526e4d35adb1233f1df2686e Mon Sep 17 00:00:00 2001 From: eyvmal Date: Thu, 31 Oct 2024 13:18:21 +0100 Subject: [PATCH 60/72] add patch and delete endpoint with documentation --- docs/openapi.yml | 95 +++++++++++++++++++++++++++++++++++++++ src/controllers/cohort.js | 41 +++++++++++++++++ src/domain/cohort.js | 18 ++++++++ src/routes/cohort.js | 10 ++++- 4 files changed, 163 insertions(+), 1 deletion(-) diff --git a/docs/openapi.yml b/docs/openapi.yml index 9c9d18fa..c1b507ff 100644 --- a/docs/openapi.yml +++ b/docs/openapi.yml @@ -513,6 +513,89 @@ paths: application/json: schema: $ref: '#/components/schemas/Error' + patch: + tags: + - cohort + summary: Patch a cohort by id + description: This can only be done by the logged in user with role TEACHER. + operationId: updateCohortById + security: + - bearerAuth: [] + parameters: + - name: id + in: path + required: true + schema: + type: integer + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/UpdateCohort' + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/Posts' + '401': + description: Unauthorised + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '404': + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '500': + description: Server error + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + delete: + tags: + - cohort + summary: Delete a cohort by id + description: This can only be done by the logged in user with role TEACHER. + operationId: deleteCohortById + security: + - bearerAuth: [] + parameters: + - name: id + in: path + required: true + schema: + type: integer + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/Posts' + '401': + description: Unauthorised + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '404': + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '500': + description: Server error + content: + application/json: + schema: + $ref: '#/components/schemas/Error' components: securitySchemes: @@ -553,6 +636,18 @@ components: items: $ref: '#/components/schemas/User' + UpdateCohort: + type: object + properties: + name: + type: string + startDate: + type: string + format: date-time + endDate: + type: string + format: date-time + AllUsers: type: object properties: diff --git a/src/controllers/cohort.js b/src/controllers/cohort.js index 13d57d7a..3618c46d 100644 --- a/src/controllers/cohort.js +++ b/src/controllers/cohort.js @@ -30,3 +30,44 @@ export const getById = async (req, res) => { return sendDataResponse(res, 200, cohort) } + +export const updateById = async (req, res) => { + const { id } = req.params + const { name, startDate, endDate } = req.body + + try { + const cohort = await Cohort.getById(Number(id)) + if (cohort) { + const content = { name, startDate, endDate } + const updatedCohort = await Cohort.updateById(Number(id), content) + return sendDataResponse(res, 200, { cohort: updatedCohort }) + } else { + return sendDataResponse(res, 404, { + content: `Cohort with id ${id} not found` + }) + } + } catch (error) { + return sendDataResponse(res, 500, { + content: `Internal server error ${error}` + }) + } +} + +export const deleteById = async (req, res) => { + const { id } = req.params + + try { + const deletedCohort = await Cohort.deleteById(Number(id)) + if (deletedCohort) { + return sendDataResponse(res, 200, { content: 'Cohort deleted' }) + } else { + return sendDataResponse(res, 404, { + content: `Cohort with id ${id} not found` + }) + } + } catch (error) { + return sendDataResponse(res, 500, { + content: 'Internal server error' + }) + } +} diff --git a/src/domain/cohort.js b/src/domain/cohort.js index 5b254883..3b63100e 100644 --- a/src/domain/cohort.js +++ b/src/domain/cohort.js @@ -92,4 +92,22 @@ export default class Cohort { }) return cohort ? Cohort.fromDb(cohort) : null } + + static async updateById(id, cohort) { + const data = {} + if (cohort.name !== undefined) data.cohortName = cohort.name + if (cohort.startDate !== undefined) data.startDate = cohort.startDate + if (cohort.endDate !== undefined) data.endDate = cohort.endDate + + return dbClient.cohort.update({ + where: { id }, + data + }) + } + + static async deleteById(id) { + return dbClient.cohort.delete({ + where: { id } + }) + } } diff --git a/src/routes/cohort.js b/src/routes/cohort.js index ac5d7b63..1c1bc77b 100644 --- a/src/routes/cohort.js +++ b/src/routes/cohort.js @@ -1,5 +1,11 @@ import { Router } from 'express' -import { create, getAll, getById } from '../controllers/cohort.js' +import { + create, + getAll, + getById, + updateById, + deleteById +} from '../controllers/cohort.js' import { validateAuthentication, validateTeacherRole @@ -10,5 +16,7 @@ const router = Router() router.post('/', validateAuthentication, validateTeacherRole, create) router.get('/', validateAuthentication, getAll) router.get('/:id', validateAuthentication, getById) +router.patch('/:id', validateAuthentication, updateById) +router.delete('/:id', validateAuthentication, deleteById) export default router From c918f73ee0e400da5f56006c7db0db601a6320fe Mon Sep 17 00:00:00 2001 From: eyvmal Date: Thu, 31 Oct 2024 13:44:04 +0100 Subject: [PATCH 61/72] update patch and delete endpoint --- src/controllers/cohort.js | 27 ++++++++++----------------- src/domain/cohort.js | 22 ++++++++++++++-------- src/routes/cohort.js | 4 ++-- 3 files changed, 26 insertions(+), 27 deletions(-) diff --git a/src/controllers/cohort.js b/src/controllers/cohort.js index 3618c46d..8ff935c2 100644 --- a/src/controllers/cohort.js +++ b/src/controllers/cohort.js @@ -36,20 +36,16 @@ export const updateById = async (req, res) => { const { name, startDate, endDate } = req.body try { - const cohort = await Cohort.getById(Number(id)) + const cohort = await Cohort.getCohortById(Number(id)) if (cohort) { const content = { name, startDate, endDate } const updatedCohort = await Cohort.updateById(Number(id), content) - return sendDataResponse(res, 200, { cohort: updatedCohort }) + return sendDataResponse(res, 200, { cohort: updatedCohort.toJSON() }) } else { - return sendDataResponse(res, 404, { - content: `Cohort with id ${id} not found` - }) + return sendMessageResponse(res, 404, `Cohort with id ${id} not found`) } } catch (error) { - return sendDataResponse(res, 500, { - content: `Internal server error ${error}` - }) + return sendDataResponse(res, 500, `Internal server error ${error}`) } } @@ -57,17 +53,14 @@ export const deleteById = async (req, res) => { const { id } = req.params try { - const deletedCohort = await Cohort.deleteById(Number(id)) - if (deletedCohort) { - return sendDataResponse(res, 200, { content: 'Cohort deleted' }) + const cohort = await Cohort.getCohortById(Number(id)) + if (cohort) { + const deletedCohort = await Cohort.deleteById(Number(id)) + return sendDataResponse(res, 200, deletedCohort) } else { - return sendDataResponse(res, 404, { - content: `Cohort with id ${id} not found` - }) + return sendMessageResponse(res, 404, `Cohort with id ${id} not found`) } } catch (error) { - return sendDataResponse(res, 500, { - content: 'Internal server error' - }) + return sendMessageResponse(res, 500, 'Internal server error') } } diff --git a/src/domain/cohort.js b/src/domain/cohort.js index 3b63100e..5f25a941 100644 --- a/src/domain/cohort.js +++ b/src/domain/cohort.js @@ -8,7 +8,9 @@ export default class Cohort { cohort.cohortName, cohort.startDate, cohort.endDate, - cohort.students.map((student) => User.fromDb(student)) + cohort.students + ? cohort.students.map((student) => User.fromDb(student)) + : [] ) } @@ -99,15 +101,19 @@ export default class Cohort { if (cohort.startDate !== undefined) data.startDate = cohort.startDate if (cohort.endDate !== undefined) data.endDate = cohort.endDate - return dbClient.cohort.update({ - where: { id }, - data - }) + return this.fromDb( + await dbClient.cohort.update({ + where: { id }, + data + }) + ) } static async deleteById(id) { - return dbClient.cohort.delete({ - where: { id } - }) + return this.fromDb( + await dbClient.cohort.delete({ + where: { id } + }) + ) } } diff --git a/src/routes/cohort.js b/src/routes/cohort.js index 1c1bc77b..dcb40dde 100644 --- a/src/routes/cohort.js +++ b/src/routes/cohort.js @@ -16,7 +16,7 @@ const router = Router() router.post('/', validateAuthentication, validateTeacherRole, create) router.get('/', validateAuthentication, getAll) router.get('/:id', validateAuthentication, getById) -router.patch('/:id', validateAuthentication, updateById) -router.delete('/:id', validateAuthentication, deleteById) +router.patch('/:id', validateAuthentication, validateTeacherRole, updateById) +router.delete('/:id', validateAuthentication, validateTeacherRole, deleteById) export default router From aaf715bd62a00aeed7dcb7ce785a181275a29e40 Mon Sep 17 00:00:00 2001 From: thomamn <126023720+thomamn@users.noreply.github.com> Date: Thu, 31 Oct 2024 13:46:34 +0100 Subject: [PATCH 62/72] Added post content validation to the create post route. --- src/middleware/post.js | 19 +++++++++++++++++++ src/routes/post.js | 7 +++++-- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/src/middleware/post.js b/src/middleware/post.js index 8f110acf..f2d189c6 100644 --- a/src/middleware/post.js +++ b/src/middleware/post.js @@ -20,3 +20,22 @@ export async function validatePostOwnership(req, res, next) { return sendDataResponse(res, 500, { content: 'Internal server error' }) } } + +export async function validatePostContent(req, res, next) { + const { content } = req.body + const maxLength = 200 + + if (!content || content.trim() === '') { + return sendDataResponse(res, 400, { + content: 'Content cannot be empty or null' + }) + } + + if (content.length > maxLength) { + return sendDataResponse(res, 400, { + content: `Content cannot exceed ${maxLength} characters` + }) + } + + next() +} diff --git a/src/routes/post.js b/src/routes/post.js index bc9ef6e6..90e42261 100644 --- a/src/routes/post.js +++ b/src/routes/post.js @@ -7,11 +7,14 @@ import { deleteById } from '../controllers/post.js' import { validateAuthentication } from '../middleware/auth.js' -import { validatePostOwnership } from '../middleware/post.js' +import { + validatePostOwnership, + validatePostContent +} from '../middleware/post.js' const router = Router() -router.post('/', validateAuthentication, create) +router.post('/', validateAuthentication, validatePostContent, create) router.get('/', validateAuthentication, getAll) router.get('/:id', validateAuthentication, getById) router.patch('/:id', validateAuthentication, validatePostOwnership, updateById) From 5fb8b0ae620a527e61c9db2d17675248ddfad0cd Mon Sep 17 00:00:00 2001 From: Tuva Aarseth Date: Thu, 31 Oct 2024 14:05:11 +0100 Subject: [PATCH 63/72] Updated prisma schema for user and post, added likedposts and likedby. Updated post domain, controller, middleware and routes for liking posts. Added new endpoints for liking and unliking posts --- docs/openapi.yml | 552 +++++++++--------- .../migration.sql | 17 + prisma/schema.prisma | 3 + prisma/seed.js | 15 + src/controllers/post.js | 32 + src/controllers/user.js | 2 - src/domain/post.js | 88 ++- src/domain/user.js | 2 - src/middleware/post.js | 15 + src/routes/post.js | 16 +- 10 files changed, 458 insertions(+), 284 deletions(-) create mode 100644 prisma/migrations/20241031114304_add_users_liked_to_post/migration.sql diff --git a/docs/openapi.yml b/docs/openapi.yml index 4d89ecf5..01d8b276 100644 --- a/docs/openapi.yml +++ b/docs/openapi.yml @@ -11,6 +11,7 @@ tags: - name: post - name: cohort - name: log + paths: /users: post: @@ -83,14 +84,14 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/login' + $ref: '#/components/schemas/Login' responses: '200': description: OK content: application/json: schema: - $ref: '#/components/schemas/loginRes' + $ref: '#/components/schemas/LoginRes' '400': description: Invalid username/password supplied /users/{id}: @@ -105,7 +106,7 @@ paths: parameters: - name: id in: path - description: 'The name that needs to be fetched. Use user1 for testing. ' + description: 'The ID of the user to fetch.' required: true schema: type: string @@ -115,12 +116,7 @@ paths: content: application/json: schema: - type: object - properties: - status: - type: string - data: - $ref: '#/components/schemas/User' + $ref: '#/components/schemas/UserResponse' '400': description: fail content: @@ -137,14 +133,14 @@ paths: tags: - user summary: Update a user - description: Only users with a TEACHER role can update the cohortId or role. Users with Students role can only update their own details. + description: Only users with a TEACHER role can update the cohortId or role. Users with STUDENT role can only update their own details. operationId: userUpdate security: - bearerAuth: [] parameters: - name: id in: path - description: 'The user id that needs to be updated' + description: 'The user ID that needs to be updated.' required: true schema: type: string @@ -155,14 +151,14 @@ paths: schema: $ref: '#/components/schemas/UpdateUser' responses: - '201': + '200': description: Successful operation content: application/json: schema: - $ref: '#/components/schemas/CreatedUser' + $ref: '#/components/schemas/UserResponse' '401': - description: fail + description: Unauthorized content: application/json: schema: @@ -178,7 +174,7 @@ paths: tags: - post summary: Create post - description: This can only be done by the logged in user. + description: This can only be done by the logged-in user. operationId: createPost security: - bearerAuth: [] @@ -187,19 +183,16 @@ paths: content: application/json: schema: - type: object - properties: - content: - type: string + $ref: '#/components/schemas/CreatePost' responses: - 201: - description: success + '201': + description: Success content: application/json: schema: - $ref: '#/components/schemas/Post' - 401: - description: Unauthorised + $ref: '#/components/schemas/PostResponse' + '401': + description: Unauthorized content: application/json: schema: @@ -214,7 +207,7 @@ paths: tags: - post summary: Get all posts - description: get all posts + description: Get all posts operationId: getPosts security: - bearerAuth: [] @@ -224,9 +217,9 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/Posts' + $ref: '#/components/schemas/PostsResponse' '401': - description: Unauthorised + description: Unauthorized content: application/json: schema: @@ -241,15 +234,15 @@ paths: get: tags: - post - summary: Get a post by id - description: get a post + summary: Get a post by ID + description: Get a post operationId: getPostById security: - bearerAuth: [] parameters: - name: id in: path - description: 'The post id that needs to be updated' + description: 'The post ID to retrieve.' required: true schema: type: integer @@ -259,15 +252,15 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/Posts' + $ref: '#/components/schemas/PostResponse' '401': - description: Unauthorised + description: Unauthorized content: application/json: schema: $ref: '#/components/schemas/Error' '404': - description: Not Found + description: Not found content: application/json: schema: @@ -281,20 +274,20 @@ paths: patch: tags: - post - summary: Patch a post by id - description: patch a post + summary: Update a post by ID + description: Update a post operationId: updatePostById security: - bearerAuth: [] parameters: - name: id in: path - description: 'The post id that needs to be updated' + description: 'The post ID that needs to be updated.' required: true schema: type: integer requestBody: - description: The post description + description: The post content to update content: application/json: schema: @@ -305,15 +298,15 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/Posts' + $ref: '#/components/schemas/PostResponse' '401': - description: Unauthorised + description: Unauthorized content: application/json: schema: $ref: '#/components/schemas/Error' '404': - description: Not Found + description: Not found content: application/json: schema: @@ -327,15 +320,15 @@ paths: delete: tags: - post - summary: Delete a post by id - description: delete a post + summary: Delete a post by ID + description: Delete a post operationId: deletePostById security: - bearerAuth: [] parameters: - name: id in: path - description: 'The post id that needs to be updated' + description: 'The post ID that needs to be deleted.' required: true schema: type: integer @@ -345,15 +338,15 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/Posts' + $ref: '#/components/schemas/PostResponse' '401': - description: Unauthorised + description: Unauthorized content: application/json: schema: $ref: '#/components/schemas/Error' '404': - description: Not Found + description: Not found content: application/json: schema: @@ -364,12 +357,82 @@ paths: application/json: schema: $ref: '#/components/schemas/Error' + /posts/{id}/like: + post: + tags: + - post + summary: Like a post + description: Allows a user to like a post. + operationId: likePost + security: + - bearerAuth: [] + parameters: + - name: id + in: path + description: ID of the post to like + required: true + schema: + type: integer + responses: + '200': + description: Post liked successfully + content: + application/json: + schema: + $ref: '#/components/schemas/PostResponse' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '404': + description: Post not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + /posts/{id}/unlike: + post: + tags: + - post + summary: Unlike a post + description: Allows a user to unlike a post. + operationId: unlikePost + security: + - bearerAuth: [] + parameters: + - name: id + in: path + description: ID of the post to unlike + required: true + schema: + type: integer + responses: + '200': + description: Post unliked successfully + content: + application/json: + schema: + $ref: '#/components/schemas/PostResponse' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '404': + description: Post not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error' /logs: post: tags: - log summary: Create delivery log - description: This can only be done by an authorised teacher user. + description: This can only be done by an authorized teacher user. operationId: createLog security: - bearerAuth: [] @@ -378,28 +441,16 @@ paths: content: application/json: schema: - type: object - properties: - date: - type: string - cohortId: - type: integer - lines: - type: array - items: - type: object - properties: - content: - type: string + $ref: '#/components/schemas/CreateLog' responses: - 201: - description: success + '201': + description: Success content: application/json: schema: - $ref: '#/components/schemas/Log' + $ref: '#/components/schemas/LogResponse' '401': - description: Unauthorised + description: Unauthorized content: application/json: schema: @@ -409,26 +460,25 @@ paths: tags: - cohort summary: Create a cohort - description: This can only be done by the logged in user with role TEACHER. + description: This can only be done by a logged-in user with role TEACHER. operationId: createCohort security: - bearerAuth: [] + requestBody: + description: Created cohort object + content: + application/json: + schema: + $ref: '#/components/schemas/CreateCohort' responses: - 201: - description: success + '201': + description: Success content: application/json: schema: - type: object - properties: - status: - type: string - data: - properties: - cohort: - $ref: '#/components/schemas/Cohort' - 400: - description: fail + $ref: '#/components/schemas/CohortResponse' + '400': + description: Fail content: application/json: schema: @@ -441,60 +491,102 @@ components: scheme: bearer bearerFormat: JWT schemas: - Post: + User: type: object properties: - status: + id: + type: integer + email: type: string - data: - properties: - post: - properties: - id: - type: integer - content: - type: string - - Cohort: + role: + type: string + cohortId: + type: integer + firstName: + type: string + lastName: + type: string + bio: + type: string + githubUrl: + type: string + username: + type: string + mobile: + type: string + specialism: + type: string + startDate: + type: string + format: date-time + endDate: + type: string + format: date-time + profileImage: + type: string + Post: type: object properties: id: type: integer + content: + type: string createdAt: type: string format: date-time updatedAt: type: string format: date-time - - AllUsers: + author: + $ref: '#/components/schemas/User' + likedBy: + type: array + items: + $ref: '#/components/schemas/User' + Cohort: type: object properties: - status: + id: + type: integer + cohortName: type: string - data: - type: object - properties: - users: - type: array - items: - $ref: '#/components/schemas/User' - - User: + startDate: + type: string + format: date-time + endDate: + type: string + format: date-time + Log: type: object properties: id: type: integer - email: - type: string - role: + date: type: string cohortId: type: integer + author: + $ref: '#/components/schemas/User' + lines: + type: array + items: + $ref: '#/components/schemas/LogLine' + LogLine: + type: object + properties: + id: + type: integer + content: + type: string + CreateUser: + type: object + properties: firstName: type: string lastName: type: string + email: + type: string bio: type: string githubUrl: @@ -511,18 +603,25 @@ components: endDate: type: string format: date-time + password: + type: string profileImage: type: string - - CreateUser: + UpdateUser: type: object properties: + email: + type: string + password: + type: string + cohortId: + type: integer + role: + type: string firstName: type: string lastName: type: string - email: - type: string bio: type: string githubUrl: @@ -539,17 +638,49 @@ components: endDate: type: string format: date-time - password: - type: string profileImage: type: string - - UpdateUser: + CreatePost: type: object properties: - email: + content: type: string - password: + UpdatePost: + type: object + properties: + content: + type: string + CreateCohort: + type: object + properties: + cohortName: + type: string + startDate: + type: string + format: date-time + endDate: + type: string + format: date-time + CreateLog: + type: object + properties: + date: + type: string + cohortId: + type: integer + lines: + type: array + items: + type: object + properties: + content: + type: string + CreatedUser: + type: object + properties: + id: + type: integer + email: type: string cohortId: type: integer @@ -577,118 +708,58 @@ components: format: date-time profileImage: type: string - - Posts: + AllUsers: + type: object + properties: + users: + type: array + items: + $ref: '#/components/schemas/User' + UserResponse: type: object properties: status: type: string data: - type: object - properties: - posts: - type: array - items: - type: object - properties: - id: - type: integer - content: - type: string - createdAt: - type: string - format: date-time - updatedAt: - type: string - format: date-time - author: - type: object - properties: - id: - type: integer - cohortId: - type: integer - role: - type: string - firstName: - type: string - lastName: - type: string - bio: - type: string - githubUrl: - type: string - username: - type: string - mobile: - type: string - specialism: - type: string - startDate: - type: string - format: date-time - endDate: - type: string - format: date-time - profileImage: - type: string - - UpdatePost: + $ref: '#/components/schemas/User' + PostResponse: type: object properties: - content: + status: type: string - - CreatedUser: + data: + $ref: '#/components/schemas/Post' + PostsResponse: type: object properties: status: type: string - example: success data: - properties: - user: - properties: - id: - type: integer - email: - type: string - cohortId: - type: integer - role: - type: string - firstName: - type: string - lastName: - type: string - bio: - type: string - githubUrl: - type: string - username: - type: string - mobile: - type: string - specialism: - type: string - startDate: - type: string - format: date-time - endDate: - type: string - format: date-time - profileImage: - type: string - - login: + type: array + items: + $ref: '#/components/schemas/Post' + CohortResponse: + type: object + properties: + status: + type: string + data: + $ref: '#/components/schemas/Cohort' + LogResponse: + type: object + properties: + status: + type: string + data: + $ref: '#/components/schemas/Log' + Login: type: object properties: email: type: string password: type: string - - loginRes: + LoginRes: type: object properties: status: @@ -698,38 +769,7 @@ components: token: type: string user: - properties: - id: - type: integer - email: - type: string - cohortId: - type: integer - role: - type: string - firstName: - type: string - lastName: - type: string - bio: - type: string - githubUrl: - type: string - username: - type: string - mobile: - type: string - specialism: - type: string - startDate: - type: string - format: date-time - endDate: - type: string - format: date-time - profileImage: - type: string - + $ref: '#/components/schemas/User' Error: type: object properties: @@ -739,37 +779,3 @@ components: properties: error: type: string - - Log: - type: object - properties: - status: - type: string - data: - properties: - log: - properties: - id: - type: integer - cohortId: - type: integer - date: - type: string - author: - type: object - properties: - id: - type: integer - firstName: - type: string - lastName: - type: string - lines: - type: array - items: - type: object - properties: - id: - type: integer - content: - type: string \ No newline at end of file diff --git a/prisma/migrations/20241031114304_add_users_liked_to_post/migration.sql b/prisma/migrations/20241031114304_add_users_liked_to_post/migration.sql new file mode 100644 index 00000000..0f35091f --- /dev/null +++ b/prisma/migrations/20241031114304_add_users_liked_to_post/migration.sql @@ -0,0 +1,17 @@ +-- CreateTable +CREATE TABLE "_UserLikesPosts" ( + "A" INTEGER NOT NULL, + "B" INTEGER NOT NULL +); + +-- CreateIndex +CREATE UNIQUE INDEX "_UserLikesPosts_AB_unique" ON "_UserLikesPosts"("A", "B"); + +-- CreateIndex +CREATE INDEX "_UserLikesPosts_B_index" ON "_UserLikesPosts"("B"); + +-- AddForeignKey +ALTER TABLE "_UserLikesPosts" ADD CONSTRAINT "_UserLikesPosts_A_fkey" FOREIGN KEY ("A") REFERENCES "Post"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "_UserLikesPosts" ADD CONSTRAINT "_UserLikesPosts_B_fkey" FOREIGN KEY ("B") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 1bf869e7..3b3a2b6e 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -26,6 +26,7 @@ model User { cohort Cohort? @relation(fields: [cohortId], references: [id]) posts Post[] deliveryLogs DeliveryLog[] + likedPosts Post[] @relation("UserLikesPosts", references: [id]) } model Profile { @@ -60,6 +61,8 @@ model Post { user User @relation(fields: [userId], references: [id]) createdAt DateTime @default(now()) updatedAt DateTime @default(now()) + likedBy User[] @relation("UserLikesPosts", references: [id]) + } model DeliveryLog { diff --git a/prisma/seed.js b/prisma/seed.js index 3f73a989..8303c8c4 100644 --- a/prisma/seed.js +++ b/prisma/seed.js @@ -45,9 +45,24 @@ async function seed() { await createPost(student.id, 'My first post!') await createPost(teacher.id, 'Hello, students') + await likePost(student.id, 2) + await likePost(teacher.id, 1) + process.exit(0) } +async function likePost(userId, postId) { + await prisma.post.update({ + where: { id: postId }, + data: { + likedBy: { + connect: { id: userId } + } + } + }) + console.info(`User ${userId} liked Post ${postId}`) +} + async function createPost(userId, content) { const post = await prisma.post.create({ data: { diff --git a/src/controllers/post.js b/src/controllers/post.js index 4500634e..47446fa8 100644 --- a/src/controllers/post.js +++ b/src/controllers/post.js @@ -87,3 +87,35 @@ export const deleteById = async (req, res) => { }) } } + +export const likePost = async (req, res) => { + const { id: postId } = req.params + const userId = req.user.id + + try { + const post = await Post.likePost(Number(postId), userId) + if (post) { + return sendDataResponse(res, 200, { post: post.toJSON() }) + } else { + return sendDataResponse(res, 404, { content: 'Post not found' }) + } + } catch (error) { + return sendDataResponse(res, 500, { content: 'Internal server error' }) + } +} + +export const unlikePost = async (req, res) => { + const { id: postId } = req.params + const userId = req.user.id + + try { + const post = await Post.unlikePost(Number(postId), userId) + if (post) { + return sendDataResponse(res, 200, { post: post.toJSON() }) + } else { + return sendDataResponse(res, 404, { content: 'Post not found' }) + } + } catch (error) { + return sendDataResponse(res, 500, { content: 'Internal server error' }) + } +} diff --git a/src/controllers/user.js b/src/controllers/user.js index 72421118..173f6e9c 100644 --- a/src/controllers/user.js +++ b/src/controllers/user.js @@ -69,8 +69,6 @@ export const updateById = async (req, res) => { userToUpdate.cohortId = req.body.cohortId userToUpdate.role = req.body.role - console.log(userToUpdate) - try { if (!userToUpdate.cohortId) { return sendDataResponse(res, 400, { cohort_id: 'Cohort ID is required' }) diff --git a/src/domain/post.js b/src/domain/post.js index 0f992f10..dae04e3e 100644 --- a/src/domain/post.js +++ b/src/domain/post.js @@ -6,13 +6,15 @@ export default class Post { content = '', user = null, createdAt = null, - updatedAt = null + updatedAt = null, + likedBy = [] ) { this.id = id this.content = content this.user = user this.createdAt = createdAt this.updatedAt = updatedAt + this.likedBy = likedBy } toJSON() { @@ -35,13 +37,27 @@ export default class Post { startDate: this.user.profile.startDate, endDate: this.user.profile.endDate, profileImage: this.user.profile.profileImage - } + }, + likedBy: this.likedBy.map((user) => ({ + id: user.id, + firstName: user.profile.firstName, + lastName: user.profile.lastName, + email: user.email + })) } } static async createPost(content, user) { return dbClient.post.create({ - data: { content: content, userId: user.id } + data: { content: content, userId: user.id }, + include: { + user: { + include: { profile: true } + }, + likedBy: { + include: { profile: true } + } + } }) } @@ -50,6 +66,9 @@ export default class Post { include: { user: { include: { profile: true } + }, + likedBy: { + include: { profile: true } } } }) @@ -60,7 +79,8 @@ export default class Post { post.content, post.user, post.createdAt, - post.updatedAt + post.updatedAt, + post.likedBy ) ) } @@ -71,6 +91,9 @@ export default class Post { include: { user: { include: { profile: true } + }, + likedBy: { + include: { profile: true } } } }) @@ -80,7 +103,8 @@ export default class Post { post.content, post.user, post.createdAt, - post.updatedAt + post.updatedAt, + post.likedBy ) : null } @@ -97,4 +121,58 @@ export default class Post { where: { id: id } }) } + + static async likePost(postId, userId) { + const updatedPost = await dbClient.post.update({ + where: { id: postId }, + data: { + likedBy: { + connect: { id: userId } + } + }, + include: { + user: { + include: { profile: true } + }, + likedBy: { + include: { profile: true } + } + } + }) + return new Post( + updatedPost.id, + updatedPost.content, + updatedPost.user, + updatedPost.createdAt, + updatedPost.updatedAt, + updatedPost.likedBy + ) + } + + static async unlikePost(postId, userId) { + const updatedPost = await dbClient.post.update({ + where: { id: postId }, + data: { + likedBy: { + disconnect: { id: userId } + } + }, + include: { + user: { + include: { profile: true } + }, + likedBy: { + include: { profile: true } + } + } + }) + return new Post( + updatedPost.id, + updatedPost.content, + updatedPost.user, + updatedPost.createdAt, + updatedPost.updatedAt, + updatedPost.likedBy + ) + } } diff --git a/src/domain/user.js b/src/domain/user.js index 5ece88ab..22347c6d 100644 --- a/src/domain/user.js +++ b/src/domain/user.js @@ -11,7 +11,6 @@ export default class User { * @returns {User} */ static fromDb(user) { - console.log(user) return new User( user.id, user.cohortId, @@ -107,7 +106,6 @@ export default class User { } toJSON() { - console.log(this) return { user: { id: this.id, diff --git a/src/middleware/post.js b/src/middleware/post.js index 8f110acf..771bb133 100644 --- a/src/middleware/post.js +++ b/src/middleware/post.js @@ -20,3 +20,18 @@ export async function validatePostOwnership(req, res, next) { return sendDataResponse(res, 500, { content: 'Internal server error' }) } } + +export async function validatePostExists(req, res, next) { + const { id: postId } = req.params + + try { + const post = await Post.getPostById(Number(postId)) + if (!post) { + return sendDataResponse(res, 404, { content: 'Post not found' }) + } + req.post = post + next() + } catch (error) { + return sendDataResponse(res, 500, { content: 'Internal server error' }) + } +} diff --git a/src/routes/post.js b/src/routes/post.js index bc9ef6e6..c23d0262 100644 --- a/src/routes/post.js +++ b/src/routes/post.js @@ -4,10 +4,15 @@ import { getAll, updateById, getById, - deleteById + deleteById, + likePost, + unlikePost } from '../controllers/post.js' import { validateAuthentication } from '../middleware/auth.js' -import { validatePostOwnership } from '../middleware/post.js' +import { + validatePostOwnership, + validatePostExists +} from '../middleware/post.js' const router = Router() @@ -16,5 +21,12 @@ router.get('/', validateAuthentication, getAll) router.get('/:id', validateAuthentication, getById) router.patch('/:id', validateAuthentication, validatePostOwnership, updateById) router.delete('/:id', validateAuthentication, validatePostOwnership, deleteById) +router.post('/:id/like', validateAuthentication, validatePostExists, likePost) +router.post( + '/:id/unlike', + validateAuthentication, + validatePostExists, + unlikePost +) export default router From dcb9dff2acc6e006a23e9c258255f11ade529785 Mon Sep 17 00:00:00 2001 From: eyvmal Date: Thu, 31 Oct 2024 14:23:07 +0100 Subject: [PATCH 64/72] update create function and dependencies --- docs/openapi.yml | 7 ++++++- src/controllers/cohort.js | 13 +++++++------ src/domain/cohort.js | 13 ++++--------- 3 files changed, 17 insertions(+), 16 deletions(-) diff --git a/docs/openapi.yml b/docs/openapi.yml index c1b507ff..9b63e681 100644 --- a/docs/openapi.yml +++ b/docs/openapi.yml @@ -413,6 +413,11 @@ paths: operationId: createCohort security: - bearerAuth: [] + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/UpdateCohort' responses: 201: description: success @@ -639,7 +644,7 @@ components: UpdateCohort: type: object properties: - name: + cohortName: type: string startDate: type: string diff --git a/src/controllers/cohort.js b/src/controllers/cohort.js index 8ff935c2..3435a989 100644 --- a/src/controllers/cohort.js +++ b/src/controllers/cohort.js @@ -2,12 +2,13 @@ import { sendDataResponse, sendMessageResponse } from '../utils/responses.js' import Cohort from '../domain/cohort.js' export const create = async (req, res) => { + const cohort = await Cohort.fromJson(req.body) try { - const createdCohort = await Cohort.save() + const createdCohort = await cohort.save() - return sendDataResponse(res, 201, createdCohort) + return sendDataResponse(res, 201, { cohort: createdCohort }) } catch (e) { - return sendMessageResponse(res, 500, 'Unable to create cohort') + return sendMessageResponse(res, 500, `Unable to create cohort ${e}`) } } @@ -33,14 +34,14 @@ export const getById = async (req, res) => { export const updateById = async (req, res) => { const { id } = req.params - const { name, startDate, endDate } = req.body + const { cohortName, startDate, endDate } = req.body try { const cohort = await Cohort.getCohortById(Number(id)) if (cohort) { - const content = { name, startDate, endDate } + const content = { cohortName, startDate, endDate } const updatedCohort = await Cohort.updateById(Number(id), content) - return sendDataResponse(res, 200, { cohort: updatedCohort.toJSON() }) + return sendDataResponse(res, 200, updatedCohort.toJSON()) } else { return sendMessageResponse(res, 404, `Cohort with id ${id} not found`) } diff --git a/src/domain/cohort.js b/src/domain/cohort.js index 5f25a941..9380def1 100644 --- a/src/domain/cohort.js +++ b/src/domain/cohort.js @@ -21,7 +21,7 @@ export default class Cohort { cohortName, startDate, endDate, - students.map((student) => User.fromJson(student)) + students ? students.map((student) => User.fromJson(student)) : [] ) } @@ -48,13 +48,8 @@ export default class Cohort { async save() { const data = { cohortName: this.cohortName, - startDate: this.startDate - } - - if (this.endDate) { - data.endDate = this.endDate - } else { - data.endDate = null + startDate: this.startDate, + endDate: this.endDate !== undefined ? this.endDate : null } const createdCohort = await dbClient.cohort.create({ data }) @@ -97,7 +92,7 @@ export default class Cohort { static async updateById(id, cohort) { const data = {} - if (cohort.name !== undefined) data.cohortName = cohort.name + if (cohort.cohortName !== undefined) data.cohortName = cohort.cohortName if (cohort.startDate !== undefined) data.startDate = cohort.startDate if (cohort.endDate !== undefined) data.endDate = cohort.endDate From 3831d83405060fdf9a7abd64988f4d72a74a0e0a Mon Sep 17 00:00:00 2001 From: Muzea001 Date: Thu, 31 Oct 2024 14:53:03 +0100 Subject: [PATCH 65/72] fixed issue in yaml file --- docs/openapi.yml | 163 +++++++++++++++++++++++------------------------ 1 file changed, 81 insertions(+), 82 deletions(-) diff --git a/docs/openapi.yml b/docs/openapi.yml index b4503921..3af8013f 100644 --- a/docs/openapi.yml +++ b/docs/openapi.yml @@ -433,6 +433,86 @@ paths: application/json: schema: $ref: '#/components/schemas/Error' + get: + tags: + - cohort + summary: Get all cohorts + operationId: getAllCohorts + security: + - bearerAuth: [] + responses: + '201': + description: success + content: + application/json: + schema: + type: object + properties: + status: + type: string + data: + properties: + cohort: + $ref: '#/components/schemas/Cohort' + '401': + description: fail + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '500': + description: Server error + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + /cohorts/{id}: + get: + tags: + - cohort + summary: Get a cohort by id + operationId: getCohortById + security: + - bearerAuth: [] + parameters: + - name: id + in: path + description: 'The cohort id' + required: true + schema: + type: integer + responses: + '201': + description: success + content: + application/json: + schema: + type: object + properties: + status: + type: string + data: + properties: + cohort: + $ref: '#/components/schemas/Cohort' + '401': + description: Unautorized + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '404': + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '500': + description: Server error + content: + application/json: + schema: + $ref: '#/components/schemas/Error' /comments: post: tags: @@ -492,7 +572,6 @@ paths: required: - content - userId - responses: '200': description: Updated @@ -540,86 +619,6 @@ paths: application/json: schema: $ref: '#/components/schemas/Error' - get: - tags: - - cohort - summary: Get all cohorts - operationId: getAllCohorts - security: - - bearerAuth: [] - responses: - '201': - description: success - content: - application/json: - schema: - type: object - properties: - status: - type: string - data: - properties: - cohort: - $ref: '#/components/schemas/Cohort' - '401': - description: fail - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - '500': - description: Server error - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - /cohorts/{id}: - get: - tags: - - cohort - summary: Get a cohort by id - operationId: getCohortById - security: - - bearerAuth: [] - parameters: - - name: id - in: path - description: 'The cohort id' - required: true - schema: - type: integer - responses: - '201': - description: success - content: - application/json: - schema: - type: object - properties: - status: - type: string - data: - properties: - cohort: - $ref: '#/components/schemas/Cohort' - '401': - description: Unautorized - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - '404': - description: Not Found - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - '500': - description: Server error - content: - application/json: - schema: - $ref: '#/components/schemas/Error' components: securitySchemes: bearerAuth: @@ -999,4 +998,4 @@ components: type: object properties: message: - type: string + type: string \ No newline at end of file From 8bb6f80ba17871ed4f880cd6d8f5d21cd98501a0 Mon Sep 17 00:00:00 2001 From: Muzea001 Date: Thu, 31 Oct 2024 15:02:32 +0100 Subject: [PATCH 66/72] fixed schema file --- prisma/schema.prisma | 2 ++ 1 file changed, 2 insertions(+) diff --git a/prisma/schema.prisma b/prisma/schema.prisma index d5387803..e8977058 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -60,6 +60,8 @@ model Post { userId Int user User @relation(fields: [userId], references: [id]) comments Comment[] + createdAt DateTime @default(now()) + updatedAt DateTime @default(now()) } model Comment { From 93dc6351ad9ce52d0dce1a939b5aa0283222af97 Mon Sep 17 00:00:00 2001 From: Muzea001 Date: Thu, 31 Oct 2024 15:42:21 +0100 Subject: [PATCH 67/72] added new yaml file, and getting comments with posts --- docs/openapi.yml | 100 +++++++++++++++++++++++++++++++++++++++++++++ src/domain/post.js | 22 +++++++--- 2 files changed, 116 insertions(+), 6 deletions(-) diff --git a/docs/openapi.yml b/docs/openapi.yml index 3af8013f..ebd6d249 100644 --- a/docs/openapi.yml +++ b/docs/openapi.yml @@ -413,6 +413,11 @@ paths: operationId: createCohort security: - bearerAuth: [] + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/UpdateCohort' responses: 201: description: success @@ -513,6 +518,89 @@ paths: application/json: schema: $ref: '#/components/schemas/Error' + patch: + tags: + - cohort + summary: Patch a cohort by id + description: This can only be done by the logged in user with role TEACHER. + operationId: updateCohortById + security: + - bearerAuth: [] + parameters: + - name: id + in: path + required: true + schema: + type: integer + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/UpdateCohort' + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/Posts' + '401': + description: Unauthorised + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '404': + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '500': + description: Server error + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + delete: + tags: + - cohort + summary: Delete a cohort by id + description: This can only be done by the logged in user with role TEACHER. + operationId: deleteCohortById + security: + - bearerAuth: [] + parameters: + - name: id + in: path + required: true + schema: + type: integer + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/Posts' + '401': + description: Unauthorised + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '404': + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '500': + description: Server error + content: + application/json: + schema: + $ref: '#/components/schemas/Error' /comments: post: tags: @@ -658,6 +746,18 @@ components: items: $ref: '#/components/schemas/User' + UpdateCohort: + type: object + properties: + cohortName: + type: string + startDate: + type: string + format: date-time + endDate: + type: string + format: date-time + AllUsers: type: object properties: diff --git a/src/domain/post.js b/src/domain/post.js index 0f992f10..53a2016d 100644 --- a/src/domain/post.js +++ b/src/domain/post.js @@ -6,13 +6,15 @@ export default class Post { content = '', user = null, createdAt = null, - updatedAt = null + updatedAt = null, + comments = [] ) { this.id = id this.content = content this.user = user this.createdAt = createdAt this.updatedAt = updatedAt + this.comments = comments } toJSON() { @@ -35,7 +37,11 @@ export default class Post { startDate: this.user.profile.startDate, endDate: this.user.profile.endDate, profileImage: this.user.profile.profileImage - } + }, + comments: this.comments.map((comment) => ({ + id: comment.id, + content: comment.content + })) } } @@ -50,7 +56,8 @@ export default class Post { include: { user: { include: { profile: true } - } + }, + comments: true } }) return posts.map( @@ -60,7 +67,8 @@ export default class Post { post.content, post.user, post.createdAt, - post.updatedAt + post.updatedAt, + post.comments ) ) } @@ -71,7 +79,8 @@ export default class Post { include: { user: { include: { profile: true } - } + }, + comments: true } }) return post @@ -80,7 +89,8 @@ export default class Post { post.content, post.user, post.createdAt, - post.updatedAt + post.updatedAt, + post.comments ) : null } From efa8a6ccb69c147199fc52a21ca7753f0668035d Mon Sep 17 00:00:00 2001 From: Tuva Aarseth Date: Fri, 1 Nov 2024 08:25:04 +0100 Subject: [PATCH 68/72] Added new changes to openapi.yml --- docs/openapi.yml | 710 +++++++++++++++++++++++++++++++---------------- 1 file changed, 465 insertions(+), 245 deletions(-) diff --git a/docs/openapi.yml b/docs/openapi.yml index 996456ee..97b34722 100644 --- a/docs/openapi.yml +++ b/docs/openapi.yml @@ -11,7 +11,6 @@ tags: - name: post - name: cohort - name: log - paths: /users: post: @@ -55,24 +54,23 @@ paths: type: string responses: '200': - description: Successful operation + description: successful operation content: application/json: schema: $ref: '#/components/schemas/AllUsers' '400': - description: Fail + description: fail content: application/json: schema: $ref: '#/components/schemas/Error' '404': - description: Fail + description: fail content: application/json: schema: $ref: '#/components/schemas/Error' - /login: post: tags: @@ -85,17 +83,16 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/Login' + $ref: '#/components/schemas/login' responses: '200': description: OK content: application/json: schema: - $ref: '#/components/schemas/LoginRes' + $ref: '#/components/schemas/loginRes' '400': description: Invalid username/password supplied - /users/{id}: get: tags: @@ -108,24 +105,30 @@ paths: parameters: - name: id in: path - description: 'The ID of the user to fetch.' + description: 'The name that needs to be fetched. Use user1 for testing. ' required: true schema: type: string responses: '200': - description: Successful operation + description: successful operation content: application/json: - $ref: '#/components/schemas/UserResponse' + schema: + type: object + properties: + status: + type: string + data: + $ref: '#/components/schemas/User' '400': - description: Fail + description: fail content: application/json: schema: $ref: '#/components/schemas/Error' '404': - description: Fail + description: fail content: application/json: schema: @@ -134,14 +137,14 @@ paths: tags: - user summary: Update a user - description: Only users with a TEACHER role can update the cohortId or role. Users with STUDENT role can only update their own details. + description: Only users with a TEACHER role can update the cohortId or role. Users with Students role can only update their own details. operationId: userUpdate security: - bearerAuth: [] parameters: - name: id in: path - description: 'The user ID that needs to be updated.' + description: 'The user id that needs to be updated' required: true schema: type: string @@ -152,13 +155,14 @@ paths: schema: $ref: '#/components/schemas/UpdateUser' responses: - '200': + '201': description: Successful operation content: application/json: - $ref: '#/components/schemas/UserResponse' + schema: + $ref: '#/components/schemas/CreatedUser' '401': - description: Unauthorized + description: fail content: application/json: schema: @@ -169,13 +173,12 @@ paths: application/json: schema: $ref: '#/components/schemas/Error' - /posts: post: tags: - post summary: Create post - description: This can only be done by the logged-in user. + description: This can only be done by the logged in user. operationId: createPost security: - bearerAuth: [] @@ -184,15 +187,19 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/CreatePost' + type: object + properties: + content: + type: string responses: - '201': - description: Success + 201: + description: success content: application/json: - $ref: '#/components/schemas/PostResponse' - '401': - description: Unauthorized + schema: + $ref: '#/components/schemas/Post' + 401: + description: Unauthorised content: application/json: schema: @@ -207,7 +214,7 @@ paths: tags: - post summary: Get all posts - description: Get all posts + description: get all posts operationId: getPosts security: - bearerAuth: [] @@ -216,9 +223,10 @@ paths: description: Successful operation content: application/json: - $ref: '#/components/schemas/PostsResponse' + schema: + $ref: '#/components/schemas/Posts' '401': - description: Unauthorized + description: Unauthorised content: application/json: schema: @@ -229,20 +237,19 @@ paths: application/json: schema: $ref: '#/components/schemas/Error' - /posts/{id}: get: tags: - post - summary: Get a post by ID - description: Get a post + summary: Get a post by id + description: get a post operationId: getPostById security: - bearerAuth: [] parameters: - name: id in: path - description: 'The post ID to retrieve.' + description: 'The post id that needs to be updated' required: true schema: type: integer @@ -251,15 +258,16 @@ paths: description: Successful operation content: application/json: - $ref: '#/components/schemas/PostResponse' + schema: + $ref: '#/components/schemas/Posts' '401': - description: Unauthorized + description: Unauthorised content: application/json: schema: $ref: '#/components/schemas/Error' '404': - description: Not found + description: Not Found content: application/json: schema: @@ -273,20 +281,20 @@ paths: patch: tags: - post - summary: Update a post by ID - description: Update a post + summary: Patch a post by id + description: patch a post operationId: updatePostById security: - bearerAuth: [] parameters: - name: id in: path - description: 'The post ID that needs to be updated.' + description: 'The post id that needs to be updated' required: true schema: type: integer requestBody: - description: The post content to update + description: The post description content: application/json: schema: @@ -296,15 +304,16 @@ paths: description: Successful operation content: application/json: - $ref: '#/components/schemas/PostResponse' + schema: + $ref: '#/components/schemas/Posts' '401': - description: Unauthorized + description: Unauthorised content: application/json: schema: $ref: '#/components/schemas/Error' '404': - description: Not found + description: Not Found content: application/json: schema: @@ -318,15 +327,15 @@ paths: delete: tags: - post - summary: Delete a post by ID - description: Delete a post + summary: Delete a post by id + description: delete a post operationId: deletePostById security: - bearerAuth: [] parameters: - name: id in: path - description: 'The post ID that needs to be deleted.' + description: 'The post id that needs to be updated' required: true schema: type: integer @@ -335,15 +344,16 @@ paths: description: Successful operation content: application/json: - $ref: '#/components/schemas/PostResponse' + schema: + $ref: '#/components/schemas/Posts' '401': - description: Unauthorized + description: Unauthorised content: application/json: schema: $ref: '#/components/schemas/Error' '404': - description: Not found + description: Not Found content: application/json: schema: @@ -354,7 +364,6 @@ paths: application/json: schema: $ref: '#/components/schemas/Error' - /posts/{id}/like: post: tags: @@ -376,7 +385,8 @@ paths: description: Post liked successfully content: application/json: - $ref: '#/components/schemas/PostResponse' + schema: + $ref: '#/components/schemas/Post' '401': description: Unauthorized content: @@ -411,7 +421,8 @@ paths: description: Post unliked successfully content: application/json: - $ref: '#/components/schemas/PostResponse' + schema: + $ref: '#/components/schemas/Post' '401': description: Unauthorized content: @@ -430,7 +441,7 @@ paths: tags: - log summary: Create delivery log - description: This can only be done by an authorized teacher user. + description: This can only be done by an authorised teacher user. operationId: createLog security: - bearerAuth: [] @@ -439,43 +450,62 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/CreateLog' + type: object + properties: + date: + type: string + cohortId: + type: integer + lines: + type: array + items: + type: object + properties: + content: + type: string responses: - '201': - description: Success + 201: + description: success content: application/json: - $ref: '#/components/schemas/LogResponse' + schema: + $ref: '#/components/schemas/Log' '401': - description: Unauthorized + description: Unauthorised content: application/json: schema: $ref: '#/components/schemas/Error' - /cohorts: post: tags: - cohort summary: Create a cohort - description: This can only be done by a logged-in user with role TEACHER. + description: This can only be done by the logged in user with role TEACHER. operationId: createCohort security: - bearerAuth: [] requestBody: - description: Created cohort object content: application/json: schema: $ref: '#/components/schemas/UpdateCohort' responses: - '201': - description: Success + 201: + description: success content: application/json: - $ref: '#/components/schemas/CohortResponse' - '400': - description: Fail + schema: + type: object + properties: + status: + type: string + data: + properties: + cohort: + $ref: '#/components/schemas/Cohort' + 400: + description: fail content: application/json: schema: @@ -488,13 +518,21 @@ paths: security: - bearerAuth: [] responses: - '200': - description: Success + '201': + description: success content: application/json: - $ref: '#/components/schemas/CohortsResponse' + schema: + type: object + properties: + status: + type: string + data: + properties: + cohort: + $ref: '#/components/schemas/Cohort' '401': - description: Unauthorized + description: fail content: application/json: schema: @@ -505,7 +543,6 @@ paths: application/json: schema: $ref: '#/components/schemas/Error' - /cohorts/{id}: get: tags: @@ -522,13 +559,21 @@ paths: schema: type: integer responses: - '200': - description: Success + '201': + description: success content: application/json: - $ref: '#/components/schemas/CohortResponse' + schema: + type: object + properties: + status: + type: string + data: + properties: + cohort: + $ref: '#/components/schemas/Cohort' '401': - description: Unauthorized + description: Unautorized content: application/json: schema: @@ -548,20 +593,18 @@ paths: patch: tags: - cohort - summary: Update a cohort by id - description: This can only be done by the logged-in user with role TEACHER. + summary: Patch a cohort by id + description: This can only be done by the logged in user with role TEACHER. operationId: updateCohortById security: - bearerAuth: [] parameters: - name: id in: path - description: 'The cohort ID that needs to be updated.' required: true schema: type: integer requestBody: - description: The cohort information to update content: application/json: schema: @@ -571,9 +614,10 @@ paths: description: Successful operation content: application/json: - $ref: '#/components/schemas/CohortResponse' + schema: + $ref: '#/components/schemas/Posts' '401': - description: Unauthorized + description: Unauthorised content: application/json: schema: @@ -594,14 +638,13 @@ paths: tags: - cohort summary: Delete a cohort by id - description: This can only be done by the logged-in user with role TEACHER. + description: This can only be done by the logged in user with role TEACHER. operationId: deleteCohortById security: - bearerAuth: [] parameters: - name: id in: path - description: 'The cohort ID that needs to be deleted.' required: true schema: type: integer @@ -611,9 +654,9 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/CohortResponse' + $ref: '#/components/schemas/Posts' '401': - description: Unauthorized + description: Unauthorised content: application/json: schema: @@ -631,67 +674,137 @@ paths: schema: $ref: '#/components/schemas/Error' + /comments: + post: + tags: + - comment + summary: Create a comment + description: Create a new comment on a post + operationId: createComment + security: + - bearerAuth: [] + requestBody: + description: Comment details + content: + application/json: + schema: + $ref: '#/components/schemas/CreateComment' + responses: + '201': + description: Created + content: + application/json: + schema: + $ref: '#/components/schemas/Comment' + '400': + description: Invalid input + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + /comments/{commentId}: + patch: + tags: + - comment + summary: Update a comment + description: Update an existing comment + operationId: updateComment + security: + - bearerAuth: [] + parameters: + - name: commentId + in: path + description: The ID of the comment to update + required: true + schema: + type: integer + requestBody: + description: Updated comment details + required: true + content: + application/json: + schema: + type: object + properties: + content: + type: string + userId: + type: integer + required: + - content + - userId + responses: + '200': + description: Updated + content: + application/json: + schema: + $ref: '#/components/schemas/Comment' + '400': + description: Invalid input + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '404': + description: Comment not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + delete: + tags: + - comment + summary: Delete a comment + description: Delete an existing comment + operationId: deleteComment + security: + - bearerAuth: [] + parameters: + - name: commentId + in: path + description: The ID of the comment to delete + required: true + schema: + type: integer + responses: + '200': + description: Deleted + content: + application/json: + schema: + $ref: '#/components/schemas/Success' + '404': + description: Comment not found + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + components: securitySchemes: bearerAuth: type: http scheme: bearer bearerFormat: JWT - schemas: - User: - type: object - properties: - id: - type: integer - email: - type: string - role: - type: string - cohortId: - type: integer - firstName: - type: string - lastName: - type: string - bio: - type: string - githubUrl: - type: string - username: - type: string - mobile: - type: string - specialism: - type: string - startDate: - type: string - format: date-time - endDate: - type: string - format: date-time - profileImage: - type: string - Post: type: object properties: - id: - type: integer - content: - type: string - createdAt: - type: string - format: date-time - updatedAt: + status: type: string - format: date-time - author: - $ref: '#/components/schemas/User' - likedBy: - type: array - items: - $ref: '#/components/schemas/User' + data: + properties: + post: + properties: + id: + type: integer + content: + type: string + likedBy: + type: array + items: + $ref: '#/components/schemas/User' Cohort: type: object @@ -706,12 +819,6 @@ components: updatedAt: type: string format: date-time - startDate: - type: string - format: date-time - endDate: - type: string - format: date-time users: type: array items: @@ -729,28 +836,51 @@ components: type: string format: date-time - Log: + AllUsers: type: object properties: - id: - type: integer - date: + status: type: string - cohortId: - type: integer - author: - $ref: '#/components/schemas/User' - lines: - type: array - items: - $ref: '#/components/schemas/LogLine' + data: + type: object + properties: + users: + type: array + items: + $ref: '#/components/schemas/User' - LogLine: + User: type: object properties: id: type: integer - content: + email: + type: string + role: + type: string + cohortId: + type: integer + firstName: + type: string + lastName: + type: string + bio: + type: string + githubUrl: + type: string + username: + type: string + mobile: + type: string + specialism: + type: string + startDate: + type: string + format: date-time + endDate: + type: string + format: date-time + profileImage: type: string CreateUser: @@ -817,11 +947,64 @@ components: profileImage: type: string - CreatePost: + Posts: type: object properties: - content: + status: type: string + data: + type: object + properties: + posts: + type: array + items: + type: object + properties: + id: + type: integer + content: + type: string + createdAt: + type: string + format: date-time + updatedAt: + type: string + format: date-time + author: + type: object + properties: + id: + type: integer + cohortId: + type: integer + role: + type: string + firstName: + type: string + lastName: + type: string + bio: + type: string + githubUrl: + type: string + username: + type: string + mobile: + type: string + specialism: + type: string + startDate: + type: string + format: date-time + endDate: + type: string + format: date-time + profileImage: + type: string + likedBy: + type: array + items: + $ref: '#/components/schemas/User' UpdatePost: type: object @@ -829,33 +1012,6 @@ components: content: type: string - CreateCohort: - type: object - properties: - cohortName: - type: string - startDate: - type: string - format: date-time - endDate: - type: string - format: date-time - - CreateLog: - type: object - properties: - date: - type: string - cohortId: - type: integer - lines: - type: array - items: - type: object - properties: - content: - type: string - CreatedUser: type: object properties: @@ -865,99 +1021,163 @@ components: data: properties: user: - $ref: '#/components/schemas/User' + properties: + id: + type: integer + email: + type: string + cohortId: + type: integer + role: + type: string + firstName: + type: string + lastName: + type: string + bio: + type: string + githubUrl: + type: string + username: + type: string + mobile: + type: string + specialism: + type: string + startDate: + type: string + format: date-time + endDate: + type: string + format: date-time + profileImage: + type: string - AllUsers: + login: type: object properties: - status: - type: string - data: - type: object - properties: - users: - type: array - items: - $ref: '#/components/schemas/User' - - UserResponse: - type: object - properties: - status: + email: type: string - data: - $ref: '#/components/schemas/User' - - PostResponse: - type: object - properties: - status: + password: type: string - data: - $ref: '#/components/schemas/Post' - PostsResponse: + loginRes: type: object properties: status: type: string data: - type: array - items: - $ref: '#/components/schemas/Post' + properties: + token: + type: string + user: + properties: + id: + type: integer + email: + type: string + cohortId: + type: integer + role: + type: string + firstName: + type: string + lastName: + type: string + bio: + type: string + githubUrl: + type: string + username: + type: string + mobile: + type: string + specialism: + type: string + startDate: + type: string + format: date-time + endDate: + type: string + format: date-time + profileImage: + type: string - CohortResponse: + Error: type: object properties: status: type: string data: - $ref: '#/components/schemas/Cohort' + properties: + error: + type: string - CohortsResponse: + Log: type: object properties: status: type: string data: - type: array - items: - $ref: '#/components/schemas/Cohort' - - LogResponse: + properties: + log: + properties: + id: + type: integer + cohortId: + type: integer + date: + type: string + author: + type: object + properties: + id: + type: integer + firstName: + type: string + lastName: + type: string + lines: + type: array + items: + type: object + properties: + id: + type: integer + content: + type: string + Comment: type: object properties: - status: + id: + type: integer + content: type: string - data: - $ref: '#/components/schemas/Log' - - Login: + userId: + type: integer + postId: + type: integer + CreateComment: type: object properties: - email: - type: string - password: + content: type: string - - LoginRes: + postId: + type: integer + userId: + type: integer + UpdateComment: type: object properties: - status: + content: type: string - data: - properties: - token: - type: string - user: - $ref: '#/components/schemas/User' - - Error: + userId: + type: integer + required: + - content + - userId + Success: type: object properties: - status: + message: type: string - data: - properties: - error: - type: string From efc308115aedc93624fd03ba9ebacdbfee91df3d Mon Sep 17 00:00:00 2001 From: tvaltn Date: Fri, 1 Nov 2024 09:02:26 +0100 Subject: [PATCH 69/72] sorts the posts descending on getAllPosts --- src/domain/post.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/domain/post.js b/src/domain/post.js index 53a2016d..34c38d18 100644 --- a/src/domain/post.js +++ b/src/domain/post.js @@ -60,7 +60,7 @@ export default class Post { comments: true } }) - return posts.map( + const returnPosts = posts.map( (post) => new Post( post.id, @@ -71,6 +71,10 @@ export default class Post { post.comments ) ) + const sortedPosts = returnPosts.sort( + (a, b) => new Date(b.createdAt) - new Date(a.createdAt) + ) + return sortedPosts } static async getPostById(id) { From 992b48876fc0f8c9dbfb7a0f168ddab84194ad11 Mon Sep 17 00:00:00 2001 From: Tuva Aarseth Date: Fri, 1 Nov 2024 09:12:50 +0100 Subject: [PATCH 70/72] Small changes to post domain to get correct response when liking and unliking post --- src/domain/post.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/domain/post.js b/src/domain/post.js index d349db9d..b2216e0b 100644 --- a/src/domain/post.js +++ b/src/domain/post.js @@ -146,7 +146,8 @@ export default class Post { }, likedBy: { include: { profile: true } - } + }, + comments: true } }) return new Post( @@ -155,6 +156,7 @@ export default class Post { updatedPost.user, updatedPost.createdAt, updatedPost.updatedAt, + updatedPost.comments, updatedPost.likedBy ) } @@ -173,7 +175,8 @@ export default class Post { }, likedBy: { include: { profile: true } - } + }, + comments: true } }) return new Post( @@ -182,6 +185,7 @@ export default class Post { updatedPost.user, updatedPost.createdAt, updatedPost.updatedAt, + updatedPost.comments, updatedPost.likedBy ) } From 9f661d6ee4d5c89f6ac36e99ed38aec8df33fc57 Mon Sep 17 00:00:00 2001 From: thomamn <126023720+thomamn@users.noreply.github.com> Date: Fri, 1 Nov 2024 09:51:10 +0100 Subject: [PATCH 71/72] Fixed the following bugs: Editing a post allows it to exceed 200 characters. User emails had to end with a "_.com" --- src/middleware/user.js | 2 +- src/routes/post.js | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/middleware/user.js b/src/middleware/user.js index 5658c201..e41cc518 100644 --- a/src/middleware/user.js +++ b/src/middleware/user.js @@ -5,7 +5,7 @@ export async function validateUser(req, res, next) { if ( email.length < 7 || email.indexOf('@') <= 0 || - email.slice(-4) !== '.com' || + !email.slice(-4, -1).includes('.') || (email.match(/@/g) || []).length > 1 || email.charAt(email.length - 5) === '@' ) { diff --git a/src/routes/post.js b/src/routes/post.js index 90e42261..4af2177a 100644 --- a/src/routes/post.js +++ b/src/routes/post.js @@ -17,7 +17,13 @@ const router = Router() router.post('/', validateAuthentication, validatePostContent, create) router.get('/', validateAuthentication, getAll) router.get('/:id', validateAuthentication, getById) -router.patch('/:id', validateAuthentication, validatePostOwnership, updateById) +router.patch( + '/:id', + validateAuthentication, + validatePostOwnership, + validatePostContent, + updateById +) router.delete('/:id', validateAuthentication, validatePostOwnership, deleteById) export default router From 452c89096980f1b7d05dd1d859888d315b8b2a55 Mon Sep 17 00:00:00 2001 From: thomamn <126023720+thomamn@users.noreply.github.com> Date: Fri, 1 Nov 2024 10:14:04 +0100 Subject: [PATCH 72/72] Undid the .com bugfix --- src/middleware/user.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/middleware/user.js b/src/middleware/user.js index e41cc518..5658c201 100644 --- a/src/middleware/user.js +++ b/src/middleware/user.js @@ -5,7 +5,7 @@ export async function validateUser(req, res, next) { if ( email.length < 7 || email.indexOf('@') <= 0 || - !email.slice(-4, -1).includes('.') || + email.slice(-4) !== '.com' || (email.match(/@/g) || []).length > 1 || email.charAt(email.length - 5) === '@' ) {