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
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# yqni13 | $\texttt{\color{cornflowerblue}{SUPPORT}}$
### $\textsf{\color{brown}{v1.6.2}}$
### $\textsf{\color{brown}{v1.6.3}}$

#### Support hub - handling feedback/ratings (`/feedback`) and bug/support requests (`/tickets`) including file attachments across multiple applications via REST API. Built with NodeJS (Typescript), Express & PostgreSQL in Docker container using API-Key authentication and rate-limiting. Created following Test-Driven Development (450+ tests) and hosting env:prod via Render, Neon and Cloudflare.

Expand Down Expand Up @@ -215,8 +215,8 @@ Preventing an unwanted merge with unfinished/failed test run, the project is set
[see changelog for all updates](/docs/CHANGELOG.md)


$\textsf{[v1.6.1\ =>\ {\textbf{\color{brown}v1.6.2}]}}$ app<br>
- $\textsf{\color{orange}Patch:}$ Updated 'tickets' routes to prepare for HTTP method .query() to use for path '/search' (not available on current version of Express.js).
$\textsf{[v1.6.2\ =>\ {\textbf{\color{brown}v1.6.3}]}}$ app<br>
- $\textsf{\color{orange}Patch:}$ Updated number parsing by removing unary "+" operator with Number() fn.

<br>

Expand Down
2 changes: 1 addition & 1 deletion backend/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "support_backend",
"version": "1.6.2",
"version": "1.6.3",
"appMeta": {
"db_version": "1.6.0",
"docker_image": "yqni13/support",
Expand Down
4 changes: 2 additions & 2 deletions backend/src/controllers/feedback.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ class FeedbackController {
async getFeedback(req: Request, res: Response, next: NextFunction) {
try {
checkValidation(req);
const id = (+req.params.id) as FeedbackId;
const id = (Number(req.params.id)) as FeedbackId;
const response: FeedbackResponseDTO | null = await feedbackService.getFeedbackById(id);
res.json(response);
} catch(err: any) {
Expand Down Expand Up @@ -45,7 +45,7 @@ class FeedbackController {
async patchFeedbackReview(req: Request, res: Response, next: NextFunction) {
try {
checkValidation(req);
const id = (+req.params.id) as FeedbackId;
const id = (Number(req.params.id)) as FeedbackId;
const response: FeedbackResponseDTO | null = await feedbackService.updateFeedbackReview(id);
res.json(response);
} catch(err: any) {
Expand Down
6 changes: 3 additions & 3 deletions backend/src/controllers/meta.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ class MetaController {
async getMetaById(req: Request, res: Response, next: NextFunction) {
try {
checkValidation(req);
const id = (+req.params.id) as MetaId;
const id = (Number(req.params.id)) as MetaId;
const response: MetaResponseDTO | null = await metaService.getMetaById(id);
res.json(response);
} catch(err: any) {
Expand Down Expand Up @@ -39,7 +39,7 @@ class MetaController {
async patchMeta(req: Request, res: Response, next: NextFunction) {
try {
checkValidation(req);
const id = (+req.params.id) as MetaId;
const id = (Number(req.params.id)) as MetaId;
const dto: MetaUpdateDTO = req.body;
const response: MetaResponseDTO | null = await metaService.updateMeta(id, dto);
res.json(response);
Expand All @@ -62,7 +62,7 @@ class MetaController {
async patchMaintenanceMode(req: Request, res: Response, next: NextFunction) {
try {
checkValidation(req);
const id = (+req.params.id) as MetaId;
const id = (Number(req.params.id)) as MetaId;
const dto: MaintenanceUpdateDTO = req.body;
const response: MaintenanceResponseDTO | null = await metaService.updateMaintenanceMode(id, dto);
res.json(response);
Expand Down
2 changes: 1 addition & 1 deletion backend/src/models/feedback-rating.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ class FeedbackRatingModel {
toFeedbackRatingResponseDTO(entity: FeedbackRating, extended: false): FeedbackRatingResponseDTO;

toFeedbackRatingResponseDTO(entity: FeedbackRating, extended: boolean) {
const delta: number = +((entity.rating_sum / entity.count).toFixed(1));
const delta: number = Number((entity.rating_sum / entity.count).toFixed(1));
if(extended) {
return {
client_id: entity.client_id,
Expand Down
2 changes: 1 addition & 1 deletion backend/tests/db-migrations.setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export async function runMigrations(testfile: string = 'unknown') {
await migrate({
databaseUrl: {
host: DB_TEST_HOST,
port: +(DB_TEST_PORT), // convert string to number
port: Number(DB_TEST_PORT),
user: DB_TEST_USER,
password: DB_TEST_PASS,
database: DB_TEST_DATABASE
Expand Down
10 changes: 4 additions & 6 deletions backend/tests/unit-tests/middleware/engines.middleware.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@ import { Violation } from "../../../src/utils/enums/violations.enum";
import { default as mockId } from "../../mock-data/id.mock-data.json";
import { ClientsId } from "../../../src/repositories/interfaces/clients.entity.interface";
import { UsersId } from "../../../src/repositories/interfaces/users.entity.interface";

// Ensure correct type by converting secret to number via unary + operator.
import { secrets } from "../../../src/utils/secrets.utils";

describe('Unit-tests (middleware), priority: class RateLimitsEngine', () => {
Expand Down Expand Up @@ -42,7 +40,7 @@ describe('Unit-tests (middleware), priority: class RateLimitsEngine', () => {
jest.spyOn(rateLimitsService, 'createRateLimit').mockImplementation();
jest.spyOn(ClientsDailyLimitRule.prototype, 'check').mockResolvedValue(null);

const rule: RateLimitsRule = new ClientsDailyLimitRule(+secrets.RATELIMITS_CLIENTSDAILYLIMIT);
const rule: RateLimitsRule = new ClientsDailyLimitRule(Number(secrets.RATELIMITS_CLIENTSDAILYLIMIT));
const count: RateLimitsCount = new RateLimitsIncrement();
const engine = new RateLimitsEngine([rule], count);

Expand All @@ -65,7 +63,7 @@ describe('Unit-tests (middleware), priority: class RateLimitsEngine', () => {

jest.spyOn(ClientsDailyLimitRule.prototype, 'check').mockResolvedValue(response);

const rule: RateLimitsRule = new ClientsDailyLimitRule(+secrets.RATELIMITS_CLIENTSDAILYLIMIT);
const rule: RateLimitsRule = new ClientsDailyLimitRule(Number(secrets.RATELIMITS_CLIENTSDAILYLIMIT));
const count: RateLimitsCount = new RateLimitsIncrement();
const engine = new RateLimitsEngine([rule], count);

Expand All @@ -85,7 +83,7 @@ describe('Unit-tests (middleware), priority: class RateLimitsEngine', () => {
jest.spyOn(demoLimitsService, 'createDemoLimit').mockImplementation();
jest.spyOn(DemoDailyLimitRule.prototype, 'check').mockResolvedValue(null);

const rule: RateLimitsRule = new DemoDailyLimitRule(+secrets.DEMOLIMITS_TOTALDAILYLIMIT);
const rule: RateLimitsRule = new DemoDailyLimitRule(Number(secrets.DEMOLIMITS_TOTALDAILYLIMIT));
const count: RateLimitsCount = new DemoLimitsIncrement();
const engine = new RateLimitsEngine([rule], count);

Expand All @@ -103,7 +101,7 @@ describe('Unit-tests (middleware), priority: class RateLimitsEngine', () => {

jest.spyOn(DemoDailyLimitRule.prototype, 'check').mockResolvedValue(response);

const rule: RateLimitsRule = new DemoDailyLimitRule(+secrets.DEMOLIMITS_TOTALDAILYLIMIT);
const rule: RateLimitsRule = new DemoDailyLimitRule(Number(secrets.DEMOLIMITS_TOTALDAILYLIMIT));
const count: RateLimitsCount = new DemoLimitsIncrement();
const engine = new RateLimitsEngine([rule], count);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,6 @@ import { TicketOption } from "../../../../src/utils/enums/ticket-option.enum";
import { ClientsId } from "../../../../src/repositories/interfaces/clients.entity.interface";
import { UsersId } from "../../../../src/repositories/interfaces/users.entity.interface";
import { TicketsId } from "../../../../src/repositories/interfaces/tickets.entity.interface";

// Ensure correct type by converting secret to number via unary + operator.
import { secrets } from "../../../../src/utils/secrets.utils"
import { MetaId } from "../../../../src/repositories/interfaces/meta.entity.interface";

Expand Down Expand Up @@ -168,8 +166,8 @@ describe('Unit-tests (middleware), priority: implementation RateLimitsRule', ()
describe('Testing valid context calls', () => {

test('Params: <RateLimitContext> number of calls within daily limit', async () => {
const ruleCDL = new ClientsDailyLimitRule(+secrets.RATELIMITS_CLIENTSDAILYLIMIT);
const mockCount = +(secrets.RATELIMITS_CLIENTSDAILYLIMIT) - 1;
const ruleCDL = new ClientsDailyLimitRule(Number(secrets.RATELIMITS_CLIENTSDAILYLIMIT));
const mockCount = Number(secrets.RATELIMITS_CLIENTSDAILYLIMIT) - 1;

jest.spyOn(rateLimitsService, 'getRateLimitCount').mockResolvedValue(mockCount);
const expectResult = null;
Expand All @@ -179,7 +177,7 @@ describe('Unit-tests (middleware), priority: implementation RateLimitsRule', ()
})

test('Params: <RateLimitContext> no existing entry', async () => {
const ruleCDL = new ClientsDailyLimitRule(+secrets.RATELIMITS_CLIENTSDAILYLIMIT);
const ruleCDL = new ClientsDailyLimitRule(Number(secrets.RATELIMITS_CLIENTSDAILYLIMIT));
const mockCount = 0;

jest.spyOn(rateLimitsService, 'getRateLimitCount').mockResolvedValue(mockCount);
Expand All @@ -193,8 +191,8 @@ describe('Unit-tests (middleware), priority: implementation RateLimitsRule', ()
describe('Testing invalid context calls', () => {

test('Params: <RateLimitContext> number of calls beyond daily limit', async () => {
const ruleCDL = new ClientsDailyLimitRule(+secrets.RATELIMITS_CLIENTSDAILYLIMIT);
const mockCount = +secrets.RATELIMITS_CLIENTSDAILYLIMIT;
const ruleCDL = new ClientsDailyLimitRule(Number(secrets.RATELIMITS_CLIENTSDAILYLIMIT));
const mockCount = Number(secrets.RATELIMITS_CLIENTSDAILYLIMIT);
const testParam_data = structuredClone(mockParam_data);
Object.assign(testParam_data, { clients: { flag: null }});

Expand Down Expand Up @@ -222,8 +220,8 @@ describe('Unit-tests (middleware), priority: implementation RateLimitsRule', ()
describe('Testing valid context calls', () => {

test('Params: <RateLimitContext> number of calls within daily limit', async () => {
const ruleUDL = new UsersDailyLimitRule(+secrets.RATELIMITS_USERSDAILYLIMIT);
const mockCount = +(secrets.RATELIMITS_USERSDAILYLIMIT) - 1;
const ruleUDL = new UsersDailyLimitRule(Number(secrets.RATELIMITS_USERSDAILYLIMIT));
const mockCount = Number(secrets.RATELIMITS_USERSDAILYLIMIT) - 1;

jest.spyOn(rateLimitsService, 'getRateLimitCount').mockResolvedValue(mockCount);
const expectResult = null;
Expand All @@ -233,7 +231,7 @@ describe('Unit-tests (middleware), priority: implementation RateLimitsRule', ()
})

test('Params: <RateLimitContext> no existing entry', async () => {
const ruleUDL = new UsersDailyLimitRule(+secrets.RATELIMITS_USERSDAILYLIMIT);
const ruleUDL = new UsersDailyLimitRule(Number(secrets.RATELIMITS_USERSDAILYLIMIT));
const mockCount = 0;

jest.spyOn(rateLimitsService, 'getRateLimitCount').mockResolvedValue(mockCount);
Expand All @@ -247,8 +245,8 @@ describe('Unit-tests (middleware), priority: implementation RateLimitsRule', ()
describe('Testing invalid context calls', () => {

test('Params: <RateLimitContext> number of calls beyond daily limit', async () => {
const ruleUDL = new UsersDailyLimitRule(+secrets.RATELIMITS_USERSDAILYLIMIT);
const mockCount = +secrets.RATELIMITS_USERSDAILYLIMIT;
const ruleUDL = new UsersDailyLimitRule(Number(secrets.RATELIMITS_USERSDAILYLIMIT));
const mockCount = Number(secrets.RATELIMITS_USERSDAILYLIMIT);
const testParam_data = structuredClone(mockParam_data);
Object.assign(testParam_data, { users: { flag: null }});

Expand Down Expand Up @@ -276,8 +274,8 @@ describe('Unit-tests (middleware), priority: implementation RateLimitsRule', ()
describe('Testing valid context calls', () => {

test('Params: <RateLimitContext> number of calls within daily limit', async () => {
const ruleTDL = new TotalDailyLimitRule(+secrets.RATELIMITS_TOTALDAILYLIMIT);
const mockCount = +(secrets.RATELIMITS_TOTALDAILYLIMIT) - 1;
const ruleTDL = new TotalDailyLimitRule(Number(secrets.RATELIMITS_TOTALDAILYLIMIT));
const mockCount = Number(secrets.RATELIMITS_TOTALDAILYLIMIT) - 1;

jest.spyOn(rateLimitsService, 'getRateLimitCount').mockResolvedValue(mockCount);
const expectResult = null;
Expand All @@ -287,7 +285,7 @@ describe('Unit-tests (middleware), priority: implementation RateLimitsRule', ()
})

test('Params: <RateLimitContext> no existing entry', async () => {
const ruleTDL = new TotalDailyLimitRule(+secrets.RATELIMITS_TOTALDAILYLIMIT);
const ruleTDL = new TotalDailyLimitRule(Number(secrets.RATELIMITS_TOTALDAILYLIMIT));
const mockCount = 0;

jest.spyOn(rateLimitsService, 'getRateLimitCount').mockResolvedValue(mockCount);
Expand All @@ -301,8 +299,8 @@ describe('Unit-tests (middleware), priority: implementation RateLimitsRule', ()
describe('Testing invalid context calls', () => {

test('Params: <RateLimitContext> number of calls beyond daily limit', async () => {
const ruleTDL = new TotalDailyLimitRule(+secrets.RATELIMITS_TOTALDAILYLIMIT);
const mockCount = +secrets.RATELIMITS_TOTALDAILYLIMIT;
const ruleTDL = new TotalDailyLimitRule(Number(secrets.RATELIMITS_TOTALDAILYLIMIT));
const mockCount = Number(secrets.RATELIMITS_TOTALDAILYLIMIT);

jest.spyOn(rateLimitsService, 'getRateLimitCount').mockResolvedValue(mockCount);
jest.spyOn(CommonUtils, 'getNextDayUTC').mockReturnValue(mockRetryAfter);
Expand All @@ -328,8 +326,8 @@ describe('Unit-tests (middleware), priority: implementation RateLimitsRule', ()
describe('Testing valid context calls', () => {

test('Params: <RateLimitContext> number of calls within daily limit', async () => {
const ruleDDL = new DemoDailyLimitRule(+secrets.DEMOLIMITS_TOTALDAILYLIMIT);
const mockCount = +(secrets.DEMOLIMITS_TOTALDAILYLIMIT) - 1;
const ruleDDL = new DemoDailyLimitRule(Number(secrets.DEMOLIMITS_TOTALDAILYLIMIT));
const mockCount = Number(secrets.DEMOLIMITS_TOTALDAILYLIMIT) - 1;

jest.spyOn(demoLimitsService, 'getDemoLimitCount').mockResolvedValue(mockCount);
const expectResult = null;
Expand All @@ -339,7 +337,7 @@ describe('Unit-tests (middleware), priority: implementation RateLimitsRule', ()
})

test('Params: <RateLimitContext> no existing entry', async () => {
const ruleDDL = new DemoDailyLimitRule(+secrets.DEMOLIMITS_TOTALDAILYLIMIT);
const ruleDDL = new DemoDailyLimitRule(Number(secrets.DEMOLIMITS_TOTALDAILYLIMIT));
const mockCount = 0;

jest.spyOn(demoLimitsService, 'getDemoLimitCount').mockResolvedValue(mockCount);
Expand All @@ -353,8 +351,8 @@ describe('Unit-tests (middleware), priority: implementation RateLimitsRule', ()
describe('Testing invalid context calls', () => {

test('Params: <RateLimitContext> number of calls beyond daily limit', async () => {
const ruleDDL = new DemoDailyLimitRule(+secrets.DEMOLIMITS_TOTALDAILYLIMIT);
const mockCount = +secrets.DEMOLIMITS_TOTALDAILYLIMIT;
const ruleDDL = new DemoDailyLimitRule(Number(secrets.DEMOLIMITS_TOTALDAILYLIMIT));
const mockCount = Number(secrets.DEMOLIMITS_TOTALDAILYLIMIT);

jest.spyOn(demoLimitsService, 'getDemoLimitCount').mockResolvedValue(mockCount);
jest.spyOn(CommonUtils, 'getNextDayUTC').mockReturnValue(mockRetryAfter);
Expand Down
9 changes: 8 additions & 1 deletion docs/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
## 📜 $\textsf{\color{salmon}{List\ of\ updates}}$

### $\textsf{\color{skyblue}2026/08/03}$

$\textsf{[v1.6.2\ =>\ {\textbf{\color{brown}v1.6.3}]}}$ app<br>
- $\textsf{\color{orange}Patch:}$ Updated number parsing by removing unary "+" operator with Number() fn.

<br>

### $\textsf{\color{skyblue}2026/07/04}$

$\textsf{[v1.6.1\ =>\ {\textbf{\color{brown}v1.6.2}]}}$ app<br>
$\textsf{[v1.6.1\ =>\ v1.6.2]}$ app<br>
- $\textsf{\color{orange}Patch:}$ Updated 'tickets' routes to prepare for HTTP method .query() to use for path '/search' (not available on current version of Express.js).

<br>
Expand Down
Loading