k8s-deployment #1

Merged
darren merged 10 commits from k8s-deployment into main 2025-05-21 21:18:21 +00:00
31 changed files with 991 additions and 399 deletions

97
.gitea-ci.yml Normal file

@ -0,0 +1,97 @@
version: 1.0
workflow:
name: Deploy to Production
on:
push:
branches:
- main
jobs:
build-and-deploy:
name: Build and Deploy
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Login to Docker Registry
uses: docker/login-action@v2
with:
registry: registry.liquidrinu.com
username: ${{ secrets.REGISTRY_USERNAME }}
password: ${{ secrets.REGISTRY_PASSWORD }}
- name: Build and Push Backend
uses: docker/build-push-action@v4
with:
context: ./backend
push: true
tags: registry.liquidrinu.com/fusero-backend:latest
cache-from: type=registry,ref=registry.liquidrinu.com/fusero-backend:buildcache
cache-to: type=registry,ref=registry.liquidrinu.com/fusero-backend:buildcache,mode=max
- name: Build and Push Frontend
uses: docker/build-push-action@v4
with:
context: ./frontend
push: true
tags: registry.liquidrinu.com/fusero-frontend:latest
cache-from: type=registry,ref=registry.liquidrinu.com/fusero-frontend:buildcache
cache-to: type=registry,ref=registry.liquidrinu.com/fusero-frontend:buildcache,mode=max
- name: Install kubectl
uses: azure/setup-kubectl@v3
with:
version: "latest"
- name: Setup kubeconfig
run: |
mkdir -p $HOME/.kube
echo "${{ secrets.KUBE_CONFIG }}" | base64 -d > $HOME/.kube/config
chmod 600 $HOME/.kube/config
- name: Create secrets file
run: |
cat > ./chart/secrets.prod.yaml << EOF
backend:
env:
POSTGRES_PASSWORD: "${{ secrets.POSTGRES_PASSWORD }}"
DEFAULT_ADMIN_PASSWORD: "${{ secrets.DEFAULT_ADMIN_PASSWORD }}"
ENCRYPTION_KEY: "${{ secrets.ENCRYPTION_KEY }}"
JWT_SECRET: "${{ secrets.JWT_SECRET }}"
CHATGPT_API_KEY: "${{ secrets.CHATGPT_API_KEY }}"
CANVAS_API_KEY: "${{ secrets.CANVAS_API_KEY }}"
EOF
- name: Delete old migration/seed job
run: |
kubectl delete job fusero-backend-db-init -n fusero-prod || true
- name: Deploy to Kubernetes
run: |
helm upgrade --install fusero ./chart \
--namespace fusero-prod \
--create-namespace \
--values ./chart/values.prod.yaml \
--values ./chart/secrets.prod.yaml \
--set backend.image.repository=registry.liquidrinu.com/fusero-backend \
--set frontend.image.repository=registry.liquidrinu.com/fusero-frontend
- name: Wait for migration/seed job
run: |
kubectl wait --for=condition=complete --timeout=300s job/fusero-backend-db-init -n fusero-prod
JOB_STATUS=$(kubectl get job fusero-backend-db-init -n fusero-prod -o jsonpath='{.status.succeeded}')
if [ "$JOB_STATUS" != "1" ]; then
echo "Migration/seed job failed!" >&2
kubectl logs job/fusero-backend-db-init -n fusero-prod
exit 1
fi
- name: Verify Deployment
run: |
kubectl rollout status deployment/fusero-backend -n fusero-prod
kubectl rollout status deployment/fusero-frontend -n fusero-prod

30
.gitignore vendored

@ -114,4 +114,32 @@ test/types/index.js
dist
# general
bkups
docs
bkups
# k8s
secrets.yml
# helm
values.dev.*
values.prod.*
# Secrets
chart/secrets.prod.yaml
chart/secrets.*.yaml
*.env
.env.*
# Development values with secrets
chart/values.dev.yaml
# Production secrets
chart/secrets.prod.yaml
chart/secrets.*.yaml
# Keep templates and public configs
!chart/secrets.prod.template.yaml
!chart/values.prod.template.yaml
!chart/values.prod.public.yaml
.bkup/

@ -6,5 +6,6 @@
"tabWidth": 2,
"useTabs": false,
"printWidth": 144,
"bracketSpacing": true
"bracketSpacing": true,
"plugins": ["@helm/prettier-plugin-helm"]
}

764
README.md

@ -1,323 +1,559 @@
# Fusero App Boilerplate
# ⚡️ Fusero App Boilerplate
A full-stack application boilerplate with React frontend and Node.js backend.
A full-stack application boilerplate with a React frontend and Node.js backend — powered by Fastify, Vite, PostgreSQL, Docker, and optional Kubernetes & Helm support. Built for modern dev workflows and AI-powered backend endpoint generation.
## Project Structure
---
## 📚 Table of Contents
- [⚡️ Fusero App Boilerplate](#-fusero-app-boilerplate)
- [📚 Table of Contents](#-table-of-contents)
- [📁 Project Structure](#-project-structure)
- [⚙️ Prerequisites](#-prerequisites)
- [💻 Development Setup](#-development-setup)
- [To create a new migration:](#to-create-a-new-migration)
- [npm run migration:create](#npm-run-migrationcreate)
- [To apply migrations:](#to-apply-migrations)
- [To seed the database:](#to-seed-the-database)
- [Alternate: Running Services in Separate Terminals](#alternate-running-services-in-separate-terminals)
- [🛠️ Environment Setup](#-environment-setup)
- [For Kubernetes, these are set in chart/values.yaml:](#for-kubernetes-these-are-set-in-chartvaluesyaml)
- [POSTGRES\_NAME=fusero-boilerplate-db](#postgres_namefusero-boilerplate-db)
- [POSTGRES\_HOSTNAME=postgres-service](#postgres_hostnamepostgres-service)
- [POSTGRES\_PORT=19095](#postgres_port19095)
- [POSTGRES\_USER=root](#postgres_userroot)
- [POSTGRES\_PASSWORD=root123](#postgres_passwordroot123)
- [🐳 Docker Development](#-docker-development)
- [To create a new migration:](#to-create-a-new-migration-1)
- [npm run migration:create](#npm-run-migrationcreate-1)
- [To apply migrations:](#to-apply-migrations-1)
- [To seed the database:](#to-seed-the-database-1)
- [🚀 Kubernetes Deployment](#-kubernetes-deployment)
- [🌐 Frontend Routing in Production](#-frontend-routing-in-production)
- [🔐 HTTPS with Self-Signed Certificates](#-https-with-self-signed-certificates)
- [🧠 Development Best Practices](#-development-best-practices)
- [📘 API Documentation](#-api-documentation)
- [🧩 ChatGPT-Powered Endpoint Creation](#-chatgpt-powered-endpoint-creation)
- [🧪 Troubleshooting](#-troubleshooting)
- [🤝 Contributing](#-contributing)
- [📄 License](#-license)
- [Kubernetes Troubleshooting \& Redeployment Commands](#kubernetes-troubleshooting--redeployment-commands)
- [1. Rebuild the backend Docker image (after code/config changes)](#1-rebuild-the-backend-docker-image-after-codeconfig-changes)
- [2. (If using a remote registry) Push the image](#2-if-using-a-remote-registry-push-the-image)
- [3. Upgrade the Helm release with the latest values](#3-upgrade-the-helm-release-with-the-latest-values)
- [4. Restart the backend deployment to pick up new images and env vars](#4-restart-the-backend-deployment-to-pick-up-new-images-and-env-vars)
- [5. Check backend pod environment variables](#5-check-backend-pod-environment-variables)
- [6. Check backend pod logs for errors](#6-check-backend-pod-logs-for-errors)
- [7. If you change DB env vars or code, repeat steps 1-6](#7-if-you-change-db-env-vars-or-code-repeat-steps-1-6)
- [Frontend Rebuild \& Redeploy (Kubernetes)](#frontend-rebuild--redeploy-kubernetes)
- [1. Rebuild the frontend Docker image](#1-rebuild-the-frontend-docker-image)
- [2. (If using a remote registry) Push the image](#2-if-using-a-remote-registry-push-the-image-1)
- [3. Upgrade the Helm release](#3-upgrade-the-helm-release)
- [4. Restart the frontend deployment](#4-restart-the-frontend-deployment)
- [Port-Forwarding for Local Access](#port-forwarding-for-local-access)
- [Frontend (React app)](#frontend-react-app)
- [Backend (API)](#backend-api)
- [Database](#database)
- [NGINX Backend Service Name: Docker Compose vs Kubernetes](#nginx-backend-service-name-docker-compose-vs-kubernetes)
- [How to update the NGINX config for Kubernetes](#how-to-update-the-nginx-config-for-kubernetes)
- [Cleaning Up Duplicate or Crashing Deployments and Pods in Kubernetes](#cleaning-up-duplicate-or-crashing-deployments-and-pods-in-kubernetes)
- [1. List deployments and pods](#1-list-deployments-and-pods)
- [2. Delete old or crashing deployments (example IDs from your cluster)](#2-delete-old-or-crashing-deployments-example-ids-from-your-cluster)
- [3. Delete old or crashing pods (example IDs from your cluster)](#3-delete-old-or-crashing-pods-example-ids-from-your-cluster)
- [Debugging Frontend Pod Crashes: NGINX SSL Certificate Errors](#debugging-frontend-pod-crashes-nginx-ssl-certificate-errors)
- [How to fix for Kubernetes (Recommended)](#how-to-fix-for-kubernetes-recommended)
- [Connecting to the Database from Your Host (DBeaver, etc.)](#connecting-to-the-database-from-your-host-dbeaver-etc)
- [🎯 Kubernetes Namespace Management](#-kubernetes-namespace-management)
- [Development Namespace Setup](#development-namespace-setup)
- [Production Namespace Setup](#production-namespace-setup)
- [Namespace Management Commands](#namespace-management-commands)
- [Recommended Kubernetes GUI Tools](#recommended-kubernetes-gui-tools)
- [🆕 Namespaced Development Environment with Helm](#-namespaced-development-environment-with-helm)
- [What was changed:](#what-was-changed)
- [How to use:](#how-to-use)
- [Why use namespaces?](#why-use-namespaces)
- [🔒 Production Security \& Best Practices](#-production-security--best-practices)
- [Environment Variables \& Secrets](#environment-variables--secrets)
- [HTTPS \& Certificates](#https--certificates)
- [CORS \& Security Headers](#cors--security-headers)
- [Logging \& Monitoring](#logging--monitoring)
- [Database Backup](#database-backup)
- [CI/CD \& Automated Testing](#cicd--automated-testing)
- [Troubleshooting Production](#troubleshooting-production)
- [🆕 Recent Improvements \& Troubleshooting](#-recent-improvements--troubleshooting)
- [🚀 Production Deployment Pipeline (CI/CD)](#-production-deployment-pipeline-cicd)
---
## 📁 Project Structure
```
fusero-app-boilerplate/
├── frontend/ # React frontend application
├── backend/ # Node.js backend application
├── docker-compose.yml # Production Docker configuration
└── docker-compose.dev.yml # Development Docker configuration
```
├── chart/ # Helm chart for Kubernetes
│ ├── Chart.yaml
│ ├── values.dev.yaml
│ ├── values.prod.yaml
│ └── templates/
├── config/
├── coverage/
├── dist/
├── docs/
├── frontend/ # React frontend app
│ ├── public/
│ └── src/
├── mikro-orm.config.ts
├── nginx/
├── node_modules/
├── package.json
├── package-lock.json
├── docker-compose.yaml
├── docker-compose.dev.yaml
├── .gitignore
├── .gitea-ci.yaml
├── .prettierrc.json
├── .eslintrc.json
├── architecture.excalidraw
├── src/ # Node.js backend source
│ ├── apps/
│ ├── constants/
│ ├── database/
│ ├── middleware/
│ ├── plugins/
│ ├── shared/
│ ├── tests/
│ ├── types/
│ └── ...
├── test/
├── utils/
└── README.md
## Prerequisites
---
## ⚙️ Prerequisites
- Node.js (v20 or higher)
- npm (v9 or higher)
- Docker and Docker Compose
- Git
## 🗃️ Create Docker Volume for Postgres
## Development Setup
Before starting the database, create a Docker volume to persist Postgres data:
```sh
docker volume create fusero-db-data
```
## 🛠️ Running Migrations and Seeding the Database in Kubernetes
To run migrations and seed the database in your Kubernetes cluster, a job is included in the Helm chart. The job runs the following command:
```sh
npx mikro-orm migration:up && npm run seed
```
This job is triggered automatically on deployment. If you need to rerun it manually, you can delete and recreate the job using kubectl.
## 💻 Development Setup
Backend setup:
- Copy the example environment file:
```sh
cp backend/.env.example backend/.env
```
- Install dependencies:
```sh
npm install
```
- Run migrations and seed:
```sh
npm run migrate
npm run seed
```
- Start the backend in development mode:
```sh
npm run dev &
```
Frontend setup:
- Copy the example environment file:
```sh
cp frontend/.env.example frontend/.env
```
- Install dependencies:
```sh
npm install
```
- Start the frontend in development mode:
```sh
npm run dev &
```
App is running:
- Frontend → http://localhost:3000
- Backend → http://localhost:14000
## Important Note: Database Must Run in Docker
### Important Note: Database Must Run in Docker
The PostgreSQL database must always run in Docker, regardless of your development setup choice. This ensures consistent database behavior across all environments.
To start the database:
```bash
docker-compose up db
docker build -t fusero-frontend-dev:local ./frontend
```
### Running Services Separately (Recommended for Development)
For better debugging experience, run the frontend and backend in separate terminal windows, while keeping the database in Docker:
1. **First, ensure the database is running in Docker**
```bash
docker-compose up db
```
2. **Then, in separate terminal windows:**
#### Terminal 1: Backend Service
### 2. (If using a remote registry) Push the image
```bash
cd backend
npm install
npm run dev
docker push <your-registry>/fusero-frontend-dev:local
```
The backend will be available at http://localhost:14000
#### Terminal 2: Frontend Service
### 3. Upgrade the Helm release
```bash
cd frontend
npm install
npm run dev
helm upgrade fusero ./chart -n fusero -f chart/values.dev.yaml
```
The frontend will be available at http://localhost:3000
### Database Setup
### 4. Restart the frontend deployment
```bash
kubectl rollout restart deployment/fusero-frontend -n fusero
```
1. **Create a New Volume**
- Ensure the database volume is created:
```bash
docker volume create fusero-db-data
```
---
2. **Run Migrations**
- Apply database migrations to set up the schema:
```bash
cd backend
npm run migrate
```
## Port-Forwarding for Local Access
3. **Seed the Database**
- Populate the database with initial data:
```bash
cd backend
npm run seed
```
To access your services running in Kubernetes from your local machine, use these commands:
### Environment Setup
### Frontend (React app)
```bash
kubectl port-forward -n fusero-dev svc/fusero-frontend-service 3000:80
```
- Access at: http://localhost:3000
1. **Backend Environment**
- Copy `.env.example` to `.env` in the backend directory
- Configure your environment variables:
```
PORT=14000
DB_HOST=localhost
DB_PORT=19090
DB_USER=postgres
DB_PASSWORD=postgres
DB_NAME=fusero
JWT_SECRET=your_jwt_secret_key_here
```
### Backend (API)
```bash
kubectl port-forward -n fusero-dev svc/fusero-backend-service 14000:14000
```
- Access at: http://localhost:14000
2. **Frontend Environment**
- Copy `.env.example` to `.env` in the frontend directory
- Set the API base URL:
```
VITE_API_BASE_URL=http://localhost:14000/api/v1
```
### Database
```bash
kubectl port-forward -n fusero-dev svc/postgres-service 5432:5432
```
- Access at: localhost:5432
## Production Deployment
---
1. **Build and Run with Docker**
```bash
docker-compose up --build
```
## NGINX Backend Service Name: Docker Compose vs Kubernetes
2. **Run Migrations and Seeders in Production**
After your containers are up, run the following commands to apply database migrations and seed data inside the backend container:
```bash
docker exec -it fusero-app-backend npx mikro-orm migration:up
docker exec -it fusero-app-backend npm run seed
```
**Note:** These commands must be run inside the backend container so they use the correct Docker network and environment variables.
**If your frontend uses NGINX to proxy API requests, you must update the backend service name depending on your environment:**
3. **Environment Variables**
- Ensure all environment variables are properly set in your production environment
- Never commit `.env` files to version control
- **Docker Compose/local:** The backend may be named `fusero-app-backend`.
- **Kubernetes:** The backend service is named `fusero-backend-service`.
## Frontend Routing in Production
### How to update the NGINX config for Kubernetes
In production, the frontend is served through nginx. To ensure client-side routing works correctly:
Edit `frontend/nginx.conf`:
1. **Nginx Configuration**
- Ensure your nginx configuration includes the following directive to handle unknown routes:
**Change this:**
```nginx
proxy_pass http://fusero-app-backend:14000/;
```
**To this:**
```nginx
proxy_pass http://fusero-backend-service:14000/;
```
Then rebuild and redeploy the frontend:
```bash
docker build -t fusero-frontend-dev:local ./frontend
# (push if needed)
helm upgrade fusero ./chart -n fusero -f chart/values.dev.yaml
kubectl rollout restart deployment/fusero-frontend -n fusero
```
**If you see an NGINX error like `host not found in upstream`, this is the cause!**
---
## Cleaning Up Duplicate or Crashing Deployments and Pods in Kubernetes
If you see multiple frontend or backend pods (or CrashLoopBackOff errors), clean up your namespace with these steps:
### 1. List deployments and pods
```bash
kubectl get deployments -n fusero
kubectl get pods -n fusero
```
### 2. Delete old or crashing deployments (example IDs from your cluster)
```bash
kubectl delete deployment fusero-frontend-65cb8db99d -n fusero
kubectl delete deployment fusero-frontend-74fcbb778 -n fusero
```
### 3. Delete old or crashing pods (example IDs from your cluster)
```bash
kubectl delete pod fusero-frontend-65cb8db99d-f2lhr -n fusero
kubectl delete pod fusero-frontend-74fcbb778-v89gm -n fusero
```
**Tip:** Only keep the latest, healthy pods and deployments. If in doubt, check with `kubectl get deployments -n fusero` and `kubectl get pods -n fusero` before deleting.
---
## Debugging Frontend Pod Crashes: NGINX SSL Certificate Errors
If your frontend pod crashes with an error like:
```
nginx: [emerg] cannot load certificate "/etc/nginx/certs/fusero-selfsigned.crt": BIO_new_file() failed (SSL: error:80000002:system library::No such file or directory)
```
This means NGINX is trying to load an SSL certificate that does not exist in the pod.
### How to fix for Kubernetes (Recommended)
1. Edit `frontend/nginx.conf`:
- Change:
```nginx
location / {
try_files $uri $uri/ /index.html;
}
listen 14443 ssl;
ssl_certificate /etc/nginx/certs/fusero-selfsigned.crt;
ssl_certificate_key /etc/nginx/certs/fusero-selfsigned.key;
```
2. **React Router Configuration**
- Set the `basename` dynamically based on the environment:
- In production, set `basename="/dashboard"`.
- In development, set `basename="/"`.
3. **Navigation Links**
- Use relative paths in your navigation links (e.g., `to="canvas/canvas-endpoints"` instead of `to="/dashboard/canvas/canvas-endpoints"`).
## HTTPS with Self-Signed Certificates
To run the application with HTTPS using a self-signed certificate:
1. **Generate a Self-Signed Certificate**
- To:
```nginx
listen 8080;
# (remove the ssl_certificate and ssl_certificate_key lines)
```
2. Rebuild the frontend Docker image:
```bash
openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout ./nginx/ssl/nginx.key -out ./nginx/ssl/nginx.crt
docker build --no-cache -t fusero-frontend-dev:local ./frontend
```
3. (If using a remote registry) Push the image.
4. Redeploy with Helm:
```bash
helm upgrade fusero ./chart -n fusero -f chart/values.dev.yaml
```
5. Check pod status:
```bash
kubectl get pods -n fusero
```
2. **Update Docker Compose**
- Ensure your `docker-compose.yml` mounts the certificate files in the nginx service:
```yaml
volumes:
- ./nginx/ssl:/etc/nginx/ssl
```
**This will make NGINX listen on port 8080 without SSL, which is standard for in-cluster Kubernetes services.**
3. **Nginx Configuration**
- Use the production nginx configuration that includes SSL settings.
---
## Development Best Practices
## Connecting to the Database from Your Host (DBeaver, etc.)
1. **Database Management**
- Always run the database in Docker
- Use `docker-compose.dev.yml` for development
- Never run PostgreSQL directly on your host machine
To connect to the Postgres database running in Kubernetes from your local machine (for example, using DBeaver or another SQL client):
2. **Running Services Separately**
- For development, it's recommended to run frontend and backend in separate terminal windows
- This allows for better debugging and hot-reloading
- You can see logs from each service clearly
1. **Port-forward the Postgres service:**
```bash
kubectl port-forward svc/postgres-service 5432:5432
```
- Keep this terminal open while you use your database client.
- If port 5432 is in use on your machine, you can use another local port (e.g., `15432:5432`) and connect to port 15432 in your client.
3. **Code Organization**
- Frontend code should be in the `frontend/` directory
- Backend code should be in the `backend/` directory
- Shared types and utilities should be in their respective directories
2. **Database connection settings:**
- **Host:** `localhost`
- **Port:** `5432` (or your chosen local port)
- **Database:** `fusero-boilerplate-db`
- **Username:** `root`
- **Password:** `root123`
4. **Version Control**
- Commit `package-lock.json` files
- Don't commit `.env` files
- Use meaningful commit messages
3. **Open DBeaver (or your preferred client) and create a new Postgres connection using the above settings.**
## API Documentation
4. **Test the connection.**
The backend API is documented using Swagger/OpenAPI. After starting the backend service, you can access the API documentation at:
- Development: http://localhost:14000/api-docs
- Production: http://your-domain/api-docs
---
## Troubleshooting
## 🎯 Kubernetes Namespace Management
1. **Port Conflicts**
- If you encounter port conflicts, check which services are running:
```bash
docker ps
```
- Or check for processes using the ports:
```bash
lsof -i :3000
lsof -i :14000
```
### Development Namespace Setup
```bash
# Create development namespace
kubectl create namespace fusero-dev
2. **Database Issues**
- Ensure PostgreSQL is running in Docker
- Check database connection settings in `.env`
- Verify database migrations are up to date
- If database issues persist, try:
```bash
docker-compose -f docker-compose.dev.yml down
docker-compose -f docker-compose.dev.yml up db
```
# Set current context to development namespace
kubectl config set-context --current --namespace=fusero-dev
3. **CORS Issues**
- If you see CORS errors, verify the frontend's API base URL
- Check backend CORS configuration
- Ensure both services are running on the correct ports
## Contributing
1. Create a new branch for your feature
2. Make your changes
3. Submit a pull request
4. Ensure all tests pass
5. Update documentation as needed
## License
This project is licensed under the MIT License - see the LICENSE file for details.
## Technical Documentation: ChatGPT-Powered Endpoint Creation
### Overview
Developers can leverage the ChatGPT modal in the Canvas Endpoints UI to create new Canvas API endpoints using natural language prompts. When a user enters a prompt like "Create a course endpoint for Canvas", the system uses ChatGPT to:
1. Interpret the intent and generate a JSON object with the required fields for the endpoint (name, method, path, description, etc.).
2. Automatically submit this JSON to the backend endpoint creation API (`/api/v1/canvas-api/endpoints`).
3. Refresh the endpoint list in the UI and display a success message.
### How it Works
- **Prompt Handling:**
- The frontend sends the user's prompt to `/api/v1/canvas-api/chatgpt/completions`.
- ChatGPT is instructed to return only a JSON object suitable for the endpoint creation form.
- **Auto-Creation:**
- If the response is a valid endpoint JSON (with `name`, `method`, and `path`), the frontend posts it to `/api/v1/canvas-api/endpoints`.
- The endpoint list is refreshed and a toast notification is shown.
- **Fallback:**
- If the response is not a valid endpoint JSON, it is displayed as a normal chat message.
### Example Prompt
```
Create a course endpoint for Canvas. Use the Canvas API docs to determine the correct path and required fields.
# Deploy to development namespace
helm upgrade --install fusero ./chart -n fusero-dev -f chart/values.dev.yaml
```
### Example ChatGPT Response
```
{
"name": "Create Course",
"method": "POST",
"path": "/courses",
"description": "Creates a new course in Canvas."
}
### Production Namespace Setup
```bash
# Create production namespace
kubectl create namespace fusero-prod
# Set current context to production namespace
kubectl config set-context --current --namespace=fusero-prod
# Deploy to production namespace
helm upgrade --install fusero ./chart -n fusero-prod -f chart/values.prod.yaml
```
### Developer Notes
- The ChatGPT modal logic is in `frontend/src/components/CanvasEndpoints.tsx`.
- The backend endpoint creation API is `/api/v1/canvas-api/endpoints`.
- The system expects ChatGPT to return a JSON object with at least `name`, `method`, and `path`.
- The endpoint list is auto-refreshed after creation.
### Namespace Management Commands
```bash
# List all namespaces
kubectl get namespaces
---
# Switch between namespaces
kubectl config set-context --current --namespace=<namespace-name>
# View resources in current namespace
kubectl get all
# Delete namespace (be careful!)
kubectl delete namespace <namespace-name>
```
### Recommended Kubernetes GUI Tools
1. **Lens** - The most popular Kubernetes IDE
- Download: https://k8slens.dev/
- Features:
- Real-time cluster monitoring
- Multi-cluster management
- Namespace isolation
- Resource visualization
- Log streaming
- Terminal access
2. **K9s** - Terminal-based UI
- Install: `brew install k9s` (Mac) or `scoop install k9s` (Windows)
- Features:
- Fast navigation
- Resource management
- Log viewing
- Port forwarding
3. **Octant** - Web-based UI
- Install: https://octant.dev/
- Features:
- Resource visualization
- Configuration management
- Log viewing
- Port forwarding
---
## 🆕 Namespaced Development Environment with Helm
You have configured your Kubernetes development environment to use a dedicated namespace (`fusero-dev`) for all core services (backend, frontend, and database). This ensures resource isolation and easier management between development and production.
### What was changed:
- All Kubernetes service templates (`backend-service.yaml`, `frontend-service.yaml`, `postgres-service.yaml`) now use a namespace variable.
- The development values file (`chart/values.dev.yaml`) sets `global.namespace: fusero-dev` and updates all internal service hostnames to use this namespace.
- Helm deployments now target the `fusero-dev` namespace for all dev resources.
### How to use:
1. **Create the development namespace (if not already created):**
```bash
kubectl create namespace fusero-dev
```
2. **Deploy all services to the namespace:**
```bash
helm upgrade --install fusero ./chart -n fusero-dev -f chart/values.dev.yaml
```
3. **Check running pods and services:**
```bash
kubectl get all -n fusero-dev
```
4. **If you update environment variables or service hostnames, repeat the Helm upgrade command.**
5. **If you see errors about immutable fields (e.g., for Jobs), delete the old Job before redeploying:**
```bash
kubectl delete job <job-name> -n fusero-dev
helm upgrade --install fusero ./chart -n fusero-dev -f chart/values.dev.yaml
```
### Why use namespaces?
- Keeps dev and prod resources isolated
- Makes it easy to clean up or redeploy all dev resources
- Prevents accidental cross-environment access
---
## 🔒 Production Security & Best Practices
### Environment Variables & Secrets
The application uses a secure secrets management approach:
1. **Development Environment**:
- Use `.env` files locally (gitignored)
- Copy from `.env.example` as template
2. **Production Environment**:
- Secrets are managed through Gitea CI/CD secrets
- Template file: `chart/secrets.prod.template.yaml`
- Actual secrets are generated during deployment
- Never commit actual secrets to the repository
3. **Required Secrets**:
- Database credentials
- Admin user credentials
- Security keys (encryption, JWT)
- API keys (ChatGPT, Canvas)
4. **Secrets in CI/CD**:
- Secrets are stored in Gitea CI/CD settings
- Automatically injected during deployment
- Used to generate `secrets.prod.yaml` at runtime
5. **Security Best Practices**:
- All secrets files are gitignored
- Template files contain placeholder values
- Production secrets are never stored in the repository
- Regular rotation of secrets recommended
### HTTPS & Certificates
- In production, use trusted certificates (e.g., Let's Encrypt).
- Configure NGINX to enforce HTTPS.
### CORS & Security Headers
- Lock down CORS settings in production.
- Use security headers (e.g., HSTS, CSP).
### Logging & Monitoring
- Configure logging for production (e.g., ELK stack, Datadog).
- Set up basic monitoring (e.g., Prometheus, Grafana).
### Database Backup
- Regularly backup your production database.
- Example: Use `pg_dump` or a managed backup service.
### CI/CD & Automated Testing
- Run tests before deploying to production.
- Example CI/CD workflow:
```yaml
# .github/workflows/deploy.yml
name: Deploy to Production
on:
push:
branch: main
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Run Tests
run: npm test
- name: Deploy to Kubernetes
run: helm upgrade --install fusero ./chart -n fusero-prod -f chart/values.prod.yaml
```
### Troubleshooting Production
- Common issues:
- Database connection errors: Check secrets and network policies.
- Pod crashes: Check logs with `kubectl logs <pod-name> -n fusero-prod`.
- Rollback: Use `helm rollback fusero <revision> -n fusero-prod`.
---
## 🆕 Recent Improvements & Troubleshooting
- **Consistent File Extensions:** All Kubernetes and Helm YAML files now use the `.yaml` extension for consistency.
- **Secrets Management:**
- Development secrets are stored in `chart/secrets.dev.yaml` (gitignored).
- Production secrets are generated by CI/CD as `chart/secrets.prod.yaml` from Gitea secrets.
- All values files (`values.dev.yaml`, `values.prod.yaml`) reference secrets via environment variables.
- **CORS Configuration:**
- For local development, set `CORS_ORIGIN: "http://localhost:3000"` in `chart/values.dev.yaml` to allow credentialed requests from the frontend.
- Do **not** use `*` for CORS origin if you need credentials.
- **Kubernetes Job Immutability:**
- If you update environment variables or secrets, you must delete the old migration/seed job before redeploying:
```bash
kubectl delete job fusero-backend-db-init -n fusero-dev
helm upgrade --install fusero ./chart -n fusero-dev -f chart/values.dev.yaml -f chart/secrets.dev.yaml
```
- **Port Forwarding for Local Access:**
- Backend:
```bash
kubectl port-forward -n fusero-dev svc/fusero-backend-service 14000:14000
```
- Frontend:
```bash
kubectl port-forward -n fusero-dev svc/fusero-frontend-service 3000:80
```
- Database:
```bash
kubectl port-forward -n fusero-dev svc/postgres-service 5432:5432
```
- **Testing Login with curl:**
```bash
curl -i -X POST http://localhost:14000/api/v1/app/users/login \
-H "Content-Type: application/json" \
-H "Origin: http://localhost:3000" \
-d '{"username":"admin","password":"admin123"}'
```
- **Troubleshooting 401 Errors:**
- If login fails after a redeploy:
- Ensure secrets and values are in sync.
- Re-run the seed job as above.
- Check backend logs for authentication errors:
```bash
kubectl logs -n fusero-dev -l app=fusero-backend --tail=100
```
---
## 🚀 Production Deployment Pipeline (CI/CD)
- On every push/merge to `main`, the Gitea CI/CD pipeline will:
1. Build and push Docker images for backend and frontend.
2. Generate `secrets.prod.yaml` from Gitea CI/CD secrets.
3. **Delete the old migration/seed job** (`fusero-backend-db-init`) to ensure a fresh run.
4. Deploy the app with Helm, which triggers the migration/seed job.
5. **Wait for the migration/seed job to complete.**
6. **Fail the pipeline if the job fails** (with logs for debugging).
7. Verify the deployment.
- This ensures your database is always migrated and seeded with every deploy, and you'll know immediately if something goes wrong.
- To trigger a production deployment, just push or merge to `main`.

13
architecture.excalidraw Normal file

@ -0,0 +1,13 @@
{
"type": "excalidraw",
"version": 2,
"source": "https://marketplace.visualstudio.com/items?itemName=pomdtr.excalidraw-editor",
"elements": [],
"appState": {
"gridSize": 20,
"gridStep": 5,
"gridModeEnabled": false,
"viewBackgroundColor": "#ffffff"
},
"files": {}
}

6
chart/Chart.yaml Normal file

@ -0,0 +1,6 @@
apiVersion: v2
name: fusero
description: Fusero App Boilerplate Helm Chart
type: application
version: 0.1.0
appVersion: "1.0.0"

6
chart/Chart.yml Normal file

@ -0,0 +1,6 @@
apiVersion: v2
name: fusero
description: Fusero App Boilerplate Helm Chart
type: application
version: 0.1.0
appVersion: "1.0.0"

@ -0,0 +1,25 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: fusero-backend
spec:
replicas: 1
selector:
matchLabels:
app: fusero-backend
template:
metadata:
labels:
app: fusero-backend
spec:
containers:
- name: backend
image: {{ .Values.backend.image }}
imagePullPolicy: {{ .Values.global.imagePullPolicy }}
ports:
- containerPort: {{ .Values.backend.port }}
env:
{{- range $key, $val := .Values.backend.env }}
- name: {{ $key }}
value: "{{ $val }}"
{{- end }}

@ -0,0 +1,25 @@
apiVersion: batch/v1
kind: Job
metadata:
name: fusero-backend-db-init
spec:
backoffLimit: 0
template:
metadata:
name: fusero-backend-db-init
spec:
containers:
- name: migrate-seed
image: {{ .Values.backend.image }}
command: ["/bin/sh", "-c"]
args:
- |
echo "Running migrations and seeds..." && \
npx mikro-orm migration:up && \
npm run seed
env:
{{- range $key, $val := .Values.backend.env }}
- name: {{ $key }}
value: "{{ $val }}"
{{- end }}
restartPolicy: Never

@ -0,0 +1,16 @@
# You can skip this if you manage secrets separately
# Included for completeness (values-dev.yaml handles them)
apiVersion: v1
kind: Secret
metadata:
name: fusero-backend-secrets
namespace: {{ .Values.global.namespace }}
type: Opaque
data:
POSTGRES_PASSWORD: {{ .Values.backend.env.POSTGRES_PASSWORD | b64enc }}
DEFAULT_ADMIN_PASSWORD: {{ .Values.backend.env.DEFAULT_ADMIN_PASSWORD | b64enc }}
ENCRYPTION_KEY: {{ .Values.backend.env.ENCRYPTION_KEY | b64enc }}
JWT_SECRET: {{ .Values.backend.env.JWT_SECRET | b64enc }}
CHATGPT_API_KEY: {{ .Values.backend.env.CHATGPT_API_KEY | b64enc }}
CANVAS_API_KEY: {{ .Values.backend.env.CANVAS_API_KEY | b64enc }}

@ -0,0 +1,12 @@
apiVersion: v1
kind: Service
metadata:
name: fusero-backend-service
namespace: {{ .Values.global.namespace }}
spec:
selector:
app: fusero-backend
ports:
- protocol: TCP
port: {{ .Values.backend.port }}
targetPort: {{ .Values.backend.port }}

@ -0,0 +1,25 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: fusero-frontend
spec:
replicas: 1
selector:
matchLabels:
app: fusero-frontend
template:
metadata:
labels:
app: fusero-frontend
spec:
containers:
- name: frontend
image: {{ .Values.frontend.image }}
imagePullPolicy: {{ .Values.global.imagePullPolicy }}
ports:
- containerPort: {{ .Values.frontend.port }}
env:
{{- range $key, $val := .Values.frontend.env }}
- name: {{ $key }}
value: "{{ $val }}"
{{- end }}

@ -0,0 +1,12 @@
apiVersion: v1
kind: Service
metadata:
name: fusero-frontend-service
namespace: {{ .Values.global.namespace }}
spec:
selector:
app: fusero-frontend
ports:
- protocol: TCP
port: 80
targetPort: {{ .Values.frontend.port }}

@ -0,0 +1,26 @@
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: fusero-app-ingress
namespace: fusero-prod
annotations:
traefik.ingress.kubernetes.io/router.entrypoints: web
spec:
rules:
- host: app.fusero.nl
http:
paths:
- path: /api
pathType: Prefix
backend:
service:
name: fusero-backend-service
port:
number: 14000
- path: /
pathType: Prefix
backend:
service:
name: fusero-frontend-service
port:
number: 80

@ -0,0 +1,18 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: postgres-config
data:
pg_hba.conf: |
# TYPE DATABASE USER ADDRESS METHOD
local all all trust
host all all 127.0.0.1/32 trust
host all all ::1/128 trust
host all all 0.0.0.0/0 md5
postgresql.conf: |
listen_addresses = '*'
max_connections = 100
shared_buffers = 128MB
dynamic_shared_memory_type = posix
max_wal_size = 1GB
min_wal_size = 80MB

@ -0,0 +1,42 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: postgres
spec:
replicas: 1
selector:
matchLabels:
app: postgres
template:
metadata:
labels:
app: postgres
spec:
containers:
- name: postgres
image: {{ .Values.postgres.image }}
ports:
- containerPort: 5432
env:
- name: POSTGRES_DB
value: {{ .Values.postgres.dbName }}
- name: POSTGRES_USER
value: {{ .Values.postgres.user }}
- name: POSTGRES_PASSWORD
value: {{ .Values.postgres.password }}
volumeMounts:
- mountPath: /var/lib/postgresql/data
name: postgres-data
- mountPath: /etc/postgresql/pg_hba.conf
name: postgres-config
subPath: pg_hba.conf
- mountPath: /etc/postgresql/postgresql.conf
name: postgres-config
subPath: postgresql.conf
volumes:
- name: postgres-data
persistentVolumeClaim:
claimName: postgres-pvc-fresh
- name: postgres-config
configMap:
name: postgres-config

@ -0,0 +1,10 @@
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: postgres-pvc-fresh
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: {{ .Values.postgres.storage }}

@ -0,0 +1,11 @@
apiVersion: v1
kind: Service
metadata:
name: postgres-service
namespace: {{ .Values.global.namespace }}
spec:
selector:
app: postgres
ports:
- protocol: TCP
port: 5432

@ -0,0 +1,53 @@
global:
namespace: fusero-prod
security:
cors:
origin: "https://your-domain.com"
methods: "GET,POST,PUT,DELETE"
credentials: true
https:
enabled: true
certSecret: "fusero-tls-secret"
logging:
level: "info"
format: "json"
monitoring:
prometheus:
enabled: true
path: "/metrics"
backend:
image: fusero-backend:latest
resources:
requests:
cpu: "200m"
memory: "256Mi"
limits:
cpu: "500m"
memory: "512Mi"
env:
# Non-sensitive values only
NODE_ENV: "production"
FASTIFY_PORT: "14000"
CANVAS_API_URL: "https://talnet.instructure.com/api/v1"
frontend:
image: fusero-frontend:latest
resources:
requests:
cpu: "100m"
memory: "128Mi"
limits:
cpu: "300m"
memory: "256Mi"
postgres:
image: postgres:15
storage: 5Gi
resources:
requests:
cpu: "200m"
memory: "256Mi"
limits:
cpu: "500m"
memory: "512Mi"

@ -1,5 +1,5 @@
# Build stage
FROM node:18-alpine as build
FROM node:18-alpine AS build
WORKDIR /app

@ -1,18 +1,17 @@
FROM node:18-alpine
FROM node:22
WORKDIR /app
# Copy package files
# Copy package files and install dependencies
COPY package*.json ./
RUN npm ci
# Install dependencies
RUN npm install
# Copy the rest of the application
# Copy .env and source code
COPY .env .
COPY . .
# Expose port 8080
# Expose the dev server port
EXPOSE 8080
# Start development server
CMD ["npm", "run", "dev", "--", "--host", "0.0.0.0", "--port", "8080"]
# Start the dev server — CORRECT host
CMD ["npm", "run", "dev", "--", "--host", "0.0.0.0", "--port", "8080"]

@ -1,12 +1,13 @@
server {
listen 14443 ssl;
# listen 14443 ssl;
listen 8080;
server_name _;
ssl_certificate /etc/nginx/certs/fusero-selfsigned.crt;
ssl_certificate_key /etc/nginx/certs/fusero-selfsigned.key;
# ssl_certificate /etc/nginx/certs/fusero-selfsigned.crt;
# ssl_certificate_key /etc/nginx/certs/fusero-selfsigned.key;
location ^~ /api/ {
proxy_pass http://fusero-app-backend:14000/;
proxy_pass http://fusero-backend-service:14000/;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
@ -15,11 +16,8 @@ server {
}
location / {
proxy_pass http://host.docker.internal:8080;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
root /usr/share/nginx/html;
index index.html;
try_files $uri $uri/ /index.html;
}
}

@ -1,27 +0,0 @@
server {
listen 80;
server_name _;
location / {
root /usr/share/nginx/html;
index index.html;
try_files $uri $uri/ /index.html;
}
# Serve favicon files
location /favicon/ {
alias /usr/share/nginx/html/dist/favicon/;
access_log off;
expires max;
}
# Proxy API requests to the backend
location /api {
proxy_pass http://fusero-app-backend:14000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
}

@ -1 +0,0 @@
/// <reference types="vite/client" />

@ -6,6 +6,13 @@ export default defineConfig({
base: '/',
server: {
port: 8080,
proxy: {
'/api': {
target: 'http://localhost:14000',
changeOrigin: true,
secure: false,
}
}
},
build: {
rollupOptions: {

@ -1,10 +1,11 @@
import dotenv from 'dotenv';
// Force reload `.env` even if it was previously loaded
dotenv.config({ override: true });
import { Options } from '@mikro-orm/core';
import { PostgreSqlDriver } from '@mikro-orm/postgresql';
import { Migrator } from '@mikro-orm/migrations';
import dotenv from 'dotenv';
if (process.env.KUBERNETES_SERVICE_HOST === undefined) {
dotenv.config({ override: true });
}
const isProduction = process.env.NODE_ENV === 'production';

@ -10,24 +10,33 @@
"@": "src"
},
"scripts": {
"fc": "fastify cli",
"build:ts": "rimraf dist && tsc",
"build:frontend:dev": "docker build -t fusero-frontend-dev:local -f frontend/Dockerfile.dev ./frontend",
"prebuild": "npm run lint",
"start": "npm run build:ts && fastify start -l info -r tsconfig-paths/register dist/src/app.js",
"dev": "npm run build:ts && concurrently -k -p '[{name}]' -n 'TypeScript,App,Watcher' -c 'teal.bold,orange.bold,purple.bold' 'npm:watch:ts' 'npm:dev:start' 'npm:watch:ts'",
"dev:start": "fastify start --ignore-watch=.ts$ -w -l info -P -r tsconfig-paths/register dist/src/app.js",
"watch:ts": "tsc -w",
"lint": "eslint src/**/*.{js,jsx,ts,tsx}",
"lint:fix": "eslint src/**/*.{js,jsx,ts,tsx} --fix",
"test": "jest",
"test:unit": "tap test/services/**/*.ts",
"test:integration": "tap test/integration/**/*.ts",
"test:watch": "jest --watch",
"test:coverage": "jest --coverage",
"start": "npm run build:ts && fastify start -l info -r tsconfig-paths/register dist/src/app.js",
"prebuild": "npm run lint",
"build:ts": "rimraf dist && tsc",
"watch:ts": "tsc -w",
"dev": "npm run build:ts && concurrently -k -p \"[{name}]\" -n \"TypeScript,App\" -c \"yellow.bold,cyan.bold\" \"npm:watch:ts\" \"npm:dev:start\"",
"dev:start": "fastify start --ignore-watch=.ts$ -w -l info -P -r tsconfig-paths/register dist/src/app.js",
"app:generate": "node ./utils/generate-app.js",
"lint": "eslint src/**/*.{js,jsx,ts,tsx}",
"lint:fix": "eslint src/**/*.{js,jsx,ts,tsx} --fix",
"test:db": "ts-node -r tsconfig-paths/register src/database/test-connection.ts",
"migration:create": "npx mikro-orm migration:create",
"migrate": "npx mikro-orm migration:up",
"seed": "ts-node -r tsconfig-paths/register src/database/seeds/run-seed.ts",
"test:db": "ts-node -r tsconfig-paths/register src/database/test-connection.ts"
"app:generate": "node ./utils/generate-app.js",
"k8s:dev:apply": "kubectl apply -f k8s/frontend/deployment.dev.yaml",
"k8s:dev:delete": "kubectl delete deployment fusero-frontend-dev 2>/dev/null || true",
"k8s:dev:run": "kubectl port-forward svc/fusero-frontend-service 3000:80",
"k8s:dev:describe": "kubectl describe pod -l app=fusero-frontend",
"k8s:dev:svc": "kubectl describe svc fusero-frontend-service",
"k8s:dev:deployment": "kubectl describe deployment fusero-frontend-dev",
"k8s:get": "kubectl get pods,svc,deployment",
"k8s:exec": "kubectl exec -it $POD_NAME -- /bin/sh"
},
"keywords": [],
"author": "",
@ -94,4 +103,4 @@
"git add"
]
}
}
}

@ -80,7 +80,14 @@ const app: FastifyPluginAsync = async (app, opts): Promise<void> => {
// Register CORS, JWT, and Cookies
app.register(fastifyCors, {
origin: ['http://localhost:3000', 'http://localhost:3001', 'http://localhost:8080', 'http://localhost:8081'],
origin: process.env.CORS_ORIGIN
? process.env.CORS_ORIGIN.split(',').map(origin => origin.trim())
: [
'http://localhost:3000',
'http://localhost:3001',
'http://localhost:8080',
'http://localhost:8081'
],
methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'],
allowedHeaders: ['Content-Type', 'Authorization', 'Accept'],
credentials: true,
@ -122,6 +129,11 @@ const app: FastifyPluginAsync = async (app, opts): Promise<void> => {
export async function buildApp(): Promise<FastifyInstance> {
const server = fastify();
await server.register(app);
// Set the port to 14000
const port = process.env.PORT || 14000;
await server.listen({ port: Number(port), host: '0.0.0.0' });
return server;
}

@ -1,33 +0,0 @@
// import fp from 'fastify-plugin';
// import crypto from 'crypto';
// import { FastifyInstance, FastifyPluginAsync } from 'fastify';
// const ENCRYPTION_KEY = process.env.ENCRYPTION_KEY as string; // Ensure this is 32 bytes for AES-256
// const IV_LENGTH = 16; // AES block size
// const cryptoPlugin: FastifyPluginAsync = async (fastify: FastifyInstance) => {
// fastify.decorate('encrypt', (text: string): string => {
// const iv = crypto.randomBytes(IV_LENGTH);
// const cipher = crypto.createCipheriv('aes-256-cbc', Buffer.from(ENCRYPTION_KEY), iv);
// let encrypted = cipher.update(text);
// encrypted = Buffer.concat([encrypted, cipher.final()]);
// return iv.toString('hex') + ':' + encrypted.toString('hex');
// });
// fastify.decorate('decrypt', (text: string): string => {
// const textParts = text.split(':');
// const iv = Buffer.from(textParts.shift()!, 'hex');
// const encryptedText = Buffer.from(textParts.join(':'), 'hex');
// const decipher = crypto.createDecipheriv('aes-256-cbc', Buffer.from(ENCRYPTION_KEY), iv);
// let decrypted = decipher.update(encryptedText);
// decrypted = Buffer.concat([decrypted, decipher.final()]);
// return decrypted.toString();
// });
// };
// export default fp(cryptoPlugin, {
// name: 'cryptoPlugin',
// decorators: {
// fastify: ['encrypt', 'decrypt'],
// },
// });

@ -1,20 +0,0 @@
// import { FastifyPluginAsync } from 'fastify';
// import fJwt, { FastifyJWTOptions } from '@fastify/jwt';
// const jwtPlugin: FastifyPluginAsync<FastifyJWTOptions> = async (fastify) => {
// await fastify.register(fJwt, {
// secret: process.env.JWT_SECRET || 'your-secret-here',
// });
// };
// const authenticateDecorator: FastifyPluginAsync = async (fastify) => {
// fastify.decorate('authenticate', async (request, reply) => {
// try {
// await request.jwtVerify();
// } catch (err) {
// reply.send(err);
// }
// });
// };
// export { jwtPlugin, authenticateDecorator };

@ -1,15 +0,0 @@
// import { HttpStatusCode } from 'axios';
// class TooManyRequests extends Error {
// public httpErrorStatusCode = HttpStatusCode.TooManyRequests;
// constructor(msg: string, customErrorCode?: HttpStatusCode) {
// super(msg);
// this.httpErrorStatusCode = this.httpErrorStatusCode ?? customErrorCode;
// // Set the prototype explicitly.
// Object.setPrototypeOf(this, TooManyRequests.prototype);
// }
// }
// export default TooManyRequests;