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
4 changes: 1 addition & 3 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,10 @@ jobs:

env:
NODE_ENV: ${{ secrets.NODE_ENV }}
SECRET_ADMIN_API: ${{ secrets.SECRET_ADMIN_API }}
SECRET_ADMIN_KEY: ${{ secrets.SECRET_ADMIN_KEY }}
SECRET_EMAIL_RECEIVER: ${{ secrets.SECRET_EMAIL_RECEIVER }}
SECRET_EMAIL_SENDER: ${{ secrets.SECRET_EMAIL_SENDER }}
SECRET_EMAIL_PASS: ${{ secrets.SECRET_EMAIL_PASS }}
SECRET_PRIVATE_KEY_ARTDV: none
SECRET_PRIVATE_KEY_TAVA: none
SECRET_BETTERSTACK_LOGGING_KEY: ${{ secrets.SECRET_BETTERSTACK_LOGGING_KEY }}
SECRET_BETTERSTACK_HOST: ${{ secrets.SECRET_BETTERSTACK_HOST }}
SECRET_DB_USER: ${{ secrets.SECRET_DB_USER }}
Expand Down
119 changes: 64 additions & 55 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,81 +1,89 @@
# yqni13 | support
$\texttt{\color{teal}{v1.4.1}}$
$\texttt{\color{teal}{v1.4.4}}$
Comment thread
yqni13 marked this conversation as resolved.

### 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. Created following Test-Driven Development (450+ tests including ephemeral database by testcontainers) and hosting env:prod via Render, Neon and Cloudflare.

<br>

<div>
<img src="assets/img/readme-bg.png" alt="logo">
<div align="center">
<a href="https://nodejs.org/en"><img src="assets/icons/nodejs.png" alt="NodeJS"></a>
<a href="https://expressjs.com/"><img src="assets/icons/express.png" alt="Express"></a>
<a href="https://jestjs.io/"><img src="assets/icons/jest.png" alt="Jest"></a>
<a href="https://neon.com/"><img src="assets/icons/neon.png" alt="Neon"></a>
<a href="https://www.docker.com/"><img src="assets/icons/docker.png" alt="Docker"></a>
<a href="https://www.jenkins.io/"><img src="assets/icons/jenkins.png" alt="Jenkins"></a>
<a href="https://www.postgresql.org/"><img src="assets/icons/postgresql.png" alt="PostgreSQL"></a>
<a href="https://www.cloudflare.com/de-de/application-services/products/cdn/"><img src="assets/icons/cloudflare.png" alt="Cloudflare"></a>
<a href="https://betterstack.com/"><img src="assets/icons/betterstack.png" alt="Betterstack"></a>
<a href="https://testcontainers.com/"><img src="assets/icons/testcontainers.png" alt="Testcontainers"></a>
</div>

### Technology

<div style="display:flex; align-items:center;">
<img src="assets/icons/nodejs.png" alt="NodeJS">
<img src="assets/icons/express.png" alt="Express">
<img src="assets/icons/jest.png" alt="Jest">
<img src="assets/icons/neon.png" alt="Neon">
</div>
<div style="display:flex; align-items:center;">
<img src="assets/icons/docker.png" alt="Docker">
<img src="assets/icons/jenkins.png" alt="Jenkins">
<img src="assets/icons/postgresql.png" alt="PostgreSQL">
</div>
<div style="display:flex; align-items:center;">
<img src="assets/icons/cloudflare.png" alt="Cloudflare">
<img src="assets/icons/betterstack.png" alt="Betterstack">
<img src="assets/icons/testcontainers.png" alt="Testcontainers">
</div>
<br><br>

<br>
## 🪄 $\textsf{\color{salmon}Getting started}$

## How to

### Build & Deploy
This application server will is hosted by <a href="https://render.com/">Render</a> in a Docker container and a PostgreSQL database hosted by Neon. Additionally a <a href="https://console.cron-job.org/">cron-job</a> is set up to keep the service alive on Render due to 15-min inactivity on free tier plan.<br>
The development process is structured by the TDD (test driven development) principle.
### $\textsf{\color{teal}Prerequisites}$
- node: v22+
- PostgreSQL v17+ (local or hosted like Neon)
- Docker v4.54+
- Cloudflare R2 bucket (file handling)
- Betterstack Telemetry (logging)

<br>

## Overview

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

<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>
<dd>:key: request verification by api-keys</dd>
<dd>🕵️ request rate limiting + violation handling</dd>
</dl>
### $\textsf{\color{teal}Local setup}$
Download or clone project
```sh
git clone https://github.com/yqni13/support
```
Create new .env file and fill in your credentials/other env data [(see docs)](./docs/CONFIGURATION.md).<br>
Navigate/cd into project directory ./backend and install dependencies via npm
```sh
npm ci
```
Run migrations [(see docs)](./docs/MIGRATION.md).<br>
Start application in local (development) environment:
```sh
npm run start:dev
```
Alternatively run application in Docker container [(see docs)](./docs/DEVOPS.md).

<br>

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

Documentation follows with finished refactoring (task: SUPPORT-65).
## 🧩 $\textsf{\color{salmon}Features}$
| Feature | Description |
|---------|-------------|
| 🪲 Ticket system | Handles support & bug reports per client with status lifecycle and optional file attachments |
| ✨ Feedback & Rating system | Abuse-resistant rating system with atomic aggregate updates - one active rating per user per client |
| 📂 Cloud file handling | Upload/delete via Cloudflare R2 (S3-compatible) - supporting pdf & images up to 1MB each, max 5 per ticket |
| 🔎 Filtered search | Query ticket and user data by properties and/or timespan |
| 🔐 Maintenance Mode | Enable/disable application triggered by request or internal logic |
| 🕵️ Rate limiting | Request throttling with violation handling |
| 🔑 API Key Auth | Client authentication via API keys |

<br>

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

Documentation follows with finished refactoring (task: SUPPORT-65).
User can utilize a feedback & rating system to rate the application in use and send criticism or praise. For every client can exist multiple entries for the entity `Feedback` but only one `FeedbackRating` which holds the accumulated data of the pointing feedback entries.<br>
Resubmissions are handled in the database by an `ON CONFLICT` upsert query [see upsertInTa()](./backend/src/repositories/feedback.repository.ts) on the unique `(client_id, user_id)` constraint, followed by an atomic aggregate update to the 'FeedbackRating' table entry. Both queries are executed within a single transaction to guarantee data consistency.<br>
The rating happens numerical (1-5) and returns an average rating value as number with up to 1 decimal place.<br>
Furthermore, if an existing feedback entry has a message stored, but is not reviewed, the feedback gets NOT updated and request throws a specific exception.

<br>

### $\textsf{\color{teal}File handling}$

User can attach files for any support/bug ticket to provide further information (screenshots, images, ...) on their message. Attachments are limited to upload up to `5` files and each file can be up to `1`mb [see validation](./backend/src/middleware/files/validate.files.middleware.ts). Currently only `images` (webp, jpg, jpeg, png) and `pdf` files are supported, but more will follow. Cloud in use is `Cloudflare` (see Figure 1) using S3Client for api communication and files will be deleted when a ticket is closed, canceled or expired (time check).
User can attach files for any support/bug ticket to provide further information (screenshots, images, ...) on their message. Attachments are limited to upload up to `5` files and each file can be up to `1`MB [see validation](./backend/src/middleware/files/validate.files.middleware.ts). Currently only `images` (webp, jpg, jpeg, png) and `pdf` files are supported, but more will follow. Cloud in use is `Cloudflare` (see Figure 1) using S3Client for api communication and files will be deleted when a ticket is closed, canceled or expired (time check).
<div align="center">
<img src="assets/img/cloudflare_demo.png" alt="&nbsp;Cloudflare upload demo">
Figure 1 - Cloudflare upload demo, v1.0.0
</div>

<br>

### $\textsf{\color{teal}Logging}$
## 📝 $\textsf{\color{salmon}Logging}$

To monitor errors the logging framework `Winston` is used in combination with Logtail from `Betterstack` as a Singleton: [config](./backend/src/logger/config.logger.ts)
<br>While working within local (DEV) or test environment, error messages are logged into the consoles. For the deployed environments (STAG/PROD) the logging is set to send logtails to Betterstack (longer storage time than app-hosting service). For easy access and monitoring of error messages, the Betterstack UI client dashboard comes in handy (see Figure 2). Additional meta data (environment + version numbers) help identifying and assigning errors.
Expand All @@ -86,7 +94,7 @@ To monitor errors the logging framework `Winston` is used in combination with Lo

<br>

## Testing
## 🔧 $\textsf{\color{salmon}Testing}$

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

Expand Down Expand Up @@ -125,7 +133,8 @@ Install the packages `@jest/globals`, `@types/jest`, `supertest`, `@testcontaine
```sh
npm install jest @jest/globals @types/jest supertest @testcontainers/postgresql testcontainers --save-dev
```
450+ 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>
Integration-Tests can only run with `active Docker service` due to the ephemeral (temporary) database by testcontainers.<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 @@ -155,22 +164,22 @@ Preventing an unwanted merge with unfinished/failed test run, the project is set

<br>

## Updates
## 📈 $\textsf{\color{salmon}Updates}$
[see changelog for all updates](/docs/CHANGELOG.md)

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

$\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{[v1.4.1\ =>\ {\textbf{\color{brown}v1.4.4}]}}$ app<br>
- $\textsf{\color{orange}Patch:}$ Updated:
+ 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.
+ entity ID's are using now nominal types instead basic string|number.
+ some model functions are renamed to keep consistency and improve readability.
+ some api routes have been shortened to keep consistency and improve readability.

<br>

### Update objectives:
<dl>
<dd>- caching layer</dd>
<dd>- background worker</dd>
<dd>- jenkins setup</dd>
<dd>- mail setup</dd>
</dl>
Binary file removed assets/img/logo.png
Binary file not shown.
Binary file removed assets/img/readme-bg.png
Binary file not shown.
Loading
Loading