Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
PORT=4000
DATABASE_URL="<your_database_url>?schema=prisma"
SHADOW_DATABASE_URL="<your_shadow_database_url>?schema=shadow"
DATABASE_URL=
SHADOW_DATABASE_URL=
JWT_SECRET="somesecurestring"
JWT_EXPIRY="24h"
45 changes: 45 additions & 0 deletions prisma/migrations/20250227123420_better_posts/migration.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
Warnings:

- Added the required column `createdAt` to the `Post` table without a default value. This is not possible if the table is not empty.
- Added the required column `updatedAt` to the `Post` table without a default value. This is not possible if the table is not empty.

*/
-- AlterTable
ALTER TABLE "Post" ADD COLUMN "createdAt" TIMESTAMP(3) NOT NULL,
ADD COLUMN "updatedAt" TIMESTAMP(3) NOT NULL;

-- CreateTable
CREATE TABLE "Like" (
"id" SERIAL NOT NULL,
"postId" INTEGER NOT NULL,
"userId" INTEGER NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL,
"updatedAt" TIMESTAMP(3) NOT NULL,

CONSTRAINT "Like_pkey" PRIMARY KEY ("id")
);

-- CreateTable
CREATE TABLE "Comment" (
"id" SERIAL NOT NULL,
"postId" INTEGER NOT NULL,
"userId" INTEGER NOT NULL,
"content" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL,
"updatedAt" TIMESTAMP(3) NOT NULL,

CONSTRAINT "Comment_pkey" PRIMARY KEY ("id")
);

-- AddForeignKey
ALTER TABLE "Like" ADD CONSTRAINT "Like_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;

-- AddForeignKey
ALTER TABLE "Like" ADD CONSTRAINT "Like_postId_fkey" FOREIGN KEY ("postId") REFERENCES "Post"("id") ON DELETE RESTRICT ON UPDATE CASCADE;

-- 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;
35 changes: 31 additions & 4 deletions prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ model User {
cohort Cohort? @relation(fields: [cohortId], references: [id])
posts Post[]
deliveryLogs DeliveryLog[]
comments Comment[]
likes Like[]
}

model Profile {
Expand All @@ -45,10 +47,35 @@ model Cohort {
}

model Post {
id Int @id @default(autoincrement())
content String
userId Int
user User @relation(fields: [userId], references: [id])
id Int @id @default(autoincrement())
content String
userId Int
user User @relation(fields: [userId], references: [id])
likes Like[]
comments Comment[]
createdAt DateTime
updatedAt DateTime
}

model Like {
id Int @id @default(autoincrement())
postId Int
post Post @relation(fields: [postId], references: [id])
userId Int
user User @relation(fields: [userId], references: [id])
createdAt DateTime
updatedAt DateTime
}

model Comment {
id Int @id @default(autoincrement())
postId Int
post Post @relation(fields: [postId], references: [id])
userId Int
user User @relation(fields: [userId], references: [id])
content String
createdAt DateTime
updatedAt DateTime
}

model DeliveryLog {
Expand Down
10 changes: 8 additions & 2 deletions prisma/seed.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,12 @@ async function seed() {
async function createPost(userId, content) {
const post = await prisma.post.create({
data: {
userId,
content
userId: userId,
content: content,
// likes: [],
// comments: [],
createdAt: new Date(),
updatedAt: new Date()
},
include: {
user: true
Expand Down Expand Up @@ -81,6 +85,8 @@ async function createUser(
githubUrl
}
}
// comments: [],
// likes: []
},
include: {
profile: true
Expand Down
48 changes: 48 additions & 0 deletions src/controllers/profile.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import Profile from '../domain/profile.js'
import { sendDataResponse, sendMessageResponse } from '../utils/responses.js'

export const create = async (req, res) => {
const profileToCreate = await Profile.fromJson(req.body)

try {
const existingProfileWithUser = await Profile.findByUserId(
profileToCreate.userId
)

if (existingProfileWithUser) {
return sendDataResponse(res, 400, { email: 'You already have a profile' })
}

const createdProfile = await profileToCreate.save()

return sendDataResponse(res, 201, createdProfile)
} catch (error) {
return sendMessageResponse(res, 500, 'Unable to create new profile')
}
}

export const getById = async (req, res) => {
const id = parseInt(req.params.id)

try {
const foundProfile = await Profile.findById(id)

if (!foundProfile) {
return sendDataResponse(res, 404, { id: 'User not found' })
}

return sendDataResponse(res, 200, foundProfile)
} catch (e) {
return sendMessageResponse(res, 500, 'Unable to get user')
}
}

export const updateById = async (req, res) => {
const { user_id: userId } = req.body

if (!userId) {
return sendDataResponse(res, 400, { user_id: 'User ID is required' })
}

return sendDataResponse(res, 201, { user: { user_id: userId } })
}
12 changes: 11 additions & 1 deletion src/controllers/user.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,24 @@ 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)
// eslint-disable-next-line
const passwordRegex = /(?=.*[a-z])(?=.*[A-Z{1,}])(?=.*[0-9{1,}])(?=.*[!-\/:-@[-`{-~{1,}]).{8,}/
const emailRegex = /[^@]{1,}[@]{1}[^@]{1,}/

if (existingUser) {
return sendDataResponse(res, 400, { email: 'Email already in use' })
}

if (!passwordRegex.test(req.body.password)) {
return sendDataResponse(res, 400, { password: 'Invalid password' })
}

if (!emailRegex.test(req.body.email)) {
return sendDataResponse(res, 400, { email: 'Invalid email' })
}

const createdUser = await userToCreate.save()

return sendDataResponse(res, 201, createdUser)
Expand Down
115 changes: 115 additions & 0 deletions src/domain/profile.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import dbClient from '../utils/dbClient.js'

export default class Profile {
static fromDb(profile) {
return new Profile(
profile.id,
profile.userId,
profile.firstName,
profile.lastName,
profile.bio,
profile.githubUrl
)
}

static async fromJson(json) {
const { userId, firstName, lastName, bio, githubUrl } = json

return new Profile(null, userId, firstName, lastName, bio, githubUrl)
}

constructor(id, userId, firstName, lastName, bio, githubUrl) {
this.id = id
this.userId = userId
this.firstName = firstName
this.lastName = lastName
this.bio = bio
this.githubUrl = githubUrl
}

toJson() {
return {
profile: {
id: this.id,
user_id: this.userId,
firstName: this.firstName,
lastName: this.lastName,
bio: this.bio,
githubUrl: this.githubUrl
}
}
}

async save() {
const data = {
firstName: this.firstName,
lastName: this.lastName,
bio: this.bio,
githubUrl: this.githubUrl
}

if (this.userId) {
data.user = {
connectOrCreate: {
id: this.userId
}
}
}

const createdProfile = await dbClient.profile.create({
data,
include: {
user: true
}
})
return Profile.fromDb(createdProfile)
}

static async findById(id) {
return Profile._findByUnique('id', id)
}

static async findAll() {
return Profile._findMany()
}

static async findByUserId(userId) {
return Profile._findByUnique('userId', userId)
}

static async _findByUnique(key, value) {
const foundProfile = await dbClient.profile.findUnique({
where: {
[key]: value
},
include: {
user: true
}
})

if (foundProfile) {
return Profile.fromDb(foundProfile)
}

return null
}

static async _findMany(key, value) {
const query = {
include: {
user: true
}
}

if (key && value) {
query.where = {
user: {
[key]: value
}
}
}

const foundProfiles = await dbClient.profile.findMany(query)
return foundProfiles.map((p) => Profile.fromDb(p))
}
}
12 changes: 12 additions & 0 deletions src/routes/profile.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { Router } from 'express'
import { create, getById, updateById } from '../controllers/profile.js'
import { validateAuthentication } from '../middleware/auth.js'

const router = Router()

// Need to validate
router.post('/', validateAuthentication, create)
router.get('/:id', validateAuthentication, getById)
router.patch('/:id', validateAuthentication, updateById)

export default router
2 changes: 2 additions & 0 deletions src/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 profileRouter from './routes/profile.js'
import cohortRouter from './routes/cohort.js'
import deliveryLogRouter from './routes/deliveryLog.js'

Expand All @@ -21,6 +22,7 @@ const docFile = fs.readFileSync('./docs/openapi.yml', 'utf8')
const swaggerDoc = YAML.parse(docFile)
app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerDoc))

app.use('/profile', profileRouter)
app.use('/users', userRouter)
app.use('/posts', postRouter)
app.use('/cohorts', cohortRouter)
Expand Down
Loading