Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 1 addition & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ on:
branches: [main]

jobs:
lint-and-test:
lint:
runs-on: ubuntu-latest
strategy:
matrix:
Expand All @@ -33,7 +33,3 @@ jobs:
- name: Check code formatting with Black
run: |
black --check .

- name: Run tests
run: |
pytest
10 changes: 1 addition & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,7 @@ A FastAPI backend with JWT authentication, Role-Based Access Control (RBAC), dat
- ✅ **Alembic Migrations** with async support
- ✅ **Rate Limiting** with slowapi
- ✅ **Docker Compose** for local development
- ✅ **CI Pipeline** with GitHub Actions (linting, formatting, testing)
- ✅ **Comprehensive Testing** setup
- ✅ **CI Pipeline** with GitHub Actions (linting, formatting)
- ✅ **Optimized Queries** with proper indexing
- ✅ **No N+1 Problems** with eager loading

Expand All @@ -29,7 +28,6 @@ A FastAPI backend with JWT authentication, Role-Based Access Control (RBAC), dat
│ ├── schemas/ # Pydantic schemas
│ └── main.py # FastAPI application
├── alembic/ # Database migrations
├── tests/ # Test files
├── docker-compose.yml # Local development setup
├── Dockerfile # Container image
└── requirements.txt # Python dependencies
Expand Down Expand Up @@ -174,12 +172,6 @@ curl -X GET "http://localhost:8000/api/v1/users/me" \

## Development

### Running Tests

```bash
pytest
```

### Code Formatting

```bash
Expand Down
2 changes: 0 additions & 2 deletions alembic/env.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
from sqlalchemy.ext.asyncio import async_engine_from_config

from alembic import context

from app.core.config import settings
from app.db.base import Base

Expand Down Expand Up @@ -98,4 +97,3 @@ def run_migrations_online() -> None:
run_migrations_offline()
else:
run_migrations_online()

140 changes: 78 additions & 62 deletions alembic/versions/001_initial_migration.py
Original file line number Diff line number Diff line change
@@ -1,89 +1,105 @@
"""Initial migration

Revision ID: 001
Revises:
Revises:
Create Date: 2024-01-01 00:00:00.000000

"""
from typing import Sequence, Union
from collections.abc import Sequence

from alembic import op
import sqlalchemy as sa

from alembic import op

# revision identifiers, used by Alembic.
revision: str = '001'
down_revision: Union[str, None] = None
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
revision: str = "001"
down_revision: str | None = None
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None


def upgrade() -> None:
op.create_table(
'roles',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('name', sa.String(length=50), nullable=False),
sa.Column('description', sa.String(length=255), nullable=True),
sa.Column('created_at', sa.DateTime(), nullable=False),
sa.Column('updated_at', sa.DateTime(), nullable=False),
sa.PrimaryKeyConstraint('id')
"roles",
sa.Column("id", sa.Integer(), nullable=False),
sa.Column("name", sa.String(length=50), nullable=False),
sa.Column("description", sa.String(length=255), nullable=True),
sa.Column("created_at", sa.DateTime(), nullable=False),
sa.Column("updated_at", sa.DateTime(), nullable=False),
sa.PrimaryKeyConstraint("id"),
)
op.create_index(op.f('ix_roles_id'), 'roles', ['id'], unique=False)
op.create_index(op.f('ix_roles_name'), 'roles', ['name'], unique=True)
op.create_index(op.f("ix_roles_id"), "roles", ["id"], unique=False)
op.create_index(op.f("ix_roles_name"), "roles", ["name"], unique=True)

op.create_table(
'permissions',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('name', sa.String(length=50), nullable=False),
sa.Column('description', sa.String(length=255), nullable=True),
sa.Column('created_at', sa.DateTime(), nullable=False),
sa.Column('updated_at', sa.DateTime(), nullable=False),
sa.PrimaryKeyConstraint('id')
"permissions",
sa.Column("id", sa.Integer(), nullable=False),
sa.Column("name", sa.String(length=50), nullable=False),
sa.Column("description", sa.String(length=255), nullable=True),
sa.Column("created_at", sa.DateTime(), nullable=False),
sa.Column("updated_at", sa.DateTime(), nullable=False),
sa.PrimaryKeyConstraint("id"),
)
op.create_index(op.f('ix_permissions_id'), 'permissions', ['id'], unique=False)
op.create_index(op.f('ix_permissions_name'), 'permissions', ['name'], unique=True)
op.create_index(op.f("ix_permissions_id"), "permissions", ["id"], unique=False)
op.create_index(op.f("ix_permissions_name"), "permissions", ["name"], unique=True)

op.create_table(
'roles_permissions',
sa.Column('role_id', sa.Integer(), nullable=False),
sa.Column('permission_id', sa.Integer(), nullable=False),
sa.ForeignKeyConstraint(['permission_id'], ['permissions.id'], ),
sa.ForeignKeyConstraint(['role_id'], ['roles.id'], ),
sa.PrimaryKeyConstraint('role_id', 'permission_id')
"roles_permissions",
sa.Column("role_id", sa.Integer(), nullable=False),
sa.Column("permission_id", sa.Integer(), nullable=False),
sa.ForeignKeyConstraint(
["permission_id"],
["permissions.id"],
),
sa.ForeignKeyConstraint(
["role_id"],
["roles.id"],
),
sa.PrimaryKeyConstraint("role_id", "permission_id"),
)
op.create_index(
op.f("ix_roles_permissions_permission_id"),
"roles_permissions",
["permission_id"],
unique=False,
)
op.create_index(
op.f("ix_roles_permissions_role_id"), "roles_permissions", ["role_id"], unique=False
)
op.create_index(op.f('ix_roles_permissions_permission_id'), 'roles_permissions', ['permission_id'], unique=False)
op.create_index(op.f('ix_roles_permissions_role_id'), 'roles_permissions', ['role_id'], unique=False)

op.create_table(
'users',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('email', sa.String(length=255), nullable=False),
sa.Column('username', sa.String(length=50), nullable=False),
sa.Column('hashed_password', sa.String(length=255), nullable=False),
sa.Column('role_id', sa.Integer(), nullable=False),
sa.Column('created_at', sa.DateTime(), nullable=False),
sa.Column('updated_at', sa.DateTime(), nullable=False),
sa.ForeignKeyConstraint(['role_id'], ['roles.id'], ),
sa.PrimaryKeyConstraint('id')
"users",
sa.Column("id", sa.Integer(), nullable=False),
sa.Column("email", sa.String(length=255), nullable=False),
sa.Column("username", sa.String(length=50), nullable=False),
sa.Column("hashed_password", sa.String(length=255), nullable=False),
sa.Column("role_id", sa.Integer(), nullable=False),
sa.Column("created_at", sa.DateTime(), nullable=False),
sa.Column("updated_at", sa.DateTime(), nullable=False),
sa.ForeignKeyConstraint(
["role_id"],
["roles.id"],
),
sa.PrimaryKeyConstraint("id"),
)
op.create_index(op.f('ix_users_id'), 'users', ['id'], unique=False)
op.create_index(op.f('ix_users_email'), 'users', ['email'], unique=True)
op.create_index(op.f('ix_users_username'), 'users', ['username'], unique=True)
op.create_index(op.f('ix_users_role_id'), 'users', ['role_id'], unique=False)
op.create_index(op.f("ix_users_id"), "users", ["id"], unique=False)
op.create_index(op.f("ix_users_email"), "users", ["email"], unique=True)
op.create_index(op.f("ix_users_username"), "users", ["username"], unique=True)
op.create_index(op.f("ix_users_role_id"), "users", ["role_id"], unique=False)


def downgrade() -> None:
op.drop_index(op.f('ix_users_role_id'), table_name='users')
op.drop_index(op.f('ix_users_username'), table_name='users')
op.drop_index(op.f('ix_users_email'), table_name='users')
op.drop_index(op.f('ix_users_id'), table_name='users')
op.drop_table('users')
op.drop_index(op.f('ix_roles_permissions_role_id'), table_name='roles_permissions')
op.drop_index(op.f('ix_roles_permissions_permission_id'), table_name='roles_permissions')
op.drop_table('roles_permissions')
op.drop_index(op.f('ix_permissions_name'), table_name='permissions')
op.drop_index(op.f('ix_permissions_id'), table_name='permissions')
op.drop_table('permissions')
op.drop_index(op.f('ix_roles_name'), table_name='roles')
op.drop_index(op.f('ix_roles_id'), table_name='roles')
op.drop_table('roles')

op.drop_index(op.f("ix_users_role_id"), table_name="users")
op.drop_index(op.f("ix_users_username"), table_name="users")
op.drop_index(op.f("ix_users_email"), table_name="users")
op.drop_index(op.f("ix_users_id"), table_name="users")
op.drop_table("users")
op.drop_index(op.f("ix_roles_permissions_role_id"), table_name="roles_permissions")
op.drop_index(op.f("ix_roles_permissions_permission_id"), table_name="roles_permissions")
op.drop_table("roles_permissions")
op.drop_index(op.f("ix_permissions_name"), table_name="permissions")
op.drop_index(op.f("ix_permissions_id"), table_name="permissions")
op.drop_table("permissions")
op.drop_index(op.f("ix_roles_name"), table_name="roles")
op.drop_index(op.f("ix_roles_id"), table_name="roles")
op.drop_table("roles")
Loading