diff --git a/.gitignore b/.gitignore index fd17afc..dfe855f 100644 --- a/.gitignore +++ b/.gitignore @@ -56,3 +56,5 @@ pids report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json /generated/prisma + +!swagger-spec.json diff --git a/docs/swagger/swagger-spec.json b/docs/swagger/swagger-spec.json new file mode 100644 index 0000000..b094ca8 --- /dev/null +++ b/docs/swagger/swagger-spec.json @@ -0,0 +1,2285 @@ +{ + "openapi": "3.0.0", + "paths": { + "/": { + "get": { + "operationId": "AppController_getHello", + "parameters": [], + "responses": { + "200": { + "description": "" + } + }, + "tags": [ + "App" + ] + } + }, + "/health": { + "get": { + "operationId": "HealthController_check", + "parameters": [], + "responses": { + "200": { + "description": "" + } + }, + "tags": [ + "Health" + ] + } + }, + "/oauth-access-tokens": { + "post": { + "operationId": "OauthAccessTokensController_create", + "parameters": [], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateOauthAccessTokenDto" + } + } + } + }, + "responses": { + "201": { + "description": "OAuth access token created", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OAuthAccessTokenEntity" + } + } + } + }, + "400": { + "description": "Incomplete task or invalid data" + } + }, + "security": [ + { + "access-token": [] + } + ], + "summary": "Create OAuth access token", + "tags": [ + "OAuth Access Tokens" + ] + }, + "get": { + "operationId": "OauthAccessTokensController_findAll", + "parameters": [], + "responses": { + "201": { + "description": "List of OAuth access token found", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/OAuthAccessTokenEntity" + } + } + } + } + }, + "400": { + "description": "List of OAuth access token not found" + } + }, + "security": [ + { + "access-token": [] + } + ], + "summary": "Find all OAuth access token", + "tags": [ + "OAuth Access Tokens" + ] + } + }, + "/oauth-access-tokens/{id}": { + "get": { + "operationId": "OauthAccessTokensController_findOne", + "parameters": [ + { + "name": "id", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + } + ], + "responses": { + "201": { + "description": "OAuth access token found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OAuthAccessTokenEntity" + } + } + } + }, + "400": { + "description": "OAuth access token not found" + } + }, + "security": [ + { + "access-token": [] + } + ], + "summary": "Find OAuth access token", + "tags": [ + "OAuth Access Tokens" + ] + }, + "patch": { + "operationId": "OauthAccessTokensController_update", + "parameters": [ + { + "name": "id", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateOauthAccessTokenDto" + } + } + } + }, + "responses": { + "201": { + "description": "OAuth access token updated", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OAuthAccessTokenEntity" + } + } + } + }, + "400": { + "description": "OAuth access token not found" + } + }, + "security": [ + { + "access-token": [] + } + ], + "summary": "Update OAuth access token", + "tags": [ + "OAuth Access Tokens" + ] + }, + "delete": { + "operationId": "OauthAccessTokensController_remove", + "parameters": [ + { + "name": "id", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + } + ], + "responses": { + "201": { + "description": "OAuth access token deleted", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OAuthAccessTokenEntity" + } + } + } + }, + "400": { + "description": "OAuth access token not found" + } + }, + "security": [ + { + "access-token": [] + } + ], + "summary": "Delete OAuth access token", + "tags": [ + "OAuth Access Tokens" + ] + } + }, + "/customers": { + "post": { + "operationId": "CustomersController_create", + "parameters": [], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateCustomerDto" + } + } + } + }, + "responses": { + "201": { + "description": "Customer created", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CustomerEntity" + } + } + } + }, + "400": { + "description": "Invalid task or invalid data" + } + }, + "security": [ + { + "access-token": [] + } + ], + "summary": "Create customer", + "tags": [ + "Customers" + ] + }, + "get": { + "operationId": "CustomersController_findAll", + "parameters": [], + "responses": { + "201": { + "description": "List of customers found", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CustomerEntity" + } + } + } + } + }, + "400": { + "description": "List of customers not found" + } + }, + "security": [ + { + "access-token": [] + } + ], + "summary": "Find all customers", + "tags": [ + "Customers" + ] + } + }, + "/customers/{id}": { + "get": { + "operationId": "CustomersController_findOne", + "parameters": [ + { + "name": "id", + "required": true, + "in": "path", + "schema": { + "type": "number" + } + } + ], + "responses": { + "201": { + "description": "Customer found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CustomerEntity" + } + } + } + }, + "400": { + "description": "Customer not found" + } + }, + "security": [ + { + "access-token": [] + } + ], + "summary": "Find customer", + "tags": [ + "Customers" + ] + }, + "patch": { + "operationId": "CustomersController_update", + "parameters": [ + { + "name": "id", + "required": true, + "in": "path", + "schema": { + "type": "number" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateCustomerDto" + } + } + } + }, + "responses": { + "201": { + "description": "Customer updated", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CustomerEntity" + } + } + } + }, + "400": { + "description": "Customer not found" + } + }, + "security": [ + { + "access-token": [] + } + ], + "summary": "Update customer", + "tags": [ + "Customers" + ] + }, + "delete": { + "operationId": "CustomersController_remove", + "parameters": [ + { + "name": "id", + "required": true, + "in": "path", + "schema": { + "type": "number" + } + } + ], + "responses": { + "201": { + "description": "Customer deleted", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CustomerEntity" + } + } + } + }, + "400": { + "description": "Customer not found" + } + }, + "security": [ + { + "access-token": [] + } + ], + "summary": "Delete customer", + "tags": [ + "Customers" + ] + } + }, + "/employees": { + "post": { + "operationId": "EmployeesController_create", + "parameters": [], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateEmployeeDto" + } + } + } + }, + "responses": { + "201": { + "description": "Employee created", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EmployeeEntity" + } + } + } + }, + "400": { + "description": "Incomplete task or invalid data" + } + }, + "security": [ + { + "access-token": [] + } + ], + "summary": "Create employee", + "tags": [ + "Employees" + ] + }, + "get": { + "operationId": "EmployeesController_findAll", + "parameters": [], + "responses": { + "201": { + "description": "List of employees found", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/EmployeeEntity" + } + } + } + } + }, + "400": { + "description": "List of employees not found" + } + }, + "security": [ + { + "access-token": [] + } + ], + "summary": "Find all employees", + "tags": [ + "Employees" + ] + } + }, + "/employees/{id}": { + "get": { + "operationId": "EmployeesController_findOne", + "parameters": [ + { + "name": "id", + "required": true, + "in": "path", + "schema": { + "type": "number" + } + } + ], + "responses": { + "201": { + "description": "Employee found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EmployeeEntity" + } + } + } + }, + "400": { + "description": "Employee not found" + } + }, + "security": [ + { + "access-token": [] + } + ], + "summary": "Find employee", + "tags": [ + "Employees" + ] + }, + "patch": { + "operationId": "EmployeesController_update", + "parameters": [ + { + "name": "id", + "required": true, + "in": "path", + "schema": { + "type": "number" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateEmployeeDto" + } + } + } + }, + "responses": { + "201": { + "description": "Employee updated", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EmployeeEntity" + } + } + } + }, + "400": { + "description": "Employee not found" + } + }, + "security": [ + { + "access-token": [] + } + ], + "summary": "Update employee", + "tags": [ + "Employees" + ] + }, + "delete": { + "operationId": "EmployeesController_remove", + "parameters": [ + { + "name": "id", + "required": true, + "in": "path", + "schema": { + "type": "number" + } + } + ], + "responses": { + "201": { + "description": "Employee deleted", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EmployeeEntity" + } + } + } + }, + "400": { + "description": "Employee not found" + } + }, + "security": [ + { + "access-token": [] + } + ], + "summary": "Delete employee", + "tags": [ + "Employees" + ] + } + }, + "/leave-requests": { + "post": { + "operationId": "LeaveRequestController_create", + "parameters": [], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateLeaveRequestsDto" + } + } + } + }, + "responses": { + "201": { + "description": "Leave request created", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LeaveRequestEntity" + } + } + } + }, + "400": { + "description": "Incomplete task or invalid data" + } + }, + "security": [ + { + "access-token": [] + } + ], + "summary": "Create leave request", + "tags": [ + "Leave Requests" + ] + }, + "get": { + "operationId": "LeaveRequestController_findAll", + "parameters": [], + "responses": { + "201": { + "description": "List of Leave requests found", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/LeaveRequestEntity" + } + } + } + } + }, + "400": { + "description": "Leave request not found" + } + }, + "security": [ + { + "access-token": [] + } + ], + "summary": "Find all leave request", + "tags": [ + "Leave Requests" + ] + } + }, + "/leave-requests/{id}": { + "get": { + "operationId": "LeaveRequestController_findOne", + "parameters": [ + { + "name": "id", + "required": true, + "in": "path", + "schema": { + "type": "number" + } + } + ], + "responses": { + "201": { + "description": "Leave request found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LeaveRequestEntity" + } + } + } + }, + "400": { + "description": "Leave request not found" + } + }, + "security": [ + { + "access-token": [] + } + ], + "summary": "Find leave request", + "tags": [ + "Leave Requests" + ] + }, + "patch": { + "operationId": "LeaveRequestController_update", + "parameters": [ + { + "name": "id", + "required": true, + "in": "path", + "schema": { + "type": "number" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateLeaveRequestsDto" + } + } + } + }, + "responses": { + "201": { + "description": "Leave request updated", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LeaveRequestEntity" + } + } + } + }, + "400": { + "description": "Leave request not found" + } + }, + "security": [ + { + "access-token": [] + } + ], + "summary": "Update leave request", + "tags": [ + "Leave Requests" + ] + }, + "delete": { + "operationId": "LeaveRequestController_remove", + "parameters": [ + { + "name": "id", + "required": true, + "in": "path", + "schema": { + "type": "number" + } + } + ], + "responses": { + "201": { + "description": "Leave request deleted", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LeaveRequestEntity" + } + } + } + }, + "400": { + "description": "Leave request not found" + } + }, + "security": [ + { + "access-token": [] + } + ], + "summary": "Delete leave request", + "tags": [ + "Leave Requests" + ] + } + }, + "/shift-codes": { + "post": { + "operationId": "ShiftCodesController_create", + "parameters": [], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateShiftCodesDto" + } + } + } + }, + "responses": { + "201": { + "description": "Shift code created", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ShiftCodesEntity" + } + } + } + }, + "400": { + "description": "Incomplete task or invalid data" + } + }, + "security": [ + { + "access-token": [] + } + ], + "summary": "Create shift code", + "tags": [ + "Shift Codes" + ] + }, + "get": { + "operationId": "ShiftCodesController_findAll", + "parameters": [], + "responses": { + "201": { + "description": "List of shift codes found", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ShiftCodesEntity" + } + } + } + } + }, + "400": { + "description": "List of shift codes not found" + } + }, + "security": [ + { + "access-token": [] + } + ], + "summary": "Find all shift codes", + "tags": [ + "Shift Codes" + ] + } + }, + "/shift-codes/{id}": { + "get": { + "operationId": "ShiftCodesController_findOne", + "parameters": [ + { + "name": "id", + "required": true, + "in": "path", + "schema": { + "type": "number" + } + } + ], + "responses": { + "201": { + "description": "Shift code found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ShiftCodesEntity" + } + } + } + }, + "400": { + "description": "Shift code not found" + } + }, + "security": [ + { + "access-token": [] + } + ], + "summary": "Find shift code", + "tags": [ + "Shift Codes" + ] + }, + "patch": { + "operationId": "ShiftCodesController_update", + "parameters": [ + { + "name": "id", + "required": true, + "in": "path", + "schema": { + "type": "number" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateShiftCodesDto" + } + } + } + }, + "responses": { + "201": { + "description": "Shift code updated", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ShiftCodesEntity" + } + } + } + }, + "400": { + "description": "Shift code not found" + } + }, + "security": [ + { + "access-token": [] + } + ], + "summary": "Update shift code", + "tags": [ + "Shift Codes" + ] + }, + "delete": { + "operationId": "ShiftCodesController_remove", + "parameters": [ + { + "name": "id", + "required": true, + "in": "path", + "schema": { + "type": "number" + } + } + ], + "responses": { + "201": { + "description": "Shift code deleted", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ShiftCodesEntity" + } + } + } + }, + "400": { + "description": "Shift code not found" + } + }, + "security": [ + { + "access-token": [] + } + ], + "summary": "Delete shift code", + "tags": [ + "Shift Codes" + ] + } + }, + "/shifts": { + "post": { + "operationId": "ShiftsController_create", + "parameters": [], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateShiftDto" + } + } + } + }, + "responses": { + "201": { + "description": "Shift created", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ShiftEntity" + } + } + } + }, + "400": { + "description": "Incomplete task or invalid data" + } + }, + "security": [ + { + "access-token": [] + } + ], + "summary": "Create shift", + "tags": [ + "Shifts" + ] + }, + "get": { + "operationId": "ShiftsController_findAll", + "parameters": [], + "responses": { + "201": { + "description": "List of shifts found", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ShiftEntity" + } + } + } + } + }, + "400": { + "description": "List of shifts not found" + } + }, + "security": [ + { + "access-token": [] + } + ], + "summary": "Find all shifts", + "tags": [ + "Shifts" + ] + } + }, + "/shifts/{id}": { + "get": { + "operationId": "ShiftsController_findOne", + "parameters": [ + { + "name": "id", + "required": true, + "in": "path", + "schema": { + "type": "number" + } + } + ], + "responses": { + "201": { + "description": "Shift found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ShiftEntity" + } + } + } + }, + "400": { + "description": "Shift not found" + } + }, + "security": [ + { + "access-token": [] + } + ], + "summary": "Find shift", + "tags": [ + "Shifts" + ] + }, + "patch": { + "operationId": "ShiftsController_update", + "parameters": [ + { + "name": "id", + "required": true, + "in": "path", + "schema": { + "type": "number" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateShiftsDto" + } + } + } + }, + "responses": { + "201": { + "description": "Shift updated", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ShiftEntity" + } + } + } + }, + "400": { + "description": "Shift not found" + } + }, + "security": [ + { + "access-token": [] + } + ], + "summary": "Update shift", + "tags": [ + "Shifts" + ] + }, + "delete": { + "operationId": "ShiftsController_remove", + "parameters": [ + { + "name": "id", + "required": true, + "in": "path", + "schema": { + "type": "number" + } + } + ], + "responses": { + "201": { + "description": "Shift deleted", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ShiftEntity" + } + } + } + }, + "400": { + "description": "Shift not found" + } + }, + "security": [ + { + "access-token": [] + } + ], + "summary": "Delete shift", + "tags": [ + "Shifts" + ] + } + }, + "/timesheets": { + "post": { + "operationId": "TimesheetsController_create", + "parameters": [], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateTimesheetDto" + } + } + } + }, + "responses": { + "201": { + "description": "Timesheet created", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TimesheetEntity" + } + } + } + }, + "400": { + "description": "Incomplete task or invalid data" + } + }, + "security": [ + { + "access-token": [] + } + ], + "summary": "Create timesheet", + "tags": [ + "Timesheets" + ] + }, + "get": { + "operationId": "TimesheetsController_findAll", + "parameters": [], + "responses": { + "201": { + "description": "List of timesheet found", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TimesheetEntity" + } + } + } + } + }, + "400": { + "description": "List of timesheets not found" + } + }, + "security": [ + { + "access-token": [] + } + ], + "summary": "Find all timesheets", + "tags": [ + "Timesheets" + ] + } + }, + "/timesheets/{id}": { + "get": { + "operationId": "TimesheetsController_findOne", + "parameters": [ + { + "name": "id", + "required": true, + "in": "path", + "schema": { + "type": "number" + } + } + ], + "responses": { + "201": { + "description": "Timesheet found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TimesheetEntity" + } + } + } + }, + "400": { + "description": "Timesheet not found" + } + }, + "security": [ + { + "access-token": [] + } + ], + "summary": "Find timesheet", + "tags": [ + "Timesheets" + ] + }, + "patch": { + "operationId": "TimesheetsController_update", + "parameters": [ + { + "name": "id", + "required": true, + "in": "path", + "schema": { + "type": "number" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateTimesheetDto" + } + } + } + }, + "responses": { + "201": { + "description": "Timesheet updated", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TimesheetEntity" + } + } + } + }, + "400": { + "description": "Timesheet not found" + } + }, + "security": [ + { + "access-token": [] + } + ], + "summary": "Update timesheet", + "tags": [ + "Timesheets" + ] + }, + "delete": { + "operationId": "TimesheetsController_remove", + "parameters": [ + { + "name": "id", + "required": true, + "in": "path", + "schema": { + "type": "number" + } + } + ], + "responses": { + "201": { + "description": "Timesheet deleted", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TimesheetEntity" + } + } + } + }, + "400": { + "description": "Timesheet not found" + } + }, + "security": [ + { + "access-token": [] + } + ], + "summary": "Delete timesheet", + "tags": [ + "Timesheets" + ] + } + } + }, + "info": { + "title": "Targo_Backend", + "description": "Documentation de l`API REST pour Targo (NestJS + Prisma)", + "version": "1.0", + "contact": {} + }, + "tags": [ + { + "name": "Users", + "description": "" + }, + { + "name": "Employees", + "description": "" + }, + { + "name": "Customers", + "description": "" + }, + { + "name": "Timesheets", + "description": "" + }, + { + "name": "Shifts", + "description": "" + }, + { + "name": "Leave Requests", + "description": "" + }, + { + "name": "Shift Codes", + "description": "" + }, + { + "name": "OAuth Access Tokens", + "description": "" + }, + { + "name": "Authorization", + "description": "" + } + ], + "servers": [], + "components": { + "securitySchemes": { + "access-token": { + "scheme": "bearer", + "bearerFormat": "JWT", + "type": "http", + "name": "Authorization", + "description": "Invalid JWT token", + "in": "header" + } + }, + "schemas": { + "CreateOauthAccessTokenDto": { + "type": "object", + "properties": { + "user_id": { + "type": "string", + "example": "S7A2U8R7O6N6", + "description": "User`s unique identification number" + }, + "application": { + "type": "string", + "example": "app.targo.ca", + "description": "URL in which the access token is used for" + }, + "access_token": { + "type": "string", + "example": "L5O6R4D3/O6F3#T8H4E3&R6I4N6G4S7 ...", + "description": "Access token" + }, + "refresh_token": { + "type": "string", + "example": "Th3731102h1p07Th3R1n92", + "description": "Refresh token" + }, + "access_token_expiry": { + "format": "date-time", + "type": "string", + "example": "25/12/3018", + "description": "Access token`s expiry date" + }, + "refresh_token_expiry": { + "format": "date-time", + "type": "string", + "example": "26/02/3019", + "description": "Refresh token`s expiry date" + }, + "scopes": { + "example": "access tolkiens, email, etc... ", + "description": "scopes of infos linked to the access token", + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "user_id", + "application", + "access_token", + "refresh_token", + "access_token_expiry" + ] + }, + "OAuthAccessTokenEntity": { + "type": "object", + "properties": { + "id": { + "type": "string", + "example": "cklwi0vb70000z2z20q6f19qk", + "description": "Unique ID of an OAuth token (auto-generated)" + }, + "user_id": { + "type": "string", + "example": "0e6e2e1f-b157-4c7c-ae3f-999b3e4f914d", + "description": "UUID User`s unique identification number" + }, + "application": { + "type": "string", + "example": "app.targo.ca", + "description": "URL in which the access token is used for" + }, + "access_token": { + "type": "string", + "example": "L5O6R4D3/O6F3#T8H4E3&R6I4N6G4S7", + "description": "Access token" + }, + "refresh_token": { + "type": "string", + "example": "Th3731102h1p07Th3R1n92", + "description": "Refresh token" + }, + "access_token_expiry": { + "format": "date-time", + "type": "string", + "example": "3018-12-25T00:00:00.000Z", + "description": "Access token`s expiry date" + }, + "refresh_token_expiry": { + "format": "date-time", + "type": "string", + "example": "3019-02-26T00:00:00.000Z", + "description": "Refresh token`s expiry date (optional)" + }, + "scopes": { + "example": [ + "email", + "profile", + "access_tolkiens" + ], + "description": "scopes of infos linked to the access token", + "type": "array", + "items": { + "type": "string" + } + }, + "is_revoked": { + "type": "boolean", + "example": false, + "description": "revoke status" + }, + "created_at": { + "format": "date-time", + "type": "string", + "example": "2025-07-22", + "description": "creation date" + }, + "updated_at": { + "format": "date-time", + "type": "string", + "example": "2025-07-23", + "description": "Latest update (optional)" + } + }, + "required": [ + "id", + "user_id", + "application", + "access_token", + "refresh_token", + "access_token_expiry", + "is_revoked", + "created_at" + ] + }, + "UpdateOauthAccessTokenDto": { + "type": "object", + "properties": { + "user_id": { + "type": "string", + "example": "S7A2U8R7O6N6", + "description": "User`s unique identification number" + }, + "application": { + "type": "string", + "example": "app.targo.ca", + "description": "URL in which the access token is used for" + }, + "access_token": { + "type": "string", + "example": "L5O6R4D3/O6F3#T8H4E3&R6I4N6G4S7 ...", + "description": "Access token" + }, + "refresh_token": { + "type": "string", + "example": "Th3731102h1p07Th3R1n92", + "description": "Refresh token" + }, + "access_token_expiry": { + "format": "date-time", + "type": "string", + "example": "25/12/3018", + "description": "Access token`s expiry date" + }, + "refresh_token_expiry": { + "format": "date-time", + "type": "string", + "example": "26/02/3019", + "description": "Refresh token`s expiry date" + }, + "scopes": { + "example": "access tolkiens, email, etc... ", + "description": "scopes of infos linked to the access token", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "CreateCustomerDto": { + "type": "object", + "properties": { + "first_name": { + "type": "string", + "example": "Gandalf", + "description": "Customer`s first name" + }, + "last_name": { + "type": "string", + "example": "TheGray", + "description": "Customer`s last name" + }, + "email": { + "type": "string", + "example": "you_shall_not_pass@middleEarth.com", + "description": "Customer`s email" + }, + "phone_number": { + "type": "number", + "example": "8436637464", + "description": "Customer`s phone number" + }, + "residence": { + "type": "string", + "example": "1 Ringbearer`s way, Mount Doom city, ME, T1R 1N6 ", + "description": "Customer`s residence" + }, + "invoice_id": { + "type": "number", + "example": "4263253", + "description": "Customer`s invoice number" + } + }, + "required": [ + "first_name", + "last_name", + "email", + "phone_number" + ] + }, + "CustomerEntity": { + "type": "object", + "properties": { + "id": { + "type": "number", + "example": 1, + "description": "Unique ID of a customer(primary-key, auto-incremented)" + }, + "user_id": { + "type": "string", + "example": "0e6e2e1f-b157-4c7c-ae3f-999b3e4f914d", + "description": "UUID of the user linked to that customer" + }, + "email": { + "type": "string", + "example": "you_shall_not_pass@middleEarth.com", + "description": "customer`s email (optional)" + }, + "phone_number": { + "type": "number", + "example": 8436637464, + "description": "customer`s phone number (numbers only)" + }, + "residence": { + "type": "string", + "example": "1 Ringbearer’s way, Mount Doom city, ME, T1R 1N6", + "description": "customer`s residence address (optional)" + }, + "invoice_id": { + "type": "number", + "example": 4263253, + "description": "customer`s invoice number (optionnal, unique)" + } + }, + "required": [ + "id", + "user_id", + "phone_number" + ] + }, + "UpdateCustomerDto": { + "type": "object", + "properties": { + "first_name": { + "type": "string", + "example": "Gandalf", + "description": "Customer`s first name" + }, + "last_name": { + "type": "string", + "example": "TheGray", + "description": "Customer`s last name" + }, + "email": { + "type": "string", + "example": "you_shall_not_pass@middleEarth.com", + "description": "Customer`s email" + }, + "phone_number": { + "type": "number", + "example": "8436637464", + "description": "Customer`s phone number" + }, + "residence": { + "type": "string", + "example": "1 Ringbearer`s way, Mount Doom city, ME, T1R 1N6 ", + "description": "Customer`s residence" + }, + "invoice_id": { + "type": "number", + "example": "4263253", + "description": "Customer`s invoice number" + } + } + }, + "CreateEmployeeDto": { + "type": "object", + "properties": { + "first_name": { + "type": "string", + "example": "Frodo", + "description": "Employee`s first name" + }, + "last_name": { + "type": "string", + "example": "Baggins", + "description": "Employee`s last name" + }, + "email": { + "type": "string", + "example": "i_cant_do_this_sam@targointernet.com", + "description": "Employee`s email" + }, + "phone_number": { + "type": "number", + "example": "82538437464", + "description": "Employee`s phone number" + }, + "residence": { + "type": "string", + "example": "1 Bagshot Row, Hobbiton, The Shire, Middle-earth", + "description": "Employee`s residence" + }, + "external_payroll_id": { + "type": "number", + "example": "BagginsF7464", + "description": "Employee`s payroll id" + }, + "company_code": { + "type": "number", + "example": "335567447", + "description": "Employee`s company code" + }, + "first_work_day": { + "format": "date-time", + "type": "string", + "example": "23/09/3018", + "description": "Employee`s first working day" + }, + "last_work_day": { + "format": "date-time", + "type": "string", + "example": "25/03/3019", + "description": "Employee`s last working day" + } + }, + "required": [ + "first_name", + "last_name", + "email", + "phone_number", + "external_payroll_id", + "company_code", + "first_work_day" + ] + }, + "EmployeeEntity": { + "type": "object", + "properties": { + "id": { + "type": "number", + "example": 1, + "description": "Unique ID of an employee(primary-key, auto-incremented)" + }, + "user_id": { + "type": "string", + "example": "0e6e2e1f-b157-4c7c-ae3f-999b3e4f914d", + "description": "UUID of the user linked to that employee" + }, + "external_payroll_id": { + "type": "number", + "example": 7464, + "description": "external ID for the pay system" + }, + "company_code": { + "type": "number", + "example": 335567447, + "description": "company code" + }, + "first_work_day": { + "format": "date-time", + "type": "string", + "example": "3018-09-23T00:00:00.000Z", + "description": "Employee first day at work" + }, + "last_work_day": { + "format": "date-time", + "type": "string", + "example": "3019-03-25T00:00:00.000Z", + "description": "Employee last day at work" + } + }, + "required": [ + "id", + "user_id", + "external_payroll_id", + "company_code", + "first_work_day" + ] + }, + "UpdateEmployeeDto": { + "type": "object", + "properties": { + "first_name": { + "type": "string", + "example": "Frodo", + "description": "Employee`s first name" + }, + "last_name": { + "type": "string", + "example": "Baggins", + "description": "Employee`s last name" + }, + "email": { + "type": "string", + "example": "i_cant_do_this_sam@targointernet.com", + "description": "Employee`s email" + }, + "phone_number": { + "type": "number", + "example": "82538437464", + "description": "Employee`s phone number" + }, + "residence": { + "type": "string", + "example": "1 Bagshot Row, Hobbiton, The Shire, Middle-earth", + "description": "Employee`s residence" + }, + "external_payroll_id": { + "type": "number", + "example": "BagginsF7464", + "description": "Employee`s payroll id" + }, + "company_code": { + "type": "number", + "example": "335567447", + "description": "Employee`s company code" + }, + "first_work_day": { + "format": "date-time", + "type": "string", + "example": "23/09/3018", + "description": "Employee`s first working day" + }, + "last_work_day": { + "format": "date-time", + "type": "string", + "example": "25/03/3019", + "description": "Employee`s last working day" + } + } + }, + "CreateLeaveRequestsDto": { + "type": "object", + "properties": { + "employee_id": { + "type": "number", + "example": "4655867", + "description": "Employee`s id" + }, + "leave_type": { + "type": "string", + "example": "Sick or Vacation or Unpaid or Bereavement or Parental or Legal", + "description": "type of leave request for an accounting perception" + }, + "start_date_time": { + "format": "date-time", + "type": "string", + "example": "22/06/2463", + "description": "Leave request`s start date" + }, + "end_date_time": { + "format": "date-time", + "type": "string", + "example": "25/03/3019", + "description": "Leave request`s end date" + }, + "comment": { + "type": "string", + "example": "My precious", + "description": "Leave request`s comment" + }, + "approval_status": { + "type": "string", + "example": "True or False or Pending or Denied or Cancelled or Escalated", + "description": "Leave request`s approval status" + } + }, + "required": [ + "employee_id", + "leave_type", + "start_date_time", + "end_date_time", + "comment", + "approval_status" + ] + }, + "LeaveRequestEntity": { + "type": "object", + "properties": { + "id": { + "type": "number", + "example": 1, + "description": "Leave request`s unique id(auto-incremented)" + }, + "employee_id": { + "type": "number", + "example": 42, + "description": "ID of concerned employee" + }, + "leave_type": { + "type": "string", + "example": "SICK", + "enum": [ + "SICK", + "VACATION", + "UNPAID", + "BEREAVEMENT", + "PARENTAL", + "LEGAL" + ], + "description": "type of leave request for an accounting perception" + }, + "start_date_time": { + "format": "date-time", + "type": "string", + "example": "22/06/2463", + "description": "Leave request`s start date" + }, + "end_date_time": { + "format": "date-time", + "type": "string", + "example": "25/03/3019", + "description": "Leave request`s end date (optionnal)" + }, + "comment": { + "type": "string", + "example": "My precious", + "description": "Leave request employee`s comment" + }, + "approval_status": { + "type": "string", + "example": "PENDING", + "enum": [ + "PENDING", + "APPROVED", + "DENIED", + "CANCELLED", + "ESCALATED" + ], + "description": "Leave request`s approval status" + } + }, + "required": [ + "id", + "employee_id", + "leave_type", + "start_date_time", + "comment", + "approval_status" + ] + }, + "UpdateLeaveRequestsDto": { + "type": "object", + "properties": { + "employee_id": { + "type": "number", + "example": "4655867", + "description": "Employee`s id" + }, + "leave_type": { + "type": "string", + "example": "Sick or Vacation or Unpaid or Bereavement or Parental or Legal", + "description": "type of leave request for an accounting perception" + }, + "start_date_time": { + "format": "date-time", + "type": "string", + "example": "22/06/2463", + "description": "Leave request`s start date" + }, + "end_date_time": { + "format": "date-time", + "type": "string", + "example": "25/03/3019", + "description": "Leave request`s end date" + }, + "comment": { + "type": "string", + "example": "My precious", + "description": "Leave request`s comment" + }, + "approval_status": { + "type": "string", + "example": "True or False or Pending or Denied or Cancelled or Escalated", + "description": "Leave request`s approval status" + } + } + }, + "CreateShiftCodesDto": { + "type": "object", + "properties": { + "shift_type": { + "type": "string", + "example": "Regular or Night or Emergency, etc...", + "description": "Type of shifts for an account perception" + }, + "bank_code": { + "type": "string", + "example": "G1, G2, G3, etc...", + "description": "bank`s code related to the type of shift" + } + }, + "required": [ + "shift_type", + "bank_code" + ] + }, + "ShiftCodesEntity": { + "type": "object", + "properties": { + "id": { + "type": "number", + "example": 1, + "description": "Unique ID of a shift-code (auto-generated)" + }, + "shift_type": { + "type": "string", + "example": "Night", + "description": "Type of shifts for an account perception" + }, + "bank_code": { + "type": "string", + "example": "G2", + "description": "bank`s code related to the type of shift" + } + }, + "required": [ + "id", + "shift_type", + "bank_code" + ] + }, + "UpdateShiftCodesDto": { + "type": "object", + "properties": { + "shift_type": { + "type": "string", + "example": "Regular or Night or Emergency, etc...", + "description": "Type of shifts for an account perception" + }, + "bank_code": { + "type": "string", + "example": "G1, G2, G3, etc...", + "description": "bank`s code related to the type of shift" + } + } + }, + "CreateShiftDto": { + "type": "object", + "properties": { + "timesheet_id": { + "type": "number", + "example": "Th3F3110w5h1pX2024", + "description": "ID number for a set timesheet" + }, + "shift_code_id": { + "type": "number", + "example": "0n3R1n962Ru13xX", + "description": "ID number of a shift code (link with shift-codes)" + }, + "date": { + "format": "date-time", + "type": "string", + "example": "20/10/3018", + "description": "Date where the shift takes place" + }, + "start_time": { + "format": "date-time", + "type": "string", + "example": "08:00", + "description": "Start time of the said shift" + }, + "end_time": { + "format": "date-time", + "type": "string", + "example": "17:00", + "description": "End time of the said shift" + } + }, + "required": [ + "timesheet_id", + "shift_code_id", + "date", + "start_time", + "end_time" + ] + }, + "ShiftEntity": { + "type": "object", + "properties": { + "id": { + "type": "number", + "example": 1, + "description": "Unique ID of the shift (auto-generated)" + }, + "timesheet_id": { + "type": "number", + "example": 101, + "description": "ID number for a set timesheet" + }, + "shift_code_id": { + "type": "number", + "example": 7, + "description": "ID number of a shift code (link with shift-codes)" + }, + "date": { + "format": "date-time", + "type": "string", + "example": "3018-10-20T00:00:00.000Z", + "description": "Date where the shift takes place" + }, + "start_time": { + "format": "date-time", + "type": "string", + "example": "3018-10-20T08:00:00.000Z", + "description": "Start time of the said shift" + }, + "end_time": { + "format": "date-time", + "type": "string", + "example": "3018-10-20T17:00:00.000Z", + "description": "End time of the said shift" + } + }, + "required": [ + "id", + "timesheet_id", + "shift_code_id", + "date", + "start_time", + "end_time" + ] + }, + "UpdateShiftsDto": { + "type": "object", + "properties": { + "timesheet_id": { + "type": "number", + "example": "Th3F3110w5h1pX2024", + "description": "ID number for a set timesheet" + }, + "shift_code_id": { + "type": "number", + "example": "0n3R1n962Ru13xX", + "description": "ID number of a shift code (link with shift-codes)" + }, + "date": { + "format": "date-time", + "type": "string", + "example": "20/10/3018", + "description": "Date where the shift takes place" + }, + "start_time": { + "format": "date-time", + "type": "string", + "example": "08:00", + "description": "Start time of the said shift" + }, + "end_time": { + "format": "date-time", + "type": "string", + "example": "17:00", + "description": "End time of the said shift" + } + } + }, + "CreateTimesheetDto": { + "type": "object", + "properties": { + "employee_id": { + "type": "number", + "example": "426433", + "description": "employee`s ID number of linked timsheet" + }, + "is_approved": { + "type": "boolean", + "example": "True or False or Pending or Denied or Cancelled or Escalated", + "description": "Timesheet`s approval status" + } + }, + "required": [ + "employee_id", + "is_approved" + ] + }, + "TimesheetEntity": { + "type": "object", + "properties": { + "id": { + "type": "number", + "example": 1, + "description": "timesheet`s unique ID (auto-generated)" + }, + "employee_id": { + "type": "number", + "example": 426433, + "description": "employee`s ID number of linked timsheet" + }, + "is_approved": { + "type": "boolean", + "example": true, + "description": "Timesheet`s approval status" + } + }, + "required": [ + "id", + "employee_id", + "is_approved" + ] + }, + "UpdateTimesheetDto": { + "type": "object", + "properties": { + "employee_id": { + "type": "number", + "example": "426433", + "description": "employee`s ID number of linked timsheet" + }, + "is_approved": { + "type": "boolean", + "example": "True or False or Pending or Denied or Cancelled or Escalated", + "description": "Timesheet`s approval status" + } + } + } + } + } +} \ No newline at end of file diff --git a/src/app.module.ts b/src/app.module.ts index 18edf62..ec0027b 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -12,6 +12,7 @@ import { LeaveRequestsModule } from './modules/leave-requests/leave-requests.mod import { ShiftCodeModule } from './modules/shift-codes/shift-codes.module'; import { ShiftsModule } from './modules/shifts/shifts.module'; import { TimesheetsModule } from './modules/timesheets/timesheets.module'; +import { AuthenticationModule } from './modules/authentication/auth.module'; @Module({ imports: [ @@ -25,6 +26,7 @@ import { TimesheetsModule } from './modules/timesheets/timesheets.module'; ShiftCodeModule, ShiftsModule, TimesheetsModule, + AuthenticationModule, ], controllers: [AppController, HealthController], providers: [AppService], diff --git a/src/main.ts b/src/main.ts index f11766c..929668a 100644 --- a/src/main.ts +++ b/src/main.ts @@ -5,11 +5,13 @@ import { ValidationPipe } from '@nestjs/common'; import { JwtAuthGuard } from './modules/authentication/guards/jwt-auth.guard'; import { RolesGuard } from './common/guards/roles.guard'; import { OwnershipGuard } from './common/guards/ownership.guard'; +import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'; +import { writeFileSync } from 'fs'; async function bootstrap() { const app = await NestFactory.create(AppModule); - //setup Reflector for Roles() - const reflector = app.get(Reflector); + + const reflector = app.get(Reflector); //setup Reflector for Roles() app.useGlobalPipes( new ValidationPipe({ whitelist: true, transform: true})); @@ -19,6 +21,37 @@ async function bootstrap() { new OwnershipGuard(reflector, app.get(ModuleRef)), //Global use of OwnershipGuard, not implemented yet ); + //swagger config + const config = new DocumentBuilder() + .setTitle('Targo_Backend') + .setDescription('Documentation de l`API REST pour Targo (NestJS + Prisma)') + .setVersion('1.0') + .addBearerAuth({ + type: 'http', + scheme: 'bearer', + bearerFormat: 'JWT', + name: 'Authorization', + description: 'Invalid JWT token', + in: 'header', + }, 'access-token') + .addTag('Users') + .addTag('Employees') + .addTag('Customers') + .addTag('Timesheets') + .addTag('Shifts') + .addTag('Leave Requests') + .addTag('Shift Codes') + .addTag('OAuth Access Tokens') + .addTag('Authorization') + .build(); + + //document builder for swagger docs + const documentFactory = () => SwaggerModule.createDocument(app, config); + const document = documentFactory() + SwaggerModule.setup('api/docs', app, document); + + writeFileSync('./docs/swagger/swagger-spec.json', JSON.stringify(document, null, 2)); + await app.listen(process.env.PORT ?? 3000); } bootstrap(); diff --git a/src/modules/customers/controllers/customers.controller.ts b/src/modules/customers/controllers/customers.controller.ts index 7b32e9b..66f216a 100644 --- a/src/modules/customers/controllers/customers.controller.ts +++ b/src/modules/customers/controllers/customers.controller.ts @@ -1,35 +1,53 @@ -import { Body, Controller, Delete, Get, Param, ParseIntPipe, Patch, Post } from '@nestjs/common'; +import { Body, Controller, Delete, Get, Param, ParseIntPipe, Patch, Post, UseGuards } from '@nestjs/common'; import { CustomersService } from '../services/customers.service'; -import { Customers, Employees } from '@prisma/client'; -import { CreateCustomerDto } from '../dtos/create-customer'; -import { UpdateCustomerDto } from '../dtos/update-customer'; +import { Customers } from '@prisma/client'; +import { CreateCustomerDto } from '../dtos/create-customer.dto'; +import { UpdateCustomerDto } from '../dtos/update-customer.dto'; import { RolesAllowed } from "src/common/decorators/roles.decorators"; import { Roles as RoleEnum } from '.prisma/client'; +import { ApiBearerAuth, ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'; +import { JwtAuthGuard } from 'src/modules/authentication/guards/jwt-auth.guard'; +import { CustomerEntity } from '../dtos/swagger-entities/customers.entity'; +@ApiTags('Customers') +@ApiBearerAuth('access-token') +@UseGuards(JwtAuthGuard) @Controller('customers') export class CustomersController { constructor(private readonly customersService: CustomersService) {} @Post() @RolesAllowed(RoleEnum.ACCOUNTING, RoleEnum.ADMIN, RoleEnum.EMPLOYEE, RoleEnum.SUPERVISOR) + @ApiOperation({ summary: 'Create customer' }) + @ApiResponse({ status: 201, description: 'Customer created', type: CustomerEntity }) + @ApiResponse({ status: 400, description: 'Invalid task or invalid data' }) create(@Body() dto: CreateCustomerDto): Promise { return this.customersService.create(dto); } @Get() @RolesAllowed(RoleEnum.ACCOUNTING, RoleEnum.ADMIN, RoleEnum.EMPLOYEE, RoleEnum.HR, RoleEnum.SUPERVISOR) + @ApiOperation({ summary: 'Find all customers' }) + @ApiResponse({ status: 201, description: 'List of customers found', type: CustomerEntity, isArray: true }) + @ApiResponse({ status: 400, description: 'List of customers not found' }) findAll(): Promise { return this.customersService.findAll(); } @Get(':id') @RolesAllowed(RoleEnum.ACCOUNTING, RoleEnum.ADMIN, RoleEnum.EMPLOYEE, RoleEnum.HR, RoleEnum.SUPERVISOR) + @ApiOperation({ summary: 'Find customer' }) + @ApiResponse({ status: 201, description: 'Customer found', type: CustomerEntity }) + @ApiResponse({ status: 400, description: 'Customer not found' }) findOne(@Param('id', ParseIntPipe) id: number): Promise { return this.customersService.findOne(id); } @Patch(':id') @RolesAllowed(RoleEnum.ACCOUNTING, RoleEnum.ADMIN, RoleEnum.EMPLOYEE,RoleEnum.SUPERVISOR) + @ApiOperation({ summary: 'Update customer' }) + @ApiResponse({ status: 201, description: 'Customer updated', type: CustomerEntity }) + @ApiResponse({ status: 400, description: 'Customer not found' }) update( @Param('id', ParseIntPipe) id: number, @Body() dto: UpdateCustomerDto, @@ -39,6 +57,9 @@ export class CustomersController { @Delete(':id') @RolesAllowed(RoleEnum.ACCOUNTING, RoleEnum.ADMIN, RoleEnum.SUPERVISOR) + @ApiOperation({ summary: 'Delete customer' }) + @ApiResponse({ status: 201, description: 'Customer deleted', type: CustomerEntity }) + @ApiResponse({ status: 400, description: 'Customer not found' }) remove(@Param('id', ParseIntPipe) id: number): Promise{ return this.customersService.remove(id); } diff --git a/src/modules/customers/dtos/create-customer.dto.ts b/src/modules/customers/dtos/create-customer.dto.ts new file mode 100644 index 0000000..201aaa3 --- /dev/null +++ b/src/modules/customers/dtos/create-customer.dto.ts @@ -0,0 +1,64 @@ +import { ApiProperty } from "@nestjs/swagger"; +import { Type } from "class-transformer"; +import { + IsEmail, + IsInt, + IsNotEmpty, + IsOptional, + IsPositive, + IsString, +} from "class-validator"; + +export class CreateCustomerDto { + + @ApiProperty({ + example: 'Gandalf', + description: 'Customer`s first name', + }) + @IsString() + @IsNotEmpty() + first_name: string; + + @ApiProperty({ + example: 'TheGray', + description: 'Customer`s last name', + }) + @IsString() + @IsNotEmpty() + last_name: string; + + @ApiProperty({ + example: 'you_shall_not_pass@middleEarth.com', + description: 'Customer`s email', + }) + @IsEmail() + @IsOptional() + email: string; + + @ApiProperty({ + example: '8436637464', + description: 'Customer`s phone number', + }) + @Type(() => Number) + @IsInt() + @IsPositive() + phone_number: number; + + @ApiProperty({ + example: '1 Ringbearer`s way, Mount Doom city, ME, T1R 1N6 ', + description: 'Customer`s residence', + required: false, + }) + @IsString() + @IsOptional() + residence?: string; + + @ApiProperty({ + example: '4263253', + description: 'Customer`s invoice number', + required: false, + }) + @IsInt() + @IsNotEmpty() + invoice_id: number; +} \ No newline at end of file diff --git a/src/modules/customers/dtos/create-customer.ts b/src/modules/customers/dtos/create-customer.ts deleted file mode 100644 index 4ae03fe..0000000 --- a/src/modules/customers/dtos/create-customer.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { Type } from "class-transformer"; -import { - IsEmail, - IsInt, - IsNotEmpty, - IsOptional, - IsPositive, - IsString, -} from "class-validator"; - -export class CreateCustomerDto { - - @IsString() - @IsNotEmpty() - first_name: string; - - @IsString() - @IsNotEmpty() - last_name: string; - - @IsEmail() - @IsOptional() - email?: string; - - @Type(() => Number) - @IsInt() - @IsPositive() - phone_number: number; - - @IsString() - @IsOptional() - residence?: string; - - @IsInt() - @IsNotEmpty() - invoice_id: number; -} \ No newline at end of file diff --git a/src/modules/customers/dtos/swagger-entities/customers.entity.ts b/src/modules/customers/dtos/swagger-entities/customers.entity.ts new file mode 100644 index 0000000..3e8ac99 --- /dev/null +++ b/src/modules/customers/dtos/swagger-entities/customers.entity.ts @@ -0,0 +1,42 @@ +import { ApiProperty } from '@nestjs/swagger'; + +export class CustomerEntity { + @ApiProperty({ + example: 1, + description: 'Unique ID of a customer(primary-key, auto-incremented)', + }) + id: number; + + @ApiProperty({ + example: '0e6e2e1f-b157-4c7c-ae3f-999b3e4f914d', + description: 'UUID of the user linked to that customer', + }) + user_id: string; + + @ApiProperty({ + example: 'you_shall_not_pass@middleEarth.com', + description: 'customer`s email (optional)', + required: false, + }) + email?: string; + + @ApiProperty({ + example: 8436637464, + description: 'customer`s phone number (numbers only)', + }) + phone_number: number; + + @ApiProperty({ + example: '1 Ringbearer’s way, Mount Doom city, ME, T1R 1N6', + description: 'customer`s residence address (optional)', + required: false, + }) + residence?: string; + + @ApiProperty({ + example: 4263253, + description: 'customer`s invoice number (optionnal, unique)', + required: false, + }) + invoice_id?: number; +} diff --git a/src/modules/customers/dtos/update-customer.ts b/src/modules/customers/dtos/update-customer.dto.ts similarity index 67% rename from src/modules/customers/dtos/update-customer.ts rename to src/modules/customers/dtos/update-customer.dto.ts index 0499fe2..fc1ba39 100644 --- a/src/modules/customers/dtos/update-customer.ts +++ b/src/modules/customers/dtos/update-customer.dto.ts @@ -1,4 +1,4 @@ import { PartialType } from "@nestjs/swagger"; -import { CreateCustomerDto } from "./create-customer"; +import { CreateCustomerDto } from "./create-customer.dto"; export class UpdateCustomerDto extends PartialType(CreateCustomerDto) {} \ No newline at end of file diff --git a/src/modules/customers/services/customers.service.ts b/src/modules/customers/services/customers.service.ts index f67f4bd..4f11bce 100644 --- a/src/modules/customers/services/customers.service.ts +++ b/src/modules/customers/services/customers.service.ts @@ -1,8 +1,8 @@ import { Injectable, NotFoundException } from '@nestjs/common'; import { PrismaService } from 'src/prisma/prisma.service'; -import { CreateCustomerDto } from '../dtos/create-customer'; +import { CreateCustomerDto } from '../dtos/create-customer.dto'; import { Customers, Users } from '@prisma/client'; -import { UpdateCustomerDto } from '../dtos/update-customer'; +import { UpdateCustomerDto } from '../dtos/update-customer.dto'; @Injectable() export class CustomersService { diff --git a/src/modules/employees/controllers/employees.controller.ts b/src/modules/employees/controllers/employees.controller.ts index 468285a..c736182 100644 --- a/src/modules/employees/controllers/employees.controller.ts +++ b/src/modules/employees/controllers/employees.controller.ts @@ -1,46 +1,52 @@ -import { - Body, - Controller, - Delete, - Get, - Param, - ParseIntPipe, - Patch, - Post, -} from '@nestjs/common'; -import { Employees } from '@prisma/client'; +import { Body,Controller,Delete,Get,Param,ParseIntPipe,Patch,Post,UseGuards } from '@nestjs/common'; +import { Employees, Roles as RoleEnum } from '@prisma/client'; import { EmployeesService } from '../services/employees.service'; import { CreateEmployeeDto } from '../dtos/create-employee.dto'; import { UpdateEmployeeDto } from '../dtos/update-employee.dto'; - -//decorators and roles imports import { RolesAllowed } from '../../../common/decorators/roles.decorators'; -import { Roles as RoleEnum } from 'prisma/prisma-client'; +import { ApiBearerAuth, ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'; +import { JwtAuthGuard } from 'src/modules/authentication/guards/jwt-auth.guard'; +import { EmployeeEntity } from '../dtos/swagger-entities/employees.entity'; +@ApiTags('Employees') +@ApiBearerAuth('access-token') +@UseGuards(JwtAuthGuard) @Controller('employees') export class EmployeesController { constructor(private readonly employeesService: EmployeesService) {} @Post() @RolesAllowed(RoleEnum.ADMIN, RoleEnum.HR, RoleEnum.SUPERVISOR) + @ApiOperation({summary: 'Create employee' }) + @ApiResponse({ status: 201, description: 'Employee created', type: EmployeeEntity }) + @ApiResponse({ status: 400, description: 'Incomplete task or invalid data' }) create(@Body() dto: CreateEmployeeDto): Promise { return this.employeesService.create(dto); } @Get() @RolesAllowed(RoleEnum.ADMIN, RoleEnum.HR, RoleEnum.SUPERVISOR, RoleEnum.ACCOUNTING) + @ApiOperation({summary: 'Find all employees' }) + @ApiResponse({ status: 201, description: 'List of employees found', type: EmployeeEntity, isArray: true }) + @ApiResponse({ status: 400, description: 'List of employees not found' }) findAll(): Promise { return this.employeesService.findAll(); } @Get(':id') @RolesAllowed(RoleEnum.ADMIN, RoleEnum.HR, RoleEnum.SUPERVISOR,RoleEnum.ACCOUNTING ) + @ApiOperation({summary: 'Find employee' }) + @ApiResponse({ status: 201, description: 'Employee found', type: EmployeeEntity }) + @ApiResponse({ status: 400, description: 'Employee not found' }) findOne(@Param('id', ParseIntPipe) id: number): Promise { return this.employeesService.findOne(id); } @Patch(':id') @RolesAllowed(RoleEnum.ADMIN, RoleEnum.HR, RoleEnum.SUPERVISOR,RoleEnum.ACCOUNTING ) + @ApiOperation({summary: 'Update employee' }) + @ApiResponse({ status: 201, description: 'Employee updated', type: EmployeeEntity }) + @ApiResponse({ status: 400, description: 'Employee not found' }) update( @Param('id', ParseIntPipe) id: number, @Body() dto: UpdateEmployeeDto, @@ -50,6 +56,9 @@ export class EmployeesController { @Delete(':id') @RolesAllowed(RoleEnum.ADMIN, RoleEnum.HR, RoleEnum.SUPERVISOR ) + @ApiOperation({summary: 'Delete employee' }) + @ApiResponse({ status: 201, description: 'Employee deleted', type: EmployeeEntity }) + @ApiResponse({ status: 400, description: 'Employee not found' }) remove(@Param('id', ParseIntPipe) id: number): Promise { return this.employeesService.remove(id); } diff --git a/src/modules/employees/dtos/create-employee.dto.ts b/src/modules/employees/dtos/create-employee.dto.ts index 88bd9e3..a595a0c 100644 --- a/src/modules/employees/dtos/create-employee.dto.ts +++ b/src/modules/employees/dtos/create-employee.dto.ts @@ -9,44 +9,84 @@ import { IsString, } from 'class-validator'; import { Type } from 'class-transformer'; +import { ApiProperty } from '@nestjs/swagger'; export class CreateEmployeeDto { + + @ApiProperty({ + example: 'Frodo', + description: 'Employee`s first name', + }) @IsString() @IsNotEmpty() first_name: string; + @ApiProperty({ + example: 'Baggins', + description: 'Employee`s last name', + }) @IsString() @IsNotEmpty() last_name: string; + @ApiProperty({ + example: 'i_cant_do_this_sam@targointernet.com', + description: 'Employee`s email', + }) @IsEmail() @IsOptional() email: string; + @ApiProperty({ + example: '82538437464', + description: 'Employee`s phone number', + }) @Type(() => Number) @IsInt() @IsPositive() phone_number: number; + @ApiProperty({ + example: '1 Bagshot Row, Hobbiton, The Shire, Middle-earth', + description: 'Employee`s residence', + required: false, + }) @IsString() @IsOptional() residence?: string; + @ApiProperty({ + example: 'BagginsF7464', + description: 'Employee`s payroll id', + }) @IsInt() @IsPositive() @Type(() => Number) external_payroll_id: number; + @ApiProperty({ + example: '335567447', + description: 'Employee`s company code', + }) @IsInt() @IsPositive() @Type(() => Number) company_code: number; + @ApiProperty({ + example: '23/09/3018', + description: 'Employee`s first working day', + }) @IsDateString() @Type(() => Date) @IsDate() first_work_day: Date; + @ApiProperty({ + example: '25/03/3019', + description: 'Employee`s last working day', + required: false, + }) @IsDateString() @Type(() => Date) @IsDate() diff --git a/src/modules/employees/dtos/swagger-entities/employees.entity.ts b/src/modules/employees/dtos/swagger-entities/employees.entity.ts new file mode 100644 index 0000000..d1431bb --- /dev/null +++ b/src/modules/employees/dtos/swagger-entities/employees.entity.ts @@ -0,0 +1,40 @@ +import { ApiProperty } from '@nestjs/swagger'; + +export class EmployeeEntity { + @ApiProperty({ + example: 1, + description: 'Unique ID of an employee(primary-key, auto-incremented)', + }) + id: number; + + @ApiProperty({ + example: '0e6e2e1f-b157-4c7c-ae3f-999b3e4f914d', + description: 'UUID of the user linked to that employee', + }) + user_id: string; + + @ApiProperty({ + example: 7464, + description: 'external ID for the pay system', + }) + external_payroll_id: number; + + @ApiProperty({ + example: 335567447, + description: 'company code', + }) + company_code: number; + + @ApiProperty({ + example: '3018-09-23T00:00:00.000Z', + description: 'Employee first day at work', + }) + first_work_day: Date; + + @ApiProperty({ + example: '3019-03-25T00:00:00.000Z', + description: 'Employee last day at work', + required: false, + }) + last_work_day?: Date; +} diff --git a/src/modules/leave-requests/controllers/leave-requests.controller.ts b/src/modules/leave-requests/controllers/leave-requests.controller.ts index f992ee5..6c13ea3 100644 --- a/src/modules/leave-requests/controllers/leave-requests.controller.ts +++ b/src/modules/leave-requests/controllers/leave-requests.controller.ts @@ -1,44 +1,62 @@ -import { Body, Controller, Delete, Get, Param, ParseIntPipe, Patch, Post } from "@nestjs/common"; +import { Body, Controller, Delete, Get, Param, ParseIntPipe, Patch, Post, UseGuards } from "@nestjs/common"; import { LeaveRequestsService } from "../services/leave-request.service"; import { CreateLeaveRequestsDto } from "../dtos/create-leave-requests.dto"; import { LeaveRequests } from "@prisma/client"; import { UpdateLeaveRequestsDto } from "../dtos/update-leave-requests.dto"; import { RolesAllowed } from "src/common/decorators/roles.decorators"; import { Roles as RoleEnum } from '.prisma/client'; +import { ApiBearerAuth, ApiOperation, ApiResponse, ApiTags } from "@nestjs/swagger"; +import { JwtAuthGuard } from "src/modules/authentication/guards/jwt-auth.guard"; +import { LeaveRequestEntity } from "../dtos/swagger-entities/leave-requests.entity"; +@ApiTags('Leave Requests') +@ApiBearerAuth('access-token') +@UseGuards(JwtAuthGuard) @Controller('leave-requests') export class LeaveRequestController { constructor(private readonly leaveRequetsService: LeaveRequestsService){} @Post() @RolesAllowed(RoleEnum.ACCOUNTING, RoleEnum.ADMIN, RoleEnum.EMPLOYEE, RoleEnum.HR, RoleEnum.SUPERVISOR) + @ApiOperation({summary: 'Create leave request' }) + @ApiResponse({ status: 201, description: 'Leave request created',type: LeaveRequestEntity }) + @ApiResponse({ status: 400, description: 'Incomplete task or invalid data' }) create(@Body() dto: CreateLeaveRequestsDto): Promise { return this. leaveRequetsService.create(dto); } @Get() @RolesAllowed(RoleEnum.ACCOUNTING, RoleEnum.ADMIN, RoleEnum.HR, RoleEnum.SUPERVISOR) + @ApiOperation({summary: 'Find all leave request' }) + @ApiResponse({ status: 201, description: 'List of Leave requests found',type: LeaveRequestEntity, isArray: true }) + @ApiResponse({ status: 400, description: 'Leave request not found' }) findAll(): Promise { return this.leaveRequetsService.findAll(); } @Get(':id') @RolesAllowed(RoleEnum.ACCOUNTING, RoleEnum.ADMIN, RoleEnum.EMPLOYEE, RoleEnum.HR, RoleEnum.SUPERVISOR) + @ApiOperation({summary: 'Find leave request' }) + @ApiResponse({ status: 201, description: 'Leave request found',type: LeaveRequestEntity }) + @ApiResponse({ status: 400, description: 'Leave request not found' }) findOne(@Param('id', ParseIntPipe) id: number): Promise { return this.leaveRequetsService.findOne(id); } @Patch(':id') @RolesAllowed(RoleEnum.ACCOUNTING, RoleEnum.ADMIN, RoleEnum.EMPLOYEE, RoleEnum.HR, RoleEnum.SUPERVISOR) - update( - @Param('id', ParseIntPipe) id: number, - @Body() dto: UpdateLeaveRequestsDto, - ): Promise { + @ApiOperation({summary: 'Update leave request' }) + @ApiResponse({ status: 201, description: 'Leave request updated',type: LeaveRequestEntity }) + @ApiResponse({ status: 400, description: 'Leave request not found' }) + update(@Param('id', ParseIntPipe) id: number,@Body() dto: UpdateLeaveRequestsDto): Promise { return this.leaveRequetsService.update(id, dto); } @Delete(':id') @RolesAllowed(RoleEnum.ACCOUNTING, RoleEnum.ADMIN, RoleEnum.EMPLOYEE, RoleEnum.HR, RoleEnum.SUPERVISOR) + @ApiOperation({summary: 'Delete leave request' }) + @ApiResponse({ status: 201, description: 'Leave request deleted',type: LeaveRequestEntity }) + @ApiResponse({ status: 400, description: 'Leave request not found' }) remove(@Param('id', ParseIntPipe) id: number): Promise { return this.leaveRequetsService.remove(id); } diff --git a/src/modules/leave-requests/dtos/create-leave-requests.dto.ts b/src/modules/leave-requests/dtos/create-leave-requests.dto.ts index 2091785..703cf91 100644 --- a/src/modules/leave-requests/dtos/create-leave-requests.dto.ts +++ b/src/modules/leave-requests/dtos/create-leave-requests.dto.ts @@ -1,27 +1,52 @@ +import { ApiProperty } from "@nestjs/swagger"; import { LeaveApprovalStatus, LeaveTypes } from "@prisma/client"; import { Type } from "class-transformer"; import { IsEnum, IsInt, IsNotEmpty, IsOptional, IsString } from "class-validator"; export class CreateLeaveRequestsDto { + @ApiProperty({ + example: '4655867', + description: 'Employee`s id', + }) @Type(()=> Number) @IsInt() employee_id: number; + @ApiProperty({ + example: 'Sick or Vacation or Unpaid or Bereavement or Parental or Legal', + description: 'type of leave request for an accounting perception', + }) @IsEnum(LeaveTypes) leave_type: LeaveTypes; + @ApiProperty({ + example: '22/06/2463', + description: 'Leave request`s start date', + }) @Type(()=>Date) @IsNotEmpty() start_date_time:Date; + @ApiProperty({ + example: '25/03/3019', + description: 'Leave request`s end date', + }) @Type(()=>Date) @IsOptional() end_date_time?: Date; + @ApiProperty({ + example: 'My precious', + description: 'Leave request`s comment', + }) @IsString() @IsNotEmpty() comment: string; + @ApiProperty({ + example: 'True or False or Pending or Denied or Cancelled or Escalated', + description: 'Leave request`s approval status', + }) @IsEnum(LeaveApprovalStatus) @IsOptional() approval_status?: LeaveApprovalStatus; diff --git a/src/modules/leave-requests/dtos/swagger-entities/leave-requests.entity.ts b/src/modules/leave-requests/dtos/swagger-entities/leave-requests.entity.ts new file mode 100644 index 0000000..2344828 --- /dev/null +++ b/src/modules/leave-requests/dtos/swagger-entities/leave-requests.entity.ts @@ -0,0 +1,49 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { LeaveApprovalStatus, LeaveTypes } from '@prisma/client'; + +export class LeaveRequestEntity { + @ApiProperty({ + example: 1, + description: 'Leave request`s unique id(auto-incremented)', + }) + id: number; + + @ApiProperty({ + example: 42, + description: 'ID of concerned employee', + }) + employee_id: number; + + @ApiProperty({ + example: 'SICK', + enum: LeaveTypes, + description: 'type of leave request for an accounting perception', + }) + leave_type: LeaveTypes; + + @ApiProperty({ + example: '22/06/2463', + description: 'Leave request`s start date', + }) + start_date_time: Date; + + @ApiProperty({ + example: '25/03/3019', + description: 'Leave request`s end date (optionnal)', + required: false, + }) + end_date_time?: Date; + + @ApiProperty({ + example: 'My precious', + description: 'Leave request employee`s comment', + }) + comment: string; + + @ApiProperty({ + example: 'PENDING', + enum: LeaveApprovalStatus, + description: 'Leave request`s approval status', + }) + approval_status: LeaveApprovalStatus; +} diff --git a/src/modules/oauth-access-tokens/controllers/oauth-access-tokens.controller.ts b/src/modules/oauth-access-tokens/controllers/oauth-access-tokens.controller.ts index edcdbde..156f463 100644 --- a/src/modules/oauth-access-tokens/controllers/oauth-access-tokens.controller.ts +++ b/src/modules/oauth-access-tokens/controllers/oauth-access-tokens.controller.ts @@ -1,41 +1,62 @@ -import { Body, Controller, Delete, Get, Param, Patch, Post } from '@nestjs/common'; +import { Body, Controller, Delete, Get, Param, Patch, Post, UseGuards } from '@nestjs/common'; import { OauthAccessTokensService } from '../services/oauth-access-tokens.service'; import { CreateOauthAccessTokenDto } from '../dtos/create-oauth-access-token.dto'; import { OAuthAccessTokens } from '@prisma/client'; import { UpdateOauthAccessTokenDto } from '../dtos/update-oauth-access-token.dto'; import { RolesAllowed } from "src/common/decorators/roles.decorators"; import { Roles as RoleEnum } from '.prisma/client'; +import { ApiBearerAuth, ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'; +import { JwtAuthGuard } from 'src/modules/authentication/guards/jwt-auth.guard'; +import { OAuthAccessTokenEntity } from '../dtos/swagger-entities/oauth-access-token.entity'; +@ApiTags('OAuth Access Tokens') +@ApiBearerAuth('access-token') +@UseGuards(JwtAuthGuard) @Controller('oauth-access-tokens') export class OauthAccessTokensController { constructor(private readonly oauthAccessTokensService: OauthAccessTokensService){} @Post() @RolesAllowed(RoleEnum.ADMIN) + @ApiOperation({summary: 'Create OAuth access token' }) + @ApiResponse({ status: 201, description: 'OAuth access token created', type: OAuthAccessTokenEntity }) + @ApiResponse({ status: 400, description: 'Incomplete task or invalid data' }) create(@Body()dto: CreateOauthAccessTokenDto): Promise { return this.oauthAccessTokensService.create(dto); } @Get() @RolesAllowed(RoleEnum.ADMIN) + @ApiOperation({summary: 'Find all OAuth access token' }) + @ApiResponse({ status: 201, description: 'List of OAuth access token found', type: OAuthAccessTokenEntity, isArray: true }) + @ApiResponse({ status: 400, description: 'List of OAuth access token not found' }) findAll(): Promise { return this.oauthAccessTokensService.findAll(); } @Get(':id') @RolesAllowed(RoleEnum.ADMIN) + @ApiOperation({summary: 'Find OAuth access token' }) + @ApiResponse({ status: 201, description: 'OAuth access token found', type: OAuthAccessTokenEntity }) + @ApiResponse({ status: 400, description: 'OAuth access token not found' }) findOne(@Param('id') id: string): Promise { return this.oauthAccessTokensService.findOne(id); } @Patch(':id') @RolesAllowed(RoleEnum.ADMIN) + @ApiOperation({summary: 'Update OAuth access token' }) + @ApiResponse({ status: 201, description: 'OAuth access token updated', type: OAuthAccessTokenEntity }) + @ApiResponse({ status: 400, description: 'OAuth access token not found' }) update(@Param('id') id: string, @Body() dto: UpdateOauthAccessTokenDto): Promise { return this.oauthAccessTokensService.update(id,dto); } @Delete(':id') @RolesAllowed(RoleEnum.ADMIN) + @ApiOperation({summary: 'Delete OAuth access token' }) + @ApiResponse({ status: 201, description: 'OAuth access token deleted', type: OAuthAccessTokenEntity }) + @ApiResponse({ status: 400, description: 'OAuth access token not found' }) remove(@Param('id') id: string): Promise { return this.oauthAccessTokensService.remove(id); } diff --git a/src/modules/oauth-access-tokens/dtos/create-oauth-access-token.dto.ts b/src/modules/oauth-access-tokens/dtos/create-oauth-access-token.dto.ts index 03db393..8e6d1ef 100644 --- a/src/modules/oauth-access-tokens/dtos/create-oauth-access-token.dto.ts +++ b/src/modules/oauth-access-tokens/dtos/create-oauth-access-token.dto.ts @@ -1,29 +1,60 @@ +import { ApiProperty } from "@nestjs/swagger"; import { Type } from "class-transformer"; import { IsArray, IsDate, IsOptional, IsString, IsUUID } from "class-validator"; export class CreateOauthAccessTokenDto { + @ApiProperty({ + example: 'S7A2U8R7O6N6', + description: 'User`s unique identification number', + }) @IsUUID() user_id: string; + @ApiProperty({ + example: 'app.targo.ca', + description: 'URL in which the access token is used for', + }) @IsString() application: string; + @ApiProperty({ + example: 'L5O6R4D3/O6F3#T8H4E3&R6I4N6G4S7 ...', + description: 'Access token', + }) @IsString() access_token: string; + @ApiProperty({ + example: 'Th3731102h1p07Th3R1n92', + description: 'Refresh token', + }) @IsString() refresh_token: string; + @ApiProperty({ + example: '25/12/3018', + description: 'Access token`s expiry date', + }) @Type(()=> Date) @IsDate() access_token_expiry: Date; + @ApiProperty({ + example: '26/02/3019', + description: 'Refresh token`s expiry date', + required: false, + }) @Type(()=> Date) @IsDate() @IsOptional() refresh_token_expiry?: Date; + @ApiProperty({ + example: 'access tolkiens, email, etc... ', + description: 'scopes of infos linked to the access token', + required: false, + }) @IsArray() @IsString() @IsOptional() diff --git a/src/modules/oauth-access-tokens/dtos/swagger-entities/oauth-access-token.entity.ts b/src/modules/oauth-access-tokens/dtos/swagger-entities/oauth-access-token.entity.ts new file mode 100644 index 0000000..e80e6d7 --- /dev/null +++ b/src/modules/oauth-access-tokens/dtos/swagger-entities/oauth-access-token.entity.ts @@ -0,0 +1,72 @@ +import { ApiProperty } from '@nestjs/swagger'; + +export class OAuthAccessTokenEntity { + @ApiProperty({ + example: 'cklwi0vb70000z2z20q6f19qk', + description: 'Unique ID of an OAuth token (auto-generated)', + }) + id: string; + + @ApiProperty({ + example: '0e6e2e1f-b157-4c7c-ae3f-999b3e4f914d', + description: 'UUID User`s unique identification number', + }) + user_id: string; + + @ApiProperty({ + example: 'app.targo.ca', + description: 'URL in which the access token is used for', + }) + application: string; + + @ApiProperty({ + example: 'L5O6R4D3/O6F3#T8H4E3&R6I4N6G4S7', + description: 'Access token', + }) + access_token: string; + + @ApiProperty({ + example: 'Th3731102h1p07Th3R1n92', + description: 'Refresh token', + }) + refresh_token: string; + + @ApiProperty({ + example: '3018-12-25T00:00:00.000Z', + description: 'Access token`s expiry date', + }) + access_token_expiry: Date; + + @ApiProperty({ + example: '3019-02-26T00:00:00.000Z', + description: 'Refresh token`s expiry date (optional)', + required: false, + }) + refresh_token_expiry?: Date; + + @ApiProperty({ + example: ['email', 'profile', 'access_tolkiens'], + description: 'scopes of infos linked to the access token', + required: false, + }) + scopes: string[]; + + @ApiProperty({ + example: false, + description: 'revoke status', + }) + is_revoked: boolean; + + @ApiProperty({ + example: '2025-07-22', + description: 'creation date', + }) + created_at: Date; + + @ApiProperty({ + example: '2025-07-23', + description: 'Latest update (optional)', + required: false, + }) + updated_at?: Date; +} diff --git a/src/modules/shift-codes/controllers/shift-codes.controller.ts b/src/modules/shift-codes/controllers/shift-codes.controller.ts index b9ea9f8..db16eb6 100644 --- a/src/modules/shift-codes/controllers/shift-codes.controller.ts +++ b/src/modules/shift-codes/controllers/shift-codes.controller.ts @@ -1,35 +1,53 @@ -import { Body, Controller, Delete, Get, Param, ParseIntPipe, Patch, Post } from "@nestjs/common"; +import { Body, Controller, Delete, Get, Param, ParseIntPipe, Patch, Post, UseGuards } from "@nestjs/common"; import { ShiftCodesService } from "../services/shift-codes.service"; import { CreateShiftCodesDto } from "../dtos/create-shift-codes.dto"; import { ShiftCodes } from "@prisma/client"; import { UpdateShiftCodesDto } from "../dtos/update-shift-codes.dto"; import { RolesAllowed } from "src/common/decorators/roles.decorators"; import { Roles as RoleEnum } from '.prisma/client'; +import { ApiBearerAuth, ApiOperation, ApiResponse, ApiTags } from "@nestjs/swagger"; +import { JwtAuthGuard } from "src/modules/authentication/guards/jwt-auth.guard"; +import { ShiftCodesEntity } from "../dtos/swagger-entities/shift-codes.entity"; -@Controller() +@ApiTags('Shift Codes') +@ApiBearerAuth('access-token') +@UseGuards(JwtAuthGuard) +@Controller('shift-codes') export class ShiftCodesController { constructor(private readonly shiftCodesService: ShiftCodesService) {} @Post() @RolesAllowed(RoleEnum.ACCOUNTING, RoleEnum.ADMIN, RoleEnum.HR) + @ApiOperation({ summary: 'Create shift code' }) + @ApiResponse({ status: 201, description: 'Shift code created',type: ShiftCodesEntity }) + @ApiResponse({ status: 400, description: 'Incomplete task or invalid data' }) create(@Body()dto: CreateShiftCodesDto): Promise { return this.shiftCodesService.create(dto); } @Get() @RolesAllowed(RoleEnum.ACCOUNTING, RoleEnum.ADMIN, RoleEnum.HR) + @ApiOperation({ summary: 'Find all shift codes' }) + @ApiResponse({ status: 201, description: 'List of shift codes found',type: ShiftCodesEntity, isArray: true }) + @ApiResponse({ status: 400, description: 'List of shift codes not found' }) findAll(): Promise { return this.shiftCodesService.findAll(); } @Get(':id') @RolesAllowed(RoleEnum.ACCOUNTING, RoleEnum.ADMIN, RoleEnum.HR) + @ApiOperation({ summary: 'Find shift code' }) + @ApiResponse({ status: 201, description: 'Shift code found',type: ShiftCodesEntity }) + @ApiResponse({ status: 400, description: 'Shift code not found' }) findOne(@Param('id', ParseIntPipe) id: number): Promise { return this.shiftCodesService.findOne(id); } @Patch(':id') @RolesAllowed(RoleEnum.ACCOUNTING, RoleEnum.ADMIN, RoleEnum.HR) + @ApiOperation({ summary: 'Update shift code' }) + @ApiResponse({ status: 201, description: 'Shift code updated',type: ShiftCodesEntity }) + @ApiResponse({ status: 400, description: 'Shift code not found' }) update(@Param('id', ParseIntPipe) id: number, @Body() dto: UpdateShiftCodesDto): Promise { return this.shiftCodesService.update(id,dto); @@ -37,6 +55,9 @@ export class ShiftCodesController { @Delete(':id') @RolesAllowed(RoleEnum.ACCOUNTING, RoleEnum.ADMIN, RoleEnum.HR) + @ApiOperation({ summary: 'Delete shift code' }) + @ApiResponse({ status: 201, description: 'Shift code deleted',type: ShiftCodesEntity }) + @ApiResponse({ status: 400, description: 'Shift code not found' }) remove(@Param('id', ParseIntPipe)id: number): Promise { return this.shiftCodesService.remove(id); } diff --git a/src/modules/shift-codes/dtos/create-shift-codes.dto.ts b/src/modules/shift-codes/dtos/create-shift-codes.dto.ts index f92d04d..3fa173c 100644 --- a/src/modules/shift-codes/dtos/create-shift-codes.dto.ts +++ b/src/modules/shift-codes/dtos/create-shift-codes.dto.ts @@ -1,10 +1,20 @@ +import { ApiProperty } from "@nestjs/swagger"; import { IsNotEmpty, IsString } from "class-validator"; export class CreateShiftCodesDto { + + @ApiProperty({ + example: 'Regular or Night or Emergency, etc...', + description: 'Type of shifts for an account perception', + }) @IsString() @IsNotEmpty() shift_type: string; + @ApiProperty({ + example: 'G1, G2, G3, etc...', + description: 'bank`s code related to the type of shift', + }) @IsString() @IsNotEmpty() bank_code: string; diff --git a/src/modules/shift-codes/dtos/swagger-entities/shift-codes.entity.ts b/src/modules/shift-codes/dtos/swagger-entities/shift-codes.entity.ts new file mode 100644 index 0000000..6a90b6b --- /dev/null +++ b/src/modules/shift-codes/dtos/swagger-entities/shift-codes.entity.ts @@ -0,0 +1,21 @@ +import { ApiProperty } from '@nestjs/swagger'; + +export class ShiftCodesEntity { + @ApiProperty({ + example: 1, + description: 'Unique ID of a shift-code (auto-generated)', + }) + id: number; + + @ApiProperty({ + example: 'Night', + description: 'Type of shifts for an account perception', + }) + shift_type: string; + + @ApiProperty({ + example: 'G2', + description: 'bank`s code related to the type of shift', + }) + bank_code: string; +} diff --git a/src/modules/shifts/controllers/shifts.controller.ts b/src/modules/shifts/controllers/shifts.controller.ts index ad7faf7..d1d23b0 100644 --- a/src/modules/shifts/controllers/shifts.controller.ts +++ b/src/modules/shifts/controllers/shifts.controller.ts @@ -1,45 +1,62 @@ -import { Body, Controller, Delete, Get, Param, ParseIntPipe, Patch, Post } from "@nestjs/common"; +import { Body, Controller, Delete, Get, Param, ParseIntPipe, Patch, Post, UseGuards } from "@nestjs/common"; import { ShiftsService } from "../services/shifts.service"; import { Shifts } from "@prisma/client"; import { CreateShiftDto } from "../dtos/create-shifts.dto"; import { UpdateShiftsDto } from "../dtos/update-shifts.dto"; import { RolesAllowed } from "src/common/decorators/roles.decorators"; import { Roles as RoleEnum } from '.prisma/client'; +import { ApiBearerAuth, ApiOperation, ApiResponse, ApiTags } from "@nestjs/swagger"; +import { JwtAuthGuard } from "src/modules/authentication/guards/jwt-auth.guard"; +import { ShiftEntity } from "../dtos/swagger-entities/shift.entity"; +@ApiTags('Shifts') +@ApiBearerAuth('access-token') +@UseGuards(JwtAuthGuard) @Controller('shifts') export class ShiftsController { constructor(private readonly shiftsService: ShiftsService){} - @Post() @RolesAllowed(RoleEnum.ACCOUNTING, RoleEnum.ADMIN, RoleEnum.EMPLOYEE, RoleEnum.HR, RoleEnum.SUPERVISOR) + @ApiOperation({ summary: 'Create shift' }) + @ApiResponse({ status: 201, description: 'Shift created',type: ShiftEntity }) + @ApiResponse({ status: 400, description: 'Incomplete task or invalid data' }) create(@Body() dto: CreateShiftDto): Promise { return this.shiftsService.create(dto); } @Get() @RolesAllowed(RoleEnum.ACCOUNTING, RoleEnum.ADMIN, RoleEnum.EMPLOYEE, RoleEnum.HR, RoleEnum.SUPERVISOR) + @ApiOperation({ summary: 'Find all shifts' }) + @ApiResponse({ status: 201, description: 'List of shifts found',type: ShiftEntity, isArray: true }) + @ApiResponse({ status: 400, description: 'List of shifts not found' }) findAll(): Promise { return this.shiftsService.findAll(); } @Get(':id') @RolesAllowed(RoleEnum.ACCOUNTING, RoleEnum.ADMIN, RoleEnum.EMPLOYEE, RoleEnum.HR, RoleEnum.SUPERVISOR) + @ApiOperation({ summary: 'Find shift' }) + @ApiResponse({ status: 201, description: 'Shift found',type: ShiftEntity }) + @ApiResponse({ status: 400, description: 'Shift not found' }) findOne(@Param('id', ParseIntPipe) id: number): Promise { return this.shiftsService.findOne(id); } @Patch(':id') @RolesAllowed(RoleEnum.ACCOUNTING, RoleEnum.ADMIN, RoleEnum.EMPLOYEE, RoleEnum.HR, RoleEnum.SUPERVISOR) - update( - @Param('id', ParseIntPipe) id: number, - @Body() dto: UpdateShiftsDto, - ): Promise { + @ApiOperation({ summary: 'Update shift' }) + @ApiResponse({ status: 201, description: 'Shift updated',type: ShiftEntity }) + @ApiResponse({ status: 400, description: 'Shift not found' }) + update(@Param('id', ParseIntPipe) id: number,@Body() dto: UpdateShiftsDto): Promise { return this.shiftsService.update(id, dto); } @Delete(':id') @RolesAllowed(RoleEnum.ACCOUNTING, RoleEnum.ADMIN, RoleEnum.EMPLOYEE, RoleEnum.HR, RoleEnum.SUPERVISOR) + @ApiOperation({ summary: 'Delete shift' }) + @ApiResponse({ status: 201, description: 'Shift deleted',type: ShiftEntity }) + @ApiResponse({ status: 400, description: 'Shift not found' }) remove(@Param('id', ParseIntPipe) id: number): Promise { return this.shiftsService.remove(id); } diff --git a/src/modules/shifts/dtos/create-shifts.dto.ts b/src/modules/shifts/dtos/create-shifts.dto.ts index 4365be6..2ed7dcb 100644 --- a/src/modules/shifts/dtos/create-shifts.dto.ts +++ b/src/modules/shifts/dtos/create-shifts.dto.ts @@ -1,26 +1,47 @@ +import { ApiProperty } from "@nestjs/swagger"; import { Type } from "class-transformer"; import { IsDate, IsDateString, IsInt } from "class-validator"; export class CreateShiftDto { + + @ApiProperty({ + example: 'Th3F3110w5h1pX2024', + description: 'ID number for a set timesheet', + }) @Type(() => Number) @IsInt() timesheet_id: number; + @ApiProperty({ + example: '0n3R1n962Ru13xX', + description: 'ID number of a shift code (link with shift-codes)', + }) @Type(() => Number) @IsInt() shift_code_id: number; - + @ApiProperty({ + example: '20/10/3018', + description: 'Date where the shift takes place', + }) @IsDateString() @Type(() => Date) @IsDate() date: Date; + @ApiProperty({ + example: '08:00', + description: 'Start time of the said shift', + }) @IsDateString() @Type(() => Date) @IsDate() start_time: Date; + @ApiProperty({ + example: '17:00', + description: 'End time of the said shift', + }) @IsDateString() @Type(() => Date) @IsDate() diff --git a/src/modules/shifts/dtos/swagger-entities/shift.entity.ts b/src/modules/shifts/dtos/swagger-entities/shift.entity.ts new file mode 100644 index 0000000..787dfea --- /dev/null +++ b/src/modules/shifts/dtos/swagger-entities/shift.entity.ts @@ -0,0 +1,39 @@ +import { ApiProperty } from '@nestjs/swagger'; + +export class ShiftEntity { + @ApiProperty({ + example: 1, + description: 'Unique ID of the shift (auto-generated)', + }) + id: number; + + @ApiProperty({ + example: 101, + description: 'ID number for a set timesheet', + }) + timesheet_id: number; + + @ApiProperty({ + example: 7, + description: 'ID number of a shift code (link with shift-codes)', + }) + shift_code_id: number; + + @ApiProperty({ + example: '3018-10-20T00:00:00.000Z', + description: 'Date where the shift takes place', + }) + date: Date; + + @ApiProperty({ + example: '3018-10-20T08:00:00.000Z', + description: 'Start time of the said shift', + }) + start_time: Date; + + @ApiProperty({ + example: '3018-10-20T17:00:00.000Z', + description: 'End time of the said shift', + }) + end_time: Date; +} diff --git a/src/modules/timesheets/controllers/timesheets.controller.ts b/src/modules/timesheets/controllers/timesheets.controller.ts index a8369ee..e90e08f 100644 --- a/src/modules/timesheets/controllers/timesheets.controller.ts +++ b/src/modules/timesheets/controllers/timesheets.controller.ts @@ -1,35 +1,53 @@ -import { Body, Controller, Delete, Get, Param, ParseIntPipe, Patch, Post } from '@nestjs/common'; +import { Body, Controller, Delete, Get, Param, ParseIntPipe, Patch, Post, UseGuards } from '@nestjs/common'; import { TimesheetsService } from '../services/timesheets.service'; import { CreateTimesheetDto } from '../dtos/create-timesheet.dto'; import { Timesheets } from '@prisma/client'; import { UpdateTimesheetDto } from '../dtos/update-timesheet.dto'; import { RolesAllowed } from "src/common/decorators/roles.decorators"; import { Roles as RoleEnum } from '.prisma/client'; +import { ApiBearerAuth, ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'; +import { JwtAuthGuard } from 'src/modules/authentication/guards/jwt-auth.guard'; +import { TimesheetEntity } from '../dtos/swagger-entities/timesheet.entity'; +@ApiTags('Timesheets') +@ApiBearerAuth('access-token') +@UseGuards(JwtAuthGuard) @Controller('timesheets') export class TimesheetsController { constructor(private readonly timesheetsService: TimesheetsService) {} @Post() @RolesAllowed(RoleEnum.ACCOUNTING, RoleEnum.ADMIN, RoleEnum.EMPLOYEE, RoleEnum.HR, RoleEnum.SUPERVISOR) + @ApiOperation({ summary: 'Create timesheet' }) + @ApiResponse({ status: 201, description: 'Timesheet created', type: TimesheetEntity }) + @ApiResponse({ status: 400, description: 'Incomplete task or invalid data' }) create(@Body() dto: CreateTimesheetDto): Promise { return this.timesheetsService.create(dto); } @Get() @RolesAllowed(RoleEnum.ACCOUNTING, RoleEnum.ADMIN, RoleEnum.EMPLOYEE, RoleEnum.HR, RoleEnum.SUPERVISOR) + @ApiOperation({ summary: 'Find all timesheets' }) + @ApiResponse({ status: 201, description: 'List of timesheet found', type: TimesheetEntity, isArray: true }) + @ApiResponse({ status: 400, description: 'List of timesheets not found' }) findAll(): Promise { return this.timesheetsService.findAll(); } @Get(':id') @RolesAllowed(RoleEnum.ACCOUNTING, RoleEnum.ADMIN, RoleEnum.EMPLOYEE, RoleEnum.HR, RoleEnum.SUPERVISOR) + @ApiOperation({ summary: 'Find timesheet' }) + @ApiResponse({ status: 201, description: 'Timesheet found', type: TimesheetEntity }) + @ApiResponse({ status: 400, description: 'Timesheet not found' }) findOne(@Param('id', ParseIntPipe) id: number): Promise { return this.timesheetsService.findOne(id); } @Patch(':id') @RolesAllowed(RoleEnum.ACCOUNTING, RoleEnum.ADMIN, RoleEnum.HR, RoleEnum.SUPERVISOR) + @ApiOperation({ summary: 'Update timesheet' }) + @ApiResponse({ status: 201, description: 'Timesheet updated', type: TimesheetEntity }) + @ApiResponse({ status: 400, description: 'Timesheet not found' }) update( @Param('id', ParseIntPipe) id:number, @Body() dto: UpdateTimesheetDto, @@ -39,6 +57,9 @@ export class TimesheetsController { @Delete(':id') @RolesAllowed(RoleEnum.ACCOUNTING, RoleEnum.ADMIN, RoleEnum.HR, RoleEnum.SUPERVISOR) + @ApiOperation({ summary: 'Delete timesheet' }) + @ApiResponse({ status: 201, description: 'Timesheet deleted', type: TimesheetEntity }) + @ApiResponse({ status: 400, description: 'Timesheet not found' }) remove(@Param('id', ParseIntPipe) id: number): Promise { return this.timesheetsService.remove(id); } diff --git a/src/modules/timesheets/dtos/create-timesheet.dto.ts b/src/modules/timesheets/dtos/create-timesheet.dto.ts index 8194cc1..26b4e63 100644 --- a/src/modules/timesheets/dtos/create-timesheet.dto.ts +++ b/src/modules/timesheets/dtos/create-timesheet.dto.ts @@ -1,12 +1,21 @@ +import { ApiProperty } from "@nestjs/swagger"; import { Type } from "class-transformer"; import { IsBoolean, IsInt, IsOptional } from "class-validator"; export class CreateTimesheetDto { + @ApiProperty({ + example: '426433', + description: 'employee`s ID number of linked timsheet', + }) @Type(() => Number) @IsInt() employee_id: number; + @ApiProperty({ + example: 'True or False or Pending or Denied or Cancelled or Escalated', + description: 'Timesheet`s approval status', + }) @IsOptional() @IsBoolean() is_approved?: boolean; diff --git a/src/modules/timesheets/dtos/swagger-entities/timesheet.entity.ts b/src/modules/timesheets/dtos/swagger-entities/timesheet.entity.ts new file mode 100644 index 0000000..617d143 --- /dev/null +++ b/src/modules/timesheets/dtos/swagger-entities/timesheet.entity.ts @@ -0,0 +1,21 @@ +import { ApiProperty } from '@nestjs/swagger'; + +export class TimesheetEntity { + @ApiProperty({ + example: 1, + description: 'timesheet`s unique ID (auto-generated)', + }) + id: number; + + @ApiProperty({ + example: 426433, + description: 'employee`s ID number of linked timsheet', + }) + employee_id: number; + + @ApiProperty({ + example: true, + description: 'Timesheet`s approval status', + }) + is_approved: boolean; +} diff --git a/src/modules/users-management/swagger-entities/user.entity.ts b/src/modules/users-management/swagger-entities/user.entity.ts new file mode 100644 index 0000000..61883ab --- /dev/null +++ b/src/modules/users-management/swagger-entities/user.entity.ts @@ -0,0 +1,48 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { Roles } from '@prisma/client'; + +export class UserEntity { + @ApiProperty({ + example: 'd67f05be-6dd1-464f-b5f7-31b325e21b4a', + description: 'User`s unique UUID (primary key)', + }) + id: string; + + @ApiProperty({ + example: 'Aragorn', + description: 'user`s first name', + }) + first_name: string; + + @ApiProperty({ + example: 'Elessar', + description: 'user`s last name', + }) + last_name: string; + + @ApiProperty({ + example: 'king@arnor-gondor.gov', + description: 'Unique email address', + }) + email: string; + + @ApiProperty({ + example: 5141234567, + description: 'Unique phone number', + }) + phone_number: number; + + @ApiProperty({ + example: 'Minas Tirith, Gondor', + description: 'residence address (optional)', + required: false, + }) + residence?: string; + + @ApiProperty({ + example: 'EMPLOYEE', + enum: Roles, + description: 'User`s given role', + }) + role: Roles; +}