Skip to content
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,3 +122,11 @@ Create Express TypeScript Starter was created by [Wubshet Zeleke](https://linked

## License
Create Express TypeScript Starter is licensed under the MIT License.



## NOTES

- this appication is an employee facing app/system so i think im going to make the decision to store pins as they are and not hashing them because
there isnt any private data or information that could be used from the app. Creating an employee is simply to have their name in the app in order
to track whos parking which ever cars
109 changes: 109 additions & 0 deletions __tests__/employee.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import request from 'supertest'
import app from '../src/app'
import prisma from '../src/services/prisma'
import { Employee } from '@prisma/client'

beforeEach(async () => {
// Clear tables with foreign keys first
await prisma.car.deleteMany() // Since Car relates to Employee
await prisma.employee.deleteMany()
await prisma.entrance.deleteMany()

// Then clear tables without dependencies
await prisma.location.deleteMany()
})

test('POST /v1/location/:locationId/employee creates a new employee', async () => {
// First, create a location to associate with the employee
const location = await prisma.location.create({ data: { name: 'Test Location - employee' } })

const res = await request(app).post(`/v1/location/${location.id}/employee`).send({ name: 'John Doe', pin: '1234' })

expect(res.status).toBe(201)
expect(res.body).toHaveProperty('message', 'Employee created')
expect(res.body.employee).toHaveProperty('name', 'John Doe')
expect(res.body.employee).toHaveProperty('pin', '1234')
expect(res.body.employee).toHaveProperty('locationId', location.id)

// Verify it exists in the DB
const employeeInDb = await prisma.employee.findFirst({ where: { pin: '1234' } })
expect(employeeInDb).not.toBeNull()
expect(employeeInDb?.locationId).toBe(location.id)
})

test('GET /v1/location/:locationId/employee retrieves employees for a location', async () => {
// Create a location and employees
const location = await prisma.location.create({ data: { name: 'Test Location - get employees' } })
await prisma.employee.createMany({
data: [
{ name: 'Alice', pin: '1111', locationId: location.id },
{ name: 'Bob', pin: '2222', locationId: location.id },
],
})

const res = await request(app).get(`/v1/location/${location.id}/employee`)

expect(res.status).toBe(200)
expect(res.body).toHaveProperty('message', `Retrieved all employees for location ${location.id}`)
expect(res.body.employees).toHaveLength(2)
const pins = res.body.employees.map((emp: Employee) => emp.pin)
expect(pins).toContain('1111')
expect(pins).toContain('2222')
})

test('GET /v1/location/:locationId/employee/:id retrieves a single employee', async () => {
// Create a location and an employee
const location = await prisma.location.create({ data: { name: 'Test Location - get single employee' } })
const employee = await prisma.employee.create({
data: { name: 'Charlie', pin: '3333', locationId: location.id },
})

const res = await request(app).get(`/v1/location/${location.id}/employee/${employee.id}`)

expect(res.status).toBe(200)
expect(res.body).toHaveProperty('message', `Retrieved employee ${employee.id}`)
expect(res.body.employee).toHaveProperty('name', 'Charlie')
expect(res.body.employee).toHaveProperty('pin', '3333')
expect(res.body.employee).toHaveProperty('locationId', location.id)
})

test('DELETE /v1/location/:locationId/employee/:id deletes an employee', async () => {
// Create a location and an employee
const location = await prisma.location.create({ data: { name: 'Test Location - delete employee' } })
const employee = await prisma.employee.create({
data: { name: 'Dave', pin: '4444', locationId: location.id },
})

const res = await request(app).delete(`/v1/location/${location.id}/employee/${employee.id}`)

expect(res.status).toBe(200)
expect(res.body).toHaveProperty('message', `Deleted employee ${employee.id}`)
expect(res.body.employee).toHaveProperty('id', employee.id)

// Verify it's deleted from the DB
const employeeInDb = await prisma.employee.findUnique({ where: { id: employee.id } })
expect(employeeInDb).toBeNull()
})

test('PUT /v1/location/:locationId/employee/:id updates an employee', async () => {
// Create a location and an employee
const location = await prisma.location.create({ data: { name: 'Test Location - update employee' } })
const employee = await prisma.employee.create({
data: { name: 'Eve', pin: '5555', locationId: location.id },
})

const res = await request(app)
.put(`/v1/location/${location.id}/employee/${employee.id}`)
.send({ updatedName: 'Eve Updated', pin: '9999' })

expect(res.status).toBe(200)
expect(res.body).toHaveProperty('message', `Updated employee ${employee.id}`)
expect(res.body.employee).toHaveProperty('name', 'Eve Updated')
expect(res.body.employee).toHaveProperty('pin', '9999')

// Verify the updates in the DB
const employeeInDb = await prisma.employee.findUnique({ where: { id: employee.id } })
expect(employeeInDb).not.toBeNull()
expect(employeeInDb?.name).toBe('Eve Updated')
expect(employeeInDb?.pin).toBe('9999')
})
8 changes: 4 additions & 4 deletions __tests__/entrance.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ import request from 'supertest'
import app from '../src/app'
import prisma from '../src/services/prisma'

beforeEach(async () => {
await prisma.entrance.deleteMany()
await prisma.location.deleteMany()
})
// beforeEach(async () => {
// await prisma.entrance.deleteMany()
// await prisma.location.deleteMany()
// })

test('POST /v1/location/:locationId/entrance creates a new entrance', async () => {
// First, create a location to associate with the entrance
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ CREATE TABLE "public"."Employee" (
"id" SERIAL NOT NULL,
"name" TEXT NOT NULL,
"pin" TEXT NOT NULL,
"locationId" INTEGER NOT NULL,

CONSTRAINT "Employee_pkey" PRIMARY KEY ("id")
);
Expand Down Expand Up @@ -56,6 +57,9 @@ CREATE UNIQUE INDEX "Car_ticket_key" ON "public"."Car"("ticket");
-- AddForeignKey
ALTER TABLE "public"."Entrance" ADD CONSTRAINT "Entrance_locationId_fkey" FOREIGN KEY ("locationId") REFERENCES "public"."Location"("id") ON DELETE RESTRICT ON UPDATE CASCADE;

-- AddForeignKey
ALTER TABLE "public"."Employee" ADD CONSTRAINT "Employee_locationId_fkey" FOREIGN KEY ("locationId") REFERENCES "public"."Location"("id") ON DELETE RESTRICT ON UPDATE CASCADE;

-- AddForeignKey
ALTER TABLE "public"."Car" ADD CONSTRAINT "Car_parkedById_fkey" FOREIGN KEY ("parkedById") REFERENCES "public"."Employee"("id") ON DELETE SET NULL ON UPDATE CASCADE;

Expand Down
9 changes: 4 additions & 5 deletions prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,13 @@ enum CarStatus {
}


model Test {
id Int @id @default(autoincrement())
name String
value Int
}


model Location {
id Int @id @default(autoincrement())
name String
entrances Entrance[]
employees Employee[]
}

model Entrance {
Expand All @@ -45,6 +42,8 @@ model Employee{
id Int @id @default(autoincrement())
name String
pin String @unique
location Location @relation(fields: [locationId], references: [id])
locationId Int
ParkedCars Car[] @relation("ParkedCars")
CheckedOutCars Car[] @relation("CheckedOutCars")
}
Expand Down
107 changes: 107 additions & 0 deletions src/resources/employee/controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import { Request, Response, NextFunction } from 'express'
import { createEmployee, deleteEmployee, getEmployeeById } from '../../services/employeeService'
import prisma from '../../services/prisma'

const makeEmployee = async (req: Request, res: Response, next: NextFunction) => {
try {
const { name, pin } = req.body
const { locationId } = req.params

if (!name || !pin) {
return res.status(400).json({ error: 'Name and Pin are required' })
}

// Check if the PIN already exists
const existingEmployee = await prisma.employee.findUnique({ where: { pin } })
if (existingEmployee) {
return res.status(409).json({ error: 'An employee with this PIN already exists' })
}

const newEmployee = await createEmployee(name, pin, parseInt(locationId))

res.status(201).json({ message: 'Employee created', employee: newEmployee })
} catch (error) {
next(error)
}
}

const getEmployees = async (req: Request, res: Response, next: NextFunction) => {
try {
const { locationId } = req.params
if (!locationId) {
return res.status(400).json({ error: 'Location ID is required' })
}
const employees = await prisma.employee.findMany({ where: { locationId: parseInt(locationId) } })
res.status(200).json({ message: `Retrieved all employees for location ${locationId}`, employees })
} catch (error) {
next(error)
}
}

const getSingleEmployee = async (req: Request, res: Response, next: NextFunction) => {
try {
const { employeeId } = req.params
if (!employeeId) {
return res.status(400).json({ error: 'Employee ID is required' })
}
const employee = await getEmployeeById(parseInt(employeeId))
if (!employee) {
return res.status(404).json({ error: 'Employee not found' })
}

res.status(200).json({ message: `Retrieved employee ${employeeId}`, employee })
} catch (error) {
next(error)
}
}

const removeEmployee = async (req: Request, res: Response, next: NextFunction) => {
try {
const { employeeId } = req.params
if (!employeeId) {
return res.status(400).json({ error: 'Employee ID is required' })
}
const deletedEmployee = await deleteEmployee(parseInt(employeeId))
if (!deletedEmployee) {
return res.status(404).json({ error: 'Employee not found' })
}
res.status(200).json({ message: `Deleted employee ${employeeId}`, employee: deletedEmployee })
} catch (error) {
next(error)
}
}

const updateEmployee = async (req: Request, res: Response, next: NextFunction) => {
try {
const { employeeId } = req.params
const { updatedName, pin } = req.body

if (!employeeId) {
return res.status(400).json({ error: 'Employee ID is required' })
}

const existingEmployee = await getEmployeeById(parseInt(employeeId))
if (!existingEmployee) {
return res.status(404).json({ error: 'Employee not found' })
}

// If pin is being updated, check for uniqueness
if (pin && pin !== existingEmployee.pin) {
const pinConflict = await prisma.employee.findUnique({ where: { pin } })
if (pinConflict) {
return res.status(409).json({ error: 'An employee with this PIN already exists' })
}
}

const updatedEmployee = await prisma.employee.update({
where: { id: parseInt(employeeId) },
data: { name: updatedName || existingEmployee.name, pin: pin || existingEmployee.pin },
})

res.status(200).json({ message: `Updated employee ${employeeId}`, employee: updatedEmployee })
} catch (error) {
next(error)
}
}

export default { makeEmployee, getEmployees, getSingleEmployee, removeEmployee, updateEmployee }
12 changes: 12 additions & 0 deletions src/resources/employee/routes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { Router } from 'express'
import employeeController from './controller'

const router = Router({ mergeParams: true })

// define routes
router.post('/', employeeController.makeEmployee)
router.delete('/:employeeId', employeeController.removeEmployee)
router.get('/', employeeController.getEmployees)
router.get('/:employeeId', employeeController.getSingleEmployee)
router.put('/:employeeId', employeeController.updateEmployee)
export default router
3 changes: 3 additions & 0 deletions src/resources/locations/routes.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Router } from 'express'
import locationController from './controller'
import entranceRouter from '../entrances/routes'
import employeeRouter from '../employee/routes'
const router = Router()

// define routes
Expand All @@ -10,5 +11,7 @@ router.get('/', locationController.getLocations)
router.get('/:id', locationController.getSingleLocation)
router.put('/:id', locationController.updateLocation)

router.use('/:locationId/employee', employeeRouter)
router.use('/:locationId/entrance', entranceRouter)

export default router
46 changes: 46 additions & 0 deletions src/services/employeeService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// src/services/employeeService.ts
import prisma from './prisma'
import { Employee } from '@prisma/client'

// Create a new employee for a location
export const createEmployee = async (name: string, pin: string, locationId: number) => {
try {
const employee = await prisma.employee.create({
data: { name, pin, locationId },
})
return employee
} catch (err) {
console.error('Error in createEmployee:', err)
throw err
}
}

// Get a single employee by ID
export const getEmployeeById = async (id: number): Promise<Employee | null> => {
try {
return await prisma.employee.findUnique({ where: { id } })
} catch (err) {
console.error('Error in getEmployeeById:', err)
throw err
}
}

// Delete an employee
export const deleteEmployee = async (id: number): Promise<Employee> => {
try {
return await prisma.employee.delete({ where: { id } })
} catch (err) {
console.error('Error in deleteEmployee:', err)
throw err
}
}

// Get all employees for a specific location
export const getEmployeesByLocation = async (locationId: number): Promise<Employee[]> => {
try {
return await prisma.employee.findMany({ where: { locationId } })
} catch (err) {
console.error('Error in getEmployeesByLocation:', err)
throw err
}
}