Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 15 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# yqni13 | support
$\texttt{\color{teal}{v1.3.5}}$
$\texttt{\color{teal}{v1.4.1}}$


<br>
Expand Down Expand Up @@ -43,6 +43,7 @@ The development process is structured by the TDD (test driven development) princ

<dl>
<dd>🪲 support/bug/feedback-ticket handling including client + user data</dd>
<dd>✨ counting/adding up ratings and administer rating average</dd>
<dd>📂 file handling (upload/delete) from requests + cloud storage</dd>
<dd>:mag: filtered search for ticket + user data (properties + timespan)</dd>
<dd>:closed_lock_with_key: en/disable application (maintenance mode) triggered by request/logic</dd>
Expand All @@ -54,7 +55,13 @@ The development process is structured by the TDD (test driven development) princ

### $\textsf{\color{teal}Tickets}$

Documentation follows with finished feedback handling (task: SUPPORT-64).
Documentation follows with finished refactoring (task: SUPPORT-65).

<br>

### $\textsf{\color{teal}Feedback/Rating}$

Documentation follows with finished refactoring (task: SUPPORT-65).

<br>

Expand Down Expand Up @@ -118,7 +125,7 @@ Install the packages `@jest/globals`, `@types/jest`, `supertest`, `@testcontaine
```sh
npm install jest @jest/globals @types/jest supertest @testcontainers/postgresql testcontainers --save-dev
```
350+ tests exist currently for models, utils, validators and workflows (integration tests) - see [tests](./backend/tests).<br>
450+ tests exist currently for models, utils, validators and workflows (integration tests) - see [tests](./backend/tests).<br>
Run tests on local device by including setup for dotenv/config to provide environment variables:
```sh
set NODE_ENV=test && jest --setupFiles dotenv/config
Expand Down Expand Up @@ -153,11 +160,12 @@ Preventing an unwanted merge with unfinished/failed test run, the project is set

### $\textsf{\color{forestgreen}last update:}$

$\textsf{[v1.3.4\ =>\ {\textbf{\color{brown}v1.3.5}]}}$ app<br>
$\textsf{[v1.5.3\ =>\ {\textbf{\color{brown}v1.5.4}]}}$ database
$\textsf{[v1.3.5\ =>\ {\textbf{\color{brown}v1.4.1}]}}$ app<br>
$\textsf{[v1.5.4\ =>\ {\textbf{\color{brown}v1.6.0}]}}$ database
- $\textsf{\color{teal}Addition:}$ Added api route + logic for Feedback/FeedbackRating to add new feedback/rating or get current average rating value.
- $\textsf{\color{orange}Patch:}$ Updated:
+ new migration to add properties 'info_browser', 'info_os' and 'info_device' as well as property 'title' with default value 'support_ticket' to table 'tickets' and increased max length of property 'message' up to 5000 characters
+ ticket validation/handling/tests to inlcude new & changed properties
+ testing whole process (request-to-response) with individual injection of middlewares when necessary.
+ database with new migration to add tables 'feedback_entries' and 'feedback_ratings' to handle single feedback/ratings seperately from accumulated average rating.

<br>

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
/**
* @type {import('node-pg-migrate').ColumnDefinitions | undefined}
*/
const shorthands = undefined;

/**
* @param {import('node-pg-migrate').MigrationBuilder} pgm
*/
async function up(pgm) {
pgm.createTable('feedback_entries', {
feedback_id: {
type: 'serial',
primaryKey: true
},
client_id: {
type: 'uuid',
notNull: true,
references: '"clients"(client_id)',
onDelete: 'CASCADE',
onUpdate: 'CASCADE'
},
user_id: {
type: 'uuid',
notNull: true,
references: '"users"(user_id)',
onDelete: 'CASCADE',
onUpdate: 'CASCADE'
},
rating: {
type: 'smallint',
notNull: true,
default: 5
},
term_accepted: {
type: 'boolean',
notNull: true,
default: false
},
message: {
type: 'text',
notNull: false
},
reviewed_on: {
type: 'TIMESTAMP WITH TIME ZONE',
notNull: false
},
last_modified: {
type: 'TIMESTAMP WITH TIME ZONE',
notNull: true,
default: 'NOW()'
},
created_on: {
type: 'TIMESTAMP WITH TIME ZONE',
notNull: true,
default: 'NOW()'
}
});
pgm.addConstraint(
'feedback_entries',
'feedback_entries_unique_clients_users',
{ unique: ['client_id', 'user_id'] }
);
pgm.sql(`ALTER SEQUENCE feedback_entries_feedback_id_seq RESTART WITH 1;`);
pgm.createTable('feedback_ratings', {
client_id: {
type: 'uuid',
primaryKey: true,
references: '"clients"(client_id)',
onDelete: 'CASCADE',
onUpdate: 'CASCADE'
},
count: {
type: 'integer',
notNull: true,
default: 0
},
rating_sum: {
type: 'integer',
notNull: true,
default: 0
},
last_modified: {
type: 'TIMESTAMP WITH TIME ZONE',
notNull: true,
default: 'NOW()'
},
created_on: {
type: 'TIMESTAMP WITH TIME ZONE',
notNull: true,
Comment thread
yqni13 marked this conversation as resolved.
default: 'NOW()'
}
});
};

/**
* @param {import('node-pg-migrate').MigrationBuilder} pgm
*/
async function down(pgm) {
pgm.dropTable('feedback_entries', {
ifExists: true,
cascade: true
});
pgm.dropTable('feedback_ratings', {
ifExists: true,
cascade: true
});
};

module.exports = { shorthands, up, down };
2 changes: 1 addition & 1 deletion backend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions backend/package.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
{
"name": "support_backend",
"version": "1.3.5",
"version": "1.4.1",
"appMeta": {
"db_version": "1.5.4",
"db_version": "1.6.0",
"docker_image": "yqni13/support",
"docker_version": "1.0.6",
"jenkins_version": "0.0.0"
Expand Down
2 changes: 2 additions & 0 deletions backend/src/configs/db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import { logError } from '../utils/common.utils';
// Global setting to parse certain db data to specific types:
// 1082: type Date [yyyy-mm-dd] - otherwise Date will be returned as full timestamp + time zone changes
pg.types.setTypeParser(1082, (val) => val);
// 1184: tell pg driver how to parse timestamp with time zone
pg.types.setTypeParser(1184, (val) => new Date(val + "Z"));

export class DBConnection {
private static instance: DBConnection;
Expand Down
39 changes: 39 additions & 0 deletions backend/src/controllers/feedback-rating.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { NextFunction, Request, Response } from "express";
import { checkValidation } from "../middleware/validation.middleware";
import feedbackRatingService from "../services/feedback-rating.service";
import { FeedbackRatingExtendedResponseDTO, FeedbackRatingResponseDTO } from "../dtos/feedback-rating.dto";

class FeedbackRatingController {
async getExtendedFeedbackRating(req: Request, res: Response, next: NextFunction) {
try {
checkValidation(req);
const id = req.params.id;
const response: FeedbackRatingExtendedResponseDTO | null = await feedbackRatingService.getExtendedFeedbackRatingById(id);
res.json(response);
} catch(err: any) {
next(err);
}
}

async getFeedbackRating(req: Request, res: Response, next: NextFunction) {
try {
checkValidation(req);
const client_name = req.params.client_name;
const response: FeedbackRatingResponseDTO | null = await feedbackRatingService.getFeedbackRatingByClientName(client_name);
res.json(response);
} catch(err: any) {
next(err);
}
}

async getAllFeedbackRatings(req: Request, res: Response, next: NextFunction) {
try {
const response: FeedbackRatingExtendedResponseDTO[] | null = await feedbackRatingService.getAllFeedbackRatings();
res.json(response);
} catch(err: any) {
next(err);
}
}
}

export default new FeedbackRatingController();
56 changes: 56 additions & 0 deletions backend/src/controllers/feedback.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { NextFunction, Request, Response } from "express";
import { checkValidation } from "../middleware/validation.middleware";
import { FeedbackCreateDTO, FeedbackFilterDTO, FeedbackResponseDTO } from "../dtos/feedback.dto";
import feedbackService from "../services/feedback.service";

class FeedbackController {
async getFeedback(req: Request, res: Response, next: NextFunction) {
try {
checkValidation(req);
const id = +req.params.id;
const response: FeedbackResponseDTO | null = await feedbackService.getFeedbackById(id);
res.json(response);
} catch(err: any) {
next(err);
}
}

async postFeedbackEntriesSearch(req: Request, res: Response, next: NextFunction) {
try {
checkValidation(req);
const dto: FeedbackFilterDTO = req.body;
const response: FeedbackResponseDTO[] | null = await feedbackService.searchFeedbackEntriesByFilter(dto);
res.json(response);
} catch(err: any) {
next(err);
}
}

async postFeedback(req: Request, res: Response, next: NextFunction) {
try {
checkValidation(req);
const dto: FeedbackCreateDTO = {
...req.body,
client_id: req.apiClients.client_id,
user_id: req.apiUsers.user_id
};
const response: FeedbackResponseDTO | null = await feedbackService.createFeedback(dto);
res.json(response);
} catch(err: any) {
next(err);
}
}

async patchFeedbackReview(req: Request, res: Response, next: NextFunction) {
try {
checkValidation(req);
const id = +req.params.id;
const response: FeedbackResponseDTO | null = await feedbackService.updateFeedbackReview(id);
res.json(response);
} catch(err: any) {
next(err);
}
}
}

export default new FeedbackController();
23 changes: 23 additions & 0 deletions backend/src/dtos/feedback-rating.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
export interface FeedbackRatingCreateDTO {
client_id: string,
count?: number,
rating_sum?: number
}

export interface FeedbackRatingUpdateDTO {
count?: number,
rating: number,
last_modified?: string
}

export interface FeedbackRatingResponseDTO {
rating_average: number,
}

export interface FeedbackRatingExtendedResponseDTO extends FeedbackRatingResponseDTO {
client_id: string,
count: number,
rating_sum: number,
last_modified: string,
created_on: string
}
43 changes: 43 additions & 0 deletions backend/src/dtos/feedback.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
export interface FeedbackCreateDTO {
client_id: string,
user_id: string,
rating: number,
term_accepted: boolean,
message?: string,
}

export interface FeedbackRequestCreateDTO {
user_email: string,
rating: number,
term_accepted: boolean,
message?: string
}

export interface FeedbackUpdateReviewDTO {
reviewed_on: string,
last_modified: string
}

export interface FeedbackFilterDTO {
client_id?: string | string[],
user_id?: string | string[],
rating?: number | number[],
term_accepted?: boolean,
reviewed_on?: string | string[],
last_modified?: string | string[],
created_on?: string | string[]
}

export interface FeedbackResponseDTO {
feedback_id: number,
client_id: string,
user_id: string,
rating: number,
rating_average_new?: number,
rating_old?: number,
term_accepted: boolean,
message?: string,
reviewed_on?: string,
last_modified: string,
created_on: string
}
4 changes: 4 additions & 0 deletions backend/src/loaders/routes.loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,14 @@ import metaRouter from '../routes/meta.route';
import ticketsRouter from '../routes/tickets.route';
import usersRouter from '../routes/users.route';
import testRouter from '../routes/test.route';
import feedbackRouter from '../routes/feedback.route';
import feedbackRatingRouter from '../routes/feedback-rating.route';

export class RoutesLoader {
static initRoutes(app: Application, version: string) {
app.use(`/api/${version}/clients`, clientsRouter);
app.use(`/api/${version}/feedback`, feedbackRouter);
app.use(`/api/${version}/feedback-rating`, feedbackRatingRouter);
app.use(`/api/${version}/mailing`, mailingRouter);
app.use(`/api/${version}/meta`, metaRouter);
app.use(`/api/${version}/test`, testRouter);
Expand Down
2 changes: 2 additions & 0 deletions backend/src/middleware/auth.user.middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { UsersCreateDTO } from "../dtos/users.dto";
import { UserStatus } from "../utils/enums/user-status.enum";
import { logError } from "../utils/common.utils";
import { Flag } from "../utils/enums/flag.enum";
import * as CommonValidators from "../validation/common.validation";

/**
* @description Status validation of existing user or create new user by email address.
Expand All @@ -14,6 +15,7 @@ export function authUser() {
return async function(req: Request, res: Response, next: NextFunction) {
try {
const email = req.body.user_email;
CommonValidators.validateEmail(email);
let user: Users | null = await usersService.getUserByEmail(email);
if(!user) {
const dto: UsersCreateDTO = {
Expand Down
Loading