diff --git a/api/README.md b/api/README.md index 60982be..07b3655 100644 --- a/api/README.md +++ b/api/README.md @@ -140,6 +140,7 @@ Get all users ``` GET /api​/user +req: none res: body: {UserDTO[]} ``` @@ -147,6 +148,7 @@ Get all users who are admins ``` GET /api​/user/admin +req: none res: body: {UserDTO[]} ``` @@ -154,6 +156,7 @@ Get all users who are not admins ``` GET /api​/user/student +req: none res: body: {UserDTO[]} ``` @@ -161,6 +164,7 @@ Get user by lsu_id ``` GET /api​/user/{lsu_id} +req: none res: body: {UserDTO} ``` @@ -178,6 +182,7 @@ Get all assignments ``` GET /api/assignment +req: none res: body: {AssignmentDTO} ``` @@ -185,6 +190,7 @@ Get all assignments assigned to admin by lsu_id ``` GET /api/assignment/user/{lsu_id} +req: none res: body: {AssignmentDTO[]} ``` @@ -192,6 +198,7 @@ Get all assignments assigned to ticket by ticket_id ``` GET /api/assignment/ticket/{ticket_id} +req: none res: body: {AssignmentDTO[]} ``` @@ -199,6 +206,7 @@ Get one assignment by assignment_id ``` GET /api/assignment/{assignment_id} +req: none res: body: {AssignmentDTO} ``` @@ -212,4 +220,42 @@ res: body: {AssignmentDTO} ## Work -TODO +Get all work + +``` +GET /api/work +req: none +res: body: {WorkDTO[]} +``` + +Get all work by ticket_id + +``` +GET /api/work/ticket/{ticket_id} +req: none +res: body: {WorkDTO[]} +``` + +Get all work by lsu_id + +``` +GET /api/work/user/{lsu_id} +req: none +res: body: {WorkDTO[]} +``` + +Get work by work_id + +``` +GET /api/work/{work_id} +req: none +res: body: {WorkDTO} +``` + +Post new work + +``` +POST /api/work +req: body: {CreateWorkDTO} +res: body: {WorkDTO} +``` diff --git a/api/src/app.module.ts b/api/src/app.module.ts index 49dab65..4d6ac3a 100644 --- a/api/src/app.module.ts +++ b/api/src/app.module.ts @@ -4,7 +4,7 @@ import { join } from 'path'; import { AppController } from './app.controller'; import { AppService } from './app.service'; import { TicketModule } from './ticket/ticket.module'; -import { TicketWorkModule } from './work/ticketwork.module'; +import { WorkModule } from './work/work.module'; import { AssignmentModule } from './assignment/assignment.module.'; import { UserModule } from './user/uer.module'; @@ -40,7 +40,7 @@ const configImports = (modules: ModuleMetadata['imports']) => { @Module({ imports: configImports([ TicketModule, - TicketWorkModule, + WorkModule, AssignmentModule, UserModule, ]), diff --git a/api/src/ticket/dto/create-ticket.dto.ts b/api/src/ticket/dto/create-ticket.dto.ts index a8adb5f..506c558 100644 --- a/api/src/ticket/dto/create-ticket.dto.ts +++ b/api/src/ticket/dto/create-ticket.dto.ts @@ -5,4 +5,5 @@ export class CreateTicketDTO extends Omit(TicketDTO, [ 'ticket_id', 'status', 'submission_date', + 'notes', ]) {} diff --git a/api/src/ticket/dto/device.dto.ts b/api/src/ticket/dto/device.dto.ts index 022de62..6d0c3d7 100644 --- a/api/src/ticket/dto/device.dto.ts +++ b/api/src/ticket/dto/device.dto.ts @@ -18,8 +18,9 @@ export class DeviceDTO { description: 'physical hardware part that is damaged', example: 'screen', nullable: true, + default: null, }) - component: string; + component: string | null; @ApiProperty({ description: 'manufacturer of device', example: 'dell' }) manufacturer: string; diff --git a/api/src/ticket/dto/ticket.dto.ts b/api/src/ticket/dto/ticket.dto.ts index 2048e6c..f2844a9 100644 --- a/api/src/ticket/dto/ticket.dto.ts +++ b/api/src/ticket/dto/ticket.dto.ts @@ -38,7 +38,6 @@ export class TicketDTO { }) description: string; - // Not sure of difference between description and problem @ApiProperty({ description: 'label of problem student is facing', example: 'computer slowdown', @@ -50,4 +49,13 @@ export class TicketDTO { example: '2020-07-21 12:44:22', }) submission_date: string; + + @ApiProperty({ + description: 'notes for admin to add', + example: 'TODO: some note', + maxLength: 500, + default: null, + nullable: true, + }) + notes: string | null; } diff --git a/api/src/ticket/dto/update-ticket.dto.ts b/api/src/ticket/dto/update-ticket.dto.ts index 0da1bdd..fdb46b4 100644 --- a/api/src/ticket/dto/update-ticket.dto.ts +++ b/api/src/ticket/dto/update-ticket.dto.ts @@ -1,7 +1,5 @@ -import { PartialType } from '@nestjs/swagger'; -import { CreateTicketDTO } from './create-ticket.dto'; -import { TicketDTO } from './ticket.dto'; import { OmitType as Omit } from '@nestjs/swagger'; +import { TicketDTO } from './ticket.dto'; export class UpdateTicketDto extends Omit(TicketDTO, [ 'ticket_id', diff --git a/api/src/ticket/ticket.service.ts b/api/src/ticket/ticket.service.ts index 50ca0ff..12e9304 100644 --- a/api/src/ticket/ticket.service.ts +++ b/api/src/ticket/ticket.service.ts @@ -502,17 +502,19 @@ export class TicketService { problem_category, status, priority, + notes, } = updateTicket; const updateTicketQuery = { name: 'update_ticket', text: - 'UPDATE ticket SET core_issue = $1, description = $2, problem_category = $3, status = $4, priority = $5 WHERE ticket_id = $6 RETURNING *', + 'UPDATE ticket SET core_issue = $1, description = $2, problem_category = $3, status = $4, priority = $5, notes = $6 WHERE ticket_id = $7 RETURNING *', values: [ core_issue, description, problem_category, status, priority, + notes, ticket_id, ], }; diff --git a/api/src/work/dto/create-ticketwork.dto.ts b/api/src/work/dto/create-ticketwork.dto.ts deleted file mode 100644 index 1644ef2..0000000 --- a/api/src/work/dto/create-ticketwork.dto.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { ApiProperty } from '@nestjs/swagger'; - -export class CreateTicketWorkDto { - @ApiProperty({ minLength: 3 }) - issue: string; - - @ApiProperty({ minLength: 3 }) - component: string; - - @ApiProperty({}) - description: number; - - @ApiProperty({}) - starttime: Date; - - @ApiProperty({}) - endtime: Date; -} diff --git a/api/src/work/dto/create-work.dto.ts b/api/src/work/dto/create-work.dto.ts new file mode 100644 index 0000000..1b6919e --- /dev/null +++ b/api/src/work/dto/create-work.dto.ts @@ -0,0 +1,4 @@ +import { OmitType as Omit } from '@nestjs/swagger'; +import { WorkDTO } from './work.dto'; + +export class CreateWorkDTO extends Omit(WorkDTO, ['work_id']) {} diff --git a/api/src/work/dto/update-ticketwork.dto.ts b/api/src/work/dto/update-ticketwork.dto.ts deleted file mode 100644 index 86d1f9c..0000000 --- a/api/src/work/dto/update-ticketwork.dto.ts +++ /dev/null @@ -1,7 +0,0 @@ -export class UpdateTicketWorkDto { - issue: string; - component: string; - description: string; - starttime: Date; - endtime: Date; -} diff --git a/api/src/work/dto/work.dto.ts b/api/src/work/dto/work.dto.ts new file mode 100644 index 0000000..62540c8 --- /dev/null +++ b/api/src/work/dto/work.dto.ts @@ -0,0 +1,40 @@ +import { ApiProperty } from '@nestjs/swagger'; + +export enum WorkType { + 'LSU_ID', + 'TICKET_ID', +} + +export class WorkDTO { + @ApiProperty({ + readOnly: true, + description: 'serially generated id for work', + }) + work_id: number; + + @ApiProperty({ + description: 'id that references ticket', + example: 1, + }) + ticket_id: number; + + @ApiProperty({ + minLength: 9, + maxLength: 9, + description: '9-digit number starting with 89', + example: 897584512, + }) + lsu_id: number; + + @ApiProperty({ + description: 'YYYY-MM-DD HH:MM:SS', + example: '2020-07-21 12:44:22', + }) + start_datetime: string; + + @ApiProperty({ + description: 'YYYY-MM-DD HH:MM:SS', + example: '2020-07-21 12:44:22', + }) + end_datetime: string; +} diff --git a/api/src/work/entities/ticketwork.entity.ts b/api/src/work/entities/ticketwork.entity.ts deleted file mode 100644 index 12679e2..0000000 --- a/api/src/work/entities/ticketwork.entity.ts +++ /dev/null @@ -1,7 +0,0 @@ -export class TicketWork { - issue: string; - component: string; - description: string; - starttime: Date; - endtime: Date; -} diff --git a/api/src/work/ticketwork.controller.ts b/api/src/work/ticketwork.controller.ts deleted file mode 100644 index 814418a..0000000 --- a/api/src/work/ticketwork.controller.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { - Controller, - Get, - Post, - Body, - Put, - Param, - Delete, -} from '@nestjs/common'; -import { TicketWorkService } from './ticketwork.service'; -import { CreateTicketWorkDto } from './dto/create-ticketwork.dto'; -import { UpdateTicketWorkDto } from './dto/update-ticketwork.dto'; -import { ApiTags } from '@nestjs/swagger'; - -@ApiTags('ticket') -@Controller('/api/ticket/work') -export class TicketWorkController { - constructor(private readonly ticketWorkService: TicketWorkService) {} - /* Working Implementation */ - @Post() - async create(@Body() createTicketWorkDto: CreateTicketWorkDto) { - return await this.ticketWorkService.create(createTicketWorkDto); - } - /* TODO */ - @Get() - findAll() { - return this.ticketWorkService.findAll(); - } - /* TODO */ - @Get(':id') - findOne(@Param('id') id: string) { - return this.ticketWorkService.findOne(+id); - } - /* TODO */ - @Put(':id') - update(@Param('id') id: string, @Body() updateTicketWorkDto: UpdateTicketWorkDto) { - return this.ticketWorkService.update(+id, updateTicketWorkDto); - } - /* TODO */ - @Delete(':id') - remove(@Param('id') id: string) { - return this.ticketWorkService.remove(+id); - } -} diff --git a/api/src/work/ticketwork.module.ts b/api/src/work/ticketwork.module.ts deleted file mode 100644 index 8756668..0000000 --- a/api/src/work/ticketwork.module.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { Module } from '@nestjs/common'; -import { TicketWorkService } from './ticketwork.service'; -import { TicketWorkController } from './ticketwork.controller'; -import { DbModule } from 'src/db/db.module'; - -@Module({ - imports:[DbModule], - controllers: [TicketWorkController], - providers: [TicketWorkService] -}) -export class TicketWorkModule {} diff --git a/api/src/work/ticketwork.service.ts b/api/src/work/ticketwork.service.ts deleted file mode 100644 index 5d75ba7..0000000 --- a/api/src/work/ticketwork.service.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { HttpException, HttpStatus, Inject, Injectable } from '@nestjs/common'; -import { Pool } from 'pg'; -import { PG_CONNECTION } from 'src/connection'; -import { CreateTicketWorkDto } from './dto/create-ticketwork.dto'; -import { UpdateTicketWorkDto } from './dto/update-ticketwork.dto'; -import { TicketWork } from './entities/ticketwork.entity'; - -/* TODO: - - Implement remaining methods - - Find solution to abstract SQL -*/ -@Injectable() -export class TicketWorkService { - constructor(@Inject(PG_CONNECTION) private connection: Pool) {} - - async create(createTicketWorkDto: CreateTicketWorkDto) { - const { - issue, - component, - description, - starttime, - endtime - } = createTicketWorkDto; - - /* Insert new user into db - TODO: implement error handling for pg request - */ - const text = - 'INSERT INTO ticketwork(issue, component, description, starttime, endtime) VALUES ($1, $2, $3, $4, $5) RETURNING *'; - const values = [issue, component, description, starttime, endtime]; - const res = (await this.connection.query(text, values)).rows[0]; - - /* Return user object without password */ - const ticketwork = new TicketWork(); - ticketwork.issue = res.issue; - ticketwork.component = res.component; - ticketwork.description = res.description; - ticketwork.starttime = res.starttime; - ticketwork.endtime = res.endtime; - return ticketwork; - } - /* TODO */ - findAll() { - return `This action returns all user`; - } - /* TODO */ - findOne(id: number) { - return `This action returns a #${id} user`; - } - /* TODO */ - update(id: number, updateTicketWorkDto: UpdateTicketWorkDto) { - return `This action updates a #${id} user`; - } - /* TODO */ - remove(id: number) { - return `This action removes a #${id} user`; - } -} diff --git a/api/src/work/work.controller.ts b/api/src/work/work.controller.ts new file mode 100644 index 0000000..8ee2c93 --- /dev/null +++ b/api/src/work/work.controller.ts @@ -0,0 +1,41 @@ +import { Body, Controller, Get, Param, Post } from '@nestjs/common'; +import { ApiTags } from '@nestjs/swagger'; +import { CreateWorkDTO } from './dto/create-work.dto'; +import { WorkType } from './dto/work.dto'; +import { WorkService } from './work.service'; + +@ApiTags('work') +@Controller('/api/work') +export class WorkController { + constructor(private readonly workService: WorkService) {} + + /* Working Implementation */ + @Post() + async create(@Body() createWorkDTO: CreateWorkDTO) { + return await this.workService.create(createWorkDTO); + } + + /* Working Implementation */ + @Get() + findAll() { + return this.workService.findAll(); + } + + /* Working Implementation */ + @Get('ticket/:ticket_id') + findAllByTicketId(@Param('ticket_id') ticket_id: number) { + return this.workService.findAllById(WorkType.TICKET_ID, ticket_id); + } + + /* Working Implementation */ + @Get('user/:lsu_id') + findAllByLsuId(@Param('lsu_id') lsu_id: number) { + return this.workService.findAllById(WorkType.LSU_ID, lsu_id); + } + + /* Working Implementation */ + @Get(':work_id') + findOne(@Param('work_id') id: number) { + return this.workService.findOne(+id); + } +} diff --git a/api/src/work/work.module.ts b/api/src/work/work.module.ts new file mode 100644 index 0000000..518ecc1 --- /dev/null +++ b/api/src/work/work.module.ts @@ -0,0 +1,11 @@ +import { Module } from '@nestjs/common'; +import { WorkService } from './work.service'; +import { WorkController } from './work.controller'; +import { DbModule } from 'src/db/db.module'; + +@Module({ + imports: [DbModule], + controllers: [WorkController], + providers: [WorkService], +}) +export class WorkModule {} diff --git a/api/src/work/work.service.ts b/api/src/work/work.service.ts new file mode 100644 index 0000000..7b504d8 --- /dev/null +++ b/api/src/work/work.service.ts @@ -0,0 +1,140 @@ +import { HttpException, HttpStatus, Inject, Injectable } from '@nestjs/common'; +import { Pool, QueryConfig } from 'pg'; +import { PG_CONNECTION } from 'src/connection'; +import { CreateWorkDTO } from './dto/create-work.dto'; +import { WorkDTO, WorkType } from './dto/work.dto'; + +/* TODO: + - Implement remaining methods + - Find solution to abstract SQL +*/ +@Injectable() +export class WorkService { + constructor(@Inject(PG_CONNECTION) private connection: Pool) {} + + async create(createTicketWorkDto: CreateWorkDTO) { + const { + lsu_id, + ticket_id, + start_datetime, + end_datetime, + } = createTicketWorkDto; + + /*Insert work into db*/ + const query: QueryConfig = { + name: 'insert_work', + text: + 'INSERT INTO work(lsu_id, ticket_id, start_datetime, end_datetime) VALUES ($1, $2, $3, $4) RETURNING *', + values: [lsu_id, ticket_id, start_datetime, end_datetime], + }; + try { + const res = await this.connection.query(query); + return res.rows[0]; + } catch (error) { + throw new HttpException( + { + query: query, + error: error, + }, + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + + /* TODO */ + async findAll() { + const query: QueryConfig = { + name: 'find_all_work', + text: 'SELECT * FROM work', + }; + try { + const res = await this.connection.query(query); + if (res.rows.length < 1) { + return []; + } + return res.rows; + } catch (error) { + throw new HttpException( + { + query: query, + error: error, + }, + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + + /* TODO */ + async findOne(work_id: number) { + const query: QueryConfig = { + name: 'find_work_by_work_id', + text: 'SELECT * FROM work WHERE work_id = $1', + values: [work_id], + }; + try { + const res = await this.connection.query(query); + if (res.rows.length < 1) { + throw new Error('BAD_REQUEST'); + } + return res.rows[0]; + } catch (error) { + if (error.message === 'BAD_REQUEST') { + throw new HttpException( + { + query: query, + error: `No work exists with work_id: ${query.values[0]}`, + }, + HttpStatus.BAD_REQUEST, + ); + } + throw new HttpException( + { + query: query, + error: error, + }, + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + async findAllById(type: WorkType, id: number) { + let query: QueryConfig; + switch (type) { + case WorkType.LSU_ID: + query = { + name: 'find_all_work_by_lsu_id', + text: 'SELECT * from work WHERE lsu_id = $1', + values: [id], + }; + break; + case WorkType.TICKET_ID: + query = { + name: 'find_all_work_by_ticket_id', + text: 'SELECT * from work WHERE ticket_id = $1', + values: [id], + }; + break; + default: + throw new HttpException( + { + error: 'Switch statement in work.service.findAllById failed', + }, + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + try { + const res = await this.connection.query(query); + if (res.rows.length < 1) { + return []; + } + return res.rows; + } catch (error) { + throw new HttpException( + { + query: query, + error: error, + }, + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } +}