diff --git a/.github/workflows/add-to-project.yml b/.github/workflows/add-to-project.yml deleted file mode 100644 index dccea83f3..000000000 --- a/.github/workflows/add-to-project.yml +++ /dev/null @@ -1,18 +0,0 @@ -name: Add to Project - -on: - pull_request_target: - issues: - types: - - opened - - reopened - -jobs: - add-to-project: - name: Add to project - runs-on: ubuntu-latest - steps: - - uses: actions/add-to-project@v1.0.2 - with: - project-url: https://github.com/orgs/fastapi/projects/2 - github-token: ${{ secrets.PROJECTS_TOKEN }} diff --git a/.github/workflows/cd-production.yml b/.github/workflows/cd-production.yml new file mode 100644 index 000000000..b4b1fd3ba --- /dev/null +++ b/.github/workflows/cd-production.yml @@ -0,0 +1,59 @@ +name: Deploy AI Platform to ECS Production + +on: + push: + tags: + - 'v*' # Deploy only when tags like v1.0.0, v2.1.0, etc., are created + +jobs: + build: + runs-on: ubuntu-latest + + permissions: + packages: write + contents: read + attestations: write + id-token: write + + steps: + - name: Checkout the repo + uses: actions/checkout@v4 + + - name: Check if tag is from main branch + id: check-branch + run: | + if [ "$(git branch -r --contains ${{ github.ref }})" == "origin/main" ]; then + echo "IS_MAIN=true" >> $GITHUB_ENV + else + echo "IS_MAIN=false" >> $GITHUB_ENV + fi + + - name: Configure AWS credentials + if: env.IS_MAIN == 'true' + uses: aws-actions/configure-aws-credentials@v4 + with: + role-to-assume: arn:aws:iam::024209611402:role/github-action-role + aws-region: ap-south-1 + + - name: Login to Amazon ECR + if: env.IS_MAIN == 'true' + id: login-ecr + uses: aws-actions/amazon-ecr-login@v2 + + - name: Build and Push Docker Image + if: env.IS_MAIN == 'true' + env: + REGISTRY: ${{ steps.login-ecr.outputs.registry }} + REPOSITORY: ${{ github.event.repository.name }}-repo + TAG: ${{ github.ref_name }} + run: | + docker build -t $REGISTRY/$REPOSITORY:$TAG ./backend + docker push $REGISTRY/$REPOSITORY:$TAG + + - name: Deploy to ECS + if: env.IS_MAIN == 'true' + run: | + aws ecs update-service \ + --cluster ${{ github.event.repository.name }}-cluster \ + --service ${{ github.event.repository.name }}-service \ + --force-new-deployment diff --git a/.github/workflows/cd-staging.yml b/.github/workflows/cd-staging.yml new file mode 100644 index 000000000..898b464ce --- /dev/null +++ b/.github/workflows/cd-staging.yml @@ -0,0 +1,44 @@ +name: Deploy AI Platform to ECS + +on: + push: + branches: + - main + +jobs: + build: + runs-on: ubuntu-latest + + permissions: + packages: write + contents: read + attestations: write + id-token: write + + + steps: + - name: checkout the repo + uses: actions/checkout@v4 + + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v4 # More information on this action can be found below in the 'AWS Credentials' section + with: + role-to-assume: arn:aws:iam::024209611402:role/github-action-role + aws-region: ap-south-1 + + - name: Login to Amazon ECR + id: login-ecr + uses: aws-actions/amazon-ecr-login@v2 + + + - name: Build and Push Docker Image + env: + REGISTRY: ${{ steps.login-ecr.outputs.registry }} + REPOSITORY: ${{ github.event.repository.name }}-staging-repo + run: | + docker build -t $REGISTRY/$REPOSITORY:latest ./backend + docker push $REGISTRY/$REPOSITORY:latest + + - name: Deploy to ECS + run: | + aws ecs update-service --cluster ${{ github.event.repository.name }}-staging-cluster --service ${{ github.event.repository.name }}-staging-service --force-new-deployment diff --git a/.github/workflows/deploy-production.yml b/.github/workflows/deploy-production.yml deleted file mode 100644 index a64d02a15..000000000 --- a/.github/workflows/deploy-production.yml +++ /dev/null @@ -1,32 +0,0 @@ -name: Deploy to Production - -on: - release: - types: - - published - -jobs: - deploy: - # Do not deploy in the main repository, only in user projects - if: github.repository_owner != 'fastapi' - runs-on: - - self-hosted - - production - env: - ENVIRONMENT: production - DOMAIN: ${{ secrets.DOMAIN_PRODUCTION }} - STACK_NAME: ${{ secrets.STACK_NAME_PRODUCTION }} - SECRET_KEY: ${{ secrets.SECRET_KEY }} - FIRST_SUPERUSER: ${{ secrets.FIRST_SUPERUSER }} - FIRST_SUPERUSER_PASSWORD: ${{ secrets.FIRST_SUPERUSER_PASSWORD }} - SMTP_HOST: ${{ secrets.SMTP_HOST }} - SMTP_USER: ${{ secrets.SMTP_USER }} - SMTP_PASSWORD: ${{ secrets.SMTP_PASSWORD }} - EMAILS_FROM_EMAIL: ${{ secrets.EMAILS_FROM_EMAIL }} - POSTGRES_PASSWORD: ${{ secrets.POSTGRES_PASSWORD }} - SENTRY_DSN: ${{ secrets.SENTRY_DSN }} - steps: - - name: Checkout - uses: actions/checkout@v4 - - run: docker compose -f docker-compose.yml --project-name ${{ secrets.STACK_NAME_PRODUCTION }} build - - run: docker compose -f docker-compose.yml --project-name ${{ secrets.STACK_NAME_PRODUCTION }} up -d diff --git a/.github/workflows/deploy-staging.yml b/.github/workflows/deploy-staging.yml deleted file mode 100644 index 26bd692fd..000000000 --- a/.github/workflows/deploy-staging.yml +++ /dev/null @@ -1,32 +0,0 @@ -name: Deploy to Staging - -on: - push: - branches: - - master - -jobs: - deploy: - # Do not deploy in the main repository, only in user projects - if: github.repository_owner != 'fastapi' - runs-on: - - self-hosted - - staging - env: - ENVIRONMENT: staging - DOMAIN: ${{ secrets.DOMAIN_STAGING }} - STACK_NAME: ${{ secrets.STACK_NAME_STAGING }} - SECRET_KEY: ${{ secrets.SECRET_KEY }} - FIRST_SUPERUSER: ${{ secrets.FIRST_SUPERUSER }} - FIRST_SUPERUSER_PASSWORD: ${{ secrets.FIRST_SUPERUSER_PASSWORD }} - SMTP_HOST: ${{ secrets.SMTP_HOST }} - SMTP_USER: ${{ secrets.SMTP_USER }} - SMTP_PASSWORD: ${{ secrets.SMTP_PASSWORD }} - EMAILS_FROM_EMAIL: ${{ secrets.EMAILS_FROM_EMAIL }} - POSTGRES_PASSWORD: ${{ secrets.POSTGRES_PASSWORD }} - SENTRY_DSN: ${{ secrets.SENTRY_DSN }} - steps: - - name: Checkout - uses: actions/checkout@v4 - - run: docker compose -f docker-compose.yml --project-name ${{ secrets.STACK_NAME_STAGING }} build - - run: docker compose -f docker-compose.yml --project-name ${{ secrets.STACK_NAME_STAGING }} up -d diff --git a/.github/workflows/generate-client.yml b/.github/workflows/generate-client.yml deleted file mode 100644 index 304363ce9..000000000 --- a/.github/workflows/generate-client.yml +++ /dev/null @@ -1,61 +0,0 @@ -name: Generate Client - -on: - pull_request: - types: - - opened - - synchronize - -jobs: - generate-client: - permissions: - contents: write - runs-on: ubuntu-latest - steps: - # For PRs from forks - - uses: actions/checkout@v4 - # For PRs from the same repo - - uses: actions/checkout@v4 - if: ( github.event_name != 'pull_request' || github.secret_source == 'Actions' ) - with: - ref: ${{ github.head_ref }} - token: ${{ secrets.FULL_STACK_FASTAPI_TEMPLATE_REPO_TOKEN }} - - uses: actions/setup-node@v4 - with: - node-version: lts/* - - uses: actions/setup-python@v5 - with: - python-version: "3.10" - - name: Install uv - uses: astral-sh/setup-uv@v5 - with: - version: "0.4.15" - enable-cache: true - - name: Install dependencies - run: npm ci - working-directory: frontend - - run: uv sync - working-directory: backend - - run: uv run bash scripts/generate-client.sh - env: - VIRTUAL_ENV: backend/.venv - ENVIRONMENT: production - SECRET_KEY: just-for-generating-client - POSTGRES_PASSWORD: just-for-generating-client - FIRST_SUPERUSER_PASSWORD: just-for-generating-client - - name: Add changes to git - run: | - git config --local user.email "github-actions@github.com" - git config --local user.name "github-actions" - git add frontend/src/client - # Same repo PRs - - name: Push changes - if: ( github.event_name != 'pull_request' || github.secret_source == 'Actions' ) - run: | - git diff --staged --quiet || git commit -m "✨ Autogenerate frontend client" - git push - # Fork PRs - - name: Check changes - if: ( github.event_name == 'pull_request' && github.secret_source != 'Actions' ) - run: | - git diff --staged --quiet || (echo "Changes detected in generated client, run scripts/generate-client.sh and commit the changes" && exit 1) diff --git a/.github/workflows/issue-manager.yml b/.github/workflows/issue-manager.yml deleted file mode 100644 index 109ac0e98..000000000 --- a/.github/workflows/issue-manager.yml +++ /dev/null @@ -1,47 +0,0 @@ -name: Issue Manager - -on: - schedule: - - cron: "21 17 * * *" - issue_comment: - types: - - created - issues: - types: - - labeled - pull_request_target: - types: - - labeled - workflow_dispatch: - -permissions: - issues: write - pull-requests: write - -jobs: - issue-manager: - if: github.repository_owner == 'fastapi' - runs-on: ubuntu-latest - steps: - - name: Dump GitHub context - env: - GITHUB_CONTEXT: ${{ toJson(github) }} - run: echo "$GITHUB_CONTEXT" - - uses: tiangolo/issue-manager@0.5.1 - with: - token: ${{ secrets.GITHUB_TOKEN }} - config: > - { - "answered": { - "delay": 864000, - "message": "Assuming the original need was handled, this will be automatically closed now. But feel free to add more comments or create new issues or PRs." - }, - "waiting": { - "delay": 2628000, - "message": "As this PR has been waiting for the original user for a while but seems to be inactive, it's now going to be closed. But if there's anyone interested, feel free to create a new PR." - }, - "invalid": { - "delay": 0, - "message": "This was marked as invalid and will be closed now. If this is an error, please provide additional details." - } - } diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml deleted file mode 100644 index e8e58015a..000000000 --- a/.github/workflows/labeler.yml +++ /dev/null @@ -1,33 +0,0 @@ -name: Labels -on: - pull_request_target: - types: - - opened - - synchronize - - reopened - # For label-checker - - labeled - - unlabeled - -jobs: - labeler: - permissions: - contents: read - pull-requests: write - runs-on: ubuntu-latest - steps: - - uses: actions/labeler@v5 - if: ${{ github.event.action != 'labeled' && github.event.action != 'unlabeled' }} - - run: echo "Done adding labels" - # Run this after labeler applied labels - check-labels: - needs: - - labeler - permissions: - pull-requests: read - runs-on: ubuntu-latest - steps: - - uses: docker://agilepathway/pull-request-label-checker:latest - with: - one_of: breaking,security,feature,bug,refactor,upgrade,docs,lang-all,internal - repo_token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/latest-changes.yml b/.github/workflows/latest-changes.yml deleted file mode 100644 index 607c5243b..000000000 --- a/.github/workflows/latest-changes.yml +++ /dev/null @@ -1,40 +0,0 @@ -name: Latest Changes - -on: - pull_request_target: - branches: - - master - types: - - closed - workflow_dispatch: - inputs: - number: - description: PR number - required: true - debug_enabled: - description: "Run the build with tmate debugging enabled (https://github.com/marketplace/actions/debugging-with-tmate)" - required: false - default: "false" - -jobs: - latest-changes: - runs-on: ubuntu-latest - permissions: - pull-requests: read - steps: - - name: Dump GitHub context - env: - GITHUB_CONTEXT: ${{ toJson(github) }} - run: echo "$GITHUB_CONTEXT" - - uses: actions/checkout@v4 - with: - # To allow latest-changes to commit to the main branch - token: ${{ secrets.LATEST_CHANGES }} - - uses: tiangolo/latest-changes@0.3.2 - with: - token: ${{ secrets.GITHUB_TOKEN }} - latest_changes_file: ./release-notes.md - latest_changes_header: "## Latest Changes" - end_regex: "^## " - debug_logs: true - label_header_prefix: "### " diff --git a/.github/workflows/lint-backend.yml b/.github/workflows/lint-backend.yml deleted file mode 100644 index a6e536bff..000000000 --- a/.github/workflows/lint-backend.yml +++ /dev/null @@ -1,28 +0,0 @@ -name: Lint Backend - -on: - push: - branches: - - master - pull_request: - types: - - opened - - synchronize - -jobs: - lint-backend: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: "3.10" - - name: Install uv - uses: astral-sh/setup-uv@v5 - with: - version: "0.4.15" - enable-cache: true - - run: uv run bash scripts/lint.sh - working-directory: backend diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml deleted file mode 100644 index 5b13c5868..000000000 --- a/.github/workflows/playwright.yml +++ /dev/null @@ -1,131 +0,0 @@ -name: Playwright Tests - -on: - push: - branches: - - master - pull_request: - types: - - opened - - synchronize - workflow_dispatch: - inputs: - debug_enabled: - description: 'Run the build with tmate debugging enabled (https://github.com/marketplace/actions/debugging-with-tmate)' - required: false - default: 'false' - -jobs: - changes: - runs-on: ubuntu-latest - # Set job outputs to values from filter step - outputs: - changed: ${{ steps.filter.outputs.changed }} - steps: - - uses: actions/checkout@v4 - # For pull requests it's not necessary to checkout the code but for the main branch it is - - uses: dorny/paths-filter@v3 - id: filter - with: - filters: | - changed: - - backend/** - - frontend/** - - .env - - docker-compose*.yml - - .github/workflows/playwright.yml - - test-playwright: - needs: - - changes - if: ${{ needs.changes.outputs.changed == 'true' }} - timeout-minutes: 60 - runs-on: ubuntu-latest - strategy: - matrix: - shardIndex: [1, 2, 3, 4] - shardTotal: [4] - fail-fast: false - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 - with: - node-version: lts/* - - uses: actions/setup-python@v5 - with: - python-version: '3.10' - - name: Setup tmate session - uses: mxschmitt/action-tmate@v3 - if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.debug_enabled == 'true' }} - with: - limit-access-to-actor: true - - name: Install uv - uses: astral-sh/setup-uv@v5 - with: - version: "0.4.15" - enable-cache: true - - run: uv sync - working-directory: backend - - run: npm ci - working-directory: frontend - - run: uv run bash scripts/generate-client.sh - env: - VIRTUAL_ENV: backend/.venv - - run: docker compose build - - run: docker compose down -v --remove-orphans - - name: Run Playwright tests - run: docker compose run --rm playwright npx playwright test --fail-on-flaky-tests --trace=retain-on-failure --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }} - - run: docker compose down -v --remove-orphans - - name: Upload blob report to GitHub Actions Artifacts - if: ${{ !cancelled() }} - uses: actions/upload-artifact@v4 - with: - name: blob-report-${{ matrix.shardIndex }} - path: frontend/blob-report - include-hidden-files: true - retention-days: 1 - - merge-playwright-reports: - needs: - - test-playwright - - changes - # Merge reports after playwright-tests, even if some shards have failed - if: ${{ !cancelled() && needs.changes.outputs.changed == 'true' }} - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 - with: - node-version: 20 - - name: Install dependencies - run: npm ci - working-directory: frontend - - name: Download blob reports from GitHub Actions Artifacts - uses: actions/download-artifact@v4 - with: - path: frontend/all-blob-reports - pattern: blob-report-* - merge-multiple: true - - name: Merge into HTML Report - run: npx playwright merge-reports --reporter html ./all-blob-reports - working-directory: frontend - - name: Upload HTML report - uses: actions/upload-artifact@v4 - with: - name: html-report--attempt-${{ github.run_attempt }} - path: frontend/playwright-report - retention-days: 30 - include-hidden-files: true - - # https://github.com/marketplace/actions/alls-green#why - alls-green-playwright: # This job does nothing and is only used for the branch protection - if: always() - needs: - - test-playwright - runs-on: ubuntu-latest - steps: - - name: Decide whether the needed jobs succeeded or failed - uses: re-actors/alls-green@release/v1 - with: - jobs: ${{ toJSON(needs) }} - allowed-skips: test-playwright diff --git a/.github/workflows/smokeshow.yml b/.github/workflows/smokeshow.yml deleted file mode 100644 index 61fde520e..000000000 --- a/.github/workflows/smokeshow.yml +++ /dev/null @@ -1,35 +0,0 @@ -name: Smokeshow - -on: - workflow_run: - workflows: [Test Backend] - types: [completed] - -jobs: - smokeshow: - if: ${{ github.event.workflow_run.conclusion == 'success' }} - runs-on: ubuntu-latest - permissions: - actions: read - statuses: write - - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-python@v5 - with: - python-version: "3.10" - - run: pip install smokeshow - - uses: actions/download-artifact@v4 - with: - name: coverage-html - path: backend/htmlcov - github-token: ${{ secrets.GITHUB_TOKEN }} - run-id: ${{ github.event.workflow_run.id }} - - run: smokeshow upload backend/htmlcov - env: - SMOKESHOW_GITHUB_STATUS_DESCRIPTION: Coverage {coverage-percentage} - SMOKESHOW_GITHUB_COVERAGE_THRESHOLD: 90 - SMOKESHOW_GITHUB_CONTEXT: coverage - SMOKESHOW_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - SMOKESHOW_GITHUB_PR_HEAD_SHA: ${{ github.event.workflow_run.head_sha }} - SMOKESHOW_AUTH_KEY: ${{ secrets.SMOKESHOW_AUTH_KEY }} diff --git a/.github/workflows/test-backend.yml b/.github/workflows/test-backend.yml deleted file mode 100644 index cbbb78de4..000000000 --- a/.github/workflows/test-backend.yml +++ /dev/null @@ -1,41 +0,0 @@ -name: Test Backend - -on: - push: - branches: - - master - pull_request: - types: - - opened - - synchronize - -jobs: - test-backend: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: "3.10" - - name: Install uv - uses: astral-sh/setup-uv@v5 - with: - version: "0.4.15" - enable-cache: true - - run: docker compose down -v --remove-orphans - - run: docker compose up -d db mailcatcher - - name: Migrate DB - run: uv run bash scripts/prestart.sh - working-directory: backend - - name: Run tests - run: uv run bash scripts/tests-start.sh "Coverage for ${{ github.sha }}" - working-directory: backend - - run: docker compose down -v --remove-orphans - - name: Store coverage files - uses: actions/upload-artifact@v4 - with: - name: coverage-html - path: backend/htmlcov - include-hidden-files: true diff --git a/.github/workflows/test-docker-compose.yml b/.github/workflows/test-docker-compose.yml deleted file mode 100644 index 17792ede5..000000000 --- a/.github/workflows/test-docker-compose.yml +++ /dev/null @@ -1,26 +0,0 @@ -name: Test Docker Compose - -on: - push: - branches: - - master - pull_request: - types: - - opened - - synchronize - -jobs: - - test-docker-compose: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - - run: docker compose build - - run: docker compose down -v --remove-orphans - - run: docker compose up -d --wait backend frontend adminer - - name: Test backend is up - run: curl http://localhost:8000/api/v1/utils/health-check - - name: Test frontend is up - run: curl http://localhost:5173 - - run: docker compose down -v --remove-orphans diff --git a/backend/Dockerfile b/backend/Dockerfile index 44c53f036..99db91d25 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -1,43 +1,42 @@ -FROM python:3.10 +# Use Python 3.12 base image +FROM python:3.12 +# Set environment variables ENV PYTHONUNBUFFERED=1 +# Set working directory WORKDIR /app/ -# Install uv -# Ref: https://docs.astral.sh/uv/guides/integration/docker/#installing-uv +# Install system dependencies +RUN apt-get update && apt-get install -y curl + +# Install uv package manager COPY --from=ghcr.io/astral-sh/uv:0.5.11 /uv /uvx /bin/ # Place executables in the environment at the front of the path -# Ref: https://docs.astral.sh/uv/guides/integration/docker/#using-the-environment ENV PATH="/app/.venv/bin:$PATH" -# Compile bytecode -# Ref: https://docs.astral.sh/uv/guides/integration/docker/#compiling-bytecode +# Enable bytecode compilation and efficient dependency linking ENV UV_COMPILE_BYTECODE=1 - -# uv Cache -# Ref: https://docs.astral.sh/uv/guides/integration/docker/#caching ENV UV_LINK_MODE=copy +# Copy dependency files +COPY pyproject.toml uv.lock ./ + # Install dependencies -# Ref: https://docs.astral.sh/uv/guides/integration/docker/#intermediate-layers RUN --mount=type=cache,target=/root/.cache/uv \ - --mount=type=bind,source=uv.lock,target=uv.lock \ - --mount=type=bind,source=pyproject.toml,target=pyproject.toml \ uv sync --frozen --no-install-project +# Set Python path ENV PYTHONPATH=/app -COPY ./scripts /app/scripts +# Copy application files +COPY scripts /app/scripts +COPY app /app/app +COPY alembic.ini /app/alembic.ini -COPY ./pyproject.toml ./uv.lock ./alembic.ini /app/ +# Expose port 80 +EXPOSE 80 -COPY ./app /app/app - -# Sync the project -# Ref: https://docs.astral.sh/uv/guides/integration/docker/#intermediate-layers -RUN --mount=type=cache,target=/root/.cache/uv \ - uv sync -CMD ["fastapi", "run", "--workers", "4", "app/main.py"] +CMD ["uv", "run", "uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "80", "--workers", "4"] diff --git a/backend/app/api/routes/utils.py b/backend/app/api/routes/utils.py index fc093419b..eb45bbc20 100644 --- a/backend/app/api/routes/utils.py +++ b/backend/app/api/routes/utils.py @@ -26,6 +26,6 @@ def test_email(email_to: EmailStr) -> Message: return Message(message="Test email sent") -@router.get("/health-check/") +@router.get("/health/") async def health_check() -> bool: return True diff --git a/deployment.md b/deployment.md index eadf76dda..b75a42137 100644 --- a/deployment.md +++ b/deployment.md @@ -1,4 +1,4 @@ -# FastAPI Project - Deployment +# AI Platform - Deployment You can deploy the project using Docker Compose to a remote server. diff --git a/development.md b/development.md index d7d41d73f..412c31927 100644 --- a/development.md +++ b/development.md @@ -1,4 +1,4 @@ -# FastAPI Project - Development +# AI Platform - Development ## Docker Compose diff --git a/envSample b/envSample index 23fe5f23c..8c9457bc9 100644 --- a/envSample +++ b/envSample @@ -13,8 +13,9 @@ FRONTEND_HOST=http://localhost:5173 # Environment: local, staging, production ENVIRONMENT=local -PROJECT_NAME="Full Stack FastAPI Project" -STACK_NAME=full-stack-fastapi-project + +PROJECT_NAME="AI Platform" +STACK_NAME=ai-platform # Backend BACKEND_CORS_ORIGINS="http://localhost"