[
  {
    "path": ".github/PULL_REQUEST_TEMPLATE.md",
    "content": "<!--- Provide a general summary of your changes in the Title above -->\n\n## Description\n<!--- Describe your changes in detail -->\n\n## Motivation and Context\n<!--- Why is this change required? What problem does it solve? -->\n\n## Screenshots (if appropriate):\n\n## Steps to reproduce (if appropriate):\n\n## Types of changes\n<!--- What types of changes does your code introduce? Put an `x` in all the boxes that apply: -->\n- [ ] Bug fix (non-breaking change which fixes an issue)\n- [ ] New feature (non-breaking change which adds functionality)\n- [ ] Breaking change (fix or feature that would cause existing functionality to change)\n\n## Checklist:\n<!--- Go over all the following points, and put an `x` in all the boxes that apply. -->\n<!--- If you're unsure about any of these, don't hesitate to ask. We're here to help! -->\n- [ ] My code follows the code style of this project.\n- [ ] My change requires documentation updates.\n- [ ] I have updated the documentation accordingly.\n- [ ] My change requires dependencies updates.\n- [ ] I have updated the dependencies accordingly."
  },
  {
    "path": ".github/workflows/ci.yml",
    "content": "name: CI\n\non:\n  push:\n  pull_request:\n\njobs:\n  build-fastapi:\n    name: FastAPI CI\n\n    runs-on: ubuntu-latest\n\n    env:\n      DATABASE_URL: ${{ secrets.DATABASE_URL }}\n      TEST_DATABASE_URL: ${{ secrets.TEST_DATABASE_URL }}\n      ACCESS_SECRET_KEY: ${{ secrets.ACCESS_SECRET_KEY }}\n      RESET_PASSWORD_SECRET_KEY: ${{ secrets.RESET_PASSWORD_SECRET_KEY }}\n      VERIFICATION_SECRET_KEY: ${{ secrets.VERIFICATION_SECRET_KEY }}\n      CORS_ORIGINS: ${{ secrets.CORS_ORIGINS }}\n\n    services:\n      postgres:\n        image: postgres:17\n        env:\n          POSTGRES_USER: postgres\n          POSTGRES_PASSWORD: password\n          POSTGRES_DB: testdatabase\n        ports:\n          - 5433:5432\n\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@v4\n\n      - name: Set up Python\n        uses: actions/setup-python@v5\n        with:\n          python-version: '3.12'\n\n      - name: Install uv\n        uses: astral-sh/setup-uv@v5\n\n      - name: Install the project\n        working-directory: ./fastapi_backend\n        run: uv sync --all-extras --dev\n\n      - name: Run tests\n        working-directory: ./fastapi_backend\n        run: uv run coverage run -m pytest\n\n      - name: Generate XML coverage report\n        working-directory: ./fastapi_backend\n        run: uv run coverage xml -o coverage.xml\n\n      - name: Coveralls GitHub Action\n        uses: coverallsapp/github-action@v2.3.4\n        with:\n          github-token: ${{ secrets.GITHUB_TOKEN }}\n          flag-name: python-coverage\n          parallel: true\n          path-to-lcov: fastapi_backend/coverage.xml\n\n  build-frontend:\n    name: Next.js CI\n\n    runs-on: ubuntu-latest\n\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@v4\n\n      - name: Install pnpm\n        uses: pnpm/action-setup@v4\n        with:\n          version: 9\n          run_install: false\n\n      - name: Set up Node.js\n        uses: actions/setup-node@v4\n        with:\n          node-version: \"20\"\n\n      - name: Install Node dependencies\n        working-directory: ./nextjs-frontend\n        run: pnpm install\n\n      - name: Run tests\n        working-directory: ./nextjs-frontend\n        run: pnpm run coverage\n\n      - name: Coveralls GitHub Action\n        uses: coverallsapp/github-action@v2.3.4\n        with:\n          github-token: ${{ secrets.GITHUB_TOKEN }}\n          flag-name: node-coverage\n          parallel: true\n\n  finish:\n\n    name: Coveralls\n\n    needs: [ build-fastapi, build-frontend ]\n\n    runs-on: ubuntu-latest\n\n    steps:\n      - name: Close parallel build\n        uses: coverallsapp/github-action@v2.3.4\n        with:\n          parallel-finished: true\n          carryforward: \"python-coverage,node-coverage\""
  },
  {
    "path": ".github/workflows/pre-commit.yml",
    "content": "name: pre-commit\n\non:\n  push:\n  pull_request:\n\njobs:\n  pre-commit:\n\n    runs-on: ubuntu-latest\n\n    env:\n      DATABASE_URL: ${{ secrets.DATABASE_URL }}\n      TEST_DATABASE_URL: ${{ secrets.TEST_DATABASE_URL }}\n      ACCESS_SECRET_KEY: ${{ secrets.ACCESS_SECRET_KEY }}\n      RESET_PASSWORD_SECRET_KEY: ${{ secrets.RESET_PASSWORD_SECRET_KEY }}\n      VERIFICATION_SECRET_KEY: ${{ secrets.VERIFICATION_SECRET_KEY }}\n      OPENAPI_OUTPUT_FILE: ${{ secrets.OPENAPI_OUTPUT_FILE }}\n      CORS_ORIGINS: ${{ secrets.CORS_ORIGINS }}\n\n\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@v4\n\n      - name: Set up Python\n        uses: actions/setup-python@v5\n        with:\n          python-version: \"3.12\"\n\n      - name: Install uv\n        uses: astral-sh/setup-uv@v5\n\n      - name: Install the project\n        working-directory: ./fastapi_backend\n        run: uv sync --all-extras --dev\n\n      - name: Install pnpm\n        uses: pnpm/action-setup@v4\n        with:\n          version: 9\n          run_install: false\n\n      - name: Set up Node.js\n        uses: actions/setup-node@v4\n        with:\n          node-version: \"20\"\n\n      - name: Install Node dependencies\n        working-directory: ./nextjs-frontend\n        run: pnpm install\n\n      - name: Run pre-commit\n        uses: pre-commit/action@v3.0.1"
  },
  {
    "path": ".github/workflows/release.yml",
    "content": "name: Release draft creation\n\non:\n  workflow_dispatch:\n    inputs:\n      version:\n        description: \"Version of the release\"\n        required: true\n        type: number\n\njobs:\n  release:\n    name: Release\n\n    runs-on: ubuntu-latest\n\n    permissions:\n      contents: write\n\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@v4\n\n      - name: Extract changelog for version\n        run: |\n          VERSION=${{ github.event.inputs.version }}\n\n          # Extract changelog for version\n          CHANGELOG=$(awk -v version=\"$VERSION\" 'BEGIN{RS=\"## \"; FS=\"\\n\"} $0 ~ version {print \"## \"$0}' CHANGELOG.md)\n\n          # Remove the first line (version title)\n          CHANGELOG=$(echo \"$CHANGELOG\" | sed '1d')\n\n          # Verify if changelog was found\n          if [ -z \"$CHANGELOG\" ]; then\n            echo \"Changelog for version $VERSION not found\"\n            exit 1\n          fi\n\n          # Set output\n          echo \"CHANGELOG<<EOF\" >> $GITHUB_ENV\n          echo \"$CHANGELOG\" >> $GITHUB_ENV\n          echo \"EOF\" >> $GITHUB_ENV\n\n      - name: Create GitHub Release Draft\n        uses: softprops/action-gh-release@v2\n        with:\n          tag_name: ${{ github.event.inputs.version }}\n          name: ${{ github.event.inputs.version }}\n          body: ${{ env.CHANGELOG }}\n          draft: true\n          prerelease: false\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n\n      - name: Summary\n        run: |\n          echo \"## 🚀 Release Summary\" >> $GITHUB_STEP_SUMMARY\n          echo \"Release draft created for version ${{ github.event.inputs.version }}.\" >> $GITHUB_STEP_SUMMARY\n          echo \"Visit the [Releases section](https://github.com/vintasoftware/nextjs-fastapi-template/releases) to review and publish the release.\" >> $GITHUB_STEP_SUMMARY\n          echo \"Once the draft is published, another action will automatically be triggered to publish the packages.\" >> $GITHUB_STEP_SUMMARY"
  },
  {
    "path": ".gitignore",
    "content": "# Byte-compiled / optimized / DLL files\n__pycache__/\n*.py[cod]\n*$py.class\n\n# C extensions\n*.so\n\n# Distribution / packaging\n.Python\nbuild/\ndevelop-eggs/\ndist/\ndownloads/\neggs/\n.eggs/\nlib/\nlib64/\nparts/\nsdist/\nvar/\nwheels/\nshare/python-wheels/\n*.egg-info/\n.installed.cfg\n*.egg\nMANIFEST\n\n# PyInstaller\n#  Usually these files are written by a python script from a template\n#  before PyInstaller builds the exe, so as to inject date/other infos into it.\n*.manifest\n*.spec\n\n# Installer logs\npip-log.txt\npip-delete-this-directory.txt\n\n# Unit test / coverage reports\nhtmlcov/\n.tox/\n.nox/\n.coverage\n.coverage.*\n.cache\nnosetests.xml\ncoverage.xml\n*.cover\n*.py,cover\n.hypothesis/\n.pytest_cache/\ncover/\n\n# Translations\n*.mo\n*.pot\n\n# Django stuff:\n*.log\nlocal_settings.py\ndb.sqlite3\ndb.sqlite3-journal\n\n# Flask stuff:\ninstance/\n.webassets-cache\n\n# Scrapy stuff:\n.scrapy\n\n# Sphinx documentation\ndocs/_build/\n\n# PyBuilder\n.pybuilder/\ntarget/\n\n# Jupyter Notebook\n.ipynb_checkpoints\n\n# IPython\nprofile_default/\nipython_config.py\n\n# pyenv\n#   For a library or package, you might want to ignore these files since the code is\n#   intended to run in multiple environments; otherwise, check them in:\n# .python-version\n\n# pipenv\n#   According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.\n#   However, in case of collaboration, if having platform-specific dependencies or dependencies\n#   having no cross-platform support, pipenv may install dependencies that don't work, or not\n#   install all needed dependencies.\n#Pipfile.lock\n\n# pdm\n#   Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.\n#pdm.lock\n#   pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it\n#   in version control.\n#   https://pdm.fming.dev/latest/usage/project/#working-with-version-control\n.pdm.toml\n.pdm-python\n.pdm-build/\n\n# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm\n__pypackages__/\n\n# Celery stuff\ncelerybeat-schedule\ncelerybeat.pid\n\n# SageMath parsed files\n*.sage.py\n\n# Environments\n.env\n.venv\nenv/\nvenv/\nENV/\nenv.bak/\nvenv.bak/\n\n# Spyder project settings\n.spyderproject\n.spyproject\n\n# Rope project settings\n.ropeproject\n\n# mkdocs documentation\n/site\n\n# mypy\n.mypy_cache/\n.dmypy.json\ndmypy.json\n\n# Pyre type checker\n.pyre/\n\n# pytype static type analyzer\n.pytype/\n\n# Cython debug symbols\ncython_debug/\n\n# PyCharm\n#  JetBrains specific template is maintained in a separate JetBrains.gitignore that can\n#  be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore\n#  and can be added to the global gitignore or merged into this file.  For a more nuclear\n#  option (not recommended) you can uncomment the following to ignore the entire idea folder.\n.idea/"
  },
  {
    "path": ".pre-commit-config.docker.yaml",
    "content": "fail_fast: true\n\nrepos:\n  - repo: https://github.com/pre-commit/pre-commit-hooks\n    rev: v4.5.0\n    hooks:\n      - id: check-added-large-files\n        args: [\"--maxkb=500\"]\n        exclude: >\n          (?x)^(\n              package-lock\\.json\n          )$\n      - id: fix-byte-order-marker\n      - id: check-case-conflict\n      - id: check-merge-conflict\n      - id: check-symlinks\n      - id: debug-statements\n      - id: detect-private-key\n\n  - repo: https://github.com/astral-sh/ruff-pre-commit\n    # Ruff version.\n    rev: v0.1.8\n    hooks:\n      # Run the linter.\n      - id: ruff\n        args: [ --fix ]\n      # Run the formatter.\n      - id: ruff-format\n\n  - repo: local\n    hooks:\n      - id: generate-openapi-schema\n        name: generate OpenAPI schema\n        entry: sh -c 'docker compose run --rm --no-deps -T backend uv run python -m commands.generate_openapi_schema'\n        language: system\n        # Only run OpenAPI schema generation if schemas.py, main.py or package version have changed:\n        files: (main\\.py$|schemas\\.py$|pyproject\\.toml)\n        pass_filenames: false\n      - id: generate-frontend-client\n        name: generate frontend client\n        entry: sh -c 'docker compose run --rm --no-deps -T frontend pnpm run generate-client'\n        language: system\n        # Only run frontend client generation if frontend files have changed:\n        files: ^local-shared-data/openapi\\.json$\n        pass_filenames: false"
  },
  {
    "path": ".pre-commit-config.yaml",
    "content": "fail_fast: true\n\nrepos:\n  - repo: https://github.com/pre-commit/pre-commit-hooks\n    rev: v5.0.0\n    hooks:\n      - id: check-added-large-files\n        args: [\"--maxkb=500\"]\n        exclude: >\n          (?x)^(\n              package-lock\\.json\n          )$\n      - id: fix-byte-order-marker\n      - id: check-case-conflict\n      - id: check-merge-conflict\n      - id: check-symlinks\n      - id: debug-statements\n      - id: detect-private-key\n\n  - repo: https://github.com/astral-sh/ruff-pre-commit\n    # Ruff version.\n    rev: v0.12.2\n    hooks:\n      # Run the linter.\n      - id: ruff\n        args: [--fix]\n      # Run the formatter.\n      - id: ruff-format\n\n  - repo: local\n    hooks:\n      - id: frontend-lint\n        name: run frontend lint\n        entry: sh -c 'cd nextjs-frontend && pnpm run lint'\n        language: system\n        types: [ file ]\n        files: ^nextjs-frontend/.*\\.(js|jsx|ts|tsx)$\n        pass_filenames: true\n      - id: frontend-prettier\n        name: Run Prettier on frontend files\n        entry: sh -c 'cd nextjs-frontend && pnpm run prettier'\n        language: system\n        types: [ file ]\n        files: ^nextjs-frontend/.*\\.(js|jsx|ts|tsx)$\n        pass_filenames: true\n      - id: frontend-tsc\n        name: run frontend tsc\n        entry: sh -c 'cd nextjs-frontend && pnpm run tsc'\n        language: system\n        types: [ file ]\n        files: ^nextjs-frontend/.*\\.(ts|tsx)$\n        pass_filenames: false\n      - id: generate-openapi-schema\n        name: generate OpenAPI schema\n        entry: sh -c 'cd fastapi_backend && uv run python -m commands.generate_openapi_schema'\n        language: system\n        # Only run OpenAPI schema generation if schemas.py, main.py or package version have changed:\n        files: (main\\.py$|schemas\\.py$|pyproject\\.toml)\n        pass_filenames: false\n      - id: generate-frontend-client\n        name: generate frontend client\n        entry: sh -c 'cd nextjs-frontend && pnpm run generate-client'\n        language: system\n        # Only run frontend client generation if frontend files have changed:\n        files: openapi\\.json$\n        pass_filenames: false"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# Changelog\n\nThis changelog references changes made both to the FastAPI backend, `fastapi_backend`, and the\nfrontend TypeScript client, `nextjs-frontend`.\n\n!!! note\n    The backend and the frontend are versioned together, that is, they have the same version number.\n    When you update the backend, you should also update the frontend to the same version.\n\n## 0.0.8 <small>December 17, 2025</small> {id=\"0.0.8\"}\n\n- Upgrade Next.js version to latest version\n\n## 0.0.7 <small>October 24, 2025</small> {id=\"0.0.7\"}\n\n- Upgrade @hey-api/openapi-ts version to ^0.83.1\n\n## 0.0.6 <small>September 1, 2025</small> {id=\"0.0.6\"}\n\n- Upgrade Next.js version to 15.5.0\n\n## 0.0.5 <small>July 9, 2025</small> {id=\"0.0.5\"}\n\n- Items Pagination\n\n## 0.0.4 <small>July 9, 2025</small> {id=\"0.0.4\"}\n\n- Fix ESlint missing for pre-commit\n\n## 0.0.3 <small>April 23, 2025</small> {id=\"0.0.3\"}\n\n- Created docs\n\n## 0.0.2 <small>March 12, 2025</small> {id=\"0.0.2\"}\n\n- Generate release draft using github actions\n\n## 0.0.1 <small>March 12, 2025</small> {id=\"0.0.1\"}\n\n- Initial release\n"
  },
  {
    "path": "LICENSE.txt",
    "content": "MIT License\n\nCopyright (c) 2017 Vinta Serviços e Soluções Tecnológicas Ltda\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "Makefile",
    "content": "# Makefile\n\n# Variables\nBACKEND_DIR=fastapi_backend\nFRONTEND_DIR=nextjs-frontend\nDOCKER_COMPOSE=docker compose\n\n# Help\n.PHONY: help\nhelp:\n\t@echo \"Available commands:\"\n\t@awk '/^[a-zA-Z_-]+:/{split($$1, target, \":\"); print \"  \" target[1] \"\\t\" substr($$0, index($$0,$$2))}' $(MAKEFILE_LIST)\n\n# Backend commands\n.PHONY: start-backend test-backend\n\nstart-backend: ## Start the backend server with FastAPI and hot reload\n\tcd $(BACKEND_DIR) && ./start.sh\n\ntest-backend: ## Run backend tests using pytest\n\tcd $(BACKEND_DIR) && uv run pytest\n\n\n# Frontend commands\n.PHONY: start-frontend test-frontend\n\nstart-frontend: ## Start the frontend server with pnpm and hot reload\n\tcd $(FRONTEND_DIR) && ./start.sh\n\ntest-frontend: ## Run frontend tests using npm\n\tcd $(FRONTEND_DIR) && pnpm run test\n\n\n# Docker commands\n.PHONY: docker-backend-shell docker-frontend-shell docker-build docker-build-backend \\\n        docker-build-frontend docker-start-backend docker-start-frontend docker-up-test-db \\\n        docker-migrate-db docker-db-schema docker-test-backend docker-test-frontend\n\n\ndocker-backend-shell: ## Access the backend container shell\n\t$(DOCKER_COMPOSE) run --rm backend sh\n\ndocker-frontend-shell: ## Access the frontend container shell\n\t$(DOCKER_COMPOSE) run --rm frontend sh\n\ndocker-build: ## Build all the services\n\t$(DOCKER_COMPOSE) build --no-cache\n\ndocker-build-backend: ## Build the backend container with no cache\n\t$(DOCKER_COMPOSE) build backend --no-cache\n\ndocker-build-frontend: ## Build the frontend container with no cache\n\t$(DOCKER_COMPOSE) build frontend --no-cache\n\ndocker-start-backend: ## Start the backend container\n\t$(DOCKER_COMPOSE) up backend\n\ndocker-start-frontend: ## Start the frontend container\n\t$(DOCKER_COMPOSE) up frontend\n\ndocker-up-test-db: ## Start the test database container\n\t$(DOCKER_COMPOSE) up db_test\n\ndocker-migrate-db: ## Run database migrations using Alembic\n\t$(DOCKER_COMPOSE) run --rm backend alembic upgrade head\n\ndocker-db-schema: ## Generate a new migration schema. Usage: make docker-db-schema migration_name=\"add users\"\n\t$(DOCKER_COMPOSE) run --rm backend alembic revision --autogenerate -m \"$(migration_name)\"\n\ndocker-test-backend: ## Run tests for the backend\n\t$(DOCKER_COMPOSE) run --rm backend pytest\n\ndocker-test-frontend: ## Run tests for the frontend\n\t$(DOCKER_COMPOSE) run --rm frontend pnpm run test\n\ndocker-up-mailhog: ## Start mailhog server\n\t$(DOCKER_COMPOSE) up mailhog"
  },
  {
    "path": "README.md",
    "content": "## Next.js FastAPI Template\n\n<a href=\"https://www.vintasoftware.com/blog/next-js-fastapi-template\"><img src=\"docs/images/banner.png\" alt=\"Next.js FastAPI Template\" width=\"auto\"></a>\n<p align=\"center\">\n    <em>Next.js FastAPI Template: Python + Modern TypeScript stack with Zod validation.</em>\n</p>\n<p align=\"center\">\n<a href=\"https://github.com/vintasoftware/nextjs-fastapi-template/actions/workflows/ci.yml\" target=\"_blank\">\n    <img src=\"https://github.com/vintasoftware/nextjs-fastapi-template/actions/workflows/ci.yml/badge.svg\" alt=\"CI\">\n</a>\n<a href=\"https://coveralls.io/github/vintasoftware/nextjs-fastapi-template\" target=\"_blank\">\n    <img src=\"https://coveralls.io/repos/github/vintasoftware/nextjs-fastapi-template/badge.svg\" alt=\"Coverage\">\n</a>\n</p>\n\n---\n\n**Documentation**: <a href=\"https://vintasoftware.github.io/nextjs-fastapi-template/\" target=\"_blank\">https://vintasoftware.github.io/nextjs-fastapi-template/</a>\n\n**Source Code**: <a href=\"https://github.com/vintasoftware/nextjs-fastapi-template/\" target=\"_blank\">https://github.com/vintasoftware/nextjs-fastapi-template/</a>\n\n---\n\nThe Next.js FastAPI Template provides a solid foundation for scalable, high-performance web applications, following clean architecture and best practices. It simplifies development by integrating FastAPI, Pydantic, and Next.js with TypeScript and Zod, ensuring end-to-end type safety and schema validation between frontend and backend.\n\nThe FastAPI backend supports fully asynchronous operations, optimizing database queries, API routes, and test execution for better performance. Deployment is seamless, with both backend and frontend fully deployable to Vercel, enabling quick product releases with minimal configuration.\n\n### Key features\n✔ End-to-end type safety – Automatically generated typed clients from the OpenAPI schema ensure seamless API contracts between frontend and backend.\n\n✔ Hot-reload updates – The client updates automatically when backend routes change, keeping FastAPI and Next.js in sync.\n\n✔ Versatile foundation – Designed for MVPs and production-ready applications, with a pre-configured authentication system and API layer.\n\n✔ Quick deployment – Deploys a full-stack application—including authentication flow and a dashboard—on Vercel in just a few steps.\n\n✔ Production-ready authentication – Includes a pre-configured authentication system and dashboard interface, allowing you to immediately start development with user management features.\n\n## Technology stack\nThis template features a carefully selected set of technologies to ensure efficiency, scalability, and ease of use:\n\n- Zod + TypeScript – Type safety and schema validation across the stack.\n- fastapi-users – Complete authentication system with:\n    - Secure password hashing\n    - JWT authentication\n- Email-based password recovery\n- shadcn/ui – Prebuilt React components with Tailwind CSS.\n- OpenAPI-fetch – Fully typed client generation from the OpenAPI schema.\n- UV – Simplified dependency management and packaging.\n- Docker Compose – Consistent environments for development and production.\n- Pre-commit hooks – Automated code linting, formatting, and validation before commits.\n- Vercel Deployment – Serverless backend and scalable frontend, deployable with minimal configuration.\n\nThis is a partial list of the technologies included in the template. For a complete overview, visit our [Technology selection](https://vintasoftware.github.io/nextjs-fastapi-template/technology-selection/) page.\n\n## Get Started\n\nTo use this template, visit our [Get Started](https://vintasoftware.github.io/nextjs-fastapi-template/get-started/) and follow the steps.\n\n## Using the template? Let's talk!\n\nWe’re always curious to see how the community builds on top of it and where it’s being used. To collaborate:\n\n- Join the conversation on [GitHub Discussions](https://github.com/vintasoftware/nextjs-fastapi-template/discussions)\n- Report bugs or suggest improvements via [issues](https://github.com/vintasoftware/nextjs-fastapi-template/issues)\n- Check the [Contributing](https://vintasoftware.github.io/nextjs-fastapi-template/contributing/) guide to get involved\n\nThis project is maintained by [Vinta Software](https://www.vinta.com.br/) and is actively used in production systems we build for clients. Talk to our expert consultants — get a free technical review: contact@vinta.com.br.\n\n*Disclaimer: This project is not affiliated with Vercel.*\n"
  },
  {
    "path": "docker-compose.yml",
    "content": "services:\n  backend:\n    build:\n      context: fastapi_backend\n    environment:\n      - OPENAPI_OUTPUT_FILE=./shared-data/openapi.json\n      - DATABASE_URL=postgresql+asyncpg://postgres:password@db:5432/mydatabase\n      - TEST_DATABASE_URL=postgresql+asyncpg://postgres:password@db:5433/testdatabase\n      - MAIL_SERVER=mailhog\n    ports:\n      - \"8000:8000\"\n    networks:\n      - my_network\n    volumes:\n      - ./fastapi_backend:/app\n      - fastapi-venv:/app/.venv\n      - ./local-shared-data:/app/shared-data\n    depends_on:\n      - db\n  db:\n    image: postgres:17\n    environment:\n      POSTGRES_USER: postgres\n      POSTGRES_PASSWORD: password\n      POSTGRES_DB: mydatabase\n    ports:\n      - \"5432:5432\"\n    networks:\n      - my_network\n    volumes:\n      - postgres_data:/var/lib/postgresql/data\n  db_test:\n    image: postgres:17\n    environment:\n      POSTGRES_USER: postgres\n      POSTGRES_PASSWORD: password\n      POSTGRES_DB: testdatabase\n    ports:\n      - \"5433:5432\"\n    networks:\n      - my_network\n    restart: always\n  frontend:\n    build:\n      context: ./nextjs-frontend\n    user: node\n    ports:\n      - \"3000:3000\"\n    networks:\n      - my_network\n    environment:\n      NODE_ENV: development\n      API_BASE_URL: http://backend:8000\n      OPENAPI_OUTPUT_FILE: ./shared-data/openapi.json\n    volumes:\n      - ./nextjs-frontend:/app\n      - nextjs-node-modules:/app/node_modules\n      - ./local-shared-data:/app/shared-data\n  mailhog:\n    image: mailhog/mailhog\n    ports:\n      - \"1025:1025\" # SMTP server\n      - \"8025:8025\" # Web UI\n    networks:\n      - my_network\n\nvolumes:\n  postgres_data:\n  nextjs-node-modules:\n  fastapi-venv:\n\nnetworks:\n  my_network:\n    driver: bridge"
  },
  {
    "path": "docs/additional-settings.md",
    "content": "### Production-Ready Authentication & Dashboard features\nThis template comes with a pre-configured authentication system and a simple dashboard interface, allowing you to start building your application with user management features immediately.\n\n### Hot Reload on development\nThe project includes two hot reloads running the application, one for the backend and one for the frontend. These automatically restart local servers when they detect changes, ensuring that the application is always up to date without needing manual restarts.\n\n- The **backend hot reload** monitors changes to the backend code.\n- The **frontend hot reload** monitors changes to the frontend code and the `openapi.json` schema generated by the backend.\n\n### Manual Execution of Hot Reload Commands\nYou can manually execute the same commands that the hot reloads call when they detect a change:\n\n1. To export the `openapi.json` schema:\n   ```bash\n   cd fastapi_backend && uv run python -m commands.generate_openapi_schema\n   ```\n   or using Docker:\n   ```bash\n   docker compose run --rm --no-deps -T backend uv run python -m commands.generate_openapi_schema\n   ```\n\n2. To generate the frontend client:\n   ```bash\n   cd nextjs-frontend && npm run generate-client\n   ```\n   or using Docker:\n   ```bash\n   docker compose run --rm --no-deps -T frontend npm run generate-client\n   ```\n\n### Testing\nTo run the tests, you need to run the test database container:\n   ```bash\n   make docker-up-test-db\n   ```\n\nThen run the tests locally:\n   ```bash\n   make test-backend\n   make test-frontend\n   ```\n\nOr using Docker:\n   ```bash\n   make docker-test-backend\n   make docker-test-frontend\n   ```\n### Pre-Commit Setup\nTo maintain code quality and consistency, the project includes two separate pre-commit configuration files:\n- `.pre-commit-config.yaml` is used to run pre-commit checks locally.\n- `.pre-commit-config.docker.yaml` is used to run pre-commit checks within Docker.\n\n### Installing and Activating Pre-Commit Hooks\nTo activate pre-commit hooks, run the following commands for each configuration file:\n\n- **For the local configuration file**:\n  ```bash\n  pre-commit install -c .pre-commit-config.yaml\n  ```\n\n- **For the Docker configuration file**:\n  ```bash\n  pre-commit install -c .pre-commit-config.docker.yaml\n  ```\n\n### Localhost Email Server Setup\n\nTo set up the email server locally, you need to start [MailHog](https://github.com/mailhog/MailHog) by running the following command:\n   ```bash\n   make docker-up-mailhog\n   ```\n\n- **Email client**: Access the email at `http://localhost:8025`.\n\n### Running Pre-Commit Checks\nTo manually run the pre-commit checks on all files, use:\n\n```bash\npre-commit run --all-files -c .pre-commit-config.yaml\n```\n\nor\n\n```bash\npre-commit run --all-files -c .pre-commit-config.docker.yaml\n```\n\n### Updating Pre-Commit Hooks\nTo update the hooks to their latest versions, run:\n\n```bash\npre-commit autoupdate\n```\n### Alembic Database Migrations\nIf you need to create a new Database Migration:\n   ```bash\n   make docker-db-schema migration_name=\"add users\"\n   ```\nthen apply the migration to the database:\n   ```bash\n   make docker-migrate-db\n   ```\n\n### GitHub Actions\nThis project has a pre-configured GitHub Actions setup to enable CI/CD. The workflow configuration files are inside the .github/workflows directory. You can customize these workflows to suit your project's needs better.\n\n### Secrets Configuration\nFor the workflows to function correctly, add the secret keys to your GitHub repository's settings. Navigate to Settings > Secrets and variables > Actions and add the following keys:\n```\nDATABASE_URL: The connection string for your primary database.\nTEST_DATABASE_URL: The connection string for your test database.\nACCESS_SECRET_KEY: The secret key for access token generation.\nRESET_PASSWORD_SECRET_KEY: The secret key for reset password functionality.\nVERIFICATION_SECRET_KEY: The secret key for email or user verification.\n```\n\n## Makefile\n\nThis project includes a `Makefile` that provides a set of commands to simplify everyday tasks such as starting the backend and frontend servers, running tests, building Docker containers, and more.\n\n### Available Commands\n\nYou can see all available commands and their descriptions by running the following command in your terminal:\n\n```bash\nmake help\n```\n"
  },
  {
    "path": "docs/contributing.md",
    "content": "# Contributing\n\nWe can always use your help to improve Next.js FastAPI Template! Please feel free to tackle existing [issues](https://github.com/vintasoftware/nextjs-fastapi-template/issues). If you have a new idea, please create a thread on [Discussions](https://github.com/vintasoftware/django-ai-assistant/discussions).\n\nPlease follow this guide to learn more about how to develop and test the project locally, before opening a pull request.\n\n## Local Dev Setup\n\n### Clone the repo\n\n```bash\ngit clone git@github.com:vintasoftware/nextjs-fastapi-template.git\n```\n\nCheck the [Get Started](get-started.md#setup) page to complete the setup.\n\n\n## Install pre-commit hooks\n\nCheck the [Additional Settings - Install pre-commit hooks](additional-settings.md#pre-commit-setup) section to complete the setup.\n\n\nIt's critical to run the pre-commit hooks before pushing your code to follow the project's code style, and avoid linting errors.\n\n## Updating the OpenAPI schema\n\nIt's critical to update the OpenAPI schema when you make changes to the FastAPI routes or related files:\n\nCheck the [Additional Settings - Manual execution of hot reload commands](additional-settings.md#manual-execution-of-hot-reload-commands) section to run the command.\n\n## Tests\n\nCheck the [Additional Settings - Testing](additional-settings.md#testing) section to run the tests.\n\n## Documentation\n\nWe use [mkdocs-material](https://squidfunk.github.io/mkdocs-material/) to generate the documentation from markdown files.\nCheck the files in the `docs` directory.\n\nTo run the documentation locally, you need to run:\n\n```bash\nuv run mkdocs serve\n```\n\n## Release\n\n!!! info\n    The backend and the frontend are versioned together, that is, they should have the same version number.\n\nTo release and publish a new version, follow these steps:\n\n1. Update the version in `fastapi_backend/pyproject.toml`, `nextjs-frontend/package.json`.\n2. Update the changelog in `CHANGELOG.md`.\n3. Open a PR with the changes.\n4. Once the PR is merged, run the [Release GitHub Action](https://github.com/vintasoftware/nextjs-fastapi-template/actions/workflows/release.yml) to create a draft release.\n5. Review the draft release, ensure the description has at least the associated changelog entry, and publish it.\n"
  },
  {
    "path": "docs/deployment.md",
    "content": "### Overview\n\n Deploying to **Vercel** is supported, with dedicated buttons for the **Frontend** and **Backend** applications. Both require specific configurations during and after deployment to ensure proper functionality.\n\n---\n\n### Frontend Deployment\n\n[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fvintasoftware%2Fnextjs-fastapi-template%2Ftree%2Fmain%2Fnextjs-frontend&env=API_BASE_URL&envDescription=The%20API_BASE_URL%20is%20the%20backend%20URL%20where%20the%20frontend%20sends%20requests.)\n\n- Click the **Frontend** button above to start the deployment process.  \n- During deployment, you will be prompted to set the `API_BASE_URL`. Use a placeholder value (e.g., `https://`) for now, as this will be updated with the backend URL later.  \n- Complete the deployment process [here](#post-deployment-configuration).\n\n### Backend Deployment\n\n[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fvintasoftware%2Fnextjs-fastapi-template%2Ftree%2Fmain%2Ffastapi_backend&env=CORS_ORIGINS,ACCESS_SECRET_KEY,RESET_PASSWORD_SECRET_KEY,VERIFICATION_SECRET_KEY&stores=%5B%7B%22type%22%3A%22postgres%22%7D%5D)\n\n- Click the **Backend** button above to begin deployment.\n- First, set up the database. The connection is automatically configured, so follow the steps, and it should work by default.\n- During the deployment process, you will be prompted to configure the following environment variables:\n\n  - **CORS_ORIGINS**  \n    - Set this to `[\"*\"]` initially to allow all origins. Later, you can update this with the frontend URL.\n\n  - **ACCESS_SECRET_KEY**, **RESET_PASSWORD_SECRET_KEY**, **VERIFICATION_SECRET_KEY**  \n    - During deployment, you can temporarily set these secret keys as plain strings (e.g., `examplekey`). However, you should generate secure keys and update them after the deployment in the **Post-Deployment Configuration** section.\n\n- Complete the deployment process [here](#post-deployment-configuration).\n\n\n## CI (GitHub Actions) Setup for Production Deployment\n\nWe provide the **prod-backend-deploy.yml** and **prod-frontend-deploy.yml** files to enable continuous integration through Github Actions. To connect them to GitHub, simply move them to the .github/workflows/ directory.\n\nYou can do it with the following commands:\n   ```bash\n    mv prod-backend-deploy.yml .github/workflows/prod-backend-deploy.yml\n    mv prod-frontend-deploy.yml .github/workflows/prod-frontend-deploy.yml\n   ```\n\n### Prerequisites\n1. **Create a Vercel Token**:  \n   - Generate your [Vercel Access Token](https://vercel.com/account/tokens).  \n   - Save the token as `VERCEL_TOKEN` in your GitHub secrets.\n\n2. **Install Vercel CLI**:  \n   ```bash\n   pnpm i -g vercel@latest\n   ```\n3. Authenticate your account.\n    ```bash\n   vercel login\n   ```\n### Database Creation (Required)\n\n   1. **Choosing a Database**\n      - You can use your database hosted on a different service or opt for the [Neon](https://neon.tech/docs/introduction) database, which integrates seamlessly with Vercel.\n\n   2. **Setting Up a Neon Database via Vercel**\n      - In the **Projects dashboard** page on Vercel, navigate to the **Storage** section.  \n      - Select the option to **Create a Database** to provision a Neon database.\n\n   3. **Configuring the Database URL**\n      - After creating the database, retrieve the **Database URL** provided by Neon.  \n      - Include this URL in your **Environment Variables** under `DATABASE_URL`.  \n\n   4. **Migrating the Database**\n      - The database migration will happen automatically during the GitHub action deployment, setting up the necessary tables and schema.\n### Frontend Setup\n\n1. Link the nextjs-frontend Project\n\n2. Navigate to the nextjs-frontend directory and run:\n   ```bash\n   cd nextjs-frontend\n   vercel link\n   ```\n3. Follow the prompts:\n   - Link to existing project? No\n   - Modify settings? No\n\n4. Save Project IDs and Add GitHub Secrets:\n  - Open `nextjs-frontend/.vercel/project.json` and add the following to your GitHub repository secrets:\n    - `projectId` → `VERCEL_PROJECT_ID_FRONTEND`\n    - `orgId` → `VERCEL_ORG_ID`\n\n### Backend Setup\n\n1. Link the fastapi_backend Project\n\n2. Navigate to the fastapi_backend directory and run:\n   ```bash\n   cd fastapi_backend\n   vercel link --local-config=vercel.prod.json\n   ```\n   - We use a specific configuration file to set the --local-config value.\n3. Follow the prompts:\n   - Link to existing project? No\n   - Modify settings? No\n\n4. Save Project IDs and Add GitHub Secrets:\n  - Open `fastapi_backend/.vercel/project.json` and add the following to your GitHub repository secrets:\n    - `projectId` → `VERCEL_PROJECT_ID_BACKEND`\n    - `orgId` → `VERCEL_ORG_ID` (Only in case you haven't added that before)\n\n5. Update requirements.txt file:\n      ```bash\n      cd fastapi_backend\n      uv export > requirements.txt\n      ```\n  - Export a new requirements.txt file is required to vercel deploy when the uv.lock is modified.\n\n### Notes\n- Once everything is set up, run `git push`, and the deployment will automatically occur.\n- Please ensure you complete the setup for both the frontend and backend separately.\n- Refer to the [Vercel CLI Documentation](https://vercel.com/docs/cli) for more details.\n- You can find the project_id into the vercel web project settings.\n- You can find the organization_id into the vercel web organization settings.\n\n## Post-Deployment Configuration\n\n### Frontend\n   - Navigate to the **Settings** page of the deployed frontend project.  \n   - Access the **Environment Variables** section.  \n   - Update the `API_BASE_URL` variable with the backend URL once the backend deployment is complete.\n\n### Backend\n   - Access the **Settings** page of the deployed backend project.  \n   - Navigate to the **Environment Variables** section and update the following variables with secure values:\n\n     - **CORS_ORIGINS**  \n       - Once the frontend is deployed, replace `[\"*\"]` with the actual frontend URL.\n\n     - **ACCESS_SECRET_KEY**  \n       - Generate a secure key for API access and set it here.  \n\n     - **RESET_PASSWORD_SECRET_KEY**\n       - Generate a secure key for password reset functionality and set it.\n\n     - **VERIFICATION_SECRET_KEY**  \n       - Generate a secure key for user verification and configure it.\n\n   - For detailed instructions on setting these secret keys, please look at the section on [Setting up Environment Variables](get-started.md#setting-up-environment-variables).\n\n### Fluid serverless activation\n[Fluid](https://vercel.com/docs/functions/fluid-compute) is Vercel's new concurrency model for serverless functions, allowing them to handle multiple \nrequests per execution instead of spinning up a new instance for each request. This improves performance, \nreduces cold starts, and optimizes resource usage, making serverless workloads more efficient.\n\nFollow this [guide](https://vercel.com/docs/functions/fluid-compute#how-to-enable-fluid-compute) to activate Fluid.\n"
  },
  {
    "path": "docs/get-started.md",
    "content": "To use this template for your own project:\n\n1. Create a new repository using this template by following GitHub's [template repository guide](https://docs.github.com/en/repositories/creating-and-managing-repositories/creating-a-repository-from-a-template#creating-a-repository-from-a-template)\n2. Clone your new repository and navigate to it: `cd your-project-name`\n3. Make sure you have Python 3.12 installed\n\nOnce completed, proceed to the [Setup](#setup) section below.\n\n## Setup\n\n### Installing Required Tools\n\n#### 1. uv\nuv is used to manage Python dependencies in the backend. Install uv by following the [official installation guide](https://docs.astral.sh/uv/getting-started/installation/).\n\n#### 2. Node.js, npm, and pnpm\nTo run the frontend, ensure Node.js and npm are installed. Follow the [Node.js installation guide](https://nodejs.org/en/download/).\nAfter that, install pnpm by running:\n```bash\nnpm install -g pnpm\n```\n\n#### 3. Docker\nDocker is needed to run the project in a containerized environment. Follow the appropriate installation guide:\n\n- [Install Docker for Mac](https://docs.docker.com/docker-for-mac/install/)\n- [Install Docker for Windows](https://docs.docker.com/docker-for-windows/install/)\n- [Get Docker CE for Linux](https://docs.docker.com/install/linux/docker-ce/)\n\n#### 4. Docker Compose\nEnsure `docker-compose` is installed. Refer to the [Docker Compose installation guide](https://docs.docker.com/compose/install/).\n\n### Setting Up Environment Variables\n\n**Backend (`fastapi_backend/.env`):**\n\nCopy the `.env.example` files to `.env` and update the variables with your own values.\n   ```bash\n   cd fastapi_backend && cp .env.example .env\n   ```\nYou will only need to update the secret keys. You can use the following command to generate a new secret key:\n   ```bash\n   python3 -c \"import secrets; print(secrets.token_hex(32))\"\n   ```\n\n- The DATABASE, MAIL, OPENAPI, CORS, and FRONTEND_URL settings are ready to use locally.\n\n- The DATABASE and MAIL settings are already configured in Docker Compose if you're using Docker.\n\n- The OPENAPI_URL setting is commented out. Uncommenting it will hide the /docs and openapi.json URLs, which is ideal for production.\n\nYou can check the .env.example file for more information about the variables.\n\n**Frontend (`nextjs-frontend/.env.local`):**\n\nCopy the `.env.example` files to `.env.local`. These values are unlikely to change, so you can leave them as they are.\n   ```bash\n   cd nextjs-frontend && cp .env.example .env.local\n   ```\n\n### Running the Database\nUse Docker to run the database to avoid local installation issues. Build and start the database container:\n   ```bash\n   docker compose build db\n   docker compose up -d db\n   ```\nRun the following command to apply database migrations:\n   ```bash\n   make docker-migrate-db\n   ```\n\n### Build the project (without Docker):\nTo set the project environment locally, use the following commands:\n\n#### Backend\n\nNavigate to the `fastapi_backend` directory and run:\n   ```bash\n   uv sync\n   ```\n\n#### Frontend\nNavigate to the `nextjs-frontend` directory and run:\n   ```bash\n   pnpm install\n   ```\n\n### Build the project (with Docker):\n\nBuild the backend and frontend containers:\n   ```bash\n   make docker-build\n   ```\n\n## Running the Application\n\n**If you are not using Docker:**\n\nStart the FastAPI server:\n   ```bash\n   make start-backend\n   ```\n\nStart the Next.js development server:\n   ```bash\n   make start-frontend\n   ```\n\n**If you are using Docker:**\n\nStart the FastAPI server container:\n   ```bash\n   make docker-start-backend\n   ```\nStart the Next.js development server container:\n   ```bash\n   make docker-start-frontend\n   ```\n\n- **Backend**: Access the API at `http://localhost:8000`.\n- **Frontend**: Access the web application at `http://localhost:3000`.\n\n## Important Considerations\n- **Environment Variables**: Ensure your `.env` files are up-to-date.\n- **Database Setup**: It is recommended to use Docker to run the database, even when running the backend and frontend locally, to simplify configuration and avoid potential conflicts.\n- **Consistency**: It is **not recommended** to switch between running the project locally and using Docker, as this may cause permission issues or unexpected problems. You can choose one method and stick with it.\n"
  },
  {
    "path": "docs/stylesheets/extra.css",
    "content": ":root  > * {\n  --md-primary-fg-color: #004BC9;\n}\n\n\n\n\n"
  },
  {
    "path": "docs/support.md",
    "content": "# Support\n\nIf you have any questions or need help, feel free to create a thread on [GitHub Discussions](https://github.com/vintasoftware/nextjs-fastapi-template/discussions).\n\nIn case you're facing a bug, please [check existing issues](https://github.com/vintasoftware/nextjs-fastapi-template/issues) and create a new one if needed.\n\n## Commercial Support\n\n[![alt text](images/vinta-logo.png \"Vinta Logo\")](https://www.vintasoftware.com/)\n\nThis is an open-source project maintained by [Vinta Software](https://www.vinta.com.br/). We are always looking for exciting work! If you need any commercial support, feel free to get in touch: contact@vinta.com.br\n"
  },
  {
    "path": "docs/technology-selection.md",
    "content": "This template streamlines building APIs with [FastAPI](https://fastapi.tiangolo.com/) and dynamic frontends with [Next.js](https://nextjs.org/). It integrates the backend and frontend using [@hey-api/openapi-ts](https://github.com/hey-ai/openapi-ts) to generate a type-safe client, with automated watchers to keep the OpenAPI schema and client updated, ensuring a smooth and synchronized development workflow.  \n\n- [Next.js](https://nextjs.org/): Fast, SEO-friendly frontend framework  \n- [FastAPI](https://fastapi.tiangolo.com/): High-performance Python backend  \n- [SQLAlchemy](https://www.sqlalchemy.org/): Powerful Python SQL toolkit and ORM\n- [PostgreSQL](https://www.postgresql.org/): Advanced open-source relational database\n- [Pydantic](https://docs.pydantic.dev/): Data validation and settings management using Python type annotations\n- [Zod](https://zod.dev/) + [TypeScript](https://www.typescriptlang.org/): End-to-end type safety and schema validation  \n- [fastapi-users](https://fastapi-users.github.io/fastapi-users/): Complete authentication system with:\n    - Secure password hashing by default\n    - JWT (JSON Web Token) authentication\n    - Email-based password recovery\n- [Shadcn/ui](https://ui.shadcn.com/): Beautiful and customizable React components\n- [OpenAPI-fetch](https://github.com/Hey-AI/openapi-fetch): Fully typed client generation from OpenAPI schema  \n- [fastapi-mail](https://sabuhish.github.io/fastapi-mail/): Efficient email handling for FastAPI applications\n- [uv](https://docs.astral.sh/uv/): An extremely fast Python package and project manager\n- [Pytest](https://docs.pytest.org/): Powerful Python testing framework\n- Code Quality Tools:\n    - [Ruff](https://github.com/astral-sh/ruff): Fast Python linter\n    - [ESLint](https://eslint.org/): JavaScript/TypeScript code quality\n- Hot reload watchers:  \n    - Backend: [Watchdog](https://github.com/gorakhargosh/watchdog) for monitoring file changes  \n    - Frontend: [Chokidar](https://github.com/paulmillr/chokidar) for live updates  \n- [Docker](https://www.docker.com/) and [Docker Compose](https://docs.docker.com/compose/): Consistent environments for development and production\n- [MailHog](https://github.com/mailhog/MailHog): Email server for development\n- [Pre-commit hooks](https://pre-commit.com/): Enforce code quality with automated checks  \n- [OpenAPI JSON schema](https://swagger.io/specification/): Centralized API documentation and client generation  \n\nWith this setup, you'll save time and maintain a seamless connection between your backend and frontend, boosting productivity and reliability.\n"
  },
  {
    "path": "fastapi_backend/.gitignore",
    "content": ".vercel\n"
  },
  {
    "path": "fastapi_backend/Dockerfile",
    "content": "FROM python:3.12-bookworm\n\n# Set the working directory\nWORKDIR /app\n\n# The uv installer requires curl (and certificates) to download the release archive\nRUN apt-get update && apt-get install -y --no-install-recommends curl ca-certificates\n\n# Download the latest uv installer\nADD https://astral.sh/uv/install.sh /uv-installer.sh\n\n# Run the uv installer then remove it\nRUN sh /uv-installer.sh && rm /uv-installer.sh\n\n# Ensure the installed binary is on the `PATH`\nENV PATH=\"/root/.local/bin/:$PATH\"\n\n# Copy dependency files first to leverage Docker caching\nCOPY pyproject.toml uv.lock ./\n\n# Install dependencies using uv\nRUN uv sync --frozen\n\nENV PATH=\"/app/.venv/bin:$PATH\"\n\n# Copy the rest of the application code\nCOPY . .\n\n# Expose the application port\nEXPOSE 8000\n\n# Command to run the application\nCMD [\"./start.sh\"]"
  },
  {
    "path": "fastapi_backend/alembic.ini",
    "content": "# A generic, single database configuration.\n\n[alembic]\n# path to migration scripts.\n# Use forward slashes (/) also on windows to provide an os agnostic path\nscript_location = alembic_migrations\n\n# template used to generate migration file names; The default value is %%(rev)s_%%(slug)s\n# Uncomment the line below if you want the files to be prepended with date and time\n# file_template = %%(year)d_%%(month).2d_%%(day).2d_%%(hour).2d%%(minute).2d-%%(rev)s_%%(slug)s\n\n# sys.path path, will be prepended to sys.path if present.\n# defaults to the current working directory.\nprepend_sys_path = .\n\n# timezone to use when rendering the date within the migration file\n# as well as the filename.\n# If specified, requires the python>=3.9 or backports.zoneinfo library.\n# Any required deps can installed by adding `alembic[tz]` to the pip requirements\n# string value is passed to ZoneInfo()\n# leave blank for localtime\n# timezone =\n\n# max length of characters to apply to the \"slug\" field\n# truncate_slug_length = 40\n\n# set to 'true' to run the environment during\n# the 'revision' command, regardless of autogenerate\n# revision_environment = false\n\n# set to 'true' to allow .pyc and .pyo files without\n# a source .py file to be detected as revisions in the\n# versions/ directory\n# sourceless = false\n\n# version location specification; This defaults\n# to alembic/versions.  When using multiple version\n# directories, initial revisions must be specified with --version-path.\n# The path separator used here should be the separator specified by \"version_path_separator\" below.\n# version_locations = %(here)s/bar:%(here)s/bat:alembic/versions\n\n# version path separator; As mentioned above, this is the character used to split\n# version_locations. The default within new alembic.ini files is \"os\", which uses os.pathsep.\n# If this key is omitted entirely, it falls back to the legacy behavior of splitting on spaces and/or commas.\n# Valid values for version_path_separator are:\n#\n# version_path_separator = :\n# version_path_separator = ;\n# version_path_separator = space\n# version_path_separator = newline\nversion_path_separator = os  # Use os.pathsep. Default configuration used for new projects.\n\n# set to 'true' to search source files recursively\n# in each \"version_locations\" directory\n# new in Alembic version 1.10\n# recursive_version_locations = false\n\n# the output encoding used when revision files\n# are written from script.py.mako\n# output_encoding = utf-8\n\n\n# We are including this url in the env.py file\n# then we can use the .env file to set the url\n# sqlalchemy.url = postgresql+asyncpg://postgres:password@localhost:5432/mydatabase\n\n\n[post_write_hooks]\n# post_write_hooks defines scripts or Python functions that are run\n# on newly generated revision scripts.  See the documentation for further\n# detail and examples\n\n# format using \"black\" - use the console_scripts runner, against the \"black\" entrypoint\n# hooks = black\n# black.type = console_scripts\n# black.entrypoint = black\n# black.options = -l 79 REVISION_SCRIPT_FILENAME\n\n# lint with attempts to fix using \"ruff\" - use the exec runner, execute a binary\n# hooks = ruff\n# ruff.type = exec\n# ruff.executable = %(here)s/.venv/bin/ruff\n# ruff.options = --fix REVISION_SCRIPT_FILENAME\n\n# Logging configuration\n[loggers]\nkeys = root,sqlalchemy,alembic\n\n[handlers]\nkeys = console\n\n[formatters]\nkeys = generic\n\n[logger_root]\nlevel = WARN\nhandlers = console\nqualname =\n\n[logger_sqlalchemy]\nlevel = WARN\nhandlers =\nqualname = sqlalchemy.engine\n\n[logger_alembic]\nlevel = INFO\nhandlers =\nqualname = alembic\n\n[handler_console]\nclass = StreamHandler\nargs = (sys.stderr,)\nlevel = NOTSET\nformatter = generic\n\n[formatter_generic]\nformat = %(levelname)-5.5s [%(name)s] %(message)s\ndatefmt = %H:%M:%S\n"
  },
  {
    "path": "fastapi_backend/alembic_migrations/README",
    "content": "Generic single-database configuration with an async dbapi."
  },
  {
    "path": "fastapi_backend/alembic_migrations/env.py",
    "content": "import asyncio\nimport os\nfrom urllib.parse import urlparse\n\nfrom logging.config import fileConfig\n\nfrom sqlalchemy import pool\nfrom sqlalchemy.engine import Connection\nfrom sqlalchemy.ext.asyncio import async_engine_from_config\n\nfrom alembic import context\nfrom app.models import Base\nfrom dotenv import load_dotenv\n\nload_dotenv()\n\n# this is the Alembic Config object, which provides\n# access to the values within the .ini file in use.\nconfig = context.config\n\n# Interpret the config file for Python logging.\n# This line sets up loggers basically.\nif config.config_file_name is not None:\n    fileConfig(config.config_file_name)\n\n# add your model's MetaData object here\n# for 'autogenerate' support\ntarget_metadata = Base.metadata\n# target_metadata = None\n\n# other values from the config, defined by the needs of env.py,\n# can be acquired:\n# my_important_option = config.get_main_option(\"my_important_option\")\n# ... etc.\n\n# Retrieve the database URL from the environment\n# set it during execution\ndatabase_url = os.getenv(\"DATABASE_URL\")\n\nif not database_url:\n    raise ValueError(\"DATABASE_URL environment variable is not set!\")\n\nparsed_db_url = urlparse(database_url)\n\nasync_db_connection_url = (\n    f\"postgresql+asyncpg://{parsed_db_url.username}:{parsed_db_url.password}@\"\n    f\"{parsed_db_url.hostname}{':' + str(parsed_db_url.port) if parsed_db_url.port else ''}\"\n    f\"{parsed_db_url.path}\"\n)\n\nconfig.set_main_option(\"sqlalchemy.url\", async_db_connection_url)\n\n\ndef run_migrations_offline() -> None:\n    \"\"\"Run migrations in 'offline' mode.\n\n    This configures the context with just a URL\n    and not an Engine, though an Engine is acceptable\n    here as well.  By skipping the Engine creation\n    we don't even need a DBAPI to be available.\n\n    Calls to context.execute() here emit the given string to the\n    script output.\n\n    \"\"\"\n    url = config.get_main_option(\"sqlalchemy.url\")\n    context.configure(\n        url=url,\n        target_metadata=target_metadata,\n        literal_binds=True,\n        dialect_opts={\"paramstyle\": \"named\"},\n    )\n\n    with context.begin_transaction():\n        context.run_migrations()\n\n\ndef do_run_migrations(connection: Connection) -> None:\n    context.configure(connection=connection, target_metadata=target_metadata)\n\n    with context.begin_transaction():\n        context.run_migrations()\n\n\nasync def run_async_migrations() -> None:\n    \"\"\"In this scenario we need to create an Engine\n    and associate a connection with the context.\n\n    \"\"\"\n\n    connectable = async_engine_from_config(\n        config.get_section(config.config_ini_section, {}),\n        prefix=\"sqlalchemy.\",\n        poolclass=pool.NullPool,\n    )\n\n    async with connectable.connect() as connection:\n        await connection.run_sync(do_run_migrations)\n\n    await connectable.dispose()\n\n\ndef run_migrations_online() -> None:\n    \"\"\"Run migrations in 'online' mode.\"\"\"\n\n    asyncio.run(run_async_migrations())\n\n\nif context.is_offline_mode():\n    run_migrations_offline()\nelse:\n    run_migrations_online()\n"
  },
  {
    "path": "fastapi_backend/alembic_migrations/script.py.mako",
    "content": "\"\"\"${message}\n\nRevision ID: ${up_revision}\nRevises: ${down_revision | comma,n}\nCreate Date: ${create_date}\n\n\"\"\"\nfrom typing import Sequence, Union\n\nfrom alembic import op\nimport sqlalchemy as sa\nimport fastapi_users_db_sqlalchemy\n${imports if imports else \"\"}\n\n# revision identifiers, used by Alembic.\nrevision: str = ${repr(up_revision)}\ndown_revision: Union[str, None] = ${repr(down_revision)}\nbranch_labels: Union[str, Sequence[str], None] = ${repr(branch_labels)}\ndepends_on: Union[str, Sequence[str], None] = ${repr(depends_on)}\n\n\ndef upgrade() -> None:\n    ${upgrades if upgrades else \"pass\"}\n\n\ndef downgrade() -> None:\n    ${downgrades if downgrades else \"pass\"}\n"
  },
  {
    "path": "fastapi_backend/alembic_migrations/versions/402d067a8b92_added_user_table.py",
    "content": "\"\"\"Added user table\n\nRevision ID: 402d067a8b92\nRevises:\nCreate Date: 2024-09-27 14:01:44.155160\n\n\"\"\"\n\nfrom typing import Sequence, Union\n\nimport fastapi_users_db_sqlalchemy\nimport sqlalchemy as sa\n\nfrom alembic import op\n\n# revision identifiers, used by Alembic.\nrevision: str = \"402d067a8b92\"\ndown_revision: Union[str, None] = None\nbranch_labels: Union[str, Sequence[str], None] = None\ndepends_on: Union[str, Sequence[str], None] = None\n\n\ndef upgrade() -> None:\n    # ### commands auto generated by Alembic - please adjust! ###\n    op.create_table(\n        \"user\",\n        sa.Column(\"id\", fastapi_users_db_sqlalchemy.generics.GUID(), nullable=False),\n        sa.Column(\"email\", sa.String(length=320), nullable=False),\n        sa.Column(\"hashed_password\", sa.String(length=1024), nullable=False),\n        sa.Column(\"is_active\", sa.Boolean(), nullable=False),\n        sa.Column(\"is_superuser\", sa.Boolean(), nullable=False),\n        sa.Column(\"is_verified\", sa.Boolean(), nullable=False),\n        sa.PrimaryKeyConstraint(\"id\"),\n    )\n    op.create_index(op.f(\"ix_user_email\"), \"user\", [\"email\"], unique=True)\n    # ### end Alembic commands ###\n\n\ndef downgrade() -> None:\n    # ### commands auto generated by Alembic - please adjust! ###\n    op.drop_index(op.f(\"ix_user_email\"), table_name=\"user\")\n    op.drop_table(\"user\")\n    # ### end Alembic commands ###\n"
  },
  {
    "path": "fastapi_backend/alembic_migrations/versions/b389592974f8_add_item_model.py",
    "content": "\"\"\"Add item model\n\nRevision ID: b389592974f8\nRevises: 402d067a8b92\nCreate Date: 2024-12-06 17:52:50.698249\n\n\"\"\"\n\nfrom typing import Sequence, Union\n\nfrom alembic import op\nimport sqlalchemy as sa\n\n\n# revision identifiers, used by Alembic.\nrevision: str = \"b389592974f8\"\ndown_revision: Union[str, None] = \"402d067a8b92\"\nbranch_labels: Union[str, Sequence[str], None] = None\ndepends_on: Union[str, Sequence[str], None] = None\n\n\ndef upgrade() -> None:\n    # ### commands auto generated by Alembic - please adjust! ###\n    op.create_table(\n        \"items\",\n        sa.Column(\"id\", sa.UUID(), nullable=False),\n        sa.Column(\"name\", sa.String(), nullable=False),\n        sa.Column(\"description\", sa.String(), nullable=True),\n        sa.Column(\"quantity\", sa.Integer(), nullable=True),\n        sa.Column(\"user_id\", sa.UUID(), nullable=False),\n        sa.ForeignKeyConstraint(\n            [\"user_id\"],\n            [\"user.id\"],\n        ),\n        sa.PrimaryKeyConstraint(\"id\"),\n    )\n    # ### end Alembic commands ###\n\n\ndef downgrade() -> None:\n    # ### commands auto generated by Alembic - please adjust! ###\n    op.drop_table(\"items\")\n    # ### end Alembic commands ###\n"
  },
  {
    "path": "fastapi_backend/api/index.py",
    "content": "from app.main import app  # noqa: F401\n"
  },
  {
    "path": "fastapi_backend/app/__init__.py",
    "content": ""
  },
  {
    "path": "fastapi_backend/app/config.py",
    "content": "from typing import Set\n\nfrom pydantic_settings import BaseSettings, SettingsConfigDict\n\n\nclass Settings(BaseSettings):\n    # OpenAPI docs\n    OPENAPI_URL: str = \"/openapi.json\"\n\n    # Database\n    DATABASE_URL: str\n    TEST_DATABASE_URL: str | None = None\n    EXPIRE_ON_COMMIT: bool = False\n\n    # User\n    ACCESS_SECRET_KEY: str\n    RESET_PASSWORD_SECRET_KEY: str\n    VERIFICATION_SECRET_KEY: str\n    ALGORITHM: str = \"HS256\"\n    ACCESS_TOKEN_EXPIRE_SECONDS: int = 3600\n\n    # Email\n    MAIL_USERNAME: str | None = None\n    MAIL_PASSWORD: str | None = None\n    MAIL_FROM: str | None = None\n    MAIL_SERVER: str | None = None\n    MAIL_PORT: int | None = None\n    MAIL_FROM_NAME: str = \"FastAPI template\"\n    MAIL_STARTTLS: bool = True\n    MAIL_SSL_TLS: bool = False\n    USE_CREDENTIALS: bool = True\n    VALIDATE_CERTS: bool = True\n    TEMPLATE_DIR: str = \"email_templates\"\n\n    # Frontend\n    FRONTEND_URL: str = \"http://localhost:3000\"\n\n    # CORS\n    CORS_ORIGINS: Set[str]\n\n    model_config = SettingsConfigDict(\n        env_file=\".env\", env_file_encoding=\"utf-8\", extra=\"ignore\"\n    )\n\n\nsettings = Settings()\n"
  },
  {
    "path": "fastapi_backend/app/database.py",
    "content": "from typing import AsyncGenerator\nfrom urllib.parse import urlparse\n\nfrom fastapi import Depends\nfrom fastapi_users.db import SQLAlchemyUserDatabase\nfrom sqlalchemy import NullPool\nfrom sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine\n\nfrom .config import settings\nfrom .models import Base, User\n\n\nparsed_db_url = urlparse(settings.DATABASE_URL)\n\nasync_db_connection_url = (\n    f\"postgresql+asyncpg://{parsed_db_url.username}:{parsed_db_url.password}@\"\n    f\"{parsed_db_url.hostname}{':' + str(parsed_db_url.port) if parsed_db_url.port else ''}\"\n    f\"{parsed_db_url.path}\"\n)\n\n# Disable connection pooling for serverless environments like Vercel\nengine = create_async_engine(async_db_connection_url, poolclass=NullPool)\n\nasync_session_maker = async_sessionmaker(\n    engine, expire_on_commit=settings.EXPIRE_ON_COMMIT\n)\n\n\nasync def create_db_and_tables():\n    async with engine.begin() as conn:\n        await conn.run_sync(Base.metadata.create_all)\n\n\nasync def get_async_session() -> AsyncGenerator[AsyncSession, None]:\n    async with async_session_maker() as session:\n        yield session\n\n\nasync def get_user_db(session: AsyncSession = Depends(get_async_session)):\n    yield SQLAlchemyUserDatabase(session, User)\n"
  },
  {
    "path": "fastapi_backend/app/email.py",
    "content": "from pathlib import Path\nimport urllib.parse\n\nfrom fastapi_mail import FastMail, MessageSchema, ConnectionConfig, MessageType\nfrom .config import settings\nfrom .models import User\n\n\ndef get_email_config():\n    conf = ConnectionConfig(\n        MAIL_USERNAME=settings.MAIL_USERNAME,\n        MAIL_PASSWORD=settings.MAIL_PASSWORD,\n        MAIL_FROM=settings.MAIL_FROM,\n        MAIL_PORT=settings.MAIL_PORT,\n        MAIL_SERVER=settings.MAIL_SERVER,\n        MAIL_FROM_NAME=settings.MAIL_FROM_NAME,\n        MAIL_STARTTLS=settings.MAIL_STARTTLS,\n        MAIL_SSL_TLS=settings.MAIL_SSL_TLS,\n        USE_CREDENTIALS=settings.USE_CREDENTIALS,\n        VALIDATE_CERTS=settings.VALIDATE_CERTS,\n        TEMPLATE_FOLDER=Path(__file__).parent / settings.TEMPLATE_DIR,\n    )\n    return conf\n\n\nasync def send_reset_password_email(user: User, token: str):\n    conf = get_email_config()\n    email = user.email\n    base_url = f\"{settings.FRONTEND_URL}/password-recovery/confirm?\"\n    params = {\"token\": token}\n    encoded_params = urllib.parse.urlencode(params)\n    link = f\"{base_url}{encoded_params}\"\n    message = MessageSchema(\n        subject=\"Password recovery\",\n        recipients=[email],\n        template_body={\"username\": email, \"link\": link},\n        subtype=MessageType.html,\n    )\n\n    fm = FastMail(conf)\n    await fm.send_message(message, template_name=\"password_reset.html\")\n"
  },
  {
    "path": "fastapi_backend/app/email_templates/__init__.py",
    "content": ""
  },
  {
    "path": "fastapi_backend/app/email_templates/password_reset.html",
    "content": "<html>\n  <body>\n    <p>Hello {{ username }},</p>\n\n    <p>We received a request to reset your password. You can reset your password by clicking the link below:</p>\n\n    <p><a href=\"{{ link }}\">Reset password</a></p>\n\n    <p>If you did not request this, please ignore this email.</p>\n\n    <p>Best regards,<br>\n    YourCompany</p>\n  </body>\n</html>"
  },
  {
    "path": "fastapi_backend/app/main.py",
    "content": "from fastapi import FastAPI\nfrom fastapi_pagination import add_pagination\nfrom .schemas import UserCreate, UserRead, UserUpdate\nfrom .users import auth_backend, fastapi_users, AUTH_URL_PATH\nfrom fastapi.middleware.cors import CORSMiddleware\nfrom .utils import simple_generate_unique_route_id\nfrom app.routes.items import router as items_router\nfrom app.config import settings\n\napp = FastAPI(\n    generate_unique_id_function=simple_generate_unique_route_id,\n    openapi_url=settings.OPENAPI_URL,\n)\n\n# Middleware for CORS configuration\napp.add_middleware(\n    CORSMiddleware,\n    allow_origins=settings.CORS_ORIGINS,\n    allow_credentials=True,\n    allow_methods=[\"*\"],\n    allow_headers=[\"*\"],\n)\n\n# Include authentication and user management routes\napp.include_router(\n    fastapi_users.get_auth_router(auth_backend),\n    prefix=f\"/{AUTH_URL_PATH}/jwt\",\n    tags=[\"auth\"],\n)\napp.include_router(\n    fastapi_users.get_register_router(UserRead, UserCreate),\n    prefix=f\"/{AUTH_URL_PATH}\",\n    tags=[\"auth\"],\n)\napp.include_router(\n    fastapi_users.get_reset_password_router(),\n    prefix=f\"/{AUTH_URL_PATH}\",\n    tags=[\"auth\"],\n)\napp.include_router(\n    fastapi_users.get_verify_router(UserRead),\n    prefix=f\"/{AUTH_URL_PATH}\",\n    tags=[\"auth\"],\n)\napp.include_router(\n    fastapi_users.get_users_router(UserRead, UserUpdate),\n    prefix=\"/users\",\n    tags=[\"users\"],\n)\n\n# Include items routes\napp.include_router(items_router, prefix=\"/items\")\nadd_pagination(app)\n"
  },
  {
    "path": "fastapi_backend/app/models.py",
    "content": "from fastapi_users.db import SQLAlchemyBaseUserTableUUID\nfrom sqlalchemy.orm import DeclarativeBase\nfrom sqlalchemy import Column, String, Integer, ForeignKey\nfrom sqlalchemy.orm import relationship\nfrom sqlalchemy.dialects.postgresql import UUID\nfrom uuid import uuid4\n\n\nclass Base(DeclarativeBase):\n    pass\n\n\nclass User(SQLAlchemyBaseUserTableUUID, Base):\n    items = relationship(\"Item\", back_populates=\"user\", cascade=\"all, delete-orphan\")\n\n\nclass Item(Base):\n    __tablename__ = \"items\"\n\n    id = Column(UUID(as_uuid=True), primary_key=True, default=uuid4)\n    name = Column(String, nullable=False)\n    description = Column(String, nullable=True)\n    quantity = Column(Integer, nullable=True)\n    user_id = Column(UUID(as_uuid=True), ForeignKey(\"user.id\"), nullable=False)\n\n    user = relationship(\"User\", back_populates=\"items\")\n"
  },
  {
    "path": "fastapi_backend/app/routes/__init__.py",
    "content": ""
  },
  {
    "path": "fastapi_backend/app/routes/items.py",
    "content": "from uuid import UUID\n\nfrom fastapi import APIRouter, Depends, HTTPException, Query\nfrom fastapi_pagination import Page, Params\nfrom fastapi_pagination.ext.sqlalchemy import apaginate\nfrom sqlalchemy.ext.asyncio import AsyncSession\nfrom sqlalchemy.future import select\n\nfrom app.database import User, get_async_session\nfrom app.models import Item\nfrom app.schemas import ItemRead, ItemCreate\nfrom app.users import current_active_user\n\nrouter = APIRouter(tags=[\"item\"])\n\n\ndef transform_items(items):\n    return [ItemRead.model_validate(item) for item in items]\n\n\n@router.get(\"/\", response_model=Page[ItemRead])\nasync def read_item(\n    db: AsyncSession = Depends(get_async_session),\n    user: User = Depends(current_active_user),\n    page: int = Query(1, ge=1, description=\"Page number\"),\n    size: int = Query(10, ge=1, le=100, description=\"Page size\"),\n):\n    params = Params(page=page, size=size)\n    query = select(Item).filter(Item.user_id == user.id)\n    return await apaginate(db, query, params, transformer=transform_items)\n\n\n@router.post(\"/\", response_model=ItemRead)\nasync def create_item(\n    item: ItemCreate,\n    db: AsyncSession = Depends(get_async_session),\n    user: User = Depends(current_active_user),\n):\n    db_item = Item(**item.model_dump(), user_id=user.id)\n    db.add(db_item)\n    await db.commit()\n    await db.refresh(db_item)\n    return db_item\n\n\n@router.delete(\"/{item_id}\")\nasync def delete_item(\n    item_id: UUID,\n    db: AsyncSession = Depends(get_async_session),\n    user: User = Depends(current_active_user),\n):\n    result = await db.execute(\n        select(Item).filter(Item.id == item_id, Item.user_id == user.id)\n    )\n    item = result.scalars().first()\n\n    if not item:\n        raise HTTPException(status_code=404, detail=\"Item not found or not authorized\")\n\n    await db.delete(item)\n    await db.commit()\n\n    return {\"message\": \"Item successfully deleted\"}\n"
  },
  {
    "path": "fastapi_backend/app/schemas.py",
    "content": "import uuid\n\nfrom fastapi_users import schemas\nfrom pydantic import BaseModel\nfrom uuid import UUID\n\n\nclass UserRead(schemas.BaseUser[uuid.UUID]):\n    pass\n\n\nclass UserCreate(schemas.BaseUserCreate):\n    pass\n\n\nclass UserUpdate(schemas.BaseUserUpdate):\n    pass\n\n\nclass ItemBase(BaseModel):\n    name: str\n    description: str | None = None\n    quantity: int | None = None\n\n\nclass ItemCreate(ItemBase):\n    pass\n\n\nclass ItemRead(ItemBase):\n    id: UUID\n    user_id: UUID\n\n    model_config = {\"from_attributes\": True}\n"
  },
  {
    "path": "fastapi_backend/app/users.py",
    "content": "import uuid\nimport re\n\nfrom typing import Optional\n\nfrom fastapi import Depends, Request\nfrom fastapi_users import (\n    BaseUserManager,\n    FastAPIUsers,\n    UUIDIDMixin,\n    InvalidPasswordException,\n)\n\nfrom fastapi_users.authentication import (\n    AuthenticationBackend,\n    BearerTransport,\n    JWTStrategy,\n)\nfrom fastapi_users.db import SQLAlchemyUserDatabase\n\nfrom .config import settings\nfrom .database import get_user_db\nfrom .email import send_reset_password_email\nfrom .models import User\nfrom .schemas import UserCreate\n\nAUTH_URL_PATH = \"auth\"\n\n\nclass UserManager(UUIDIDMixin, BaseUserManager[User, uuid.UUID]):\n    reset_password_token_secret = settings.RESET_PASSWORD_SECRET_KEY\n    verification_token_secret = settings.VERIFICATION_SECRET_KEY\n\n    async def on_after_register(self, user: User, request: Optional[Request] = None):\n        print(f\"User {user.id} has registered.\")\n\n    async def on_after_forgot_password(\n        self, user: User, token: str, request: Optional[Request] = None\n    ):\n        await send_reset_password_email(user, token)\n\n    async def on_after_request_verify(\n        self, user: User, token: str, request: Optional[Request] = None\n    ):\n        print(f\"Verification requested for user {user.id}. Verification token: {token}\")\n\n    async def validate_password(\n        self,\n        password: str,\n        user: UserCreate,\n    ) -> None:\n        errors = []\n\n        if len(password) < 8:\n            errors.append(\"Password should be at least 8 characters.\")\n        if user.email in password:\n            errors.append(\"Password should not contain e-mail.\")\n        if not any(char.isupper() for char in password):\n            errors.append(\"Password should contain at least one uppercase letter.\")\n        if not re.search(r'[!@#$%^&*(),.?\":{}|<>]', password):\n            errors.append(\"Password should contain at least one special character.\")\n\n        if errors:\n            raise InvalidPasswordException(reason=errors)\n\n\nasync def get_user_manager(user_db: SQLAlchemyUserDatabase = Depends(get_user_db)):\n    yield UserManager(user_db)\n\n\nbearer_transport = BearerTransport(tokenUrl=f\"{AUTH_URL_PATH}/jwt/login\")\n\n\ndef get_jwt_strategy() -> JWTStrategy:\n    return JWTStrategy(\n        secret=settings.ACCESS_SECRET_KEY,\n        lifetime_seconds=settings.ACCESS_TOKEN_EXPIRE_SECONDS,\n    )\n\n\nauth_backend = AuthenticationBackend(\n    name=\"jwt\",\n    transport=bearer_transport,\n    get_strategy=get_jwt_strategy,\n)\n\nfastapi_users = FastAPIUsers[User, uuid.UUID](get_user_manager, [auth_backend])\n\ncurrent_active_user = fastapi_users.current_user(active=True)\n"
  },
  {
    "path": "fastapi_backend/app/utils.py",
    "content": "from fastapi.routing import APIRoute\n\n\ndef simple_generate_unique_route_id(route: APIRoute):\n    return f\"{route.tags[0]}-{route.name}\"\n"
  },
  {
    "path": "fastapi_backend/commands/__init__.py",
    "content": ""
  },
  {
    "path": "fastapi_backend/commands/generate_openapi_schema.py",
    "content": "import json\nfrom pathlib import Path\nfrom app.main import app\nimport os\n\nfrom dotenv import load_dotenv\n\nload_dotenv()\n\nOUTPUT_FILE = os.getenv(\"OPENAPI_OUTPUT_FILE\")\n\n\ndef generate_openapi_schema(output_file):\n    schema = app.openapi()\n    output_path = Path(output_file)\n\n    updated_schema = remove_operation_id_tag(schema)\n\n    output_path.write_text(json.dumps(updated_schema, indent=2))\n    print(f\"OpenAPI schema saved to {output_file}\")\n\n\ndef remove_operation_id_tag(schema):\n    \"\"\"\n    Removes the tag prefix from the operation IDs in the OpenAPI schema.\n\n    This cleans up the OpenAPI operation IDs that are used by the frontend\n    client generator to create the names of the functions. The modified\n    schema is then returned.\n    \"\"\"\n    for path_data in schema[\"paths\"].values():\n        for operation in path_data.values():\n            tag = operation[\"tags\"][0]\n            operation_id = operation[\"operationId\"]\n            to_remove = f\"{tag}-\"\n            new_operation_id = operation_id[len(to_remove) :]\n            operation[\"operationId\"] = new_operation_id\n    return schema\n\n\nif __name__ == \"__main__\":\n    generate_openapi_schema(OUTPUT_FILE)\n"
  },
  {
    "path": "fastapi_backend/mypy.ini",
    "content": "[mypy]\npython_version = 3.12\nfiles = src/**/*.py\nfollow_imports = skip\nignore_missing_imports = True\nstrict = True"
  },
  {
    "path": "fastapi_backend/pyproject.toml",
    "content": "[project]\nname = \"app\"\nversion = \"0.0.6\"\ndescription = \"\"\nauthors = [{ name = \"Anderson Resende\", email = \"anderson@vinta.com.br\" }]\nrequires-python = \">=3.12,<3.13\"\nreadme = \"README.md\"\ndependencies = [\n    \"fastapi[standard]>=0.115.0,<0.116\",\n    \"asyncpg>=0.29.0,<0.30\",\n    \"fastapi-users[sqlalchemy]>=13.0.0,<14\",\n    \"pydantic-settings>=2.5.2,<3\",\n    \"fastapi-mail>=1.4.1,<2\",\n    \"fastapi-pagination==0.13.3\"\n]\n\n[dependency-groups]\ndev = [\n    \"pre-commit>=3.4.0,<4\",\n    \"ruff>=0.1.0,<0.2\",\n    \"watchdog>=5.0.3,<6\",\n    \"python-dotenv>=1.0.1,<2\",\n    \"pytest>=8.3.3,<9\",\n    \"pytest-mock>=3.14.0,<4\",\n    \"mypy>=1.13.0,<2\",\n    \"coveralls>=4.0.1,<5\",\n    \"alembic>=1.14.0,<2\",\n    \"pytest-asyncio>=0.24.0,<0.25\",\n    \"mkdocs-material>=9.6.9\",\n    \"mkdocs-material[imaging]>=9.6.9\",\n]\n\n[tool.uv]\npackage = false\n\n[tool.hatch.build.targets.sdist]\ninclude = [\"commands\"]\n\n[tool.hatch.build.targets.wheel]\ninclude = [\"commands\"]\n\n[build-system]\nrequires = [\"hatchling\"]\nbuild-backend = \"hatchling.build\"\n"
  },
  {
    "path": "fastapi_backend/pytest.ini",
    "content": "[pytest]\nasyncio_mode = auto"
  },
  {
    "path": "fastapi_backend/requirements.txt",
    "content": "# This file was autogenerated by uv via the following command:\n#    uv export\naiosmtplib==2.0.2 \\\n    --hash=sha256:138599a3227605d29a9081b646415e9e793796ca05322a78f69179f0135016a3 \\\n    --hash=sha256:1e631a7a3936d3e11c6a144fb8ffd94bb4a99b714f2cb433e825d88b698e37bc\nalembic==1.14.0 \\\n    --hash=sha256:99bd884ca390466db5e27ffccff1d179ec5c05c965cfefc0607e69f9e411cb25 \\\n    --hash=sha256:b00892b53b3642d0b8dbedba234dbf1924b69be83a9a769d5a624b01094e304b\nannotated-types==0.7.0 \\\n    --hash=sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53 \\\n    --hash=sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89\nanyio==4.8.0 \\\n    --hash=sha256:1d9fe889df5212298c0c0723fa20479d1b94883a2df44bd3897aa91083316f7a \\\n    --hash=sha256:b5011f270ab5eb0abf13385f851315585cc37ef330dd88e27ec3d34d651fd47a\nargon2-cffi==23.1.0 \\\n    --hash=sha256:879c3e79a2729ce768ebb7d36d4609e3a78a4ca2ec3a9f12286ca057e3d0db08 \\\n    --hash=sha256:c670642b78ba29641818ab2e68bd4e6a78ba53b7eff7b4c3815ae16abf91c7ea\nargon2-cffi-bindings==21.2.0 \\\n    --hash=sha256:58ed19212051f49a523abb1dbe954337dc82d947fb6e5a0da60f7c8471a8476c \\\n    --hash=sha256:603ca0aba86b1349b147cab91ae970c63118a0f30444d4bc80355937c950c082 \\\n    --hash=sha256:8cd69c07dd875537a824deec19f978e0f2078fdda07fd5c42ac29668dda5f40f \\\n    --hash=sha256:9524464572e12979364b7d600abf96181d3541da11e23ddf565a32e70bd4dc0d \\\n    --hash=sha256:b2ef1c30440dbbcba7a5dc3e319408b59676e2e039e2ae11a8775ecf482b192f \\\n    --hash=sha256:b746dba803a79238e925d9046a63aa26bf86ab2a2fe74ce6b009a1c3f5c8f2ae \\\n    --hash=sha256:bb89ceffa6c791807d1305ceb77dbfacc5aa499891d2c55661c6459651fc39e3 \\\n    --hash=sha256:bd46088725ef7f58b5a1ef7ca06647ebaf0eb4baff7d1d0d177c6cc8744abd86 \\\n    --hash=sha256:ccb949252cb2ab3a08c02024acb77cfb179492d5701c7cbdbfd776124d4d2367 \\\n    --hash=sha256:e415e3f62c8d124ee16018e491a009937f8cf7ebf5eb430ffc5de21b900dad93 \\\n    --hash=sha256:f1152ac548bd5b8bcecfb0b0371f082037e47128653df2e8ba6e914d384f3c3e\nasyncpg==0.29.0 \\\n    --hash=sha256:2245be8ec5047a605e0b454c894e54bf2ec787ac04b1cb7e0d3c67aa1e32f0fe \\\n    --hash=sha256:37a2ec1b9ff88d8773d3eb6d3784dc7e3fee7756a5317b67f923172a4748a175 \\\n    --hash=sha256:54858bc25b49d1114178d65a88e48ad50cb2b6f3e475caa0f0c092d5f527c106 \\\n    --hash=sha256:6011b0dc29886ab424dc042bf9eeb507670a3b40aece3439944006aafe023178 \\\n    --hash=sha256:b544ffc66b039d5ec5a7454667f855f7fec08e0dfaf5a5490dfafbb7abbd2cfb \\\n    --hash=sha256:bb1292d9fad43112a85e98ecdc2e051602bce97c199920586be83254d9dafc02 \\\n    --hash=sha256:bde17a1861cf10d5afce80a36fca736a86769ab3579532c03e45f83ba8a09c59 \\\n    --hash=sha256:d1c49e1f44fffafd9a55e1a9b101590859d881d639ea2922516f5d9c512d354e \\\n    --hash=sha256:d84156d5fb530b06c493f9e7635aa18f518fa1d1395ef240d211cb563c4e2364\nbabel==2.17.0 \\\n    --hash=sha256:0c54cffb19f690cdcc52a3b50bcbf71e07a808d1c80d549f2459b9d2cf0afb9d \\\n    --hash=sha256:4d0b53093fdfb4b21c92b5213dba5a1b23885afa8383709427046b21c366e5f2\nbackrefs==5.8 \\\n    --hash=sha256:2cab642a205ce966af3dd4b38ee36009b31fa9502a35fd61d59ccc116e40a6bd \\\n    --hash=sha256:2e1c15e4af0e12e45c8701bd5da0902d326b2e200cafcd25e49d9f06d44bb61b \\\n    --hash=sha256:a66851e4533fb5b371aa0628e1fee1af05135616b86140c9d787a2ffdf4b8fdc \\\n    --hash=sha256:bbef7169a33811080d67cdf1538c8289f76f0942ff971222a16034da88a73486 \\\n    --hash=sha256:c67f6638a34a5b8730812f5101376f9d41dc38c43f1fdc35cb54700f6ed4465d\nbcrypt==4.1.2 \\\n    --hash=sha256:02d9ef8915f72dd6daaef40e0baeef8a017ce624369f09754baf32bb32dba25f \\\n    --hash=sha256:1c28973decf4e0e69cee78c68e30a523be441972c826703bb93099868a8ff5b5 \\\n    --hash=sha256:2a298db2a8ab20056120b45e86c00a0a5eb50ec4075b6142db35f593b97cb3fb \\\n    --hash=sha256:33313a1200a3ae90b75587ceac502b048b840fc69e7f7a0905b5f87fac7a1258 \\\n    --hash=sha256:3566a88234e8de2ccae31968127b0ecccbb4cddb629da744165db72b58d88ca4 \\\n    --hash=sha256:387e7e1af9a4dd636b9505a465032f2f5cb8e61ba1120e79a0e1cd0b512f3dfc \\\n    --hash=sha256:44290ccc827d3a24604f2c8bcd00d0da349e336e6503656cb8192133e27335e2 \\\n    --hash=sha256:57fa9442758da926ed33a91644649d3e340a71e2d0a5a8de064fb621fd5a3326 \\\n    --hash=sha256:68e3c6642077b0c8092580c819c1684161262b2e30c4f45deb000c38947bf483 \\\n    --hash=sha256:69057b9fc5093ea1ab00dd24ede891f3e5e65bee040395fb1e66ee196f9c9b4a \\\n    --hash=sha256:6cad43d8c63f34b26aef462b6f5e44fdcf9860b723d2453b5d391258c4c8e966 \\\n    --hash=sha256:71b8be82bc46cedd61a9f4ccb6c1a493211d031415a34adde3669ee1b0afbb63 \\\n    --hash=sha256:732b3920a08eacf12f93e6b04ea276c489f1c8fb49344f564cca2adb663b3e4c \\\n    --hash=sha256:9800ae5bd5077b13725e2e3934aa3c9c37e49d3ea3d06318010aa40f54c63551 \\\n    --hash=sha256:ac621c093edb28200728a9cca214d7e838529e557027ef0581685909acd28b5e \\\n    --hash=sha256:b8df79979c5bae07f1db22dcc49cc5bccf08a0380ca5c6f391cbb5790355c0b0 \\\n    --hash=sha256:b90e216dc36864ae7132cb151ffe95155a37a14e0de3a8f64b49655dd959ff9c \\\n    --hash=sha256:ba55e40de38a24e2d78d34c2d36d6e864f93e0d79d0b6ce915e4335aa81d01b1 \\\n    --hash=sha256:be3ab1071662f6065899fe08428e45c16aa36e28bc42921c4901a191fda6ee42 \\\n    --hash=sha256:ea505c97a5c465ab8c3ba75c0805a102ce526695cd6818c6de3b1a38f6f60da1 \\\n    --hash=sha256:eb3bd3321517916696233b5e0c67fd7d6281f0ef48e66812db35fc963a422a1c \\\n    --hash=sha256:f70d9c61f9c4ca7d57f3bfe88a5ccf62546ffbadf3681bb1e268d9d2e41c91a7 \\\n    --hash=sha256:fbe188b878313d01b7718390f31528be4010fed1faa798c5a1d0469c9c48c369\nblinker==1.9.0 \\\n    --hash=sha256:b4ce2265a7abece45e7cc896e98dbebe6cead56bcf805a3d23136d145f5445bf \\\n    --hash=sha256:ba0efaa9080b619ff2f3459d1d500c57bddea4a6b424b60a91141db6fd2f08bc\ncairocffi==1.7.1 \\\n    --hash=sha256:2e48ee864884ec4a3a34bfa8c9ab9999f688286eb714a15a43ec9d068c36557b \\\n    --hash=sha256:9803a0e11f6c962f3b0ae2ec8ba6ae45e957a146a004697a1ac1bbf16b073b3f\ncairosvg==2.7.1 \\\n    --hash=sha256:432531d72347291b9a9ebfb6777026b607563fd8719c46ee742db0aef7271ba0 \\\n    --hash=sha256:8a5222d4e6c3f86f1f7046b63246877a63b49923a1cd202184c3a634ef546b3b\ncertifi==2024.12.14 \\\n    --hash=sha256:1275f7a45be9464efc1173084eaa30f866fe2e47d389406136d332ed4967ec56 \\\n    --hash=sha256:b650d30f370c2b724812bee08008be0c4163b163ddaec3f2546c1caf65f191db\ncffi==1.17.1 \\\n    --hash=sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36 \\\n    --hash=sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824 \\\n    --hash=sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3 \\\n    --hash=sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8 \\\n    --hash=sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903 \\\n    --hash=sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c \\\n    --hash=sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4 \\\n    --hash=sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65 \\\n    --hash=sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93 \\\n    --hash=sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff \\\n    --hash=sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5 \\\n    --hash=sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99\ncfgv==3.4.0 \\\n    --hash=sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9 \\\n    --hash=sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560\ncharset-normalizer==3.4.1 \\\n    --hash=sha256:0f55e69f030f7163dffe9fd0752b32f070566451afe180f99dbeeb81f511ad8d \\\n    --hash=sha256:2369eea1ee4a7610a860d88f268eb39b95cb588acd7235e02fd5a5601773d4fa \\\n    --hash=sha256:44251f18cd68a75b56585dd00dae26183e102cd5e0f9f1466e6df5da2ed64ea3 \\\n    --hash=sha256:5df196eb874dae23dcfb968c83d4f8fdccb333330fe1fc278ac5ceeb101003a9 \\\n    --hash=sha256:6ff8a4a60c227ad87030d76e99cd1698345d4491638dfa6673027c48b3cd395f \\\n    --hash=sha256:73d94b58ec7fecbc7366247d3b0b10a21681004153238750bb67bd9012414545 \\\n    --hash=sha256:804a4d582ba6e5b747c625bf1255e6b1507465494a40a2130978bda7b932c90b \\\n    --hash=sha256:9b23ca7ef998bc739bf6ffc077c2116917eabcc901f88da1b9856b210ef63f35 \\\n    --hash=sha256:bc2722592d8998c870fa4e290c2eec2c1569b87fe58618e67d38b4665dfa680d \\\n    --hash=sha256:c30197aa96e8eed02200a83fba2657b4c3acd0f0aa4bdc9f6c1af8e8962e0757 \\\n    --hash=sha256:c4c3e6da02df6fa1410a7680bd3f63d4f710232d3139089536310d027950696a \\\n    --hash=sha256:d98b1668f06378c6dbefec3b92299716b931cd4e6061f3c875a71ced1780ab85 \\\n    --hash=sha256:dad3e487649f498dd991eeb901125411559b22e8d7ab25d3aeb1af367df5efd7 \\\n    --hash=sha256:e358e64305fe12299a08e08978f51fc21fac060dcfcddd95453eabe5b93ed0e1 \\\n    --hash=sha256:ffc9202a29ab3920fa812879e95a9e78b2465fd10be7fcbd042899695d75e616\nclick==8.1.8 \\\n    --hash=sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2 \\\n    --hash=sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a\ncolorama==0.4.6 \\\n    --hash=sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44 \\\n    --hash=sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6\ncoverage==7.6.10 \\\n    --hash=sha256:204a8238afe787323a8b47d8be4df89772d5c1e4651b9ffa808552bdf20e1d50 \\\n    --hash=sha256:27c6e64726b307782fa5cbe531e7647aee385a29b2107cd87ba7c0105a5d3853 \\\n    --hash=sha256:55b201b97286cf61f5e76063f9e2a1d8d2972fc2fcfd2c1272530172fd28c359 \\\n    --hash=sha256:714f942b9c15c3a7a5fe6876ce30af831c2ad4ce902410b7466b662358c852c0 \\\n    --hash=sha256:7fb105327c8f8f0682e29843e2ff96af9dcbe5bab8eeb4b398c6a33a16d80a23 \\\n    --hash=sha256:abb02e2f5a3187b2ac4cd46b8ced85a0858230b577ccb2c62c81482ca7d18852 \\\n    --hash=sha256:c56e097019e72c373bae32d946ecf9858fda841e48d82df7e81c63ac25554078 \\\n    --hash=sha256:c7827a5bc7bdb197b9e066cdf650b2887597ad124dd99777332776f7b7c7d0d0 \\\n    --hash=sha256:e4ae5ac5e0d1e4edfc9b4b57b4cbecd5bc266a6915c500f358817a8496739247 \\\n    --hash=sha256:e67926f51821b8e9deb6426ff3164870976fe414d033ad90ea75e7ed0c2e5022 \\\n    --hash=sha256:e78b270eadb5702938c3dbe9367f878249b5ef9a2fcc5360ac7bff694310d17b\ncoveralls==4.0.1 \\\n    --hash=sha256:7a6b1fa9848332c7b2221afb20f3df90272ac0167060f41b5fe90429b30b1809 \\\n    --hash=sha256:7b2a0a2bcef94f295e3cf28dcc55ca40b71c77d1c2446b538e85f0f7bc21aa69\ncryptography==44.0.0 \\\n    --hash=sha256:1923cb251c04be85eec9fda837661c67c1049063305d6be5721643c22dd4e2b7 \\\n    --hash=sha256:3c672a53c0fb4725a29c303be906d3c1fa99c32f58abe008a82705f9ee96f40b \\\n    --hash=sha256:404fdc66ee5f83a1388be54300ae978b2efd538018de18556dde92575e05defc \\\n    --hash=sha256:4ac4c9f37eba52cb6fbeaf5b59c152ea976726b865bd4cf87883a7e7006cc543 \\\n    --hash=sha256:660cb7312a08bc38be15b696462fa7cc7cd85c3ed9c576e81f4dc4d8b2b31591 \\\n    --hash=sha256:708ee5f1bafe76d041b53a4f95eb28cdeb8d18da17e597d46d7833ee59b97ede \\\n    --hash=sha256:761817a3377ef15ac23cd7834715081791d4ec77f9297ee694ca1ee9c2c7e5eb \\\n    --hash=sha256:831c3c4d0774e488fdc83a1923b49b9957d33287de923d58ebd3cec47a0ae43f \\\n    --hash=sha256:84111ad4ff3f6253820e6d3e58be2cc2a00adb29335d4cacb5ab4d4d34f2a123 \\\n    --hash=sha256:9e6fc8a08e116fb7c7dd1f040074c9d7b51d74a8ea40d4df2fc7aa08b76b9e6c \\\n    --hash=sha256:a01956ddfa0a6790d594f5b34fc1bfa6098aca434696a03cfdbe469b8ed79285 \\\n    --hash=sha256:abc998e0c0eee3c8a1904221d3f67dcfa76422b23620173e28c11d3e626c21bd \\\n    --hash=sha256:b15492a11f9e1b62ba9d73c210e2416724633167de94607ec6069ef724fad092 \\\n    --hash=sha256:c5eb858beed7835e5ad1faba59e865109f3e52b3783b9ac21e7e47dc5554e289 \\\n    --hash=sha256:cd4e834f340b4293430701e772ec543b0fbe6c2dea510a5286fe0acabe153a02 \\\n    --hash=sha256:d2436114e46b36d00f8b72ff57e598978b37399d2786fd39793c36c6d5cb1c64 \\\n    --hash=sha256:eb33480f1bad5b78233b0ad3e1b0be21e8ef1da745d8d2aecbb20671658b9053 \\\n    --hash=sha256:eca27345e1214d1b9f9490d200f9db5a874479be914199194e746c893788d417 \\\n    --hash=sha256:ed3534eb1090483c96178fcb0f8893719d96d5274dfde98aa6add34614e97c8e \\\n    --hash=sha256:f3f6fdfa89ee2d9d496e2c087cebef9d4fcbb0ad63c40e821b39f74bf48d9c5e \\\n    --hash=sha256:f53c2c87e0fb4b0c00fa9571082a057e37690a8f12233306161c8f4b819960b7\ncssselect2==0.8.0 \\\n    --hash=sha256:46fc70ebc41ced7a32cd42d58b1884d72ade23d21e5a4eaaf022401c13f0e76e \\\n    --hash=sha256:7674ffb954a3b46162392aee2a3a0aedb2e14ecf99fcc28644900f4e6e3e9d3a\ndefusedxml==0.7.1 \\\n    --hash=sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69 \\\n    --hash=sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61\ndistlib==0.3.9 \\\n    --hash=sha256:47f8c22fd27c27e25a65601af709b38e4f0a45ea4fc2e710f65755fa8caaaf87 \\\n    --hash=sha256:a60f20dea646b8a33f3e7772f74dc0b2d0772d2837ee1342a00645c81edf9403\ndnspython==2.7.0 \\\n    --hash=sha256:b4c34b7d10b51bcc3a5071e7b8dee77939f1e878477eeecc965e9835f63c6c86 \\\n    --hash=sha256:ce9c432eda0dc91cf618a5cedf1a4e142651196bbcd2c80e89ed5a907e5cfaf1\ndocopt==0.6.2 \\\n    --hash=sha256:49b3a825280bd66b3aa83585ef59c4a8c82f2c8a522dbe754a8bc8d08c85c491\nemail-validator==2.1.2 \\\n    --hash=sha256:14c0f3d343c4beda37400421b39fa411bbe33a75df20825df73ad53e06a9f04c \\\n    --hash=sha256:d89f6324e13b1e39889eab7f9ca2f91dc9aebb6fa50a6d8bd4329ab50f251115\nfastapi==0.115.6 \\\n    --hash=sha256:9ec46f7addc14ea472958a96aae5b5de65f39721a46aaf5705c480d9a8b76654 \\\n    --hash=sha256:e9240b29e36fa8f4bb7290316988e90c381e5092e0cbe84e7818cc3713bcf305\nfastapi-cli==0.0.7 \\\n    --hash=sha256:02b3b65956f526412515907a0793c9094abd4bfb5457b389f645b0ea6ba3605e \\\n    --hash=sha256:d549368ff584b2804336c61f192d86ddea080c11255f375959627911944804f4\nfastapi-mail==1.4.1 \\\n    --hash=sha256:9095b713bd9d3abb02fe6d7abb637502aaf680b52e177d60f96273ef6bc8bb70 \\\n    --hash=sha256:fa5ef23b2dea4d3ba4587f4bbb53f8f15274124998fb4e40629b3b636c76c398\nfastapi-users==13.0.0 \\\n    --hash=sha256:b397c815b7051c8fd4b560fbeee707acd28e00bd3e8f25c292ad158a1e47e884 \\\n    --hash=sha256:e6246529e3080a5b50e5afeed1e996663b661f1dc791a1ac478925cb5bfc0fa0\nfastapi-users-db-sqlalchemy==7.0.0 \\\n    --hash=sha256:5fceac018e7cfa69efc70834dd3035b3de7988eb4274154a0dbe8b14f5aa001e \\\n    --hash=sha256:6823eeedf8a92f819276a2b2210ef1dcfd71fe8b6e37f7b4da8d1c60e3dfd595\nfilelock==3.16.1 \\\n    --hash=sha256:2082e5703d51fbf98ea75855d9d5527e33d8ff23099bec374a134febee6946b0 \\\n    --hash=sha256:c249fbfcd5db47e5e2d6d62198e565475ee65e4831e2561c8e313fa7eb961435\nghp-import==2.1.0 \\\n    --hash=sha256:8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619 \\\n    --hash=sha256:9c535c4c61193c2df8871222567d7fd7e5014d835f97dc7b7439069e2413d343\ngreenlet==3.1.1 \\\n    --hash=sha256:1443279c19fca463fc33e65ef2a935a5b09bb90f978beab37729e1c3c6c25fe9 \\\n    --hash=sha256:23f20bb60ae298d7d8656c6ec6db134bca379ecefadb0b19ce6f19d1f232a942 \\\n    --hash=sha256:2846930c65b47d70b9d178e89c7e1a69c95c1f68ea5aa0a58646b7a96df12441 \\\n    --hash=sha256:4afe7ea89de619adc868e087b4d2359282058479d7cfb94970adf4b55284574d \\\n    --hash=sha256:4ce3ac6cdb6adf7946475d7ef31777c26d94bccc377e070a7986bd2d5c515467 \\\n    --hash=sha256:7124e16b4c55d417577c2077be379514321916d5790fa287c9ed6f23bd2ffd01 \\\n    --hash=sha256:99cfaa2110534e2cf3ba31a7abcac9d328d1d9f1b95beede58294a60348fba36 \\\n    --hash=sha256:b7cede291382a78f7bb5f04a529cb18e068dd29e0fb27376074b6d0317bf4dd0 \\\n    --hash=sha256:c3a701fe5a9695b238503ce5bbe8218e03c3bcccf7e204e455e7462d770268aa \\\n    --hash=sha256:f406b22b7c9a9b4f8aa9d2ab13d6ae0ac3e85c9a809bd590ad53fed2bf70dc79\nh11==0.14.0 \\\n    --hash=sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d \\\n    --hash=sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761\nhttpcore==1.0.7 \\\n    --hash=sha256:8551cb62a169ec7162ac7be8d4817d561f60e08eaa485234898414bb5a8a0b4c \\\n    --hash=sha256:a3fff8f43dc260d5bd363d9f9cf1830fa3a458b332856f34282de498ed420edd\nhttptools==0.6.4 \\\n    --hash=sha256:16e603a3bff50db08cd578d54f07032ca1631450ceb972c2f834c2b860c28ea2 \\\n    --hash=sha256:4e93eee4add6493b59a5c514da98c939b244fce4a0d8879cd3f466562f4b7d5c \\\n    --hash=sha256:69422b7f458c5af875922cdb5bd586cc1f1033295aa9ff63ee196a87519ac8e1 \\\n    --hash=sha256:85071a1e8c2d051b507161f6c3e26155b5c790e4e28d7f236422dbacc2a9cc44 \\\n    --hash=sha256:db78cb9ca56b59b016e64b6031eda5653be0589dba2b1b43453f6e8b405a0970 \\\n    --hash=sha256:df017d6c780287d5c80601dafa31f17bddb170232d85c066604d8558683711a2 \\\n    --hash=sha256:ec4f178901fa1834d4a060320d2f3abc5c9e39766953d038f1458cb885f47e81 \\\n    --hash=sha256:f9eb89ecf8b290f2e293325c646a211ff1c2493222798bb80a530c5e7502494f\nhttpx==0.28.1 \\\n    --hash=sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc \\\n    --hash=sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad\nidentify==2.6.5 \\\n    --hash=sha256:14181a47091eb75b337af4c23078c9d09225cd4c48929f521f3bf16b09d02566 \\\n    --hash=sha256:c10b33f250e5bba374fae86fb57f3adcebf1161bce7cdf92031915fd480c13bc\nidna==3.10 \\\n    --hash=sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9 \\\n    --hash=sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3\niniconfig==2.0.0 \\\n    --hash=sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3 \\\n    --hash=sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374\njinja2==3.1.5 \\\n    --hash=sha256:8fefff8dc3034e27bb80d67c671eb8a9bc424c0ef4c0826edbff304cceff43bb \\\n    --hash=sha256:aba0f4dc9ed8013c424088f68a5c226f7d6097ed89b246d7749c2ec4175c6adb\nmakefun==1.15.6 \\\n    --hash=sha256:26bc63442a6182fb75efed8b51741dd2d1db2f176bec8c64e20a586256b8f149 \\\n    --hash=sha256:e69b870f0bb60304765b1e3db576aaecf2f9b3e5105afe8cfeff8f2afe6ad067\nmako==1.3.8 \\\n    --hash=sha256:42f48953c7eb91332040ff567eb7eea69b22e7a4affbc5ba8e845e8f730f6627 \\\n    --hash=sha256:577b97e414580d3e088d47c2dbbe9594aa7a5146ed2875d4dfa9075af2dd3cc8\nmarkdown==3.7 \\\n    --hash=sha256:2ae2471477cfd02dbbf038d5d9bc226d40def84b4fe2986e49b59b6b472bbed2 \\\n    --hash=sha256:7eb6df5690b81a1d7942992c97fad2938e956e79df20cbc6186e9c3a77b1c803\nmarkdown-it-py==3.0.0 \\\n    --hash=sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1 \\\n    --hash=sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb\nmarkupsafe==3.0.2 \\\n    --hash=sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30 \\\n    --hash=sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028 \\\n    --hash=sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557 \\\n    --hash=sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22 \\\n    --hash=sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225 \\\n    --hash=sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c \\\n    --hash=sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87 \\\n    --hash=sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf \\\n    --hash=sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48 \\\n    --hash=sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8 \\\n    --hash=sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0\nmdurl==0.1.2 \\\n    --hash=sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8 \\\n    --hash=sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba\nmergedeep==1.3.4 \\\n    --hash=sha256:0096d52e9dad9939c3d975a774666af186eda617e6ca84df4c94dec30004f2a8 \\\n    --hash=sha256:70775750742b25c0d8f36c55aed03d24c3384d17c951b3175d898bd778ef0307\nmkdocs==1.6.1 \\\n    --hash=sha256:7b432f01d928c084353ab39c57282f29f92136665bdd6abf7c1ec8d822ef86f2 \\\n    --hash=sha256:db91759624d1647f3f34aa0c3f327dd2601beae39a366d6e064c03468d35c20e\nmkdocs-get-deps==0.2.0 \\\n    --hash=sha256:162b3d129c7fad9b19abfdcb9c1458a651628e4b1dea628ac68790fb3061c60c \\\n    --hash=sha256:2bf11d0b133e77a0dd036abeeb06dec8775e46efa526dc70667d8863eefc6134\nmkdocs-material==9.6.9 \\\n    --hash=sha256:6e61b7fb623ce2aa4622056592b155a9eea56ff3487d0835075360be45a4c8d1 \\\n    --hash=sha256:a4872139715a1f27b2aa3f3dc31a9794b7bbf36333c0ba4607cf04786c94f89c\nmkdocs-material-extensions==1.3.1 \\\n    --hash=sha256:10c9511cea88f568257f960358a467d12b970e1f7b2c0e5fb2bb48cab1928443 \\\n    --hash=sha256:adff8b62700b25cb77b53358dad940f3ef973dd6db797907c49e3c2ef3ab4e31\nmypy==1.14.1 \\\n    --hash=sha256:30ff5ef8519bbc2e18b3b54521ec319513a26f1bba19a7582e7b1f58a6e69f14 \\\n    --hash=sha256:553c293b1fbdebb6c3c4030589dab9fafb6dfa768995a453d8a5d3b23784af2e \\\n    --hash=sha256:7ec88144fe9b510e8475ec2f5f251992690fcf89ccb4500b214b4226abcd32d6 \\\n    --hash=sha256:8b4e3413e0bddea671012b063e27591b953d653209e7a4fa5e48759cda77ca11 \\\n    --hash=sha256:8fa2220e54d2946e94ab6dbb3ba0a992795bd68b16dc852db33028df2b00191b \\\n    --hash=sha256:b66a60cc4073aeb8ae00057f9c1f64d49e90f918fbcef9a977eb121da8b8f1d1 \\\n    --hash=sha256:cb9f255c18052343c70234907e2e532bc7e55a62565d64536dbc7706a20b78b9 \\\n    --hash=sha256:fad79bfe3b65fe6a1efaed97b445c3d37f7be9fdc348bdb2d7cac75579607c89\nmypy-extensions==1.0.0 \\\n    --hash=sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d \\\n    --hash=sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782\nnodeenv==1.9.1 \\\n    --hash=sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f \\\n    --hash=sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9\npackaging==24.2 \\\n    --hash=sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759 \\\n    --hash=sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f\npaginate==0.5.7 \\\n    --hash=sha256:22bd083ab41e1a8b4f3690544afb2c60c25e5c9a63a30fa2f483f6c60c8e5945 \\\n    --hash=sha256:b885e2af73abcf01d9559fd5216b57ef722f8c42affbb63942377668e35c7591\npathspec==0.12.1 \\\n    --hash=sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08 \\\n    --hash=sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712\npillow==10.4.0 \\\n    --hash=sha256:166c1cd4d24309b30d61f79f4a9114b7b2313d7450912277855ff5dfd7cd4a06 \\\n    --hash=sha256:1d846aea995ad352d4bdcc847535bd56e0fd88d36829d2c90be880ef1ee4668a \\\n    --hash=sha256:29dbdc4207642ea6aad70fbde1a9338753d33fb23ed6956e706936706f52dd80 \\\n    --hash=sha256:37fb69d905be665f68f28a8bba3c6d3223c8efe1edf14cc4cfa06c241f8c81d9 \\\n    --hash=sha256:673655af3eadf4df6b5457033f086e90299fdd7a47983a13827acf7459c15d94 \\\n    --hash=sha256:780c072c2e11c9b2c7ca37f9a2ee8ba66f44367ac3e5c7832afcfe5104fd6d1b \\\n    --hash=sha256:7dfecdbad5c301d7b5bde160150b4db4c659cee2b69589705b6f8a0c509d9f42 \\\n    --hash=sha256:866b6942a92f56300012f5fbac71f2d610312ee65e22f1aa2609e491284e5597 \\\n    --hash=sha256:86dcb5a1eb778d8b25659d5e4341269e8590ad6b4e8b44d9f4b07f8d136c414a \\\n    --hash=sha256:bf2342ac639c4cf38799a44950bbc2dfcb685f052b9e262f446482afaf4bffca \\\n    --hash=sha256:e553cad5179a66ba15bb18b353a19020e73a7921296a7979c4a2b7f6a5cd57f9 \\\n    --hash=sha256:f5b92f4d70791b4a67157321c4e8225d60b119c5cc9aee8ecf153aace4aad4ef\nplatformdirs==4.3.6 \\\n    --hash=sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907 \\\n    --hash=sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb\npluggy==1.5.0 \\\n    --hash=sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1 \\\n    --hash=sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669\npre-commit==3.8.0 \\\n    --hash=sha256:8bb6494d4a20423842e198980c9ecf9f96607a07ea29549e180eef9ae80fe7af \\\n    --hash=sha256:9a90a53bf82fdd8778d58085faf8d83df56e40dfe18f45b19446e26bf1b3a63f\npwdlib==0.2.0 \\\n    --hash=sha256:b1bdafc064310eb6d3d07144a210267063ab4f45ac73a97be948e6589f74e861 \\\n    --hash=sha256:be53812012ab66795a57ac9393a59716ae7c2b60841ed453eb1262017fdec144\npycparser==2.22 \\\n    --hash=sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6 \\\n    --hash=sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc\npydantic==2.10.4 \\\n    --hash=sha256:597e135ea68be3a37552fb524bc7d0d66dcf93d395acd93a00682f1efcb8ee3d \\\n    --hash=sha256:82f12e9723da6de4fe2ba888b5971157b3be7ad914267dea8f05f82b28254f06\npydantic-core==2.27.2 \\\n    --hash=sha256:05e3a55d124407fffba0dd6b0c0cd056d10e983ceb4e5dbd10dda135c31071d6 \\\n    --hash=sha256:172fce187655fece0c90d90a678424b013f8fbb0ca8b036ac266749c09438cb7 \\\n    --hash=sha256:1e2cb691ed9834cd6a8be61228471d0a503731abfb42f82458ff27be7b2186fc \\\n    --hash=sha256:220f892729375e2d736b97d0e51466252ad84c51857d4d15f5e9692f9ef12be4 \\\n    --hash=sha256:28ccb213807e037460326424ceb8b5245acb88f32f3d2777427476e1b32c48c4 \\\n    --hash=sha256:3911ac9284cd8a1792d3cb26a2da18f3ca26c6908cc434a18f730dc0db7bfa3b \\\n    --hash=sha256:519f29f5213271eeeeb3093f662ba2fd512b91c5f188f3bb7b27bc5973816934 \\\n    --hash=sha256:6fb4aadc0b9a0c063206846d603b92030eb6f03069151a625667f982887153e2 \\\n    --hash=sha256:83097677b8e3bd7eaa6775720ec8e0405f1575015a463285a92bfdfe254529ef \\\n    --hash=sha256:9c3ed807c7b91de05e63930188f19e921d1fe90de6b4f5cd43ee7fcc3525cb8c \\\n    --hash=sha256:9e0c8cfefa0ef83b4da9588448b6d8d2a2bf1a53c3f1ae5fca39eb3061e2f0b0 \\\n    --hash=sha256:a0fcd29cd6b4e74fe8ddd2c90330fd8edf2e30cb52acda47f06dd615ae72da57 \\\n    --hash=sha256:cc3f1a99a4f4f9dd1de4fe0312c114e740b5ddead65bb4102884b384c15d8bc9 \\\n    --hash=sha256:de3cd1899e2c279b140adde9357c4495ed9d47131b4a4eaff9052f23398076b3 \\\n    --hash=sha256:eb026e5a4c1fee05726072337ff51d1efb6f59090b7da90d30ea58625b1ffb39\npydantic-settings==2.7.1 \\\n    --hash=sha256:10c9caad35e64bfb3c2fbf70a078c0e25cc92499782e5200747f942a065dec93 \\\n    --hash=sha256:590be9e6e24d06db33a4262829edef682500ef008565a969c73d39d5f8bfb3fd\npygments==2.19.0 \\\n    --hash=sha256:4755e6e64d22161d5b61432c0600c923c5927214e7c956e31c23923c89251a9b \\\n    --hash=sha256:afc4146269910d4bdfabcd27c24923137a74d562a23a320a41a55ad303e19783\npyjwt==2.8.0 \\\n    --hash=sha256:57e28d156e3d5c10088e0c68abb90bfac3df82b40a71bd0daa20c65ccd5c23de \\\n    --hash=sha256:59127c392cc44c2da5bb3192169a91f429924e17aff6534d70fdc02ab3e04320\npymdown-extensions==10.14.3 \\\n    --hash=sha256:05e0bee73d64b9c71a4ae17c72abc2f700e8bc8403755a00580b49a4e9f189e9 \\\n    --hash=sha256:41e576ce3f5d650be59e900e4ceff231e0aed2a88cf30acaee41e02f063a061b\npytest==8.3.4 \\\n    --hash=sha256:50e16d954148559c9a74109af1eaf0c945ba2d8f30f0a3d3335edde19788b6f6 \\\n    --hash=sha256:965370d062bce11e73868e0335abac31b4d3de0e82f4007408d242b4f8610761\npytest-asyncio==0.24.0 \\\n    --hash=sha256:a811296ed596b69bf0b6f3dc40f83bcaf341b155a269052d82efa2b25ac7037b \\\n    --hash=sha256:d081d828e576d85f875399194281e92bf8a68d60d72d1a2faf2feddb6c46b276\npytest-mock==3.14.0 \\\n    --hash=sha256:0b72c38033392a5f4621342fe11e9219ac11ec9d375f8e2a0c164539e0d70f6f \\\n    --hash=sha256:2719255a1efeceadbc056d6bf3df3d1c5015530fb40cf347c0f9afac88410bd0\npython-dateutil==2.9.0.post0 \\\n    --hash=sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3 \\\n    --hash=sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427\npython-dotenv==1.0.1 \\\n    --hash=sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca \\\n    --hash=sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a\npython-multipart==0.0.9 \\\n    --hash=sha256:03f54688c663f1b7977105f021043b0793151e4cb1c1a9d4a11fc13d622c4026 \\\n    --hash=sha256:97ca7b8ea7b05f977dc3849c3ba99d51689822fab725c3703af7c866a0c2b215\npyyaml==6.0.2 \\\n    --hash=sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48 \\\n    --hash=sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5 \\\n    --hash=sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8 \\\n    --hash=sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476 \\\n    --hash=sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b \\\n    --hash=sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425 \\\n    --hash=sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab \\\n    --hash=sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725 \\\n    --hash=sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e \\\n    --hash=sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4\npyyaml-env-tag==0.1 \\\n    --hash=sha256:70092675bda14fdec33b31ba77e7543de9ddc88f2e5b99160396572d11525bdb \\\n    --hash=sha256:af31106dec8a4d68c60207c1886031cbf839b68aa7abccdb19868200532c2069\nrequests==2.32.3 \\\n    --hash=sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760 \\\n    --hash=sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6\nrich==13.9.4 \\\n    --hash=sha256:439594978a49a09530cff7ebc4b5c7103ef57baf48d5ea3184f21d9a2befa098 \\\n    --hash=sha256:6049d5e6ec054bf2779ab3358186963bac2ea89175919d699e378b99738c2a90\nrich-toolkit==0.12.0 \\\n    --hash=sha256:a2da4416384410ae871e890db7edf8623e1f5e983341dbbc8cc03603ce24f0ab \\\n    --hash=sha256:facb0b40418010309f77abd44e2583b4936656f6ee5c8625da807564806a6c40\nruff==0.1.15 \\\n    --hash=sha256:1bab866aafb53da39c2cadfb8e1c4550ac5340bb40300083eb8967ba25481447 \\\n    --hash=sha256:2417e1cb6e2068389b07e6fa74c306b2810fe3ee3476d5b8a96616633f40d14f \\\n    --hash=sha256:3837ac73d869efc4182d9036b1405ef4c73d9b1f88da2413875e34e0d6919587 \\\n    --hash=sha256:5fe8d54df166ecc24106db7dd6a68d44852d14eb0729ea4672bb4d96c320b7df \\\n    --hash=sha256:6c629cf64bacfd136c07c78ac10a54578ec9d1bd2a9d395efbee0935868bf852 \\\n    --hash=sha256:6f0bfbb53c4b4de117ac4d6ddfd33aa5fc31beeaa21d23c45c6dd249faf9126f \\\n    --hash=sha256:6f8ad828f01e8dd32cc58bc28375150171d198491fc901f6f98d2a39ba8e3ff5 \\\n    --hash=sha256:86811954eec63e9ea162af0ffa9f8d09088bab51b7438e8b6488b9401863c25e \\\n    --hash=sha256:9405fa9ac0e97f35aaddf185a1be194a589424b8713e3b97b762336ec79ff807 \\\n    --hash=sha256:9a933dfb1c14ec7a33cceb1e49ec4a16b51ce3c20fd42663198746efc0427360 \\\n    --hash=sha256:abf4822129ed3a5ce54383d5f0e964e7fef74a41e48eb1dfad404151efc130a2 \\\n    --hash=sha256:b17b93c02cdb6aeb696effecea1095ac93f3884a49a554a9afa76bb125c114c1 \\\n    --hash=sha256:c66ec24fe36841636e814b8f90f572a8c0cb0e54d8b5c2d0e300d28a0d7bffec \\\n    --hash=sha256:ddb87643be40f034e97e97f5bc2ef7ce39de20e34608f3f829db727a93fb82c5 \\\n    --hash=sha256:e0d432aec35bfc0d800d4f70eba26e23a352386be3a6cf157083d18f6f5881c8 \\\n    --hash=sha256:f6dfa8c1b21c913c326919056c390966648b680966febcb796cc9d1aaab8564e \\\n    --hash=sha256:fd4025ac5e87d9b80e1f300207eb2fd099ff8200fa2320d7dc066a3f4622dc6b\nshellingham==1.5.4 \\\n    --hash=sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686 \\\n    --hash=sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de\nsix==1.17.0 \\\n    --hash=sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274 \\\n    --hash=sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81\nsniffio==1.3.1 \\\n    --hash=sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2 \\\n    --hash=sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc\nsqlalchemy==2.0.36 \\\n    --hash=sha256:1bc330d9d29c7f06f003ab10e1eaced295e87940405afe1b110f2eb93a233588 \\\n    --hash=sha256:46331b00096a6db1fdc052d55b101dbbfc99155a548e20a0e4a8e5e4d1362855 \\\n    --hash=sha256:79d2e78abc26d871875b419e1fd3c0bca31a1cb0043277d0d850014599626c2e \\\n    --hash=sha256:7f2767680b6d2398aea7082e45a774b2b0767b5c8d8ffb9c8b683088ea9b29c5 \\\n    --hash=sha256:90812a8933df713fdf748b355527e3af257a11e415b613dd794512461eb8a686 \\\n    --hash=sha256:ac9dfa18ff2a67b09b372d5db8743c27966abf0e5344c555d86cc7199f7ad83a \\\n    --hash=sha256:b544ad1935a8541d177cb402948b94e871067656b3a0b9e91dbec136b06a2ff5 \\\n    --hash=sha256:f7b64e6ec3f02c35647be6b4851008b26cff592a95ecb13b6788a54ef80bbdd4 \\\n    --hash=sha256:fddbe92b4760c6f5d48162aef14824add991aeda8ddadb3c31d56eb15ca69f8e \\\n    --hash=sha256:fdf3386a801ea5aba17c6410dd1dc8d39cf454ca2565541b5ac42a84e1e28f53\nstarlette==0.41.3 \\\n    --hash=sha256:0e4ab3d16522a255be6b28260b938eae2482f98ce5cc934cb08dce8dc3ba5835 \\\n    --hash=sha256:44cedb2b7c77a9de33a8b74b2b90e9f50d11fcf25d8270ea525ad71a25374ff7\ntinycss2==1.4.0 \\\n    --hash=sha256:10c0972f6fc0fbee87c3edb76549357415e94548c1ae10ebccdea16fb404a9b7 \\\n    --hash=sha256:3a49cf47b7675da0b15d0c6e1df8df4ebd96e9394bb905a5775adb0d884c5289\ntyper==0.15.1 \\\n    --hash=sha256:7994fb7b8155b64d3402518560648446072864beefd44aa2dc36972a5972e847 \\\n    --hash=sha256:a0588c0a7fa68a1978a069818657778f86abe6ff5ea6abf472f940a08bfe4f0a\ntyping-extensions==4.12.2 \\\n    --hash=sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d \\\n    --hash=sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8\nurllib3==2.3.0 \\\n    --hash=sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df \\\n    --hash=sha256:f8c5449b3cf0861679ce7e0503c7b44b5ec981bec0d1d3795a07f1ba96f0204d\nuvicorn==0.34.0 \\\n    --hash=sha256:023dc038422502fa28a09c7a30bf2b6991512da7dcdb8fd35fe57cfc154126f4 \\\n    --hash=sha256:404051050cd7e905de2c9a7e61790943440b3416f49cb409f965d9dcd0fa73e9\nuvloop==0.21.0 ; platform_python_implementation != 'PyPy' and sys_platform != 'cygwin' and sys_platform != 'win32' \\\n    --hash=sha256:183aef7c8730e54c9a3ee3227464daed66e37ba13040bb3f350bc2ddc040f22f \\\n    --hash=sha256:359ec2c888397b9e592a889c4d72ba3d6befba8b2bb01743f72fffbde663b59c \\\n    --hash=sha256:3bf12b0fda68447806a7ad847bfa591613177275d35b6724b1ee573faa3704e3 \\\n    --hash=sha256:461d9ae6660fbbafedd07559c6a2e57cd553b34b0065b6550685f6653a98c1cb \\\n    --hash=sha256:86975dca1c773a2c9864f4c52c5a55631038e387b47eaf56210f873887b6c8dc \\\n    --hash=sha256:baa4dcdbd9ae0a372f2167a207cd98c9f9a1ea1188a8a526431eef2f8116cc8d \\\n    --hash=sha256:f7089d2dc73179ce5ac255bdf37c236a9f914b264825fdaacaded6990a7fb4c2\nvirtualenv==20.28.1 \\\n    --hash=sha256:412773c85d4dab0409b83ec36f7a6499e72eaf08c80e81e9576bca61831c71cb \\\n    --hash=sha256:5d34ab240fdb5d21549b76f9e8ff3af28252f5499fb6d6f031adac4e5a8c5329\nwatchdog==5.0.3 \\\n    --hash=sha256:0f9332243355643d567697c3e3fa07330a1d1abf981611654a1f2bf2175612b7 \\\n    --hash=sha256:108f42a7f0345042a854d4d0ad0834b741d421330d5f575b81cb27b883500176 \\\n    --hash=sha256:1e9679245e3ea6498494b3028b90c7b25dbb2abe65c7d07423ecfc2d6218ff7c \\\n    --hash=sha256:26dd201857d702bdf9d78c273cafcab5871dd29343748524695cecffa44a8d97 \\\n    --hash=sha256:294b7a598974b8e2c6123d19ef15de9abcd282b0fbbdbc4d23dfa812959a9e05 \\\n    --hash=sha256:349c9488e1d85d0a58e8cb14222d2c51cbc801ce11ac3936ab4c3af986536926 \\\n    --hash=sha256:49f4d36cb315c25ea0d946e018c01bb028048023b9e103d3d3943f58e109dd45 \\\n    --hash=sha256:53a3f10b62c2d569e260f96e8d966463dec1a50fa4f1b22aec69e3f91025060e \\\n    --hash=sha256:78864cc8f23dbee55be34cc1494632a7ba30263951b5b2e8fc8286b95845f82c \\\n    --hash=sha256:9413384f26b5d050b6978e6fcd0c1e7f0539be7a4f1a885061473c5deaa57221 \\\n    --hash=sha256:94d11b07c64f63f49876e0ab8042ae034674c8653bfcdaa8c4b32e71cfff87e8 \\\n    --hash=sha256:c66f80ee5b602a9c7ab66e3c9f36026590a0902db3aea414d59a2f55188c1f49 \\\n    --hash=sha256:dd021efa85970bd4824acacbb922066159d0f9e546389a4743d56919b6758b91 \\\n    --hash=sha256:f00b4cf737f568be9665563347a910f8bdc76f88c2970121c86243c8cfdf90e9\nwatchfiles==1.0.3 \\\n    --hash=sha256:0d1ec043f02ca04bf21b1b32cab155ce90c651aaf5540db8eb8ad7f7e645cba8 \\\n    --hash=sha256:1df924ba82ae9e77340101c28d56cbaff2c991bd6fe8444a545d24075abb0a87 \\\n    --hash=sha256:48681c86f2cb08348631fed788a116c89c787fdf1e6381c5febafd782f6c3b44 \\\n    --hash=sha256:49bc1bc26abf4f32e132652f4b3bfeec77d8f8f62f57652703ef127e85a3e38d \\\n    --hash=sha256:632a52dcaee44792d0965c17bdfe5dc0edad5b86d6a29e53d6ad4bf92dc0ff49 \\\n    --hash=sha256:65ab1fb635476f6170b07e8e21db0424de94877e4b76b7feabfe11f9a5fc12b5 \\\n    --hash=sha256:6a5bc3ca468bb58a2ef50441f953e1f77b9a61bd1b8c347c8223403dc9b4ac9a \\\n    --hash=sha256:80bf4b459d94a0387617a1b499f314aa04d8a64b7a0747d15d425b8c8b151da0 \\\n    --hash=sha256:93436ed550e429da007fbafb723e0769f25bae178fbb287a94cb4ccdf42d3af3 \\\n    --hash=sha256:9e080cf917b35b20c889225a13f290f2716748362f6071b859b60b8847a6aa43 \\\n    --hash=sha256:c18f3502ad0737813c7dad70e3e1cc966cc147fbaeef47a09463bbffe70b0a00 \\\n    --hash=sha256:ca94c85911601b097d53caeeec30201736ad69a93f30d15672b967558df02885 \\\n    --hash=sha256:f3ff7da165c99a5412fe5dd2304dd2dbaaaa5da718aad942dcb3a178eaa70c56 \\\n    --hash=sha256:f58d3bfafecf3d81c15d99fc0ecf4319e80ac712c77cf0ce2661c8cf8bf84066\nwebencodings==0.5.1 \\\n    --hash=sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78 \\\n    --hash=sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923\nwebsockets==14.1 \\\n    --hash=sha256:1d045cbe1358d76b24d5e20e7b1878efe578d9897a25c24e6006eef788c0fdf0 \\\n    --hash=sha256:398b10c77d471c0aab20a845e7a60076b6390bfdaac7a6d2edb0d2c59d75e8d8 \\\n    --hash=sha256:4b6caec8576e760f2c7dd878ba817653144d5f369200b6ddf9771d64385b84d4 \\\n    --hash=sha256:4d4fc827a20abe6d544a119896f6b78ee13fe81cbfef416f3f2ddf09a03f0e2e \\\n    --hash=sha256:6a6c9bcf7cdc0fd41cc7b7944447982e8acfd9f0d560ea6d6845428ed0562058 \\\n    --hash=sha256:87e31011b5c14a33b29f17eb48932e63e1dcd3fa31d72209848652310d3d1f0d \\\n    --hash=sha256:90f4c7a069c733d95c308380aae314f2cb45bd8a904fb03eb36d1a4983a4993f \\\n    --hash=sha256:9777564c0a72a1d457f0848977a1cbe15cfa75fa2f67ce267441e465717dcf1a \\\n    --hash=sha256:a3dfff83ca578cada2d19e665e9c8368e1598d4e787422a460ec70e531dbdd58 \\\n    --hash=sha256:a655bde548ca98f55b43711b0ceefd2a88a71af6350b0c168aa77562104f3f45 \\\n    --hash=sha256:bc6ccf7d54c02ae47a48ddf9414c54d48af9c01076a2e1023e3b486b6e72c707 \\\n    --hash=sha256:eb6d38971c800ff02e4a6afd791bbe3b923a9a57ca9aeab7314c21c84bf9ff05 \\\n    --hash=sha256:ed907449fe5e021933e46a3e65d651f641975a768d0649fee59f10c2985529ed\nfastapi-pagination==0.13.3"
  },
  {
    "path": "fastapi_backend/start.sh",
    "content": "#!/bin/bash\n\nif [ -f /.dockerenv ]; then\n    echo \"Running in Docker\"\n    fastapi dev app/main.py --host 0.0.0.0 --port 8000 --reload &\n    python watcher.py\nelse\n    echo \"Running locally with uv\"\n    uv run fastapi dev app/main.py --host 0.0.0.0 --port 8000 --reload &\n    uv run python watcher.py\nfi\n\nwait\n"
  },
  {
    "path": "fastapi_backend/tests/__init__.py",
    "content": ""
  },
  {
    "path": "fastapi_backend/tests/commands/__init__.py",
    "content": ""
  },
  {
    "path": "fastapi_backend/tests/commands/files/openapi_test.json",
    "content": "{\n  \"openapi\": \"3.1.0\",\n  \"info\": {\n    \"title\": \"FastAPI\",\n    \"version\": \"0.1.0\"\n  },\n  \"paths\": {\n    \"/auth/jwt/login\": {\n      \"post\": {\n        \"tags\": [\n          \"auth\"\n        ],\n        \"summary\": \"Auth:Jwt.Login\",\n        \"operationId\": \"auth-auth:jwt.login_post\",\n        \"requestBody\": {\n          \"content\": {\n            \"application/x-www-form-urlencoded\": {\n              \"schema\": {\n                \"$ref\": \"#/components/schemas/login_post\"\n              }\n            }\n          },\n          \"required\": true\n        },\n        \"responses\": {\n          \"200\": {\n            \"description\": \"Successful Response\",\n            \"content\": {\n              \"application/json\": {\n                \"schema\": {\n                  \"$ref\": \"#/components/schemas/BearerResponse\"\n                },\n                \"example\": {\n                  \"access_token\": \"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiOTIyMWZmYzktNjQwZi00MzcyLTg2ZDMtY2U2NDJjYmE1NjAzIiwiYXVkIjoiZmFzdGFwaS11c2VyczphdXRoIiwiZXhwIjoxNTcxNTA0MTkzfQ.M10bjOe45I5Ncu_uXvOmVV8QxnL-nZfcH96U90JaocI\",\n                  \"token_type\": \"bearer\"\n                }\n              }\n            }\n          },\n          \"400\": {\n            \"description\": \"Bad Request\",\n            \"content\": {\n              \"application/json\": {\n                \"schema\": {\n                  \"$ref\": \"#/components/schemas/ErrorModel\"\n                },\n                \"examples\": {\n                  \"LOGIN_BAD_CREDENTIALS\": {\n                    \"summary\": \"Bad credentials or the user is inactive.\",\n                    \"value\": {\n                      \"detail\": \"LOGIN_BAD_CREDENTIALS\"\n                    }\n                  },\n                  \"LOGIN_USER_NOT_VERIFIED\": {\n                    \"summary\": \"The user is not verified.\",\n                    \"value\": {\n                      \"detail\": \"LOGIN_USER_NOT_VERIFIED\"\n                    }\n                  }\n                }\n              }\n            }\n          },\n          \"422\": {\n            \"description\": \"Validation Error\",\n            \"content\": {\n              \"application/json\": {\n                \"schema\": {\n                  \"$ref\": \"#/components/schemas/HTTPValidationError\"\n                }\n              }\n            }\n          }\n        }\n      }\n    },\n    \"/auth/jwt/logout\": {\n      \"post\": {\n        \"tags\": [\n          \"auth\"\n        ],\n        \"summary\": \"Auth:Jwt.Logout\",\n        \"operationId\": \"auth-auth:jwt.logout_post\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"Successful Response\",\n            \"content\": {\n              \"application/json\": {\n                \"schema\": {}\n              }\n            }\n          },\n          \"401\": {\n            \"description\": \"Missing token or inactive user.\"\n          }\n        },\n        \"security\": [\n          {\n            \"OAuth2PasswordBearer\": []\n          }\n        ]\n      }\n    },\n    \"/auth/register\": {\n      \"post\": {\n        \"tags\": [\n          \"auth\"\n        ],\n        \"summary\": \"Register:Register\",\n        \"operationId\": \"auth-register:register_post\",\n        \"requestBody\": {\n          \"content\": {\n            \"application/json\": {\n              \"schema\": {\n                \"$ref\": \"#/components/schemas/UserCreate\"\n              }\n            }\n          },\n          \"required\": true\n        },\n        \"responses\": {\n          \"201\": {\n            \"description\": \"Successful Response\",\n            \"content\": {\n              \"application/json\": {\n                \"schema\": {\n                  \"$ref\": \"#/components/schemas/UserRead\"\n                }\n              }\n            }\n          },\n          \"400\": {\n            \"description\": \"Bad Request\",\n            \"content\": {\n              \"application/json\": {\n                \"schema\": {\n                  \"$ref\": \"#/components/schemas/ErrorModel\"\n                },\n                \"examples\": {\n                  \"REGISTER_USER_ALREADY_EXISTS\": {\n                    \"summary\": \"A user with this email already exists.\",\n                    \"value\": {\n                      \"detail\": \"REGISTER_USER_ALREADY_EXISTS\"\n                    }\n                  },\n                  \"REGISTER_INVALID_PASSWORD\": {\n                    \"summary\": \"Password validation failed.\",\n                    \"value\": {\n                      \"detail\": {\n                        \"code\": \"REGISTER_INVALID_PASSWORD\",\n                        \"reason\": \"Password should beat least 3 characters\"\n                      }\n                    }\n                  }\n                }\n              }\n            }\n          },\n          \"422\": {\n            \"description\": \"Validation Error\",\n            \"content\": {\n              \"application/json\": {\n                \"schema\": {\n                  \"$ref\": \"#/components/schemas/HTTPValidationError\"\n                }\n              }\n            }\n          }\n        }\n      }\n    },\n    \"/auth/forgot-password\": {\n      \"post\": {\n        \"tags\": [\n          \"auth\"\n        ],\n        \"summary\": \"Reset:Forgot Password\",\n        \"operationId\": \"auth-reset:forgot_password_post\",\n        \"requestBody\": {\n          \"content\": {\n            \"application/json\": {\n              \"schema\": {\n                \"$ref\": \"#/components/schemas/Body_auth-reset_forgot_password_post\"\n              }\n            }\n          },\n          \"required\": true\n        },\n        \"responses\": {\n          \"202\": {\n            \"description\": \"Successful Response\",\n            \"content\": {\n              \"application/json\": {\n                \"schema\": {}\n              }\n            }\n          },\n          \"422\": {\n            \"description\": \"Validation Error\",\n            \"content\": {\n              \"application/json\": {\n                \"schema\": {\n                  \"$ref\": \"#/components/schemas/HTTPValidationError\"\n                }\n              }\n            }\n          }\n        }\n      }\n    },\n    \"/auth/reset-password\": {\n      \"post\": {\n        \"tags\": [\n          \"auth\"\n        ],\n        \"summary\": \"Reset:Reset Password\",\n        \"operationId\": \"auth-reset:reset_password_post\",\n        \"requestBody\": {\n          \"content\": {\n            \"application/json\": {\n              \"schema\": {\n                \"$ref\": \"#/components/schemas/Body_auth-reset_reset_password_post\"\n              }\n            }\n          },\n          \"required\": true\n        },\n        \"responses\": {\n          \"200\": {\n            \"description\": \"Successful Response\",\n            \"content\": {\n              \"application/json\": {\n                \"schema\": {}\n              }\n            }\n          },\n          \"400\": {\n            \"description\": \"Bad Request\",\n            \"content\": {\n              \"application/json\": {\n                \"schema\": {\n                  \"$ref\": \"#/components/schemas/ErrorModel\"\n                },\n                \"examples\": {\n                  \"RESET_PASSWORD_BAD_TOKEN\": {\n                    \"summary\": \"Bad or expired token.\",\n                    \"value\": {\n                      \"detail\": \"RESET_PASSWORD_BAD_TOKEN\"\n                    }\n                  },\n                  \"RESET_PASSWORD_INVALID_PASSWORD\": {\n                    \"summary\": \"Password validation failed.\",\n                    \"value\": {\n                      \"detail\": {\n                        \"code\": \"RESET_PASSWORD_INVALID_PASSWORD\",\n                        \"reason\": \"Password should be at least 3 characters\"\n                      }\n                    }\n                  }\n                }\n              }\n            }\n          },\n          \"422\": {\n            \"description\": \"Validation Error\",\n            \"content\": {\n              \"application/json\": {\n                \"schema\": {\n                  \"$ref\": \"#/components/schemas/HTTPValidationError\"\n                }\n              }\n            }\n          }\n        }\n      }\n    },\n    \"/auth/request-verify-token\": {\n      \"post\": {\n        \"tags\": [\n          \"auth\"\n        ],\n        \"summary\": \"Verify:Request-Token\",\n        \"operationId\": \"auth-verify:request-token_post\",\n        \"requestBody\": {\n          \"content\": {\n            \"application/json\": {\n              \"schema\": {\n                \"$ref\": \"#/components/schemas/Body_auth-verify_request-token_post\"\n              }\n            }\n          },\n          \"required\": true\n        },\n        \"responses\": {\n          \"202\": {\n            \"description\": \"Successful Response\",\n            \"content\": {\n              \"application/json\": {\n                \"schema\": {}\n              }\n            }\n          },\n          \"422\": {\n            \"description\": \"Validation Error\",\n            \"content\": {\n              \"application/json\": {\n                \"schema\": {\n                  \"$ref\": \"#/components/schemas/HTTPValidationError\"\n                }\n              }\n            }\n          }\n        }\n      }\n    },\n    \"/auth/verify\": {\n      \"post\": {\n        \"tags\": [\n          \"auth\"\n        ],\n        \"summary\": \"Verify:Verify\",\n        \"operationId\": \"auth-verify:verify_post\",\n        \"requestBody\": {\n          \"content\": {\n            \"application/json\": {\n              \"schema\": {\n                \"$ref\": \"#/components/schemas/Body_auth-verify_verify_post\"\n              }\n            }\n          },\n          \"required\": true\n        },\n        \"responses\": {\n          \"200\": {\n            \"description\": \"Successful Response\",\n            \"content\": {\n              \"application/json\": {\n                \"schema\": {\n                  \"$ref\": \"#/components/schemas/UserRead\"\n                }\n              }\n            }\n          },\n          \"400\": {\n            \"description\": \"Bad Request\",\n            \"content\": {\n              \"application/json\": {\n                \"schema\": {\n                  \"$ref\": \"#/components/schemas/ErrorModel\"\n                },\n                \"examples\": {\n                  \"VERIFY_USER_BAD_TOKEN\": {\n                    \"summary\": \"Bad token, not existing user ornot the e-mail currently set for the user.\",\n                    \"value\": {\n                      \"detail\": \"VERIFY_USER_BAD_TOKEN\"\n                    }\n                  },\n                  \"VERIFY_USER_ALREADY_VERIFIED\": {\n                    \"summary\": \"The user is already verified.\",\n                    \"value\": {\n                      \"detail\": \"VERIFY_USER_ALREADY_VERIFIED\"\n                    }\n                  }\n                }\n              }\n            }\n          },\n          \"422\": {\n            \"description\": \"Validation Error\",\n            \"content\": {\n              \"application/json\": {\n                \"schema\": {\n                  \"$ref\": \"#/components/schemas/HTTPValidationError\"\n                }\n              }\n            }\n          }\n        }\n      }\n    },\n    \"/users/me\": {\n      \"get\": {\n        \"tags\": [\n          \"users\"\n        ],\n        \"summary\": \"Users:Current User\",\n        \"operationId\": \"users-users:current_user_get\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"Successful Response\",\n            \"content\": {\n              \"application/json\": {\n                \"schema\": {\n                  \"$ref\": \"#/components/schemas/UserRead\"\n                }\n              }\n            }\n          },\n          \"401\": {\n            \"description\": \"Missing token or inactive user.\"\n          }\n        },\n        \"security\": [\n          {\n            \"OAuth2PasswordBearer\": []\n          }\n        ]\n      },\n      \"patch\": {\n        \"tags\": [\n          \"users\"\n        ],\n        \"summary\": \"Users:Patch Current User\",\n        \"operationId\": \"users-users:patch_current_user_patch\",\n        \"requestBody\": {\n          \"content\": {\n            \"application/json\": {\n              \"schema\": {\n                \"$ref\": \"#/components/schemas/UserUpdate\"\n              }\n            }\n          },\n          \"required\": true\n        },\n        \"responses\": {\n          \"200\": {\n            \"description\": \"Successful Response\",\n            \"content\": {\n              \"application/json\": {\n                \"schema\": {\n                  \"$ref\": \"#/components/schemas/UserRead\"\n                }\n              }\n            }\n          },\n          \"401\": {\n            \"description\": \"Missing token or inactive user.\"\n          },\n          \"400\": {\n            \"description\": \"Bad Request\",\n            \"content\": {\n              \"application/json\": {\n                \"schema\": {\n                  \"$ref\": \"#/components/schemas/ErrorModel\"\n                },\n                \"examples\": {\n                  \"UPDATE_USER_EMAIL_ALREADY_EXISTS\": {\n                    \"summary\": \"A user with this email already exists.\",\n                    \"value\": {\n                      \"detail\": \"UPDATE_USER_EMAIL_ALREADY_EXISTS\"\n                    }\n                  },\n                  \"UPDATE_USER_INVALID_PASSWORD\": {\n                    \"summary\": \"Password validation failed.\",\n                    \"value\": {\n                      \"detail\": {\n                        \"code\": \"UPDATE_USER_INVALID_PASSWORD\",\n                        \"reason\": \"Password should beat least 3 characters\"\n                      }\n                    }\n                  }\n                }\n              }\n            }\n          },\n          \"422\": {\n            \"description\": \"Validation Error\",\n            \"content\": {\n              \"application/json\": {\n                \"schema\": {\n                  \"$ref\": \"#/components/schemas/HTTPValidationError\"\n                }\n              }\n            }\n          }\n        },\n        \"security\": [\n          {\n            \"OAuth2PasswordBearer\": []\n          }\n        ]\n      }\n    },\n    \"/users/{id}\": {\n      \"get\": {\n        \"tags\": [\n          \"users\"\n        ],\n        \"summary\": \"Users:User\",\n        \"operationId\": \"users-users:user_get\",\n        \"security\": [\n          {\n            \"OAuth2PasswordBearer\": []\n          }\n        ],\n        \"parameters\": [\n          {\n            \"name\": \"id\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"schema\": {\n              \"type\": \"string\",\n              \"title\": \"Id\"\n            }\n          }\n        ],\n        \"responses\": {\n          \"200\": {\n            \"description\": \"Successful Response\",\n            \"content\": {\n              \"application/json\": {\n                \"schema\": {\n                  \"$ref\": \"#/components/schemas/UserRead\"\n                }\n              }\n            }\n          },\n          \"401\": {\n            \"description\": \"Missing token or inactive user.\"\n          },\n          \"403\": {\n            \"description\": \"Not a superuser.\"\n          },\n          \"404\": {\n            \"description\": \"The user does not exist.\"\n          },\n          \"422\": {\n            \"description\": \"Validation Error\",\n            \"content\": {\n              \"application/json\": {\n                \"schema\": {\n                  \"$ref\": \"#/components/schemas/HTTPValidationError\"\n                }\n              }\n            }\n          }\n        }\n      },\n      \"patch\": {\n        \"tags\": [\n          \"users\"\n        ],\n        \"summary\": \"Users:Patch User\",\n        \"operationId\": \"users-users:patch_user_patch\",\n        \"security\": [\n          {\n            \"OAuth2PasswordBearer\": []\n          }\n        ],\n        \"parameters\": [\n          {\n            \"name\": \"id\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"schema\": {\n              \"type\": \"string\",\n              \"title\": \"Id\"\n            }\n          }\n        ],\n        \"requestBody\": {\n          \"required\": true,\n          \"content\": {\n            \"application/json\": {\n              \"schema\": {\n                \"$ref\": \"#/components/schemas/UserUpdate\"\n              }\n            }\n          }\n        },\n        \"responses\": {\n          \"200\": {\n            \"description\": \"Successful Response\",\n            \"content\": {\n              \"application/json\": {\n                \"schema\": {\n                  \"$ref\": \"#/components/schemas/UserRead\"\n                }\n              }\n            }\n          },\n          \"401\": {\n            \"description\": \"Missing token or inactive user.\"\n          },\n          \"403\": {\n            \"description\": \"Not a superuser.\"\n          },\n          \"404\": {\n            \"description\": \"The user does not exist.\"\n          },\n          \"400\": {\n            \"content\": {\n              \"application/json\": {\n                \"examples\": {\n                  \"UPDATE_USER_EMAIL_ALREADY_EXISTS\": {\n                    \"summary\": \"A user with this email already exists.\",\n                    \"value\": {\n                      \"detail\": \"UPDATE_USER_EMAIL_ALREADY_EXISTS\"\n                    }\n                  },\n                  \"UPDATE_USER_INVALID_PASSWORD\": {\n                    \"summary\": \"Password validation failed.\",\n                    \"value\": {\n                      \"detail\": {\n                        \"code\": \"UPDATE_USER_INVALID_PASSWORD\",\n                        \"reason\": \"Password should beat least 3 characters\"\n                      }\n                    }\n                  }\n                },\n                \"schema\": {\n                  \"$ref\": \"#/components/schemas/ErrorModel\"\n                }\n              }\n            },\n            \"description\": \"Bad Request\"\n          },\n          \"422\": {\n            \"description\": \"Validation Error\",\n            \"content\": {\n              \"application/json\": {\n                \"schema\": {\n                  \"$ref\": \"#/components/schemas/HTTPValidationError\"\n                }\n              }\n            }\n          }\n        }\n      },\n      \"delete\": {\n        \"tags\": [\n          \"users\"\n        ],\n        \"summary\": \"Users:Delete User\",\n        \"operationId\": \"users-users:delete_user_delete\",\n        \"security\": [\n          {\n            \"OAuth2PasswordBearer\": []\n          }\n        ],\n        \"parameters\": [\n          {\n            \"name\": \"id\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"schema\": {\n              \"type\": \"string\",\n              \"title\": \"Id\"\n            }\n          }\n        ],\n        \"responses\": {\n          \"204\": {\n            \"description\": \"Successful Response\"\n          },\n          \"401\": {\n            \"description\": \"Missing token or inactive user.\"\n          },\n          \"403\": {\n            \"description\": \"Not a superuser.\"\n          },\n          \"404\": {\n            \"description\": \"The user does not exist.\"\n          },\n          \"422\": {\n            \"description\": \"Validation Error\",\n            \"content\": {\n              \"application/json\": {\n                \"schema\": {\n                  \"$ref\": \"#/components/schemas/HTTPValidationError\"\n                }\n              }\n            }\n          }\n        }\n      }\n    },\n    \"/authenticated-route\": {\n      \"get\": {\n        \"tags\": [\n          \"custom-auth\"\n        ],\n        \"summary\": \"Authenticated Route\",\n        \"operationId\": \"custom-auth-authenticated_route_get\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"Successful Response\",\n            \"content\": {\n              \"application/json\": {\n                \"schema\": {}\n              }\n            }\n          }\n        },\n        \"security\": [\n          {\n            \"OAuth2PasswordBearer\": []\n          }\n        ]\n      }\n    }\n  },\n  \"components\": {\n    \"schemas\": {\n      \"BearerResponse\": {\n        \"properties\": {\n          \"access_token\": {\n            \"type\": \"string\",\n            \"title\": \"Access Token\"\n          },\n          \"token_type\": {\n            \"type\": \"string\",\n            \"title\": \"Token Type\"\n          }\n        },\n        \"type\": \"object\",\n        \"required\": [\n          \"access_token\",\n          \"token_type\"\n        ],\n        \"title\": \"BearerResponse\"\n      },\n      \"Body_auth-reset_forgot_password_post\": {\n        \"properties\": {\n          \"email\": {\n            \"type\": \"string\",\n            \"format\": \"email\",\n            \"title\": \"Email\"\n          }\n        },\n        \"type\": \"object\",\n        \"required\": [\n          \"email\"\n        ],\n        \"title\": \"Body_auth-reset:forgot_password_post\"\n      },\n      \"Body_auth-reset_reset_password_post\": {\n        \"properties\": {\n          \"token\": {\n            \"type\": \"string\",\n            \"title\": \"Token\"\n          },\n          \"password\": {\n            \"type\": \"string\",\n            \"title\": \"Password\"\n          }\n        },\n        \"type\": \"object\",\n        \"required\": [\n          \"token\",\n          \"password\"\n        ],\n        \"title\": \"Body_auth-reset:reset_password_post\"\n      },\n      \"Body_auth-verify_request-token_post\": {\n        \"properties\": {\n          \"email\": {\n            \"type\": \"string\",\n            \"format\": \"email\",\n            \"title\": \"Email\"\n          }\n        },\n        \"type\": \"object\",\n        \"required\": [\n          \"email\"\n        ],\n        \"title\": \"Body_auth-verify:request-token_post\"\n      },\n      \"Body_auth-verify_verify_post\": {\n        \"properties\": {\n          \"token\": {\n            \"type\": \"string\",\n            \"title\": \"Token\"\n          }\n        },\n        \"type\": \"object\",\n        \"required\": [\n          \"token\"\n        ],\n        \"title\": \"Body_auth-verify:verify_post\"\n      },\n      \"ErrorModel\": {\n        \"properties\": {\n          \"detail\": {\n            \"anyOf\": [\n              {\n                \"type\": \"string\"\n              },\n              {\n                \"additionalProperties\": {\n                  \"type\": \"string\"\n                },\n                \"type\": \"object\"\n              }\n            ],\n            \"title\": \"Detail\"\n          }\n        },\n        \"type\": \"object\",\n        \"required\": [\n          \"detail\"\n        ],\n        \"title\": \"ErrorModel\"\n      },\n      \"HTTPValidationError\": {\n        \"properties\": {\n          \"detail\": {\n            \"items\": {\n              \"$ref\": \"#/components/schemas/ValidationError\"\n            },\n            \"type\": \"array\",\n            \"title\": \"Detail\"\n          }\n        },\n        \"type\": \"object\",\n        \"title\": \"HTTPValidationError\"\n      },\n      \"UserCreate\": {\n        \"properties\": {\n          \"email\": {\n            \"type\": \"string\",\n            \"format\": \"email\",\n            \"title\": \"Email\"\n          },\n          \"password\": {\n            \"type\": \"string\",\n            \"title\": \"Password\"\n          },\n          \"is_active\": {\n            \"anyOf\": [\n              {\n                \"type\": \"boolean\"\n              },\n              {\n                \"type\": \"null\"\n              }\n            ],\n            \"title\": \"Is Active\",\n            \"default\": true\n          },\n          \"is_superuser\": {\n            \"anyOf\": [\n              {\n                \"type\": \"boolean\"\n              },\n              {\n                \"type\": \"null\"\n              }\n            ],\n            \"title\": \"Is Superuser\",\n            \"default\": false\n          },\n          \"is_verified\": {\n            \"anyOf\": [\n              {\n                \"type\": \"boolean\"\n              },\n              {\n                \"type\": \"null\"\n              }\n            ],\n            \"title\": \"Is Verified\",\n            \"default\": false\n          }\n        },\n        \"type\": \"object\",\n        \"required\": [\n          \"email\",\n          \"password\"\n        ],\n        \"title\": \"UserCreate\"\n      },\n      \"UserRead\": {\n        \"properties\": {\n          \"id\": {\n            \"type\": \"string\",\n            \"format\": \"uuid\",\n            \"title\": \"Id\"\n          },\n          \"email\": {\n            \"type\": \"string\",\n            \"format\": \"email\",\n            \"title\": \"Email\"\n          },\n          \"is_active\": {\n            \"type\": \"boolean\",\n            \"title\": \"Is Active\",\n            \"default\": true\n          },\n          \"is_superuser\": {\n            \"type\": \"boolean\",\n            \"title\": \"Is Superuser\",\n            \"default\": false\n          },\n          \"is_verified\": {\n            \"type\": \"boolean\",\n            \"title\": \"Is Verified\",\n            \"default\": false\n          }\n        },\n        \"type\": \"object\",\n        \"required\": [\n          \"id\",\n          \"email\"\n        ],\n        \"title\": \"UserRead\"\n      },\n      \"UserUpdate\": {\n        \"properties\": {\n          \"password\": {\n            \"anyOf\": [\n              {\n                \"type\": \"string\"\n              },\n              {\n                \"type\": \"null\"\n              }\n            ],\n            \"title\": \"Password\"\n          },\n          \"email\": {\n            \"anyOf\": [\n              {\n                \"type\": \"string\",\n                \"format\": \"email\"\n              },\n              {\n                \"type\": \"null\"\n              }\n            ],\n            \"title\": \"Email\"\n          },\n          \"is_active\": {\n            \"anyOf\": [\n              {\n                \"type\": \"boolean\"\n              },\n              {\n                \"type\": \"null\"\n              }\n            ],\n            \"title\": \"Is Active\"\n          },\n          \"is_superuser\": {\n            \"anyOf\": [\n              {\n                \"type\": \"boolean\"\n              },\n              {\n                \"type\": \"null\"\n              }\n            ],\n            \"title\": \"Is Superuser\"\n          },\n          \"is_verified\": {\n            \"anyOf\": [\n              {\n                \"type\": \"boolean\"\n              },\n              {\n                \"type\": \"null\"\n              }\n            ],\n            \"title\": \"Is Verified\"\n          }\n        },\n        \"type\": \"object\",\n        \"title\": \"UserUpdate\"\n      },\n      \"ValidationError\": {\n        \"properties\": {\n          \"loc\": {\n            \"items\": {\n              \"anyOf\": [\n                {\n                  \"type\": \"string\"\n                },\n                {\n                  \"type\": \"integer\"\n                }\n              ]\n            },\n            \"type\": \"array\",\n            \"title\": \"Location\"\n          },\n          \"msg\": {\n            \"type\": \"string\",\n            \"title\": \"Message\"\n          },\n          \"type\": {\n            \"type\": \"string\",\n            \"title\": \"Error Type\"\n          }\n        },\n        \"type\": \"object\",\n        \"required\": [\n          \"loc\",\n          \"msg\",\n          \"type\"\n        ],\n        \"title\": \"ValidationError\"\n      },\n      \"login_post\": {\n        \"properties\": {\n          \"grant_type\": {\n            \"anyOf\": [\n              {\n                \"type\": \"string\",\n                \"pattern\": \"password\"\n              },\n              {\n                \"type\": \"null\"\n              }\n            ],\n            \"title\": \"Grant Type\"\n          },\n          \"username\": {\n            \"type\": \"string\",\n            \"title\": \"Username\"\n          },\n          \"password\": {\n            \"type\": \"string\",\n            \"title\": \"Password\"\n          },\n          \"scope\": {\n            \"type\": \"string\",\n            \"title\": \"Scope\",\n            \"default\": \"\"\n          },\n          \"client_id\": {\n            \"anyOf\": [\n              {\n                \"type\": \"string\"\n              },\n              {\n                \"type\": \"null\"\n              }\n            ],\n            \"title\": \"Client Id\"\n          },\n          \"client_secret\": {\n            \"anyOf\": [\n              {\n                \"type\": \"string\"\n              },\n              {\n                \"type\": \"null\"\n              }\n            ],\n            \"title\": \"Client Secret\"\n          }\n        },\n        \"type\": \"object\",\n        \"required\": [\n          \"username\",\n          \"password\"\n        ],\n        \"title\": \"Body_auth-auth:jwt.login_post\"\n      }\n    },\n    \"securitySchemes\": {\n      \"OAuth2PasswordBearer\": {\n        \"type\": \"oauth2\",\n        \"flows\": {\n          \"password\": {\n            \"scopes\": {},\n            \"tokenUrl\": \"auth/jwt/login\"\n          }\n        }\n      }\n    }\n  }\n}"
  },
  {
    "path": "fastapi_backend/tests/commands/files/openapi_test_output.json",
    "content": "{\n  \"openapi\": \"3.1.0\",\n  \"info\": {\n    \"title\": \"FastAPI\",\n    \"version\": \"0.1.0\"\n  },\n  \"paths\": {\n    \"/auth/jwt/login\": {\n      \"post\": {\n        \"tags\": [\n          \"auth\"\n        ],\n        \"summary\": \"Auth:Jwt.Login\",\n        \"operationId\": \"auth:jwt.login_post\",\n        \"requestBody\": {\n          \"content\": {\n            \"application/x-www-form-urlencoded\": {\n              \"schema\": {\n                \"$ref\": \"#/components/schemas/login_post\"\n              }\n            }\n          },\n          \"required\": true\n        },\n        \"responses\": {\n          \"200\": {\n            \"description\": \"Successful Response\",\n            \"content\": {\n              \"application/json\": {\n                \"schema\": {\n                  \"$ref\": \"#/components/schemas/BearerResponse\"\n                },\n                \"example\": {\n                  \"access_token\": \"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiOTIyMWZmYzktNjQwZi00MzcyLTg2ZDMtY2U2NDJjYmE1NjAzIiwiYXVkIjoiZmFzdGFwaS11c2VyczphdXRoIiwiZXhwIjoxNTcxNTA0MTkzfQ.M10bjOe45I5Ncu_uXvOmVV8QxnL-nZfcH96U90JaocI\",\n                  \"token_type\": \"bearer\"\n                }\n              }\n            }\n          },\n          \"400\": {\n            \"description\": \"Bad Request\",\n            \"content\": {\n              \"application/json\": {\n                \"schema\": {\n                  \"$ref\": \"#/components/schemas/ErrorModel\"\n                },\n                \"examples\": {\n                  \"LOGIN_BAD_CREDENTIALS\": {\n                    \"summary\": \"Bad credentials or the user is inactive.\",\n                    \"value\": {\n                      \"detail\": \"LOGIN_BAD_CREDENTIALS\"\n                    }\n                  },\n                  \"LOGIN_USER_NOT_VERIFIED\": {\n                    \"summary\": \"The user is not verified.\",\n                    \"value\": {\n                      \"detail\": \"LOGIN_USER_NOT_VERIFIED\"\n                    }\n                  }\n                }\n              }\n            }\n          },\n          \"422\": {\n            \"description\": \"Validation Error\",\n            \"content\": {\n              \"application/json\": {\n                \"schema\": {\n                  \"$ref\": \"#/components/schemas/HTTPValidationError\"\n                }\n              }\n            }\n          }\n        }\n      }\n    },\n    \"/auth/jwt/logout\": {\n      \"post\": {\n        \"tags\": [\n          \"auth\"\n        ],\n        \"summary\": \"Auth:Jwt.Logout\",\n        \"operationId\": \"auth:jwt.logout_post\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"Successful Response\",\n            \"content\": {\n              \"application/json\": {\n                \"schema\": {}\n              }\n            }\n          },\n          \"401\": {\n            \"description\": \"Missing token or inactive user.\"\n          }\n        },\n        \"security\": [\n          {\n            \"OAuth2PasswordBearer\": []\n          }\n        ]\n      }\n    },\n    \"/auth/register\": {\n      \"post\": {\n        \"tags\": [\n          \"auth\"\n        ],\n        \"summary\": \"Register:Register\",\n        \"operationId\": \"register:register_post\",\n        \"requestBody\": {\n          \"content\": {\n            \"application/json\": {\n              \"schema\": {\n                \"$ref\": \"#/components/schemas/UserCreate\"\n              }\n            }\n          },\n          \"required\": true\n        },\n        \"responses\": {\n          \"201\": {\n            \"description\": \"Successful Response\",\n            \"content\": {\n              \"application/json\": {\n                \"schema\": {\n                  \"$ref\": \"#/components/schemas/UserRead\"\n                }\n              }\n            }\n          },\n          \"400\": {\n            \"description\": \"Bad Request\",\n            \"content\": {\n              \"application/json\": {\n                \"schema\": {\n                  \"$ref\": \"#/components/schemas/ErrorModel\"\n                },\n                \"examples\": {\n                  \"REGISTER_USER_ALREADY_EXISTS\": {\n                    \"summary\": \"A user with this email already exists.\",\n                    \"value\": {\n                      \"detail\": \"REGISTER_USER_ALREADY_EXISTS\"\n                    }\n                  },\n                  \"REGISTER_INVALID_PASSWORD\": {\n                    \"summary\": \"Password validation failed.\",\n                    \"value\": {\n                      \"detail\": {\n                        \"code\": \"REGISTER_INVALID_PASSWORD\",\n                        \"reason\": \"Password should beat least 3 characters\"\n                      }\n                    }\n                  }\n                }\n              }\n            }\n          },\n          \"422\": {\n            \"description\": \"Validation Error\",\n            \"content\": {\n              \"application/json\": {\n                \"schema\": {\n                  \"$ref\": \"#/components/schemas/HTTPValidationError\"\n                }\n              }\n            }\n          }\n        }\n      }\n    },\n    \"/auth/forgot-password\": {\n      \"post\": {\n        \"tags\": [\n          \"auth\"\n        ],\n        \"summary\": \"Reset:Forgot Password\",\n        \"operationId\": \"reset:forgot_password_post\",\n        \"requestBody\": {\n          \"content\": {\n            \"application/json\": {\n              \"schema\": {\n                \"$ref\": \"#/components/schemas/Body_auth-reset_forgot_password_post\"\n              }\n            }\n          },\n          \"required\": true\n        },\n        \"responses\": {\n          \"202\": {\n            \"description\": \"Successful Response\",\n            \"content\": {\n              \"application/json\": {\n                \"schema\": {}\n              }\n            }\n          },\n          \"422\": {\n            \"description\": \"Validation Error\",\n            \"content\": {\n              \"application/json\": {\n                \"schema\": {\n                  \"$ref\": \"#/components/schemas/HTTPValidationError\"\n                }\n              }\n            }\n          }\n        }\n      }\n    },\n    \"/auth/reset-password\": {\n      \"post\": {\n        \"tags\": [\n          \"auth\"\n        ],\n        \"summary\": \"Reset:Reset Password\",\n        \"operationId\": \"reset:reset_password_post\",\n        \"requestBody\": {\n          \"content\": {\n            \"application/json\": {\n              \"schema\": {\n                \"$ref\": \"#/components/schemas/Body_auth-reset_reset_password_post\"\n              }\n            }\n          },\n          \"required\": true\n        },\n        \"responses\": {\n          \"200\": {\n            \"description\": \"Successful Response\",\n            \"content\": {\n              \"application/json\": {\n                \"schema\": {}\n              }\n            }\n          },\n          \"400\": {\n            \"description\": \"Bad Request\",\n            \"content\": {\n              \"application/json\": {\n                \"schema\": {\n                  \"$ref\": \"#/components/schemas/ErrorModel\"\n                },\n                \"examples\": {\n                  \"RESET_PASSWORD_BAD_TOKEN\": {\n                    \"summary\": \"Bad or expired token.\",\n                    \"value\": {\n                      \"detail\": \"RESET_PASSWORD_BAD_TOKEN\"\n                    }\n                  },\n                  \"RESET_PASSWORD_INVALID_PASSWORD\": {\n                    \"summary\": \"Password validation failed.\",\n                    \"value\": {\n                      \"detail\": {\n                        \"code\": \"RESET_PASSWORD_INVALID_PASSWORD\",\n                        \"reason\": \"Password should be at least 3 characters\"\n                      }\n                    }\n                  }\n                }\n              }\n            }\n          },\n          \"422\": {\n            \"description\": \"Validation Error\",\n            \"content\": {\n              \"application/json\": {\n                \"schema\": {\n                  \"$ref\": \"#/components/schemas/HTTPValidationError\"\n                }\n              }\n            }\n          }\n        }\n      }\n    },\n    \"/auth/request-verify-token\": {\n      \"post\": {\n        \"tags\": [\n          \"auth\"\n        ],\n        \"summary\": \"Verify:Request-Token\",\n        \"operationId\": \"verify:request-token_post\",\n        \"requestBody\": {\n          \"content\": {\n            \"application/json\": {\n              \"schema\": {\n                \"$ref\": \"#/components/schemas/Body_auth-verify_request-token_post\"\n              }\n            }\n          },\n          \"required\": true\n        },\n        \"responses\": {\n          \"202\": {\n            \"description\": \"Successful Response\",\n            \"content\": {\n              \"application/json\": {\n                \"schema\": {}\n              }\n            }\n          },\n          \"422\": {\n            \"description\": \"Validation Error\",\n            \"content\": {\n              \"application/json\": {\n                \"schema\": {\n                  \"$ref\": \"#/components/schemas/HTTPValidationError\"\n                }\n              }\n            }\n          }\n        }\n      }\n    },\n    \"/auth/verify\": {\n      \"post\": {\n        \"tags\": [\n          \"auth\"\n        ],\n        \"summary\": \"Verify:Verify\",\n        \"operationId\": \"verify:verify_post\",\n        \"requestBody\": {\n          \"content\": {\n            \"application/json\": {\n              \"schema\": {\n                \"$ref\": \"#/components/schemas/Body_auth-verify_verify_post\"\n              }\n            }\n          },\n          \"required\": true\n        },\n        \"responses\": {\n          \"200\": {\n            \"description\": \"Successful Response\",\n            \"content\": {\n              \"application/json\": {\n                \"schema\": {\n                  \"$ref\": \"#/components/schemas/UserRead\"\n                }\n              }\n            }\n          },\n          \"400\": {\n            \"description\": \"Bad Request\",\n            \"content\": {\n              \"application/json\": {\n                \"schema\": {\n                  \"$ref\": \"#/components/schemas/ErrorModel\"\n                },\n                \"examples\": {\n                  \"VERIFY_USER_BAD_TOKEN\": {\n                    \"summary\": \"Bad token, not existing user ornot the e-mail currently set for the user.\",\n                    \"value\": {\n                      \"detail\": \"VERIFY_USER_BAD_TOKEN\"\n                    }\n                  },\n                  \"VERIFY_USER_ALREADY_VERIFIED\": {\n                    \"summary\": \"The user is already verified.\",\n                    \"value\": {\n                      \"detail\": \"VERIFY_USER_ALREADY_VERIFIED\"\n                    }\n                  }\n                }\n              }\n            }\n          },\n          \"422\": {\n            \"description\": \"Validation Error\",\n            \"content\": {\n              \"application/json\": {\n                \"schema\": {\n                  \"$ref\": \"#/components/schemas/HTTPValidationError\"\n                }\n              }\n            }\n          }\n        }\n      }\n    },\n    \"/users/me\": {\n      \"get\": {\n        \"tags\": [\n          \"users\"\n        ],\n        \"summary\": \"Users:Current User\",\n        \"operationId\": \"users:current_user_get\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"Successful Response\",\n            \"content\": {\n              \"application/json\": {\n                \"schema\": {\n                  \"$ref\": \"#/components/schemas/UserRead\"\n                }\n              }\n            }\n          },\n          \"401\": {\n            \"description\": \"Missing token or inactive user.\"\n          }\n        },\n        \"security\": [\n          {\n            \"OAuth2PasswordBearer\": []\n          }\n        ]\n      },\n      \"patch\": {\n        \"tags\": [\n          \"users\"\n        ],\n        \"summary\": \"Users:Patch Current User\",\n        \"operationId\": \"users:patch_current_user_patch\",\n        \"requestBody\": {\n          \"content\": {\n            \"application/json\": {\n              \"schema\": {\n                \"$ref\": \"#/components/schemas/UserUpdate\"\n              }\n            }\n          },\n          \"required\": true\n        },\n        \"responses\": {\n          \"200\": {\n            \"description\": \"Successful Response\",\n            \"content\": {\n              \"application/json\": {\n                \"schema\": {\n                  \"$ref\": \"#/components/schemas/UserRead\"\n                }\n              }\n            }\n          },\n          \"401\": {\n            \"description\": \"Missing token or inactive user.\"\n          },\n          \"400\": {\n            \"description\": \"Bad Request\",\n            \"content\": {\n              \"application/json\": {\n                \"schema\": {\n                  \"$ref\": \"#/components/schemas/ErrorModel\"\n                },\n                \"examples\": {\n                  \"UPDATE_USER_EMAIL_ALREADY_EXISTS\": {\n                    \"summary\": \"A user with this email already exists.\",\n                    \"value\": {\n                      \"detail\": \"UPDATE_USER_EMAIL_ALREADY_EXISTS\"\n                    }\n                  },\n                  \"UPDATE_USER_INVALID_PASSWORD\": {\n                    \"summary\": \"Password validation failed.\",\n                    \"value\": {\n                      \"detail\": {\n                        \"code\": \"UPDATE_USER_INVALID_PASSWORD\",\n                        \"reason\": \"Password should beat least 3 characters\"\n                      }\n                    }\n                  }\n                }\n              }\n            }\n          },\n          \"422\": {\n            \"description\": \"Validation Error\",\n            \"content\": {\n              \"application/json\": {\n                \"schema\": {\n                  \"$ref\": \"#/components/schemas/HTTPValidationError\"\n                }\n              }\n            }\n          }\n        },\n        \"security\": [\n          {\n            \"OAuth2PasswordBearer\": []\n          }\n        ]\n      }\n    },\n    \"/users/{id}\": {\n      \"get\": {\n        \"tags\": [\n          \"users\"\n        ],\n        \"summary\": \"Users:User\",\n        \"operationId\": \"users:user_get\",\n        \"security\": [\n          {\n            \"OAuth2PasswordBearer\": []\n          }\n        ],\n        \"parameters\": [\n          {\n            \"name\": \"id\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"schema\": {\n              \"type\": \"string\",\n              \"title\": \"Id\"\n            }\n          }\n        ],\n        \"responses\": {\n          \"200\": {\n            \"description\": \"Successful Response\",\n            \"content\": {\n              \"application/json\": {\n                \"schema\": {\n                  \"$ref\": \"#/components/schemas/UserRead\"\n                }\n              }\n            }\n          },\n          \"401\": {\n            \"description\": \"Missing token or inactive user.\"\n          },\n          \"403\": {\n            \"description\": \"Not a superuser.\"\n          },\n          \"404\": {\n            \"description\": \"The user does not exist.\"\n          },\n          \"422\": {\n            \"description\": \"Validation Error\",\n            \"content\": {\n              \"application/json\": {\n                \"schema\": {\n                  \"$ref\": \"#/components/schemas/HTTPValidationError\"\n                }\n              }\n            }\n          }\n        }\n      },\n      \"patch\": {\n        \"tags\": [\n          \"users\"\n        ],\n        \"summary\": \"Users:Patch User\",\n        \"operationId\": \"users:patch_user_patch\",\n        \"security\": [\n          {\n            \"OAuth2PasswordBearer\": []\n          }\n        ],\n        \"parameters\": [\n          {\n            \"name\": \"id\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"schema\": {\n              \"type\": \"string\",\n              \"title\": \"Id\"\n            }\n          }\n        ],\n        \"requestBody\": {\n          \"required\": true,\n          \"content\": {\n            \"application/json\": {\n              \"schema\": {\n                \"$ref\": \"#/components/schemas/UserUpdate\"\n              }\n            }\n          }\n        },\n        \"responses\": {\n          \"200\": {\n            \"description\": \"Successful Response\",\n            \"content\": {\n              \"application/json\": {\n                \"schema\": {\n                  \"$ref\": \"#/components/schemas/UserRead\"\n                }\n              }\n            }\n          },\n          \"401\": {\n            \"description\": \"Missing token or inactive user.\"\n          },\n          \"403\": {\n            \"description\": \"Not a superuser.\"\n          },\n          \"404\": {\n            \"description\": \"The user does not exist.\"\n          },\n          \"400\": {\n            \"content\": {\n              \"application/json\": {\n                \"examples\": {\n                  \"UPDATE_USER_EMAIL_ALREADY_EXISTS\": {\n                    \"summary\": \"A user with this email already exists.\",\n                    \"value\": {\n                      \"detail\": \"UPDATE_USER_EMAIL_ALREADY_EXISTS\"\n                    }\n                  },\n                  \"UPDATE_USER_INVALID_PASSWORD\": {\n                    \"summary\": \"Password validation failed.\",\n                    \"value\": {\n                      \"detail\": {\n                        \"code\": \"UPDATE_USER_INVALID_PASSWORD\",\n                        \"reason\": \"Password should beat least 3 characters\"\n                      }\n                    }\n                  }\n                },\n                \"schema\": {\n                  \"$ref\": \"#/components/schemas/ErrorModel\"\n                }\n              }\n            },\n            \"description\": \"Bad Request\"\n          },\n          \"422\": {\n            \"description\": \"Validation Error\",\n            \"content\": {\n              \"application/json\": {\n                \"schema\": {\n                  \"$ref\": \"#/components/schemas/HTTPValidationError\"\n                }\n              }\n            }\n          }\n        }\n      },\n      \"delete\": {\n        \"tags\": [\n          \"users\"\n        ],\n        \"summary\": \"Users:Delete User\",\n        \"operationId\": \"users:delete_user_delete\",\n        \"security\": [\n          {\n            \"OAuth2PasswordBearer\": []\n          }\n        ],\n        \"parameters\": [\n          {\n            \"name\": \"id\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"schema\": {\n              \"type\": \"string\",\n              \"title\": \"Id\"\n            }\n          }\n        ],\n        \"responses\": {\n          \"204\": {\n            \"description\": \"Successful Response\"\n          },\n          \"401\": {\n            \"description\": \"Missing token or inactive user.\"\n          },\n          \"403\": {\n            \"description\": \"Not a superuser.\"\n          },\n          \"404\": {\n            \"description\": \"The user does not exist.\"\n          },\n          \"422\": {\n            \"description\": \"Validation Error\",\n            \"content\": {\n              \"application/json\": {\n                \"schema\": {\n                  \"$ref\": \"#/components/schemas/HTTPValidationError\"\n                }\n              }\n            }\n          }\n        }\n      }\n    },\n    \"/authenticated-route\": {\n      \"get\": {\n        \"tags\": [\n          \"custom-auth\"\n        ],\n        \"summary\": \"Authenticated Route\",\n        \"operationId\": \"authenticated_route_get\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"Successful Response\",\n            \"content\": {\n              \"application/json\": {\n                \"schema\": {}\n              }\n            }\n          }\n        },\n        \"security\": [\n          {\n            \"OAuth2PasswordBearer\": []\n          }\n        ]\n      }\n    }\n  },\n  \"components\": {\n    \"schemas\": {\n      \"BearerResponse\": {\n        \"properties\": {\n          \"access_token\": {\n            \"type\": \"string\",\n            \"title\": \"Access Token\"\n          },\n          \"token_type\": {\n            \"type\": \"string\",\n            \"title\": \"Token Type\"\n          }\n        },\n        \"type\": \"object\",\n        \"required\": [\n          \"access_token\",\n          \"token_type\"\n        ],\n        \"title\": \"BearerResponse\"\n      },\n      \"Body_auth-reset_forgot_password_post\": {\n        \"properties\": {\n          \"email\": {\n            \"type\": \"string\",\n            \"format\": \"email\",\n            \"title\": \"Email\"\n          }\n        },\n        \"type\": \"object\",\n        \"required\": [\n          \"email\"\n        ],\n        \"title\": \"Body_auth-reset:forgot_password_post\"\n      },\n      \"Body_auth-reset_reset_password_post\": {\n        \"properties\": {\n          \"token\": {\n            \"type\": \"string\",\n            \"title\": \"Token\"\n          },\n          \"password\": {\n            \"type\": \"string\",\n            \"title\": \"Password\"\n          }\n        },\n        \"type\": \"object\",\n        \"required\": [\n          \"token\",\n          \"password\"\n        ],\n        \"title\": \"Body_auth-reset:reset_password_post\"\n      },\n      \"Body_auth-verify_request-token_post\": {\n        \"properties\": {\n          \"email\": {\n            \"type\": \"string\",\n            \"format\": \"email\",\n            \"title\": \"Email\"\n          }\n        },\n        \"type\": \"object\",\n        \"required\": [\n          \"email\"\n        ],\n        \"title\": \"Body_auth-verify:request-token_post\"\n      },\n      \"Body_auth-verify_verify_post\": {\n        \"properties\": {\n          \"token\": {\n            \"type\": \"string\",\n            \"title\": \"Token\"\n          }\n        },\n        \"type\": \"object\",\n        \"required\": [\n          \"token\"\n        ],\n        \"title\": \"Body_auth-verify:verify_post\"\n      },\n      \"ErrorModel\": {\n        \"properties\": {\n          \"detail\": {\n            \"anyOf\": [\n              {\n                \"type\": \"string\"\n              },\n              {\n                \"additionalProperties\": {\n                  \"type\": \"string\"\n                },\n                \"type\": \"object\"\n              }\n            ],\n            \"title\": \"Detail\"\n          }\n        },\n        \"type\": \"object\",\n        \"required\": [\n          \"detail\"\n        ],\n        \"title\": \"ErrorModel\"\n      },\n      \"HTTPValidationError\": {\n        \"properties\": {\n          \"detail\": {\n            \"items\": {\n              \"$ref\": \"#/components/schemas/ValidationError\"\n            },\n            \"type\": \"array\",\n            \"title\": \"Detail\"\n          }\n        },\n        \"type\": \"object\",\n        \"title\": \"HTTPValidationError\"\n      },\n      \"UserCreate\": {\n        \"properties\": {\n          \"email\": {\n            \"type\": \"string\",\n            \"format\": \"email\",\n            \"title\": \"Email\"\n          },\n          \"password\": {\n            \"type\": \"string\",\n            \"title\": \"Password\"\n          },\n          \"is_active\": {\n            \"anyOf\": [\n              {\n                \"type\": \"boolean\"\n              },\n              {\n                \"type\": \"null\"\n              }\n            ],\n            \"title\": \"Is Active\",\n            \"default\": true\n          },\n          \"is_superuser\": {\n            \"anyOf\": [\n              {\n                \"type\": \"boolean\"\n              },\n              {\n                \"type\": \"null\"\n              }\n            ],\n            \"title\": \"Is Superuser\",\n            \"default\": false\n          },\n          \"is_verified\": {\n            \"anyOf\": [\n              {\n                \"type\": \"boolean\"\n              },\n              {\n                \"type\": \"null\"\n              }\n            ],\n            \"title\": \"Is Verified\",\n            \"default\": false\n          }\n        },\n        \"type\": \"object\",\n        \"required\": [\n          \"email\",\n          \"password\"\n        ],\n        \"title\": \"UserCreate\"\n      },\n      \"UserRead\": {\n        \"properties\": {\n          \"id\": {\n            \"type\": \"string\",\n            \"format\": \"uuid\",\n            \"title\": \"Id\"\n          },\n          \"email\": {\n            \"type\": \"string\",\n            \"format\": \"email\",\n            \"title\": \"Email\"\n          },\n          \"is_active\": {\n            \"type\": \"boolean\",\n            \"title\": \"Is Active\",\n            \"default\": true\n          },\n          \"is_superuser\": {\n            \"type\": \"boolean\",\n            \"title\": \"Is Superuser\",\n            \"default\": false\n          },\n          \"is_verified\": {\n            \"type\": \"boolean\",\n            \"title\": \"Is Verified\",\n            \"default\": false\n          }\n        },\n        \"type\": \"object\",\n        \"required\": [\n          \"id\",\n          \"email\"\n        ],\n        \"title\": \"UserRead\"\n      },\n      \"UserUpdate\": {\n        \"properties\": {\n          \"password\": {\n            \"anyOf\": [\n              {\n                \"type\": \"string\"\n              },\n              {\n                \"type\": \"null\"\n              }\n            ],\n            \"title\": \"Password\"\n          },\n          \"email\": {\n            \"anyOf\": [\n              {\n                \"type\": \"string\",\n                \"format\": \"email\"\n              },\n              {\n                \"type\": \"null\"\n              }\n            ],\n            \"title\": \"Email\"\n          },\n          \"is_active\": {\n            \"anyOf\": [\n              {\n                \"type\": \"boolean\"\n              },\n              {\n                \"type\": \"null\"\n              }\n            ],\n            \"title\": \"Is Active\"\n          },\n          \"is_superuser\": {\n            \"anyOf\": [\n              {\n                \"type\": \"boolean\"\n              },\n              {\n                \"type\": \"null\"\n              }\n            ],\n            \"title\": \"Is Superuser\"\n          },\n          \"is_verified\": {\n            \"anyOf\": [\n              {\n                \"type\": \"boolean\"\n              },\n              {\n                \"type\": \"null\"\n              }\n            ],\n            \"title\": \"Is Verified\"\n          }\n        },\n        \"type\": \"object\",\n        \"title\": \"UserUpdate\"\n      },\n      \"ValidationError\": {\n        \"properties\": {\n          \"loc\": {\n            \"items\": {\n              \"anyOf\": [\n                {\n                  \"type\": \"string\"\n                },\n                {\n                  \"type\": \"integer\"\n                }\n              ]\n            },\n            \"type\": \"array\",\n            \"title\": \"Location\"\n          },\n          \"msg\": {\n            \"type\": \"string\",\n            \"title\": \"Message\"\n          },\n          \"type\": {\n            \"type\": \"string\",\n            \"title\": \"Error Type\"\n          }\n        },\n        \"type\": \"object\",\n        \"required\": [\n          \"loc\",\n          \"msg\",\n          \"type\"\n        ],\n        \"title\": \"ValidationError\"\n      },\n      \"login_post\": {\n        \"properties\": {\n          \"grant_type\": {\n            \"anyOf\": [\n              {\n                \"type\": \"string\",\n                \"pattern\": \"password\"\n              },\n              {\n                \"type\": \"null\"\n              }\n            ],\n            \"title\": \"Grant Type\"\n          },\n          \"username\": {\n            \"type\": \"string\",\n            \"title\": \"Username\"\n          },\n          \"password\": {\n            \"type\": \"string\",\n            \"title\": \"Password\"\n          },\n          \"scope\": {\n            \"type\": \"string\",\n            \"title\": \"Scope\",\n            \"default\": \"\"\n          },\n          \"client_id\": {\n            \"anyOf\": [\n              {\n                \"type\": \"string\"\n              },\n              {\n                \"type\": \"null\"\n              }\n            ],\n            \"title\": \"Client Id\"\n          },\n          \"client_secret\": {\n            \"anyOf\": [\n              {\n                \"type\": \"string\"\n              },\n              {\n                \"type\": \"null\"\n              }\n            ],\n            \"title\": \"Client Secret\"\n          }\n        },\n        \"type\": \"object\",\n        \"required\": [\n          \"username\",\n          \"password\"\n        ],\n        \"title\": \"Body_auth-auth:jwt.login_post\"\n      }\n    },\n    \"securitySchemes\": {\n      \"OAuth2PasswordBearer\": {\n        \"type\": \"oauth2\",\n        \"flows\": {\n          \"password\": {\n            \"scopes\": {},\n            \"tokenUrl\": \"auth/jwt/login\"\n          }\n        }\n      }\n    }\n  }\n}"
  },
  {
    "path": "fastapi_backend/tests/commands/test_generate_openapi_schema.py",
    "content": "import json\nimport os\nimport pytest\nfrom pathlib import Path\n\nfrom commands.generate_openapi_schema import (\n    generate_openapi_schema,\n    remove_operation_id_tag,\n)\n\n\ndef load_json_file(filename):\n    test_dir = os.path.dirname(__file__)\n    file_path = os.path.join(test_dir, \"files\", filename)\n    with open(file_path, \"r\") as f:\n        return json.load(f)\n\n\n@pytest.fixture\ndef sample_openapi_schema():\n    return load_json_file(\"openapi_test.json\")\n\n\n@pytest.fixture\ndef expected_output_schema():\n    return load_json_file(\"openapi_test_output.json\")\n\n\ndef test_remove_operation_id_tag(sample_openapi_schema, expected_output_schema):\n    cleaned_schema = remove_operation_id_tag(sample_openapi_schema)\n    assert cleaned_schema == expected_output_schema\n\n\n@pytest.fixture\ndef mock_app(mocker):\n    app = mocker.patch(\"commands.generate_openapi_schema.app\")\n    app.openapi.return_value = {\n        \"openapi\": \"3.1.0\",\n        \"info\": {\"title\": \"FastAPI\", \"version\": \"0.1.0\"},\n        \"paths\": {},\n    }\n    return app\n\n\ndef test_generate_openapi_schema(mocker, mock_app):\n    mock_remove_operation_id_tag = mocker.patch(\n        \"commands.generate_openapi_schema.remove_operation_id_tag\"\n    )\n    mock_remove_operation_id_tag.return_value = {\"mocked_schema\": True}\n\n    output_file = \"openapi_test.json\"\n    expected_output = json.dumps({\"mocked_schema\": True}, indent=2)\n\n    generate_openapi_schema(output_file)\n\n    mock_app.openapi.assert_called_once()\n    mock_remove_operation_id_tag.assert_called_once_with(mock_app.openapi.return_value)\n\n    output_path = Path(output_file)\n    assert output_path.is_file()\n    with open(output_file, \"r\") as f:\n        content = f.read()\n        assert content == expected_output\n\n    output_path.unlink()\n"
  },
  {
    "path": "fastapi_backend/tests/conftest.py",
    "content": "from httpx import AsyncClient, ASGITransport\nimport pytest_asyncio\nfrom sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine\nfrom fastapi_users.db import SQLAlchemyUserDatabase\nfrom fastapi_users.password import PasswordHelper\nimport uuid\n\nfrom app.config import settings\nfrom app.models import User, Base\n\nfrom app.database import get_user_db, get_async_session\nfrom app.main import app\nfrom app.users import get_jwt_strategy\n\n\n@pytest_asyncio.fixture(scope=\"function\")\nasync def engine():\n    \"\"\"Create a fresh test database engine for each test function.\"\"\"\n    engine = create_async_engine(settings.TEST_DATABASE_URL, echo=True)\n\n    async with engine.begin() as conn:\n        await conn.run_sync(Base.metadata.create_all)\n\n    yield engine\n\n    async with engine.begin() as conn:\n        await conn.run_sync(Base.metadata.drop_all)\n\n    await engine.dispose()\n\n\n@pytest_asyncio.fixture(scope=\"function\")\nasync def db_session(engine):\n    \"\"\"Create a fresh database session for each test.\"\"\"\n    async_session_maker = async_sessionmaker(\n        engine, class_=AsyncSession, expire_on_commit=False\n    )\n\n    async with async_session_maker() as session:\n        yield session\n        await session.rollback()\n        await session.close()\n\n\n@pytest_asyncio.fixture(scope=\"function\")\nasync def test_client(db_session):\n    \"\"\"Fixture to create a test client that uses the test database session.\"\"\"\n\n    # FastAPI-Users database override (wraps session with user operation helpers)\n    async def override_get_user_db():\n        session = SQLAlchemyUserDatabase(db_session, User)\n        try:\n            yield session\n        finally:\n            await db_session.close()\n\n    # General database override (raw session access)\n    async def override_get_async_session():\n        try:\n            yield db_session\n        finally:\n            await db_session.close()\n\n    # Set up test database overrides\n    app.dependency_overrides[get_user_db] = override_get_user_db\n    app.dependency_overrides[get_async_session] = override_get_async_session\n\n    async with AsyncClient(\n        transport=ASGITransport(app=app), base_url=\"http://localhost:8000\"\n    ) as client:\n        yield client\n\n\n@pytest_asyncio.fixture(scope=\"function\")\nasync def authenticated_user(test_client, db_session):\n    \"\"\"Fixture to create and authenticate a test user directly in the database.\"\"\"\n\n    # Create user data\n    user_data = {\n        \"id\": uuid.uuid4(),\n        \"email\": \"test@example.com\",\n        \"hashed_password\": PasswordHelper().hash(\"TestPassword123#\"),\n        \"is_active\": True,\n        \"is_superuser\": False,\n        \"is_verified\": True,\n    }\n\n    # Create user directly in database\n    user = User(**user_data)\n    db_session.add(user)\n    await db_session.commit()\n    await db_session.refresh(user)\n\n    # Generate token using the strategy directly\n    strategy = get_jwt_strategy()\n    access_token = await strategy.write_token(user)\n\n    # Return both the headers and the user data\n    return {\n        \"headers\": {\"Authorization\": f\"Bearer {access_token}\"},\n        \"user\": user,\n        \"user_data\": {\"email\": user_data[\"email\"], \"password\": \"TestPassword123#\"},\n    }\n"
  },
  {
    "path": "fastapi_backend/tests/main/__init__.py",
    "content": ""
  },
  {
    "path": "fastapi_backend/tests/main/test_main.py",
    "content": "import pytest\nfrom fastapi import status\nfrom fastapi_users.router import ErrorCode\nfrom sqlalchemy import select\nfrom app.models import User\n\n\nclass TestPasswordValidation:\n    @pytest.mark.parametrize(\n        \"email, password, expected_status, expected_detail\",\n        [\n            (\n                \"test@example.com\",\n                \"short\",\n                status.HTTP_400_BAD_REQUEST,\n                {\n                    \"detail\": {\n                        \"code\": ErrorCode.REGISTER_INVALID_PASSWORD.value,\n                        \"reason\": [\"Password should be at least 8 characters.\"],\n                    }\n                },\n            ),\n            (\n                \"test@example.com\",\n                \"test@example.com\",\n                status.HTTP_400_BAD_REQUEST,\n                {\n                    \"detail\": {\n                        \"code\": ErrorCode.REGISTER_INVALID_PASSWORD.value,\n                        \"reason\": [\"Password should not contain e-mail.\"],\n                    }\n                },\n            ),\n            (\n                \"test@example.com\",\n                \"lowercasepassword\",\n                status.HTTP_400_BAD_REQUEST,\n                {\n                    \"detail\": {\n                        \"code\": ErrorCode.REGISTER_INVALID_PASSWORD.value,\n                        \"reason\": [\n                            \"Password should contain at least one uppercase letter.\"\n                        ],\n                    }\n                },\n            ),\n            (\n                \"test@example.com\",\n                \"Nosppecialchar1\",\n                status.HTTP_400_BAD_REQUEST,\n                {\n                    \"detail\": {\n                        \"code\": ErrorCode.REGISTER_INVALID_PASSWORD.value,\n                        \"reason\": [\n                            \"Password should contain at least one special character.\"\n                        ],\n                    }\n                },\n            ),\n            (\n                \"test@example.com\",\n                \"shorttest\",\n                status.HTTP_400_BAD_REQUEST,\n                {\n                    \"detail\": {\n                        \"code\": ErrorCode.REGISTER_INVALID_PASSWORD.value,\n                        \"reason\": [\n                            \"Password should be at least 8 characters.\",\n                            \"Password should contain at least one uppercase letter.\",\n                            \"Password should contain at least one special character.\",\n                        ],\n                    }\n                },\n            ),\n        ],\n    )\n    @pytest.mark.asyncio(loop_scope=\"function\")\n    async def test_password_validation(\n        self, test_client, email, password, expected_status, expected_detail\n    ):\n        \"\"\"Test user registration with password validation.\"\"\"\n        json = {\"email\": email, \"password\": password}\n        response = await test_client.post(\"/auth/register\", json=json)\n\n        assert response.status_code == expected_status\n\n    @pytest.mark.asyncio(loop_scope=\"function\")\n    async def test_register_user_with_valid_password(self, test_client, db_session):\n        \"\"\"Test user registration with success\"\"\"\n        json = {\n            \"email\": \"user@1.com\",\n            \"password\": \"Sppecialchar1#\",\n        }\n        response = await test_client.post(\"/auth/register\", json=json)\n\n        row = await db_session.execute(select(User))\n\n        user = row.scalars().first()\n\n        assert response.status_code == status.HTTP_201_CREATED\n        assert user is not None\n        assert user.email == \"user@1.com\"\n"
  },
  {
    "path": "fastapi_backend/tests/routes/__init__.py",
    "content": ""
  },
  {
    "path": "fastapi_backend/tests/routes/test_items.py",
    "content": "import pytest\nfrom fastapi import status\nfrom sqlalchemy import select, insert\nfrom app.models import Item\n\n\nclass TestItems:\n    @pytest.mark.asyncio(loop_scope=\"function\")\n    async def test_create_item(self, test_client, db_session, authenticated_user):\n        \"\"\"Test creating an item.\"\"\"\n        item_data = {\"name\": \"Test Item\", \"description\": \"Test Description\"}\n        create_response = await test_client.post(\n            \"/items/\", json=item_data, headers=authenticated_user[\"headers\"]\n        )\n\n        assert create_response.status_code == status.HTTP_200_OK\n        created_item = create_response.json()\n        assert created_item[\"name\"] == item_data[\"name\"]\n        assert created_item[\"description\"] == item_data[\"description\"]\n\n        # Check if the item is in the database\n        item = await db_session.execute(\n            select(Item).where(Item.id == created_item[\"id\"])\n        )\n        item = item.scalar()\n\n        assert item is not None\n        assert item.name == item_data[\"name\"]\n        assert item.description == item_data[\"description\"]\n\n    @pytest.mark.asyncio(loop_scope=\"function\")\n    async def test_read_items(self, test_client, db_session, authenticated_user):\n        \"\"\"Test reading items.\"\"\"\n        # Create multiple items\n        items_data = [\n            {\n                \"name\": \"First Item\",\n                \"description\": \"First Description\",\n                \"user_id\": authenticated_user[\"user\"].id,\n            },\n            {\n                \"name\": \"Second Item\",\n                \"description\": \"Second Description\",\n                \"user_id\": authenticated_user[\"user\"].id,\n            },\n        ]\n        # create items in the database\n        for item_data in items_data:\n            await db_session.execute(insert(Item).values(**item_data))\n\n        await db_session.commit()  # Add commit to ensure items are saved\n\n        # Read items - test pagination response\n        read_response = await test_client.get(\n            \"/items/\", headers=authenticated_user[\"headers\"]\n        )\n        assert read_response.status_code == status.HTTP_200_OK\n        response_data = read_response.json()\n\n        # Check pagination structure\n        assert \"items\" in response_data\n        assert \"total\" in response_data\n        assert \"page\" in response_data\n        assert \"size\" in response_data\n\n        items = response_data[\"items\"]\n\n        # Filter items created in this test (to avoid interference from other tests)\n        test_items = [\n            item for item in items if item[\"name\"] in [\"First Item\", \"Second Item\"]\n        ]\n\n        assert len(test_items) == 2\n        assert any(item[\"name\"] == \"First Item\" for item in test_items)\n        assert any(item[\"name\"] == \"Second Item\" for item in test_items)\n\n    @pytest.mark.asyncio(loop_scope=\"function\")\n    async def test_delete_item(self, test_client, db_session, authenticated_user):\n        \"\"\"Test deleting an item.\"\"\"\n        # Create an item directly in the database\n        item_data = {\n            \"name\": \"Item to Delete\",\n            \"description\": \"Will be deleted\",\n            \"user_id\": authenticated_user[\"user\"].id,\n        }\n        await db_session.execute(insert(Item).values(**item_data))\n\n        # Get the created item from database\n        db_item = (\n            await db_session.execute(select(Item).where(Item.name == item_data[\"name\"]))\n        ).scalar()\n\n        # Delete the item\n        delete_response = await test_client.delete(\n            f\"/items/{db_item.id}\", headers=authenticated_user[\"headers\"]\n        )\n        assert delete_response.status_code == status.HTTP_200_OK\n\n        # Verify item is deleted from database\n        db_check = (\n            await db_session.execute(select(Item).where(Item.id == db_item.id))\n        ).scalar()\n        assert db_check is None\n\n    @pytest.mark.asyncio(loop_scope=\"function\")\n    async def test_delete_nonexistent_item(self, test_client, authenticated_user):\n        \"\"\"Test deleting an item that doesn't exist.\"\"\"\n        # Try to delete non-existent item\n        delete_response = await test_client.delete(\n            \"/items/00000000-0000-0000-0000-000000000000\",\n            headers=authenticated_user[\"headers\"],\n        )\n        assert delete_response.status_code == status.HTTP_404_NOT_FOUND\n\n    @pytest.mark.asyncio(loop_scope=\"function\")\n    async def test_unauthorized_read_items(self, test_client):\n        \"\"\"Test reading items without authentication.\"\"\"\n        response = await test_client.get(\"/items/\")\n        assert response.status_code == status.HTTP_401_UNAUTHORIZED\n\n    @pytest.mark.asyncio(loop_scope=\"function\")\n    async def test_unauthorized_create_item(self, test_client):\n        \"\"\"Test creating item without authentication.\"\"\"\n        item_data = {\"name\": \"Unauthorized Item\", \"description\": \"Should fail\"}\n        response = await test_client.post(\"/items/\", json=item_data)\n        assert response.status_code == status.HTTP_401_UNAUTHORIZED\n\n    @pytest.mark.asyncio(loop_scope=\"function\")\n    async def test_unauthorized_delete_item(self, test_client):\n        \"\"\"Test deleting item without authentication.\"\"\"\n        response = await test_client.delete(\n            \"/items/00000000-0000-0000-0000-000000000000\"\n        )\n        assert response.status_code == status.HTTP_401_UNAUTHORIZED\n"
  },
  {
    "path": "fastapi_backend/tests/test_database.py",
    "content": "import pytest\nfrom sqlalchemy.ext.asyncio import AsyncSession, AsyncEngine\nfrom fastapi_users.db import SQLAlchemyUserDatabase\n\nfrom app.database import (\n    async_session_maker,\n    create_db_and_tables,\n    get_async_session,\n    get_user_db,\n)\nfrom app.models import Base, User\n\n\n@pytest.fixture\nasync def mock_engine(mocker):\n    # Mock the engine\n    mock_engine = mocker.AsyncMock(spec=AsyncEngine)\n\n    # Create a mock connection\n    mock_conn = mocker.AsyncMock()\n    mock_conn.run_sync = mocker.AsyncMock()\n\n    # Set up the context manager properly\n    mock_context = mocker.AsyncMock()\n    mock_context.__aenter__.return_value = mock_conn\n    mock_engine.begin.return_value = mock_context\n\n    return mock_engine\n\n\n@pytest.fixture\nasync def mock_session(mocker):\n    # Create a mock session\n    mock_session = mocker.AsyncMock(spec=AsyncSession)\n\n    # Mock the session context manager\n    mock_session.__aenter__.return_value = mock_session\n    mock_session.__aexit__.return_value = None\n\n    # Mock the session maker\n    mock_session_maker = mocker.patch(\"app.database.async_session_maker\")\n    mock_session_maker.return_value = mock_session\n\n    return mock_session\n\n\n@pytest.mark.asyncio\nasync def test_create_db_and_tables(mock_engine, mocker):\n    # Replace the real engine with our mock\n    mocker.patch(\"app.database.engine\", mock_engine)\n\n    await create_db_and_tables()\n\n    # Verify that begin was called\n    mock_engine.begin.assert_called_once()\n\n    # Verify that create_all was called\n    mock_conn = mock_engine.begin.return_value.__aenter__.return_value\n    mock_conn.run_sync.assert_called_once_with(Base.metadata.create_all)\n\n\n@pytest.mark.asyncio\nasync def test_get_async_session(mock_session):\n    # Test the session generator\n    session_generator = get_async_session()\n    session = await session_generator.__anext__()\n\n    # Verify we got the mock session\n    assert session == mock_session\n\n    # Verify the session was created with the expected context\n    mock_session.__aenter__.assert_called_once()\n\n\n@pytest.mark.asyncio\nasync def test_get_user_db(mock_session):\n    # Test the user db generator\n    user_db_generator = get_user_db(mock_session)\n    user_db = await user_db_generator.__anext__()\n\n    # Verify we got a SQLAlchemyUserDatabase instance\n    assert isinstance(user_db, SQLAlchemyUserDatabase)\n    assert user_db.session == mock_session\n    # Verify the model class is correct\n    assert user_db.user_table == User\n\n\ndef test_engine_creation(mocker):\n    # Mock settings\n    mock_settings = mocker.patch(\"app.database.settings\")\n    mock_settings.DATABASE_URL = \"sqlite+aiosqlite:///./test.db\"\n    mock_settings.EXPIRE_ON_COMMIT = False\n\n    # Import engine to trigger creation with mocked settings\n    from app.database import engine, async_session_maker\n\n    # Verify engine is created\n    assert isinstance(engine, AsyncEngine)\n\n    # Verify session maker is configured\n    assert async_session_maker.kw[\"expire_on_commit\"] is False\n\n\n@pytest.mark.asyncio\nasync def test_session_maker_configuration():\n    # Create a test session\n    async with async_session_maker() as session:\n        assert isinstance(session, AsyncSession)\n"
  },
  {
    "path": "fastapi_backend/tests/test_email.py",
    "content": "import pytest\nfrom pathlib import Path\nfrom fastapi_mail import ConnectionConfig, MessageSchema\nfrom app.email import get_email_config, send_reset_password_email\nfrom app.models import User\n\n\n@pytest.fixture\ndef mock_settings(mocker):\n    mock = mocker.patch(\"app.email.settings\")\n    # Set up mock settings with test values\n    mock.MAIL_USERNAME = \"test_user\"\n    mock.MAIL_PASSWORD = \"test_pass\"\n    mock.MAIL_FROM = \"test@example.com\"\n    mock.MAIL_PORT = 587\n    mock.MAIL_SERVER = \"smtp.test.com\"\n    mock.MAIL_FROM_NAME = \"Test Sender\"\n    mock.MAIL_STARTTLS = True\n    mock.MAIL_SSL_TLS = False\n    mock.USE_CREDENTIALS = True\n    mock.VALIDATE_CERTS = True\n    mock.TEMPLATE_DIR = \"email_templates\"\n    mock.FRONTEND_URL = \"http://test-frontend.com\"\n    return mock\n\n\n@pytest.fixture\ndef mock_user():\n    return User(\n        email=\"user@example.com\",\n    )\n\n\ndef test_get_email_config(mock_settings):\n    config = get_email_config()\n\n    assert isinstance(config, ConnectionConfig)\n    assert config.MAIL_USERNAME == \"test_user\"\n    assert config.MAIL_PASSWORD == \"test_pass\"\n    assert config.MAIL_FROM == \"test@example.com\"\n    assert config.MAIL_PORT == 587\n    assert config.MAIL_SERVER == \"smtp.test.com\"\n    assert config.MAIL_FROM_NAME == \"Test Sender\"\n    assert config.MAIL_STARTTLS\n    assert not config.MAIL_SSL_TLS\n    assert config.USE_CREDENTIALS\n    assert config.VALIDATE_CERTS\n    assert isinstance(config.TEMPLATE_FOLDER, Path)\n\n\n@pytest.mark.asyncio\nasync def test_send_reset_password_email(mock_settings, mock_user, mocker):\n    # Mock FastMail\n    mock_fastmail = mocker.patch(\"app.email.FastMail\")\n    mock_fastmail_instance = mock_fastmail.return_value\n    mock_fastmail_instance.send_message = mocker.AsyncMock()\n\n    # Test data\n    test_token = \"test-token-123\"\n\n    # Call the function\n    await send_reset_password_email(mock_user, test_token)\n\n    # Verify FastMail was instantiated with correct config\n    mock_fastmail.assert_called_once()\n    config_arg = mock_fastmail.call_args[0][0]\n    assert isinstance(config_arg, ConnectionConfig)\n\n    # Verify send_message was called\n    mock_fastmail_instance.send_message.assert_called_once()\n\n    # Verify the message schema\n    message_arg = mock_fastmail_instance.send_message.call_args[0][0]\n    assert isinstance(message_arg, MessageSchema)\n    assert message_arg.subject == \"Password recovery\"\n    assert message_arg.recipients == [mock_user.email]\n\n    # Verify template body contains correct data\n    expected_link = (\n        f\"http://test-frontend.com/password-recovery/confirm?token={test_token}\"\n    )\n    assert message_arg.template_body == {\n        \"username\": mock_user.email,\n        \"link\": expected_link,\n    }\n\n    # Verify template name\n    template_name = mock_fastmail_instance.send_message.call_args[1][\"template_name\"]\n    assert template_name == \"password_reset.html\"\n"
  },
  {
    "path": "fastapi_backend/tests/utils/__init__.py",
    "content": ""
  },
  {
    "path": "fastapi_backend/tests/utils/test_utils.py",
    "content": "from fastapi.routing import APIRoute\nfrom app.utils import simple_generate_unique_route_id\n\n\ndef test_simple_generate_unique_route_id(mocker):\n    mock_route = mocker.Mock(spec=APIRoute)\n\n    mock_route.tags = [\"auth\"]\n    mock_route.name = \"authenticate_user\"\n\n    unique_id = simple_generate_unique_route_id(mock_route)\n\n    assert unique_id == \"auth-authenticate_user\"\n"
  },
  {
    "path": "fastapi_backend/vercel.json",
    "content": "{\n  \"buildCommand\": \"python3 -m venv venv && . venv/bin/activate && pip install -r requirements.txt && alembic upgrade head && deactivate && rm -rf venv\",\n  \"outputDirectory\": \"api\",\n  \"git\": {\n    \"deploymentEnabled\": {\n      \"main\": false\n    }\n  },\n  \"routes\": [\n    {\n      \"src\": \"/(.*)\",\n      \"dest\": \"api/index.py\"\n    }\n  ]\n}"
  },
  {
    "path": "fastapi_backend/vercel.prod.json",
    "content": "{\n  \"routes\": [\n    {\n      \"src\": \"/(.*)\",\n      \"dest\": \"api/index.py\"\n    }\n  ]\n}"
  },
  {
    "path": "fastapi_backend/watcher.py",
    "content": "import time\nimport re\nimport subprocess\nimport os\nfrom watchdog.observers import Observer\nfrom watchdog.events import FileSystemEventHandler\nfrom threading import Timer\n\n# Updated regex to include main.py, schemas.py, and all .py files in app/routes\nWATCHER_REGEX_PATTERN = re.compile(r\"(main\\.py|schemas\\.py|routes/.*\\.py)$\")\nAPP_PATH = \"app\"\n\n\nclass MyHandler(FileSystemEventHandler):\n    def __init__(self):\n        super().__init__()\n        self.debounce_timer = None\n        self.last_modified = 0\n\n    def on_modified(self, event):\n        if not event.is_directory and WATCHER_REGEX_PATTERN.search(\n            os.path.relpath(event.src_path, APP_PATH)\n        ):\n            current_time = time.time()\n            if current_time - self.last_modified > 1:\n                self.last_modified = current_time\n                if self.debounce_timer:\n                    self.debounce_timer.cancel()\n                self.debounce_timer = Timer(1.0, self.execute_command, [event.src_path])\n                self.debounce_timer.start()\n\n    def execute_command(self, file_path):\n        print(f\"File {file_path} has been modified and saved.\")\n        self.run_mypy_checks()\n        self.run_openapi_schema_generation()\n\n    def run_mypy_checks(self):\n        \"\"\"Run mypy type checks and print output.\"\"\"\n        print(\"Running mypy type checks...\")\n        result = subprocess.run(\n            [\"uv\", \"run\", \"mypy\", \"app\"],\n            capture_output=True,\n            text=True,\n            check=False,\n        )\n        print(result.stdout, result.stderr, sep=\"\\n\")\n        print(\n            \"Type errors detected! We recommend checking the mypy output for \"\n            \"more information on the issues.\"\n            if result.returncode\n            else \"No type errors detected.\"\n        )\n\n    def run_openapi_schema_generation(self):\n        \"\"\"Run the OpenAPI schema generation command.\"\"\"\n        print(\"Proceeding with OpenAPI schema generation...\")\n        try:\n            subprocess.run(\n                [\n                    \"uv\",\n                    \"run\",\n                    \"python\",\n                    \"-m\",\n                    \"commands.generate_openapi_schema\",\n                ],\n                check=True,\n            )\n            print(\"OpenAPI schema generation completed successfully.\")\n        except subprocess.CalledProcessError as e:\n            print(f\"An error occurred while generating OpenAPI schema: {e}\")\n\n\nif __name__ == \"__main__\":\n    observer = Observer()\n    observer.schedule(MyHandler(), APP_PATH, recursive=True)\n    observer.start()\n    try:\n        while True:\n            time.sleep(1)\n    except KeyboardInterrupt:\n        observer.stop()\n    observer.join()\n"
  },
  {
    "path": "local-shared-data/openapi.json",
    "content": "{\n  \"openapi\": \"3.1.0\",\n  \"info\": {\n    \"title\": \"FastAPI\",\n    \"version\": \"0.1.0\"\n  },\n  \"paths\": {\n    \"/auth/jwt/login\": {\n      \"post\": {\n        \"tags\": [\n          \"auth\"\n        ],\n        \"summary\": \"Auth:Jwt.Login\",\n        \"operationId\": \"auth:jwt.login\",\n        \"requestBody\": {\n          \"content\": {\n            \"application/x-www-form-urlencoded\": {\n              \"schema\": {\n                \"$ref\": \"#/components/schemas/login\"\n              }\n            }\n          },\n          \"required\": true\n        },\n        \"responses\": {\n          \"200\": {\n            \"description\": \"Successful Response\",\n            \"content\": {\n              \"application/json\": {\n                \"schema\": {\n                  \"$ref\": \"#/components/schemas/BearerResponse\"\n                },\n                \"example\": {\n                  \"access_token\": \"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiOTIyMWZmYzktNjQwZi00MzcyLTg2ZDMtY2U2NDJjYmE1NjAzIiwiYXVkIjoiZmFzdGFwaS11c2VyczphdXRoIiwiZXhwIjoxNTcxNTA0MTkzfQ.M10bjOe45I5Ncu_uXvOmVV8QxnL-nZfcH96U90JaocI\",\n                  \"token_type\": \"bearer\"\n                }\n              }\n            }\n          },\n          \"400\": {\n            \"description\": \"Bad Request\",\n            \"content\": {\n              \"application/json\": {\n                \"schema\": {\n                  \"$ref\": \"#/components/schemas/ErrorModel\"\n                },\n                \"examples\": {\n                  \"LOGIN_BAD_CREDENTIALS\": {\n                    \"summary\": \"Bad credentials or the user is inactive.\",\n                    \"value\": {\n                      \"detail\": \"LOGIN_BAD_CREDENTIALS\"\n                    }\n                  },\n                  \"LOGIN_USER_NOT_VERIFIED\": {\n                    \"summary\": \"The user is not verified.\",\n                    \"value\": {\n                      \"detail\": \"LOGIN_USER_NOT_VERIFIED\"\n                    }\n                  }\n                }\n              }\n            }\n          },\n          \"422\": {\n            \"description\": \"Validation Error\",\n            \"content\": {\n              \"application/json\": {\n                \"schema\": {\n                  \"$ref\": \"#/components/schemas/HTTPValidationError\"\n                }\n              }\n            }\n          }\n        }\n      }\n    },\n    \"/auth/jwt/logout\": {\n      \"post\": {\n        \"tags\": [\n          \"auth\"\n        ],\n        \"summary\": \"Auth:Jwt.Logout\",\n        \"operationId\": \"auth:jwt.logout\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"Successful Response\",\n            \"content\": {\n              \"application/json\": {\n                \"schema\": {}\n              }\n            }\n          },\n          \"401\": {\n            \"description\": \"Missing token or inactive user.\"\n          }\n        },\n        \"security\": [\n          {\n            \"OAuth2PasswordBearer\": []\n          }\n        ]\n      }\n    },\n    \"/auth/register\": {\n      \"post\": {\n        \"tags\": [\n          \"auth\"\n        ],\n        \"summary\": \"Register:Register\",\n        \"operationId\": \"register:register\",\n        \"requestBody\": {\n          \"content\": {\n            \"application/json\": {\n              \"schema\": {\n                \"$ref\": \"#/components/schemas/UserCreate\"\n              }\n            }\n          },\n          \"required\": true\n        },\n        \"responses\": {\n          \"201\": {\n            \"description\": \"Successful Response\",\n            \"content\": {\n              \"application/json\": {\n                \"schema\": {\n                  \"$ref\": \"#/components/schemas/UserRead\"\n                }\n              }\n            }\n          },\n          \"400\": {\n            \"description\": \"Bad Request\",\n            \"content\": {\n              \"application/json\": {\n                \"schema\": {\n                  \"$ref\": \"#/components/schemas/ErrorModel\"\n                },\n                \"examples\": {\n                  \"REGISTER_USER_ALREADY_EXISTS\": {\n                    \"summary\": \"A user with this email already exists.\",\n                    \"value\": {\n                      \"detail\": \"REGISTER_USER_ALREADY_EXISTS\"\n                    }\n                  },\n                  \"REGISTER_INVALID_PASSWORD\": {\n                    \"summary\": \"Password validation failed.\",\n                    \"value\": {\n                      \"detail\": {\n                        \"code\": \"REGISTER_INVALID_PASSWORD\",\n                        \"reason\": \"Password should beat least 3 characters\"\n                      }\n                    }\n                  }\n                }\n              }\n            }\n          },\n          \"422\": {\n            \"description\": \"Validation Error\",\n            \"content\": {\n              \"application/json\": {\n                \"schema\": {\n                  \"$ref\": \"#/components/schemas/HTTPValidationError\"\n                }\n              }\n            }\n          }\n        }\n      }\n    },\n    \"/auth/forgot-password\": {\n      \"post\": {\n        \"tags\": [\n          \"auth\"\n        ],\n        \"summary\": \"Reset:Forgot Password\",\n        \"operationId\": \"reset:forgot_password\",\n        \"requestBody\": {\n          \"content\": {\n            \"application/json\": {\n              \"schema\": {\n                \"$ref\": \"#/components/schemas/Body_auth-reset_forgot_password\"\n              }\n            }\n          },\n          \"required\": true\n        },\n        \"responses\": {\n          \"202\": {\n            \"description\": \"Successful Response\",\n            \"content\": {\n              \"application/json\": {\n                \"schema\": {}\n              }\n            }\n          },\n          \"422\": {\n            \"description\": \"Validation Error\",\n            \"content\": {\n              \"application/json\": {\n                \"schema\": {\n                  \"$ref\": \"#/components/schemas/HTTPValidationError\"\n                }\n              }\n            }\n          }\n        }\n      }\n    },\n    \"/auth/reset-password\": {\n      \"post\": {\n        \"tags\": [\n          \"auth\"\n        ],\n        \"summary\": \"Reset:Reset Password\",\n        \"operationId\": \"reset:reset_password\",\n        \"requestBody\": {\n          \"content\": {\n            \"application/json\": {\n              \"schema\": {\n                \"$ref\": \"#/components/schemas/Body_auth-reset_reset_password\"\n              }\n            }\n          },\n          \"required\": true\n        },\n        \"responses\": {\n          \"200\": {\n            \"description\": \"Successful Response\",\n            \"content\": {\n              \"application/json\": {\n                \"schema\": {}\n              }\n            }\n          },\n          \"400\": {\n            \"description\": \"Bad Request\",\n            \"content\": {\n              \"application/json\": {\n                \"schema\": {\n                  \"$ref\": \"#/components/schemas/ErrorModel\"\n                },\n                \"examples\": {\n                  \"RESET_PASSWORD_BAD_TOKEN\": {\n                    \"summary\": \"Bad or expired token.\",\n                    \"value\": {\n                      \"detail\": \"RESET_PASSWORD_BAD_TOKEN\"\n                    }\n                  },\n                  \"RESET_PASSWORD_INVALID_PASSWORD\": {\n                    \"summary\": \"Password validation failed.\",\n                    \"value\": {\n                      \"detail\": {\n                        \"code\": \"RESET_PASSWORD_INVALID_PASSWORD\",\n                        \"reason\": \"Password should be at least 3 characters\"\n                      }\n                    }\n                  }\n                }\n              }\n            }\n          },\n          \"422\": {\n            \"description\": \"Validation Error\",\n            \"content\": {\n              \"application/json\": {\n                \"schema\": {\n                  \"$ref\": \"#/components/schemas/HTTPValidationError\"\n                }\n              }\n            }\n          }\n        }\n      }\n    },\n    \"/auth/request-verify-token\": {\n      \"post\": {\n        \"tags\": [\n          \"auth\"\n        ],\n        \"summary\": \"Verify:Request-Token\",\n        \"operationId\": \"verify:request-token\",\n        \"requestBody\": {\n          \"content\": {\n            \"application/json\": {\n              \"schema\": {\n                \"$ref\": \"#/components/schemas/Body_auth-verify_request-token\"\n              }\n            }\n          },\n          \"required\": true\n        },\n        \"responses\": {\n          \"202\": {\n            \"description\": \"Successful Response\",\n            \"content\": {\n              \"application/json\": {\n                \"schema\": {}\n              }\n            }\n          },\n          \"422\": {\n            \"description\": \"Validation Error\",\n            \"content\": {\n              \"application/json\": {\n                \"schema\": {\n                  \"$ref\": \"#/components/schemas/HTTPValidationError\"\n                }\n              }\n            }\n          }\n        }\n      }\n    },\n    \"/auth/verify\": {\n      \"post\": {\n        \"tags\": [\n          \"auth\"\n        ],\n        \"summary\": \"Verify:Verify\",\n        \"operationId\": \"verify:verify\",\n        \"requestBody\": {\n          \"content\": {\n            \"application/json\": {\n              \"schema\": {\n                \"$ref\": \"#/components/schemas/Body_auth-verify_verify\"\n              }\n            }\n          },\n          \"required\": true\n        },\n        \"responses\": {\n          \"200\": {\n            \"description\": \"Successful Response\",\n            \"content\": {\n              \"application/json\": {\n                \"schema\": {\n                  \"$ref\": \"#/components/schemas/UserRead\"\n                }\n              }\n            }\n          },\n          \"400\": {\n            \"description\": \"Bad Request\",\n            \"content\": {\n              \"application/json\": {\n                \"schema\": {\n                  \"$ref\": \"#/components/schemas/ErrorModel\"\n                },\n                \"examples\": {\n                  \"VERIFY_USER_BAD_TOKEN\": {\n                    \"summary\": \"Bad token, not existing user ornot the e-mail currently set for the user.\",\n                    \"value\": {\n                      \"detail\": \"VERIFY_USER_BAD_TOKEN\"\n                    }\n                  },\n                  \"VERIFY_USER_ALREADY_VERIFIED\": {\n                    \"summary\": \"The user is already verified.\",\n                    \"value\": {\n                      \"detail\": \"VERIFY_USER_ALREADY_VERIFIED\"\n                    }\n                  }\n                }\n              }\n            }\n          },\n          \"422\": {\n            \"description\": \"Validation Error\",\n            \"content\": {\n              \"application/json\": {\n                \"schema\": {\n                  \"$ref\": \"#/components/schemas/HTTPValidationError\"\n                }\n              }\n            }\n          }\n        }\n      }\n    },\n    \"/users/me\": {\n      \"get\": {\n        \"tags\": [\n          \"users\"\n        ],\n        \"summary\": \"Users:Current User\",\n        \"operationId\": \"users:current_user\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"Successful Response\",\n            \"content\": {\n              \"application/json\": {\n                \"schema\": {\n                  \"$ref\": \"#/components/schemas/UserRead\"\n                }\n              }\n            }\n          },\n          \"401\": {\n            \"description\": \"Missing token or inactive user.\"\n          }\n        },\n        \"security\": [\n          {\n            \"OAuth2PasswordBearer\": []\n          }\n        ]\n      },\n      \"patch\": {\n        \"tags\": [\n          \"users\"\n        ],\n        \"summary\": \"Users:Patch Current User\",\n        \"operationId\": \"users:patch_current_user\",\n        \"requestBody\": {\n          \"content\": {\n            \"application/json\": {\n              \"schema\": {\n                \"$ref\": \"#/components/schemas/UserUpdate\"\n              }\n            }\n          },\n          \"required\": true\n        },\n        \"responses\": {\n          \"200\": {\n            \"description\": \"Successful Response\",\n            \"content\": {\n              \"application/json\": {\n                \"schema\": {\n                  \"$ref\": \"#/components/schemas/UserRead\"\n                }\n              }\n            }\n          },\n          \"401\": {\n            \"description\": \"Missing token or inactive user.\"\n          },\n          \"400\": {\n            \"description\": \"Bad Request\",\n            \"content\": {\n              \"application/json\": {\n                \"schema\": {\n                  \"$ref\": \"#/components/schemas/ErrorModel\"\n                },\n                \"examples\": {\n                  \"UPDATE_USER_EMAIL_ALREADY_EXISTS\": {\n                    \"summary\": \"A user with this email already exists.\",\n                    \"value\": {\n                      \"detail\": \"UPDATE_USER_EMAIL_ALREADY_EXISTS\"\n                    }\n                  },\n                  \"UPDATE_USER_INVALID_PASSWORD\": {\n                    \"summary\": \"Password validation failed.\",\n                    \"value\": {\n                      \"detail\": {\n                        \"code\": \"UPDATE_USER_INVALID_PASSWORD\",\n                        \"reason\": \"Password should beat least 3 characters\"\n                      }\n                    }\n                  }\n                }\n              }\n            }\n          },\n          \"422\": {\n            \"description\": \"Validation Error\",\n            \"content\": {\n              \"application/json\": {\n                \"schema\": {\n                  \"$ref\": \"#/components/schemas/HTTPValidationError\"\n                }\n              }\n            }\n          }\n        },\n        \"security\": [\n          {\n            \"OAuth2PasswordBearer\": []\n          }\n        ]\n      }\n    },\n    \"/users/{id}\": {\n      \"get\": {\n        \"tags\": [\n          \"users\"\n        ],\n        \"summary\": \"Users:User\",\n        \"operationId\": \"users:user\",\n        \"security\": [\n          {\n            \"OAuth2PasswordBearer\": []\n          }\n        ],\n        \"parameters\": [\n          {\n            \"name\": \"id\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"schema\": {\n              \"type\": \"string\",\n              \"title\": \"Id\"\n            }\n          }\n        ],\n        \"responses\": {\n          \"200\": {\n            \"description\": \"Successful Response\",\n            \"content\": {\n              \"application/json\": {\n                \"schema\": {\n                  \"$ref\": \"#/components/schemas/UserRead\"\n                }\n              }\n            }\n          },\n          \"401\": {\n            \"description\": \"Missing token or inactive user.\"\n          },\n          \"403\": {\n            \"description\": \"Not a superuser.\"\n          },\n          \"404\": {\n            \"description\": \"The user does not exist.\"\n          },\n          \"422\": {\n            \"description\": \"Validation Error\",\n            \"content\": {\n              \"application/json\": {\n                \"schema\": {\n                  \"$ref\": \"#/components/schemas/HTTPValidationError\"\n                }\n              }\n            }\n          }\n        }\n      },\n      \"patch\": {\n        \"tags\": [\n          \"users\"\n        ],\n        \"summary\": \"Users:Patch User\",\n        \"operationId\": \"users:patch_user\",\n        \"security\": [\n          {\n            \"OAuth2PasswordBearer\": []\n          }\n        ],\n        \"parameters\": [\n          {\n            \"name\": \"id\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"schema\": {\n              \"type\": \"string\",\n              \"title\": \"Id\"\n            }\n          }\n        ],\n        \"requestBody\": {\n          \"required\": true,\n          \"content\": {\n            \"application/json\": {\n              \"schema\": {\n                \"$ref\": \"#/components/schemas/UserUpdate\"\n              }\n            }\n          }\n        },\n        \"responses\": {\n          \"200\": {\n            \"description\": \"Successful Response\",\n            \"content\": {\n              \"application/json\": {\n                \"schema\": {\n                  \"$ref\": \"#/components/schemas/UserRead\"\n                }\n              }\n            }\n          },\n          \"401\": {\n            \"description\": \"Missing token or inactive user.\"\n          },\n          \"403\": {\n            \"description\": \"Not a superuser.\"\n          },\n          \"404\": {\n            \"description\": \"The user does not exist.\"\n          },\n          \"400\": {\n            \"content\": {\n              \"application/json\": {\n                \"examples\": {\n                  \"UPDATE_USER_EMAIL_ALREADY_EXISTS\": {\n                    \"summary\": \"A user with this email already exists.\",\n                    \"value\": {\n                      \"detail\": \"UPDATE_USER_EMAIL_ALREADY_EXISTS\"\n                    }\n                  },\n                  \"UPDATE_USER_INVALID_PASSWORD\": {\n                    \"summary\": \"Password validation failed.\",\n                    \"value\": {\n                      \"detail\": {\n                        \"code\": \"UPDATE_USER_INVALID_PASSWORD\",\n                        \"reason\": \"Password should beat least 3 characters\"\n                      }\n                    }\n                  }\n                },\n                \"schema\": {\n                  \"$ref\": \"#/components/schemas/ErrorModel\"\n                }\n              }\n            },\n            \"description\": \"Bad Request\"\n          },\n          \"422\": {\n            \"description\": \"Validation Error\",\n            \"content\": {\n              \"application/json\": {\n                \"schema\": {\n                  \"$ref\": \"#/components/schemas/HTTPValidationError\"\n                }\n              }\n            }\n          }\n        }\n      },\n      \"delete\": {\n        \"tags\": [\n          \"users\"\n        ],\n        \"summary\": \"Users:Delete User\",\n        \"operationId\": \"users:delete_user\",\n        \"security\": [\n          {\n            \"OAuth2PasswordBearer\": []\n          }\n        ],\n        \"parameters\": [\n          {\n            \"name\": \"id\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"schema\": {\n              \"type\": \"string\",\n              \"title\": \"Id\"\n            }\n          }\n        ],\n        \"responses\": {\n          \"204\": {\n            \"description\": \"Successful Response\"\n          },\n          \"401\": {\n            \"description\": \"Missing token or inactive user.\"\n          },\n          \"403\": {\n            \"description\": \"Not a superuser.\"\n          },\n          \"404\": {\n            \"description\": \"The user does not exist.\"\n          },\n          \"422\": {\n            \"description\": \"Validation Error\",\n            \"content\": {\n              \"application/json\": {\n                \"schema\": {\n                  \"$ref\": \"#/components/schemas/HTTPValidationError\"\n                }\n              }\n            }\n          }\n        }\n      }\n    },\n    \"/items/\": {\n      \"get\": {\n        \"tags\": [\n          \"item\"\n        ],\n        \"summary\": \"Read Item\",\n        \"operationId\": \"read_item\",\n        \"security\": [\n          {\n            \"OAuth2PasswordBearer\": []\n          }\n        ],\n        \"parameters\": [\n          {\n            \"name\": \"page\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"schema\": {\n              \"type\": \"integer\",\n              \"minimum\": 1,\n              \"description\": \"Page number\",\n              \"default\": 1,\n              \"title\": \"Page\"\n            },\n            \"description\": \"Page number\"\n          },\n          {\n            \"name\": \"size\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"schema\": {\n              \"type\": \"integer\",\n              \"maximum\": 100,\n              \"minimum\": 1,\n              \"description\": \"Page size\",\n              \"default\": 50,\n              \"title\": \"Size\"\n            },\n            \"description\": \"Page size\"\n          }\n        ],\n        \"responses\": {\n          \"200\": {\n            \"description\": \"Successful Response\",\n            \"content\": {\n              \"application/json\": {\n                \"schema\": {\n                  \"$ref\": \"#/components/schemas/Page_ItemRead_\"\n                }\n              }\n            }\n          },\n          \"422\": {\n            \"description\": \"Validation Error\",\n            \"content\": {\n              \"application/json\": {\n                \"schema\": {\n                  \"$ref\": \"#/components/schemas/HTTPValidationError\"\n                }\n              }\n            }\n          }\n        }\n      },\n      \"post\": {\n        \"tags\": [\n          \"item\"\n        ],\n        \"summary\": \"Create Item\",\n        \"operationId\": \"create_item\",\n        \"security\": [\n          {\n            \"OAuth2PasswordBearer\": []\n          }\n        ],\n        \"requestBody\": {\n          \"required\": true,\n          \"content\": {\n            \"application/json\": {\n              \"schema\": {\n                \"$ref\": \"#/components/schemas/ItemCreate\"\n              }\n            }\n          }\n        },\n        \"responses\": {\n          \"200\": {\n            \"description\": \"Successful Response\",\n            \"content\": {\n              \"application/json\": {\n                \"schema\": {\n                  \"$ref\": \"#/components/schemas/ItemRead\"\n                }\n              }\n            }\n          },\n          \"422\": {\n            \"description\": \"Validation Error\",\n            \"content\": {\n              \"application/json\": {\n                \"schema\": {\n                  \"$ref\": \"#/components/schemas/HTTPValidationError\"\n                }\n              }\n            }\n          }\n        }\n      }\n    },\n    \"/items/{item_id}\": {\n      \"delete\": {\n        \"tags\": [\n          \"item\"\n        ],\n        \"summary\": \"Delete Item\",\n        \"operationId\": \"delete_item\",\n        \"security\": [\n          {\n            \"OAuth2PasswordBearer\": []\n          }\n        ],\n        \"parameters\": [\n          {\n            \"name\": \"item_id\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"schema\": {\n              \"type\": \"string\",\n              \"format\": \"uuid\",\n              \"title\": \"Item Id\"\n            }\n          }\n        ],\n        \"responses\": {\n          \"200\": {\n            \"description\": \"Successful Response\",\n            \"content\": {\n              \"application/json\": {\n                \"schema\": {}\n              }\n            }\n          },\n          \"422\": {\n            \"description\": \"Validation Error\",\n            \"content\": {\n              \"application/json\": {\n                \"schema\": {\n                  \"$ref\": \"#/components/schemas/HTTPValidationError\"\n                }\n              }\n            }\n          }\n        }\n      }\n    }\n  },\n  \"components\": {\n    \"schemas\": {\n      \"BearerResponse\": {\n        \"properties\": {\n          \"access_token\": {\n            \"type\": \"string\",\n            \"title\": \"Access Token\"\n          },\n          \"token_type\": {\n            \"type\": \"string\",\n            \"title\": \"Token Type\"\n          }\n        },\n        \"type\": \"object\",\n        \"required\": [\n          \"access_token\",\n          \"token_type\"\n        ],\n        \"title\": \"BearerResponse\"\n      },\n      \"Body_auth-reset_forgot_password\": {\n        \"properties\": {\n          \"email\": {\n            \"type\": \"string\",\n            \"format\": \"email\",\n            \"title\": \"Email\"\n          }\n        },\n        \"type\": \"object\",\n        \"required\": [\n          \"email\"\n        ],\n        \"title\": \"Body_auth-reset:forgot_password\"\n      },\n      \"Body_auth-reset_reset_password\": {\n        \"properties\": {\n          \"token\": {\n            \"type\": \"string\",\n            \"title\": \"Token\"\n          },\n          \"password\": {\n            \"type\": \"string\",\n            \"title\": \"Password\"\n          }\n        },\n        \"type\": \"object\",\n        \"required\": [\n          \"token\",\n          \"password\"\n        ],\n        \"title\": \"Body_auth-reset:reset_password\"\n      },\n      \"Body_auth-verify_request-token\": {\n        \"properties\": {\n          \"email\": {\n            \"type\": \"string\",\n            \"format\": \"email\",\n            \"title\": \"Email\"\n          }\n        },\n        \"type\": \"object\",\n        \"required\": [\n          \"email\"\n        ],\n        \"title\": \"Body_auth-verify:request-token\"\n      },\n      \"Body_auth-verify_verify\": {\n        \"properties\": {\n          \"token\": {\n            \"type\": \"string\",\n            \"title\": \"Token\"\n          }\n        },\n        \"type\": \"object\",\n        \"required\": [\n          \"token\"\n        ],\n        \"title\": \"Body_auth-verify:verify\"\n      },\n      \"ErrorModel\": {\n        \"properties\": {\n          \"detail\": {\n            \"anyOf\": [\n              {\n                \"type\": \"string\"\n              },\n              {\n                \"additionalProperties\": {\n                  \"type\": \"string\"\n                },\n                \"type\": \"object\"\n              }\n            ],\n            \"title\": \"Detail\"\n          }\n        },\n        \"type\": \"object\",\n        \"required\": [\n          \"detail\"\n        ],\n        \"title\": \"ErrorModel\"\n      },\n      \"HTTPValidationError\": {\n        \"properties\": {\n          \"detail\": {\n            \"items\": {\n              \"$ref\": \"#/components/schemas/ValidationError\"\n            },\n            \"type\": \"array\",\n            \"title\": \"Detail\"\n          }\n        },\n        \"type\": \"object\",\n        \"title\": \"HTTPValidationError\"\n      },\n      \"ItemCreate\": {\n        \"properties\": {\n          \"name\": {\n            \"type\": \"string\",\n            \"title\": \"Name\"\n          },\n          \"description\": {\n            \"anyOf\": [\n              {\n                \"type\": \"string\"\n              },\n              {\n                \"type\": \"null\"\n              }\n            ],\n            \"title\": \"Description\"\n          },\n          \"quantity\": {\n            \"anyOf\": [\n              {\n                \"type\": \"integer\"\n              },\n              {\n                \"type\": \"null\"\n              }\n            ],\n            \"title\": \"Quantity\"\n          }\n        },\n        \"type\": \"object\",\n        \"required\": [\n          \"name\"\n        ],\n        \"title\": \"ItemCreate\"\n      },\n      \"ItemRead\": {\n        \"properties\": {\n          \"name\": {\n            \"type\": \"string\",\n            \"title\": \"Name\"\n          },\n          \"description\": {\n            \"anyOf\": [\n              {\n                \"type\": \"string\"\n              },\n              {\n                \"type\": \"null\"\n              }\n            ],\n            \"title\": \"Description\"\n          },\n          \"quantity\": {\n            \"anyOf\": [\n              {\n                \"type\": \"integer\"\n              },\n              {\n                \"type\": \"null\"\n              }\n            ],\n            \"title\": \"Quantity\"\n          },\n          \"id\": {\n            \"type\": \"string\",\n            \"format\": \"uuid\",\n            \"title\": \"Id\"\n          },\n          \"user_id\": {\n            \"type\": \"string\",\n            \"format\": \"uuid\",\n            \"title\": \"User Id\"\n          }\n        },\n        \"type\": \"object\",\n        \"required\": [\n          \"name\",\n          \"id\",\n          \"user_id\"\n        ],\n        \"title\": \"ItemRead\"\n      },\n      \"Page_ItemRead_\": {\n        \"properties\": {\n          \"items\": {\n            \"items\": {\n              \"$ref\": \"#/components/schemas/ItemRead\"\n            },\n            \"type\": \"array\",\n            \"title\": \"Items\"\n          },\n          \"total\": {\n            \"anyOf\": [\n              {\n                \"type\": \"integer\",\n                \"minimum\": 0.0\n              },\n              {\n                \"type\": \"null\"\n              }\n            ],\n            \"title\": \"Total\"\n          },\n          \"page\": {\n            \"anyOf\": [\n              {\n                \"type\": \"integer\",\n                \"minimum\": 1.0\n              },\n              {\n                \"type\": \"null\"\n              }\n            ],\n            \"title\": \"Page\"\n          },\n          \"size\": {\n            \"anyOf\": [\n              {\n                \"type\": \"integer\",\n                \"minimum\": 1.0\n              },\n              {\n                \"type\": \"null\"\n              }\n            ],\n            \"title\": \"Size\"\n          },\n          \"pages\": {\n            \"anyOf\": [\n              {\n                \"type\": \"integer\",\n                \"minimum\": 0.0\n              },\n              {\n                \"type\": \"null\"\n              }\n            ],\n            \"title\": \"Pages\"\n          }\n        },\n        \"type\": \"object\",\n        \"required\": [\n          \"items\",\n          \"page\",\n          \"size\"\n        ],\n        \"title\": \"Page[ItemRead]\"\n      },\n      \"UserCreate\": {\n        \"properties\": {\n          \"email\": {\n            \"type\": \"string\",\n            \"format\": \"email\",\n            \"title\": \"Email\"\n          },\n          \"password\": {\n            \"type\": \"string\",\n            \"title\": \"Password\"\n          },\n          \"is_active\": {\n            \"anyOf\": [\n              {\n                \"type\": \"boolean\"\n              },\n              {\n                \"type\": \"null\"\n              }\n            ],\n            \"title\": \"Is Active\",\n            \"default\": true\n          },\n          \"is_superuser\": {\n            \"anyOf\": [\n              {\n                \"type\": \"boolean\"\n              },\n              {\n                \"type\": \"null\"\n              }\n            ],\n            \"title\": \"Is Superuser\",\n            \"default\": false\n          },\n          \"is_verified\": {\n            \"anyOf\": [\n              {\n                \"type\": \"boolean\"\n              },\n              {\n                \"type\": \"null\"\n              }\n            ],\n            \"title\": \"Is Verified\",\n            \"default\": false\n          }\n        },\n        \"type\": \"object\",\n        \"required\": [\n          \"email\",\n          \"password\"\n        ],\n        \"title\": \"UserCreate\"\n      },\n      \"UserRead\": {\n        \"properties\": {\n          \"id\": {\n            \"type\": \"string\",\n            \"format\": \"uuid\",\n            \"title\": \"Id\"\n          },\n          \"email\": {\n            \"type\": \"string\",\n            \"format\": \"email\",\n            \"title\": \"Email\"\n          },\n          \"is_active\": {\n            \"type\": \"boolean\",\n            \"title\": \"Is Active\",\n            \"default\": true\n          },\n          \"is_superuser\": {\n            \"type\": \"boolean\",\n            \"title\": \"Is Superuser\",\n            \"default\": false\n          },\n          \"is_verified\": {\n            \"type\": \"boolean\",\n            \"title\": \"Is Verified\",\n            \"default\": false\n          }\n        },\n        \"type\": \"object\",\n        \"required\": [\n          \"id\",\n          \"email\"\n        ],\n        \"title\": \"UserRead\"\n      },\n      \"UserUpdate\": {\n        \"properties\": {\n          \"password\": {\n            \"anyOf\": [\n              {\n                \"type\": \"string\"\n              },\n              {\n                \"type\": \"null\"\n              }\n            ],\n            \"title\": \"Password\"\n          },\n          \"email\": {\n            \"anyOf\": [\n              {\n                \"type\": \"string\",\n                \"format\": \"email\"\n              },\n              {\n                \"type\": \"null\"\n              }\n            ],\n            \"title\": \"Email\"\n          },\n          \"is_active\": {\n            \"anyOf\": [\n              {\n                \"type\": \"boolean\"\n              },\n              {\n                \"type\": \"null\"\n              }\n            ],\n            \"title\": \"Is Active\"\n          },\n          \"is_superuser\": {\n            \"anyOf\": [\n              {\n                \"type\": \"boolean\"\n              },\n              {\n                \"type\": \"null\"\n              }\n            ],\n            \"title\": \"Is Superuser\"\n          },\n          \"is_verified\": {\n            \"anyOf\": [\n              {\n                \"type\": \"boolean\"\n              },\n              {\n                \"type\": \"null\"\n              }\n            ],\n            \"title\": \"Is Verified\"\n          }\n        },\n        \"type\": \"object\",\n        \"title\": \"UserUpdate\"\n      },\n      \"ValidationError\": {\n        \"properties\": {\n          \"loc\": {\n            \"items\": {\n              \"anyOf\": [\n                {\n                  \"type\": \"string\"\n                },\n                {\n                  \"type\": \"integer\"\n                }\n              ]\n            },\n            \"type\": \"array\",\n            \"title\": \"Location\"\n          },\n          \"msg\": {\n            \"type\": \"string\",\n            \"title\": \"Message\"\n          },\n          \"type\": {\n            \"type\": \"string\",\n            \"title\": \"Error Type\"\n          }\n        },\n        \"type\": \"object\",\n        \"required\": [\n          \"loc\",\n          \"msg\",\n          \"type\"\n        ],\n        \"title\": \"ValidationError\"\n      },\n      \"login\": {\n        \"properties\": {\n          \"grant_type\": {\n            \"anyOf\": [\n              {\n                \"type\": \"string\",\n                \"pattern\": \"password\"\n              },\n              {\n                \"type\": \"null\"\n              }\n            ],\n            \"title\": \"Grant Type\"\n          },\n          \"username\": {\n            \"type\": \"string\",\n            \"title\": \"Username\"\n          },\n          \"password\": {\n            \"type\": \"string\",\n            \"title\": \"Password\"\n          },\n          \"scope\": {\n            \"type\": \"string\",\n            \"title\": \"Scope\",\n            \"default\": \"\"\n          },\n          \"client_id\": {\n            \"anyOf\": [\n              {\n                \"type\": \"string\"\n              },\n              {\n                \"type\": \"null\"\n              }\n            ],\n            \"title\": \"Client Id\"\n          },\n          \"client_secret\": {\n            \"anyOf\": [\n              {\n                \"type\": \"string\"\n              },\n              {\n                \"type\": \"null\"\n              }\n            ],\n            \"title\": \"Client Secret\"\n          }\n        },\n        \"type\": \"object\",\n        \"required\": [\n          \"username\",\n          \"password\"\n        ],\n        \"title\": \"Body_auth-auth:jwt.login\"\n      }\n    },\n    \"securitySchemes\": {\n      \"OAuth2PasswordBearer\": {\n        \"type\": \"oauth2\",\n        \"flows\": {\n          \"password\": {\n            \"scopes\": {},\n            \"tokenUrl\": \"auth/jwt/login\"\n          }\n        }\n      }\n    }\n  }\n}"
  },
  {
    "path": "mkdocs.yml",
    "content": "site_name: Next.js FastAPI Template\nsite_description: Kickstart scalable apps with our Next.js FastAPI Template. Includes auth, type safety (Zod), hot reload, Docker, and Vercel-ready deployment.\nsite_url: https://vintasoftware.github.io/nextjs-fastapi-template/\n\nrepo_name: vintasoftware/nextjs-fastapi-template\nrepo_url: https://github.com/vintasoftware/nextjs-fastapi-template/\nedit_uri: blob/main/docs/\n\ncopyright: From <a href=\"https://www.vintasoftware.com\">Vinta Software</a> to the community with 💙\n\ntheme:\n  name: material\n  custom_dir: overrides\n  logo: images/nav-logo.png\n  favicon: images/github-favicon.png\n  features:\n    - navigation.footer\n    - navigation.indexes\n    - navigation.sections\n    - navigation.tabs\n    - navigation.top\n    - navigation.tracking\n    - search.highlight\n    - search.share\n    - search.suggest\n    - toc.follow\n  palette:\n    # Palette toggle for automatic mode\n    - media: \"(prefers-color-scheme)\"\n      primary: custom\n      toggle:\n        icon: material/brightness-auto\n        name: Switch to light mode\n\n    # Palette toggle for light mode\n    - media: \"(prefers-color-scheme: light)\"\n      scheme: default\n      primary: custom\n      toggle:\n        icon: material/brightness-7\n        name: Switch to dark mode\n\n    # Palette toggle for dark mode\n    - media: \"(prefers-color-scheme: dark)\"\n      scheme: slate\n      primary: custom\n      toggle:\n        icon: material/brightness-4\n        name: Switch to system preference\n\nmarkdown_extensions:\n  - admonition\n  - pymdownx.highlight:\n      use_pygments: true\n  - pymdownx.inlinehilite\n  - pymdownx.superfences\n  - pymdownx.snippets:\n      check_paths: true\n  - toc:\n      permalink: true\n  - attr_list\n  - pymdownx.emoji:\n      emoji_index: !!python/name:material.extensions.emoji.twemoji\n      emoji_generator: !!python/name:material.extensions.emoji.to_svg\n\nnav:\n  - Home: README.md\n  - Get Started: get-started.md\n  - Additional Settings: additional-settings.md\n  - Technology Selection: technology-selection.md\n  - Deployment: deployment.md\n  - Changelog: CHANGELOG.md\n  - Contributing: contributing.md\n  - Support: support.md\n\n\nplugins:\n  - search\n\nextra:\n  social:\n    - icon: fontawesome/brands/github\n      link: https://github.com/vintasoftware/nextjs-fastapi-template/\n      name: Nextjs FastAPI Template GitHub\n    - icon: fontawesome/brands/x-twitter\n      link: https://x.com/vintasoftware\n      name: Vinta Software X\n    - icon: fontawesome/brands/linkedin\n      link: https://linkedin.com/company/vintasoftware\n      name: Vinta Software LinkedIn\n  version:\n    provider: mike\n  analytics:\n    provider: google\n    property: GTM-M9GMGBHR\n\nextra_css:\n  - stylesheets/extra.css\n"
  },
  {
    "path": "nextjs-frontend/.gitignore",
    "content": "# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.\n\n# dependencies\n/node_modules\n/.pnp\n.pnp.js\n.yarn/install-state.gz\n\n# testing\n/coverage\n\n# next.js\n/.next/\n/out/\n\n# production\n/build\n\n# misc\n.DS_Store\n*.pem\n\n# debug\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\n\n# local env files\n.env\n.env.local\n\n# vercel\n.vercel\n\n# typescript\n*.tsbuildinfo\nnext-env.d.ts\n\n!/lib/"
  },
  {
    "path": "nextjs-frontend/.prettierignore",
    "content": "openapi.json"
  },
  {
    "path": "nextjs-frontend/Dockerfile",
    "content": "FROM node:20-bookworm\n\n# Set the working directory\nWORKDIR /app\n\n# Install pnpm globally\nRUN npm install -g pnpm\n\n# Copy package files and install dependencies\nCOPY package*.json ./\n\n# Install dependencies as root (or myappuser, depending on your structure)\nRUN pnpm install\n\n## Switch to the non-root user\nUSER node\n\n# Copy the rest of your application code\nCOPY . .\n\nEXPOSE 3000\n\n# Start the application\nCMD [\"./start.sh\"]"
  },
  {
    "path": "nextjs-frontend/__tests__/login.test.tsx",
    "content": "import { login } from \"@/components/actions/login-action\";\nimport { authJwtLogin } from \"@/app/clientService\";\nimport { cookies } from \"next/headers\";\n\njest.mock(\"../app/clientService\", () => ({\n  authJwtLogin: jest.fn(),\n}));\n\njest.mock(\"next/headers\", () => {\n  const mockSet = jest.fn();\n  return { cookies: jest.fn().mockResolvedValue({ set: mockSet }) };\n});\n\njest.mock(\"next/navigation\", () => ({\n  redirect: jest.fn(),\n}));\n\ndescribe(\"login action\", () => {\n  it(\"should call login service action with the correct input\", async () => {\n    const formData = new FormData();\n    formData.set(\"username\", \"a@a.com\");\n    formData.set(\"password\", \"Q12341414#\");\n\n    const mockSet = (await cookies()).set;\n\n    // Mock a successful login\n    (authJwtLogin as jest.Mock).mockResolvedValue({\n      data: { access_token: \"1245token\" },\n    });\n\n    await login({}, formData);\n\n    expect(authJwtLogin).toHaveBeenCalledWith({\n      body: {\n        username: \"a@a.com\",\n        password: \"Q12341414#\",\n      },\n    });\n\n    expect(cookies).toHaveBeenCalled();\n    expect(mockSet).toHaveBeenCalledWith(\"accessToken\", \"1245token\");\n  });\n\n  it(\"should should return an error if the server validation fails\", async () => {\n    const formData = new FormData();\n    formData.set(\"username\", \"invalid@invalid.com\");\n    formData.set(\"password\", \"Q12341414#\");\n\n    // Mock a failed login\n    (authJwtLogin as jest.Mock).mockResolvedValue({\n      error: {\n        detail: \"LOGIN_BAD_CREDENTIALS\",\n      },\n    });\n\n    const result = await login(undefined, formData);\n\n    expect(authJwtLogin).toHaveBeenCalledWith({\n      body: {\n        username: \"invalid@invalid.com\",\n        password: \"Q12341414#\",\n      },\n    });\n\n    expect(result).toEqual({\n      server_validation_error: \"LOGIN_BAD_CREDENTIALS\",\n    });\n\n    expect(cookies).not.toHaveBeenCalled();\n  });\n\n  it(\"should should return an error if either the password or username is not sent\", async () => {\n    const formData = new FormData();\n    formData.set(\"username\", \"\");\n    formData.set(\"password\", \"\");\n\n    const result = await login({}, formData);\n\n    expect(authJwtLogin).not.toHaveBeenCalledWith();\n\n    expect(result).toEqual({\n      errors: {\n        password: [\"Password is required\"],\n        username: [\"Username is required\"],\n      },\n    });\n\n    expect(cookies).not.toHaveBeenCalled();\n  });\n\n  it(\"should handle unexpected errors and return server error message\", async () => {\n    // Mock the authJwtLogin to throw an error\n    const mockError = new Error(\"Network error\");\n    (authJwtLogin as jest.Mock).mockRejectedValue(mockError);\n\n    const formData = new FormData();\n    formData.append(\"username\", \"testuser\");\n    formData.append(\"password\", \"password123\");\n\n    const result = await login(undefined, formData);\n\n    expect(result).toEqual({\n      server_error: \"An unexpected error occurred. Please try again later.\",\n    });\n  });\n});\n"
  },
  {
    "path": "nextjs-frontend/__tests__/loginPage.test.tsx",
    "content": "import { render, screen, fireEvent, waitFor } from \"@testing-library/react\";\nimport \"@testing-library/jest-dom\";\n\nimport Page from \"@/app/login/page\";\nimport { login } from \"@/components/actions/login-action\";\n\njest.mock(\"../components/actions/login-action\", () => ({\n  login: jest.fn(),\n}));\n\ndescribe(\"Login Page\", () => {\n  afterEach(() => {\n    jest.clearAllMocks();\n  });\n\n  it(\"renders the form with username and password input and submit button\", () => {\n    render(<Page />);\n\n    expect(screen.getByLabelText(/username/i)).toBeInTheDocument();\n    expect(screen.getByLabelText(/password/i)).toBeInTheDocument();\n    expect(\n      screen.getByRole(\"button\", { name: /sign in/i }),\n    ).toBeInTheDocument();\n  });\n\n  it(\"calls login in successful form submission\", async () => {\n    (login as jest.Mock).mockResolvedValue({});\n\n    render(<Page />);\n\n    const usernameInput = screen.getByLabelText(/username/i);\n    const passwordInput = screen.getByLabelText(/password/i);\n    const submitButton = screen.getByRole(\"button\", { name: /sign in/i });\n\n    fireEvent.change(usernameInput, {\n      target: { value: \"testuser@example.com\" },\n    });\n    fireEvent.change(passwordInput, { target: { value: \"#123176a@\" } });\n    fireEvent.click(submitButton);\n\n    await waitFor(() => {\n      const formData = new FormData();\n      formData.set(\"username\", \"testuser@example.com\");\n      formData.set(\"password\", \"#123176a@\");\n      expect(login).toHaveBeenCalledWith(undefined, formData);\n    });\n  });\n\n  it(\"displays error message if login fails\", async () => {\n    // Mock a failed login\n    (login as jest.Mock).mockResolvedValue({\n      server_validation_error: \"LOGIN_BAD_CREDENTIALS\",\n    });\n\n    render(<Page />);\n\n    const usernameInput = screen.getByLabelText(/username/i);\n    const passwordInput = screen.getByLabelText(/password/i);\n    const submitButton = screen.getByRole(\"button\", { name: /sign in/i });\n\n    fireEvent.change(usernameInput, { target: { value: \"wrong@example.com\" } });\n    fireEvent.change(passwordInput, { target: { value: \"wrongpass\" } });\n    fireEvent.click(submitButton);\n\n    await waitFor(() => {\n      expect(screen.getByText(\"LOGIN_BAD_CREDENTIALS\")).toBeInTheDocument();\n    });\n  });\n\n  it(\"displays server error for unexpected errors\", async () => {\n    (login as jest.Mock).mockResolvedValue({\n      server_error: \"An unexpected error occurred. Please try again later.\",\n    });\n\n    render(<Page />);\n\n    const usernameInput = screen.getByLabelText(/username/i);\n    const passwordInput = screen.getByLabelText(/password/i);\n    const submitButton = screen.getByRole(\"button\", { name: /sign in/i });\n\n    fireEvent.change(usernameInput, { target: { value: \"test@test.com\" } });\n    fireEvent.change(passwordInput, { target: { value: \"password123\" } });\n    fireEvent.click(submitButton);\n\n    await waitFor(() => {\n      expect(\n        screen.getByText(\n          \"An unexpected error occurred. Please try again later.\",\n        ),\n      ).toBeInTheDocument();\n    });\n  });\n});\n"
  },
  {
    "path": "nextjs-frontend/__tests__/passwordReset.test.tsx",
    "content": "import { passwordReset } from \"@/components/actions/password-reset-action\";\nimport { resetForgotPassword } from \"@/app/clientService\";\n\njest.mock(\"../app/openapi-client/sdk.gen\", () => ({\n  resetForgotPassword: jest.fn(),\n}));\n\njest.mock(\"../lib/clientConfig\", () => ({\n  client: {\n    setConfig: jest.fn(),\n  },\n}));\n\ndescribe(\"passwordReset action\", () => {\n  afterEach(() => {\n    jest.clearAllMocks();\n  });\n\n  it(\"should call resetForgotPassword with the correct input and return success message\", async () => {\n    const formData = new FormData();\n    formData.set(\"email\", \"testuser@example.com\");\n    // Mock a successful password reset\n    (resetForgotPassword as jest.Mock).mockResolvedValue({});\n\n    const result = await passwordReset({}, formData);\n\n    expect(resetForgotPassword).toHaveBeenCalledWith({\n      body: { email: \"testuser@example.com\" },\n    });\n    expect(result).toEqual({\n      message: \"Password reset instructions sent to your email.\",\n    });\n  });\n\n  it(\"should return a server validation error if the server call fails\", async () => {\n    const formData = new FormData();\n    formData.set(\"email\", \"testuser@example.com\");\n\n    // Mock a failed password reset\n    (resetForgotPassword as jest.Mock).mockResolvedValue({\n      error: { detail: \"User not found\" },\n    });\n\n    const result = await passwordReset({}, formData);\n\n    expect(result).toEqual({ server_validation_error: \"User not found\" });\n    expect(resetForgotPassword).toHaveBeenCalledWith({\n      body: { email: \"testuser@example.com\" },\n    });\n  });\n\n  it(\"should handle unexpected errors and return server error message\", async () => {\n    // Mock the resetForgotPassword to throw an error\n    const mockError = new Error(\"Network error\");\n    (resetForgotPassword as jest.Mock).mockRejectedValue(mockError);\n\n    const formData = new FormData();\n    formData.append(\"email\", \"testuser@example.com\");\n\n    const result = await passwordReset(undefined, formData);\n\n    expect(result).toEqual({\n      server_error: \"An unexpected error occurred. Please try again later.\",\n    });\n  });\n});\n"
  },
  {
    "path": "nextjs-frontend/__tests__/passwordResetConfirm.test.tsx",
    "content": "import { passwordResetConfirm } from \"@/components/actions/password-reset-action\";\nimport { resetResetPassword } from \"@/app/clientService\";\nimport { redirect } from \"next/navigation\";\n\njest.mock(\"next/navigation\", () => ({\n  redirect: jest.fn(),\n}));\n\njest.mock(\"../app/openapi-client/sdk.gen\", () => ({\n  resetResetPassword: jest.fn(),\n}));\n\njest.mock(\"../lib/clientConfig\", () => ({\n  client: {\n    setConfig: jest.fn(),\n  },\n}));\n\ndescribe(\"passwordReset action\", () => {\n  afterEach(() => {\n    jest.clearAllMocks();\n  });\n\n  it(\"should call resetPassword with the correct input\", async () => {\n    const formData = new FormData();\n    formData.set(\"resetToken\", \"token\");\n    formData.set(\"password\", \"P12345678#\");\n    formData.set(\"passwordConfirm\", \"P12345678#\");\n    // Mock a successful password reset confirm\n    (resetResetPassword as jest.Mock).mockResolvedValue({});\n\n    await passwordResetConfirm({}, formData);\n\n    expect(resetResetPassword).toHaveBeenCalledWith({\n      body: { token: \"token\", password: \"P12345678#\" },\n    });\n    expect(redirect).toHaveBeenCalled();\n  });\n\n  it(\"should return an error message if password reset fails\", async () => {\n    const formData = new FormData();\n    formData.set(\"resetToken\", \"invalid_token\");\n    formData.set(\"password\", \"P12345678#\");\n    formData.set(\"passwordConfirm\", \"P12345678#\");\n\n    // Mock a failed password reset\n    (resetResetPassword as jest.Mock).mockResolvedValue({\n      error: { detail: \"Invalid token\" },\n    });\n\n    const result = await passwordResetConfirm(undefined, formData);\n\n    expect(result).toEqual({ server_validation_error: \"Invalid token\" });\n    expect(resetResetPassword).toHaveBeenCalledWith({\n      body: { token: \"invalid_token\", password: \"P12345678#\" },\n    });\n  });\n\n  it(\"should return an validation error if passwords are invalid and don't match\", async () => {\n    const formData = new FormData();\n    formData.set(\"resetToken\", \"token\");\n    formData.set(\"password\", \"12345678#\");\n    formData.set(\"passwordConfirm\", \"45678#\");\n\n    const result = await passwordResetConfirm(undefined, formData);\n\n    expect(result).toEqual({\n      errors: {\n        password: [\"Password should contain at least one uppercase letter.\"],\n        passwordConfirm: [\"Passwords must match.\"],\n      },\n    });\n    expect(resetResetPassword).not.toHaveBeenCalledWith();\n  });\n\n  it(\"should handle unexpected errors and return server error message\", async () => {\n    // Mock the resetResetPassword to throw an error\n    const mockError = new Error(\"Network error\");\n    (resetResetPassword as jest.Mock).mockRejectedValue(mockError);\n\n    const formData = new FormData();\n    formData.append(\"resetToken\", \"token\");\n    formData.append(\"password\", \"P12345678#\");\n    formData.append(\"passwordConfirm\", \"P12345678#\");\n\n    const result = await passwordResetConfirm(undefined, formData);\n\n    expect(result).toEqual({\n      server_error: \"An unexpected error occurred. Please try again later.\",\n    });\n  });\n});\n"
  },
  {
    "path": "nextjs-frontend/__tests__/passwordResetConfirmPage.test.tsx",
    "content": "import { render, screen, fireEvent, waitFor } from \"@testing-library/react\";\nimport \"@testing-library/jest-dom\";\n\nimport Page from \"@/app/password-recovery/confirm/page\";\nimport { passwordResetConfirm } from \"@/components/actions/password-reset-action\";\nimport { useSearchParams, notFound } from \"next/navigation\";\n\njest.mock(\"next/navigation\", () => ({\n  ...jest.requireActual(\"next/navigation\"),\n  useSearchParams: jest.fn(),\n  notFound: jest.fn(),\n}));\n\njest.mock(\"../components/actions/password-reset-action\", () => ({\n  passwordResetConfirm: jest.fn(),\n}));\n\ndescribe(\"Password Reset Confirm Page\", () => {\n  afterEach(() => {\n    jest.clearAllMocks();\n  });\n\n  it(\"renders the form with password and confirm password input and submit button\", () => {\n    (useSearchParams as jest.Mock).mockImplementation(() => ({\n      get: (key: string) => (key === \"token\" ? \"mock-token\" : null),\n    }));\n\n    render(<Page />);\n\n    expect(screen.getByLabelText(\"Password\")).toBeInTheDocument();\n    expect(screen.getByLabelText(\"Password Confirm\")).toBeInTheDocument();\n    expect(screen.getByRole(\"button\", { name: /send/i })).toBeInTheDocument();\n  });\n\n  it(\"renders the 404 page in case there is not a token\", () => {\n    (useSearchParams as jest.Mock).mockImplementation(() => ({\n      get: (key: string) => (key === \"token\" ? \"\" : undefined),\n    }));\n\n    render(<Page />);\n\n    expect(notFound).toHaveBeenCalled();\n  });\n\n  it(\"displays error message if password reset fails\", async () => {\n    (useSearchParams as jest.Mock).mockImplementation(() => ({\n      get: (key: string) => (key === \"token\" ? \"invalid-mock-token\" : null),\n    }));\n\n    // Mock a successful password reset\n    (passwordResetConfirm as jest.Mock).mockResolvedValue({\n      server_validation_error: \"Invalid Token\",\n    });\n\n    render(<Page />);\n\n    const password = screen.getByLabelText(\"Password\");\n    const passwordConfirm = screen.getByLabelText(\"Password Confirm\");\n\n    const submitButton = screen.getByRole(\"button\", { name: /send/i });\n\n    fireEvent.change(password, { target: { value: \"P12345678#\" } });\n    fireEvent.change(passwordConfirm, { target: { value: \"P12345678#\" } });\n    fireEvent.click(submitButton);\n\n    await waitFor(() => {\n      expect(screen.getByText(\"Invalid Token\")).toBeInTheDocument();\n    });\n\n    const formData = new FormData();\n    formData.set(\"password\", \"P12345678#\");\n    formData.set(\"passwordConfirm\", \"P12345678#\");\n    formData.set(\"resetToken\", \"invalid-mock-token\");\n    expect(passwordResetConfirm).toHaveBeenCalledWith(undefined, formData);\n  });\n  it(\"displays validation errors if password is invalid and don't match\", async () => {\n    (useSearchParams as jest.Mock).mockImplementation(() => ({\n      get: (key: string) => (key === \"token\" ? \"mock-token\" : null),\n    }));\n\n    // Mock a successful password reset\n    (passwordResetConfirm as jest.Mock).mockResolvedValue({\n      errors: {\n        password: [\"Password should contain at least one uppercase letter.\"],\n        passwordConfirm: [\"Passwords must match.\"],\n      },\n    });\n\n    render(<Page />);\n\n    const password = screen.getByLabelText(\"Password\");\n    const passwordConfirm = screen.getByLabelText(\"Password Confirm\");\n\n    const submitButton = screen.getByRole(\"button\", { name: /send/i });\n\n    fireEvent.change(password, { target: { value: \"12345678#\" } });\n    fireEvent.change(passwordConfirm, { target: { value: \"45678#\" } });\n    fireEvent.click(submitButton);\n\n    await waitFor(() => {\n      expect(\n        screen.getByText(\n          \"Password should contain at least one uppercase letter.\",\n        ),\n      ).toBeInTheDocument();\n      expect(screen.getByText(\"Passwords must match.\")).toBeInTheDocument();\n    });\n\n    const formData = new FormData();\n    formData.set(\"password\", \"12345678#\");\n    formData.set(\"passwordConfirm\", \"45678#\");\n    formData.set(\"resetToken\", \"mock-token\");\n    expect(passwordResetConfirm).toHaveBeenCalledWith(undefined, formData);\n  });\n});\n"
  },
  {
    "path": "nextjs-frontend/__tests__/passwordResetPage.test.tsx",
    "content": "import { render, screen, fireEvent, waitFor } from \"@testing-library/react\";\nimport \"@testing-library/jest-dom\";\n\nimport Page from \"@/app/password-recovery/page\";\nimport { passwordReset } from \"@/components/actions/password-reset-action\";\n\njest.mock(\"../components/actions/password-reset-action\", () => ({\n  passwordReset: jest.fn(),\n}));\n\ndescribe(\"Password Reset Page\", () => {\n  afterEach(() => {\n    jest.clearAllMocks();\n  });\n\n  it(\"renders the form with email input and submit button\", () => {\n    render(<Page />);\n\n    expect(screen.getByLabelText(/email/i)).toBeInTheDocument();\n    expect(screen.getByRole(\"button\", { name: /send/i })).toBeInTheDocument();\n  });\n\n  it(\"displays success message on successful form submission\", async () => {\n    // Mock a successful password reset\n    (passwordReset as jest.Mock).mockResolvedValue({\n      message: \"Password reset instructions sent to your email.\",\n    });\n\n    render(<Page />);\n\n    const emailInput = screen.getByLabelText(/email/i);\n    const submitButton = screen.getByRole(\"button\", { name: /send/i });\n\n    fireEvent.change(emailInput, { target: { value: \"testuser@example.com\" } });\n    fireEvent.click(submitButton);\n\n    await waitFor(() => {\n      expect(\n        screen.getByText(\"Password reset instructions sent to your email.\"),\n      ).toBeInTheDocument();\n    });\n\n    const formData = new FormData();\n    formData.set(\"email\", \"testuser@example.com\");\n    expect(passwordReset).toHaveBeenCalledWith(undefined, formData);\n  });\n\n  it(\"displays error message if password reset fails\", async () => {\n    // Mock a failed password reset\n    (passwordReset as jest.Mock).mockResolvedValue({\n      server_validation_error: \"User not found\",\n    });\n\n    render(<Page />);\n\n    const emailInput = screen.getByLabelText(/email/i);\n    const submitButton = screen.getByRole(\"button\", { name: /send/i });\n\n    fireEvent.change(emailInput, {\n      target: { value: \"invaliduser@example.com\" },\n    });\n    fireEvent.click(submitButton);\n\n    await waitFor(() => {\n      expect(screen.getByText(\"User not found\")).toBeInTheDocument();\n    });\n\n    const formData = new FormData();\n    formData.set(\"email\", \"invaliduser@example.com\");\n    expect(passwordReset).toHaveBeenCalledWith(undefined, formData);\n  });\n});\n"
  },
  {
    "path": "nextjs-frontend/__tests__/register.test.ts",
    "content": "import { register } from \"@/components/actions/register-action\";\nimport { redirect } from \"next/navigation\";\nimport { registerRegister } from \"@/app/clientService\";\n\njest.mock(\"next/navigation\", () => ({\n  redirect: jest.fn(),\n}));\n\njest.mock(\"../app/clientService\", () => ({\n  registerRegister: jest.fn(),\n}));\n\ndescribe(\"register action\", () => {\n  it(\"should call register service action with the correct input\", async () => {\n    const formData = new FormData();\n    formData.set(\"email\", \"a@a.com\");\n    formData.set(\"password\", \"Q12341414#\");\n\n    // Mock a successful register\n    (registerRegister as jest.Mock).mockResolvedValue({});\n\n    await register({}, formData);\n\n    expect(registerRegister).toHaveBeenCalledWith({\n      body: {\n        email: \"a@a.com\",\n        password: \"Q12341414#\",\n      },\n    });\n\n    expect(redirect).toHaveBeenCalled();\n  });\n  it(\"should should return an error if the server call fails\", async () => {\n    const formData = new FormData();\n    formData.set(\"email\", \"a@a.com\");\n    formData.set(\"password\", \"Q12341414#\");\n\n    // Mock a failed register\n    (registerRegister as jest.Mock).mockResolvedValue({\n      error: {\n        detail: \"REGISTER_USER_ALREADY_EXISTS\",\n      },\n    });\n\n    const result = await register({}, formData);\n\n    expect(registerRegister).toHaveBeenCalledWith({\n      body: {\n        email: \"a@a.com\",\n        password: \"Q12341414#\",\n      },\n    });\n    expect(result).toEqual({\n      server_validation_error: \"REGISTER_USER_ALREADY_EXISTS\",\n    });\n  });\n\n  it(\"should return an validation error if the form is invalid\", async () => {\n    const formData = new FormData();\n    formData.set(\"email\", \"email\");\n    formData.set(\"password\", \"invalid_password\");\n\n    const result = await register({}, formData);\n\n    expect(result).toEqual({\n      errors: {\n        email: [\"Invalid email address\"],\n        password: [\n          \"Password should contain at least one uppercase letter.\",\n          \"Password should contain at least one special character.\",\n        ],\n      },\n    });\n    expect(registerRegister).not.toHaveBeenCalledWith();\n  });\n\n  it(\"should handle unexpected errors and return server error message\", async () => {\n    // Mock the registerRegister to throw an error\n    const mockError = new Error(\"Network error\");\n    (registerRegister as jest.Mock).mockRejectedValue(mockError);\n\n    const formData = new FormData();\n    formData.append(\"email\", \"testuser@example.com\");\n    formData.append(\"password\", \"Password123#\");\n\n    const result = await register(undefined, formData);\n\n    expect(result).toEqual({\n      server_error: \"An unexpected error occurred. Please try again later.\",\n    });\n  });\n});\n"
  },
  {
    "path": "nextjs-frontend/__tests__/registerPage.test.tsx",
    "content": "import { render, screen, fireEvent, waitFor } from \"@testing-library/react\";\nimport \"@testing-library/jest-dom\";\n\nimport Page from \"@/app/register/page\";\nimport { register } from \"@/components/actions/register-action\";\n\njest.mock(\"../components/actions/register-action\", () => ({\n  register: jest.fn(),\n}));\n\ndescribe(\"Register Page\", () => {\n  afterEach(() => {\n    jest.clearAllMocks();\n  });\n\n  it(\"renders the form with email and password input and submit button\", () => {\n    render(<Page />);\n\n    expect(screen.getByLabelText(/email/i)).toBeInTheDocument();\n    expect(screen.getByLabelText(/password/i)).toBeInTheDocument();\n    expect(\n      screen.getByRole(\"button\", { name: /sign up/i }),\n    ).toBeInTheDocument();\n  });\n\n  it(\"displays success message on successful form submission\", async () => {\n    // Mock a successful register\n    (register as jest.Mock).mockResolvedValue({});\n\n    render(<Page />);\n\n    const emailInput = screen.getByLabelText(/email/i);\n    const passwordInput = screen.getByLabelText(/password/i);\n    const submitButton = screen.getByRole(\"button\", { name: /sign up/i });\n\n    fireEvent.change(emailInput, { target: { value: \"testuser@example.com\" } });\n    fireEvent.change(passwordInput, { target: { value: \"@1231231%a\" } });\n    fireEvent.click(submitButton);\n\n    await waitFor(() => {\n      const formData = new FormData();\n      formData.set(\"email\", \"testuser@example.com\");\n      formData.set(\"password\", \"@1231231%a\");\n      expect(register).toHaveBeenCalledWith(undefined, formData);\n    });\n  });\n\n  it(\"displays server validation error if register fails\", async () => {\n    (register as jest.Mock).mockResolvedValue({\n      server_validation_error: \"User already exists\",\n    });\n\n    render(<Page />);\n\n    const emailInput = screen.getByLabelText(/email/i);\n    const passwordInput = screen.getByLabelText(/password/i);\n    const submitButton = screen.getByRole(\"button\", { name: /sign up/i });\n\n    fireEvent.change(emailInput, { target: { value: \"already@already.com\" } });\n    fireEvent.change(passwordInput, { target: { value: \"@1231231%a\" } });\n    fireEvent.click(submitButton);\n\n    await waitFor(() => {\n      expect(screen.getByText(\"User already exists\")).toBeInTheDocument();\n    });\n  });\n\n  it(\"displays server error for unexpected errors\", async () => {\n    (register as jest.Mock).mockResolvedValue({\n      server_error: \"An unexpected error occurred. Please try again later.\",\n    });\n\n    render(<Page />);\n\n    const emailInput = screen.getByLabelText(/email/i);\n    const passwordInput = screen.getByLabelText(/password/i);\n    const submitButton = screen.getByRole(\"button\", { name: /sign up/i });\n\n    fireEvent.change(emailInput, { target: { value: \"test@test.com\" } });\n    fireEvent.change(passwordInput, { target: { value: \"@1231231%a\" } });\n    fireEvent.click(submitButton);\n\n    await waitFor(() => {\n      expect(\n        screen.getByText(\n          \"An unexpected error occurred. Please try again later.\",\n        ),\n      ).toBeInTheDocument();\n    });\n\n    const formData = new FormData();\n    formData.set(\"email\", \"test@test.com\");\n    formData.set(\"password\", \"@1231231%a\");\n    expect(register).toHaveBeenCalledWith(undefined, formData);\n  });\n\n  it(\"displays validation errors if password and email are invalid\", async () => {\n    // Mock a successful password register\n    (register as jest.Mock).mockResolvedValue({\n      errors: {\n        email: [\"Invalid email address\"],\n        password: [\n          \"Password should contain at least one uppercase letter.\",\n          \"Password should contain at least one special character.\",\n        ],\n      },\n    });\n\n    render(<Page />);\n\n    const emailInput = screen.getByLabelText(/email/i);\n    const passwordInput = screen.getByLabelText(/password/i);\n    const submitButton = screen.getByRole(\"button\", { name: /sign up/i });\n\n    fireEvent.change(emailInput, { target: { value: \"email@email.com\" } });\n    fireEvent.change(passwordInput, { target: { value: \"invalid_password\" } });\n    fireEvent.click(submitButton);\n\n    await waitFor(() => {\n      expect(\n        screen.getByText(\n          \"Password should contain at least one uppercase letter.\",\n        ),\n      ).toBeInTheDocument();\n      expect(\n        screen.getByText(\n          \"Password should contain at least one special character.\",\n        ),\n      ).toBeInTheDocument();\n      expect(screen.getByText(\"Invalid email address\")).toBeInTheDocument();\n    });\n\n    const formData = new FormData();\n    formData.set(\"email\", \"email@email.com\");\n    formData.set(\"password\", \"invalid_password\");\n    expect(register).toHaveBeenCalledWith(undefined, formData);\n  });\n});\n"
  },
  {
    "path": "nextjs-frontend/app/clientService.ts",
    "content": "export * from \"./openapi-client\";\n\nimport \"@/lib/clientConfig\";\n"
  },
  {
    "path": "nextjs-frontend/app/dashboard/add-item/page.tsx",
    "content": "\"use client\";\n\nimport { Input } from \"@/components/ui/input\";\nimport { Label } from \"@/components/ui/label\";\nimport { addItem } from \"@/components/actions/items-action\";\nimport { useActionState } from \"react\";\nimport { SubmitButton } from \"@/components/ui/submitButton\";\n\nconst initialState = { message: \"\" };\n\nexport default function CreateItemPage() {\n  const [state, dispatch] = useActionState(addItem, initialState);\n\n  return (\n    <div className=\"bg-gray-50 dark:bg-gray-900 min-h-screen\">\n      <div className=\"max-w-4xl mx-auto p-6\">\n        <header className=\"mb-6\">\n          <h1 className=\"text-3xl font-semibold text-gray-800 dark:text-white\">\n            Create New Item\n          </h1>\n          <p className=\"text-lg text-gray-600 dark:text-gray-400\">\n            Enter the details of the new item below.\n          </p>\n        </header>\n\n        <form\n          action={dispatch}\n          className=\"bg-white dark:bg-gray-800 rounded-lg shadow-lg p-8 space-y-6\"\n        >\n          <div className=\"space-y-6\">\n            <div className=\"space-y-3\">\n              <Label\n                htmlFor=\"name\"\n                className=\"text-gray-700 dark:text-gray-300\"\n              >\n                Item Name\n              </Label>\n              <Input\n                id=\"name\"\n                name=\"name\"\n                type=\"text\"\n                placeholder=\"Item name\"\n                required\n                className=\"w-full border-gray-300 dark:border-gray-600\"\n              />\n              {state.errors?.name && (\n                <p className=\"text-red-500 text-sm\">{state.errors.name}</p>\n              )}\n            </div>\n\n            <div className=\"space-y-3\">\n              <Label\n                htmlFor=\"description\"\n                className=\"text-gray-700 dark:text-gray-300\"\n              >\n                Item Description\n              </Label>\n              <Input\n                id=\"description\"\n                name=\"description\"\n                type=\"text\"\n                placeholder=\"Description of the item\"\n                required\n                className=\"w-full border-gray-300 dark:border-gray-600\"\n              />\n              {state.errors?.description && (\n                <p className=\"text-red-500 text-sm\">\n                  {state.errors.description}\n                </p>\n              )}\n            </div>\n\n            <div className=\"space-y-3\">\n              <Label\n                htmlFor=\"quantity\"\n                className=\"text-gray-700 dark:text-gray-300\"\n              >\n                Quantity\n              </Label>\n              <Input\n                id=\"quantity\"\n                name=\"quantity\"\n                type=\"number\"\n                placeholder=\"Quantity\"\n                required\n                className=\"w-full border-gray-300 dark:border-gray-600\"\n              />\n              {state.errors?.quantity && (\n                <p className=\"text-red-500 text-sm\">{state.errors.quantity}</p>\n              )}\n            </div>\n          </div>\n\n          <SubmitButton text=\"Create Item\" />\n\n          {state?.message && (\n            <div className=\"mt-2 text-center text-sm text-red-500\">\n              <p>{state.message}</p>\n            </div>\n          )}\n        </form>\n      </div>\n    </div>\n  );\n}\n"
  },
  {
    "path": "nextjs-frontend/app/dashboard/deleteButton.tsx",
    "content": "\"use client\";\n\nimport { removeItem } from \"@/components/actions/items-action\";\nimport { DropdownMenuItem } from \"@/components/ui/dropdown-menu\";\n\ninterface DeleteButtonProps {\n  itemId: string;\n}\n\nexport function DeleteButton({ itemId }: DeleteButtonProps) {\n  const handleDelete = async () => {\n    await removeItem(itemId);\n  };\n\n  return (\n    <DropdownMenuItem\n      className=\"text-red-500 cursor-pointer\"\n      onClick={handleDelete}\n    >\n      Delete\n    </DropdownMenuItem>\n  );\n}\n"
  },
  {
    "path": "nextjs-frontend/app/dashboard/layout.tsx",
    "content": "import Link from \"next/link\";\nimport { Home, Users2, List } from \"lucide-react\";\nimport Image from \"next/image\";\n\nimport {\n  Breadcrumb,\n  BreadcrumbItem,\n  BreadcrumbLink,\n  BreadcrumbList,\n  BreadcrumbSeparator,\n} from \"@/components/ui/breadcrumb\";\nimport {\n  DropdownMenu,\n  DropdownMenuContent,\n  DropdownMenuItem,\n  DropdownMenuTrigger,\n} from \"@/components/ui/dropdown-menu\";\nimport { Avatar, AvatarFallback } from \"@/components/ui/avatar\";\nimport { logout } from \"@/components/actions/logout-action\";\n\nexport default function DashboardLayout({\n  children,\n}: {\n  children: React.ReactNode;\n}) {\n  return (\n    <div className=\"flex min-h-screen\">\n      <aside className=\"fixed inset-y-0 left-0 z-10 w-16 flex flex-col border-r bg-background p-4\">\n        <div className=\"flex flex-col items-center gap-8\">\n          <Link\n            href=\"/\"\n            className=\"flex items-center justify-center rounded-full\"\n          >\n            <Image\n              src=\"/images/vinta.png\"\n              alt=\"Vinta\"\n              width={64}\n              height={64}\n              className=\"object-cover transition-transform duration-200 hover:scale-105\"\n            />\n          </Link>\n          <Link\n            href=\"/dashboard\"\n            className=\"flex items-center gap-2 text-muted-foreground hover:text-foreground\"\n          >\n            <List className=\"h-5 w-5\" />\n          </Link>\n          <Link\n            href=\"/customers\"\n            className=\"flex items-center gap-2 text-muted-foreground hover:text-foreground\"\n          >\n            <Users2 className=\"h-5 w-5\" />\n          </Link>\n        </div>\n      </aside>\n      <main className=\"ml-16 w-full p-8 bg-muted/40\">\n        <header className=\"flex justify-between items-center mb-6\">\n          <Breadcrumb>\n            <BreadcrumbList>\n              <BreadcrumbItem>\n                <BreadcrumbLink asChild>\n                  <Link href=\"/\" className=\"flex items-center gap-2\">\n                    <Home className=\"h-4 w-4\" />\n                    <span>Home</span>\n                  </Link>\n                </BreadcrumbLink>\n              </BreadcrumbItem>\n              <BreadcrumbSeparator>/</BreadcrumbSeparator>\n              <BreadcrumbItem>\n                <BreadcrumbLink asChild>\n                  <Link href=\"/dashboard\" className=\"flex items-center gap-2\">\n                    <List className=\"h-4 w-4\" />\n                    <span>Dashboard</span>\n                  </Link>\n                </BreadcrumbLink>\n              </BreadcrumbItem>\n            </BreadcrumbList>\n          </Breadcrumb>\n          <div className=\"relative\">\n            <DropdownMenu>\n              <DropdownMenuTrigger asChild>\n                <button className=\"flex items-center justify-center w-10 h-10 rounded-full bg-gray-300 hover:bg-gray-400\">\n                  <Avatar>\n                    <AvatarFallback>U</AvatarFallback>\n                  </Avatar>\n                </button>\n              </DropdownMenuTrigger>\n              <DropdownMenuContent align=\"end\" side=\"bottom\">\n                <DropdownMenuItem>\n                  <Link\n                    href=\"/support\"\n                    className=\"block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100\"\n                  >\n                    Support\n                  </Link>\n                </DropdownMenuItem>\n                <DropdownMenuItem>\n                  <button\n                    onClick={logout}\n                    className=\"block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100\"\n                  >\n                    Logout\n                  </button>\n                </DropdownMenuItem>\n              </DropdownMenuContent>\n            </DropdownMenu>\n          </div>\n        </header>\n        <section className=\"grid gap-6\">{children}</section>\n      </main>\n    </div>\n  );\n}\n"
  },
  {
    "path": "nextjs-frontend/app/dashboard/page.tsx",
    "content": "import {\n  Table,\n  TableBody,\n  TableCell,\n  TableHead,\n  TableRow,\n  TableHeader,\n} from \"@/components/ui/table\";\nimport {\n  DropdownMenu,\n  DropdownMenuTrigger,\n  DropdownMenuContent,\n  DropdownMenuItem,\n} from \"@/components/ui/dropdown-menu\";\nimport { fetchItems } from \"@/components/actions/items-action\";\nimport { DeleteButton } from \"./deleteButton\";\nimport { ReadItemResponse } from \"@/app/openapi-client\";\nimport { Button } from \"@/components/ui/button\";\nimport Link from \"next/link\";\nimport { PageSizeSelector } from \"@/components/page-size-selector\";\nimport { PagePagination } from \"@/components/page-pagination\";\n\ninterface DashboardPageProps {\n  searchParams: Promise<{\n    page?: string;\n    size?: string;\n  }>;\n}\n\nexport default async function DashboardPage({\n  searchParams,\n}: DashboardPageProps) {\n  const params = await searchParams;\n  const page = Number(params.page) || 1;\n  const size = Number(params.size) || 10;\n\n  const items = (await fetchItems(page, size)) as ReadItemResponse;\n  const totalPages = Math.ceil((items.total || 0) / size);\n\n  return (\n    <div>\n      <h2 className=\"text-2xl font-semibold mb-6\">Welcome to your Dashboard</h2>\n      <p className=\"text-lg mb-6\">\n        Here, you can see the overview of your items and manage them.\n      </p>\n\n      <div className=\"mb-6\">\n        <Link href=\"/dashboard/add-item\">\n          <Button variant=\"outline\" className=\"text-lg px-4 py-2\">\n            Add New Item\n          </Button>\n        </Link>\n      </div>\n\n      <section className=\"p-6 bg-white rounded-lg shadow-lg mt-8\">\n        <div className=\"flex items-center justify-between mb-4\">\n          <h2 className=\"text-xl font-semibold\">Items</h2>\n          <PageSizeSelector currentSize={size} />\n        </div>\n\n        <Table className=\"min-w-full text-sm\">\n          <TableHeader>\n            <TableRow>\n              <TableHead className=\"w-[120px]\">Name</TableHead>\n              <TableHead>Description</TableHead>\n              <TableHead className=\"text-center\">Quantity</TableHead>\n              <TableHead className=\"text-center\">Actions</TableHead>\n            </TableRow>\n          </TableHeader>\n          <TableBody>\n            {!items.items?.length ? (\n              <TableRow>\n                <TableCell colSpan={4} className=\"text-center\">\n                  No results.\n                </TableCell>\n              </TableRow>\n            ) : (\n              items.items.map((item, index) => (\n                <TableRow key={index}>\n                  <TableCell>{item.name}</TableCell>\n                  <TableCell>{item.description}</TableCell>\n                  <TableCell className=\"text-center\">{item.quantity}</TableCell>\n                  <TableCell className=\"text-center\">\n                    <DropdownMenu>\n                      <DropdownMenuTrigger className=\"cursor-pointer p-1 text-gray-600 hover:text-gray-800\">\n                        <span className=\"text-lg font-semibold\">...</span>\n                      </DropdownMenuTrigger>\n                      <DropdownMenuContent className=\"p-2\">\n                        <DropdownMenuItem disabled={true}>\n                          Edit\n                        </DropdownMenuItem>\n                        <DeleteButton itemId={item.id} />\n                      </DropdownMenuContent>\n                    </DropdownMenu>\n                  </TableCell>\n                </TableRow>\n              ))\n            )}\n          </TableBody>\n        </Table>\n\n        {/* Pagination Controls */}\n        <PagePagination\n          currentPage={page}\n          totalPages={totalPages}\n          pageSize={size}\n          totalItems={items.total || 0}\n          basePath=\"/dashboard\"\n        />\n      </section>\n    </div>\n  );\n}\n"
  },
  {
    "path": "nextjs-frontend/app/globals.css",
    "content": "@tailwind base;\n@tailwind components;\n@tailwind utilities;\n@layer base {\n  :root {\n    --background: 0 0% 100%;\n    --foreground: 0 0% 3.9%;\n    --card: 0 0% 100%;\n    --card-foreground: 0 0% 3.9%;\n    --popover: 0 0% 100%;\n    --popover-foreground: 0 0% 3.9%;\n    --primary: 0 0% 9%;\n    --primary-foreground: 0 0% 98%;\n    --secondary: 0 0% 96.1%;\n    --secondary-foreground: 0 0% 9%;\n    --muted: 0 0% 96.1%;\n    --muted-foreground: 0 0% 45.1%;\n    --accent: 0 0% 96.1%;\n    --accent-foreground: 0 0% 9%;\n    --destructive: 0 84.2% 60.2%;\n    --destructive-foreground: 0 0% 98%;\n    --border: 0 0% 89.8%;\n    --input: 0 0% 89.8%;\n    --ring: 0 0% 3.9%;\n    --chart-1: 12 76% 61%;\n    --chart-2: 173 58% 39%;\n    --chart-3: 197 37% 24%;\n    --chart-4: 43 74% 66%;\n    --chart-5: 27 87% 67%;\n    --radius: 0.5rem;\n  }\n\n  .dark {\n    --background: 0 0% 3.9%;\n    --foreground: 0 0% 98%;\n    --card: 0 0% 3.9%;\n    --card-foreground: 0 0% 98%;\n    --popover: 0 0% 3.9%;\n    --popover-foreground: 0 0% 98%;\n    --primary: 0 0% 98%;\n    --primary-foreground: 0 0% 9%;\n    --secondary: 0 0% 14.9%;\n    --secondary-foreground: 0 0% 98%;\n    --muted: 0 0% 14.9%;\n    --muted-foreground: 0 0% 63.9%;\n    --accent: 0 0% 14.9%;\n    --accent-foreground: 0 0% 98%;\n    --destructive: 0 62.8% 30.6%;\n    --destructive-foreground: 0 0% 98%;\n    --border: 0 0% 14.9%;\n    --input: 0 0% 14.9%;\n    --ring: 0 0% 83.1%;\n    --chart-1: 220 70% 50%;\n    --chart-2: 160 60% 45%;\n    --chart-3: 30 80% 55%;\n    --chart-4: 280 65% 60%;\n    --chart-5: 340 75% 55%;\n  }\n}\n\n@layer base {\n  * {\n    @apply border-border;\n  }\n\n  body {\n    @apply bg-background text-foreground;\n  }\n}\n"
  },
  {
    "path": "nextjs-frontend/app/layout.tsx",
    "content": "import type { Metadata } from \"next\";\nimport localFont from \"next/font/local\";\nimport \"./globals.css\";\n\nconst geistSans = localFont({\n  src: \"./fonts/GeistVF.woff\",\n  variable: \"--font-geist-sans\",\n  weight: \"100 900\",\n});\nconst geistMono = localFont({\n  src: \"./fonts/GeistMonoVF.woff\",\n  variable: \"--font-geist-mono\",\n  weight: \"100 900\",\n});\n\nexport const metadata: Metadata = {\n  title: \"Create Next App\",\n  description: \"Generated by create next app\",\n};\n\nexport default function RootLayout({\n  children,\n}: Readonly<{\n  children: React.ReactNode;\n}>) {\n  return (\n    <html lang=\"en\">\n      <body className={`${geistSans.variable} ${geistMono.variable}`}>\n        {children}\n      </body>\n    </html>\n  );\n}\n"
  },
  {
    "path": "nextjs-frontend/app/login/page.tsx",
    "content": "\"use client\";\n\nimport Link from \"next/link\";\nimport {\n  Card,\n  CardContent,\n  CardDescription,\n  CardHeader,\n  CardTitle,\n} from \"@/components/ui/card\";\nimport { Input } from \"@/components/ui/input\";\nimport { Label } from \"@/components/ui/label\";\n\nimport { login } from \"@/components/actions/login-action\";\nimport { useActionState } from \"react\";\nimport { SubmitButton } from \"@/components/ui/submitButton\";\nimport { FieldError, FormError } from \"@/components/ui/FormError\";\n\nexport default function Page() {\n  const [state, dispatch] = useActionState(login, undefined);\n  return (\n    <div className=\"flex h-screen w-full items-center justify-center bg-gray-50 dark:bg-gray-900 px-4\">\n      <form action={dispatch}>\n        <Card className=\"w-full max-w-sm rounded-lg shadow-lg border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-800\">\n          <CardHeader className=\"text-center\">\n            <CardTitle className=\"text-2xl font-semibold text-gray-800 dark:text-white\">\n              Login\n            </CardTitle>\n            <CardDescription className=\"text-sm text-gray-600 dark:text-gray-400\">\n              Enter your email below to log in to your account.\n            </CardDescription>\n          </CardHeader>\n          <CardContent className=\"grid gap-6 p-6\">\n            <div className=\"grid gap-3\">\n              <Label\n                htmlFor=\"username\"\n                className=\"text-gray-700 dark:text-gray-300\"\n              >\n                Username\n              </Label>\n              <Input\n                id=\"username\"\n                name=\"username\"\n                type=\"email\"\n                placeholder=\"m@example.com\"\n                required\n                className=\"border-gray-300 dark:border-gray-600\"\n              />\n              <FieldError state={state} field=\"username\" />\n            </div>\n            <div className=\"grid gap-3\">\n              <Label\n                htmlFor=\"password\"\n                className=\"text-gray-700 dark:text-gray-300\"\n              >\n                Password\n              </Label>\n              <Input\n                id=\"password\"\n                name=\"password\"\n                type=\"password\"\n                required\n                className=\"border-gray-300 dark:border-gray-600\"\n              />\n              <FieldError state={state} field=\"password\" />\n              <Link\n                href=\"/password-recovery\"\n                className=\"ml-auto inline-block text-sm text-blue-500 hover:text-blue-600 dark:text-blue-400 dark:hover:text-blue-500\"\n              >\n                Forgot your password?\n              </Link>\n            </div>\n            <SubmitButton text=\"Sign In\" />\n            <FormError state={state} />\n            <div className=\"mt-4 text-center text-sm text-gray-600 dark:text-gray-400\">\n              Don&apos;t have an account?{\" \"}\n              <Link\n                href=\"/register\"\n                className=\"text-blue-500 hover:text-blue-600 dark:text-blue-400 dark:hover:text-blue-500\"\n              >\n                Sign up\n              </Link>\n            </div>\n          </CardContent>\n        </Card>\n      </form>\n    </div>\n  );\n}\n"
  },
  {
    "path": "nextjs-frontend/app/openapi-client/client/client.gen.ts",
    "content": "// This file is auto-generated by @hey-api/openapi-ts\n\nimport type { AxiosError, AxiosInstance, RawAxiosRequestHeaders } from \"axios\";\nimport axios from \"axios\";\n\nimport { createSseClient } from \"../core/serverSentEvents.gen\";\nimport type { HttpMethod } from \"../core/types.gen\";\nimport { getValidRequestBody } from \"../core/utils.gen\";\nimport type { Client, Config, RequestOptions } from \"./types.gen\";\nimport {\n  buildUrl,\n  createConfig,\n  mergeConfigs,\n  mergeHeaders,\n  setAuthParams,\n} from \"./utils.gen\";\n\nexport const createClient = (config: Config = {}): Client => {\n  let _config = mergeConfigs(createConfig(), config);\n\n  let instance: AxiosInstance;\n\n  if (_config.axios && !(\"Axios\" in _config.axios)) {\n    instance = _config.axios;\n  } else {\n    // eslint-disable-next-line @typescript-eslint/no-unused-vars\n    const { auth, ...configWithoutAuth } = _config;\n    instance = axios.create(configWithoutAuth);\n  }\n\n  const getConfig = (): Config => ({ ..._config });\n\n  const setConfig = (config: Config): Config => {\n    _config = mergeConfigs(_config, config);\n    instance.defaults = {\n      ...instance.defaults,\n      ..._config,\n      // @ts-expect-error\n      headers: mergeHeaders(instance.defaults.headers, _config.headers),\n    };\n    return getConfig();\n  };\n\n  const beforeRequest = async (options: RequestOptions) => {\n    const opts = {\n      ..._config,\n      ...options,\n      axios: options.axios ?? _config.axios ?? instance,\n      headers: mergeHeaders(_config.headers, options.headers),\n    };\n\n    if (opts.security) {\n      await setAuthParams({\n        ...opts,\n        security: opts.security,\n      });\n    }\n\n    if (opts.requestValidator) {\n      await opts.requestValidator(opts);\n    }\n\n    if (opts.body !== undefined && opts.bodySerializer) {\n      opts.body = opts.bodySerializer(opts.body);\n    }\n\n    const url = buildUrl(opts);\n\n    return { opts, url };\n  };\n\n  // @ts-expect-error\n  const request: Client[\"request\"] = async (options) => {\n    // @ts-expect-error\n    const { opts, url } = await beforeRequest(options);\n    try {\n      // assign Axios here for consistency with fetch\n      const _axios = opts.axios!;\n      // eslint-disable-next-line @typescript-eslint/no-unused-vars\n      const { auth, ...optsWithoutAuth } = opts;\n      const response = await _axios({\n        ...optsWithoutAuth,\n        baseURL: opts.baseURL as string,\n        data: getValidRequestBody(opts),\n        headers: opts.headers as RawAxiosRequestHeaders,\n        // let `paramsSerializer()` handle query params if it exists\n        params: opts.paramsSerializer ? opts.query : undefined,\n        url,\n      });\n\n      let { data } = response;\n\n      if (opts.responseType === \"json\") {\n        if (opts.responseValidator) {\n          await opts.responseValidator(data);\n        }\n\n        if (opts.responseTransformer) {\n          data = await opts.responseTransformer(data);\n        }\n      }\n\n      return {\n        ...response,\n        data: data ?? {},\n      };\n    } catch (error) {\n      const e = error as AxiosError;\n      if (opts.throwOnError) {\n        throw e;\n      }\n      // @ts-expect-error\n      e.error = e.response?.data ?? {};\n      return e;\n    }\n  };\n\n  const makeMethodFn =\n    (method: Uppercase<HttpMethod>) => (options: RequestOptions) =>\n      request({ ...options, method });\n\n  const makeSseFn =\n    (method: Uppercase<HttpMethod>) => async (options: RequestOptions) => {\n      const { opts, url } = await beforeRequest(options);\n      return createSseClient({\n        ...opts,\n        body: opts.body as BodyInit | null | undefined,\n        headers: opts.headers as Record<string, string>,\n        method,\n        // @ts-expect-error\n        signal: opts.signal,\n        url,\n      });\n    };\n\n  return {\n    buildUrl,\n    connect: makeMethodFn(\"CONNECT\"),\n    delete: makeMethodFn(\"DELETE\"),\n    get: makeMethodFn(\"GET\"),\n    getConfig,\n    head: makeMethodFn(\"HEAD\"),\n    instance,\n    options: makeMethodFn(\"OPTIONS\"),\n    patch: makeMethodFn(\"PATCH\"),\n    post: makeMethodFn(\"POST\"),\n    put: makeMethodFn(\"PUT\"),\n    request,\n    setConfig,\n    sse: {\n      connect: makeSseFn(\"CONNECT\"),\n      delete: makeSseFn(\"DELETE\"),\n      get: makeSseFn(\"GET\"),\n      head: makeSseFn(\"HEAD\"),\n      options: makeSseFn(\"OPTIONS\"),\n      patch: makeSseFn(\"PATCH\"),\n      post: makeSseFn(\"POST\"),\n      put: makeSseFn(\"PUT\"),\n      trace: makeSseFn(\"TRACE\"),\n    },\n    trace: makeMethodFn(\"TRACE\"),\n  } as Client;\n};\n"
  },
  {
    "path": "nextjs-frontend/app/openapi-client/client/index.ts",
    "content": "// This file is auto-generated by @hey-api/openapi-ts\n\nexport type { Auth } from \"../core/auth.gen\";\nexport type { QuerySerializerOptions } from \"../core/bodySerializer.gen\";\nexport {\n  formDataBodySerializer,\n  jsonBodySerializer,\n  urlSearchParamsBodySerializer,\n} from \"../core/bodySerializer.gen\";\nexport { buildClientParams } from \"../core/params.gen\";\nexport { createClient } from \"./client.gen\";\nexport type {\n  Client,\n  ClientOptions,\n  Config,\n  CreateClientConfig,\n  Options,\n  OptionsLegacyParser,\n  RequestOptions,\n  RequestResult,\n  TDataShape,\n} from \"./types.gen\";\nexport { createConfig } from \"./utils.gen\";\n"
  },
  {
    "path": "nextjs-frontend/app/openapi-client/client/types.gen.ts",
    "content": "// This file is auto-generated by @hey-api/openapi-ts\n\nimport type {\n  AxiosError,\n  AxiosInstance,\n  AxiosRequestHeaders,\n  AxiosResponse,\n  AxiosStatic,\n  CreateAxiosDefaults,\n} from \"axios\";\n\nimport type { Auth } from \"../core/auth.gen\";\nimport type {\n  ServerSentEventsOptions,\n  ServerSentEventsResult,\n} from \"../core/serverSentEvents.gen\";\nimport type {\n  Client as CoreClient,\n  Config as CoreConfig,\n} from \"../core/types.gen\";\n\nexport interface Config<T extends ClientOptions = ClientOptions>\n  extends Omit<CreateAxiosDefaults, \"auth\" | \"baseURL\" | \"headers\" | \"method\">,\n    CoreConfig {\n  /**\n   * Axios implementation. You can use this option to provide either an\n   * `AxiosStatic` or an `AxiosInstance`.\n   *\n   * @default axios\n   */\n  axios?: AxiosStatic | AxiosInstance;\n  /**\n   * Base URL for all requests made by this client.\n   */\n  baseURL?: T[\"baseURL\"];\n  /**\n   * An object containing any HTTP headers that you want to pre-populate your\n   * `Headers` object with.\n   *\n   * {@link https://developer.mozilla.org/docs/Web/API/Headers/Headers#init See more}\n   */\n  headers?:\n    | AxiosRequestHeaders\n    | Record<\n        string,\n        | string\n        | number\n        | boolean\n        | (string | number | boolean)[]\n        | null\n        | undefined\n        | unknown\n      >;\n  /**\n   * Throw an error instead of returning it in the response?\n   *\n   * @default false\n   */\n  throwOnError?: T[\"throwOnError\"];\n}\n\nexport interface RequestOptions<\n  TData = unknown,\n  ThrowOnError extends boolean = boolean,\n  Url extends string = string,\n> extends Config<{\n      throwOnError: ThrowOnError;\n    }>,\n    Pick<\n      ServerSentEventsOptions<TData>,\n      | \"onSseError\"\n      | \"onSseEvent\"\n      | \"sseDefaultRetryDelay\"\n      | \"sseMaxRetryAttempts\"\n      | \"sseMaxRetryDelay\"\n    > {\n  /**\n   * Any body that you want to add to your request.\n   *\n   * {@link https://developer.mozilla.org/docs/Web/API/fetch#body}\n   */\n  body?: unknown;\n  path?: Record<string, unknown>;\n  query?: Record<string, unknown>;\n  /**\n   * Security mechanism(s) to use for the request.\n   */\n  security?: ReadonlyArray<Auth>;\n  url: Url;\n}\n\nexport interface ClientOptions {\n  baseURL?: string;\n  throwOnError?: boolean;\n}\n\nexport type RequestResult<\n  TData = unknown,\n  TError = unknown,\n  ThrowOnError extends boolean = boolean,\n> = ThrowOnError extends true\n  ? Promise<\n      AxiosResponse<\n        TData extends Record<string, unknown> ? TData[keyof TData] : TData\n      >\n    >\n  : Promise<\n      | (AxiosResponse<\n          TData extends Record<string, unknown> ? TData[keyof TData] : TData\n        > & { error: undefined })\n      | (AxiosError<\n          TError extends Record<string, unknown> ? TError[keyof TError] : TError\n        > & {\n          data: undefined;\n          error: TError extends Record<string, unknown>\n            ? TError[keyof TError]\n            : TError;\n        })\n    >;\n\ntype MethodFn = <\n  TData = unknown,\n  TError = unknown,\n  ThrowOnError extends boolean = false,\n>(\n  options: Omit<RequestOptions<TData, ThrowOnError>, \"method\">,\n) => RequestResult<TData, TError, ThrowOnError>;\n\ntype SseFn = <\n  TData = unknown,\n  TError = unknown,\n  ThrowOnError extends boolean = false,\n>(\n  options: Omit<RequestOptions<TData, ThrowOnError>, \"method\">,\n) => Promise<ServerSentEventsResult<TData, TError>>;\n\ntype RequestFn = <\n  TData = unknown,\n  TError = unknown,\n  ThrowOnError extends boolean = false,\n>(\n  options: Omit<RequestOptions<TData, ThrowOnError>, \"method\"> &\n    Pick<Required<RequestOptions<TData, ThrowOnError>>, \"method\">,\n) => RequestResult<TData, TError, ThrowOnError>;\n\ntype BuildUrlFn = <\n  TData extends {\n    body?: unknown;\n    path?: Record<string, unknown>;\n    query?: Record<string, unknown>;\n    url: string;\n  },\n>(\n  options: Pick<TData, \"url\"> & Omit<Options<TData>, \"axios\">,\n) => string;\n\nexport type Client = CoreClient<\n  RequestFn,\n  Config,\n  MethodFn,\n  BuildUrlFn,\n  SseFn\n> & {\n  instance: AxiosInstance;\n};\n\n/**\n * The `createClientConfig()` function will be called on client initialization\n * and the returned object will become the client's initial configuration.\n *\n * You may want to initialize your client this way instead of calling\n * `setConfig()`. This is useful for example if you're using Next.js\n * to ensure your client always has the correct values.\n */\nexport type CreateClientConfig<T extends ClientOptions = ClientOptions> = (\n  override?: Config<ClientOptions & T>,\n) => Config<Required<ClientOptions> & T>;\n\nexport interface TDataShape {\n  body?: unknown;\n  headers?: unknown;\n  path?: unknown;\n  query?: unknown;\n  url: string;\n}\n\ntype OmitKeys<T, K> = Pick<T, Exclude<keyof T, K>>;\n\nexport type Options<\n  TData extends TDataShape = TDataShape,\n  ThrowOnError extends boolean = boolean,\n  TResponse = unknown,\n> = OmitKeys<\n  RequestOptions<TResponse, ThrowOnError>,\n  \"body\" | \"path\" | \"query\" | \"url\"\n> &\n  Omit<TData, \"url\">;\n\nexport type OptionsLegacyParser<\n  TData = unknown,\n  ThrowOnError extends boolean = boolean,\n> = TData extends { body?: any }\n  ? TData extends { headers?: any }\n    ? OmitKeys<\n        RequestOptions<unknown, ThrowOnError>,\n        \"body\" | \"headers\" | \"url\"\n      > &\n        TData\n    : OmitKeys<RequestOptions<unknown, ThrowOnError>, \"body\" | \"url\"> &\n        TData &\n        Pick<RequestOptions<unknown, ThrowOnError>, \"headers\">\n  : TData extends { headers?: any }\n    ? OmitKeys<RequestOptions<unknown, ThrowOnError>, \"headers\" | \"url\"> &\n        TData &\n        Pick<RequestOptions<unknown, ThrowOnError>, \"body\">\n    : OmitKeys<RequestOptions<unknown, ThrowOnError>, \"url\"> & TData;\n"
  },
  {
    "path": "nextjs-frontend/app/openapi-client/client/utils.gen.ts",
    "content": "// This file is auto-generated by @hey-api/openapi-ts\n\nimport { getAuthToken } from \"../core/auth.gen\";\nimport type { QuerySerializerOptions } from \"../core/bodySerializer.gen\";\nimport {\n  serializeArrayParam,\n  serializeObjectParam,\n  serializePrimitiveParam,\n} from \"../core/pathSerializer.gen\";\nimport { getUrl } from \"../core/utils.gen\";\nimport type {\n  Client,\n  ClientOptions,\n  Config,\n  RequestOptions,\n} from \"./types.gen\";\n\nexport const createQuerySerializer = <T = unknown>({\n  allowReserved,\n  array,\n  object,\n}: QuerySerializerOptions = {}) => {\n  const querySerializer = (queryParams: T) => {\n    const search: string[] = [];\n    if (queryParams && typeof queryParams === \"object\") {\n      for (const name in queryParams) {\n        const value = queryParams[name];\n\n        if (value === undefined || value === null) {\n          continue;\n        }\n\n        if (Array.isArray(value)) {\n          const serializedArray = serializeArrayParam({\n            allowReserved,\n            explode: true,\n            name,\n            style: \"form\",\n            value,\n            ...array,\n          });\n          if (serializedArray) search.push(serializedArray);\n        } else if (typeof value === \"object\") {\n          const serializedObject = serializeObjectParam({\n            allowReserved,\n            explode: true,\n            name,\n            style: \"deepObject\",\n            value: value as Record<string, unknown>,\n            ...object,\n          });\n          if (serializedObject) search.push(serializedObject);\n        } else {\n          const serializedPrimitive = serializePrimitiveParam({\n            allowReserved,\n            name,\n            value: value as string,\n          });\n          if (serializedPrimitive) search.push(serializedPrimitive);\n        }\n      }\n    }\n    return search.join(\"&\");\n  };\n  return querySerializer;\n};\n\nconst checkForExistence = (\n  options: Pick<RequestOptions, \"auth\" | \"query\"> & {\n    headers: Record<any, unknown>;\n  },\n  name?: string,\n): boolean => {\n  if (!name) {\n    return false;\n  }\n  if (name in options.headers || options.query?.[name]) {\n    return true;\n  }\n  if (\n    \"Cookie\" in options.headers &&\n    options.headers[\"Cookie\"] &&\n    typeof options.headers[\"Cookie\"] === \"string\"\n  ) {\n    return options.headers[\"Cookie\"].includes(`${name}=`);\n  }\n  return false;\n};\n\nexport const setAuthParams = async ({\n  security,\n  ...options\n}: Pick<Required<RequestOptions>, \"security\"> &\n  Pick<RequestOptions, \"auth\" | \"query\"> & {\n    headers: Record<any, unknown>;\n  }) => {\n  for (const auth of security) {\n    if (checkForExistence(options, auth.name)) {\n      continue;\n    }\n    const token = await getAuthToken(auth, options.auth);\n\n    if (!token) {\n      continue;\n    }\n\n    const name = auth.name ?? \"Authorization\";\n\n    switch (auth.in) {\n      case \"query\":\n        if (!options.query) {\n          options.query = {};\n        }\n        options.query[name] = token;\n        break;\n      case \"cookie\": {\n        const value = `${name}=${token}`;\n        if (\"Cookie\" in options.headers && options.headers[\"Cookie\"]) {\n          options.headers[\"Cookie\"] = `${options.headers[\"Cookie\"]}; ${value}`;\n        } else {\n          options.headers[\"Cookie\"] = value;\n        }\n        break;\n      }\n      case \"header\":\n      default:\n        options.headers[name] = token;\n        break;\n    }\n  }\n};\n\nexport const buildUrl: Client[\"buildUrl\"] = (options) =>\n  getUrl({\n    baseUrl: options.baseURL as string,\n    path: options.path,\n    // let `paramsSerializer()` handle query params if it exists\n    query: !options.paramsSerializer ? options.query : undefined,\n    querySerializer:\n      typeof options.querySerializer === \"function\"\n        ? options.querySerializer\n        : createQuerySerializer(options.querySerializer),\n    url: options.url,\n  });\n\nexport const mergeConfigs = (a: Config, b: Config): Config => {\n  const config = { ...a, ...b };\n  config.headers = mergeHeaders(a.headers, b.headers);\n  return config;\n};\n\n/**\n * Special Axios headers keywords allowing to set headers by request method.\n */\nexport const axiosHeadersKeywords = [\n  \"common\",\n  \"delete\",\n  \"get\",\n  \"head\",\n  \"patch\",\n  \"post\",\n  \"put\",\n] as const;\n\nexport const mergeHeaders = (\n  ...headers: Array<Required<Config>[\"headers\"] | undefined>\n): Record<any, unknown> => {\n  const mergedHeaders: Record<any, unknown> = {};\n  for (const header of headers) {\n    if (!header || typeof header !== \"object\") {\n      continue;\n    }\n\n    const iterator = Object.entries(header);\n\n    for (const [key, value] of iterator) {\n      if (\n        axiosHeadersKeywords.includes(\n          key as (typeof axiosHeadersKeywords)[number],\n        ) &&\n        typeof value === \"object\"\n      ) {\n        mergedHeaders[key] = {\n          ...(mergedHeaders[key] as Record<any, unknown>),\n          ...value,\n        };\n      } else if (value === null) {\n        delete mergedHeaders[key];\n      } else if (Array.isArray(value)) {\n        for (const v of value) {\n          // @ts-expect-error\n          mergedHeaders[key] = [...(mergedHeaders[key] ?? []), v as string];\n        }\n      } else if (value !== undefined) {\n        // assume object headers are meant to be JSON stringified, i.e. their\n        // content value in OpenAPI specification is 'application/json'\n        mergedHeaders[key] =\n          typeof value === \"object\" ? JSON.stringify(value) : (value as string);\n      }\n    }\n  }\n  return mergedHeaders;\n};\n\nexport const createConfig = <T extends ClientOptions = ClientOptions>(\n  override: Config<Omit<ClientOptions, keyof T> & T> = {},\n): Config<Omit<ClientOptions, keyof T> & T> => ({\n  ...override,\n});\n"
  },
  {
    "path": "nextjs-frontend/app/openapi-client/client.gen.ts",
    "content": "// This file is auto-generated by @hey-api/openapi-ts\n\nimport type { ClientOptions } from \"./types.gen\";\nimport {\n  type ClientOptions as DefaultClientOptions,\n  type Config,\n  createClient,\n  createConfig,\n} from \"./client\";\n\n/**\n * The `createClientConfig()` function will be called on client initialization\n * and the returned object will become the client's initial configuration.\n *\n * You may want to initialize your client this way instead of calling\n * `setConfig()`. This is useful for example if you're using Next.js\n * to ensure your client always has the correct values.\n */\nexport type CreateClientConfig<T extends DefaultClientOptions = ClientOptions> =\n  (\n    override?: Config<DefaultClientOptions & T>,\n  ) => Config<Required<DefaultClientOptions> & T>;\n\nexport const client = createClient(createConfig<ClientOptions>());\n"
  },
  {
    "path": "nextjs-frontend/app/openapi-client/core/auth.gen.ts",
    "content": "// This file is auto-generated by @hey-api/openapi-ts\n\nexport type AuthToken = string | undefined;\n\nexport interface Auth {\n  /**\n   * Which part of the request do we use to send the auth?\n   *\n   * @default 'header'\n   */\n  in?: \"header\" | \"query\" | \"cookie\";\n  /**\n   * Header or query parameter name.\n   *\n   * @default 'Authorization'\n   */\n  name?: string;\n  scheme?: \"basic\" | \"bearer\";\n  type: \"apiKey\" | \"http\";\n}\n\nexport const getAuthToken = async (\n  auth: Auth,\n  callback: ((auth: Auth) => Promise<AuthToken> | AuthToken) | AuthToken,\n): Promise<string | undefined> => {\n  const token =\n    typeof callback === \"function\" ? await callback(auth) : callback;\n\n  if (!token) {\n    return;\n  }\n\n  if (auth.scheme === \"bearer\") {\n    return `Bearer ${token}`;\n  }\n\n  if (auth.scheme === \"basic\") {\n    return `Basic ${btoa(token)}`;\n  }\n\n  return token;\n};\n"
  },
  {
    "path": "nextjs-frontend/app/openapi-client/core/bodySerializer.gen.ts",
    "content": "// This file is auto-generated by @hey-api/openapi-ts\n\nimport type {\n  ArrayStyle,\n  ObjectStyle,\n  SerializerOptions,\n} from \"./pathSerializer.gen\";\n\nexport type QuerySerializer = (query: Record<string, unknown>) => string;\n\nexport type BodySerializer = (body: any) => any;\n\nexport interface QuerySerializerOptions {\n  allowReserved?: boolean;\n  array?: SerializerOptions<ArrayStyle>;\n  object?: SerializerOptions<ObjectStyle>;\n}\n\nconst serializeFormDataPair = (\n  data: FormData,\n  key: string,\n  value: unknown,\n): void => {\n  if (typeof value === \"string\" || value instanceof Blob) {\n    data.append(key, value);\n  } else if (value instanceof Date) {\n    data.append(key, value.toISOString());\n  } else {\n    data.append(key, JSON.stringify(value));\n  }\n};\n\nconst serializeUrlSearchParamsPair = (\n  data: URLSearchParams,\n  key: string,\n  value: unknown,\n): void => {\n  if (typeof value === \"string\") {\n    data.append(key, value);\n  } else {\n    data.append(key, JSON.stringify(value));\n  }\n};\n\nexport const formDataBodySerializer = {\n  bodySerializer: <T extends Record<string, any> | Array<Record<string, any>>>(\n    body: T,\n  ): FormData => {\n    const data = new FormData();\n\n    Object.entries(body).forEach(([key, value]) => {\n      if (value === undefined || value === null) {\n        return;\n      }\n      if (Array.isArray(value)) {\n        value.forEach((v) => serializeFormDataPair(data, key, v));\n      } else {\n        serializeFormDataPair(data, key, value);\n      }\n    });\n\n    return data;\n  },\n};\n\nexport const jsonBodySerializer = {\n  bodySerializer: <T>(body: T): string =>\n    JSON.stringify(body, (_key, value) =>\n      typeof value === \"bigint\" ? value.toString() : value,\n    ),\n};\n\nexport const urlSearchParamsBodySerializer = {\n  bodySerializer: <T extends Record<string, any> | Array<Record<string, any>>>(\n    body: T,\n  ): string => {\n    const data = new URLSearchParams();\n\n    Object.entries(body).forEach(([key, value]) => {\n      if (value === undefined || value === null) {\n        return;\n      }\n      if (Array.isArray(value)) {\n        value.forEach((v) => serializeUrlSearchParamsPair(data, key, v));\n      } else {\n        serializeUrlSearchParamsPair(data, key, value);\n      }\n    });\n\n    return data.toString();\n  },\n};\n"
  },
  {
    "path": "nextjs-frontend/app/openapi-client/core/params.gen.ts",
    "content": "// This file is auto-generated by @hey-api/openapi-ts\n\ntype Slot = \"body\" | \"headers\" | \"path\" | \"query\";\n\nexport type Field =\n  | {\n      in: Exclude<Slot, \"body\">;\n      /**\n       * Field name. This is the name we want the user to see and use.\n       */\n      key: string;\n      /**\n       * Field mapped name. This is the name we want to use in the request.\n       * If omitted, we use the same value as `key`.\n       */\n      map?: string;\n    }\n  | {\n      in: Extract<Slot, \"body\">;\n      /**\n       * Key isn't required for bodies.\n       */\n      key?: string;\n      map?: string;\n    };\n\nexport interface Fields {\n  allowExtra?: Partial<Record<Slot, boolean>>;\n  args?: ReadonlyArray<Field>;\n}\n\nexport type FieldsConfig = ReadonlyArray<Field | Fields>;\n\nconst extraPrefixesMap: Record<string, Slot> = {\n  $body_: \"body\",\n  $headers_: \"headers\",\n  $path_: \"path\",\n  $query_: \"query\",\n};\nconst extraPrefixes = Object.entries(extraPrefixesMap);\n\ntype KeyMap = Map<\n  string,\n  {\n    in: Slot;\n    map?: string;\n  }\n>;\n\nconst buildKeyMap = (fields: FieldsConfig, map?: KeyMap): KeyMap => {\n  if (!map) {\n    map = new Map();\n  }\n\n  for (const config of fields) {\n    if (\"in\" in config) {\n      if (config.key) {\n        map.set(config.key, {\n          in: config.in,\n          map: config.map,\n        });\n      }\n    } else if (config.args) {\n      buildKeyMap(config.args, map);\n    }\n  }\n\n  return map;\n};\n\ninterface Params {\n  body: unknown;\n  headers: Record<string, unknown>;\n  path: Record<string, unknown>;\n  query: Record<string, unknown>;\n}\n\nconst stripEmptySlots = (params: Params) => {\n  for (const [slot, value] of Object.entries(params)) {\n    if (value && typeof value === \"object\" && !Object.keys(value).length) {\n      delete params[slot as Slot];\n    }\n  }\n};\n\nexport const buildClientParams = (\n  args: ReadonlyArray<unknown>,\n  fields: FieldsConfig,\n) => {\n  const params: Params = {\n    body: {},\n    headers: {},\n    path: {},\n    query: {},\n  };\n\n  const map = buildKeyMap(fields);\n\n  let config: FieldsConfig[number] | undefined;\n\n  for (const [index, arg] of args.entries()) {\n    if (fields[index]) {\n      config = fields[index];\n    }\n\n    if (!config) {\n      continue;\n    }\n\n    if (\"in\" in config) {\n      if (config.key) {\n        const field = map.get(config.key)!;\n        const name = field.map || config.key;\n        (params[field.in] as Record<string, unknown>)[name] = arg;\n      } else {\n        params.body = arg;\n      }\n    } else {\n      for (const [key, value] of Object.entries(arg ?? {})) {\n        const field = map.get(key);\n\n        if (field) {\n          const name = field.map || key;\n          (params[field.in] as Record<string, unknown>)[name] = value;\n        } else {\n          const extra = extraPrefixes.find(([prefix]) =>\n            key.startsWith(prefix),\n          );\n\n          if (extra) {\n            const [prefix, slot] = extra;\n            (params[slot] as Record<string, unknown>)[\n              key.slice(prefix.length)\n            ] = value;\n          } else {\n            for (const [slot, allowed] of Object.entries(\n              config.allowExtra ?? {},\n            )) {\n              if (allowed) {\n                (params[slot as Slot] as Record<string, unknown>)[key] = value;\n                break;\n              }\n            }\n          }\n        }\n      }\n    }\n  }\n\n  stripEmptySlots(params);\n\n  return params;\n};\n"
  },
  {
    "path": "nextjs-frontend/app/openapi-client/core/pathSerializer.gen.ts",
    "content": "// This file is auto-generated by @hey-api/openapi-ts\n\ninterface SerializeOptions<T>\n  extends SerializePrimitiveOptions,\n    SerializerOptions<T> {}\n\ninterface SerializePrimitiveOptions {\n  allowReserved?: boolean;\n  name: string;\n}\n\nexport interface SerializerOptions<T> {\n  /**\n   * @default true\n   */\n  explode: boolean;\n  style: T;\n}\n\nexport type ArrayStyle = \"form\" | \"spaceDelimited\" | \"pipeDelimited\";\nexport type ArraySeparatorStyle = ArrayStyle | MatrixStyle;\ntype MatrixStyle = \"label\" | \"matrix\" | \"simple\";\nexport type ObjectStyle = \"form\" | \"deepObject\";\ntype ObjectSeparatorStyle = ObjectStyle | MatrixStyle;\n\ninterface SerializePrimitiveParam extends SerializePrimitiveOptions {\n  value: string;\n}\n\nexport const separatorArrayExplode = (style: ArraySeparatorStyle) => {\n  switch (style) {\n    case \"label\":\n      return \".\";\n    case \"matrix\":\n      return \";\";\n    case \"simple\":\n      return \",\";\n    default:\n      return \"&\";\n  }\n};\n\nexport const separatorArrayNoExplode = (style: ArraySeparatorStyle) => {\n  switch (style) {\n    case \"form\":\n      return \",\";\n    case \"pipeDelimited\":\n      return \"|\";\n    case \"spaceDelimited\":\n      return \"%20\";\n    default:\n      return \",\";\n  }\n};\n\nexport const separatorObjectExplode = (style: ObjectSeparatorStyle) => {\n  switch (style) {\n    case \"label\":\n      return \".\";\n    case \"matrix\":\n      return \";\";\n    case \"simple\":\n      return \",\";\n    default:\n      return \"&\";\n  }\n};\n\nexport const serializeArrayParam = ({\n  allowReserved,\n  explode,\n  name,\n  style,\n  value,\n}: SerializeOptions<ArraySeparatorStyle> & {\n  value: unknown[];\n}) => {\n  if (!explode) {\n    const joinedValues = (\n      allowReserved ? value : value.map((v) => encodeURIComponent(v as string))\n    ).join(separatorArrayNoExplode(style));\n    switch (style) {\n      case \"label\":\n        return `.${joinedValues}`;\n      case \"matrix\":\n        return `;${name}=${joinedValues}`;\n      case \"simple\":\n        return joinedValues;\n      default:\n        return `${name}=${joinedValues}`;\n    }\n  }\n\n  const separator = separatorArrayExplode(style);\n  const joinedValues = value\n    .map((v) => {\n      if (style === \"label\" || style === \"simple\") {\n        return allowReserved ? v : encodeURIComponent(v as string);\n      }\n\n      return serializePrimitiveParam({\n        allowReserved,\n        name,\n        value: v as string,\n      });\n    })\n    .join(separator);\n  return style === \"label\" || style === \"matrix\"\n    ? separator + joinedValues\n    : joinedValues;\n};\n\nexport const serializePrimitiveParam = ({\n  allowReserved,\n  name,\n  value,\n}: SerializePrimitiveParam) => {\n  if (value === undefined || value === null) {\n    return \"\";\n  }\n\n  if (typeof value === \"object\") {\n    throw new Error(\n      \"Deeply-nested arrays/objects aren’t supported. Provide your own `querySerializer()` to handle these.\",\n    );\n  }\n\n  return `${name}=${allowReserved ? value : encodeURIComponent(value)}`;\n};\n\nexport const serializeObjectParam = ({\n  allowReserved,\n  explode,\n  name,\n  style,\n  value,\n  valueOnly,\n}: SerializeOptions<ObjectSeparatorStyle> & {\n  value: Record<string, unknown> | Date;\n  valueOnly?: boolean;\n}) => {\n  if (value instanceof Date) {\n    return valueOnly ? value.toISOString() : `${name}=${value.toISOString()}`;\n  }\n\n  if (style !== \"deepObject\" && !explode) {\n    let values: string[] = [];\n    Object.entries(value).forEach(([key, v]) => {\n      values = [\n        ...values,\n        key,\n        allowReserved ? (v as string) : encodeURIComponent(v as string),\n      ];\n    });\n    const joinedValues = values.join(\",\");\n    switch (style) {\n      case \"form\":\n        return `${name}=${joinedValues}`;\n      case \"label\":\n        return `.${joinedValues}`;\n      case \"matrix\":\n        return `;${name}=${joinedValues}`;\n      default:\n        return joinedValues;\n    }\n  }\n\n  const separator = separatorObjectExplode(style);\n  const joinedValues = Object.entries(value)\n    .map(([key, v]) =>\n      serializePrimitiveParam({\n        allowReserved,\n        name: style === \"deepObject\" ? `${name}[${key}]` : key,\n        value: v as string,\n      }),\n    )\n    .join(separator);\n  return style === \"label\" || style === \"matrix\"\n    ? separator + joinedValues\n    : joinedValues;\n};\n"
  },
  {
    "path": "nextjs-frontend/app/openapi-client/core/serverSentEvents.gen.ts",
    "content": "// This file is auto-generated by @hey-api/openapi-ts\n\nimport type { Config } from \"./types.gen\";\n\nexport type ServerSentEventsOptions<TData = unknown> = Omit<\n  RequestInit,\n  \"method\"\n> &\n  Pick<Config, \"method\" | \"responseTransformer\" | \"responseValidator\"> & {\n    /**\n     * Fetch API implementation. You can use this option to provide a custom\n     * fetch instance.\n     *\n     * @default globalThis.fetch\n     */\n    fetch?: typeof fetch;\n    /**\n     * Implementing clients can call request interceptors inside this hook.\n     */\n    onRequest?: (url: string, init: RequestInit) => Promise<Request>;\n    /**\n     * Callback invoked when a network or parsing error occurs during streaming.\n     *\n     * This option applies only if the endpoint returns a stream of events.\n     *\n     * @param error The error that occurred.\n     */\n    onSseError?: (error: unknown) => void;\n    /**\n     * Callback invoked when an event is streamed from the server.\n     *\n     * This option applies only if the endpoint returns a stream of events.\n     *\n     * @param event Event streamed from the server.\n     * @returns Nothing (void).\n     */\n    onSseEvent?: (event: StreamEvent<TData>) => void;\n    serializedBody?: RequestInit[\"body\"];\n    /**\n     * Default retry delay in milliseconds.\n     *\n     * This option applies only if the endpoint returns a stream of events.\n     *\n     * @default 3000\n     */\n    sseDefaultRetryDelay?: number;\n    /**\n     * Maximum number of retry attempts before giving up.\n     */\n    sseMaxRetryAttempts?: number;\n    /**\n     * Maximum retry delay in milliseconds.\n     *\n     * Applies only when exponential backoff is used.\n     *\n     * This option applies only if the endpoint returns a stream of events.\n     *\n     * @default 30000\n     */\n    sseMaxRetryDelay?: number;\n    /**\n     * Optional sleep function for retry backoff.\n     *\n     * Defaults to using `setTimeout`.\n     */\n    sseSleepFn?: (ms: number) => Promise<void>;\n    url: string;\n  };\n\nexport interface StreamEvent<TData = unknown> {\n  data: TData;\n  event?: string;\n  id?: string;\n  retry?: number;\n}\n\nexport type ServerSentEventsResult<\n  TData = unknown,\n  TReturn = void,\n  TNext = unknown,\n> = {\n  stream: AsyncGenerator<\n    TData extends Record<string, unknown> ? TData[keyof TData] : TData,\n    TReturn,\n    TNext\n  >;\n};\n\nexport const createSseClient = <TData = unknown>({\n  onRequest,\n  onSseError,\n  onSseEvent,\n  responseTransformer,\n  responseValidator,\n  sseDefaultRetryDelay,\n  sseMaxRetryAttempts,\n  sseMaxRetryDelay,\n  sseSleepFn,\n  url,\n  ...options\n}: ServerSentEventsOptions): ServerSentEventsResult<TData> => {\n  let lastEventId: string | undefined;\n\n  const sleep =\n    sseSleepFn ??\n    ((ms: number) => new Promise((resolve) => setTimeout(resolve, ms)));\n\n  const createStream = async function* () {\n    let retryDelay: number = sseDefaultRetryDelay ?? 3000;\n    let attempt = 0;\n    const signal = options.signal ?? new AbortController().signal;\n\n    while (true) {\n      if (signal.aborted) break;\n\n      attempt++;\n\n      const headers =\n        options.headers instanceof Headers\n          ? options.headers\n          : new Headers(options.headers as Record<string, string> | undefined);\n\n      if (lastEventId !== undefined) {\n        headers.set(\"Last-Event-ID\", lastEventId);\n      }\n\n      try {\n        const requestInit: RequestInit = {\n          redirect: \"follow\",\n          ...options,\n          body: options.serializedBody,\n          headers,\n          signal,\n        };\n        let request = new Request(url, requestInit);\n        if (onRequest) {\n          request = await onRequest(url, requestInit);\n        }\n        // fetch must be assigned here, otherwise it would throw the error:\n        // TypeError: Failed to execute 'fetch' on 'Window': Illegal invocation\n        const _fetch = options.fetch ?? globalThis.fetch;\n        const response = await _fetch(request);\n\n        if (!response.ok)\n          throw new Error(\n            `SSE failed: ${response.status} ${response.statusText}`,\n          );\n\n        if (!response.body) throw new Error(\"No body in SSE response\");\n\n        const reader = response.body\n          .pipeThrough(new TextDecoderStream())\n          .getReader();\n\n        let buffer = \"\";\n\n        const abortHandler = () => {\n          try {\n            reader.cancel();\n          } catch {\n            // noop\n          }\n        };\n\n        signal.addEventListener(\"abort\", abortHandler);\n\n        try {\n          while (true) {\n            const { done, value } = await reader.read();\n            if (done) break;\n            buffer += value;\n\n            const chunks = buffer.split(\"\\n\\n\");\n            buffer = chunks.pop() ?? \"\";\n\n            for (const chunk of chunks) {\n              const lines = chunk.split(\"\\n\");\n              const dataLines: Array<string> = [];\n              let eventName: string | undefined;\n\n              for (const line of lines) {\n                if (line.startsWith(\"data:\")) {\n                  dataLines.push(line.replace(/^data:\\s*/, \"\"));\n                } else if (line.startsWith(\"event:\")) {\n                  eventName = line.replace(/^event:\\s*/, \"\");\n                } else if (line.startsWith(\"id:\")) {\n                  lastEventId = line.replace(/^id:\\s*/, \"\");\n                } else if (line.startsWith(\"retry:\")) {\n                  const parsed = Number.parseInt(\n                    line.replace(/^retry:\\s*/, \"\"),\n                    10,\n                  );\n                  if (!Number.isNaN(parsed)) {\n                    retryDelay = parsed;\n                  }\n                }\n              }\n\n              let data: unknown;\n              let parsedJson = false;\n\n              if (dataLines.length) {\n                const rawData = dataLines.join(\"\\n\");\n                try {\n                  data = JSON.parse(rawData);\n                  parsedJson = true;\n                } catch {\n                  data = rawData;\n                }\n              }\n\n              if (parsedJson) {\n                if (responseValidator) {\n                  await responseValidator(data);\n                }\n\n                if (responseTransformer) {\n                  data = await responseTransformer(data);\n                }\n              }\n\n              onSseEvent?.({\n                data,\n                event: eventName,\n                id: lastEventId,\n                retry: retryDelay,\n              });\n\n              if (dataLines.length) {\n                yield data as any;\n              }\n            }\n          }\n        } finally {\n          signal.removeEventListener(\"abort\", abortHandler);\n          reader.releaseLock();\n        }\n\n        break; // exit loop on normal completion\n      } catch (error) {\n        // connection failed or aborted; retry after delay\n        onSseError?.(error);\n\n        if (\n          sseMaxRetryAttempts !== undefined &&\n          attempt >= sseMaxRetryAttempts\n        ) {\n          break; // stop after firing error\n        }\n\n        // exponential backoff: double retry each attempt, cap at 30s\n        const backoff = Math.min(\n          retryDelay * 2 ** (attempt - 1),\n          sseMaxRetryDelay ?? 30000,\n        );\n        await sleep(backoff);\n      }\n    }\n  };\n\n  const stream = createStream();\n\n  return { stream };\n};\n"
  },
  {
    "path": "nextjs-frontend/app/openapi-client/core/types.gen.ts",
    "content": "// This file is auto-generated by @hey-api/openapi-ts\n\nimport type { Auth, AuthToken } from \"./auth.gen\";\nimport type {\n  BodySerializer,\n  QuerySerializer,\n  QuerySerializerOptions,\n} from \"./bodySerializer.gen\";\n\nexport type HttpMethod =\n  | \"connect\"\n  | \"delete\"\n  | \"get\"\n  | \"head\"\n  | \"options\"\n  | \"patch\"\n  | \"post\"\n  | \"put\"\n  | \"trace\";\n\nexport type Client<\n  RequestFn = never,\n  Config = unknown,\n  MethodFn = never,\n  BuildUrlFn = never,\n  SseFn = never,\n> = {\n  /**\n   * Returns the final request URL.\n   */\n  buildUrl: BuildUrlFn;\n  getConfig: () => Config;\n  request: RequestFn;\n  setConfig: (config: Config) => Config;\n} & {\n  [K in HttpMethod]: MethodFn;\n} & ([SseFn] extends [never]\n    ? { sse?: never }\n    : { sse: { [K in HttpMethod]: SseFn } });\n\nexport interface Config {\n  /**\n   * Auth token or a function returning auth token. The resolved value will be\n   * added to the request payload as defined by its `security` array.\n   */\n  auth?: ((auth: Auth) => Promise<AuthToken> | AuthToken) | AuthToken;\n  /**\n   * A function for serializing request body parameter. By default,\n   * {@link JSON.stringify()} will be used.\n   */\n  bodySerializer?: BodySerializer | null;\n  /**\n   * An object containing any HTTP headers that you want to pre-populate your\n   * `Headers` object with.\n   *\n   * {@link https://developer.mozilla.org/docs/Web/API/Headers/Headers#init See more}\n   */\n  headers?:\n    | RequestInit[\"headers\"]\n    | Record<\n        string,\n        | string\n        | number\n        | boolean\n        | (string | number | boolean)[]\n        | null\n        | undefined\n        | unknown\n      >;\n  /**\n   * The request method.\n   *\n   * {@link https://developer.mozilla.org/docs/Web/API/fetch#method See more}\n   */\n  method?: Uppercase<HttpMethod>;\n  /**\n   * A function for serializing request query parameters. By default, arrays\n   * will be exploded in form style, objects will be exploded in deepObject\n   * style, and reserved characters are percent-encoded.\n   *\n   * This method will have no effect if the native `paramsSerializer()` Axios\n   * API function is used.\n   *\n   * {@link https://swagger.io/docs/specification/serialization/#query View examples}\n   */\n  querySerializer?: QuerySerializer | QuerySerializerOptions;\n  /**\n   * A function validating request data. This is useful if you want to ensure\n   * the request conforms to the desired shape, so it can be safely sent to\n   * the server.\n   */\n  requestValidator?: (data: unknown) => Promise<unknown>;\n  /**\n   * A function transforming response data before it's returned. This is useful\n   * for post-processing data, e.g. converting ISO strings into Date objects.\n   */\n  responseTransformer?: (data: unknown) => Promise<unknown>;\n  /**\n   * A function validating response data. This is useful if you want to ensure\n   * the response conforms to the desired shape, so it can be safely passed to\n   * the transformers and returned to the user.\n   */\n  responseValidator?: (data: unknown) => Promise<unknown>;\n}\n\ntype IsExactlyNeverOrNeverUndefined<T> = [T] extends [never]\n  ? true\n  : [T] extends [never | undefined]\n    ? [undefined] extends [T]\n      ? false\n      : true\n    : false;\n\nexport type OmitNever<T extends Record<string, unknown>> = {\n  [K in keyof T as IsExactlyNeverOrNeverUndefined<T[K]> extends true\n    ? never\n    : K]: T[K];\n};\n"
  },
  {
    "path": "nextjs-frontend/app/openapi-client/core/utils.gen.ts",
    "content": "// This file is auto-generated by @hey-api/openapi-ts\n\nimport type { BodySerializer, QuerySerializer } from \"./bodySerializer.gen\";\nimport {\n  type ArraySeparatorStyle,\n  serializeArrayParam,\n  serializeObjectParam,\n  serializePrimitiveParam,\n} from \"./pathSerializer.gen\";\n\nexport interface PathSerializer {\n  path: Record<string, unknown>;\n  url: string;\n}\n\nexport const PATH_PARAM_RE = /\\{[^{}]+\\}/g;\n\nexport const defaultPathSerializer = ({ path, url: _url }: PathSerializer) => {\n  let url = _url;\n  const matches = _url.match(PATH_PARAM_RE);\n  if (matches) {\n    for (const match of matches) {\n      let explode = false;\n      let name = match.substring(1, match.length - 1);\n      let style: ArraySeparatorStyle = \"simple\";\n\n      if (name.endsWith(\"*\")) {\n        explode = true;\n        name = name.substring(0, name.length - 1);\n      }\n\n      if (name.startsWith(\".\")) {\n        name = name.substring(1);\n        style = \"label\";\n      } else if (name.startsWith(\";\")) {\n        name = name.substring(1);\n        style = \"matrix\";\n      }\n\n      const value = path[name];\n\n      if (value === undefined || value === null) {\n        continue;\n      }\n\n      if (Array.isArray(value)) {\n        url = url.replace(\n          match,\n          serializeArrayParam({ explode, name, style, value }),\n        );\n        continue;\n      }\n\n      if (typeof value === \"object\") {\n        url = url.replace(\n          match,\n          serializeObjectParam({\n            explode,\n            name,\n            style,\n            value: value as Record<string, unknown>,\n            valueOnly: true,\n          }),\n        );\n        continue;\n      }\n\n      if (style === \"matrix\") {\n        url = url.replace(\n          match,\n          `;${serializePrimitiveParam({\n            name,\n            value: value as string,\n          })}`,\n        );\n        continue;\n      }\n\n      const replaceValue = encodeURIComponent(\n        style === \"label\" ? `.${value as string}` : (value as string),\n      );\n      url = url.replace(match, replaceValue);\n    }\n  }\n  return url;\n};\n\nexport const getUrl = ({\n  baseUrl,\n  path,\n  query,\n  querySerializer,\n  url: _url,\n}: {\n  baseUrl?: string;\n  path?: Record<string, unknown>;\n  query?: Record<string, unknown>;\n  querySerializer: QuerySerializer;\n  url: string;\n}) => {\n  const pathUrl = _url.startsWith(\"/\") ? _url : `/${_url}`;\n  let url = (baseUrl ?? \"\") + pathUrl;\n  if (path) {\n    url = defaultPathSerializer({ path, url });\n  }\n  let search = query ? querySerializer(query) : \"\";\n  if (search.startsWith(\"?\")) {\n    search = search.substring(1);\n  }\n  if (search) {\n    url += `?${search}`;\n  }\n  return url;\n};\n\nexport function getValidRequestBody(options: {\n  body?: unknown;\n  bodySerializer?: BodySerializer | null;\n  serializedBody?: unknown;\n}) {\n  const hasBody = options.body !== undefined;\n  const isSerializedBody = hasBody && options.bodySerializer;\n\n  if (isSerializedBody) {\n    if (\"serializedBody\" in options) {\n      const hasSerializedBody =\n        options.serializedBody !== undefined && options.serializedBody !== \"\";\n\n      return hasSerializedBody ? options.serializedBody : null;\n    }\n\n    // not all clients implement a serializedBody property (i.e. client-axios)\n    return options.body !== \"\" ? options.body : null;\n  }\n\n  // plain/text body\n  if (hasBody) {\n    return options.body;\n  }\n\n  // no body was provided\n  return undefined;\n}\n"
  },
  {
    "path": "nextjs-frontend/app/openapi-client/index.ts",
    "content": "// This file is auto-generated by @hey-api/openapi-ts\n\nexport * from \"./types.gen\";\nexport * from \"./sdk.gen\";\n"
  },
  {
    "path": "nextjs-frontend/app/openapi-client/sdk.gen.ts",
    "content": "// This file is auto-generated by @hey-api/openapi-ts\n\nimport {\n  type Options as ClientOptions,\n  type Client,\n  type TDataShape,\n  urlSearchParamsBodySerializer,\n} from \"./client\";\nimport type {\n  AuthJwtLoginData,\n  AuthJwtLoginResponses,\n  AuthJwtLoginErrors,\n  AuthJwtLogoutData,\n  AuthJwtLogoutResponses,\n  AuthJwtLogoutErrors,\n  RegisterRegisterData,\n  RegisterRegisterResponses,\n  RegisterRegisterErrors,\n  ResetForgotPasswordData,\n  ResetForgotPasswordResponses,\n  ResetForgotPasswordErrors,\n  ResetResetPasswordData,\n  ResetResetPasswordResponses,\n  ResetResetPasswordErrors,\n  VerifyRequestTokenData,\n  VerifyRequestTokenResponses,\n  VerifyRequestTokenErrors,\n  VerifyVerifyData,\n  VerifyVerifyResponses,\n  VerifyVerifyErrors,\n  UsersCurrentUserData,\n  UsersCurrentUserResponses,\n  UsersCurrentUserErrors,\n  UsersPatchCurrentUserData,\n  UsersPatchCurrentUserResponses,\n  UsersPatchCurrentUserErrors,\n  UsersDeleteUserData,\n  UsersDeleteUserResponses,\n  UsersDeleteUserErrors,\n  UsersUserData,\n  UsersUserResponses,\n  UsersUserErrors,\n  UsersPatchUserData,\n  UsersPatchUserResponses,\n  UsersPatchUserErrors,\n  ReadItemData,\n  ReadItemResponses,\n  ReadItemErrors,\n  CreateItemData,\n  CreateItemResponses,\n  CreateItemErrors,\n  DeleteItemData,\n  DeleteItemResponses,\n  DeleteItemErrors,\n} from \"./types.gen\";\nimport { client } from \"./client.gen\";\n\nexport type Options<\n  TData extends TDataShape = TDataShape,\n  ThrowOnError extends boolean = boolean,\n> = ClientOptions<TData, ThrowOnError> & {\n  /**\n   * You can provide a client instance returned by `createClient()` instead of\n   * individual options. This might be also useful if you want to implement a\n   * custom client.\n   */\n  client?: Client;\n  /**\n   * You can pass arbitrary values through the `meta` object. This can be\n   * used to access values that aren't defined as part of the SDK function.\n   */\n  meta?: Record<string, unknown>;\n};\n\n/**\n * Auth:Jwt.Login\n */\nexport const authJwtLogin = <ThrowOnError extends boolean = false>(\n  options: Options<AuthJwtLoginData, ThrowOnError>,\n) => {\n  return (options.client ?? client).post<\n    AuthJwtLoginResponses,\n    AuthJwtLoginErrors,\n    ThrowOnError\n  >({\n    ...urlSearchParamsBodySerializer,\n    responseType: \"json\",\n    url: \"/auth/jwt/login\",\n    ...options,\n    headers: {\n      \"Content-Type\": \"application/x-www-form-urlencoded\",\n      ...options.headers,\n    },\n  });\n};\n\n/**\n * Auth:Jwt.Logout\n */\nexport const authJwtLogout = <ThrowOnError extends boolean = false>(\n  options?: Options<AuthJwtLogoutData, ThrowOnError>,\n) => {\n  return (options?.client ?? client).post<\n    AuthJwtLogoutResponses,\n    AuthJwtLogoutErrors,\n    ThrowOnError\n  >({\n    responseType: \"json\",\n    security: [\n      {\n        scheme: \"bearer\",\n        type: \"http\",\n      },\n    ],\n    url: \"/auth/jwt/logout\",\n    ...options,\n  });\n};\n\n/**\n * Register:Register\n */\nexport const registerRegister = <ThrowOnError extends boolean = false>(\n  options: Options<RegisterRegisterData, ThrowOnError>,\n) => {\n  return (options.client ?? client).post<\n    RegisterRegisterResponses,\n    RegisterRegisterErrors,\n    ThrowOnError\n  >({\n    responseType: \"json\",\n    url: \"/auth/register\",\n    ...options,\n    headers: {\n      \"Content-Type\": \"application/json\",\n      ...options.headers,\n    },\n  });\n};\n\n/**\n * Reset:Forgot Password\n */\nexport const resetForgotPassword = <ThrowOnError extends boolean = false>(\n  options: Options<ResetForgotPasswordData, ThrowOnError>,\n) => {\n  return (options.client ?? client).post<\n    ResetForgotPasswordResponses,\n    ResetForgotPasswordErrors,\n    ThrowOnError\n  >({\n    responseType: \"json\",\n    url: \"/auth/forgot-password\",\n    ...options,\n    headers: {\n      \"Content-Type\": \"application/json\",\n      ...options.headers,\n    },\n  });\n};\n\n/**\n * Reset:Reset Password\n */\nexport const resetResetPassword = <ThrowOnError extends boolean = false>(\n  options: Options<ResetResetPasswordData, ThrowOnError>,\n) => {\n  return (options.client ?? client).post<\n    ResetResetPasswordResponses,\n    ResetResetPasswordErrors,\n    ThrowOnError\n  >({\n    responseType: \"json\",\n    url: \"/auth/reset-password\",\n    ...options,\n    headers: {\n      \"Content-Type\": \"application/json\",\n      ...options.headers,\n    },\n  });\n};\n\n/**\n * Verify:Request-Token\n */\nexport const verifyRequestToken = <ThrowOnError extends boolean = false>(\n  options: Options<VerifyRequestTokenData, ThrowOnError>,\n) => {\n  return (options.client ?? client).post<\n    VerifyRequestTokenResponses,\n    VerifyRequestTokenErrors,\n    ThrowOnError\n  >({\n    responseType: \"json\",\n    url: \"/auth/request-verify-token\",\n    ...options,\n    headers: {\n      \"Content-Type\": \"application/json\",\n      ...options.headers,\n    },\n  });\n};\n\n/**\n * Verify:Verify\n */\nexport const verifyVerify = <ThrowOnError extends boolean = false>(\n  options: Options<VerifyVerifyData, ThrowOnError>,\n) => {\n  return (options.client ?? client).post<\n    VerifyVerifyResponses,\n    VerifyVerifyErrors,\n    ThrowOnError\n  >({\n    responseType: \"json\",\n    url: \"/auth/verify\",\n    ...options,\n    headers: {\n      \"Content-Type\": \"application/json\",\n      ...options.headers,\n    },\n  });\n};\n\n/**\n * Users:Current User\n */\nexport const usersCurrentUser = <ThrowOnError extends boolean = false>(\n  options?: Options<UsersCurrentUserData, ThrowOnError>,\n) => {\n  return (options?.client ?? client).get<\n    UsersCurrentUserResponses,\n    UsersCurrentUserErrors,\n    ThrowOnError\n  >({\n    responseType: \"json\",\n    security: [\n      {\n        scheme: \"bearer\",\n        type: \"http\",\n      },\n    ],\n    url: \"/users/me\",\n    ...options,\n  });\n};\n\n/**\n * Users:Patch Current User\n */\nexport const usersPatchCurrentUser = <ThrowOnError extends boolean = false>(\n  options: Options<UsersPatchCurrentUserData, ThrowOnError>,\n) => {\n  return (options.client ?? client).patch<\n    UsersPatchCurrentUserResponses,\n    UsersPatchCurrentUserErrors,\n    ThrowOnError\n  >({\n    responseType: \"json\",\n    security: [\n      {\n        scheme: \"bearer\",\n        type: \"http\",\n      },\n    ],\n    url: \"/users/me\",\n    ...options,\n    headers: {\n      \"Content-Type\": \"application/json\",\n      ...options.headers,\n    },\n  });\n};\n\n/**\n * Users:Delete User\n */\nexport const usersDeleteUser = <ThrowOnError extends boolean = false>(\n  options: Options<UsersDeleteUserData, ThrowOnError>,\n) => {\n  return (options.client ?? client).delete<\n    UsersDeleteUserResponses,\n    UsersDeleteUserErrors,\n    ThrowOnError\n  >({\n    security: [\n      {\n        scheme: \"bearer\",\n        type: \"http\",\n      },\n    ],\n    url: \"/users/{id}\",\n    ...options,\n  });\n};\n\n/**\n * Users:User\n */\nexport const usersUser = <ThrowOnError extends boolean = false>(\n  options: Options<UsersUserData, ThrowOnError>,\n) => {\n  return (options.client ?? client).get<\n    UsersUserResponses,\n    UsersUserErrors,\n    ThrowOnError\n  >({\n    responseType: \"json\",\n    security: [\n      {\n        scheme: \"bearer\",\n        type: \"http\",\n      },\n    ],\n    url: \"/users/{id}\",\n    ...options,\n  });\n};\n\n/**\n * Users:Patch User\n */\nexport const usersPatchUser = <ThrowOnError extends boolean = false>(\n  options: Options<UsersPatchUserData, ThrowOnError>,\n) => {\n  return (options.client ?? client).patch<\n    UsersPatchUserResponses,\n    UsersPatchUserErrors,\n    ThrowOnError\n  >({\n    responseType: \"json\",\n    security: [\n      {\n        scheme: \"bearer\",\n        type: \"http\",\n      },\n    ],\n    url: \"/users/{id}\",\n    ...options,\n    headers: {\n      \"Content-Type\": \"application/json\",\n      ...options.headers,\n    },\n  });\n};\n\n/**\n * Read Item\n */\nexport const readItem = <ThrowOnError extends boolean = false>(\n  options?: Options<ReadItemData, ThrowOnError>,\n) => {\n  return (options?.client ?? client).get<\n    ReadItemResponses,\n    ReadItemErrors,\n    ThrowOnError\n  >({\n    responseType: \"json\",\n    security: [\n      {\n        scheme: \"bearer\",\n        type: \"http\",\n      },\n    ],\n    url: \"/items/\",\n    ...options,\n  });\n};\n\n/**\n * Create Item\n */\nexport const createItem = <ThrowOnError extends boolean = false>(\n  options: Options<CreateItemData, ThrowOnError>,\n) => {\n  return (options.client ?? client).post<\n    CreateItemResponses,\n    CreateItemErrors,\n    ThrowOnError\n  >({\n    responseType: \"json\",\n    security: [\n      {\n        scheme: \"bearer\",\n        type: \"http\",\n      },\n    ],\n    url: \"/items/\",\n    ...options,\n    headers: {\n      \"Content-Type\": \"application/json\",\n      ...options.headers,\n    },\n  });\n};\n\n/**\n * Delete Item\n */\nexport const deleteItem = <ThrowOnError extends boolean = false>(\n  options: Options<DeleteItemData, ThrowOnError>,\n) => {\n  return (options.client ?? client).delete<\n    DeleteItemResponses,\n    DeleteItemErrors,\n    ThrowOnError\n  >({\n    responseType: \"json\",\n    security: [\n      {\n        scheme: \"bearer\",\n        type: \"http\",\n      },\n    ],\n    url: \"/items/{item_id}\",\n    ...options,\n  });\n};\n"
  },
  {
    "path": "nextjs-frontend/app/openapi-client/types.gen.ts",
    "content": "// This file is auto-generated by @hey-api/openapi-ts\n\n/**\n * BearerResponse\n */\nexport type BearerResponse = {\n  /**\n   * Access Token\n   */\n  access_token: string;\n  /**\n   * Token Type\n   */\n  token_type: string;\n};\n\n/**\n * Body_auth-reset:forgot_password\n */\nexport type BodyAuthResetForgotPassword = {\n  /**\n   * Email\n   */\n  email: string;\n};\n\n/**\n * Body_auth-reset:reset_password\n */\nexport type BodyAuthResetResetPassword = {\n  /**\n   * Token\n   */\n  token: string;\n  /**\n   * Password\n   */\n  password: string;\n};\n\n/**\n * Body_auth-verify:request-token\n */\nexport type BodyAuthVerifyRequestToken = {\n  /**\n   * Email\n   */\n  email: string;\n};\n\n/**\n * Body_auth-verify:verify\n */\nexport type BodyAuthVerifyVerify = {\n  /**\n   * Token\n   */\n  token: string;\n};\n\n/**\n * ErrorModel\n */\nexport type ErrorModel = {\n  /**\n   * Detail\n   */\n  detail:\n    | string\n    | {\n        [key: string]: string;\n      };\n};\n\n/**\n * HTTPValidationError\n */\nexport type HttpValidationError = {\n  /**\n   * Detail\n   */\n  detail?: Array<ValidationError>;\n};\n\n/**\n * ItemCreate\n */\nexport type ItemCreate = {\n  /**\n   * Name\n   */\n  name: string;\n  /**\n   * Description\n   */\n  description?: string | null;\n  /**\n   * Quantity\n   */\n  quantity?: number | null;\n};\n\n/**\n * ItemRead\n */\nexport type ItemRead = {\n  /**\n   * Name\n   */\n  name: string;\n  /**\n   * Description\n   */\n  description?: string | null;\n  /**\n   * Quantity\n   */\n  quantity?: number | null;\n  /**\n   * Id\n   */\n  id: string;\n  /**\n   * User Id\n   */\n  user_id: string;\n};\n\n/**\n * Page[ItemRead]\n */\nexport type PageItemRead = {\n  /**\n   * Items\n   */\n  items: Array<ItemRead>;\n  /**\n   * Total\n   */\n  total?: number | null;\n  /**\n   * Page\n   */\n  page: number | null;\n  /**\n   * Size\n   */\n  size: number | null;\n  /**\n   * Pages\n   */\n  pages?: number | null;\n};\n\n/**\n * UserCreate\n */\nexport type UserCreate = {\n  /**\n   * Email\n   */\n  email: string;\n  /**\n   * Password\n   */\n  password: string;\n  /**\n   * Is Active\n   */\n  is_active?: boolean | null;\n  /**\n   * Is Superuser\n   */\n  is_superuser?: boolean | null;\n  /**\n   * Is Verified\n   */\n  is_verified?: boolean | null;\n};\n\n/**\n * UserRead\n */\nexport type UserRead = {\n  /**\n   * Id\n   */\n  id: string;\n  /**\n   * Email\n   */\n  email: string;\n  /**\n   * Is Active\n   */\n  is_active?: boolean;\n  /**\n   * Is Superuser\n   */\n  is_superuser?: boolean;\n  /**\n   * Is Verified\n   */\n  is_verified?: boolean;\n};\n\n/**\n * UserUpdate\n */\nexport type UserUpdate = {\n  /**\n   * Password\n   */\n  password?: string | null;\n  /**\n   * Email\n   */\n  email?: string | null;\n  /**\n   * Is Active\n   */\n  is_active?: boolean | null;\n  /**\n   * Is Superuser\n   */\n  is_superuser?: boolean | null;\n  /**\n   * Is Verified\n   */\n  is_verified?: boolean | null;\n};\n\n/**\n * ValidationError\n */\nexport type ValidationError = {\n  /**\n   * Location\n   */\n  loc: Array<string | number>;\n  /**\n   * Message\n   */\n  msg: string;\n  /**\n   * Error Type\n   */\n  type: string;\n};\n\n/**\n * Body_auth-auth:jwt.login\n */\nexport type Login = {\n  /**\n   * Grant Type\n   */\n  grant_type?: string | null;\n  /**\n   * Username\n   */\n  username: string;\n  /**\n   * Password\n   */\n  password: string;\n  /**\n   * Scope\n   */\n  scope?: string;\n  /**\n   * Client Id\n   */\n  client_id?: string | null;\n  /**\n   * Client Secret\n   */\n  client_secret?: string | null;\n};\n\nexport type AuthJwtLoginData = {\n  body: Login;\n  path?: never;\n  query?: never;\n  url: \"/auth/jwt/login\";\n};\n\nexport type AuthJwtLoginErrors = {\n  /**\n   * Bad Request\n   */\n  400: ErrorModel;\n  /**\n   * Validation Error\n   */\n  422: HttpValidationError;\n};\n\nexport type AuthJwtLoginError = AuthJwtLoginErrors[keyof AuthJwtLoginErrors];\n\nexport type AuthJwtLoginResponses = {\n  /**\n   * Successful Response\n   */\n  200: BearerResponse;\n};\n\nexport type AuthJwtLoginResponse =\n  AuthJwtLoginResponses[keyof AuthJwtLoginResponses];\n\nexport type AuthJwtLogoutData = {\n  body?: never;\n  path?: never;\n  query?: never;\n  url: \"/auth/jwt/logout\";\n};\n\nexport type AuthJwtLogoutErrors = {\n  /**\n   * Missing token or inactive user.\n   */\n  401: unknown;\n};\n\nexport type AuthJwtLogoutResponses = {\n  /**\n   * Successful Response\n   */\n  200: unknown;\n};\n\nexport type RegisterRegisterData = {\n  body: UserCreate;\n  path?: never;\n  query?: never;\n  url: \"/auth/register\";\n};\n\nexport type RegisterRegisterErrors = {\n  /**\n   * Bad Request\n   */\n  400: ErrorModel;\n  /**\n   * Validation Error\n   */\n  422: HttpValidationError;\n};\n\nexport type RegisterRegisterError =\n  RegisterRegisterErrors[keyof RegisterRegisterErrors];\n\nexport type RegisterRegisterResponses = {\n  /**\n   * Successful Response\n   */\n  201: UserRead;\n};\n\nexport type RegisterRegisterResponse =\n  RegisterRegisterResponses[keyof RegisterRegisterResponses];\n\nexport type ResetForgotPasswordData = {\n  body: BodyAuthResetForgotPassword;\n  path?: never;\n  query?: never;\n  url: \"/auth/forgot-password\";\n};\n\nexport type ResetForgotPasswordErrors = {\n  /**\n   * Validation Error\n   */\n  422: HttpValidationError;\n};\n\nexport type ResetForgotPasswordError =\n  ResetForgotPasswordErrors[keyof ResetForgotPasswordErrors];\n\nexport type ResetForgotPasswordResponses = {\n  /**\n   * Successful Response\n   */\n  202: unknown;\n};\n\nexport type ResetResetPasswordData = {\n  body: BodyAuthResetResetPassword;\n  path?: never;\n  query?: never;\n  url: \"/auth/reset-password\";\n};\n\nexport type ResetResetPasswordErrors = {\n  /**\n   * Bad Request\n   */\n  400: ErrorModel;\n  /**\n   * Validation Error\n   */\n  422: HttpValidationError;\n};\n\nexport type ResetResetPasswordError =\n  ResetResetPasswordErrors[keyof ResetResetPasswordErrors];\n\nexport type ResetResetPasswordResponses = {\n  /**\n   * Successful Response\n   */\n  200: unknown;\n};\n\nexport type VerifyRequestTokenData = {\n  body: BodyAuthVerifyRequestToken;\n  path?: never;\n  query?: never;\n  url: \"/auth/request-verify-token\";\n};\n\nexport type VerifyRequestTokenErrors = {\n  /**\n   * Validation Error\n   */\n  422: HttpValidationError;\n};\n\nexport type VerifyRequestTokenError =\n  VerifyRequestTokenErrors[keyof VerifyRequestTokenErrors];\n\nexport type VerifyRequestTokenResponses = {\n  /**\n   * Successful Response\n   */\n  202: unknown;\n};\n\nexport type VerifyVerifyData = {\n  body: BodyAuthVerifyVerify;\n  path?: never;\n  query?: never;\n  url: \"/auth/verify\";\n};\n\nexport type VerifyVerifyErrors = {\n  /**\n   * Bad Request\n   */\n  400: ErrorModel;\n  /**\n   * Validation Error\n   */\n  422: HttpValidationError;\n};\n\nexport type VerifyVerifyError = VerifyVerifyErrors[keyof VerifyVerifyErrors];\n\nexport type VerifyVerifyResponses = {\n  /**\n   * Successful Response\n   */\n  200: UserRead;\n};\n\nexport type VerifyVerifyResponse =\n  VerifyVerifyResponses[keyof VerifyVerifyResponses];\n\nexport type UsersCurrentUserData = {\n  body?: never;\n  path?: never;\n  query?: never;\n  url: \"/users/me\";\n};\n\nexport type UsersCurrentUserErrors = {\n  /**\n   * Missing token or inactive user.\n   */\n  401: unknown;\n};\n\nexport type UsersCurrentUserResponses = {\n  /**\n   * Successful Response\n   */\n  200: UserRead;\n};\n\nexport type UsersCurrentUserResponse =\n  UsersCurrentUserResponses[keyof UsersCurrentUserResponses];\n\nexport type UsersPatchCurrentUserData = {\n  body: UserUpdate;\n  path?: never;\n  query?: never;\n  url: \"/users/me\";\n};\n\nexport type UsersPatchCurrentUserErrors = {\n  /**\n   * Bad Request\n   */\n  400: ErrorModel;\n  /**\n   * Missing token or inactive user.\n   */\n  401: unknown;\n  /**\n   * Validation Error\n   */\n  422: HttpValidationError;\n};\n\nexport type UsersPatchCurrentUserError =\n  UsersPatchCurrentUserErrors[keyof UsersPatchCurrentUserErrors];\n\nexport type UsersPatchCurrentUserResponses = {\n  /**\n   * Successful Response\n   */\n  200: UserRead;\n};\n\nexport type UsersPatchCurrentUserResponse =\n  UsersPatchCurrentUserResponses[keyof UsersPatchCurrentUserResponses];\n\nexport type UsersDeleteUserData = {\n  body?: never;\n  path: {\n    /**\n     * Id\n     */\n    id: string;\n  };\n  query?: never;\n  url: \"/users/{id}\";\n};\n\nexport type UsersDeleteUserErrors = {\n  /**\n   * Missing token or inactive user.\n   */\n  401: unknown;\n  /**\n   * Not a superuser.\n   */\n  403: unknown;\n  /**\n   * The user does not exist.\n   */\n  404: unknown;\n  /**\n   * Validation Error\n   */\n  422: HttpValidationError;\n};\n\nexport type UsersDeleteUserError =\n  UsersDeleteUserErrors[keyof UsersDeleteUserErrors];\n\nexport type UsersDeleteUserResponses = {\n  /**\n   * Successful Response\n   */\n  204: void;\n};\n\nexport type UsersDeleteUserResponse =\n  UsersDeleteUserResponses[keyof UsersDeleteUserResponses];\n\nexport type UsersUserData = {\n  body?: never;\n  path: {\n    /**\n     * Id\n     */\n    id: string;\n  };\n  query?: never;\n  url: \"/users/{id}\";\n};\n\nexport type UsersUserErrors = {\n  /**\n   * Missing token or inactive user.\n   */\n  401: unknown;\n  /**\n   * Not a superuser.\n   */\n  403: unknown;\n  /**\n   * The user does not exist.\n   */\n  404: unknown;\n  /**\n   * Validation Error\n   */\n  422: HttpValidationError;\n};\n\nexport type UsersUserError = UsersUserErrors[keyof UsersUserErrors];\n\nexport type UsersUserResponses = {\n  /**\n   * Successful Response\n   */\n  200: UserRead;\n};\n\nexport type UsersUserResponse = UsersUserResponses[keyof UsersUserResponses];\n\nexport type UsersPatchUserData = {\n  body: UserUpdate;\n  path: {\n    /**\n     * Id\n     */\n    id: string;\n  };\n  query?: never;\n  url: \"/users/{id}\";\n};\n\nexport type UsersPatchUserErrors = {\n  /**\n   * Bad Request\n   */\n  400: ErrorModel;\n  /**\n   * Missing token or inactive user.\n   */\n  401: unknown;\n  /**\n   * Not a superuser.\n   */\n  403: unknown;\n  /**\n   * The user does not exist.\n   */\n  404: unknown;\n  /**\n   * Validation Error\n   */\n  422: HttpValidationError;\n};\n\nexport type UsersPatchUserError =\n  UsersPatchUserErrors[keyof UsersPatchUserErrors];\n\nexport type UsersPatchUserResponses = {\n  /**\n   * Successful Response\n   */\n  200: UserRead;\n};\n\nexport type UsersPatchUserResponse =\n  UsersPatchUserResponses[keyof UsersPatchUserResponses];\n\nexport type ReadItemData = {\n  body?: never;\n  path?: never;\n  query?: {\n    /**\n     * Page\n     * Page number\n     */\n    page?: number;\n    /**\n     * Size\n     * Page size\n     */\n    size?: number;\n  };\n  url: \"/items/\";\n};\n\nexport type ReadItemErrors = {\n  /**\n   * Validation Error\n   */\n  422: HttpValidationError;\n};\n\nexport type ReadItemError = ReadItemErrors[keyof ReadItemErrors];\n\nexport type ReadItemResponses = {\n  /**\n   * Successful Response\n   */\n  200: PageItemRead;\n};\n\nexport type ReadItemResponse = ReadItemResponses[keyof ReadItemResponses];\n\nexport type CreateItemData = {\n  body: ItemCreate;\n  path?: never;\n  query?: never;\n  url: \"/items/\";\n};\n\nexport type CreateItemErrors = {\n  /**\n   * Validation Error\n   */\n  422: HttpValidationError;\n};\n\nexport type CreateItemError = CreateItemErrors[keyof CreateItemErrors];\n\nexport type CreateItemResponses = {\n  /**\n   * Successful Response\n   */\n  200: ItemRead;\n};\n\nexport type CreateItemResponse = CreateItemResponses[keyof CreateItemResponses];\n\nexport type DeleteItemData = {\n  body?: never;\n  path: {\n    /**\n     * Item Id\n     */\n    item_id: string;\n  };\n  query?: never;\n  url: \"/items/{item_id}\";\n};\n\nexport type DeleteItemErrors = {\n  /**\n   * Validation Error\n   */\n  422: HttpValidationError;\n};\n\nexport type DeleteItemError = DeleteItemErrors[keyof DeleteItemErrors];\n\nexport type DeleteItemResponses = {\n  /**\n   * Successful Response\n   */\n  200: unknown;\n};\n\nexport type ClientOptions = {\n  baseURL: `${string}://openapi.json` | (string & {});\n};\n"
  },
  {
    "path": "nextjs-frontend/app/page.tsx",
    "content": "import { Button } from \"@/components/ui/button\";\nimport Link from \"next/link\";\nimport { FaGithub } from \"react-icons/fa\";\nimport { Badge } from \"@/components/ui/badge\";\n\nexport default function Home() {\n  return (\n    <main className=\"flex min-h-screen flex-col items-center justify-center bg-gray-50 dark:bg-gray-900 p-8\">\n      <div className=\"text-center max-w-2xl\">\n        <h1 className=\"text-5xl font-bold text-gray-800 dark:text-white mb-6\">\n          Welcome to the Next.js & FastAPI Boilerplate\n        </h1>\n        <p className=\"text-lg text-gray-600 dark:text-gray-300 mb-8\">\n          A simple and powerful template to get started with full-stack\n          development using Next.js and FastAPI.\n        </p>\n\n        {/* Link to Dashboard */}\n        <Link href=\"/dashboard\">\n          <Button className=\"px-8 py-4 text-xl font-semibold rounded-full shadow-lg bg-gradient-to-r from-blue-500 to-indigo-500 text-white hover:from-blue-600 hover:to-indigo-600 focus:ring-4 focus:ring-blue-300\">\n            Go to Dashboard\n          </Button>\n        </Link>\n\n        {/* GitHub Badge */}\n        <div className=\"mt-6\">\n          <Badge\n            variant=\"outline\"\n            className=\"text-sm flex items-center gap-2 px-3 py-2 rounded-lg border-gray-300 dark:border-gray-700\"\n          >\n            <FaGithub className=\"w-5 h-5 text-black dark:text-white\" />\n            <Link\n              href=\"https://github.com/vintasoftware/nextjs-fastapi-template\"\n              target=\"_blank\"\n              className=\"hover:underline\"\n            >\n              View on GitHub\n            </Link>\n          </Badge>\n        </div>\n      </div>\n    </main>\n  );\n}\n"
  },
  {
    "path": "nextjs-frontend/app/password-recovery/confirm/page.tsx",
    "content": "\"use client\";\n\nimport { useActionState } from \"react\";\nimport { notFound, useSearchParams } from \"next/navigation\";\nimport { passwordResetConfirm } from \"@/components/actions/password-reset-action\";\nimport { SubmitButton } from \"@/components/ui/submitButton\";\nimport {\n  Card,\n  CardContent,\n  CardDescription,\n  CardHeader,\n  CardTitle,\n} from \"@/components/ui/card\";\nimport { Label } from \"@/components/ui/label\";\nimport { Input } from \"@/components/ui/input\";\nimport { Suspense } from \"react\";\nimport { FieldError, FormError } from \"@/components/ui/FormError\";\n\nfunction ResetPasswordForm() {\n  const [state, dispatch] = useActionState(passwordResetConfirm, undefined);\n  const searchParams = useSearchParams();\n  const token = searchParams.get(\"token\");\n\n  if (!token) {\n    notFound();\n  }\n\n  return (\n    <form action={dispatch}>\n      <Card className=\"w-full max-w-sm\">\n        <CardHeader>\n          <CardTitle className=\"text-2xl\">Reset your Password</CardTitle>\n          <CardDescription>\n            Enter the new password and confirm it.\n          </CardDescription>\n        </CardHeader>\n        <CardContent className=\"grid gap-4\">\n          <div className=\"grid gap-2\">\n            <Label htmlFor=\"password\">Password</Label>\n            <Input id=\"password\" name=\"password\" type=\"password\" required />\n          </div>\n          <FieldError state={state} field=\"password\" />\n          <div className=\"grid gap-2\">\n            <Label htmlFor=\"passwordConfirm\">Password Confirm</Label>\n            <Input\n              id=\"passwordConfirm\"\n              name=\"passwordConfirm\"\n              type=\"password\"\n              required\n            />\n          </div>\n          <FieldError state={state} field=\"passwordConfirm\" />\n          <input\n            type=\"hidden\"\n            id=\"resetToken\"\n            name=\"resetToken\"\n            value={token}\n            readOnly\n          />\n          <SubmitButton text={\"Send\"} />\n          <FormError state={state} />\n        </CardContent>\n      </Card>\n    </form>\n  );\n}\n\nexport default function Page() {\n  return (\n    <div className=\"flex h-screen w-full items-center justify-center px-4\">\n      <Suspense fallback={<div>Loading reset form...</div>}>\n        <ResetPasswordForm />\n      </Suspense>\n    </div>\n  );\n}\n"
  },
  {
    "path": "nextjs-frontend/app/password-recovery/page.tsx",
    "content": "\"use client\";\n\nimport {\n  Card,\n  CardContent,\n  CardDescription,\n  CardHeader,\n  CardTitle,\n} from \"@/components/ui/card\";\nimport { Input } from \"@/components/ui/input\";\nimport { Label } from \"@/components/ui/label\";\n\nimport { passwordReset } from \"@/components/actions/password-reset-action\";\nimport { useActionState } from \"react\";\nimport { SubmitButton } from \"@/components/ui/submitButton\";\nimport Link from \"next/link\";\nimport { FormError } from \"@/components/ui/FormError\";\n\nexport default function Page() {\n  const [state, dispatch] = useActionState(passwordReset, undefined);\n\n  return (\n    <div className=\"flex h-screen w-full items-center justify-center bg-gray-50 dark:bg-gray-900 px-4\">\n      <form action={dispatch}>\n        <Card className=\"w-full max-w-sm rounded-lg shadow-lg border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-800\">\n          <CardHeader className=\"text-center\">\n            <CardTitle className=\"text-2xl font-semibold text-gray-800 dark:text-white\">\n              Password Recovery\n            </CardTitle>\n            <CardDescription className=\"text-sm text-gray-600 dark:text-gray-400\">\n              Enter your email to receive instructions to reset your password.\n            </CardDescription>\n          </CardHeader>\n          <CardContent className=\"grid gap-6 p-6\">\n            <div className=\"grid gap-3\">\n              <Label\n                htmlFor=\"email\"\n                className=\"text-gray-700 dark:text-gray-300\"\n              >\n                Email\n              </Label>\n              <Input\n                id=\"email\"\n                name=\"email\"\n                type=\"email\"\n                placeholder=\"m@example.com\"\n                required\n                className=\"border-gray-300 dark:border-gray-600\"\n              />\n            </div>\n            <SubmitButton text=\"Send\" />\n            <FormError state={state} />\n            <div className=\"mt-2 text-sm text-center text-blue-500\">\n              {state?.message && <p>{state.message}</p>}\n            </div>\n            <div className=\"mt-4 text-center text-sm text-gray-600 dark:text-gray-400\">\n              <Link\n                href=\"/login\"\n                className=\"text-blue-500 hover:text-blue-600 dark:text-blue-400 dark:hover:text-blue-500\"\n              >\n                Back to login\n              </Link>\n            </div>\n          </CardContent>\n        </Card>\n      </form>\n    </div>\n  );\n}\n"
  },
  {
    "path": "nextjs-frontend/app/register/page.tsx",
    "content": "\"use client\";\n\nimport {\n  Card,\n  CardContent,\n  CardDescription,\n  CardHeader,\n  CardTitle,\n} from \"@/components/ui/card\";\nimport { Input } from \"@/components/ui/input\";\nimport { Label } from \"@/components/ui/label\";\n\nimport { register } from \"@/components/actions/register-action\";\nimport { useActionState } from \"react\";\nimport { SubmitButton } from \"@/components/ui/submitButton\";\nimport Link from \"next/link\";\nimport { FieldError, FormError } from \"@/components/ui/FormError\";\n\nexport default function Page() {\n  const [state, dispatch] = useActionState(register, undefined);\n  return (\n    <div className=\"flex h-screen w-full items-center justify-center bg-gray-50 dark:bg-gray-900 px-4\">\n      <form action={dispatch}>\n        <Card className=\"w-full max-w-sm rounded-lg shadow-lg border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-800\">\n          <CardHeader className=\"text-center\">\n            <CardTitle className=\"text-2xl font-semibold text-gray-800 dark:text-white\">\n              Sign Up\n            </CardTitle>\n            <CardDescription className=\"text-sm text-gray-600 dark:text-gray-400\">\n              Enter your email and password below to create your account.\n            </CardDescription>\n          </CardHeader>\n          <CardContent className=\"grid gap-6 p-6\">\n            <div className=\"grid gap-3\">\n              <Label\n                htmlFor=\"email\"\n                className=\"text-gray-700 dark:text-gray-300\"\n              >\n                Email\n              </Label>\n              <Input\n                id=\"email\"\n                name=\"email\"\n                type=\"email\"\n                placeholder=\"m@example.com\"\n                required\n                className=\"border-gray-300 dark:border-gray-600\"\n              />\n              <FieldError state={state} field=\"email\" />\n            </div>\n            <div className=\"grid gap-3\">\n              <Label\n                htmlFor=\"password\"\n                className=\"text-gray-700 dark:text-gray-300\"\n              >\n                Password\n              </Label>\n              <Input\n                id=\"password\"\n                name=\"password\"\n                type=\"password\"\n                required\n                className=\"border-gray-300 dark:border-gray-600\"\n              />\n              <FieldError state={state} field=\"password\" />\n            </div>\n            <SubmitButton text=\"Sign Up\" />\n            <FormError state={state} />\n            <div className=\"mt-4 text-center text-sm text-gray-600 dark:text-gray-400\">\n              <Link\n                href=\"/login\"\n                className=\"text-blue-500 hover:text-blue-600 dark:text-blue-400 dark:hover:text-blue-500\"\n              >\n                Back to login\n              </Link>\n            </div>\n          </CardContent>\n        </Card>\n      </form>\n    </div>\n  );\n}\n"
  },
  {
    "path": "nextjs-frontend/components/actions/items-action.ts",
    "content": "\"use server\";\n\nimport { cookies } from \"next/headers\";\nimport { readItem, deleteItem, createItem } from \"@/app/clientService\";\nimport { revalidatePath } from \"next/cache\";\nimport { redirect } from \"next/navigation\";\nimport { itemSchema } from \"@/lib/definitions\";\n\nexport async function fetchItems(page: number = 1, size: number = 10) {\n  const cookieStore = await cookies();\n  const token = cookieStore.get(\"accessToken\")?.value;\n\n  if (!token) {\n    return { message: \"No access token found\" };\n  }\n\n  const { data, error } = await readItem({\n    query: {\n      page: page,\n      size: size,\n    },\n    headers: {\n      Authorization: `Bearer ${token}`,\n    },\n  });\n\n  if (error) {\n    return { message: error };\n  }\n\n  return data;\n}\n\nexport async function removeItem(id: string) {\n  const cookieStore = await cookies();\n  const token = cookieStore.get(\"accessToken\")?.value;\n\n  if (!token) {\n    return { message: \"No access token found\" };\n  }\n\n  const { error } = await deleteItem({\n    headers: {\n      Authorization: `Bearer ${token}`,\n    },\n    path: {\n      item_id: id,\n    },\n  });\n\n  if (error) {\n    return { message: error };\n  }\n  revalidatePath(\"/dashboard\");\n}\n\nexport async function addItem(prevState: {}, formData: FormData) {\n  const cookieStore = await cookies();\n  const token = cookieStore.get(\"accessToken\")?.value;\n\n  if (!token) {\n    return { message: \"No access token found\" };\n  }\n\n  const validatedFields = itemSchema.safeParse({\n    name: formData.get(\"name\"),\n    description: formData.get(\"description\"),\n    quantity: formData.get(\"quantity\"),\n  });\n\n  if (!validatedFields.success) {\n    return { errors: validatedFields.error.flatten().fieldErrors };\n  }\n\n  const { name, description, quantity } = validatedFields.data;\n\n  const input = {\n    headers: {\n      Authorization: `Bearer ${token}`,\n    },\n    body: {\n      name,\n      description,\n      quantity,\n    },\n  };\n  const { error } = await createItem(input);\n  if (error) {\n    return { message: `${error.detail}` };\n  }\n  redirect(`/dashboard`);\n}\n"
  },
  {
    "path": "nextjs-frontend/components/actions/login-action.ts",
    "content": "\"use server\";\n\nimport { cookies } from \"next/headers\";\n\nimport { authJwtLogin } from \"@/app/clientService\";\nimport { redirect } from \"next/navigation\";\nimport { loginSchema } from \"@/lib/definitions\";\nimport { getErrorMessage } from \"@/lib/utils\";\n\nexport async function login(prevState: unknown, formData: FormData) {\n  const validatedFields = loginSchema.safeParse({\n    username: formData.get(\"username\") as string,\n    password: formData.get(\"password\") as string,\n  });\n\n  if (!validatedFields.success) {\n    return {\n      errors: validatedFields.error.flatten().fieldErrors,\n    };\n  }\n\n  const { username, password } = validatedFields.data;\n\n  const input = {\n    body: {\n      username,\n      password,\n    },\n  };\n\n  try {\n    const { data, error } = await authJwtLogin(input);\n    if (error) {\n      return { server_validation_error: getErrorMessage(error) };\n    }\n    (await cookies()).set(\"accessToken\", data.access_token);\n  } catch (err) {\n    console.error(\"Login error:\", err);\n    return {\n      server_error: \"An unexpected error occurred. Please try again later.\",\n    };\n  }\n  redirect(\"/dashboard\");\n}\n"
  },
  {
    "path": "nextjs-frontend/components/actions/logout-action.ts",
    "content": "\"use server\";\n\nimport { cookies } from \"next/headers\";\nimport { authJwtLogout } from \"@/app/clientService\";\nimport { redirect } from \"next/navigation\";\n\nexport async function logout() {\n  const cookieStore = await cookies();\n  const token = cookieStore.get(\"accessToken\")?.value;\n\n  if (!token) {\n    return { message: \"No access token found\" };\n  }\n\n  const { error } = await authJwtLogout({\n    headers: {\n      Authorization: `Bearer ${token}`,\n    },\n  });\n\n  if (error) {\n    return { message: error };\n  }\n\n  cookieStore.delete(\"accessToken\");\n  redirect(`/login`);\n}\n"
  },
  {
    "path": "nextjs-frontend/components/actions/password-reset-action.ts",
    "content": "\"use server\";\n\nimport { resetForgotPassword, resetResetPassword } from \"@/app/clientService\";\nimport { redirect } from \"next/navigation\";\nimport { passwordResetConfirmSchema } from \"@/lib/definitions\";\nimport { getErrorMessage } from \"@/lib/utils\";\n\nexport async function passwordReset(prevState: unknown, formData: FormData) {\n  const input = {\n    body: {\n      email: formData.get(\"email\") as string,\n    },\n  };\n\n  try {\n    const { error } = await resetForgotPassword(input);\n    if (error) {\n      return { server_validation_error: getErrorMessage(error) };\n    }\n    return { message: \"Password reset instructions sent to your email.\" };\n  } catch (err) {\n    console.error(\"Password reset error:\", err);\n    return {\n      server_error: \"An unexpected error occurred. Please try again later.\",\n    };\n  }\n}\n\nexport async function passwordResetConfirm(\n  prevState: unknown,\n  formData: FormData,\n) {\n  const validatedFields = passwordResetConfirmSchema.safeParse({\n    token: formData.get(\"resetToken\") as string,\n    password: formData.get(\"password\") as string,\n    passwordConfirm: formData.get(\"passwordConfirm\") as string,\n  });\n\n  if (!validatedFields.success) {\n    return {\n      errors: validatedFields.error.flatten().fieldErrors,\n    };\n  }\n\n  const { token, password } = validatedFields.data;\n  const input = {\n    body: {\n      token,\n      password,\n    },\n  };\n  try {\n    const { error } = await resetResetPassword(input);\n    if (error) {\n      return { server_validation_error: getErrorMessage(error) };\n    }\n    redirect(`/login`);\n  } catch (err) {\n    console.error(\"Password reset confirmation error:\", err);\n    return {\n      server_error: \"An unexpected error occurred. Please try again later.\",\n    };\n  }\n}\n"
  },
  {
    "path": "nextjs-frontend/components/actions/register-action.ts",
    "content": "\"use server\";\n\nimport { redirect } from \"next/navigation\";\n\nimport { registerRegister } from \"@/app/clientService\";\n\nimport { registerSchema } from \"@/lib/definitions\";\nimport { getErrorMessage } from \"@/lib/utils\";\n\nexport async function register(prevState: unknown, formData: FormData) {\n  const validatedFields = registerSchema.safeParse({\n    email: formData.get(\"email\") as string,\n    password: formData.get(\"password\") as string,\n  });\n\n  if (!validatedFields.success) {\n    return {\n      errors: validatedFields.error.flatten().fieldErrors,\n    };\n  }\n\n  const { email, password } = validatedFields.data;\n\n  const input = {\n    body: {\n      email,\n      password,\n    },\n  };\n  try {\n    const { error } = await registerRegister(input);\n    if (error) {\n      return { server_validation_error: getErrorMessage(error) };\n    }\n  } catch (err) {\n    console.error(\"Registration error:\", err);\n    return {\n      server_error: \"An unexpected error occurred. Please try again later.\",\n    };\n  }\n  redirect(`/login`);\n}\n"
  },
  {
    "path": "nextjs-frontend/components/page-pagination.tsx",
    "content": "import { Button } from \"@/components/ui/button\";\nimport Link from \"next/link\";\nimport {\n  ChevronLeftIcon,\n  ChevronRightIcon,\n  DoubleArrowLeftIcon,\n  DoubleArrowRightIcon,\n} from \"@radix-ui/react-icons\";\n\ninterface PagePaginationProps {\n  currentPage: number;\n  totalPages: number;\n  pageSize: number;\n  totalItems: number;\n  basePath?: string;\n}\n\nexport function PagePagination({\n  currentPage,\n  totalPages,\n  pageSize,\n  totalItems,\n  basePath = \"/dashboard\",\n}: PagePaginationProps) {\n  const hasNextPage = currentPage < totalPages;\n  const hasPreviousPage = currentPage > 1;\n\n  const buildUrl = (page: number) =>\n    `${basePath}?page=${page}&size=${pageSize}`;\n\n  return (\n    <div className=\"flex items-center justify-between my-4\">\n      <div className=\"text-sm text-gray-600\">\n        {totalItems === 0 ? (\n          <>Showing 0 of 0 results</>\n        ) : (\n          <>\n            Showing {(currentPage - 1) * pageSize + 1} to{\" \"}\n            {Math.min(currentPage * pageSize, totalItems)} of {totalItems}{\" \"}\n            results\n          </>\n        )}\n      </div>\n\n      <div className=\"flex items-center space-x-2\">\n        {/* First Page */}\n        <Link\n          href={buildUrl(1)}\n          className={!hasPreviousPage ? \"pointer-events-none opacity-50\" : \"\"}\n        >\n          <Button variant=\"outline\" size=\"sm\" disabled={!hasPreviousPage}>\n            <DoubleArrowLeftIcon className=\"h-4 w-4\" />\n          </Button>\n        </Link>\n\n        {/* Previous Page */}\n        <Link\n          href={buildUrl(currentPage - 1)}\n          className={!hasPreviousPage ? \"pointer-events-none opacity-50\" : \"\"}\n        >\n          <Button variant=\"outline\" size=\"sm\" disabled={!hasPreviousPage}>\n            <ChevronLeftIcon className=\"h-4 w-4\" />\n          </Button>\n        </Link>\n\n        {/* Page Info */}\n        {totalPages > 0 && (\n          <span className=\"text-sm font-medium\">\n            Page {currentPage} of {totalPages}\n          </span>\n        )}\n\n        {/* Next Page */}\n        <Link\n          href={buildUrl(currentPage + 1)}\n          className={hasNextPage ? \"\" : \"pointer-events-none opacity-50\"}\n        >\n          <Button variant=\"outline\" size=\"sm\" disabled={!hasNextPage}>\n            <ChevronRightIcon className=\"h-4 w-4\" />\n          </Button>\n        </Link>\n\n        {/* Last Page */}\n        <Link\n          href={buildUrl(totalPages)}\n          className={hasNextPage ? \"\" : \"pointer-events-none opacity-50\"}\n        >\n          <Button variant=\"outline\" size=\"sm\" disabled={!hasNextPage}>\n            <DoubleArrowRightIcon className=\"h-4 w-4\" />\n          </Button>\n        </Link>\n      </div>\n    </div>\n  );\n}\n"
  },
  {
    "path": "nextjs-frontend/components/page-size-selector.tsx",
    "content": "\"use client\";\n\nimport {\n  Select,\n  SelectContent,\n  SelectItem,\n  SelectTrigger,\n  SelectValue,\n} from \"@/components/ui/select\";\nimport { useRouter } from \"next/navigation\";\n\ninterface PageSizeSelectorProps {\n  currentSize: number;\n}\n\nexport function PageSizeSelector({ currentSize }: PageSizeSelectorProps) {\n  const router = useRouter();\n  const pageSizeOptions = [5, 10, 20, 50, 100];\n\n  const handleSizeChange = (newSize: string) => {\n    router.push(`/dashboard?page=1&size=${newSize}`);\n  };\n\n  return (\n    <div className=\"flex items-center space-x-2\">\n      <span className=\"text-sm text-gray-600\">Items per page:</span>\n      <Select value={currentSize.toString()} onValueChange={handleSizeChange}>\n        <SelectTrigger className=\"w-20\">\n          <SelectValue />\n        </SelectTrigger>\n        <SelectContent>\n          {pageSizeOptions.map((option) => (\n            <SelectItem key={option} value={option.toString()}>\n              {option}\n            </SelectItem>\n          ))}\n        </SelectContent>\n      </Select>\n    </div>\n  );\n}\n"
  },
  {
    "path": "nextjs-frontend/components/ui/FormError.tsx",
    "content": "interface ErrorState {\n  errors?: {\n    [key: string]: string | string[];\n  };\n  server_validation_error?: string;\n  server_error?: string;\n}\n\ninterface FormErrorProps {\n  state?: ErrorState;\n  className?: string;\n}\n\nexport function FormError({ state, className = \"\" }: FormErrorProps) {\n  if (!state) return null;\n\n  const error = state.server_validation_error || state.server_error;\n  if (!error) return null;\n\n  return <p className={`text-sm text-red-500 ${className}`}>{error}</p>;\n}\n\ninterface FieldErrorProps {\n  state?: ErrorState;\n  field: string;\n  className?: string;\n}\n\nexport function FieldError({ state, field, className = \"\" }: FieldErrorProps) {\n  if (!state?.errors) return null;\n\n  const error = state.errors[field];\n  if (!error) return null;\n\n  if (Array.isArray(error)) {\n    return (\n      <div className={`text-sm text-red-500 ${className}`}>\n        <ul className=\"list-disc ml-4\">\n          {error.map((err) => (\n            <li key={err}>{err}</li>\n          ))}\n        </ul>\n      </div>\n    );\n  }\n\n  return <p className={`text-sm text-red-500 ${className}`}>{error}</p>;\n}\n"
  },
  {
    "path": "nextjs-frontend/components/ui/avatar.tsx",
    "content": "\"use client\";\n\nimport * as React from \"react\";\nimport * as AvatarPrimitive from \"@radix-ui/react-avatar\";\n\nimport { cn } from \"@/lib/utils\";\n\nconst Avatar = React.forwardRef<\n  React.ElementRef<typeof AvatarPrimitive.Root>,\n  React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root>\n>(({ className, ...props }, ref) => (\n  <AvatarPrimitive.Root\n    ref={ref}\n    className={cn(\n      \"relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full\",\n      className,\n    )}\n    {...props}\n  />\n));\nAvatar.displayName = AvatarPrimitive.Root.displayName;\n\nconst AvatarImage = React.forwardRef<\n  React.ElementRef<typeof AvatarPrimitive.Image>,\n  React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Image>\n>(({ className, ...props }, ref) => (\n  <AvatarPrimitive.Image\n    ref={ref}\n    className={cn(\"aspect-square h-full w-full\", className)}\n    {...props}\n  />\n));\nAvatarImage.displayName = AvatarPrimitive.Image.displayName;\n\nconst AvatarFallback = React.forwardRef<\n  React.ElementRef<typeof AvatarPrimitive.Fallback>,\n  React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Fallback>\n>(({ className, ...props }, ref) => (\n  <AvatarPrimitive.Fallback\n    ref={ref}\n    className={cn(\n      \"flex h-full w-full items-center justify-center rounded-full bg-muted\",\n      className,\n    )}\n    {...props}\n  />\n));\nAvatarFallback.displayName = AvatarPrimitive.Fallback.displayName;\n\nexport { Avatar, AvatarImage, AvatarFallback };\n"
  },
  {
    "path": "nextjs-frontend/components/ui/badge.tsx",
    "content": "import * as React from \"react\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\n\nimport { cn } from \"@/lib/utils\";\n\nconst badgeVariants = cva(\n  \"inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2\",\n  {\n    variants: {\n      variant: {\n        default:\n          \"border-transparent bg-primary text-primary-foreground shadow hover:bg-primary/80\",\n        secondary:\n          \"border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80\",\n        destructive:\n          \"border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80\",\n        outline: \"text-foreground\",\n      },\n    },\n    defaultVariants: {\n      variant: \"default\",\n    },\n  },\n);\n\nexport interface BadgeProps\n  extends React.HTMLAttributes<HTMLDivElement>,\n    VariantProps<typeof badgeVariants> {}\n\nfunction Badge({ className, variant, ...props }: BadgeProps) {\n  return (\n    <div className={cn(badgeVariants({ variant }), className)} {...props} />\n  );\n}\n\nexport { Badge, badgeVariants };\n"
  },
  {
    "path": "nextjs-frontend/components/ui/breadcrumb.tsx",
    "content": "import * as React from \"react\";\nimport { Slot } from \"@radix-ui/react-slot\";\nimport { cn } from \"@/lib/utils\";\nimport { ChevronRightIcon, DotsHorizontalIcon } from \"@radix-ui/react-icons\";\n\nconst Breadcrumb = React.forwardRef<\n  HTMLElement,\n  React.ComponentPropsWithoutRef<\"nav\"> & {\n    separator?: React.ReactNode;\n  }\n>(({ ...props }, ref) => <nav ref={ref} aria-label=\"breadcrumb\" {...props} />);\nBreadcrumb.displayName = \"Breadcrumb\";\n\nconst BreadcrumbList = React.forwardRef<\n  HTMLOListElement,\n  React.ComponentPropsWithoutRef<\"ol\">\n>(({ className, ...props }, ref) => (\n  <ol\n    ref={ref}\n    className={cn(\n      \"flex flex-wrap items-center gap-1.5 break-words text-sm text-muted-foreground sm:gap-2.5\",\n      className,\n    )}\n    {...props}\n  />\n));\nBreadcrumbList.displayName = \"BreadcrumbList\";\n\nconst BreadcrumbItem = React.forwardRef<\n  HTMLLIElement,\n  React.ComponentPropsWithoutRef<\"li\">\n>(({ className, ...props }, ref) => (\n  <li\n    ref={ref}\n    className={cn(\"inline-flex items-center gap-1.5\", className)}\n    {...props}\n  />\n));\nBreadcrumbItem.displayName = \"BreadcrumbItem\";\n\nconst BreadcrumbLink = React.forwardRef<\n  HTMLAnchorElement,\n  React.ComponentPropsWithoutRef<\"a\"> & {\n    asChild?: boolean;\n  }\n>(({ asChild, className, ...props }, ref) => {\n  const Comp = asChild ? Slot : \"a\";\n\n  return (\n    <Comp\n      ref={ref}\n      className={cn(\"transition-colors hover:text-foreground\", className)}\n      {...props}\n    />\n  );\n});\nBreadcrumbLink.displayName = \"BreadcrumbLink\";\n\nconst BreadcrumbPage = React.forwardRef<\n  HTMLSpanElement,\n  React.ComponentPropsWithoutRef<\"span\">\n>(({ className, ...props }, ref) => (\n  <span\n    ref={ref}\n    role=\"link\"\n    aria-disabled=\"true\"\n    aria-current=\"page\"\n    className={cn(\"font-normal text-foreground\", className)}\n    {...props}\n  />\n));\nBreadcrumbPage.displayName = \"BreadcrumbPage\";\n\nconst BreadcrumbSeparator = ({\n  children,\n  className,\n  ...props\n}: React.ComponentProps<\"li\">) => (\n  <li\n    role=\"presentation\"\n    aria-hidden=\"true\"\n    className={cn(\"[&>svg]:w-3.5 [&>svg]:h-3.5\", className)}\n    {...props}\n  >\n    {children ?? <ChevronRightIcon />}\n  </li>\n);\nBreadcrumbSeparator.displayName = \"BreadcrumbSeparator\";\n\nconst BreadcrumbEllipsis = ({\n  className,\n  ...props\n}: React.ComponentProps<\"span\">) => (\n  <span\n    role=\"presentation\"\n    aria-hidden=\"true\"\n    className={cn(\"flex h-9 w-9 items-center justify-center\", className)}\n    {...props}\n  >\n    <DotsHorizontalIcon className=\"h-4 w-4\" />\n    <span className=\"sr-only\">More</span>\n  </span>\n);\nBreadcrumbEllipsis.displayName = \"BreadcrumbElipssis\";\n\nexport {\n  Breadcrumb,\n  BreadcrumbList,\n  BreadcrumbItem,\n  BreadcrumbLink,\n  BreadcrumbPage,\n  BreadcrumbSeparator,\n  BreadcrumbEllipsis,\n};\n"
  },
  {
    "path": "nextjs-frontend/components/ui/button.tsx",
    "content": "import * as React from \"react\";\nimport { Slot } from \"@radix-ui/react-slot\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\n\nimport { cn } from \"@/lib/utils\";\n\nconst buttonVariants = cva(\n  \"inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50\",\n  {\n    variants: {\n      variant: {\n        default:\n          \"bg-primary text-primary-foreground shadow hover:bg-primary/90\",\n        destructive:\n          \"bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90\",\n        outline:\n          \"border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground\",\n        secondary:\n          \"bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80\",\n        ghost: \"hover:bg-accent hover:text-accent-foreground\",\n        link: \"text-primary underline-offset-4 hover:underline\",\n      },\n      size: {\n        default: \"h-9 px-4 py-2\",\n        sm: \"h-8 rounded-md px-3 text-xs\",\n        lg: \"h-10 rounded-md px-8\",\n        icon: \"h-9 w-9\",\n      },\n    },\n    defaultVariants: {\n      variant: \"default\",\n      size: \"default\",\n    },\n  },\n);\n\nexport interface ButtonProps\n  extends React.ButtonHTMLAttributes<HTMLButtonElement>,\n    VariantProps<typeof buttonVariants> {\n  asChild?: boolean;\n}\n\nconst Button = React.forwardRef<HTMLButtonElement, ButtonProps>(\n  ({ className, variant, size, asChild = false, ...props }, ref) => {\n    const Comp = asChild ? Slot : \"button\";\n    return (\n      <Comp\n        className={cn(buttonVariants({ variant, size, className }))}\n        ref={ref}\n        {...props}\n      />\n    );\n  },\n);\nButton.displayName = \"Button\";\n\nexport { Button, buttonVariants };\n"
  },
  {
    "path": "nextjs-frontend/components/ui/card.tsx",
    "content": "import * as React from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\nconst Card = React.forwardRef<\n  HTMLDivElement,\n  React.HTMLAttributes<HTMLDivElement>\n>(({ className, ...props }, ref) => (\n  <div\n    ref={ref}\n    className={cn(\n      \"rounded-xl border bg-card text-card-foreground shadow\",\n      className,\n    )}\n    {...props}\n  />\n));\nCard.displayName = \"Card\";\n\nconst CardHeader = React.forwardRef<\n  HTMLDivElement,\n  React.HTMLAttributes<HTMLDivElement>\n>(({ className, ...props }, ref) => (\n  <div\n    ref={ref}\n    className={cn(\"flex flex-col space-y-1.5 p-6\", className)}\n    {...props}\n  />\n));\nCardHeader.displayName = \"CardHeader\";\n\nconst CardTitle = React.forwardRef<\n  HTMLParagraphElement,\n  React.HTMLAttributes<HTMLHeadingElement>\n>(({ className, ...props }, ref) => (\n  <h3\n    ref={ref}\n    className={cn(\"font-semibold leading-none tracking-tight\", className)}\n    {...props}\n  />\n));\nCardTitle.displayName = \"CardTitle\";\n\nconst CardDescription = React.forwardRef<\n  HTMLParagraphElement,\n  React.HTMLAttributes<HTMLParagraphElement>\n>(({ className, ...props }, ref) => (\n  <p\n    ref={ref}\n    className={cn(\"text-sm text-muted-foreground\", className)}\n    {...props}\n  />\n));\nCardDescription.displayName = \"CardDescription\";\n\nconst CardContent = React.forwardRef<\n  HTMLDivElement,\n  React.HTMLAttributes<HTMLDivElement>\n>(({ className, ...props }, ref) => (\n  <div ref={ref} className={cn(\"p-6 pt-0\", className)} {...props} />\n));\nCardContent.displayName = \"CardContent\";\n\nconst CardFooter = React.forwardRef<\n  HTMLDivElement,\n  React.HTMLAttributes<HTMLDivElement>\n>(({ className, ...props }, ref) => (\n  <div\n    ref={ref}\n    className={cn(\"flex items-center p-6 pt-0\", className)}\n    {...props}\n  />\n));\nCardFooter.displayName = \"CardFooter\";\n\nexport {\n  Card,\n  CardHeader,\n  CardFooter,\n  CardTitle,\n  CardDescription,\n  CardContent,\n};\n"
  },
  {
    "path": "nextjs-frontend/components/ui/dropdown-menu.tsx",
    "content": "\"use client\";\n\nimport * as React from \"react\";\nimport * as DropdownMenuPrimitive from \"@radix-ui/react-dropdown-menu\";\nimport { cn } from \"@/lib/utils\";\nimport {\n  CheckIcon,\n  ChevronRightIcon,\n  DotFilledIcon,\n} from \"@radix-ui/react-icons\";\n\nconst DropdownMenu = DropdownMenuPrimitive.Root;\n\nconst DropdownMenuTrigger = DropdownMenuPrimitive.Trigger;\n\nconst DropdownMenuGroup = DropdownMenuPrimitive.Group;\n\nconst DropdownMenuPortal = DropdownMenuPrimitive.Portal;\n\nconst DropdownMenuSub = DropdownMenuPrimitive.Sub;\n\nconst DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup;\n\nconst DropdownMenuSubTrigger = React.forwardRef<\n  React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>,\n  React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & {\n    inset?: boolean;\n  }\n>(({ className, inset, children, ...props }, ref) => (\n  <DropdownMenuPrimitive.SubTrigger\n    ref={ref}\n    className={cn(\n      \"flex cursor-default gap-2 select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0\",\n      inset && \"pl-8\",\n      className,\n    )}\n    {...props}\n  >\n    {children}\n    <ChevronRightIcon className=\"ml-auto\" />\n  </DropdownMenuPrimitive.SubTrigger>\n));\nDropdownMenuSubTrigger.displayName =\n  DropdownMenuPrimitive.SubTrigger.displayName;\n\nconst DropdownMenuSubContent = React.forwardRef<\n  React.ElementRef<typeof DropdownMenuPrimitive.SubContent>,\n  React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent>\n>(({ className, ...props }, ref) => (\n  <DropdownMenuPrimitive.SubContent\n    ref={ref}\n    className={cn(\n      \"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2\",\n      className,\n    )}\n    {...props}\n  />\n));\nDropdownMenuSubContent.displayName =\n  DropdownMenuPrimitive.SubContent.displayName;\n\nconst DropdownMenuContent = React.forwardRef<\n  React.ElementRef<typeof DropdownMenuPrimitive.Content>,\n  React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>\n>(({ className, sideOffset = 4, ...props }, ref) => (\n  <DropdownMenuPrimitive.Portal>\n    <DropdownMenuPrimitive.Content\n      ref={ref}\n      sideOffset={sideOffset}\n      className={cn(\n        \"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md\",\n        \"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2\",\n        className,\n      )}\n      {...props}\n    />\n  </DropdownMenuPrimitive.Portal>\n));\nDropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName;\n\nconst DropdownMenuItem = React.forwardRef<\n  React.ElementRef<typeof DropdownMenuPrimitive.Item>,\n  React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {\n    inset?: boolean;\n  }\n>(({ className, inset, ...props }, ref) => (\n  <DropdownMenuPrimitive.Item\n    ref={ref}\n    className={cn(\n      \"relative flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&>svg]:size-4 [&>svg]:shrink-0\",\n      inset && \"pl-8\",\n      className,\n    )}\n    {...props}\n  />\n));\nDropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName;\n\nconst DropdownMenuCheckboxItem = React.forwardRef<\n  React.ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>,\n  React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.CheckboxItem>\n>(({ className, children, checked, ...props }, ref) => (\n  <DropdownMenuPrimitive.CheckboxItem\n    ref={ref}\n    className={cn(\n      \"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50\",\n      className,\n    )}\n    checked={checked}\n    {...props}\n  >\n    <span className=\"absolute left-2 flex h-3.5 w-3.5 items-center justify-center\">\n      <DropdownMenuPrimitive.ItemIndicator>\n        <CheckIcon className=\"h-4 w-4\" />\n      </DropdownMenuPrimitive.ItemIndicator>\n    </span>\n    {children}\n  </DropdownMenuPrimitive.CheckboxItem>\n));\nDropdownMenuCheckboxItem.displayName =\n  DropdownMenuPrimitive.CheckboxItem.displayName;\n\nconst DropdownMenuRadioItem = React.forwardRef<\n  React.ElementRef<typeof DropdownMenuPrimitive.RadioItem>,\n  React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.RadioItem>\n>(({ className, children, ...props }, ref) => (\n  <DropdownMenuPrimitive.RadioItem\n    ref={ref}\n    className={cn(\n      \"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50\",\n      className,\n    )}\n    {...props}\n  >\n    <span className=\"absolute left-2 flex h-3.5 w-3.5 items-center justify-center\">\n      <DropdownMenuPrimitive.ItemIndicator>\n        <DotFilledIcon className=\"h-2 w-2 fill-current\" />\n      </DropdownMenuPrimitive.ItemIndicator>\n    </span>\n    {children}\n  </DropdownMenuPrimitive.RadioItem>\n));\nDropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName;\n\nconst DropdownMenuLabel = React.forwardRef<\n  React.ElementRef<typeof DropdownMenuPrimitive.Label>,\n  React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & {\n    inset?: boolean;\n  }\n>(({ className, inset, ...props }, ref) => (\n  <DropdownMenuPrimitive.Label\n    ref={ref}\n    className={cn(\n      \"px-2 py-1.5 text-sm font-semibold\",\n      inset && \"pl-8\",\n      className,\n    )}\n    {...props}\n  />\n));\nDropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName;\n\nconst DropdownMenuSeparator = React.forwardRef<\n  React.ElementRef<typeof DropdownMenuPrimitive.Separator>,\n  React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator>\n>(({ className, ...props }, ref) => (\n  <DropdownMenuPrimitive.Separator\n    ref={ref}\n    className={cn(\"-mx-1 my-1 h-px bg-muted\", className)}\n    {...props}\n  />\n));\nDropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName;\n\nconst DropdownMenuShortcut = ({\n  className,\n  ...props\n}: React.HTMLAttributes<HTMLSpanElement>) => {\n  return (\n    <span\n      className={cn(\"ml-auto text-xs tracking-widest opacity-60\", className)}\n      {...props}\n    />\n  );\n};\nDropdownMenuShortcut.displayName = \"DropdownMenuShortcut\";\n\nexport {\n  DropdownMenu,\n  DropdownMenuTrigger,\n  DropdownMenuContent,\n  DropdownMenuItem,\n  DropdownMenuCheckboxItem,\n  DropdownMenuRadioItem,\n  DropdownMenuLabel,\n  DropdownMenuSeparator,\n  DropdownMenuShortcut,\n  DropdownMenuGroup,\n  DropdownMenuPortal,\n  DropdownMenuSub,\n  DropdownMenuSubContent,\n  DropdownMenuSubTrigger,\n  DropdownMenuRadioGroup,\n};\n"
  },
  {
    "path": "nextjs-frontend/components/ui/form.tsx",
    "content": "\"use client\";\n\nimport * as React from \"react\";\nimport * as LabelPrimitive from \"@radix-ui/react-label\";\nimport { Slot } from \"@radix-ui/react-slot\";\nimport {\n  Controller,\n  ControllerProps,\n  FieldPath,\n  FieldValues,\n  FormProvider,\n  useFormContext,\n} from \"react-hook-form\";\n\nimport { cn } from \"@/lib/utils\";\nimport { Label } from \"@/components/ui/label\";\n\nconst Form = FormProvider;\n\ntype FormFieldContextValue<\n  TFieldValues extends FieldValues = FieldValues,\n  TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,\n> = {\n  name: TName;\n};\n\nconst FormFieldContext = React.createContext<FormFieldContextValue>(\n  {} as FormFieldContextValue,\n);\n\nconst FormField = <\n  TFieldValues extends FieldValues = FieldValues,\n  TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,\n>({\n  ...props\n}: ControllerProps<TFieldValues, TName>) => {\n  return (\n    <FormFieldContext.Provider value={{ name: props.name }}>\n      <Controller {...props} />\n    </FormFieldContext.Provider>\n  );\n};\n\nconst useFormField = () => {\n  const fieldContext = React.useContext(FormFieldContext);\n  const itemContext = React.useContext(FormItemContext);\n  const { getFieldState, formState } = useFormContext();\n\n  const fieldState = getFieldState(fieldContext.name, formState);\n\n  if (!fieldContext) {\n    throw new Error(\"useFormField should be used within <FormField>\");\n  }\n\n  const { id } = itemContext;\n\n  return {\n    id,\n    name: fieldContext.name,\n    formItemId: `${id}-form-item`,\n    formDescriptionId: `${id}-form-item-description`,\n    formMessageId: `${id}-form-item-message`,\n    ...fieldState,\n  };\n};\n\ntype FormItemContextValue = {\n  id: string;\n};\n\nconst FormItemContext = React.createContext<FormItemContextValue>(\n  {} as FormItemContextValue,\n);\n\nconst FormItem = React.forwardRef<\n  HTMLDivElement,\n  React.HTMLAttributes<HTMLDivElement>\n>(({ className, ...props }, ref) => {\n  const id = React.useId();\n\n  return (\n    <FormItemContext.Provider value={{ id }}>\n      <div ref={ref} className={cn(\"space-y-2\", className)} {...props} />\n    </FormItemContext.Provider>\n  );\n});\nFormItem.displayName = \"FormItem\";\n\nconst FormLabel = React.forwardRef<\n  React.ElementRef<typeof LabelPrimitive.Root>,\n  React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root>\n>(({ className, ...props }, ref) => {\n  const { error, formItemId } = useFormField();\n\n  return (\n    <Label\n      ref={ref}\n      className={cn(error && \"text-destructive\", className)}\n      htmlFor={formItemId}\n      {...props}\n    />\n  );\n});\nFormLabel.displayName = \"FormLabel\";\n\nconst FormControl = React.forwardRef<\n  React.ElementRef<typeof Slot>,\n  React.ComponentPropsWithoutRef<typeof Slot>\n>(({ ...props }, ref) => {\n  const { error, formItemId, formDescriptionId, formMessageId } =\n    useFormField();\n\n  return (\n    <Slot\n      ref={ref}\n      id={formItemId}\n      aria-describedby={\n        !error\n          ? `${formDescriptionId}`\n          : `${formDescriptionId} ${formMessageId}`\n      }\n      aria-invalid={!!error}\n      {...props}\n    />\n  );\n});\nFormControl.displayName = \"FormControl\";\n\nconst FormDescription = React.forwardRef<\n  HTMLParagraphElement,\n  React.HTMLAttributes<HTMLParagraphElement>\n>(({ className, ...props }, ref) => {\n  const { formDescriptionId } = useFormField();\n\n  return (\n    <p\n      ref={ref}\n      id={formDescriptionId}\n      className={cn(\"text-[0.8rem] text-muted-foreground\", className)}\n      {...props}\n    />\n  );\n});\nFormDescription.displayName = \"FormDescription\";\n\nconst FormMessage = React.forwardRef<\n  HTMLParagraphElement,\n  React.HTMLAttributes<HTMLParagraphElement>\n>(({ className, children, ...props }, ref) => {\n  const { error, formMessageId } = useFormField();\n  const body = error ? String(error?.message) : children;\n\n  if (!body) {\n    return null;\n  }\n\n  return (\n    <p\n      ref={ref}\n      id={formMessageId}\n      className={cn(\"text-[0.8rem] font-medium text-destructive\", className)}\n      {...props}\n    >\n      {body}\n    </p>\n  );\n});\nFormMessage.displayName = \"FormMessage\";\n\nexport {\n  useFormField,\n  Form,\n  FormItem,\n  FormLabel,\n  FormControl,\n  FormDescription,\n  FormMessage,\n  FormField,\n};\n"
  },
  {
    "path": "nextjs-frontend/components/ui/input.tsx",
    "content": "import * as React from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\nexport interface InputProps\n  extends React.InputHTMLAttributes<HTMLInputElement> {}\n\nconst Input = React.forwardRef<HTMLInputElement, InputProps>(\n  ({ className, type, ...props }, ref) => {\n    return (\n      <input\n        type={type}\n        className={cn(\n          \"flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50\",\n          className,\n        )}\n        ref={ref}\n        {...props}\n      />\n    );\n  },\n);\nInput.displayName = \"Input\";\n\nexport { Input };\n"
  },
  {
    "path": "nextjs-frontend/components/ui/label.tsx",
    "content": "\"use client\";\n\nimport * as React from \"react\";\nimport * as LabelPrimitive from \"@radix-ui/react-label\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\n\nimport { cn } from \"@/lib/utils\";\n\nconst labelVariants = cva(\n  \"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70\",\n);\n\nconst Label = React.forwardRef<\n  React.ElementRef<typeof LabelPrimitive.Root>,\n  React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> &\n    VariantProps<typeof labelVariants>\n>(({ className, ...props }, ref) => (\n  <LabelPrimitive.Root\n    ref={ref}\n    className={cn(labelVariants(), className)}\n    {...props}\n  />\n));\nLabel.displayName = LabelPrimitive.Root.displayName;\n\nexport { Label };\n"
  },
  {
    "path": "nextjs-frontend/components/ui/select.tsx",
    "content": "\"use client\";\n\nimport * as React from \"react\";\nimport * as SelectPrimitive from \"@radix-ui/react-select\";\nimport { cn } from \"@/lib/utils\";\nimport {\n  CheckIcon,\n  ChevronDownIcon,\n  ChevronUpIcon,\n} from \"@radix-ui/react-icons\";\n\nconst Select = SelectPrimitive.Root;\n\nconst SelectGroup = SelectPrimitive.Group;\n\nconst SelectValue = SelectPrimitive.Value;\n\nconst SelectTrigger = React.forwardRef<\n  React.ElementRef<typeof SelectPrimitive.Trigger>,\n  React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>\n>(({ className, children, ...props }, ref) => (\n  <SelectPrimitive.Trigger\n    ref={ref}\n    className={cn(\n      \"flex h-9 w-full items-center justify-between whitespace-nowrap rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm ring-offset-background data-[placeholder]:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1\",\n      className,\n    )}\n    {...props}\n  >\n    {children}\n    <SelectPrimitive.Icon asChild>\n      <ChevronDownIcon className=\"h-4 w-4 opacity-50\" />\n    </SelectPrimitive.Icon>\n  </SelectPrimitive.Trigger>\n));\nSelectTrigger.displayName = SelectPrimitive.Trigger.displayName;\n\nconst SelectScrollUpButton = React.forwardRef<\n  React.ElementRef<typeof SelectPrimitive.ScrollUpButton>,\n  React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollUpButton>\n>(({ className, ...props }, ref) => (\n  <SelectPrimitive.ScrollUpButton\n    ref={ref}\n    className={cn(\n      \"flex cursor-default items-center justify-center py-1\",\n      className,\n    )}\n    {...props}\n  >\n    <ChevronUpIcon className=\"h-4 w-4\" />\n  </SelectPrimitive.ScrollUpButton>\n));\nSelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName;\n\nconst SelectScrollDownButton = React.forwardRef<\n  React.ElementRef<typeof SelectPrimitive.ScrollDownButton>,\n  React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollDownButton>\n>(({ className, ...props }, ref) => (\n  <SelectPrimitive.ScrollDownButton\n    ref={ref}\n    className={cn(\n      \"flex cursor-default items-center justify-center py-1\",\n      className,\n    )}\n    {...props}\n  >\n    <ChevronDownIcon className=\"h-4 w-4\" />\n  </SelectPrimitive.ScrollDownButton>\n));\nSelectScrollDownButton.displayName =\n  SelectPrimitive.ScrollDownButton.displayName;\n\nconst SelectContent = React.forwardRef<\n  React.ElementRef<typeof SelectPrimitive.Content>,\n  React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>\n>(({ className, children, position = \"popper\", ...props }, ref) => (\n  <SelectPrimitive.Portal>\n    <SelectPrimitive.Content\n      ref={ref}\n      className={cn(\n        \"relative z-50 max-h-[--radix-select-content-available-height] min-w-[8rem] overflow-y-auto overflow-x-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[--radix-select-content-transform-origin]\",\n        position === \"popper\" &&\n          \"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1\",\n        className,\n      )}\n      position={position}\n      {...props}\n    >\n      <SelectScrollUpButton />\n      <SelectPrimitive.Viewport\n        className={cn(\n          \"p-1\",\n          position === \"popper\" &&\n            \"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]\",\n        )}\n      >\n        {children}\n      </SelectPrimitive.Viewport>\n      <SelectScrollDownButton />\n    </SelectPrimitive.Content>\n  </SelectPrimitive.Portal>\n));\nSelectContent.displayName = SelectPrimitive.Content.displayName;\n\nconst SelectLabel = React.forwardRef<\n  React.ElementRef<typeof SelectPrimitive.Label>,\n  React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>\n>(({ className, ...props }, ref) => (\n  <SelectPrimitive.Label\n    ref={ref}\n    className={cn(\"px-2 py-1.5 text-sm font-semibold\", className)}\n    {...props}\n  />\n));\nSelectLabel.displayName = SelectPrimitive.Label.displayName;\n\nconst SelectItem = React.forwardRef<\n  React.ElementRef<typeof SelectPrimitive.Item>,\n  React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>\n>(({ className, children, ...props }, ref) => (\n  <SelectPrimitive.Item\n    ref={ref}\n    className={cn(\n      \"relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-2 pr-8 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50\",\n      className,\n    )}\n    {...props}\n  >\n    <span className=\"absolute right-2 flex h-3.5 w-3.5 items-center justify-center\">\n      <SelectPrimitive.ItemIndicator>\n        <CheckIcon className=\"h-4 w-4\" />\n      </SelectPrimitive.ItemIndicator>\n    </span>\n    <SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>\n  </SelectPrimitive.Item>\n));\nSelectItem.displayName = SelectPrimitive.Item.displayName;\n\nconst SelectSeparator = React.forwardRef<\n  React.ElementRef<typeof SelectPrimitive.Separator>,\n  React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>\n>(({ className, ...props }, ref) => (\n  <SelectPrimitive.Separator\n    ref={ref}\n    className={cn(\"-mx-1 my-1 h-px bg-muted\", className)}\n    {...props}\n  />\n));\nSelectSeparator.displayName = SelectPrimitive.Separator.displayName;\n\nexport {\n  Select,\n  SelectGroup,\n  SelectValue,\n  SelectTrigger,\n  SelectContent,\n  SelectLabel,\n  SelectItem,\n  SelectSeparator,\n  SelectScrollUpButton,\n  SelectScrollDownButton,\n};\n"
  },
  {
    "path": "nextjs-frontend/components/ui/submitButton.tsx",
    "content": "import { useFormStatus } from \"react-dom\";\nimport { Button } from \"@/components/ui/button\";\n\nexport function SubmitButton({ text }: { text: string }) {\n  const { pending } = useFormStatus();\n\n  return (\n    <Button className=\"w-full\" type=\"submit\" disabled={pending}>\n      {pending ? \"Loading...\" : text}\n    </Button>\n  );\n}\n"
  },
  {
    "path": "nextjs-frontend/components/ui/table.tsx",
    "content": "import * as React from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\nconst Table = React.forwardRef<\n  HTMLTableElement,\n  React.HTMLAttributes<HTMLTableElement>\n>(({ className, ...props }, ref) => (\n  <div className=\"relative w-full overflow-auto\">\n    <table\n      ref={ref}\n      className={cn(\"w-full caption-bottom text-sm\", className)}\n      {...props}\n    />\n  </div>\n));\nTable.displayName = \"Table\";\n\nconst TableHeader = React.forwardRef<\n  HTMLTableSectionElement,\n  React.HTMLAttributes<HTMLTableSectionElement>\n>(({ className, ...props }, ref) => (\n  <thead ref={ref} className={cn(\"[&_tr]:border-b\", className)} {...props} />\n));\nTableHeader.displayName = \"TableHeader\";\n\nconst TableBody = React.forwardRef<\n  HTMLTableSectionElement,\n  React.HTMLAttributes<HTMLTableSectionElement>\n>(({ className, ...props }, ref) => (\n  <tbody\n    ref={ref}\n    className={cn(\"[&_tr:last-child]:border-0\", className)}\n    {...props}\n  />\n));\nTableBody.displayName = \"TableBody\";\n\nconst TableFooter = React.forwardRef<\n  HTMLTableSectionElement,\n  React.HTMLAttributes<HTMLTableSectionElement>\n>(({ className, ...props }, ref) => (\n  <tfoot\n    ref={ref}\n    className={cn(\n      \"border-t bg-muted/50 font-medium [&>tr]:last:border-b-0\",\n      className,\n    )}\n    {...props}\n  />\n));\nTableFooter.displayName = \"TableFooter\";\n\nconst TableRow = React.forwardRef<\n  HTMLTableRowElement,\n  React.HTMLAttributes<HTMLTableRowElement>\n>(({ className, ...props }, ref) => (\n  <tr\n    ref={ref}\n    className={cn(\n      \"border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted\",\n      className,\n    )}\n    {...props}\n  />\n));\nTableRow.displayName = \"TableRow\";\n\nconst TableHead = React.forwardRef<\n  HTMLTableCellElement,\n  React.ThHTMLAttributes<HTMLTableCellElement>\n>(({ className, ...props }, ref) => (\n  <th\n    ref={ref}\n    className={cn(\n      \"h-10 px-2 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]\",\n      className,\n    )}\n    {...props}\n  />\n));\nTableHead.displayName = \"TableHead\";\n\nconst TableCell = React.forwardRef<\n  HTMLTableCellElement,\n  React.TdHTMLAttributes<HTMLTableCellElement>\n>(({ className, ...props }, ref) => (\n  <td\n    ref={ref}\n    className={cn(\n      \"p-2 align-middle [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]\",\n      className,\n    )}\n    {...props}\n  />\n));\nTableCell.displayName = \"TableCell\";\n\nconst TableCaption = React.forwardRef<\n  HTMLTableCaptionElement,\n  React.HTMLAttributes<HTMLTableCaptionElement>\n>(({ className, ...props }, ref) => (\n  <caption\n    ref={ref}\n    className={cn(\"mt-4 text-sm text-muted-foreground\", className)}\n    {...props}\n  />\n));\nTableCaption.displayName = \"TableCaption\";\n\nexport {\n  Table,\n  TableHeader,\n  TableBody,\n  TableFooter,\n  TableHead,\n  TableRow,\n  TableCell,\n  TableCaption,\n};\n"
  },
  {
    "path": "nextjs-frontend/components/ui/tabs.tsx",
    "content": "\"use client\";\n\nimport * as React from \"react\";\nimport * as TabsPrimitive from \"@radix-ui/react-tabs\";\n\nimport { cn } from \"@/lib/utils\";\n\nconst Tabs = TabsPrimitive.Root;\n\nconst TabsList = React.forwardRef<\n  React.ElementRef<typeof TabsPrimitive.List>,\n  React.ComponentPropsWithoutRef<typeof TabsPrimitive.List>\n>(({ className, ...props }, ref) => (\n  <TabsPrimitive.List\n    ref={ref}\n    className={cn(\n      \"inline-flex h-9 items-center justify-center rounded-lg bg-muted p-1 text-muted-foreground\",\n      className,\n    )}\n    {...props}\n  />\n));\nTabsList.displayName = TabsPrimitive.List.displayName;\n\nconst TabsTrigger = React.forwardRef<\n  React.ElementRef<typeof TabsPrimitive.Trigger>,\n  React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>\n>(({ className, ...props }, ref) => (\n  <TabsPrimitive.Trigger\n    ref={ref}\n    className={cn(\n      \"inline-flex items-center justify-center whitespace-nowrap rounded-md px-3 py-1 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow\",\n      className,\n    )}\n    {...props}\n  />\n));\nTabsTrigger.displayName = TabsPrimitive.Trigger.displayName;\n\nconst TabsContent = React.forwardRef<\n  React.ElementRef<typeof TabsPrimitive.Content>,\n  React.ComponentPropsWithoutRef<typeof TabsPrimitive.Content>\n>(({ className, ...props }, ref) => (\n  <TabsPrimitive.Content\n    ref={ref}\n    className={cn(\n      \"mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2\",\n      className,\n    )}\n    {...props}\n  />\n));\nTabsContent.displayName = TabsPrimitive.Content.displayName;\n\nexport { Tabs, TabsList, TabsTrigger, TabsContent };\n"
  },
  {
    "path": "nextjs-frontend/components.json",
    "content": "{\n  \"$schema\": \"https://ui.shadcn.com/schema.json\",\n  \"style\": \"new-york\",\n  \"rsc\": true,\n  \"tsx\": true,\n  \"tailwind\": {\n    \"config\": \"tailwind.config.js\",\n    \"css\": \"app/globals.css\",\n    \"baseColor\": \"neutral\",\n    \"cssVariables\": true,\n    \"prefix\": \"\"\n  },\n  \"aliases\": {\n    \"components\": \"@/components\",\n    \"utils\": \"@/lib/utils\",\n    \"ui\": \"@/components/ui\",\n    \"lib\": \"@/lib\",\n    \"hooks\": \"@/hooks\"\n  }\n}\n"
  },
  {
    "path": "nextjs-frontend/eslint.config.mjs",
    "content": "import js from \"@eslint/js\";\nimport nextPlugin from \"@next/eslint-plugin-next\";\nimport tseslint from \"typescript-eslint\";\nimport prettier from \"eslint-config-prettier\";\nimport globals from \"globals\";\n\nconst eslintConfig = [\n  {\n    ignores: [\n      \"node_modules/**\",\n      \".next/**\",\n      \"out/**\",\n      \"build/**\",\n      \"next-env.d.ts\",\n      \"app/openapi-client/**\",\n    ],\n  },\n  js.configs.recommended,\n  ...tseslint.configs.recommended,\n  {\n    plugins: {\n      \"@next/next\": nextPlugin,\n    },\n    rules: {\n      ...nextPlugin.configs.recommended.rules,\n      ...nextPlugin.configs[\"core-web-vitals\"].rules,\n      \"@typescript-eslint/no-empty-object-type\": \"off\",\n    },\n  },\n  // CommonJS config files\n  {\n    files: [\"*.js\", \"*.cjs\"],\n    languageOptions: {\n      globals: {\n        ...globals.node,\n      },\n      sourceType: \"commonjs\",\n    },\n  },\n  prettier,\n];\n\nexport default eslintConfig;\n"
  },
  {
    "path": "nextjs-frontend/jest.config.ts",
    "content": "/**\n * For a detailed explanation regarding each configuration property, visit:\n * https://jestjs.io/docs/configuration\n */\n\nimport type { Config } from \"jest\";\nimport nextJest from \"next/jest.js\";\n\nconst createJestConfig = nextJest({\n  // Provide the path to your Next.js app to load next.config.js and .env files in your test environment\n  dir: \"./\",\n});\n\nconst config: Config = {\n  // All imported modules in your tests should be mocked automatically\n  // automock: false,\n\n  // Stop running tests after `n` failures\n  // bail: 0,\n\n  // The directory where Jest should store its cached dependency information\n  // cacheDirectory: \"/tmp/jest_rs\",\n\n  // Automatically clear mock calls, instances, contexts and results before every test\n  clearMocks: true,\n\n  // Indicates whether the coverage information should be collected while executing the test\n  // collectCoverage: false,\n\n  // An array of glob patterns indicating a set of files for which coverage information should be collected\n  // collectCoverageFrom: undefined,\n\n  // The directory where Jest should output its coverage files\n  // coverageDirectory: undefined,\n\n  // An array of regexp pattern strings used to skip coverage collection\n  // coveragePathIgnorePatterns: [\n  //   \"/node_modules/\"\n  // ],\n\n  // Indicates which provider should be used to instrument code for coverage\n  coverageProvider: \"v8\",\n\n  // A list of reporter names that Jest uses when writing coverage reports\n  // coverageReporters: [\n  //   \"json\",\n  //   \"text\",\n  //   \"lcov\",\n  //   \"clover\"\n  // ],\n\n  // An object that configures minimum threshold enforcement for coverage results\n  // coverageThreshold: undefined,\n\n  // A path to a custom dependency extractor\n  // dependencyExtractor: undefined,\n\n  // Make calling deprecated APIs throw helpful error messages\n  // errorOnDeprecated: false,\n\n  // The default configuration for fake timers\n  // fakeTimers: {\n  //   \"enableGlobally\": false\n  // },\n\n  // Force coverage collection from ignored files using an array of glob patterns\n  // forceCoverageMatch: [],\n\n  // A path to a module which exports an async function that is triggered once before all test suites\n  // globalSetup: undefined,\n\n  // A path to a module which exports an async function that is triggered once after all test suites\n  // globalTeardown: undefined,\n\n  // A set of global variables that need to be available in all test environments\n  // globals: {},\n\n  // The maximum amount of workers used to run your tests. Can be specified as % or a number. E.g. maxWorkers: 10% will use 10% of your CPU amount + 1 as the maximum worker number. maxWorkers: 2 will use a maximum of 2 workers.\n  // maxWorkers: \"50%\",\n\n  // An array of directory names to be searched recursively up from the requiring module's location\n  // moduleDirectories: [\n  //   \"node_modules\"\n  // ],\n\n  // An array of file extensions your modules use\n  // moduleFileExtensions: [\n  //   \"js\",\n  //   \"mjs\",\n  //   \"cjs\",\n  //   \"jsx\",\n  //   \"ts\",\n  //   \"tsx\",\n  //   \"json\",\n  //   \"node\"\n  // ],\n\n  // A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module\n  // moduleNameMapper: {},\n\n  // An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader\n  // modulePathIgnorePatterns: [],\n\n  // Activates notifications for test results\n  // notify: false,\n\n  // An enum that specifies notification mode. Requires { notify: true }\n  // notifyMode: \"failure-change\",\n\n  // A preset that is used as a base for Jest's configuration\n  // preset: undefined,\n\n  // Run tests from one or more projects\n  // projects: undefined,\n\n  // Use this configuration option to add custom reporters to Jest\n  // reporters: undefined,\n\n  // Automatically reset mock state before every test\n  // resetMocks: false,\n\n  // Reset the module registry before running each individual test\n  // resetModules: false,\n\n  // A path to a custom resolver\n  // resolver: undefined,\n\n  // Automatically restore mock state and implementation before every test\n  // restoreMocks: false,\n\n  // The root directory that Jest should scan for tests and modules within\n  // rootDir: undefined,\n\n  // A list of paths to directories that Jest should use to search for files in\n  // roots: [\n  //   \"<rootDir>\"\n  // ],\n\n  // Allows you to use a custom runner instead of Jest's default test runner\n  // runner: \"jest-runner\",\n\n  // The paths to modules that run some code to configure or set up the testing environment before each test\n  // setupFiles: [],\n\n  // A list of paths to modules that run some code to configure or set up the testing framework before each test\n  // setupFilesAfterEnv: [],\n\n  // The number of seconds after which a test is considered as slow and reported as such in the results.\n  // slowTestThreshold: 5,\n\n  // A list of paths to snapshot serializer modules Jest should use for snapshot testing\n  // snapshotSerializers: [],\n\n  // The test environment that will be used for testing\n  // testEnvironment: \"jest-environment-node\",\n  testEnvironment: \"jsdom\",\n\n  // Options that will be passed to the testEnvironment\n  // testEnvironmentOptions: {},\n\n  // Adds a location field to test results\n  // testLocationInResults: false,\n\n  // The glob patterns Jest uses to detect test files\n  // testMatch: [\n  //   \"**/__tests__/**/*.[jt]s?(x)\",\n  //   \"**/?(*.)+(spec|test).[tj]s?(x)\"\n  // ],\n\n  // An array of regexp pattern strings that are matched against all test paths, matched tests are skipped\n  // testPathIgnorePatterns: [\n  //   \"/node_modules/\"\n  // ],\n\n  // The regexp pattern or array of patterns that Jest uses to detect test files\n  // testRegex: [],\n\n  // This option allows the use of a custom results processor\n  // testResultsProcessor: undefined,\n\n  // This option allows use of a custom test runner\n  // testRunner: \"jest-circus/runner\",\n\n  // A map from regular expressions to paths to transformers\n  // transform: undefined,\n\n  // An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation\n  // transformIgnorePatterns: [\n  //   \"/node_modules/\",\n  //   \"\\\\.pnp\\\\.[^\\\\/]+$\"\n  // ],\n\n  // An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them\n  // unmockedModulePathPatterns: undefined,\n\n  // Indicates whether each individual test should be reported during the run\n  // verbose: undefined,\n\n  // An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode\n  // watchPathIgnorePatterns: [],\n\n  // Whether to use watchman for file crawling\n  // watchman: true,\n};\n\nexport default createJestConfig(config);\n"
  },
  {
    "path": "nextjs-frontend/next.config.mjs",
    "content": "import ForkTsCheckerWebpackPlugin from 'fork-ts-checker-webpack-plugin';\n\n/** @type {import('next').NextConfig} */\nconst nextConfig = {\n  webpack: (config, { isServer }) => {\n    if (!isServer) {\n      config.plugins.push(\n        new ForkTsCheckerWebpackPlugin({\n          async: true, // Run type checking synchronously to block the build\n          typescript: {\n            configOverwrite: {\n              compilerOptions: {\n                skipLibCheck: true,\n              },\n            },\n          },\n        })\n      );\n    }\n    return config;\n  },\n};\n\nexport default nextConfig;"
  },
  {
    "path": "nextjs-frontend/openapi-ts.config.ts",
    "content": "import { defineConfig } from \"@hey-api/openapi-ts\";\nimport { config } from \"dotenv\";\n\nconfig({ path: \".env.local\" });\n\nconst openapiFile = process.env.OPENAPI_OUTPUT_FILE;\n\nexport default defineConfig({\n  input: openapiFile as string,\n  output: {\n    format: \"prettier\",\n    lint: \"eslint\",\n    path: \"app/openapi-client\",\n  },\n  plugins: [\"@hey-api/client-axios\"],\n});\n"
  },
  {
    "path": "nextjs-frontend/openapi.json",
    "content": "{\n  \"openapi\": \"3.1.0\",\n  \"info\": {\n    \"title\": \"FastAPI\",\n    \"version\": \"0.1.0\"\n  },\n  \"paths\": {\n    \"/auth/jwt/login\": {\n      \"post\": {\n        \"tags\": [\n          \"auth\"\n        ],\n        \"summary\": \"Auth:Jwt.Login\",\n        \"operationId\": \"auth:jwt.login\",\n        \"requestBody\": {\n          \"content\": {\n            \"application/x-www-form-urlencoded\": {\n              \"schema\": {\n                \"$ref\": \"#/components/schemas/login\"\n              }\n            }\n          },\n          \"required\": true\n        },\n        \"responses\": {\n          \"200\": {\n            \"description\": \"Successful Response\",\n            \"content\": {\n              \"application/json\": {\n                \"schema\": {\n                  \"$ref\": \"#/components/schemas/BearerResponse\"\n                },\n                \"example\": {\n                  \"access_token\": \"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiOTIyMWZmYzktNjQwZi00MzcyLTg2ZDMtY2U2NDJjYmE1NjAzIiwiYXVkIjoiZmFzdGFwaS11c2VyczphdXRoIiwiZXhwIjoxNTcxNTA0MTkzfQ.M10bjOe45I5Ncu_uXvOmVV8QxnL-nZfcH96U90JaocI\",\n                  \"token_type\": \"bearer\"\n                }\n              }\n            }\n          },\n          \"400\": {\n            \"description\": \"Bad Request\",\n            \"content\": {\n              \"application/json\": {\n                \"schema\": {\n                  \"$ref\": \"#/components/schemas/ErrorModel\"\n                },\n                \"examples\": {\n                  \"LOGIN_BAD_CREDENTIALS\": {\n                    \"summary\": \"Bad credentials or the user is inactive.\",\n                    \"value\": {\n                      \"detail\": \"LOGIN_BAD_CREDENTIALS\"\n                    }\n                  },\n                  \"LOGIN_USER_NOT_VERIFIED\": {\n                    \"summary\": \"The user is not verified.\",\n                    \"value\": {\n                      \"detail\": \"LOGIN_USER_NOT_VERIFIED\"\n                    }\n                  }\n                }\n              }\n            }\n          },\n          \"422\": {\n            \"description\": \"Validation Error\",\n            \"content\": {\n              \"application/json\": {\n                \"schema\": {\n                  \"$ref\": \"#/components/schemas/HTTPValidationError\"\n                }\n              }\n            }\n          }\n        }\n      }\n    },\n    \"/auth/jwt/logout\": {\n      \"post\": {\n        \"tags\": [\n          \"auth\"\n        ],\n        \"summary\": \"Auth:Jwt.Logout\",\n        \"operationId\": \"auth:jwt.logout\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"Successful Response\",\n            \"content\": {\n              \"application/json\": {\n                \"schema\": {}\n              }\n            }\n          },\n          \"401\": {\n            \"description\": \"Missing token or inactive user.\"\n          }\n        },\n        \"security\": [\n          {\n            \"OAuth2PasswordBearer\": []\n          }\n        ]\n      }\n    },\n    \"/auth/register\": {\n      \"post\": {\n        \"tags\": [\n          \"auth\"\n        ],\n        \"summary\": \"Register:Register\",\n        \"operationId\": \"register:register\",\n        \"requestBody\": {\n          \"content\": {\n            \"application/json\": {\n              \"schema\": {\n                \"$ref\": \"#/components/schemas/UserCreate\"\n              }\n            }\n          },\n          \"required\": true\n        },\n        \"responses\": {\n          \"201\": {\n            \"description\": \"Successful Response\",\n            \"content\": {\n              \"application/json\": {\n                \"schema\": {\n                  \"$ref\": \"#/components/schemas/UserRead\"\n                }\n              }\n            }\n          },\n          \"400\": {\n            \"description\": \"Bad Request\",\n            \"content\": {\n              \"application/json\": {\n                \"schema\": {\n                  \"$ref\": \"#/components/schemas/ErrorModel\"\n                },\n                \"examples\": {\n                  \"REGISTER_USER_ALREADY_EXISTS\": {\n                    \"summary\": \"A user with this email already exists.\",\n                    \"value\": {\n                      \"detail\": \"REGISTER_USER_ALREADY_EXISTS\"\n                    }\n                  },\n                  \"REGISTER_INVALID_PASSWORD\": {\n                    \"summary\": \"Password validation failed.\",\n                    \"value\": {\n                      \"detail\": {\n                        \"code\": \"REGISTER_INVALID_PASSWORD\",\n                        \"reason\": \"Password should beat least 3 characters\"\n                      }\n                    }\n                  }\n                }\n              }\n            }\n          },\n          \"422\": {\n            \"description\": \"Validation Error\",\n            \"content\": {\n              \"application/json\": {\n                \"schema\": {\n                  \"$ref\": \"#/components/schemas/HTTPValidationError\"\n                }\n              }\n            }\n          }\n        }\n      }\n    },\n    \"/auth/forgot-password\": {\n      \"post\": {\n        \"tags\": [\n          \"auth\"\n        ],\n        \"summary\": \"Reset:Forgot Password\",\n        \"operationId\": \"reset:forgot_password\",\n        \"requestBody\": {\n          \"content\": {\n            \"application/json\": {\n              \"schema\": {\n                \"$ref\": \"#/components/schemas/Body_auth-reset_forgot_password\"\n              }\n            }\n          },\n          \"required\": true\n        },\n        \"responses\": {\n          \"202\": {\n            \"description\": \"Successful Response\",\n            \"content\": {\n              \"application/json\": {\n                \"schema\": {}\n              }\n            }\n          },\n          \"422\": {\n            \"description\": \"Validation Error\",\n            \"content\": {\n              \"application/json\": {\n                \"schema\": {\n                  \"$ref\": \"#/components/schemas/HTTPValidationError\"\n                }\n              }\n            }\n          }\n        }\n      }\n    },\n    \"/auth/reset-password\": {\n      \"post\": {\n        \"tags\": [\n          \"auth\"\n        ],\n        \"summary\": \"Reset:Reset Password\",\n        \"operationId\": \"reset:reset_password\",\n        \"requestBody\": {\n          \"content\": {\n            \"application/json\": {\n              \"schema\": {\n                \"$ref\": \"#/components/schemas/Body_auth-reset_reset_password\"\n              }\n            }\n          },\n          \"required\": true\n        },\n        \"responses\": {\n          \"200\": {\n            \"description\": \"Successful Response\",\n            \"content\": {\n              \"application/json\": {\n                \"schema\": {}\n              }\n            }\n          },\n          \"400\": {\n            \"description\": \"Bad Request\",\n            \"content\": {\n              \"application/json\": {\n                \"schema\": {\n                  \"$ref\": \"#/components/schemas/ErrorModel\"\n                },\n                \"examples\": {\n                  \"RESET_PASSWORD_BAD_TOKEN\": {\n                    \"summary\": \"Bad or expired token.\",\n                    \"value\": {\n                      \"detail\": \"RESET_PASSWORD_BAD_TOKEN\"\n                    }\n                  },\n                  \"RESET_PASSWORD_INVALID_PASSWORD\": {\n                    \"summary\": \"Password validation failed.\",\n                    \"value\": {\n                      \"detail\": {\n                        \"code\": \"RESET_PASSWORD_INVALID_PASSWORD\",\n                        \"reason\": \"Password should be at least 3 characters\"\n                      }\n                    }\n                  }\n                }\n              }\n            }\n          },\n          \"422\": {\n            \"description\": \"Validation Error\",\n            \"content\": {\n              \"application/json\": {\n                \"schema\": {\n                  \"$ref\": \"#/components/schemas/HTTPValidationError\"\n                }\n              }\n            }\n          }\n        }\n      }\n    },\n    \"/auth/request-verify-token\": {\n      \"post\": {\n        \"tags\": [\n          \"auth\"\n        ],\n        \"summary\": \"Verify:Request-Token\",\n        \"operationId\": \"verify:request-token\",\n        \"requestBody\": {\n          \"content\": {\n            \"application/json\": {\n              \"schema\": {\n                \"$ref\": \"#/components/schemas/Body_auth-verify_request-token\"\n              }\n            }\n          },\n          \"required\": true\n        },\n        \"responses\": {\n          \"202\": {\n            \"description\": \"Successful Response\",\n            \"content\": {\n              \"application/json\": {\n                \"schema\": {}\n              }\n            }\n          },\n          \"422\": {\n            \"description\": \"Validation Error\",\n            \"content\": {\n              \"application/json\": {\n                \"schema\": {\n                  \"$ref\": \"#/components/schemas/HTTPValidationError\"\n                }\n              }\n            }\n          }\n        }\n      }\n    },\n    \"/auth/verify\": {\n      \"post\": {\n        \"tags\": [\n          \"auth\"\n        ],\n        \"summary\": \"Verify:Verify\",\n        \"operationId\": \"verify:verify\",\n        \"requestBody\": {\n          \"content\": {\n            \"application/json\": {\n              \"schema\": {\n                \"$ref\": \"#/components/schemas/Body_auth-verify_verify\"\n              }\n            }\n          },\n          \"required\": true\n        },\n        \"responses\": {\n          \"200\": {\n            \"description\": \"Successful Response\",\n            \"content\": {\n              \"application/json\": {\n                \"schema\": {\n                  \"$ref\": \"#/components/schemas/UserRead\"\n                }\n              }\n            }\n          },\n          \"400\": {\n            \"description\": \"Bad Request\",\n            \"content\": {\n              \"application/json\": {\n                \"schema\": {\n                  \"$ref\": \"#/components/schemas/ErrorModel\"\n                },\n                \"examples\": {\n                  \"VERIFY_USER_BAD_TOKEN\": {\n                    \"summary\": \"Bad token, not existing user ornot the e-mail currently set for the user.\",\n                    \"value\": {\n                      \"detail\": \"VERIFY_USER_BAD_TOKEN\"\n                    }\n                  },\n                  \"VERIFY_USER_ALREADY_VERIFIED\": {\n                    \"summary\": \"The user is already verified.\",\n                    \"value\": {\n                      \"detail\": \"VERIFY_USER_ALREADY_VERIFIED\"\n                    }\n                  }\n                }\n              }\n            }\n          },\n          \"422\": {\n            \"description\": \"Validation Error\",\n            \"content\": {\n              \"application/json\": {\n                \"schema\": {\n                  \"$ref\": \"#/components/schemas/HTTPValidationError\"\n                }\n              }\n            }\n          }\n        }\n      }\n    },\n    \"/users/me\": {\n      \"get\": {\n        \"tags\": [\n          \"users\"\n        ],\n        \"summary\": \"Users:Current User\",\n        \"operationId\": \"users:current_user\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"Successful Response\",\n            \"content\": {\n              \"application/json\": {\n                \"schema\": {\n                  \"$ref\": \"#/components/schemas/UserRead\"\n                }\n              }\n            }\n          },\n          \"401\": {\n            \"description\": \"Missing token or inactive user.\"\n          }\n        },\n        \"security\": [\n          {\n            \"OAuth2PasswordBearer\": []\n          }\n        ]\n      },\n      \"patch\": {\n        \"tags\": [\n          \"users\"\n        ],\n        \"summary\": \"Users:Patch Current User\",\n        \"operationId\": \"users:patch_current_user\",\n        \"requestBody\": {\n          \"content\": {\n            \"application/json\": {\n              \"schema\": {\n                \"$ref\": \"#/components/schemas/UserUpdate\"\n              }\n            }\n          },\n          \"required\": true\n        },\n        \"responses\": {\n          \"200\": {\n            \"description\": \"Successful Response\",\n            \"content\": {\n              \"application/json\": {\n                \"schema\": {\n                  \"$ref\": \"#/components/schemas/UserRead\"\n                }\n              }\n            }\n          },\n          \"401\": {\n            \"description\": \"Missing token or inactive user.\"\n          },\n          \"400\": {\n            \"description\": \"Bad Request\",\n            \"content\": {\n              \"application/json\": {\n                \"schema\": {\n                  \"$ref\": \"#/components/schemas/ErrorModel\"\n                },\n                \"examples\": {\n                  \"UPDATE_USER_EMAIL_ALREADY_EXISTS\": {\n                    \"summary\": \"A user with this email already exists.\",\n                    \"value\": {\n                      \"detail\": \"UPDATE_USER_EMAIL_ALREADY_EXISTS\"\n                    }\n                  },\n                  \"UPDATE_USER_INVALID_PASSWORD\": {\n                    \"summary\": \"Password validation failed.\",\n                    \"value\": {\n                      \"detail\": {\n                        \"code\": \"UPDATE_USER_INVALID_PASSWORD\",\n                        \"reason\": \"Password should beat least 3 characters\"\n                      }\n                    }\n                  }\n                }\n              }\n            }\n          },\n          \"422\": {\n            \"description\": \"Validation Error\",\n            \"content\": {\n              \"application/json\": {\n                \"schema\": {\n                  \"$ref\": \"#/components/schemas/HTTPValidationError\"\n                }\n              }\n            }\n          }\n        },\n        \"security\": [\n          {\n            \"OAuth2PasswordBearer\": []\n          }\n        ]\n      }\n    },\n    \"/users/{id}\": {\n      \"get\": {\n        \"tags\": [\n          \"users\"\n        ],\n        \"summary\": \"Users:User\",\n        \"operationId\": \"users:user\",\n        \"security\": [\n          {\n            \"OAuth2PasswordBearer\": []\n          }\n        ],\n        \"parameters\": [\n          {\n            \"name\": \"id\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"schema\": {\n              \"type\": \"string\",\n              \"title\": \"Id\"\n            }\n          }\n        ],\n        \"responses\": {\n          \"200\": {\n            \"description\": \"Successful Response\",\n            \"content\": {\n              \"application/json\": {\n                \"schema\": {\n                  \"$ref\": \"#/components/schemas/UserRead\"\n                }\n              }\n            }\n          },\n          \"401\": {\n            \"description\": \"Missing token or inactive user.\"\n          },\n          \"403\": {\n            \"description\": \"Not a superuser.\"\n          },\n          \"404\": {\n            \"description\": \"The user does not exist.\"\n          },\n          \"422\": {\n            \"description\": \"Validation Error\",\n            \"content\": {\n              \"application/json\": {\n                \"schema\": {\n                  \"$ref\": \"#/components/schemas/HTTPValidationError\"\n                }\n              }\n            }\n          }\n        }\n      },\n      \"patch\": {\n        \"tags\": [\n          \"users\"\n        ],\n        \"summary\": \"Users:Patch User\",\n        \"operationId\": \"users:patch_user\",\n        \"security\": [\n          {\n            \"OAuth2PasswordBearer\": []\n          }\n        ],\n        \"parameters\": [\n          {\n            \"name\": \"id\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"schema\": {\n              \"type\": \"string\",\n              \"title\": \"Id\"\n            }\n          }\n        ],\n        \"requestBody\": {\n          \"required\": true,\n          \"content\": {\n            \"application/json\": {\n              \"schema\": {\n                \"$ref\": \"#/components/schemas/UserUpdate\"\n              }\n            }\n          }\n        },\n        \"responses\": {\n          \"200\": {\n            \"description\": \"Successful Response\",\n            \"content\": {\n              \"application/json\": {\n                \"schema\": {\n                  \"$ref\": \"#/components/schemas/UserRead\"\n                }\n              }\n            }\n          },\n          \"401\": {\n            \"description\": \"Missing token or inactive user.\"\n          },\n          \"403\": {\n            \"description\": \"Not a superuser.\"\n          },\n          \"404\": {\n            \"description\": \"The user does not exist.\"\n          },\n          \"400\": {\n            \"content\": {\n              \"application/json\": {\n                \"examples\": {\n                  \"UPDATE_USER_EMAIL_ALREADY_EXISTS\": {\n                    \"summary\": \"A user with this email already exists.\",\n                    \"value\": {\n                      \"detail\": \"UPDATE_USER_EMAIL_ALREADY_EXISTS\"\n                    }\n                  },\n                  \"UPDATE_USER_INVALID_PASSWORD\": {\n                    \"summary\": \"Password validation failed.\",\n                    \"value\": {\n                      \"detail\": {\n                        \"code\": \"UPDATE_USER_INVALID_PASSWORD\",\n                        \"reason\": \"Password should beat least 3 characters\"\n                      }\n                    }\n                  }\n                },\n                \"schema\": {\n                  \"$ref\": \"#/components/schemas/ErrorModel\"\n                }\n              }\n            },\n            \"description\": \"Bad Request\"\n          },\n          \"422\": {\n            \"description\": \"Validation Error\",\n            \"content\": {\n              \"application/json\": {\n                \"schema\": {\n                  \"$ref\": \"#/components/schemas/HTTPValidationError\"\n                }\n              }\n            }\n          }\n        }\n      },\n      \"delete\": {\n        \"tags\": [\n          \"users\"\n        ],\n        \"summary\": \"Users:Delete User\",\n        \"operationId\": \"users:delete_user\",\n        \"security\": [\n          {\n            \"OAuth2PasswordBearer\": []\n          }\n        ],\n        \"parameters\": [\n          {\n            \"name\": \"id\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"schema\": {\n              \"type\": \"string\",\n              \"title\": \"Id\"\n            }\n          }\n        ],\n        \"responses\": {\n          \"204\": {\n            \"description\": \"Successful Response\"\n          },\n          \"401\": {\n            \"description\": \"Missing token or inactive user.\"\n          },\n          \"403\": {\n            \"description\": \"Not a superuser.\"\n          },\n          \"404\": {\n            \"description\": \"The user does not exist.\"\n          },\n          \"422\": {\n            \"description\": \"Validation Error\",\n            \"content\": {\n              \"application/json\": {\n                \"schema\": {\n                  \"$ref\": \"#/components/schemas/HTTPValidationError\"\n                }\n              }\n            }\n          }\n        }\n      }\n    },\n    \"/items/\": {\n      \"get\": {\n        \"tags\": [\n          \"item\"\n        ],\n        \"summary\": \"Read Item\",\n        \"operationId\": \"read_item\",\n        \"security\": [\n          {\n            \"OAuth2PasswordBearer\": []\n          }\n        ],\n        \"parameters\": [\n          {\n            \"name\": \"page\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"schema\": {\n              \"type\": \"integer\",\n              \"minimum\": 1,\n              \"description\": \"Page number\",\n              \"default\": 1,\n              \"title\": \"Page\"\n            },\n            \"description\": \"Page number\"\n          },\n          {\n            \"name\": \"size\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"schema\": {\n              \"type\": \"integer\",\n              \"maximum\": 100,\n              \"minimum\": 1,\n              \"description\": \"Page size\",\n              \"default\": 50,\n              \"title\": \"Size\"\n            },\n            \"description\": \"Page size\"\n          }\n        ],\n        \"responses\": {\n          \"200\": {\n            \"description\": \"Successful Response\",\n            \"content\": {\n              \"application/json\": {\n                \"schema\": {\n                  \"$ref\": \"#/components/schemas/Page_ItemRead_\"\n                }\n              }\n            }\n          },\n          \"422\": {\n            \"description\": \"Validation Error\",\n            \"content\": {\n              \"application/json\": {\n                \"schema\": {\n                  \"$ref\": \"#/components/schemas/HTTPValidationError\"\n                }\n              }\n            }\n          }\n        }\n      },\n      \"post\": {\n        \"tags\": [\n          \"item\"\n        ],\n        \"summary\": \"Create Item\",\n        \"operationId\": \"create_item\",\n        \"security\": [\n          {\n            \"OAuth2PasswordBearer\": []\n          }\n        ],\n        \"requestBody\": {\n          \"required\": true,\n          \"content\": {\n            \"application/json\": {\n              \"schema\": {\n                \"$ref\": \"#/components/schemas/ItemCreate\"\n              }\n            }\n          }\n        },\n        \"responses\": {\n          \"200\": {\n            \"description\": \"Successful Response\",\n            \"content\": {\n              \"application/json\": {\n                \"schema\": {\n                  \"$ref\": \"#/components/schemas/ItemRead\"\n                }\n              }\n            }\n          },\n          \"422\": {\n            \"description\": \"Validation Error\",\n            \"content\": {\n              \"application/json\": {\n                \"schema\": {\n                  \"$ref\": \"#/components/schemas/HTTPValidationError\"\n                }\n              }\n            }\n          }\n        }\n      }\n    },\n    \"/items/{item_id}\": {\n      \"delete\": {\n        \"tags\": [\n          \"item\"\n        ],\n        \"summary\": \"Delete Item\",\n        \"operationId\": \"delete_item\",\n        \"security\": [\n          {\n            \"OAuth2PasswordBearer\": []\n          }\n        ],\n        \"parameters\": [\n          {\n            \"name\": \"item_id\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"schema\": {\n              \"type\": \"string\",\n              \"format\": \"uuid\",\n              \"title\": \"Item Id\"\n            }\n          }\n        ],\n        \"responses\": {\n          \"200\": {\n            \"description\": \"Successful Response\",\n            \"content\": {\n              \"application/json\": {\n                \"schema\": {}\n              }\n            }\n          },\n          \"422\": {\n            \"description\": \"Validation Error\",\n            \"content\": {\n              \"application/json\": {\n                \"schema\": {\n                  \"$ref\": \"#/components/schemas/HTTPValidationError\"\n                }\n              }\n            }\n          }\n        }\n      }\n    }\n  },\n  \"components\": {\n    \"schemas\": {\n      \"BearerResponse\": {\n        \"properties\": {\n          \"access_token\": {\n            \"type\": \"string\",\n            \"title\": \"Access Token\"\n          },\n          \"token_type\": {\n            \"type\": \"string\",\n            \"title\": \"Token Type\"\n          }\n        },\n        \"type\": \"object\",\n        \"required\": [\n          \"access_token\",\n          \"token_type\"\n        ],\n        \"title\": \"BearerResponse\"\n      },\n      \"Body_auth-reset_forgot_password\": {\n        \"properties\": {\n          \"email\": {\n            \"type\": \"string\",\n            \"format\": \"email\",\n            \"title\": \"Email\"\n          }\n        },\n        \"type\": \"object\",\n        \"required\": [\n          \"email\"\n        ],\n        \"title\": \"Body_auth-reset:forgot_password\"\n      },\n      \"Body_auth-reset_reset_password\": {\n        \"properties\": {\n          \"token\": {\n            \"type\": \"string\",\n            \"title\": \"Token\"\n          },\n          \"password\": {\n            \"type\": \"string\",\n            \"title\": \"Password\"\n          }\n        },\n        \"type\": \"object\",\n        \"required\": [\n          \"token\",\n          \"password\"\n        ],\n        \"title\": \"Body_auth-reset:reset_password\"\n      },\n      \"Body_auth-verify_request-token\": {\n        \"properties\": {\n          \"email\": {\n            \"type\": \"string\",\n            \"format\": \"email\",\n            \"title\": \"Email\"\n          }\n        },\n        \"type\": \"object\",\n        \"required\": [\n          \"email\"\n        ],\n        \"title\": \"Body_auth-verify:request-token\"\n      },\n      \"Body_auth-verify_verify\": {\n        \"properties\": {\n          \"token\": {\n            \"type\": \"string\",\n            \"title\": \"Token\"\n          }\n        },\n        \"type\": \"object\",\n        \"required\": [\n          \"token\"\n        ],\n        \"title\": \"Body_auth-verify:verify\"\n      },\n      \"ErrorModel\": {\n        \"properties\": {\n          \"detail\": {\n            \"anyOf\": [\n              {\n                \"type\": \"string\"\n              },\n              {\n                \"additionalProperties\": {\n                  \"type\": \"string\"\n                },\n                \"type\": \"object\"\n              }\n            ],\n            \"title\": \"Detail\"\n          }\n        },\n        \"type\": \"object\",\n        \"required\": [\n          \"detail\"\n        ],\n        \"title\": \"ErrorModel\"\n      },\n      \"HTTPValidationError\": {\n        \"properties\": {\n          \"detail\": {\n            \"items\": {\n              \"$ref\": \"#/components/schemas/ValidationError\"\n            },\n            \"type\": \"array\",\n            \"title\": \"Detail\"\n          }\n        },\n        \"type\": \"object\",\n        \"title\": \"HTTPValidationError\"\n      },\n      \"ItemCreate\": {\n        \"properties\": {\n          \"name\": {\n            \"type\": \"string\",\n            \"title\": \"Name\"\n          },\n          \"description\": {\n            \"anyOf\": [\n              {\n                \"type\": \"string\"\n              },\n              {\n                \"type\": \"null\"\n              }\n            ],\n            \"title\": \"Description\"\n          },\n          \"quantity\": {\n            \"anyOf\": [\n              {\n                \"type\": \"integer\"\n              },\n              {\n                \"type\": \"null\"\n              }\n            ],\n            \"title\": \"Quantity\"\n          }\n        },\n        \"type\": \"object\",\n        \"required\": [\n          \"name\"\n        ],\n        \"title\": \"ItemCreate\"\n      },\n      \"ItemRead\": {\n        \"properties\": {\n          \"name\": {\n            \"type\": \"string\",\n            \"title\": \"Name\"\n          },\n          \"description\": {\n            \"anyOf\": [\n              {\n                \"type\": \"string\"\n              },\n              {\n                \"type\": \"null\"\n              }\n            ],\n            \"title\": \"Description\"\n          },\n          \"quantity\": {\n            \"anyOf\": [\n              {\n                \"type\": \"integer\"\n              },\n              {\n                \"type\": \"null\"\n              }\n            ],\n            \"title\": \"Quantity\"\n          },\n          \"id\": {\n            \"type\": \"string\",\n            \"format\": \"uuid\",\n            \"title\": \"Id\"\n          },\n          \"user_id\": {\n            \"type\": \"string\",\n            \"format\": \"uuid\",\n            \"title\": \"User Id\"\n          }\n        },\n        \"type\": \"object\",\n        \"required\": [\n          \"name\",\n          \"id\",\n          \"user_id\"\n        ],\n        \"title\": \"ItemRead\"\n      },\n      \"Page_ItemRead_\": {\n        \"properties\": {\n          \"items\": {\n            \"items\": {\n              \"$ref\": \"#/components/schemas/ItemRead\"\n            },\n            \"type\": \"array\",\n            \"title\": \"Items\"\n          },\n          \"total\": {\n            \"anyOf\": [\n              {\n                \"type\": \"integer\",\n                \"minimum\": 0.0\n              },\n              {\n                \"type\": \"null\"\n              }\n            ],\n            \"title\": \"Total\"\n          },\n          \"page\": {\n            \"anyOf\": [\n              {\n                \"type\": \"integer\",\n                \"minimum\": 1.0\n              },\n              {\n                \"type\": \"null\"\n              }\n            ],\n            \"title\": \"Page\"\n          },\n          \"size\": {\n            \"anyOf\": [\n              {\n                \"type\": \"integer\",\n                \"minimum\": 1.0\n              },\n              {\n                \"type\": \"null\"\n              }\n            ],\n            \"title\": \"Size\"\n          },\n          \"pages\": {\n            \"anyOf\": [\n              {\n                \"type\": \"integer\",\n                \"minimum\": 0.0\n              },\n              {\n                \"type\": \"null\"\n              }\n            ],\n            \"title\": \"Pages\"\n          }\n        },\n        \"type\": \"object\",\n        \"required\": [\n          \"items\",\n          \"page\",\n          \"size\"\n        ],\n        \"title\": \"Page[ItemRead]\"\n      },\n      \"UserCreate\": {\n        \"properties\": {\n          \"email\": {\n            \"type\": \"string\",\n            \"format\": \"email\",\n            \"title\": \"Email\"\n          },\n          \"password\": {\n            \"type\": \"string\",\n            \"title\": \"Password\"\n          },\n          \"is_active\": {\n            \"anyOf\": [\n              {\n                \"type\": \"boolean\"\n              },\n              {\n                \"type\": \"null\"\n              }\n            ],\n            \"title\": \"Is Active\",\n            \"default\": true\n          },\n          \"is_superuser\": {\n            \"anyOf\": [\n              {\n                \"type\": \"boolean\"\n              },\n              {\n                \"type\": \"null\"\n              }\n            ],\n            \"title\": \"Is Superuser\",\n            \"default\": false\n          },\n          \"is_verified\": {\n            \"anyOf\": [\n              {\n                \"type\": \"boolean\"\n              },\n              {\n                \"type\": \"null\"\n              }\n            ],\n            \"title\": \"Is Verified\",\n            \"default\": false\n          }\n        },\n        \"type\": \"object\",\n        \"required\": [\n          \"email\",\n          \"password\"\n        ],\n        \"title\": \"UserCreate\"\n      },\n      \"UserRead\": {\n        \"properties\": {\n          \"id\": {\n            \"type\": \"string\",\n            \"format\": \"uuid\",\n            \"title\": \"Id\"\n          },\n          \"email\": {\n            \"type\": \"string\",\n            \"format\": \"email\",\n            \"title\": \"Email\"\n          },\n          \"is_active\": {\n            \"type\": \"boolean\",\n            \"title\": \"Is Active\",\n            \"default\": true\n          },\n          \"is_superuser\": {\n            \"type\": \"boolean\",\n            \"title\": \"Is Superuser\",\n            \"default\": false\n          },\n          \"is_verified\": {\n            \"type\": \"boolean\",\n            \"title\": \"Is Verified\",\n            \"default\": false\n          }\n        },\n        \"type\": \"object\",\n        \"required\": [\n          \"id\",\n          \"email\"\n        ],\n        \"title\": \"UserRead\"\n      },\n      \"UserUpdate\": {\n        \"properties\": {\n          \"password\": {\n            \"anyOf\": [\n              {\n                \"type\": \"string\"\n              },\n              {\n                \"type\": \"null\"\n              }\n            ],\n            \"title\": \"Password\"\n          },\n          \"email\": {\n            \"anyOf\": [\n              {\n                \"type\": \"string\",\n                \"format\": \"email\"\n              },\n              {\n                \"type\": \"null\"\n              }\n            ],\n            \"title\": \"Email\"\n          },\n          \"is_active\": {\n            \"anyOf\": [\n              {\n                \"type\": \"boolean\"\n              },\n              {\n                \"type\": \"null\"\n              }\n            ],\n            \"title\": \"Is Active\"\n          },\n          \"is_superuser\": {\n            \"anyOf\": [\n              {\n                \"type\": \"boolean\"\n              },\n              {\n                \"type\": \"null\"\n              }\n            ],\n            \"title\": \"Is Superuser\"\n          },\n          \"is_verified\": {\n            \"anyOf\": [\n              {\n                \"type\": \"boolean\"\n              },\n              {\n                \"type\": \"null\"\n              }\n            ],\n            \"title\": \"Is Verified\"\n          }\n        },\n        \"type\": \"object\",\n        \"title\": \"UserUpdate\"\n      },\n      \"ValidationError\": {\n        \"properties\": {\n          \"loc\": {\n            \"items\": {\n              \"anyOf\": [\n                {\n                  \"type\": \"string\"\n                },\n                {\n                  \"type\": \"integer\"\n                }\n              ]\n            },\n            \"type\": \"array\",\n            \"title\": \"Location\"\n          },\n          \"msg\": {\n            \"type\": \"string\",\n            \"title\": \"Message\"\n          },\n          \"type\": {\n            \"type\": \"string\",\n            \"title\": \"Error Type\"\n          }\n        },\n        \"type\": \"object\",\n        \"required\": [\n          \"loc\",\n          \"msg\",\n          \"type\"\n        ],\n        \"title\": \"ValidationError\"\n      },\n      \"login\": {\n        \"properties\": {\n          \"grant_type\": {\n            \"anyOf\": [\n              {\n                \"type\": \"string\",\n                \"pattern\": \"password\"\n              },\n              {\n                \"type\": \"null\"\n              }\n            ],\n            \"title\": \"Grant Type\"\n          },\n          \"username\": {\n            \"type\": \"string\",\n            \"title\": \"Username\"\n          },\n          \"password\": {\n            \"type\": \"string\",\n            \"title\": \"Password\"\n          },\n          \"scope\": {\n            \"type\": \"string\",\n            \"title\": \"Scope\",\n            \"default\": \"\"\n          },\n          \"client_id\": {\n            \"anyOf\": [\n              {\n                \"type\": \"string\"\n              },\n              {\n                \"type\": \"null\"\n              }\n            ],\n            \"title\": \"Client Id\"\n          },\n          \"client_secret\": {\n            \"anyOf\": [\n              {\n                \"type\": \"string\"\n              },\n              {\n                \"type\": \"null\"\n              }\n            ],\n            \"title\": \"Client Secret\"\n          }\n        },\n        \"type\": \"object\",\n        \"required\": [\n          \"username\",\n          \"password\"\n        ],\n        \"title\": \"Body_auth-auth:jwt.login\"\n      }\n    },\n    \"securitySchemes\": {\n      \"OAuth2PasswordBearer\": {\n        \"type\": \"oauth2\",\n        \"flows\": {\n          \"password\": {\n            \"scopes\": {},\n            \"tokenUrl\": \"auth/jwt/login\"\n          }\n        }\n      }\n    }\n  }\n}"
  },
  {
    "path": "nextjs-frontend/package.json",
    "content": "{\n  \"name\": \"nextjs-frontend\",\n  \"version\": \"0.0.8\",\n  \"private\": true,\n  \"scripts\": {\n    \"dev\": \"next dev --webpack\",\n    \"build\": \"next build --webpack\",\n    \"start\": \"next start\",\n    \"lint\": \"eslint .\",\n    \"lint:fix\": \"eslint . --fix\",\n    \"generate-client\": \"openapi-ts\",\n    \"test\": \"jest\",\n    \"coverage\": \"jest --coverage\",\n    \"prettier\": \"prettier --write '**/*.{js,jsx,ts,tsx,json,css,html}'\",\n    \"tsc\": \"tsc\"\n  },\n  \"dependencies\": {\n    \"@heroicons/react\": \"^2.2.0\",\n    \"@hey-api/client-axios\": \"^0.9.1\",\n    \"@hey-api/client-fetch\": \"^0.13.1\",\n    \"axios\": \"^1.7.9\",\n    \"@hookform/resolvers\": \"^3.9.1\",\n    \"@radix-ui/react-avatar\": \"^1.1.1\",\n    \"@radix-ui/react-dropdown-menu\": \"^2.1.2\",\n    \"@radix-ui/react-icons\": \"^1.3.0\",\n    \"@radix-ui/react-label\": \"^2.1.0\",\n    \"@radix-ui/react-select\": \"^2.2.5\",\n    \"@radix-ui/react-slot\": \"^1.1.0\",\n    \"@radix-ui/react-tabs\": \"^1.1.1\",\n    \"class-variance-authority\": \"^0.7.0\",\n    \"clsx\": \"^2.1.1\",\n    \"dotenv\": \"^16.4.5\",\n    \"lucide-react\": \"^0.452.0\",\n    \"next\": \"16.0.8\",\n    \"react\": \"19.2.1\",\n    \"react-dom\": \"19.2.1\",\n    \"react-hook-form\": \"^7.54.0\",\n    \"react-icons\": \"^5.4.0\",\n    \"tailwind-merge\": \"^2.5.4\",\n    \"tailwindcss-animate\": \"^1.0.7\",\n    \"zod\": \"^3.23.8\"\n  },\n  \"devDependencies\": {\n    \"@eslint/eslintrc\": \"^3.3.1\",\n    \"@eslint/js\": \"^9.18.0\",\n    \"@next/eslint-plugin-next\": \"16.0.8\",\n    \"@hey-api/openapi-ts\": \"^0.83.1\",\n    \"@testing-library/jest-dom\": \"^6.6.3\",\n    \"@testing-library/react\": \"^16.0.1\",\n    \"@types/jest\": \"^29.5.14\",\n    \"@types/node\": \"^20\",\n    \"@types/react\": \"19.2.7\",\n    \"@types/react-dom\": \"19.2.3\",\n    \"@typescript-eslint/eslint-plugin\": \"^8.20.0\",\n    \"autoprefixer\": \"^10.4.20\",\n    \"chokidar\": \"^4.0.1\",\n    \"chokidar-cli\": \"^3.0.0\",\n    \"eslint\": \"^9.18.0\",\n    \"eslint-config-next\": \"16.0.8\",\n    \"eslint-config-prettier\": \"^10.0.1\",\n    \"fork-ts-checker-webpack-plugin\": \"^9.0.2\",\n    \"globals\": \"^15.14.0\",\n    \"jest\": \"^29.7.0\",\n    \"jest-environment-jsdom\": \"^29.7.0\",\n    \"postcss\": \"^8.4.47\",\n    \"prettier\": \"^3.3.3\",\n    \"tailwindcss\": \"^3.4.13\",\n    \"ts-jest\": \"^29.2.5\",\n    \"ts-node\": \"^10.9.2\",\n    \"typescript\": \"^5\",\n    \"typescript-eslint\": \"^8.20.0\"\n  },\n  \"overrides\": {\n    \"@types/react\": \"19.2.7\",\n    \"@types/react-dom\": \"19.2.3\"\n  },\n  \"packageManager\": \"pnpm@10.7.1+sha512.2d92c86b7928dc8284f53494fb4201f983da65f0fb4f0d40baafa5cf628fa31dae3e5968f12466f17df7e97310e30f343a648baea1b9b350685dafafffdf5808\"\n}\n"
  },
  {
    "path": "nextjs-frontend/pnpm-workspace.yaml",
    "content": "packages:\n  - \".\"\nonlyBuiltDependencies:\n  - sharp\n"
  },
  {
    "path": "nextjs-frontend/postcss.config.js",
    "content": "module.exports = {\n  plugins: {\n    tailwindcss: {},\n    autoprefixer: {},\n  },\n};\n"
  },
  {
    "path": "nextjs-frontend/proxy.ts",
    "content": "import { NextResponse } from \"next/server\";\nimport type { NextRequest } from \"next/server\";\nimport { usersCurrentUser } from \"@/app/clientService\";\n\nexport async function proxy(request: NextRequest) {\n  const token = request.cookies.get(\"accessToken\");\n\n  if (!token) {\n    return NextResponse.redirect(new URL(\"/login\", request.url));\n  }\n\n  const options = {\n    headers: {\n      Authorization: `Bearer ${token.value}`,\n    },\n  };\n\n  const { error } = await usersCurrentUser(options);\n\n  if (error) {\n    return NextResponse.redirect(new URL(\"/login\", request.url));\n  }\n  return NextResponse.next();\n}\n\nexport const config = {\n  matcher: [\"/dashboard/:path*\"],\n};\n"
  },
  {
    "path": "nextjs-frontend/start.sh",
    "content": "#!/bin/bash\n\npnpm run dev &\n\nnode watcher.js\n\nwait"
  },
  {
    "path": "nextjs-frontend/tailwind.config.js",
    "content": "/** @type {import('tailwindcss').Config} */\n\n/* eslint-disable @typescript-eslint/no-require-imports */\nmodule.exports = {\n  darkMode: [\"class\"],\n  content: [\n    \"./app/**/*.{js,ts,jsx,tsx,mdx}\",\n    \"./pages/**/*.{js,ts,jsx,tsx,mdx}\",\n    \"./components/**/*.{js,ts,jsx,tsx,mdx}\",\n\n    // Or if using `src` directory:\n    \"./src/**/*.{js,ts,jsx,tsx,mdx}\",\n  ],\n  theme: {\n    extend: {\n      borderRadius: {\n        lg: \"var(--radius)\",\n        md: \"calc(var(--radius) - 2px)\",\n        sm: \"calc(var(--radius) - 4px)\",\n      },\n      colors: {\n        background: \"hsl(var(--background))\",\n        foreground: \"hsl(var(--foreground))\",\n        card: {\n          DEFAULT: \"hsl(var(--card))\",\n          foreground: \"hsl(var(--card-foreground))\",\n        },\n        popover: {\n          DEFAULT: \"hsl(var(--popover))\",\n          foreground: \"hsl(var(--popover-foreground))\",\n        },\n        primary: {\n          DEFAULT: \"hsl(var(--primary))\",\n          foreground: \"hsl(var(--primary-foreground))\",\n        },\n        secondary: {\n          DEFAULT: \"hsl(var(--secondary))\",\n          foreground: \"hsl(var(--secondary-foreground))\",\n        },\n        muted: {\n          DEFAULT: \"hsl(var(--muted))\",\n          foreground: \"hsl(var(--muted-foreground))\",\n        },\n        accent: {\n          DEFAULT: \"hsl(var(--accent))\",\n          foreground: \"hsl(var(--accent-foreground))\",\n        },\n        destructive: {\n          DEFAULT: \"hsl(var(--destructive))\",\n          foreground: \"hsl(var(--destructive-foreground))\",\n        },\n        border: \"hsl(var(--border))\",\n        input: \"hsl(var(--input))\",\n        ring: \"hsl(var(--ring))\",\n        chart: {\n          1: \"hsl(var(--chart-1))\",\n          2: \"hsl(var(--chart-2))\",\n          3: \"hsl(var(--chart-3))\",\n          4: \"hsl(var(--chart-4))\",\n          5: \"hsl(var(--chart-5))\",\n        },\n      },\n    },\n  },\n  plugins: [require(\"tailwindcss-animate\")],\n};\n"
  },
  {
    "path": "nextjs-frontend/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"lib\": [\"dom\", \"dom.iterable\", \"esnext\"],\n    \"allowJs\": true,\n    \"skipLibCheck\": true,\n    \"strict\": true,\n    \"noEmit\": true,\n    \"esModuleInterop\": true,\n    \"module\": \"esnext\",\n    \"moduleResolution\": \"bundler\",\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"jsx\": \"react-jsx\",\n    \"incremental\": true,\n    \"plugins\": [\n      {\n        \"name\": \"next\"\n      }\n    ],\n    \"paths\": {\n      \"@/*\": [\"./*\"]\n    },\n    \"target\": \"ES2017\"\n  },\n  \"include\": [\n    \"next-env.d.ts\",\n    \"**/*.ts\",\n    \"**/*.tsx\",\n    \".next/types/**/*.ts\",\n    \".next/dev/types/**/*.ts\"\n  ],\n  \"exclude\": [\"node_modules\"]\n}\n"
  },
  {
    "path": "nextjs-frontend/vercel.json",
    "content": "{\n  \"git\": {\n    \"deploymentEnabled\": {\n      \"main\": false\n    }\n  }\n}\n"
  },
  {
    "path": "nextjs-frontend/watcher.js",
    "content": "/* eslint-disable @typescript-eslint/no-require-imports */\nconst chokidar = require(\"chokidar\");\nconst { exec } = require(\"child_process\");\nconst { config } = require(\"dotenv\");\n\nconfig({ path: \".env.local\" });\n\nconst openapiFile = process.env.OPENAPI_OUTPUT_FILE;\n// Watch the specific file for changes\nchokidar.watch(openapiFile).on(\"change\", (path) => {\n  console.log(`File ${path} has been modified. Running generate-client...`);\n  exec(\"pnpm run generate-client\", (error, stdout, stderr) => {\n    if (error) {\n      console.error(`Error: ${error.message}`);\n      return;\n    }\n    if (stderr) {\n      console.error(`stderr: ${stderr}`);\n      return;\n    }\n    console.log(`stdout: ${stdout}`);\n  });\n});\n"
  },
  {
    "path": "overrides/main.html",
    "content": "{% extends \"base.html\" %}\n\n{% block extrahead %}\n  {% set title = config.site_name %}\n  {% if page and page.meta and page.meta.title %}\n    {% set title = title ~ \" - \" ~ page.meta.title %}\n  {% elif page and page.title and not page.is_homepage %}\n    {% set title = title ~ \" - \" ~ page.title %}\n  {% endif %}\n  <meta property=\"og:type\" content=\"website\" />\n  <meta property=\"og:title\" content=\"{{ title }}\" />\n  <meta property=\"og:description\" content=\"{{ config.site_description }}\" />\n  <meta property=\"og:url\" content=\"{{ page.canonical_url }}\" />\n  <meta property=\"og:image\" content=\"{{ page.canonical_url }}/images/thumbnail.png\" />\n  <meta property=\"og:image:type\" content=\"image/png\" />\n  <meta property=\"og:image:width\" content=\"1200\" />\n  <meta property=\"og:image:height\" content=\"630\" />\n\n  <meta property=\"twitter:card\" content=\"summary_large_image\" />\n  <meta property=\"twitter:title\" content=\"{{ title }}\" />\n  <meta property=\"twitter:description\" content=\"{{ config.site_description }}\" />\n  <meta property=\"twitter:image\" content=\"{{ page.canonical_url }}/images/thumbnail.png\" />\n\n\n{% endblock %}\n"
  },
  {
    "path": "prod-backend-deploy.yml",
    "content": "name: Vercel Production Backend Deployment\nenv:\n  VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }}\n  VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID_BACKEND }}\non:\n  push:\n    branches:\n      - main\njobs:\n  Deploy-Production-Backend:\n    runs-on: ubuntu-latest\n    defaults:\n      run:\n        working-directory: fastapi_backend\n    steps:\n      - uses: actions/checkout@v2\n\n      # Install Vercel CLI globally\n      - name: Install Vercel CLI\n        run: npm install --global vercel@latest\n\n      # Pull Vercel environment information\n      - name: Pull Vercel Environment Information\n        run: vercel pull --yes --environment=production --local-config=vercel.prod.json --token=${{ secrets.VERCEL_TOKEN }}\n\n      # Pull Vercel environment variables\n      - name: Pull Vercel Environment Information\n        run: vercel env pull --yes --environment=production --token=${{ secrets.VERCEL_TOKEN }}\n\n      # Install Alembic and other required dependencies\n      - name: Install Alembic and dependencies\n        run: pip install -r requirements.txt\n\n      # Run alembic migration\n      - name: Run alembic migration\n        run: |\n          set -o allexport\n          source .env.local\n          set +o allexport \n          alembic upgrade head\n\n      # Build project artifacts\n      - name: Build Project Artifacts\n        run: vercel build --prod --local-config=vercel.prod.json --token=${{ secrets.VERCEL_TOKEN }}\n\n      # Deploy project artifacts to Vercel\n      - name: Deploy Project Artifacts to Vercel\n        run: vercel deploy --prebuilt --prod --archive=split-tgz --local-config=vercel.prod.json --token=${{ secrets.VERCEL_TOKEN }}"
  },
  {
    "path": "prod-frontend-deploy.yml",
    "content": "name: Vercel Production Frontend Deployment\nenv:\n  VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }}\n  VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID_FRONTEND }}\non:\n  push:\n    branches:\n      - main\njobs:\n  Deploy-Production-Frontend:\n    runs-on: ubuntu-latest\n    defaults:\n      run:\n        working-directory: nextjs-frontend\n    steps:\n      - uses: actions/checkout@v2\n\n      # Install pnpm\n      - name: Install pnpm\n        run: npm install -g pnpm\n\n      # Install Vercel CLI globally\n      - name: Install Vercel CLI\n        run: npm install --global vercel@latest\n\n      # Pull Vercel environment information\n      - name: Pull Vercel Environment Information\n        run: vercel pull --yes --environment=production --token=${{ secrets.VERCEL_TOKEN }}\n\n      # Build project artifacts\n      - name: Build Project Artifacts\n        run: vercel build --prod --token=${{ secrets.VERCEL_TOKEN }}\n\n      # Deploy project artifacts to Vercel\n      - name: Deploy Project Artifacts to Vercel\n        run: vercel deploy --prebuilt --prod --archive=split-tgz --token=${{ secrets.VERCEL_TOKEN }}"
  }
]