From 4112d8738284af7453a11d480f07e12d3908b442 Mon Sep 17 00:00:00 2001 From: Alex Date: Sat, 28 Nov 2020 05:41:34 -0600 Subject: [PATCH 1/6] created new combined dto --- api/src/ticket/dto/combined.dto.ts | 8 ++++++++ api/src/ticket/dto/create-combined.dto.ts | 14 ++++++++++++++ api/src/ticket/ticket.controller.ts | 20 ++++++++++---------- 3 files changed, 32 insertions(+), 10 deletions(-) create mode 100644 api/src/ticket/dto/combined.dto.ts create mode 100644 api/src/ticket/dto/create-combined.dto.ts diff --git a/api/src/ticket/dto/combined.dto.ts b/api/src/ticket/dto/combined.dto.ts new file mode 100644 index 0000000..50d1870 --- /dev/null +++ b/api/src/ticket/dto/combined.dto.ts @@ -0,0 +1,8 @@ +import { IntersectionType } from '@nestjs/swagger'; +import { Ticket } from './ticket.dto'; +import { User } from 'src/user/dto/user.dto'; +import { Device } from './device.dto'; + +class TicketAndDevice extends IntersectionType(Ticket, Device) {} + +export class Combined extends IntersectionType(TicketAndDevice, User) {} diff --git a/api/src/ticket/dto/create-combined.dto.ts b/api/src/ticket/dto/create-combined.dto.ts new file mode 100644 index 0000000..2abca50 --- /dev/null +++ b/api/src/ticket/dto/create-combined.dto.ts @@ -0,0 +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'; + +class createTicketAndDevice extends IntersectionType( + CreateTicket, + Omit(CreateDevice, ['ticket_id']), +) {} + +export class CreateCombined extends IntersectionType( + createTicketAndDevice, + Omit(CreateUser, ['admin']), +) {} diff --git a/api/src/ticket/ticket.controller.ts b/api/src/ticket/ticket.controller.ts index ebece71..2521d3e 100644 --- a/api/src/ticket/ticket.controller.ts +++ b/api/src/ticket/ticket.controller.ts @@ -1,17 +1,17 @@ import { + Body, Controller, Get, + Logger, + Param, Post, - Body, Put, - Param, - Logger, } from '@nestjs/common'; -import { TicketService } from './ticket.service'; -import { CreateTicket } from './dto/create-ticket.dto'; -import { UpdateTicketDto } from './dto/update-ticket.dto'; import { ApiTags } from '@nestjs/swagger'; -import { Ticket, TicketType } from './dto/ticket.dto'; +import { CreateCombined } from './dto/create-combined.dto'; +import { TicketType } from './dto/ticket.dto'; +import { UpdateTicketDto } from './dto/update-ticket.dto'; +import { TicketService } from './ticket.service'; @ApiTags('ticket') @Controller('/api/ticket') @@ -20,19 +20,19 @@ export class TicketController { /* TODO: test implementation */ @Post() - create(@Body() createTicketDto: CreateTicket) { + create(@Body() createCombined: CreateCombined) { Logger.log( { req: { http: 'POST /api/ticket', params: 'none', - body: createTicketDto, + body: createCombined, }, }, 'TicketController.create', false, ); - return this.ticketService.create(createTicketDto); + return this.ticketService.create(createCombined); } /* Working implementation */ From fef7abd89fdac99db486cef79d0540c305f78d98 Mon Sep 17 00:00:00 2001 From: Alex Date: Sat, 28 Nov 2020 05:42:04 -0600 Subject: [PATCH 2/6] created device dto --- api/src/ticket/dto/create-device.dto.ts | 4 +++ api/src/ticket/dto/device.dto.ts | 36 +++++++++++++++++++++++++ 2 files changed, 40 insertions(+) create mode 100644 api/src/ticket/dto/create-device.dto.ts create mode 100644 api/src/ticket/dto/device.dto.ts diff --git a/api/src/ticket/dto/create-device.dto.ts b/api/src/ticket/dto/create-device.dto.ts new file mode 100644 index 0000000..490c3f0 --- /dev/null +++ b/api/src/ticket/dto/create-device.dto.ts @@ -0,0 +1,4 @@ +import { OmitType as Omit } from '@nestjs/swagger'; +import { Device } from './device.dto'; + +export class CreateDevice extends Omit(Device, ['device_id']) {} diff --git a/api/src/ticket/dto/device.dto.ts b/api/src/ticket/dto/device.dto.ts new file mode 100644 index 0000000..fcba3f0 --- /dev/null +++ b/api/src/ticket/dto/device.dto.ts @@ -0,0 +1,36 @@ +import { ApiProperty } from '@nestjs/swagger'; + +export class Device { + @ApiProperty({ description: 'id of device', example: 1 }) + device_id: number; + + @ApiProperty({ description: 'id of associated ticket', example: 1 }) + ticket_id: number; + + @ApiProperty({ + minLength: 1, + description: 'model of device', + example: '15', + }) + model: string; + + @ApiProperty({ + description: 'physical hardware part that is damaged', + example: 'screen', + nullable: true, + }) + component: string; + + @ApiProperty({ description: 'manufacturer of device', example: 'dell' }) + manufacturer: string; + + @ApiProperty({ + minLength: 1, + description: 'operating system', + example: 'windows', + }) + operating_system: string; + + @ApiProperty({ description: 'operating system version', example: 'version1' }) + operating_system_version: string; +} From a87b6d7eed433bf07f6e847753ca79065c9278ac Mon Sep 17 00:00:00 2001 From: Alex Date: Sat, 28 Nov 2020 05:42:20 -0600 Subject: [PATCH 3/6] updated create-ticket dto --- api/src/ticket/dto/create-ticket.dto.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/api/src/ticket/dto/create-ticket.dto.ts b/api/src/ticket/dto/create-ticket.dto.ts index 7774d36..cd893c9 100644 --- a/api/src/ticket/dto/create-ticket.dto.ts +++ b/api/src/ticket/dto/create-ticket.dto.ts @@ -1,8 +1,4 @@ import { Ticket } from './ticket.dto'; import { OmitType as Omit } from '@nestjs/swagger'; -export class CreateTicket extends Omit(Ticket, [ - 'ticket_id', - 'priority', - 'status', -]) {} +export class CreateTicket extends Omit(Ticket, ['ticket_id', 'status']) {} From 29fccc51996d8f4e2115bfcbe310945d182a97ca Mon Sep 17 00:00:00 2001 From: Alex Date: Sat, 28 Nov 2020 05:42:38 -0600 Subject: [PATCH 4/6] removed placeholder comments --- api/src/ticket/dto/ticket.dto.ts | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/api/src/ticket/dto/ticket.dto.ts b/api/src/ticket/dto/ticket.dto.ts index 54d725f..a62c001 100644 --- a/api/src/ticket/dto/ticket.dto.ts +++ b/api/src/ticket/dto/ticket.dto.ts @@ -50,23 +50,3 @@ export class Ticket { }) submission_date: string; } -// @ApiProperty({ description: 'manufacturer of device', example: 'dell' }) -// manufacturer: string; - -// @ApiProperty({ -// minLength: 1, -// description: 'model of device', -// example: '15', -// }) -// model: number; - -// @ApiProperty({ -// minLength: 1, -// description: 'operating system', -// example: 'windows', -// }) -// os: string; - -// // Not sure of example data for os version -// @ApiProperty({ description: 'operating system version', example: 'version1' }) -// version: string; From 6b8167021db5ca07804eb623a7874670550df0c2 Mon Sep 17 00:00:00 2001 From: Alex Date: Sat, 28 Nov 2020 05:43:58 -0600 Subject: [PATCH 5/6] updated ticket.service to create/query of users, devices --- api/src/ticket/ticket.service.ts | 231 ++++++++++++++++++++++++++----- 1 file changed, 199 insertions(+), 32 deletions(-) diff --git a/api/src/ticket/ticket.service.ts b/api/src/ticket/ticket.service.ts index 81986c7..cc3c40d 100644 --- a/api/src/ticket/ticket.service.ts +++ b/api/src/ticket/ticket.service.ts @@ -7,7 +7,13 @@ 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 { UpdateTicketDto } from './dto/update-ticket.dto'; @@ -15,40 +21,92 @@ import { UpdateTicketDto } from './dto/update-ticket.dto'; export class TicketService { constructor(@Inject(PG_CONNECTION) private connection: Pool) {} - /* TODO: test implementation */ - async create(createTicketDto: CreateTicket) { + /** + * Queries and inserts user if user does not exists + * + * @param createUser + * @returns User + */ + private async createUser(createUser: CreateUser) { + const { + lsu_id, + email, + first_name, + last_name, + phone_number, + department, + admin, + } = createUser; + + 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 */ + if (res.rows.length > 0) { + return res.rows[0]; + } + } catch (error) { + throw new HttpException( + { query: findQuery, error: error }, + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + + /* Insert new student into db */ + const insertQuery: QueryConfig = { + name: 'insert_user', + + text: + 'INSERT INTO "user" (lsu_id, email, first_name, last_name, phone_number, department, admin) VALUES ($1, $2, $3, $4, $5, $6, $7) RETURNING *', + values: [ + lsu_id, + email, + first_name, + last_name, + phone_number, + department, + admin, + ], + }; + try { + const res = await this.connection.query(insertQuery); + return res.rows[0]; + } catch (error) { + throw new HttpException( + { query: insertQuery, error: error }, + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + + /** + * Inserts ticket into ticket table + * + * @param createTicket + * @returns Ticket + */ + private async createTicket(createTicket: CreateTicket) { const { lsu_id, core_issue, description, problem_category, - } = createTicketDto; - const status = 'OPEN'; - const priority = 1; - /** - * Adapted from: - * https://stackoverflow.com/questions/10632346/how-to-format-a-date-in-mm-dd-yyyy-hhmmss-format-in-javascript - * - * START - */ - //@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 submission_date = - [d.getMonth() + 1, d.getDate(), d.getFullYear()].join('-') + - ' ' + - [d.getHours(), d.getMinutes(), d.getSeconds()].join(':'); + priority, + } = createTicket; - /**END */ + const status = 'OPEN'; + const submission_date = this.createDate(); - /* Insert new ticket into db */ const query: QueryConfig = { name: 'insert_ticket', text: - 'INSERT INTO ticket(lsu_id, core_issues, description, problem_category, status, priority, submission_date) VALUES ($1, $2, $3, $4, $5, $6, $7) RETURNING *', + 'INSERT INTO ticket (lsu_id, core_issue, description, problem_category, status, priority, submission_date) VALUES ($1, $2, $3, $4, $5, $6, $7) RETURNING *', values: [ lsu_id, core_issue, @@ -59,13 +117,50 @@ export class TicketService { submission_date, ], }; - /* Handle db errors */ + + try { + const res = await this.connection.query(query); + return res.rows[0]; + } catch (error) { + throw new HttpException( + { + message: query, + error: error, + }, + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } + } + + /** + * Inserts new device into device table + * @param createDevice + * @returns Device + */ + private async createDevice(createDevice: CreateDevice) { + const { + ticket_id, + manufacturer, + model, + operating_system, + operating_system_version, + } = createDevice; + const query: QueryConfig = { + name: 'insert_device', + text: + 'INSERT INTO device(ticket_id, manufacturer, model, operating_system, operating_system_version) VALUES ($1, $2, $3, $4, $5) RETURNING *', + values: [ + ticket_id, + manufacturer, + model, + operating_system, + operating_system_version, + ], + }; try { - const res = await this.connection.query(query); - /* If no db errors return Ticket object */ + const res = await this.connection.query(query); return res.rows[0]; } catch (error) { - /* If db error return response with error */ throw new HttpException( { message: query, @@ -76,6 +171,73 @@ 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) { + /* Response Accumulator */ + let response: Combined; + + let device: Device; + let ticket: Ticket; + let user: User; + + /* Insert or Retrieve User */ + const createUser: CreateUser = { ...createCombined, admin: false }; // tickets are created by students so admin is false + try { + user = await this.createUser(createUser); + response = { ...response, ...user }; + } catch (error) { + Logger.error(error); + return error; + } + + /* Create new Ticket */ + const createTicket: CreateTicket = { ...createCombined }; + try { + ticket = await this.createTicket(createTicket); + response = { ...response, ...ticket }; + } catch (error) { + Logger.error(error); + return error; + } + + /* Create new Device */ + const createDevice: CreateDevice = { + ...createCombined, + ticket_id: ticket.ticket_id, + }; + try { + device = await this.createDevice(createDevice); + response = { ...response, ...device }; + } catch (error) { + Logger.error(error); + return error; + } + + return response; + } + /* WORKING implementation */ async findAll(param: TicketType | number) { let query: QueryConfig; @@ -83,27 +245,32 @@ export class TicketService { case TicketType.ANY: query = { name: 'find_all_tickets', - text: 'SELECT * FROM ticket', + text: + 'SELECT * FROM ticket LEFT JOIN "user" ON ticket.lsu_id = "user".lsu_id LEFT JOIN "device" ON ticket.ticket_id = device.ticket_id', }; break; case TicketType.OPENED: query = { name: 'find_opened_tickets', - text: 'SELECT * FROM ticket WHERE "status" = \'OPEN\'', + text: + 'SELECT * FROM ticket LEFT JOIN "user" ON ticket.lsu_id = "user".lsu_id LEFT JOIN "device" ON ticket.ticket_id = device.ticket_id WHERE "status" = \'OPEN\'', }; break; case TicketType.CLOSED: query = { name: 'find_closed_tickets', - text: 'SELECT * FROM ticket WHERE "status" = \'CLOSE\'', + text: + 'SELECT * FROM ticket LEFT JOIN "user" ON ticket.lsu_id = "user".lsu_id LEFT JOIN "device" ON ticket.ticket_id = device.ticket_id WHERE "status" = \'CLOSE\'', }; break; default: query = { name: 'find_ticket_by_lsu_id', - text: 'SELECT * FROM ticket WHERE lsu_id = $1', + text: + 'SELECT * FROM ticket LEFT JOIN "user" ON ticket.lsu_id = "user".lsu_id LEFT JOIN "device" ON ticket.ticket_id = device.ticket_id WHERE ticket.lsu_id = $1', values: [param], }; + Logger.log(query); break; } try { From 8162ebb1838dec1a3ffc692c1e8c48dbf14c35b1 Mon Sep 17 00:00:00 2001 From: Alex Date: Sat, 28 Nov 2020 06:05:57 -0600 Subject: [PATCH 6/6] updated README --- api/README.md | 99 ++++++++++++++++++++++++++++----------------------- 1 file changed, 55 insertions(+), 44 deletions(-) diff --git a/api/README.md b/api/README.md index d8ab411..c8821c6 100644 --- a/api/README.md +++ b/api/README.md @@ -65,96 +65,107 @@ client handles all routing on https://lsu-it-support-demo.herokuapp.com excludin # API Routes -## technician +Check https://lsu-it-support-demo.herokuapp.com/api/docs for schema DTO definitions -``` -POST /api​/tech -``` +## Ticket -``` -GET /api​/tech -``` +Create new user, ticket, and device ``` -GET /api​/tech​/{id} -``` +POST /api​/ticket +req: body: {createCombinedDTO} +res: body: {CombinedDTO} ``` -PUT /api​/tech​/{id} -``` + +Get all tickets with user/ticket/device info ``` -DELETE /api​/tech​/{id} +GET /api​/ticket +req: none +res: body: {CombinedDTO[]} ``` -## ticket +Get all opened tickets with user/ticket/device info ``` -POST /api​/ticket +GET /api​/ticket/opened +req: none +res: body: {CombinedDTO[]} ``` +Get all closed tickets with user/ticket/device info + ``` -GET /api​/ticket +GET /api​/ticket/closed +req: none +res: body: {CombinedDTO[]} ``` +Get one ticket by ticket_id with user/ticket/device info + ``` -GET /api​/ticket​/{id} +GET /api​/ticket​/{ticket_id} +req: none +res: body: {CombineDTO} ``` +Update ticket + ``` +/* NOT WORKING */ PUT /api​/ticket​/{id} ``` -``` -POST /api​/ticket​/work -``` +## User -``` -GET /api​/ticket​/work -``` +Create new user ``` -GET /api​/ticket​/work​/{id} +POST /api​/user +req: body: {CreateUserDTO} +res: body: {UserDTO} ``` -``` -PUT /api​/ticket​/work​/{id} -``` +Get all users ``` -DELETE /api​/ticket​/work​/{id} +GET /api​/user +res: body: {UserDTO[]} ``` -``` -POST /api​/ticket​/assign -``` +Get all users who are admins ``` -GET /api​/ticket​/assign +GET /api​/user/admin +res: body: {UserDTO[]} ``` -``` -GET /api​/ticket​/assign​/{id} -``` +Get all users who are not admins ``` -PUT /api​/ticket​/assign​/{id} +GET /api​/user/student +res: body: {UserDTO[]} ``` +Get user by lsu_id + ``` -DELETE /api​/ticket​/assign​/{id} +GET /api​/user/{lsu_id} +res: body: {UserDTO} ``` -## Customer +Update user ``` -POST /api​/customer +/* NOT WORKING */ +PUT /api/user/{lsu_id} ``` -``` -GET /api​/customer -``` +## Assign -``` -GET /api​/customer​/{id} -``` +TODO + +## Work + +TODO