diff --git a/.gitea-ci.yml b/.gitea-ci.yml new file mode 100644 index 0000000..1821deb --- /dev/null +++ b/.gitea-ci.yml @@ -0,0 +1,12 @@ +image: lachlanevenson/k8s-helm:latest + +stages: + - deploy + +variables: + KUBECONFIG: /root/.kube/config # Adjust if you're mounting a kubeconfig differently + +deploy: + stage: deploy + script: + - helm upgrade --install fusero ./chart -f ./chart/values-prod.yaml diff --git a/.gitignore b/.gitignore index 2a6e875..9e87c4d 100644 --- a/.gitignore +++ b/.gitignore @@ -114,4 +114,13 @@ test/types/index.js dist # general -bkups \ No newline at end of file +docs +bkups + +# k8s +secrets.yml + +# helm +values.dev.* +values.prod.* + diff --git a/.prettierrc.json b/.prettierrc.json index 2ad5967..a3436dc 100644 --- a/.prettierrc.json +++ b/.prettierrc.json @@ -6,5 +6,6 @@ "tabWidth": 2, "useTabs": false, "printWidth": 144, - "bracketSpacing": true + "bracketSpacing": true, + "plugins": ["@helm/prettier-plugin-helm"] } diff --git a/README.md b/README.md index 4fb866c..3598aa6 100644 --- a/README.md +++ b/README.md @@ -1,269 +1,486 @@ -# 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 +--- -``` -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 -``` +## 📚 Table of Contents -## Prerequisites +- [⚡️ Fusero App Boilerplate](#️-fusero-app-boilerplate) + - [📚 Table of Contents](#-table-of-contents) + - [📁 Project Structure](#-project-structure) + - [⚙️ Prerequisites](#️-prerequisites) + - [💻 Development Setup](#-development-setup) + - [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) + - [🚀 Production Deployment](#-production-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) + - [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) -- Node.js (v20 or higher) -- npm (v9 or higher) -- Docker and Docker Compose +--- + +## 📁 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 deployment + +--- + +## ⚙️ Prerequisites + +- Node.js (v20 or higher) +- npm (v9 or higher) +- Docker & Docker Compose - Git -## Development Setup +--- -### 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. +## 💻 Development Setup -To start the database: -```bash -docker-compose up db -``` +🗃️ PostgreSQL must run in Docker for consistent behavior. -### Running Services Separately (Recommended for Development) +Create volume and start the database: +docker volume create fusero-db-data +docker-compose up -d db -For better debugging experience, run the frontend and backend in separate terminal windows, while keeping the database in Docker: +Backend setup: +cd backend +cp .env.example .env +npm install +npm run migrate +npm run seed +npm run dev & +cd .. -1. **First, ensure the database is running in Docker** - ```bash - docker-compose up db - ``` +Frontend setup: +cd frontend +cp .env.example .env +npm install +npm run dev & +cd .. -2. **Then, in separate terminal windows:** +App is running: +Frontend → http://localhost:3000 +Backend → http://localhost:14000 -#### Terminal 1: Backend Service -```bash -cd backend -npm install +--- + +### Alternate: Running Services in Separate Terminals + +Terminal 1 (backend): +cd backend +npm install npm run dev -``` -The backend will be available at http://localhost:14000 -#### Terminal 2: Frontend Service -```bash -cd frontend -npm install +Terminal 2 (frontend): +cd frontend +npm install npm run dev -``` -The frontend will be available at http://localhost:3000 -### Database Setup +--- -1. **Create a New Volume** - - Ensure the database volume is created: - ```bash - docker volume create fusero-db-data - ``` +## 🛠️ Environment Setup -2. **Run Migrations** - - Apply database migrations to set up the schema: - ```bash - cd backend - npm run migrate - ``` +backend/.env: +POSTGRES_NAME=fusero-boilerplate-db +POSTGRES_HOSTNAME=localhost +POSTGRES_PORT=19095 +POSTGRES_USER=root +POSTGRES_PASSWORD=root123 +JWT_SECRET=your_jwt_secret_key_here -3. **Seed the Database** - - Populate the database with initial data: - ```bash - cd backend - npm run seed - ``` +# For Kubernetes, these are set in chart/values.yaml: +# POSTGRES_NAME=fusero-boilerplate-db +# POSTGRES_HOSTNAME=postgres-service +# POSTGRES_PORT=19095 +# POSTGRES_USER=root +# POSTGRES_PASSWORD=root123 -### Environment Setup +frontend/.env: +VITE_API_BASE_URL=http://localhost:14000/api/v1 -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 - ``` +--- -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 - ``` +## 🚀 Production Deployment -## Production Deployment +1. Build and run with Docker: +docker-compose up --build -1. **Build and Run with Docker** - ```bash - docker-compose up --build - ``` +2. Apply migrations and seed inside backend container: +docker exec -it fusero-app-backend npx mikro-orm migration:up +docker exec -it fusero-app-backend npm run seed -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. +3. Ensure all required environment variables are configured. +Never commit `.env` files. -3. **Environment Variables** - - Ensure all environment variables are properly set in your production environment - - Never commit `.env` files to version control +--- -## Frontend Routing in Production +## 🌐 Frontend Routing in Production -In production, the frontend is served through nginx. To ensure client-side routing works correctly: +In production, the frontend is served through NGINX. -1. **Nginx Configuration** - - Ensure your nginx configuration includes the following directive to handle unknown routes: - ```nginx - location / { - try_files $uri $uri/ /index.html; - } - ``` - -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** - ```bash - openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout ./nginx/ssl/nginx.key -out ./nginx/ssl/nginx.crt - ``` - -2. **Update Docker Compose** - - Ensure your `docker-compose.yml` mounts the certificate files in the nginx service: - ```yaml - volumes: - - ./nginx/ssl:/etc/nginx/ssl - ``` - -3. **Nginx Configuration** - - Use the production nginx configuration that includes SSL settings. - -## Development Best Practices - -1. **Database Management** - - Always run the database in Docker - - Use `docker-compose.dev.yml` for development - - Never run PostgreSQL directly on your host machine - -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 - -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 - -4. **Version Control** - - Commit `package-lock.json` files - - Don't commit `.env` files - - Use meaningful commit messages - -## API Documentation - -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 - -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 - ``` - -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 - ``` - -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. -``` - -### Example ChatGPT Response -``` -{ - "name": "Create Course", - "method": "POST", - "path": "/courses", - "description": "Creates a new course in Canvas." +NGINX configuration (important for React routing): +location / { + try_files $uri $uri/ /index.html; } + +React Router Configuration: +Use `basename="/"` in dev, and `basename="/dashboard"` in production. + +Use relative paths in links: +Correct: to="canvas/canvas-endpoints" +Wrong: to="/dashboard/canvas/canvas-endpoints" + +--- + +## 🔐 HTTPS with Self-Signed Certificates + +Generate a self-signed cert: +openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout ./nginx/ssl/nginx.key -out ./nginx/ssl/nginx.crt + +Ensure `docker-compose.yml` mounts the certs: +volumes: +- ./nginx/ssl:/etc/nginx/ssl + +Configure NGINX to use the cert in production. + +--- + +## 🧠 Development Best Practices + +- Always run the DB via Docker +- Use `docker-compose.dev.yml` for development +- Never run PostgreSQL directly on host +- Run frontend and backend separately for hot reload +- Use `.env.example` as a template +- Never commit `.env` +- Commit `package-lock.json` +- Use meaningful commit messages + +--- + +## 📘 API Documentation + +After running the backend: + +Development → http://localhost:14000/api-docs +Production → https://your-domain/api-docs + +--- + +## 🧩 ChatGPT-Powered Endpoint Creation + +Prompts like "Create a course endpoint for Canvas" auto-generate API endpoints. + +How it works: +1. The frontend sends your prompt to `/api/v1/canvas-api/chatgpt/completions` +2. If ChatGPT returns a valid endpoint JSON, it's POSTed to `/api/v1/canvas-api/endpoints` +3. The UI auto-refreshes the endpoint list and shows a toast + +Example Prompt: +Create a course endpoint for Canvas. + +Expected JSON: +{ + "name": "Create Course", + "method": "POST", + "path": "/courses", + "description": "Creates a new course in Canvas." +} + +Developer Notes: +- Frontend logic: frontend/src/components/CanvasEndpoints.tsx +- Backend API: /api/v1/canvas-api/endpoints + +--- + +## 🧪 Troubleshooting + +Port Conflicts: +docker ps +lsof -i :3000 +lsof -i :14000 + +Database Issues: +Ensure DB is in Docker and configured correctly +Try restarting: +docker-compose -f docker-compose.dev.yml down +docker-compose -f docker-compose.dev.yml up db + +CORS Issues: +Check API base URL in frontend `.env` +Check backend CORS settings +Verify ports match and services are running + +--- + +## 🤝 Contributing + +1. Create a branch +2. Make your changes +3. Pass all tests +4. Open a pull request +5. Update docs if needed + +--- + +## 📄 License + +This project is licensed under the MIT License. +See the LICENSE file for full details. + +--- + +## Kubernetes Troubleshooting & Redeployment Commands + +If your backend is not picking up environment variables or is failing to connect to the database, follow these steps: + +### 1. Rebuild the backend Docker image (after code/config changes) +```bash +docker build -t fusero-backend-dev:local . ``` -### 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. +### 2. (If using a remote registry) Push the image +```bash +docker push /fusero-backend-dev:local +``` ---- \ No newline at end of file +### 3. Upgrade the Helm release with the latest values +```bash +helm upgrade fusero ./chart -n fusero -f chart/values.dev.yaml +``` + +### 4. Restart the backend deployment to pick up new images and env vars +```bash +kubectl rollout restart deployment/fusero-backend -n fusero +``` + +### 5. Check backend pod environment variables +```bash +kubectl get pods -n fusero +# Replace with the actual pod name from above +kubectl exec -n fusero -- printenv | grep POSTGRES +``` + +### 6. Check backend pod logs for errors +```bash +kubectl logs -n fusero --tail=50 +``` + +### 7. If you change DB env vars or code, repeat steps 1-6 + +--- + +**Note:** +- Make sure your backend code does NOT load `.env` at runtime in Kubernetes. It should use the environment variables provided by the pod. +- If you see connection errors to the DB, always check the pod's environment and logs as above. + +--- + +## Frontend Rebuild & Redeploy (Kubernetes) + +If you change the VITE_API_BASE_URL or any frontend environment variable, rebuild and redeploy the frontend: + +### 1. Rebuild the frontend Docker image +```bash +docker build -t fusero-frontend-dev:local ./frontend +``` + +### 2. (If using a remote registry) Push the image +```bash +docker push /fusero-frontend-dev:local +``` + +### 3. Upgrade the Helm release +```bash +helm upgrade fusero ./chart -n fusero -f chart/values.dev.yaml +``` + +### 4. Restart the frontend deployment +```bash +kubectl rollout restart deployment/fusero-frontend -n fusero +``` + +--- + +## Port-Forwarding for Local Access + +To access your services running in Kubernetes from your local machine, use these commands: + +### Frontend (React app) +```bash +kubectl port-forward -n fusero svc/fusero-frontend-service 3000:80 +``` +- Access at: http://localhost:3000 + +### Backend (API) +```bash +kubectl port-forward -n fusero svc/fusero-backend-service 14000:14000 +``` +- Access at: http://localhost:14000 + +--- + +## NGINX Backend Service Name: Docker Compose vs Kubernetes + +**If your frontend uses NGINX to proxy API requests, you must update the backend service name depending on your environment:** + +- **Docker Compose/local:** The backend may be named `fusero-app-backend`. +- **Kubernetes:** The backend service is named `fusero-backend-service`. + +### How to update the NGINX config for Kubernetes + +Edit `frontend/nginx.conf`: + +**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 + listen 14443 ssl; + ssl_certificate /etc/nginx/certs/fusero-selfsigned.crt; + ssl_certificate_key /etc/nginx/certs/fusero-selfsigned.key; + ``` + - To: + ```nginx + listen 8080; + # (remove the ssl_certificate and ssl_certificate_key lines) + ``` +2. Rebuild the frontend Docker image: + ```bash + 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 + ``` + +**This will make NGINX listen on port 8080 without SSL, which is standard for in-cluster Kubernetes services.** + +--- + +## Connecting to the Database from Your Host (DBeaver, etc.) + +To connect to the Postgres database running in Kubernetes from your local machine (for example, using DBeaver or another SQL client): + +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. + +2. **Database connection settings:** + - **Host:** `localhost` + - **Port:** `5432` (or your chosen local port) + - **Database:** `fusero-boilerplate-db` + - **Username:** `root` + - **Password:** `root123` + +3. **Open DBeaver (or your preferred client) and create a new Postgres connection using the above settings.** + +4. **Test the connection.** + +--- diff --git a/architecture.excalidraw b/architecture.excalidraw new file mode 100644 index 0000000..9f06bc3 --- /dev/null +++ b/architecture.excalidraw @@ -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": {} +} \ No newline at end of file diff --git a/chart/Chart.yaml b/chart/Chart.yaml new file mode 100644 index 0000000..3708c76 --- /dev/null +++ b/chart/Chart.yaml @@ -0,0 +1,6 @@ +apiVersion: v2 +name: fusero +description: Fusero application Helm chart +type: application +version: 0.1.0 +appVersion: "1.0.0" diff --git a/chart/templates/backend-deployment.yaml b/chart/templates/backend-deployment.yaml new file mode 100644 index 0000000..14b3668 --- /dev/null +++ b/chart/templates/backend-deployment.yaml @@ -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 }} diff --git a/chart/templates/backend-migration.job.yaml b/chart/templates/backend-migration.job.yaml new file mode 100644 index 0000000..2d6bac8 --- /dev/null +++ b/chart/templates/backend-migration.job.yaml @@ -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 diff --git a/chart/templates/backend-secrets.yaml b/chart/templates/backend-secrets.yaml new file mode 100644 index 0000000..3db53f4 --- /dev/null +++ b/chart/templates/backend-secrets.yaml @@ -0,0 +1,2 @@ +# You can skip this if you manage secrets separately +# Included for completeness (values-dev.yaml handles them) diff --git a/chart/templates/backend-service.yaml b/chart/templates/backend-service.yaml new file mode 100644 index 0000000..98c4aad --- /dev/null +++ b/chart/templates/backend-service.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: Service +metadata: + name: fusero-backend-service +spec: + selector: + app: fusero-backend + ports: + - protocol: TCP + port: {{ .Values.backend.port }} + targetPort: {{ .Values.backend.port }} diff --git a/chart/templates/frontend-deployment.yaml b/chart/templates/frontend-deployment.yaml new file mode 100644 index 0000000..5023f09 --- /dev/null +++ b/chart/templates/frontend-deployment.yaml @@ -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 }} diff --git a/chart/templates/frontend-service.yaml b/chart/templates/frontend-service.yaml new file mode 100644 index 0000000..660e3bf --- /dev/null +++ b/chart/templates/frontend-service.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: Service +metadata: + name: fusero-frontend-service +spec: + selector: + app: fusero-frontend + ports: + - protocol: TCP + port: 80 + targetPort: {{ .Values.frontend.port }} diff --git a/chart/templates/postgres-deployment.yaml b/chart/templates/postgres-deployment.yaml new file mode 100644 index 0000000..af643e2 --- /dev/null +++ b/chart/templates/postgres-deployment.yaml @@ -0,0 +1,33 @@ +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 + volumes: + - name: postgres-data + persistentVolumeClaim: + claimName: postgres-pvc-fresh diff --git a/chart/templates/postgres-pvc.yaml b/chart/templates/postgres-pvc.yaml new file mode 100644 index 0000000..d66a485 --- /dev/null +++ b/chart/templates/postgres-pvc.yaml @@ -0,0 +1,10 @@ +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: postgres-pvc-fresh +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: {{ .Values.postgres.storage }} diff --git a/chart/templates/postgres-service.yaml b/chart/templates/postgres-service.yaml new file mode 100644 index 0000000..ec5c270 --- /dev/null +++ b/chart/templates/postgres-service.yaml @@ -0,0 +1,10 @@ +apiVersion: v1 +kind: Service +metadata: + name: postgres-service +spec: + selector: + app: postgres + ports: + - protocol: TCP + port: 5432 diff --git a/frontend/Dockerfile b/frontend/Dockerfile index 4040591..da95337 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -1,5 +1,5 @@ # Build stage -FROM node:18-alpine as build +FROM node:18-alpine AS build WORKDIR /app diff --git a/frontend/Dockerfile.dev b/frontend/Dockerfile.dev index dfc9215..1f2f70f 100644 --- a/frontend/Dockerfile.dev +++ b/frontend/Dockerfile.dev @@ -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"] \ No newline at end of file +# Start the dev server — CORRECT host +CMD ["npm", "run", "dev", "--", "--host", "0.0.0.0", "--port", "8080"] diff --git a/frontend/nginx.conf b/frontend/nginx.conf index 6e501a6..6aa0f3c 100644 --- a/frontend/nginx.conf +++ b/frontend/nginx.conf @@ -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; } } \ No newline at end of file diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index 107cdca..db7bf6e 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -6,6 +6,13 @@ export default defineConfig({ base: '/', server: { port: 8080, + proxy: { + '/api': { + target: 'http://localhost:14000', + changeOrigin: true, + secure: false, + } + } }, build: { rollupOptions: { diff --git a/mikro-orm.config.ts b/mikro-orm.config.ts index aee09ce..34cee35 100644 --- a/mikro-orm.config.ts +++ b/mikro-orm.config.ts @@ -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'; diff --git a/package.json b/package.json index f913dcd..1ca7dda 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,14 @@ "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", + "build:frontend:dev": "docker build -t fusero-frontend-dev:local -f frontend/Dockerfile.dev ./frontend", + "k8s:dev:apply": "kubectl apply -f k8s/frontend/deployment.dev.yml", + "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", "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", @@ -27,7 +35,8 @@ "lint:fix": "eslint src/**/*.{js,jsx,ts,tsx} --fix", "migration:create": "npx mikro-orm migration:create", "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" + "test:db": "ts-node -r tsconfig-paths/register src/database/test-connection.ts", + "k8s:exec": "kubectl exec -it $POD_NAME -- /bin/sh" }, "keywords": [], "author": "", diff --git a/src/app.ts b/src/app.ts index 8323ff4..cb9569f 100644 --- a/src/app.ts +++ b/src/app.ts @@ -80,7 +80,14 @@ const app: FastifyPluginAsync = async (app, opts): Promise => { // 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,