From 7e1da32965362801f9aad0b05a3e226dde5b33fc Mon Sep 17 00:00:00 2001 From: Cavin Date: Sat, 4 Jul 2026 14:15:11 +0300 Subject: [PATCH] feat: implement CI workflows for backend and frontend, update README links, and add Postman collection --- .../workflows/{ci.yaml => backend-ci.yaml} | 4 +- .github/workflows/deploy-render.yaml | 2 +- .github/workflows/frontend-ci.yaml | 39 ++ README.md | 2 +- frontend/.env | 1 + frontend/README.md | 75 --- postman/gatelog.postman_collection.json | 562 ++++++++++++++++++ 7 files changed, 606 insertions(+), 79 deletions(-) rename .github/workflows/{ci.yaml => backend-ci.yaml} (97%) create mode 100644 .github/workflows/frontend-ci.yaml create mode 100644 frontend/.env delete mode 100644 frontend/README.md create mode 100644 postman/gatelog.postman_collection.json diff --git a/.github/workflows/ci.yaml b/.github/workflows/backend-ci.yaml similarity index 97% rename from .github/workflows/ci.yaml rename to .github/workflows/backend-ci.yaml index 4e7842f..15ec513 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/backend-ci.yaml @@ -1,4 +1,4 @@ -name: CI +name: Backend CI on: push: @@ -79,4 +79,4 @@ jobs: tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} cache-from: type=gha - cache-to: type=gha,mode=max \ No newline at end of file + cache-to: type=gha,mode=max \ No newline at end of file diff --git a/.github/workflows/deploy-render.yaml b/.github/workflows/deploy-render.yaml index 8194ef2..b8e11c9 100644 --- a/.github/workflows/deploy-render.yaml +++ b/.github/workflows/deploy-render.yaml @@ -2,7 +2,7 @@ name: Deploy to Render on: workflow_run: - workflows: ["CI"] + workflows: ["Backend CI"] types: [completed] branches: [main] diff --git a/.github/workflows/frontend-ci.yaml b/.github/workflows/frontend-ci.yaml new file mode 100644 index 0000000..0e371f7 --- /dev/null +++ b/.github/workflows/frontend-ci.yaml @@ -0,0 +1,39 @@ +name: Frontend CI +on: + push: + branches: [main] + paths: ["frontend/**"] + pull_request: + branches: [main] + paths: ["frontend/**"] + +jobs: + build: + runs-on: ubuntu-latest + defaults: + run: + working-directory: frontend + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: 24 + cache: 'npm' + cache-dependency-path: frontend/package-lock.json + + - run: npm ci + + - name: Lint + run: npm run lint + + - name: Type check + run: npm run type-check --if-present + + - name: Test + run: npm test --if-present + + - name: Build + run: npm run build + env: + VITE_API_BASE_URL: ${{ vars.VITE_API_BASE_URL }} \ No newline at end of file diff --git a/README.md b/README.md index 3746a6c..5de8718 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ The project is built to demonstrate end-to-end product engineering: system desig --- ## Live Demo -- Frontend - [https://gatelog.vercel.app](https://gatelog.vercel.app) +- Frontend - [htts://gatelogapp.vercel.app](htts://gatelogapp.vercel.app) --- diff --git a/frontend/.env b/frontend/.env new file mode 100644 index 0000000..95c8cdd --- /dev/null +++ b/frontend/.env @@ -0,0 +1 @@ +VITE_API_BASE_URL = http://localhost:8080 \ No newline at end of file diff --git a/frontend/README.md b/frontend/README.md deleted file mode 100644 index c300135..0000000 --- a/frontend/README.md +++ /dev/null @@ -1,75 +0,0 @@ -# React + TypeScript + Vite - -This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. - -Currently, two official plugins are available: - -- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Oxc](https://oxc.rs) -- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) - -## React Compiler - -The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation). - -## Expanding the ESLint configuration - -If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules: - -```js -export default defineConfig([ - globalIgnores(['dist']), - { - files: ['**/*.{ts,tsx}'], - extends: [ - // Other configs... - - // Remove tseslint.configs.recommended and replace with this - tseslint.configs.recommendedTypeChecked, - // Alternatively, use this for stricter rules - tseslint.configs.strictTypeChecked, - // Optionally, add this for stylistic rules - tseslint.configs.stylisticTypeChecked, - - // Other configs... - ], - languageOptions: { - parserOptions: { - project: ['./tsconfig.node.json', './tsconfig.app.json'], - tsconfigRootDir: import.meta.dirname, - }, - // other options... - }, - }, -]) - -``` - -You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules: - -```js -// eslint.config.js -import reactX from 'eslint-plugin-react-x' -import reactDom from 'eslint-plugin-react-dom' - -export default defineConfig([ - globalIgnores(['dist']), - { - files: ['**/*.{ts,tsx}'], - extends: [ - // Other configs... - // Enable lint rules for React - reactX.configs['recommended-typescript'], - // Enable lint rules for React DOM - reactDom.configs.recommended, - ], - languageOptions: { - parserOptions: { - project: ['./tsconfig.node.json', './tsconfig.app.json'], - tsconfigRootDir: import.meta.dirname, - }, - // other options... - }, - }, -]) - -``` diff --git a/postman/gatelog.postman_collection.json b/postman/gatelog.postman_collection.json new file mode 100644 index 0000000..56b7f3a --- /dev/null +++ b/postman/gatelog.postman_collection.json @@ -0,0 +1,562 @@ +{ + "info": { + "name": "gatelog", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" + }, + "item": [ + { + "name": "auth", + "item": [ + { + "name": "login", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "const response = pm.response.json();", + "", + "if(response.accessToken && response.refreshToken) {", + " pm.collectionVariables.set(\"accessToken\", response.accessToken);", + " pm.collectionVariables.set(\"refreshToken\", response.refreshToken);", + " console.log(\"Login Successful! Tokens saved.\")", + "}", + "" + ], + "type": "text/javascript", + "packages": {}, + "requests": {} + } + } + ], + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"email\": \"manager.a@gatelog.app\",\n \"password\": \"Manager012345\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/auth/login", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "auth", + "login" + ] + } + }, + "response": [] + }, + { + "name": "refresh", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "const response = pm.response.json();", + "", + "if (response.accessToken && response.refreshToken) {", + " pm.collectionVariables.set(\"accessToken\", response.accessToken);", + " pm.collectionVariables.set(\"refreshToken\", response.refreshToken);", + " console.log(\"Token refreshed\")", + "}" + ], + "type": "text/javascript", + "packages": {}, + "requests": {} + } + } + ], + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"token\": \"{{refreshToken}}\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/auth/refresh", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "auth", + "refresh" + ] + } + }, + "response": [] + }, + { + "name": "logout", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "// invalidate the tokens ", + "pm.collectionVariables.unset(\"accessToken\")", + "pm.collectionVariables.unset(\"refreshToken\")", + "", + "console.log(\"Logged Out! Tokens cleared.\")" + ], + "type": "text/javascript", + "packages": {}, + "requests": {} + } + } + ], + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"token\": \"{{refreshToken}}\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/auth/logout", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "auth", + "logout" + ] + } + }, + "response": [] + } + ] + }, + { + "name": "user", + "item": [ + { + "name": "get-all", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{baseUrl}}/users", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "users" + ] + } + }, + "response": [] + }, + { + "name": "get-by-id", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{baseUrl}}/users/6a0d1cba-e2d1-4585-a889-ba6b358070d3", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "users", + "6a0d1cba-e2d1-4585-a889-ba6b358070d3" + ] + } + }, + "response": [] + }, + { + "name": "update", + "request": { + "method": "PUT", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"name\": \"Admin\",\n \"email\": \"admin@gatelog.app\",\n \"roleName\": \"ADMIN\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/users/", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "users", + "" + ] + } + }, + "response": [] + }, + { + "name": "change-password", + "request": { + "method": "PATCH", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"currentPassword\": \"ChangeMe123!\",\n \"newPassword\": \"Admin012345\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/users/me/password", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "users", + "me", + "password" + ] + } + }, + "response": [] + }, + { + "name": "register", + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"name\": \"Staff\",\n \"email\": \"staff.a2@gatelog.app\",\n \"password\": \"Staff012345\",\n \"roleName\": \"STAFF\",\n \"siteId\": \"794bdb3d-4074-4b02-8741-31dc33d912dd\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/users/register", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "users", + "register" + ] + } + }, + "response": [] + } + ] + }, + { + "name": "site", + "item": [ + { + "name": "get-by-id", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{baseUrl}}/sites/794bdb3d-4074-4b02-8741-31dc33d912dd", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "sites", + "794bdb3d-4074-4b02-8741-31dc33d912dd" + ] + } + }, + "response": [] + }, + { + "name": "add", + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"name\": \"Site A\",\n \"location\": \"Nairobi\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/sites", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "sites" + ] + } + }, + "response": [] + }, + { + "name": "get-all", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{baseUrl}}/sites", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "sites" + ] + } + }, + "response": [] + } + ] + }, + { + "name": "zone", + "item": [ + { + "name": "add", + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"name\": \"Zone A\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/sites/6a578feb-3293-4e9f-9244-cfa28ffcd8f7/zones", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "sites", + "6a578feb-3293-4e9f-9244-cfa28ffcd8f7", + "zones" + ] + } + }, + "response": [] + }, + { + "name": "get-all", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{baseUrl}}/sites/6a578feb-3293-4e9f-9244-cfa28ffcd8f7/zones", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "sites", + "6a578feb-3293-4e9f-9244-cfa28ffcd8f7", + "zones" + ] + } + }, + "response": [] + } + ] + }, + { + "name": "visitor", + "item": [ + { + "name": "add", + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"name\": \"Visitor C\",\n \"phone\": \"0123456787\",\n \"visitorType\": \"Guest\",\n \"purpose\": \"General visit\",\n \"zoneId\": \"5034f6ca-e9fb-47f1-814e-b529499a6629\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/visitors", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "visitors" + ] + } + }, + "response": [] + }, + { + "name": "get-all", + "protocolProfileBehavior": { + "disableBodyPruning": true + }, + "request": { + "method": "GET", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"name\": \"Visitor C\",\n \"phone\": \"0123456787\",\n \"visitorType\": \"Guest\",\n \"purpose\": \"General visit\",\n \"zoneId\": \"5034f6ca-e9fb-47f1-814e-b529499a6629\"\n}" + }, + "url": { + "raw": "{{baseUrl}}/visitors", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "visitors" + ] + } + }, + "response": [] + }, + { + "name": "check-out", + "request": { + "method": "PATCH", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"name\": \"Visitor C\",\n \"phone\": \"0123456787\",\n \"visitorType\": \"Guest\",\n \"purpose\": \"General visit\",\n \"zoneId\": \"5034f6ca-e9fb-47f1-814e-b529499a6629\"\n}" + }, + "url": { + "raw": "{{baseUrl}}/visitors/20f786d0-30a5-4ed0-a23f-ba4ad9db6b3c/checkout", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "visitors", + "20f786d0-30a5-4ed0-a23f-ba4ad9db6b3c", + "checkout" + ] + } + }, + "response": [] + } + ] + }, + { + "name": "dashboard", + "item": [ + { + "name": "get-dashboard", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{baseUrl}}/dashboard", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "dashboard" + ] + } + }, + "response": [] + } + ] + }, + { + "name": "report", + "item": [ + { + "name": "get-report", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{baseUrl}}/reports/visitors/csv", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "reports", + "visitors", + "csv" + ] + } + }, + "response": [] + } + ] + } + ], + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{accessToken}}", + "type": "string" + } + ] + }, + "event": [ + { + "listen": "prerequest", + "script": { + "type": "text/javascript", + "packages": {}, + "requests": {}, + "exec": [ + "" + ] + } + }, + { + "listen": "test", + "script": { + "type": "text/javascript", + "packages": {}, + "requests": {}, + "exec": [ + "" + ] + } + } + ], + "variable": [ + { + "key": "baseUrl", + "value": "" + }, + { + "key": "accessToken", + "value": "" + }, + { + "key": "refreshToken", + "value": "" + } + ] +} \ No newline at end of file