diff --git a/.github/workflows/lint_docker.yml b/.github/workflows/lint_docker.yml new file mode 100644 index 0000000..2380717 --- /dev/null +++ b/.github/workflows/lint_docker.yml @@ -0,0 +1,65 @@ +name: Python Lint & Docker Build + +on: + push: + branches: + - 'main' + pull_request: + +env: + REGISTRY: ghcr.io + IMAGE_NAME: ${{ github.repository }} + +jobs: + lint: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup python + uses: actions/setup-python@v5 + with: + cache: 'pip' + + - name: Run flake8 + uses: py-actions/flake8@v2 + with: + exclude: "app/migrations/versions/" + path: "app" + + build: + name: Build docker image + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Log in to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract metadata for Docker + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + tags: | + type=ref,event=pr + type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' }} + + - name: Build and push docker image + uses: docker/build-push-action@v6 + with: + context: app + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + diff --git a/.github/workflows/migrations.yml b/.github/workflows/migrations.yml new file mode 100644 index 0000000..4c4674f --- /dev/null +++ b/.github/workflows/migrations.yml @@ -0,0 +1,146 @@ +name: Check Migrations + +on: + pull_request: + +jobs: + detect-diffs: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Changes in models directories + id: model-changes + uses: tj-actions/changed-files@v45 + with: + path: "app/services" + files: "./*/models/**" + + - name: Model changes output + env: + CHANGED_MODEL_FILES: ${{ steps.model-changes.outputs.all_changed_files }} + run: | + for file in "${CHANGED_MODEL_FILES}"; do + echo "$file was changed" + done + + - name: Changes in migrations + id: migration-changes + uses: tj-actions/changed-files@v45 + with: + path: "app/alembic/versions" + files: "*.py" + + - name: Migration changes output + env: + CHANGED_MIGRATION_FILES: ${{ steps.migration-changes.outputs.all_changed_files }} + run: | + for file in ${CHANGED_MIGRATION_FILES}; do + echo "$file was changed" + done + + outputs: + changed_migration_files: ${{ steps.migration-changes.outputs.all_changed_files }} + changed_model_files: ${{ steps.model-changes.outputs.all_changed_files }} + + + prevent-changes: + runs-on: ubuntu-latest + needs: detect-diffs + steps: + - name: Checkout Repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + ref: ${{ github.base_ref }} + + - name: prevent-changes-to-existing-migrations + run: | + EXISTING_MIGRATIONS=$(git ls-tree -r HEAD --name-only | grep "app/alembic/versions/.*\.py") + CHANGED_MIGRATION_FILES="${{ needs.detect-diffs.outputs.changed_migration_files }}" + + for changed_migration in $CHANGED_MIGRATION_FILES; do + if echo $EXISTING_MIGRATIONS | grep "$changed_migration"; then + echo "An already existing migration should not be edited!" + exit 1 + fi + done + + check-missing-imports: + runs-on: ubuntu-latest + needs: detect-diffs + steps: + - name: Checkout Repository + uses: actions/checkout@v4 + + - name: Check for Missing imports in alembic + id: missing-imports + run: | + CHANGED_MODEL_FILES="${{ needs.detect-diffs.outputs.changed_model_files }}" + + IMPORTS=$(grep -oP "from services\.\w+\.models\.\w+ import \w+" app/alembic/env.py) + MISSING_IMPORTS=() + + for line in $CHANGED_MODEL_FILES; do + model_name=$(basename "$line" .py) + + if ! echo "$IMPORTS" | grep -q "$model_name"; then + MISSING_IMPORTS+=("$model_name") + fi + done + + if [ ${#MISSING_IMPORTS[@]} -ne 0 ]; then + echo "You probably forget to import follwoing models within the alembic/env file: ${MISSING_IMPORTS[*]}" + exit 1 + fi + + migrations: + runs-on: ubuntu-latest + services: + postgres: + image: postgres:17 + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + env: + POSTGRES_HOST: localhost + POSTGRES_PORT: 5432 + POSTGRES_USER: app + POSTGRES_PASSWORD: app + POSTGRES_DB: app + ports: + - "5432:5432" + + steps: + - name: Checkout Repository + uses: actions/checkout@v4 + + - name: Setup python + uses: actions/setup-python@v5 + with: + cache: 'pip' + + - name: Install requirements + working-directory: ./app + run: pip install -r requirements.txt + + - name: Create .env File + working-directory: ./app + run: | + echo "DATABASE_HOST=localhost" >> .env + + - name: Run Migrations UP + working-directory: ./app + run: alembic upgrade head + + - name: Run alembic check + working-directory: ./app + run: | + alembic check + + - name: Run Migrations DOWN + working-directory: ./app + run: alembic downgrade base diff --git a/app/main.py b/app/main.py index 7fd2378..50f2932 100644 --- a/app/main.py +++ b/app/main.py @@ -4,7 +4,6 @@ from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware from sqlalchemy.exc import OperationalError - from utils.config import settings from utils.database.connection import engine diff --git a/app/migrations/env.py b/app/migrations/env.py index d7b4e3c..6bcb2fe 100644 --- a/app/migrations/env.py +++ b/app/migrations/env.py @@ -2,7 +2,6 @@ from alembic import context from sqlalchemy import engine_from_config, pool - from utils.database.connection import SQLALCHEMY_DATABASE_URL, Base # this is the Alembic Config object, which provides diff --git a/app/models/contract.py b/app/models/contract.py index b949ef1..9e3faa6 100644 --- a/app/models/contract.py +++ b/app/models/contract.py @@ -1,10 +1,9 @@ from datetime import datetime from uuid import uuid4 +from models.static import ContractKeyEnum from sqlalchemy import Column, DateTime, Enum, String from sqlalchemy.dialects.postgresql import UUID - -from models.static import ContractKeyEnum from utils.database.connection import Base diff --git a/app/models/customer.py b/app/models/customer.py index 3a3c310..6798b8b 100644 --- a/app/models/customer.py +++ b/app/models/customer.py @@ -4,7 +4,6 @@ from sqlalchemy import Column, DateTime, String from sqlalchemy.dialects.postgresql import UUID from sqlalchemy.orm import relationship - from utils.database.connection import Base diff --git a/app/models/customer_product.py b/app/models/customer_product.py index 4b305ae..e338403 100644 --- a/app/models/customer_product.py +++ b/app/models/customer_product.py @@ -4,7 +4,6 @@ from sqlalchemy import Column, DateTime, ForeignKey, Integer from sqlalchemy.dialects.postgresql import UUID from sqlalchemy.orm import relationship - from utils.database.connection import Base diff --git a/app/models/customer_product_contract.py b/app/models/customer_product_contract.py index e563059..04075aa 100644 --- a/app/models/customer_product_contract.py +++ b/app/models/customer_product_contract.py @@ -2,7 +2,6 @@ from sqlalchemy import Column, DateTime, ForeignKey from sqlalchemy.dialects.postgresql import UUID - from utils.database.connection import Base diff --git a/app/models/inbox_message.py b/app/models/inbox_message.py index 79ede4c..bdb7f43 100644 --- a/app/models/inbox_message.py +++ b/app/models/inbox_message.py @@ -1,11 +1,10 @@ from datetime import datetime from uuid import uuid4 +from models.static import MessageStatusEnum from sqlalchemy import Column, DateTime, Enum, ForeignKey, String, Text from sqlalchemy.dialects.postgresql import UUID from sqlalchemy.orm import relationship - -from models.static import MessageStatusEnum from utils.database.connection import Base diff --git a/app/models/invoice.py b/app/models/invoice.py index 51c9cb5..4902f44 100644 --- a/app/models/invoice.py +++ b/app/models/invoice.py @@ -1,11 +1,10 @@ from datetime import datetime from uuid import uuid4 +from models.static import InvoiceStatusEnum from sqlalchemy import Column, DateTime, Enum, ForeignKey, Numeric from sqlalchemy.dialects.postgresql import UUID from sqlalchemy.orm import relationship - -from models.static import InvoiceStatusEnum from utils.database.connection import Base diff --git a/app/models/product.py b/app/models/product.py index 14edead..a30502c 100644 --- a/app/models/product.py +++ b/app/models/product.py @@ -4,7 +4,6 @@ from sqlalchemy import Column, DateTime, String, Text from sqlalchemy.dialects.postgresql import UUID from sqlalchemy.orm import relationship - from utils.database.connection import Base diff --git a/app/models/product_plan.py b/app/models/product_plan.py index 948de84..69f7502 100644 --- a/app/models/product_plan.py +++ b/app/models/product_plan.py @@ -1,11 +1,10 @@ from datetime import datetime from uuid import uuid4 +from models.static import PlanTypeEnum from sqlalchemy import Boolean, Column, DateTime, Enum, ForeignKey, Numeric from sqlalchemy.dialects.postgresql import UUID from sqlalchemy.orm import relationship - -from models.static import PlanTypeEnum from utils.database.connection import Base diff --git a/app/models/user.py b/app/models/user.py index 780bb8c..174a4f3 100644 --- a/app/models/user.py +++ b/app/models/user.py @@ -1,11 +1,10 @@ from datetime import datetime from uuid import uuid4 +from models.static import RoleEnum from sqlalchemy import Column, DateTime, Enum, ForeignKey, String from sqlalchemy.dialects.postgresql import UUID from sqlalchemy.orm import relationship - -from models.static import RoleEnum from utils.database.connection import Base diff --git a/app/models/voucher.py b/app/models/voucher.py index 4acfab7..bd6d25e 100644 --- a/app/models/voucher.py +++ b/app/models/voucher.py @@ -11,7 +11,6 @@ Text, ) from sqlalchemy.dialects.postgresql import UUID - from utils.database.connection import Base diff --git a/app/utils/database/connection.py b/app/utils/database/connection.py index ef26c4d..a5ff596 100644 --- a/app/utils/database/connection.py +++ b/app/utils/database/connection.py @@ -1,12 +1,12 @@ from sqlalchemy import create_engine from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import sessionmaker - from utils.config import settings SQLALCHEMY_DATABASE_URL = ( f"postgresql://{settings.DATABASE_USERNAME}:{settings.DATABASE_PASSWORD}" - f"@{settings.DATABASE_HOSTNAME}:{settings.DATABASE_PORT}/{settings.DATABASE_NAME}" + f"@{settings.DATABASE_HOSTNAME}:{settings.DATABASE_PORT}" + f"/{settings.DATABASE_NAME}" ) engine = create_engine(SQLALCHEMY_DATABASE_URL) diff --git a/app/utils/database/session.py b/app/utils/database/session.py index 519e81f..0a55951 100644 --- a/app/utils/database/session.py +++ b/app/utils/database/session.py @@ -1,5 +1,4 @@ from sqlalchemy.orm import Session - from utils.database.connection import SessionLocal