diff --git a/api/README.md b/api/README.md index c8821c6..35c332e 100644 --- a/api/README.md +++ b/api/README.md @@ -110,11 +110,12 @@ req: none res: body: {CombineDTO} ``` -Update ticket +Updates one ticket by ticket_id; Does NOT update user or device ``` -/* NOT WORKING */ -PUT /api​/ticket​/{id} +PUT /api​/ticket​/{ticket_id} +req: body: {UpdateTicketDTO} +res: body: {TicketDTO} ``` ## User @@ -155,16 +156,51 @@ GET /api​/user/{lsu_id} res: body: {UserDTO} ``` -Update user +Update user by lsu_id ``` -/* NOT WORKING */ PUT /api/user/{lsu_id} +req: body: {UpdateUserDTO} +res: body: {UserDTO} ``` -## Assign +## Assignment -TODO +Get all assignments + +``` +GET /api/assignment +res: body: {AssignmentDTO} +``` + +Get all assignments assigned to admin by lsu_id + +``` +GET /api/assignment/user/{lsu_id} +res: body: {AssignmentDTO[]} +``` + +Get all assignments assigned to ticket by ticket_id + +``` +GET /api/assignment/ticket/{ticket_id} +res: body: {AssignmentDTO[]} +``` + +Get one assignment by assignment_id + +``` +GET /api/assignment/{assignment_id} +res: body: {AssignmentDTO} +``` + +Post new assignment + +``` +POST /api/assignment +req: body: {CreateAssignmentDTO} +res: body: {AssignmentDTO} +``` ## Work diff --git a/api/src/app.module.ts b/api/src/app.module.ts index 837029a..49dab65 100644 --- a/api/src/app.module.ts +++ b/api/src/app.module.ts @@ -5,7 +5,7 @@ import { AppController } from './app.controller'; import { AppService } from './app.service'; import { TicketModule } from './ticket/ticket.module'; import { TicketWorkModule } from './work/ticketwork.module'; -import { TicketAssignModule } from './assign/ticketassign.module'; +import { AssignmentModule } from './assignment/assignment.module.'; import { UserModule } from './user/uer.module'; /** @@ -41,7 +41,7 @@ const configImports = (modules: ModuleMetadata['imports']) => { imports: configImports([ TicketModule, TicketWorkModule, - TicketAssignModule, + AssignmentModule, UserModule, ]), controllers: [AppController], diff --git a/api/src/assign/dto/create-ticketassign.dto.ts b/api/src/assign/dto/create-ticketassign.dto.ts deleted file mode 100644 index ff9feb5..0000000 --- a/api/src/assign/dto/create-ticketassign.dto.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { ApiProperty } from '@nestjs/swagger'; - -export class CreateTicketAssignDto { - @ApiProperty({ minLength: 3 }) - assignedby: string; - - @ApiProperty({ minLength: 3 }) - assignedto: string; - - @ApiProperty({}) - comment: number; - - @ApiProperty({}) - assigndate: Date; -} diff --git a/api/src/assign/dto/update-ticketassign.dto.ts b/api/src/assign/dto/update-ticketassign.dto.ts deleted file mode 100644 index 07b8fee..0000000 --- a/api/src/assign/dto/update-ticketassign.dto.ts +++ /dev/null @@ -1,6 +0,0 @@ -export class UpdateTicketAssignDto { - assignedby: string; - assignedto: string; - comment: string; - assigndate: string; -} diff --git a/api/src/assign/entities/ticketassign.entity.ts b/api/src/assign/entities/ticketassign.entity.ts deleted file mode 100644 index 49fb52b..0000000 --- a/api/src/assign/entities/ticketassign.entity.ts +++ /dev/null @@ -1,6 +0,0 @@ -export class TicketAssign { - assignedby: string; - assignedto: string; - comment: string; - assigndate: Date; -} diff --git a/api/src/assign/ticketassign.controller.ts b/api/src/assign/ticketassign.controller.ts deleted file mode 100644 index 26a93e5..0000000 --- a/api/src/assign/ticketassign.controller.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { - Controller, - Get, - Post, - Body, - Put, - Param, - Delete, -} from '@nestjs/common'; -import { TicketAssignService } from './ticketassign.service'; -import { CreateTicketAssignDto } from './dto/create-ticketassign.dto'; -import { UpdateTicketAssignDto } from './dto/update-ticketassign.dto'; -import { ApiTags } from '@nestjs/swagger'; - -@ApiTags('ticket') -@Controller('/api/ticket/assign') -export class TicketAssignController { - constructor(private readonly ticketAssignService: TicketAssignService) {} - /* Working Implementation */ - @Post() - async create(@Body() createTicketAssignDto: CreateTicketAssignDto) { - return await this.ticketAssignService.create(createTicketAssignDto); - } - /* TODO */ - @Get() - findAll() { - return this.ticketAssignService.findAll(); - } - /* TODO */ - @Get(':id') - findOne(@Param('id') id: string) { - return this.ticketAssignService.findOne(+id); - } - /* TODO */ - @Put(':id') - update( - @Param('id') id: string, - @Body() updateTicketAssignDto: UpdateTicketAssignDto, - ) { - return this.ticketAssignService.update(+id, updateTicketAssignDto); - } - /* TODO */ - @Delete(':id') - remove(@Param('id') id: string) { - return this.ticketAssignService.remove(+id); - } -} diff --git a/api/src/assign/ticketassign.module.ts b/api/src/assign/ticketassign.module.ts deleted file mode 100644 index 877b897..0000000 --- a/api/src/assign/ticketassign.module.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { Module } from '@nestjs/common'; -import { TicketAssignService } from './ticketassign.service'; -import { TicketAssignController } from './ticketassign.controller'; -import { DbModule } from 'src/db/db.module'; - -@Module({ - imports:[DbModule], - controllers: [TicketAssignController], - providers: [TicketAssignService] -}) -export class TicketAssignModule {} diff --git a/api/src/assign/ticketassign.service.ts b/api/src/assign/ticketassign.service.ts deleted file mode 100644 index 29f3ea4..0000000 --- a/api/src/assign/ticketassign.service.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { HttpException, HttpStatus, Inject, Injectable } from '@nestjs/common'; -import { Pool } from 'pg'; -import { PG_CONNECTION } from 'src/connection'; -import { CreateTicketAssignDto } from './dto/create-ticketassign.dto'; -import { UpdateTicketAssignDto } from './dto/update-ticketassign.dto'; -import { TicketAssign } from './entities/ticketassign.entity'; - -/* TODO: - - Implement remaining methods - - Find solution to abstract SQL -*/ -@Injectable() -export class TicketAssignService { - constructor(@Inject(PG_CONNECTION) private connection: Pool) {} - - async create(createTicketAssignDto: CreateTicketAssignDto) { - const { - assignedby, - assignedto, - comment, - assigndate, - } = createTicketAssignDto; - - /* Insert new user into db - TODO: implement error handling for pg request - */ - const text = - 'INSERT INTO ticketwork(assignedby, assignedto, comment, assigndate) VALUES ($1, $2, $3, $4) RETURNING *'; - const values = [assignedby, assignedto, comment, assigndate]; - const res = (await this.connection.query(text, values)) - .rows[0]; - - /* Return user object without password */ - const ticketassign = new TicketAssign(); - ticketassign.assignedby = res.assignedby; - ticketassign.assignedto = res.assignedto; - ticketassign.comment = res.comment; - ticketassign.assigndate = res.assigndate; - return ticketassign; - } - /* TODO */ - findAll() { - return `This action returns all user`; - } - /* TODO */ - findOne(id: number) { - return `This action returns a #${id} user`; - } - /* TODO */ - update(id: number, updateTicketAssignDto: UpdateTicketAssignDto) { - return `This action updates a #${id} user`; - } - /* TODO */ - remove(id: number) { - return `This action removes a #${id} user`; - } -} diff --git a/api/src/assignment/assignment.controller.ts b/api/src/assignment/assignment.controller.ts new file mode 100644 index 0000000..00d2b80 --- /dev/null +++ b/api/src/assignment/assignment.controller.ts @@ -0,0 +1,45 @@ +import { Body, Controller, Get, Param, Post } from '@nestjs/common'; +import { ApiTags } from '@nestjs/swagger'; +import { AssignmentService } from './assignment.service'; +import { AssignmentType } from './dto/assignment.dto'; +import { CreateAssignmentDTO } from './dto/create-assignment.dto'; + +@ApiTags('assignment') +@Controller('/api/assign') +export class AssignmentController { + constructor(private readonly ticketAssignService: AssignmentService) {} + + /* WORKING Implementation */ + /* TODO: fix date formatting */ + @Post() + async create(@Body() createAssignmentDTO: CreateAssignmentDTO) { + return await this.ticketAssignService.create(createAssignmentDTO); + } + + /* WORKING Implementation */ + @Get() + findAll() { + return this.ticketAssignService.findAll(); + } + + /* WORKING Implementation */ + @Get('user/:lsu_id') + findAllByLsuId(@Param('lsu_id') lsu_id: number) { + return this.ticketAssignService.findAllById(AssignmentType.LSU_ID, lsu_id); + } + + /* WORKING Implementation */ + @Get('ticket/:ticket_id') + findAllByTicketId(@Param('ticket_id') ticket_id: number) { + return this.ticketAssignService.findAllById( + AssignmentType.TICKET_ID, + ticket_id, + ); + } + + /* WORKING Implementation */ + @Get(':assignment_id') + findOne(@Param('assignment_id') assignment_id: string) { + return this.ticketAssignService.findOne(+assignment_id); + } +} diff --git a/api/src/assignment/assignment.module..ts b/api/src/assignment/assignment.module..ts new file mode 100644 index 0000000..ffe4a42 --- /dev/null +++ b/api/src/assignment/assignment.module..ts @@ -0,0 +1,11 @@ +import { Module } from '@nestjs/common'; +import { AssignmentService } from './assignment.service'; +import { AssignmentController } from './assignment.controller'; +import { DbModule } from 'src/db/db.module'; + +@Module({ + imports: [DbModule], + controllers: [AssignmentController], + providers: [AssignmentService], +}) +export class AssignmentModule {} diff --git a/api/src/assignment/assignment.service.ts b/api/src/assignment/assignment.service.ts new file mode 100644 index 0000000..97d9b8b --- /dev/null +++ b/api/src/assignment/assignment.service.ts @@ -0,0 +1,141 @@ +import { HttpException, HttpStatus, Inject, Injectable } from '@nestjs/common'; +import { Pool, Query, QueryConfig } from 'pg'; +import { PG_CONNECTION } from 'src/connection'; +import { CreateAssignmentDTO } from './dto/create-assignment.dto'; +import { AssignmentDTO, AssignmentType } from './dto/assignment.dto'; +import { createDate } from 'src/util'; + +/* TODO: + - Implement remaining methods + - Find solution to abstract SQL +*/ +@Injectable() +export class AssignmentService { + constructor(@Inject(PG_CONNECTION) private connection: Pool) {} + + async create(createAssignmentDTO: CreateAssignmentDTO) { + const { lsu_id, ticket_id } = createAssignmentDTO; + const assigned_date = createDate(); + + /* Insert new assignment into db */ + const query: QueryConfig = { + name: 'insert_assignment', + text: + 'INSERT INTO assignment(lsu_id, ticket_id, assigned_date) VALUES ($1, $2, $3) RETURNING *', + values: [lsu_id, ticket_id, assigned_date], + }; + try { + const res = await this.connection.query(query); + + /* Return newly inserted assignment */ + return res.rows[0]; + } catch (error) { + throw new HttpException( + { + query: query, + error: error, + }, + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + /* TODO */ + async findAllById(type: AssignmentType, id: number) { + let query: QueryConfig; + switch (type) { + case AssignmentType.LSU_ID: + query = { + name: 'select_all_assignment_by_lsu_id', + text: 'SELECT * FROM assignment WHERE lsu_id = $1', + values: [id], + }; + break; + case AssignmentType.TICKET_ID: + query = { + name: 'select_all_assignment_by_ticket_id', + text: 'SELECT * FROM assignment WHERE ticket_id = $1', + values: [id], + }; + break; + + default: + /* default error caused by not using controller properly */ + throw new HttpException( + { + error: + 'Switch statment in assignment.service.findAllById somehow failed', + }, + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + try { + const res = await this.connection.query(query); + + /* If none exist, return empty array */ + if (res.rows.length < 1) { + return []; + } + /* return array of Assignment objects */ + return res.rows; + } catch (error) { + throw new HttpException( + { + query: query, + error: error, + }, + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + + async findAll() { + const query: QueryConfig = { + name: 'select_all_assignment', + text: 'SELECT * FROM assignment', + }; + try { + const res = await this.connection.query(query); + + /* If none exist, return empty array */ + if (res.rows.length < 1) { + return []; + } + /* return array of Assignment objects */ + return res.rows; + } catch (error) { + throw new HttpException( + { + query: query, + error: error, + }, + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + /* TODO */ + async findOne(assignment_id: number) { + const query: QueryConfig = { + name: 'select_assignment_by_assignment_id', + text: 'SELECT * FROM assignment WHERE assignment_id = $1', + values: [assignment_id], + }; + try { + const res = await this.connection.query(query); + + /* If none exist, return empty object */ + if (res.rows.length < 1) { + return {}; + } + /* return Assignment object */ + return res.rows[0]; + } catch (error) { + throw new HttpException( + { + query: query, + error: error, + }, + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } +} diff --git a/api/src/assignment/dto/assignment.dto.ts b/api/src/assignment/dto/assignment.dto.ts new file mode 100644 index 0000000..fcd42c4 --- /dev/null +++ b/api/src/assignment/dto/assignment.dto.ts @@ -0,0 +1,32 @@ +import { ApiProperty } from '@nestjs/swagger'; +export enum AssignmentType { + 'LSU_ID', + 'TICKET_ID', +} +export class AssignmentDTO { + @ApiProperty({ + description: 'serial generated assignment id', + example: 1, + }) + assignment_id: number; + + @ApiProperty({ + minLength: 9, + maxLength: 9, + description: '9-digit number starting with 89', + example: 897584512, + }) + lsu_id: number; + + @ApiProperty({ + description: 'serial generated ticket id', + example: 1, + }) + ticket_id: number; + + @ApiProperty({ + description: 'YYYY-MM-DD HH:MM:SS', + example: '2020-07-21 12:44:22', + }) + assigned_date: string; +} diff --git a/api/src/assignment/dto/create-assignment.dto.ts b/api/src/assignment/dto/create-assignment.dto.ts new file mode 100644 index 0000000..533aeaa --- /dev/null +++ b/api/src/assignment/dto/create-assignment.dto.ts @@ -0,0 +1,7 @@ +import { ApiProperty, OmitType as Omit } from '@nestjs/swagger'; +import { AssignmentDTO } from './assignment.dto'; + +export class CreateAssignmentDTO extends Omit(AssignmentDTO, [ + 'assignment_id', + 'assigned_date', +]) {} diff --git a/api/src/main.ts b/api/src/main.ts index 99e07ee..ffa443f 100644 --- a/api/src/main.ts +++ b/api/src/main.ts @@ -30,7 +30,7 @@ async function bootstrap() { .setVersion('0.1.0') .setExternalDoc( 'additional documentation', - `http://${HOST}:${PORT}/api/docs/more`, + `https://github.com/cdalton713/CSC_4402_DB_Project/blob/master/api/README.md`, ) .build(); const document = SwaggerModule.createDocument(app, options); diff --git a/api/src/ticket/dto/combined.dto.ts b/api/src/ticket/dto/combined.dto.ts index 50d1870..19a9be4 100644 --- a/api/src/ticket/dto/combined.dto.ts +++ b/api/src/ticket/dto/combined.dto.ts @@ -1,8 +1,11 @@ import { IntersectionType } from '@nestjs/swagger'; -import { Ticket } from './ticket.dto'; -import { User } from 'src/user/dto/user.dto'; -import { Device } from './device.dto'; +import { TicketDTO } from './ticket.dto'; +import { UserDTO } from 'src/user/dto/user.dto'; +import { DeviceDTO } from './device.dto'; -class TicketAndDevice extends IntersectionType(Ticket, Device) {} +class TicketAndDeviceDTO extends IntersectionType(TicketDTO, DeviceDTO) {} -export class Combined extends IntersectionType(TicketAndDevice, User) {} +export class CombinedDTO extends IntersectionType( + TicketAndDeviceDTO, + UserDTO, +) {} diff --git a/api/src/ticket/dto/create-combined.dto.ts b/api/src/ticket/dto/create-combined.dto.ts index 2abca50..c4a5807 100644 --- a/api/src/ticket/dto/create-combined.dto.ts +++ b/api/src/ticket/dto/create-combined.dto.ts @@ -1,14 +1,14 @@ import { IntersectionType, OmitType as Omit } from '@nestjs/swagger'; -import { CreateUser } from 'src/user/dto/create-user.dto'; -import { CreateTicket } from './create-ticket.dto'; -import { CreateDevice } from './create-device.dto'; +import { CreateUserDTO } from 'src/user/dto/create-user.dto'; +import { CreateTicketDTO } from './create-ticket.dto'; +import { CreateDeviceDTO } from './create-device.dto'; -class createTicketAndDevice extends IntersectionType( - CreateTicket, - Omit(CreateDevice, ['ticket_id']), +class createTicketAndDeviceDTO extends IntersectionType( + CreateTicketDTO, + Omit(CreateDeviceDTO, ['ticket_id']), ) {} -export class CreateCombined extends IntersectionType( - createTicketAndDevice, - Omit(CreateUser, ['admin']), +export class CreateCombinedDTO extends IntersectionType( + createTicketAndDeviceDTO, + Omit(CreateUserDTO, ['admin']), ) {} diff --git a/api/src/ticket/dto/create-device.dto.ts b/api/src/ticket/dto/create-device.dto.ts index 490c3f0..ee9b3ea 100644 --- a/api/src/ticket/dto/create-device.dto.ts +++ b/api/src/ticket/dto/create-device.dto.ts @@ -1,4 +1,4 @@ import { OmitType as Omit } from '@nestjs/swagger'; -import { Device } from './device.dto'; +import { DeviceDTO } from './device.dto'; -export class CreateDevice extends Omit(Device, ['device_id']) {} +export class CreateDeviceDTO extends Omit(DeviceDTO, ['device_id']) {} diff --git a/api/src/ticket/dto/create-ticket.dto.ts b/api/src/ticket/dto/create-ticket.dto.ts index cd893c9..a8adb5f 100644 --- a/api/src/ticket/dto/create-ticket.dto.ts +++ b/api/src/ticket/dto/create-ticket.dto.ts @@ -1,4 +1,8 @@ -import { Ticket } from './ticket.dto'; +import { TicketDTO } from './ticket.dto'; import { OmitType as Omit } from '@nestjs/swagger'; -export class CreateTicket extends Omit(Ticket, ['ticket_id', 'status']) {} +export class CreateTicketDTO extends Omit(TicketDTO, [ + 'ticket_id', + 'status', + 'submission_date', +]) {} diff --git a/api/src/ticket/dto/device.dto.ts b/api/src/ticket/dto/device.dto.ts index fcba3f0..022de62 100644 --- a/api/src/ticket/dto/device.dto.ts +++ b/api/src/ticket/dto/device.dto.ts @@ -1,6 +1,6 @@ import { ApiProperty } from '@nestjs/swagger'; -export class Device { +export class DeviceDTO { @ApiProperty({ description: 'id of device', example: 1 }) device_id: number; diff --git a/api/src/ticket/dto/ticket.dto.ts b/api/src/ticket/dto/ticket.dto.ts index a62c001..2048e6c 100644 --- a/api/src/ticket/dto/ticket.dto.ts +++ b/api/src/ticket/dto/ticket.dto.ts @@ -6,7 +6,7 @@ export enum TicketType { 'ANY', } -export class Ticket { +export class TicketDTO { @ApiProperty({ readOnly: true }) ticket_id: number; @@ -47,6 +47,7 @@ export class Ticket { @ApiProperty({ description: 'YYYY-MM-DD HH:MM:SS', + example: '2020-07-21 12:44:22', }) submission_date: string; } diff --git a/api/src/ticket/dto/update-ticket.dto.ts b/api/src/ticket/dto/update-ticket.dto.ts index 3f2cbad..0da1bdd 100644 --- a/api/src/ticket/dto/update-ticket.dto.ts +++ b/api/src/ticket/dto/update-ticket.dto.ts @@ -1,4 +1,10 @@ import { PartialType } from '@nestjs/swagger'; -import { Ticket } from './ticket.dto'; +import { CreateTicketDTO } from './create-ticket.dto'; +import { TicketDTO } from './ticket.dto'; +import { OmitType as Omit } from '@nestjs/swagger'; -export class UpdateTicketDto extends PartialType(Ticket) {} +export class UpdateTicketDto extends Omit(TicketDTO, [ + 'ticket_id', + 'submission_date', + 'lsu_id', +]) {} diff --git a/api/src/ticket/ticket.controller.ts b/api/src/ticket/ticket.controller.ts index 2521d3e..a93d7f6 100644 --- a/api/src/ticket/ticket.controller.ts +++ b/api/src/ticket/ticket.controller.ts @@ -8,7 +8,7 @@ import { Put, } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; -import { CreateCombined } from './dto/create-combined.dto'; +import { CreateCombinedDTO } from './dto/create-combined.dto'; import { TicketType } from './dto/ticket.dto'; import { UpdateTicketDto } from './dto/update-ticket.dto'; import { TicketService } from './ticket.service'; @@ -18,61 +18,63 @@ import { TicketService } from './ticket.service'; export class TicketController { constructor(private readonly ticketService: TicketService) {} - /* TODO: test implementation */ + /* WORKING Implementation */ @Post() - create(@Body() createCombined: CreateCombined) { + create(@Body() createCombinedDTO: CreateCombinedDTO) { Logger.log( { req: { http: 'POST /api/ticket', params: 'none', - body: createCombined, + body: createCombinedDTO, }, }, 'TicketController.create', false, ); - return this.ticketService.create(createCombined); + return this.ticketService.create(createCombinedDTO); } - /* Working implementation */ + /* WORKING Implementation */ @Get() findAll() { return this.ticketService.findAll(TicketType.ANY); } - /* Working implementation */ + /* WORKING Implementation */ @Get('/opened') findAllOpened() { return this.ticketService.findAll(TicketType.OPENED); } - /* Working implementation */ + /* WORKING Implementation */ @Get('/closed') findAllClosed() { return this.ticketService.findAll(TicketType.CLOSED); } - /* Working implementation */ + /* WORKING Implementation */ @Get('/user/:lsu_id') findAllByLsuId(@Param('lsu_id') lsu_id: number) { return this.ticketService.findAll(lsu_id); } - /* Working implementation */ + /* WORKING Implementation */ @Get(':ticket_id') findOne(@Param('ticket_id') ticket_id: number) { return this.ticketService.findOne(+ticket_id); } - /* TODO: FIX */ - /* NOT WORKING */ - @Put(':id') - update(@Param('id') id: number, @Body() updateTicketDto: UpdateTicketDto) { + /* WORKING Implementation */ + @Put(':ticket_id') + update( + @Param('ticket_id') ticket_id: number, + @Body() updateTicketDto: UpdateTicketDto, + ) { Logger.log( { req: { - http: `PUT /api/ticket/${id}`, - params: id, + http: `PUT /api/ticket/${ticket_id}`, + params: ticket_id, body: updateTicketDto, }, }, @@ -80,6 +82,6 @@ export class TicketController { false, ); - return this.ticketService.update(id, updateTicketDto); + return this.ticketService.update(ticket_id, updateTicketDto); } } diff --git a/api/src/ticket/ticket.service.ts b/api/src/ticket/ticket.service.ts index cc3c40d..ca257b3 100644 --- a/api/src/ticket/ticket.service.ts +++ b/api/src/ticket/ticket.service.ts @@ -7,15 +7,16 @@ import { } from '@nestjs/common'; import { Pool, QueryConfig } from 'pg'; import { PG_CONNECTION } from 'src/connection'; -import { CreateUser } from 'src/user/dto/create-user.dto'; -import { User } from 'src/user/dto/user.dto'; -import { Combined } from './dto/combined.dto'; -import { CreateCombined } from './dto/create-combined.dto'; -import { CreateDevice } from './dto/create-device.dto'; -import { CreateTicket } from './dto/create-ticket.dto'; -import { Device } from './dto/device.dto'; -import { Ticket, TicketType } from './dto/ticket.dto'; +import { CreateUserDTO } from 'src/user/dto/create-user.dto'; +import { UserDTO } from 'src/user/dto/user.dto'; +import { CombinedDTO } from './dto/combined.dto'; +import { CreateCombinedDTO } from './dto/create-combined.dto'; +import { CreateDeviceDTO } from './dto/create-device.dto'; +import { CreateTicketDTO } from './dto/create-ticket.dto'; +import { DeviceDTO } from './dto/device.dto'; +import { TicketDTO, TicketType } from './dto/ticket.dto'; import { UpdateTicketDto } from './dto/update-ticket.dto'; +import { createDate } from 'src/util'; @Injectable() export class TicketService { @@ -24,10 +25,10 @@ export class TicketService { /** * Queries and inserts user if user does not exists * - * @param createUser + * @param createUserDTO * @returns User */ - private async createUser(createUser: CreateUser) { + private async createUser(createUserDTO: CreateUserDTO) { const { lsu_id, email, @@ -36,29 +37,33 @@ export class TicketService { phone_number, department, admin, - } = createUser; + } = createUserDTO; + /* Check to see if user exists */ const findQuery: QueryConfig = { name: 'select_user_by_id_or_email', text: 'SELECT * FROM "user" WHERE lsu_id = $1 OR email = $2', values: [lsu_id, email], }; - /* Query User by lsuid or email */ try { - const res = await this.connection.query(findQuery); - /* Test to see if student exists */ + const res = await this.connection.query(findQuery); + + /* If user exists abort insert operation by returning early */ if (res.rows.length > 0) { return res.rows[0]; } } catch (error) { throw new HttpException( - { query: findQuery, error: error }, + { + query: findQuery, + error: error, + }, HttpStatus.INTERNAL_SERVER_ERROR, ); } - /* Insert new student into db */ + /* Insert new user into db */ const insertQuery: QueryConfig = { name: 'insert_user', @@ -75,11 +80,16 @@ export class TicketService { ], }; try { - const res = await this.connection.query(insertQuery); + const res = await this.connection.query(insertQuery); + + /* Return newly inserted user */ return res.rows[0]; } catch (error) { throw new HttpException( - { query: insertQuery, error: error }, + { + query: insertQuery, + error: error, + }, HttpStatus.INTERNAL_SERVER_ERROR, ); } @@ -88,21 +98,25 @@ export class TicketService { /** * Inserts ticket into ticket table * - * @param createTicket + * @param createTicketDTO * @returns Ticket */ - private async createTicket(createTicket: CreateTicket) { + private async createTicket(createTicketDTO: CreateTicketDTO) { const { lsu_id, core_issue, description, problem_category, priority, - } = createTicket; + } = createTicketDTO; + /* Status set to open for new ticket */ const status = 'OPEN'; - const submission_date = this.createDate(); + /* Submission date set to current server time */ + const submission_date = createDate(); + + /* Insert new ticket into db */ const query: QueryConfig = { name: 'insert_ticket', text: @@ -119,12 +133,14 @@ export class TicketService { }; try { - const res = await this.connection.query(query); + const res = await this.connection.query(query); + + /* Return newly inserted ticket */ return res.rows[0]; } catch (error) { throw new HttpException( { - message: query, + query: query, error: error, }, HttpStatus.INTERNAL_SERVER_ERROR, @@ -134,17 +150,19 @@ export class TicketService { /** * Inserts new device into device table - * @param createDevice + * @param createDeviceDTO * @returns Device */ - private async createDevice(createDevice: CreateDevice) { + private async createDevice(createDeviceDTO: CreateDeviceDTO) { const { ticket_id, manufacturer, model, operating_system, operating_system_version, - } = createDevice; + } = createDeviceDTO; + + /* Insert Device into db */ const query: QueryConfig = { name: 'insert_device', text: @@ -158,12 +176,14 @@ export class TicketService { ], }; try { - const res = await this.connection.query(query); + const res = await this.connection.query(query); + + /* Return newly inserted device */ return res.rows[0]; } catch (error) { throw new HttpException( { - message: query, + query: query, error: error, }, HttpStatus.INTERNAL_SERVER_ERROR, @@ -171,41 +191,21 @@ export class TicketService { } } - /** - * Helper method for createTicket to get current date in SQL format - * - * @author KooiInc - * (https://stackoverflow.com/users/58186/kooiinc) - * - * @adapted from https://stackoverflow.com/questions/10632346/how-to-format-a-date-in-mm-dd-yyyy-hhmmss-format-in-javascript - * @returns Date (YYYY-MM-DD HH:MM:SS) - */ - private createDate() { - //@ts-ignore - Number.prototype.padLeft = function (base, chr) { - var len = String(base || 10).length - String(this).length + 1; - return len > 0 ? new Array(len).join(chr || '0') + this : this; - }; - const d = new Date(Date.now()); - const date = - [d.getFullYear(), d.getMonth() + 1, d.getDate()].join('-') + - ' ' + - [d.getHours(), d.getMinutes(), d.getSeconds()].join(':'); - return date; - } - /* TODO: test implementation */ - async create(createCombined: CreateCombined) { + /* WORKING Implementation */ + async create(createCombinedDTO: CreateCombinedDTO) { /* Response Accumulator */ - let response: Combined; + let response: CombinedDTO; - let device: Device; - let ticket: Ticket; - let user: User; + let device: DeviceDTO; + let ticket: TicketDTO; + let user: UserDTO; /* Insert or Retrieve User */ - const createUser: CreateUser = { ...createCombined, admin: false }; // tickets are created by students so admin is false + const createUser: CreateUserDTO = { ...createCombinedDTO, admin: false }; // tickets are created by students so admin is false try { user = await this.createUser(createUser); + + /* Accumulate user to response */ response = { ...response, ...user }; } catch (error) { Logger.error(error); @@ -213,9 +213,11 @@ export class TicketService { } /* Create new Ticket */ - const createTicket: CreateTicket = { ...createCombined }; + const createTicket: CreateTicketDTO = { ...createCombinedDTO }; try { ticket = await this.createTicket(createTicket); + + /* Accumulate ticket to response */ response = { ...response, ...ticket }; } catch (error) { Logger.error(error); @@ -223,18 +225,21 @@ export class TicketService { } /* Create new Device */ - const createDevice: CreateDevice = { - ...createCombined, + const createDevice: CreateDeviceDTO = { + ...createCombinedDTO, ticket_id: ticket.ticket_id, }; try { device = await this.createDevice(createDevice); + + /* Accumulate device to response */ response = { ...response, ...device }; } catch (error) { Logger.error(error); return error; } + /* Return flat object with the intersection of properties from user, ticket, and deivce */ return response; } @@ -274,9 +279,9 @@ export class TicketService { break; } try { - const queryRes = await this.connection.query(query); + const queryRes = await this.connection.query(query); - /* If no tickets */ + /* If no tickets return empty array */ if (queryRes.rows.length === 0) { return []; } @@ -285,7 +290,7 @@ export class TicketService { } catch (error) { throw new HttpException( { - message: query, + query: query, error: error, }, HttpStatus.INTERNAL_SERVER_ERROR, @@ -295,24 +300,25 @@ export class TicketService { /* WORKING implementation */ async findOne(ticket_id: number) { + /* Query ticket by ticket_id */ const query: QueryConfig = { name: 'select_ticket_by_ticket_id', text: 'SELECT * FROM ticket WHERE ticket_id = $1', values: [ticket_id], }; try { - const queryRes = await this.connection.query(query); + const res = await this.connection.query(query); - /* If no ticket found */ - if (queryRes.rows.length === 0) { + /* If no ticket found return empty object*/ + if (res.rows.length === 0) { return {}; } - return queryRes.rows[0]; + return res.rows[0]; } catch (error) { throw new HttpException( { - message: query, + query: query, error: error, }, HttpStatus.INTERNAL_SERVER_ERROR, @@ -320,35 +326,71 @@ export class TicketService { } } - /* NOT WORKING implementation */ - async update(id: number, updateTicketDto: UpdateTicketDto) { - id = Number(id); - let keys = []; - let values = []; - - /* unknown properties to be updated */ - for (let key in updateTicketDto) { - if (!!updateTicketDto[key]) { - keys = [...keys, key]; - values = [...values, updateTicketDto[key]]; + /* WORKING Implementation */ + async update(ticket_id: number, updateTicketDto: UpdateTicketDto) { + const { + core_issue, + description, + problem_category, + status, + priority, + } = updateTicketDto; + + /* Check to see if ticket exist */ + const findQuery: QueryConfig = { + name: 'select_ticket_by_ticket_id', + text: 'SELECT * FROM ticket WHERE ticket_id = $1', + values: [ticket_id], + }; + try { + const res = await this.connection.query(findQuery); + + /* If no ticket found throw custom error*/ + if (res.rows.length < 1) { + throw new Error('BAD_REQUEST'); + } + } catch (error) { + /* catch custom error for ticket not found */ + if (error.message === 'BAD_REQUEST') { + /* Throw HttpException for ticket not found */ + throw new HttpException( + { + message: `Ticket with ticket_id: ${ticket_id} DOES NOT EXIST`, + }, + HttpStatus.BAD_REQUEST, + ); } + /* Throw HttpException for postgres errors */ + throw new HttpException( + { + query: findQuery, + error: error, + }, + HttpStatus.INTERNAL_SERVER_ERROR, + ); } - /* keys variable defined by interface NOT user so avoids sql injection */ - const columns = keys.map((val, idx) => `${val} = $${idx + 1}`).join(', '); - let txt = String.raw`UPDATE ticket SET ${columns} WHERE ticket_id = $${keys.length}`; - const query = { + /* Update ticket */ + const updateQuery = { name: 'update_ticket', - text: txt, - values: [...values, id], + text: + 'UPDATE ticket SET core_issue = $1, description = $2, problem_category = $3, status = $4, priority = $5 WHERE ticket_id = $6 RETURNING *', + values: [ + core_issue, + description, + problem_category, + status, + priority, + ticket_id, + ], }; try { - const queryRes = await this.connection.query(query, [...values, id]); - return queryRes.rows; + const res = await this.connection.query(updateQuery); + return res.rows[0]; } catch (error) { throw new HttpException( { - message: query, + query: updateQuery, error: error, }, HttpStatus.INTERNAL_SERVER_ERROR, diff --git a/api/src/user/dto/create-user.dto.ts b/api/src/user/dto/create-user.dto.ts index 9190090..3ed9592 100644 --- a/api/src/user/dto/create-user.dto.ts +++ b/api/src/user/dto/create-user.dto.ts @@ -1,3 +1,3 @@ -import { User } from './user.dto'; +import { UserDTO } from './user.dto'; -export class CreateUser extends User {} +export class CreateUserDTO extends UserDTO {} diff --git a/api/src/user/dto/update-user.dto.ts b/api/src/user/dto/update-user.dto.ts new file mode 100644 index 0000000..f4c017e --- /dev/null +++ b/api/src/user/dto/update-user.dto.ts @@ -0,0 +1,3 @@ +import { UserDTO } from './user.dto'; + +export class UpdateUserDTO extends UserDTO {} diff --git a/api/src/user/dto/user.dto.ts b/api/src/user/dto/user.dto.ts index 6701000..8d43540 100644 --- a/api/src/user/dto/user.dto.ts +++ b/api/src/user/dto/user.dto.ts @@ -4,7 +4,7 @@ export enum UserType { 'STUDENT', 'USER', } -export class User { +export class UserDTO { @ApiProperty({ minLength: 9, maxLength: 9, diff --git a/api/src/user/user.controller.ts b/api/src/user/user.controller.ts index 1df3f96..0a5bfda 100644 --- a/api/src/user/user.controller.ts +++ b/api/src/user/user.controller.ts @@ -8,9 +8,10 @@ import { Delete, } from '@nestjs/common'; import { UserService } from './user.service'; -import { CreateUser } from './dto/create-user.dto'; +import { CreateUserDTO } from './dto/create-user.dto'; import { UserType } from './dto/user.dto'; import { ApiTags } from '@nestjs/swagger'; +import { UpdateUserDTO } from './dto/update-user.dto'; @ApiTags('user') @Controller('/api/user') @@ -18,7 +19,7 @@ export class UserController { constructor(private readonly userService: UserService) {} /* WORKING implementation */ @Post() - async create(@Body() createUserDto: CreateUser) { + async create(@Body() createUserDto: CreateUserDTO) { return await this.userService.create(createUserDto); } /* WORKING implementation */ @@ -44,4 +45,13 @@ export class UserController { findOne(@Param('lsu_id') lsu_id: number) { return this.userService.findOne(lsu_id); } + + /* WORKING implementation */ + @Put(':lsu_id') + update( + @Param('lsu_id') lsu_id: number, + @Body() updateUserDTO: UpdateUserDTO, + ) { + return this.userService.update(lsu_id, updateUserDTO); + } } diff --git a/api/src/user/user.service.ts b/api/src/user/user.service.ts index 4ad1efc..a7d7ad5 100644 --- a/api/src/user/user.service.ts +++ b/api/src/user/user.service.ts @@ -7,15 +7,16 @@ import { } from '@nestjs/common'; import { Pool, QueryConfig } from 'pg'; import { PG_CONNECTION } from 'src/connection'; -import { CreateUser } from './dto/create-user.dto'; -import { User, UserType } from './dto/user.dto'; +import { CreateUserDTO } from './dto/create-user.dto'; +import { UpdateUserDTO } from './dto/update-user.dto'; +import { UserDTO, UserType } from './dto/user.dto'; @Injectable() export class UserService { constructor(@Inject(PG_CONNECTION) private connection: Pool) {} /* WORKING implementation */ - async create(createUser: CreateUser) { + async create(createUserDTO: CreateUserDTO) { const { lsu_id, email, @@ -24,7 +25,7 @@ export class UserService { phone_number, department, admin, - } = createUser; + } = createUserDTO; const findQuery: QueryConfig = { name: 'select_user_by_id_or_email', @@ -79,7 +80,7 @@ export class UserService { ], }; try { - const res = await this.connection.query(insertQuery); + const res = await this.connection.query(insertQuery); return res.rows[0]; } catch (error) { throw new HttpException( @@ -113,7 +114,7 @@ export class UserService { } try { - const queryRes = await this.connection.query(query); + const queryRes = await this.connection.query(query); /* If no users found return empty array */ if (queryRes.rows.length < 1) { return []; @@ -136,7 +137,7 @@ export class UserService { text: 'SELECT * FROM "user" WHERE "lsu_id" = $1', }; try { - const queryRes = await this.connection.query(query, [lsu_id]); + const queryRes = await this.connection.query(query, [lsu_id]); /* If customer not found return empty object */ if (queryRes.rows.length < 1) { return {}; @@ -153,4 +154,83 @@ export class UserService { ); } } + + /* WORKING implementation */ + async update(old_lsu_id: number, updateUserDTO: UpdateUserDTO) { + const { + lsu_id, + email, + first_name, + last_name, + phone_number, + department, + admin, + } = updateUserDTO; + + /* Find user by old_lsu_id supplied in route i.e. /api/user/{lsu_id} */ + const findQuery: QueryConfig = { + name: 'select_user_by_id', + text: 'SELECT lsu_id FROM "user" WHERE lsu_id = $1', + values: [old_lsu_id], + }; + try { + const res = await this.connection.query(findQuery); + + /* Test to see if user exists */ + if (res.rows.length < 1) { + /*Throw custom error to be handled in catch*/ + throw new Error('BAD_REQUEST'); + } + } catch (error) { + /* Catch custom error if user does not exist + Then throw Bad Request HttpException + */ + if (error.message === 'BAD_REQUEST') { + throw new HttpException( + { + error: `User with lsu_id ${lsu_id} DOES NOT EXIST`, + }, + HttpStatus.BAD_REQUEST, + ); + } + /* Catch postgres error and throw Internal Error HttpException */ + throw new HttpException( + { + message: findQuery, + error: error, + }, + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + + /* Update user in db */ + const updateQuery: QueryConfig = { + name: 'update_user', + + text: + 'UPDATE "user" SET lsu_id = $1, email = $2, first_name = $3, last_name = $4, phone_number = $5, department = $6, admin = $7 WHERE lsu_id = $8 RETURNING *', + values: [ + lsu_id, + email, + first_name, + last_name, + phone_number, + department, + admin, + old_lsu_id, + ], + }; + try { + const res = await this.connection.query(updateQuery); + return res.rows[0]; + } catch (error) { + throw new HttpException( + { + message: updateQuery, + error: error, + }, + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } } diff --git a/api/src/util.ts b/api/src/util.ts new file mode 100644 index 0000000..1150951 --- /dev/null +++ b/api/src/util.ts @@ -0,0 +1,22 @@ +/** + * Helper method for createTicket to get current date in SQL format + * + * @author KooiInc + * (https://stackoverflow.com/users/58186/kooiinc) + * + * @adapted from https://stackoverflow.com/questions/10632346/how-to-format-a-date-in-mm-dd-yyyy-hhmmss-format-in-javascript + * @returns Date (YYYY-MM-DD HH:MM:SS) + */ +export const createDate = () => { + //@ts-ignore + Number.prototype.padLeft = function (base, chr) { + var len = String(base || 10).length - String(this).length + 1; + return len > 0 ? new Array(len).join(chr || '0') + this : this; + }; + const d = new Date(Date.now()); + const date = + [d.getFullYear(), d.getMonth() + 1, d.getDate()].join('-') + + ' ' + + [d.getHours(), d.getMinutes(), d.getSeconds()].join(':'); + return date; +}; diff --git a/schema.sql b/schema.sql index a269e65..376f41a 100644 --- a/schema.sql +++ b/schema.sql @@ -26,7 +26,8 @@ create table if not exists ticket status varchar(20) not null, problem_category varchar(50) not null, description varchar(500), - core_issue varchar(50) + core_issue varchar(50), + submission_date timestamp without time zone ); alter table ticket owner to brvuirrqqcjzsb; @@ -67,7 +68,9 @@ create table if not exists assignment ticket_id integer not null constraint assignment_ticket_ticket_id_fk references ticket - on update cascade on delete cascade + on update cascade on delete cascade, + assigned_date timestamp without time zone + ); alter table assignment owner to brvuirrqqcjzsb;