diff --git a/.github/workflows/branch-name-check.yml b/.github/workflows/branch-name-check.yml index e61ec4f..922f5de 100644 --- a/.github/workflows/branch-name-check.yml +++ b/.github/workflows/branch-name-check.yml @@ -17,11 +17,11 @@ jobs: result-encoding: string script: | const enforceSemantic = (branchName) => { - const semanticFormat = /(feat\W|feature\/|fix\/|chore\/|refactor\/|dependabot\/|docs\/|style\/|test\/)/ - if (!semanticFormat.test(currentBranch)) { + const semanticFormat = /^(feat|feature|fix|chore|refactor|dependabot|docs|style|test|ci)\// + if (!semanticFormat.test(branchName)) { core.setFailed( - `Branch names in PRs should use semantic conventions, e.g. (feat, fix, chore, refactor). To fix. ` + - `perform: git branch -m && git push origin -u <: ` + + `Branch names in PRs should use semantic conventions, e.g. (feat, fix, chore, refactor, ci). ` + + `To fix, run: git branch -m && git push origin -u . ` + `For more details, please review https://www.conventionalcommits.org/ and https://gist.github.com/joshbuchea/6f47e86d2510bce28f8e7f42ae84c716`) } } diff --git a/.github/workflows/dependabot-auto-merge.yml b/.github/workflows/dependabot-auto-merge.yml index 3ef30a5..e8628c7 100644 --- a/.github/workflows/dependabot-auto-merge.yml +++ b/.github/workflows/dependabot-auto-merge.yml @@ -20,8 +20,7 @@ jobs: - name: Auto-merge patch and minor updates if: > steps.metadata.outputs.update-type == 'version-update:semver-patch' || - steps.metadata.outputs.update-type == 'version-update:semver-minor' || - steps.metadata.outputs.update-type == 'version-update:semver-major' + steps.metadata.outputs.update-type == 'version-update:semver-minor' run: gh pr merge --auto --squash "$PR_URL" env: PR_URL: ${{ github.event.pull_request.html_url }} diff --git a/.github/workflows/pr-validate.yml b/.github/workflows/pr-validate.yml index 0798db1..f376fe7 100644 --- a/.github/workflows/pr-validate.yml +++ b/.github/workflows/pr-validate.yml @@ -35,6 +35,10 @@ jobs: # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide flake8 . --count --show-source --exit-zero --max-complexity=10 \ --max-line-length=96 --statistics --per-file-ignore='specs/**/*:E9,F63,F7,F82' + - name: Type check with mypy + run: | + cd backend && find "$(pwd)/src" -type f -name "*.py" ! -name "*test_*" \ + -exec python -m mypy {} + - name: Run pytest tests run: | chmod +x ./backend/scripts/test-coverage.sh diff --git a/.husky/pre-commit b/.husky/pre-commit index da8fe4f..b1ad51c 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -7,4 +7,4 @@ npm run lint:backend npm run format:frontend npm run lint:frontend -git add . +git update-index --again diff --git a/backend/mypy.ini b/backend/mypy.ini new file mode 100644 index 0000000..e7d4e87 --- /dev/null +++ b/backend/mypy.ini @@ -0,0 +1,7 @@ +[mypy] + +[mypy-jose.*] +ignore_missing_imports = True + +[mypy-asyncpg.*] +ignore_missing_imports = True diff --git a/backend/scripts/lint.sh b/backend/scripts/lint.sh index 3efb759..247056c 100755 --- a/backend/scripts/lint.sh +++ b/backend/scripts/lint.sh @@ -1,5 +1,5 @@ #!/bin/bash set -e flake8 src/ specs/ -find "$(pwd)/src" -type f -name "\"*.py\"" ! -name "\"*test_*\"" \ +find "$(pwd)/src" -type f -name "*.py" ! -name "*test_*" \ -exec python -m mypy {} + diff --git a/backend/src/api/resolvers/data.py b/backend/src/api/resolvers/data.py index 221bf6c..b8c9fcb 100644 --- a/backend/src/api/resolvers/data.py +++ b/backend/src/api/resolvers/data.py @@ -104,7 +104,7 @@ async def fetch_available_rooms(db, checkin_date, checkout_date) -> Dict[str, An return {"success": True, "rooms": available_rooms} -async def fetch_room(db, room_id: str) -> Room: +async def fetch_room(db, room_id: str) -> Dict[str, Any]: result = await fetch_by_id(db, Room, id=room_id) if result["success"]: return result diff --git a/backend/src/api/resolvers/mutations.py b/backend/src/api/resolvers/mutations.py index fd0e0c7..1449533 100644 --- a/backend/src/api/resolvers/mutations.py +++ b/backend/src/api/resolvers/mutations.py @@ -25,9 +25,9 @@ async def create_reservation_resolver(obj, info, input: dict) -> Dict[str, Any]: utils.log_api_message(__name__, str(error)) return {"success": False, "errors": [str(error)]} except Exception as e: - error = f"Unexpected error: {str(e)}" - utils.log_api_message(__name__, f"Unexpected error: {error}") - return {"success": False, "errors": [error]} + message = f"Unexpected error: {str(e)}" + utils.log_api_message(__name__, message) + return {"success": False, "errors": [message]} finally: await db.close() @@ -41,8 +41,8 @@ async def delete_reservation_resolver(obj, info, reservationId: int) -> Dict[str utils.log_api_message(__name__, str(error)) return {"success": False, "errors": [str(error)]} except Exception as e: - error = f"Unexpected error: {str(e)}" - utils.log_api_message(__name__, f"Unexpected error: {error}") - return {"success": False, "errors": [error]} + message = f"Unexpected error: {str(e)}" + utils.log_api_message(__name__, message) + return {"success": False, "errors": [message]} finally: await db.close() diff --git a/backend/src/api/resolvers/queries.py b/backend/src/api/resolvers/queries.py index c073aef..d9fbaa3 100644 --- a/backend/src/api/resolvers/queries.py +++ b/backend/src/api/resolvers/queries.py @@ -19,9 +19,9 @@ async def get_reservation_resolver(obj, info, id) -> Dict[str, Any]: "errors": [message], } except Exception as e: - error = f"Unexpected error: {str(e)}" - utils.log_api_message(__name__, f"Unexpected error: {error}") - return {"success": False, "errors": [error]} + message = f"Unexpected error: {str(e)}" + utils.log_api_message(__name__, message) + return {"success": False, "errors": [message]} finally: await db.close() @@ -34,9 +34,9 @@ async def get_all_reservations_resolver(obj, info) -> Dict[str, Any]: utils.log_api_message(__name__, str(error)) return {"success": False, "errors": [str(error)]} except Exception as e: - error = f"Unexpected error: {str(e)}" - utils.log_api_message(__name__, f"Unexpected error: {error}") - return {"success": False, "errors": [error]} + message = f"Unexpected error: {str(e)}" + utils.log_api_message(__name__, message) + return {"success": False, "errors": [message]} finally: await db.close() @@ -49,9 +49,9 @@ async def get_all_rooms_resolver(obj, info) -> Dict[str, Any]: utils.log_api_message(__name__, str(error)) return {"success": False, "errors": [str(error)]} except Exception as e: - error = f"Unexpected error: {str(e)}" - utils.log_api_message(__name__, f"Unexpected error: {error}") - return {"success": False, "errors": [error]} + message = f"Unexpected error: {str(e)}" + utils.log_api_message(__name__, message) + return {"success": False, "errors": [message]} finally: await db.close() @@ -71,8 +71,8 @@ async def get_available_rooms_resolver(obj, info, input: dict) -> Dict[str, Any] utils.log_api_message(__name__, str(error)) return {"success": False, "errors": [str(error)]} except Exception as e: - error = f"Unexpected error: {str(e)}" - utils.log_api_message(__name__, f"Unexpected error: {error}") - return {"success": False, "errors": [error]} + message = f"Unexpected error: {str(e)}" + utils.log_api_message(__name__, message) + return {"success": False, "errors": [message]} finally: await db.close() diff --git a/backend/src/requirements_docker.txt b/backend/src/requirements_docker.txt index 6b12e73..0f152e9 100644 --- a/backend/src/requirements_docker.txt +++ b/backend/src/requirements_docker.txt @@ -5,6 +5,7 @@ fastapi==0.139.0 flake8==7.3.0 httpx==0.28.1 isort==8.0.1 +mypy==1.14.1 psycopg2-binary==2.9.12 pydantic==2.13.4 pytest==9.1.1 diff --git a/backend/src/routes/about.py b/backend/src/routes/about.py index 22f86ca..fba273c 100644 --- a/backend/src/routes/about.py +++ b/backend/src/routes/about.py @@ -1,3 +1,5 @@ +from typing import Any, Dict + from fastapi import APIRouter import api.utils as utils @@ -18,7 +20,7 @@ async def about_handler() -> ApiData: desc = """This is a GraphQL API that allows you to create and list reservations as well as the ability to list available rooms for a given date range.""" - data = {"name": f"{API_NAME}", "description": desc} + data: Dict[str, Any] = {"name": f"{API_NAME}", "description": desc} response = ApiData(data=data, status=utils.StatusCode.OK) except Exception as error: messages = str(error) diff --git a/backend/src/routes/graphql.py b/backend/src/routes/graphql.py index f3474ee..526fcb9 100644 --- a/backend/src/routes/graphql.py +++ b/backend/src/routes/graphql.py @@ -51,8 +51,8 @@ def _setup_routes(self): async def graphql_handler(request: Request) -> ApiData: try: result = await graphql_app.handle_request(request) - result = result.body.decode() - inner_data = json.loads(result).get("data", {}) + body = bytes(result.body).decode() + inner_data = json.loads(body).get("data", {}) response = ApiData(data=inner_data, status=utils.StatusCode.OK) except Exception as error: messages = str(error) diff --git a/frontend/.eslintignore b/frontend/.eslintignore deleted file mode 100644 index a66a47e..0000000 --- a/frontend/.eslintignore +++ /dev/null @@ -1,5 +0,0 @@ -node_modules -node_modules/* -public -build -dist diff --git a/frontend/.eslintrc b/frontend/.eslintrc deleted file mode 100644 index 5e17c31..0000000 --- a/frontend/.eslintrc +++ /dev/null @@ -1,30 +0,0 @@ -{ - "extends": [ - "eslint:recommended", - "plugin:eslint-comments/recommended", - "plugin:react/jsx-runtime" - ], - "plugins": [ "eslint-comments", "react" ], - "root": true, - "env": { "browser": true, "es2020": true }, - "parserOptions": { - "sourceType": "module", - "ecmaVersion": "latest" - }, - "ignorePatterns": ["build","specs",".eslintrc"], - "settings": { "react": { "version": "18.2" } }, - "rules": { - "react/jsx-uses-react": "error", - "react/jsx-uses-vars": "error", - "react/jsx-no-target-blank": "off", - "explicit-function-return-type": "off", - "strict-boolean-expressions": "off", - "no-floating-promises": "off", - "no-unused-vars": ["warn", { "argsIgnorePattern": "_" }], - "no-explicit-any": "off", - "no-empty-function": ["error", { "allow": ["methods", "arrowFunctions"] }], - "no-case-declarations": "off", - "no-console": "error", - "no-debugger": "error" - } -} diff --git a/frontend/eslint.config.mjs b/frontend/eslint.config.mjs new file mode 100644 index 0000000..52ed5d3 --- /dev/null +++ b/frontend/eslint.config.mjs @@ -0,0 +1,39 @@ +import js from '@eslint/js'; +import react from 'eslint-plugin-react'; +import comments from 'eslint-plugin-eslint-comments'; +import globals from 'globals'; + +export default [ + { + ignores: ['node_modules/**', 'public/**', 'build/**', 'dist/**', 'specs/**'], + }, + js.configs.recommended, + react.configs.flat['jsx-runtime'], + { + files: ['src/**/*.{js,jsx}'], + plugins: { + 'eslint-comments': comments, + }, + languageOptions: { + ecmaVersion: 'latest', + sourceType: 'module', + globals: { + ...globals.browser, + }, + }, + settings: { + react: { version: '18.2' }, + }, + rules: { + ...comments.configs.recommended.rules, + 'react/jsx-uses-react': 'error', + 'react/jsx-uses-vars': 'error', + 'react/jsx-no-target-blank': 'off', + 'no-unused-vars': ['warn', { argsIgnorePattern: '_' }], + 'no-empty-function': ['error', { allow: ['methods', 'arrowFunctions'] }], + 'no-case-declarations': 'off', + 'no-console': 'error', + 'no-debugger': 'error', + }, + }, +]; diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 3fa09c4..f4766e9 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -4,6 +4,7 @@ "requires": true, "packages": { "": { + "name": "frontend", "dependencies": { "@reduxjs/toolkit": "^2.12.0", "axios": "^1.18.1", @@ -3693,9 +3694,6 @@ "cpu": [ "arm64" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -3712,9 +3710,6 @@ "cpu": [ "arm64" ], - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -3731,9 +3726,6 @@ "cpu": [ "ppc64" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -3750,9 +3742,6 @@ "cpu": [ "s390x" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -3769,9 +3758,6 @@ "cpu": [ "x64" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -3788,9 +3774,6 @@ "cpu": [ "x64" ], - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -9324,9 +9307,9 @@ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" }, "node_modules/js-yaml": { - "version": "3.14.2", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", - "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.15.0.tgz", + "integrity": "sha512-ttBQIIQPDeLjpPOohtUdXuXUVoA2uIB6fEH9HyJ7234s5mBJ5wTx20njxplLZQgLaOfpmPQA7X2t5AX6tIPbog==", "dev": true, "license": "MIT", "dependencies": { @@ -9673,9 +9656,6 @@ "cpu": [ "arm64" ], - "libc": [ - "glibc" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -9696,9 +9676,6 @@ "cpu": [ "arm64" ], - "libc": [ - "musl" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -9719,9 +9696,6 @@ "cpu": [ "x64" ], - "libc": [ - "glibc" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -9742,9 +9716,6 @@ "cpu": [ "x64" ], - "libc": [ - "musl" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -11830,10 +11801,11 @@ } }, "node_modules/undici": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/undici/-/undici-7.27.1.tgz", - "integrity": "sha512-UDdpiex+mzigiyrXrGbiUaF4HzTNhKbh2vRNFaTMzcqmLIPrZxaCtwo/1TMSuWoM1Xz3WiTo9KdgI3kRqYzJGg==", + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.28.0.tgz", + "integrity": "sha512-cRZYrTDwWznlnRiPjggAGxZXanty6M8RV1ff8Wm4LWXBp7/IG8v5DnOm74DtUBp9OONpK75YlPnIjQqX0dBDtA==", "dev": true, + "license": "MIT", "engines": { "node": ">=20.18.1" } diff --git a/requirements.txt b/requirements.txt index fdcb771..380aea8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,6 +5,7 @@ fastapi==0.136.3 flake8==6.1.0 httpx==0.26.0 isort==8.0.1 +mypy==1.14.1 psycopg2-binary==2.9.12 pydantic==2.13.4 pytest==9.0.3