Skip to content
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ Once you have complete the above guide, continue to the steps below.

[todo]: <Deploy and update the link below>

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

Expand Down
89 changes: 89 additions & 0 deletions docs/openapi.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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:
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
6 changes: 6 additions & 0 deletions prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
28 changes: 26 additions & 2 deletions prisma/seed.js
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand All @@ -22,6 +28,12 @@ async function seed() {
'Sanchez',
'Hello there!',
'teacher1',
'username',
'mobile',
'startDate',
'endDate',
'specialism',
'jobTitle',
'TEACHER'
)

Expand Down Expand Up @@ -65,6 +77,12 @@ async function createUser(
lastName,
bio,
githubUrl,
username,
mobile,
startDate,
endDate,
specialism,
jobTitle,
role = 'STUDENT'
) {
const user = await prisma.user.create({
Expand All @@ -78,7 +96,13 @@ async function createUser(
firstName,
lastName,
bio,
githubUrl
githubUrl,
username,
mobile,
startDate,
endDate,
specialism,
jobTitle
}
}
},
Expand Down
35 changes: 35 additions & 0 deletions src/controllers/user.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,28 @@
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)

Expand Down Expand Up @@ -65,3 +85,18 @@

return sendDataResponse(res, 201, { user: { cohort_id: cohortId } })
}


Check failure on line 89 in src/controllers/user.js

View workflow job for this annotation

GitHub Actions / test

Delete `⏎`
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)
}
75 changes: 72 additions & 3 deletions src/domain/user.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -19,14 +19,33 @@ 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
)
}

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)

Expand All @@ -38,6 +57,12 @@ export default class User {
email,
biography,
githubUrl,
username,
mobile,
startDate,
endDate,
specialism,
jobTitle,
passwordHash
)
}
Expand All @@ -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'
) {
Expand All @@ -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
}
Expand All @@ -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
}
}
}
Expand Down Expand Up @@ -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)
}
Expand Down
3 changes: 2 additions & 1 deletion src/routes/user.js
Original file line number Diff line number Diff line change
@@ -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'

Check failure on line 2 in src/routes/user.js

View workflow job for this annotation

GitHub Actions / test

Replace `·create,·getById,·getAll,·updateById,·createProfile·` with `⏎··create,⏎··getById,⏎··getAll,⏎··updateById,⏎··createProfile⏎`
import {
validateAuthentication,
validateTeacherRole
Expand All @@ -11,5 +11,6 @@
router.get('/', validateAuthentication, getAll)
router.get('/:id', validateAuthentication, getById)
router.patch('/:id', validateAuthentication, validateTeacherRole, updateById)
router.post('/profile/:id', validateAuthentication, createProfile)

export default router
Loading