diff --git a/README.md b/README.md index 5e2dda48..57e01aaa 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ Once you have complete the above guide, continue to the steps below. [todo]: -[Deployed API Spec](https://UPDATEME) +[Deployed API Spec](https://team-dev-api.nymo.xyz/api-docs) The API Spec is hosted by the server itself (i.e. this project), and the view/page is generated automatically by the SwaggerUI libraryi. diff --git a/docs/openapi.yml b/docs/openapi.yml index 5f2a05f2..f48d3e5f 100644 --- a/docs/openapi.yml +++ b/docs/openapi.yml @@ -65,6 +65,37 @@ paths: application/json: schema: $ref: '#/components/schemas/Error' + + /users/profile/{id}: + post: + tags: + - profile + summary: Create profile + description: Create new profile + operationId: createProfile + security: + - bearerAuth: [] + parameters: + - name: id + in: path + description: 'The name that needs to be fetched. Use user1 for testing. ' + required: true + schema: + type: string + requestBody: + description: User registration details + content: + application/json: + schema: + $ref: '#/components/schemas/CreateProfile' + responses: + '201': + description: Created + content: + application/json: + schema: + $ref: '#/components/schemas/CreatedProfile' + /login: post: tags: @@ -368,6 +399,30 @@ components: password: type: string + CreateProfile: + type: object + properties: + firstName: + type: string + lastName: + type: string + bio: + type: string + githubUrl: + type: string + username: + type: string + mobile: + type: string + startDate: + type: string + endDate: + type: string + specialism: + type: string + jobTitle: + type: string + UpdateUser: type: object properties: @@ -457,6 +512,40 @@ components: type: string githubUrl: type: string + + CreatedProfile: + type: object + properties: + status: + type: string + example: success + data: + properties: + user: + properties: + id: + type: integer + firstName: + type: string + lastName: + type: string + bio: + type: string + githubUrl: + type: string + username: + type: string + mobile: + type: string + startDate: + type: string + endDate: + type: string + specialism: + type: string + jobTitle: + type: string + login: type: object properties: diff --git a/prisma/migrations/20250226143811_update_user_profile/migration.sql b/prisma/migrations/20250226143811_update_user_profile/migration.sql new file mode 100644 index 00000000..c4c143da --- /dev/null +++ b/prisma/migrations/20250226143811_update_user_profile/migration.sql @@ -0,0 +1,7 @@ +-- AlterTable +ALTER TABLE "Profile" ADD COLUMN "endDate" TEXT, +ADD COLUMN "jobTitle" TEXT, +ADD COLUMN "mobile" TEXT, +ADD COLUMN "specialism" TEXT, +ADD COLUMN "startDate" TEXT, +ADD COLUMN "username" TEXT; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 72ec5632..b62eb7b6 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -36,6 +36,12 @@ model Profile { lastName String bio String? githubUrl String? + username String? + mobile String? + startDate String? + endDate String? + specialism String? + jobTitle String? } model Cohort { diff --git a/prisma/seed.js b/prisma/seed.js index 21684795..121139bf 100644 --- a/prisma/seed.js +++ b/prisma/seed.js @@ -12,7 +12,13 @@ async function seed() { 'Joe', 'Bloggs', 'Hello, world!', - 'student1' + 'student1', + 'username', + 'mobile', + 'startDate', + 'endDate', + 'specialism', + 'jobTitle' ) const teacher = await createUser( 'teacher@test.com', @@ -22,6 +28,12 @@ async function seed() { 'Sanchez', 'Hello there!', 'teacher1', + 'username', + 'mobile', + 'startDate', + 'endDate', + 'specialism', + 'jobTitle', 'TEACHER' ) @@ -65,6 +77,12 @@ async function createUser( lastName, bio, githubUrl, + username, + mobile, + startDate, + endDate, + specialism, + jobTitle, role = 'STUDENT' ) { const user = await prisma.user.create({ @@ -78,7 +96,13 @@ async function createUser( firstName, lastName, bio, - githubUrl + githubUrl, + username, + mobile, + startDate, + endDate, + specialism, + jobTitle } } }, diff --git a/src/controllers/user.js b/src/controllers/user.js index 40ff0f1c..c91850f2 100644 --- a/src/controllers/user.js +++ b/src/controllers/user.js @@ -2,8 +2,28 @@ import User from '../domain/user.js' import { sendDataResponse, sendMessageResponse } from '../utils/responses.js' export const create = async (req, res) => { + const rawPassword = req.body.password const userToCreate = await User.fromJson(req.body) + // validate email format + const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/ + if (!emailRegex.test(userToCreate.email)) { + return sendDataResponse(res, 400, { email: 'Invalid email format' }) + } + + // validate password format + // - At least 8 characters in length + // - Contains at least one uppercase letter + // - Contains at least one number + // - Contains at least one special character + const passwordRegex = /^(?=.*[A-Z])(?=.*\d)(?=.*[^A-Za-z0-9]).{8,}$/ + if (!passwordRegex.test(rawPassword)) { + return sendDataResponse(res, 400, { + password: + 'Password must be at least 8 characters long, contain at least one uppercase letter, one number, and one special character' + }) + } + try { const existingUser = await User.findByEmail(userToCreate.email) @@ -65,3 +85,18 @@ export const updateById = async (req, res) => { return sendDataResponse(res, 201, { user: { cohort_id: cohortId } }) } + + +export const createProfile = async (req, res) => { + const paramId = parseInt(req.params.id) + const user = await User.findById(paramId) + + if (user == null) { + return sendDataResponse(res, 404, 'user not found!') + } + + const profile = await User.fromJson(req.body) + const createdProfile = await profile.createProfile(paramId) + + return sendDataResponse(res, 201, createdProfile) +} diff --git a/src/domain/user.js b/src/domain/user.js index fd7734c7..85adaf11 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: string, startDate: string, endDate: string, specialism: string, jobTitle: string } } } user * @returns {User} */ static fromDb(user) { @@ -19,6 +19,12 @@ export default class User { user.email, user.profile?.bio, user.profile?.githubUrl, + user.profile?.username, + user.profile?.mobile, + user.profile?.startDate, + user.profile?.endDate, + user.profile?.specialism, + user.profile?.jobTitle, user.password, user.role ) @@ -26,7 +32,20 @@ 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, + biography, + githubUrl, + username, + mobile, + startDate, + endDate, + specialism, + jobTitle, + password + } = json const passwordHash = await bcrypt.hash(password, 8) @@ -38,6 +57,12 @@ export default class User { email, biography, githubUrl, + username, + mobile, + startDate, + endDate, + specialism, + jobTitle, passwordHash ) } @@ -50,6 +75,12 @@ export default class User { email, bio, githubUrl, + username, + mobile, + startDate = 'January 2025', + endDate = 'June 2025', + specialism = 'Software Developer', + jobTitle, passwordHash = null, role = 'STUDENT' ) { @@ -60,6 +91,12 @@ export default class User { this.email = email this.bio = bio this.githubUrl = githubUrl + this.username = username + this.mobile = mobile + this.startDate = startDate + this.endDate = endDate + this.specialism = specialism + this.jobTitle = jobTitle this.passwordHash = passwordHash this.role = role } @@ -74,7 +111,13 @@ export default class User { lastName: this.lastName, email: this.email, biography: this.bio, - githubUrl: this.githubUrl + githubUrl: this.githubUrl, + username: this.username, + mobile: this.mobile, + startDate: this.startDate, + endDate: this.endDate, + specialism: this.specialism, + jobTitle: this.jobTitle } } } @@ -118,6 +161,32 @@ export default class User { return User.fromDb(createdUser) } + async createProfile(id) { + console.log(typeof id) + const data = { + firstName: this.firstName, + lastName: this.lastName, + githubUrl: this.githubUrl, + bio: this.bio, + username: this.username, + mobile: this.mobile, + startDate: this.startDate, + endDate: this.endDate, + specialism: this.specialism, + jobTitle: this.jobTitle, + user: { + connect: { + id: id + } + } + } + const updatedUser = await dbClient.profile.create({ + data + }) + + return User.fromDb(updatedUser) + } + static async findByEmail(email) { return User._findByUnique('email', email) } diff --git a/src/routes/user.js b/src/routes/user.js index 9f63d162..fe3b2253 100644 --- a/src/routes/user.js +++ b/src/routes/user.js @@ -1,5 +1,5 @@ import { Router } from 'express' -import { create, getById, getAll, updateById } from '../controllers/user.js' +import { create, getById, getAll, updateById, createProfile } from '../controllers/user.js' import { validateAuthentication, validateTeacherRole @@ -11,5 +11,6 @@ router.post('/', create) router.get('/', validateAuthentication, getAll) router.get('/:id', validateAuthentication, getById) router.patch('/:id', validateAuthentication, validateTeacherRole, updateById) +router.post('/profile/:id', validateAuthentication, createProfile) export default router