feat: implement auth, projects, and frontend foundation
This commit is contained in:
@@ -0,0 +1,125 @@
|
||||
# Design: Database Models
|
||||
|
||||
## Technology Choices
|
||||
|
||||
- **SQLAlchemy 2.0**: Modern async ORM with type annotations
|
||||
- **asyncpg**: High-performance async PostgreSQL driver
|
||||
- **Alembic**: Database migration tool
|
||||
- **UUID**: All primary keys use UUID for distributed safety
|
||||
|
||||
## Architecture
|
||||
|
||||
### Base Model
|
||||
|
||||
All models inherit from a common base with:
|
||||
- `id`: UUID primary key (default=uuid4)
|
||||
- `created_at`: Timestamp
|
||||
- `updated_at`: Timestamp (auto-updated)
|
||||
|
||||
### Models
|
||||
|
||||
1. **User**
|
||||
- id: UUID PK
|
||||
- email: str, unique, indexed
|
||||
- name: str
|
||||
- authentik_id: str, unique (external auth reference)
|
||||
- avatar_url: str | None
|
||||
- created_at, updated_at
|
||||
|
||||
2. **Project**
|
||||
- id: UUID PK
|
||||
- name: str
|
||||
- description: str | None
|
||||
- owner_id: UUID → User
|
||||
- default_ssh_key_id: UUID → SSHKey | None
|
||||
- created_at, updated_at
|
||||
|
||||
3. **GitRepository**
|
||||
- id: UUID PK
|
||||
- name: str
|
||||
- path: str (filesystem path to bare repo)
|
||||
- project_id: UUID → Project
|
||||
- owner_id: UUID → User
|
||||
- is_mirror: bool
|
||||
- remote_url: str | None
|
||||
- last_push: datetime | None
|
||||
- created_at
|
||||
|
||||
4. **SSHKey**
|
||||
- id: UUID PK
|
||||
- name: str
|
||||
- public_key: str
|
||||
- private_key_encrypted: str (Fernet encrypted)
|
||||
- user_id: UUID → User
|
||||
- project_id: UUID → Project | None
|
||||
- created_at
|
||||
|
||||
5. **UserConfig**
|
||||
- id: UUID PK
|
||||
- user_id: UUID → User
|
||||
- config: JSONB (PostgreSQL native JSON)
|
||||
- created_at, updated_at
|
||||
|
||||
### Relationships
|
||||
|
||||
```
|
||||
User 1--N Project
|
||||
User 1--N SSHKey
|
||||
User 1--1 UserConfig
|
||||
Project 1--N GitRepository
|
||||
Project N--1 SSHKey (default_ssh_key)
|
||||
```
|
||||
|
||||
### File Structure
|
||||
|
||||
```
|
||||
apps/api/
|
||||
├── src/
|
||||
│ ├── models/
|
||||
│ │ ├── __init__.py
|
||||
│ │ ├── base.py # DeclarativeBase + common columns
|
||||
│ │ ├── user.py
|
||||
│ │ ├── project.py
|
||||
│ │ ├── git_repository.py
|
||||
│ │ ├── ssh_key.py
|
||||
│ │ └── user_config.py
|
||||
│ ├── database.py # Async engine + session
|
||||
│ └── config.py # Settings with pydantic-settings
|
||||
├── alembic/
|
||||
│ ├── env.py
|
||||
│ ├── script.py.mako
|
||||
│ └── versions/
|
||||
│ └── 001_initial.py
|
||||
├── tests/
|
||||
│ └── test_models.py
|
||||
└── scripts/
|
||||
└── seed.py
|
||||
```
|
||||
|
||||
## Async Pattern
|
||||
|
||||
```python
|
||||
from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
|
||||
engine = create_async_engine(DATABASE_URL)
|
||||
AsyncSessionLocal = sessionmaker(engine, class_=AsyncSession)
|
||||
```
|
||||
|
||||
## Migration Strategy
|
||||
|
||||
- Single initial migration creating all tables
|
||||
- Future migrations use `alembic revision --autogenerate`
|
||||
- Run with `make migrate` (docker compose exec api alembic upgrade head)
|
||||
|
||||
## Seed Data
|
||||
|
||||
- Create a test user with sample data
|
||||
- Run via `docker compose exec api python scripts/seed.py`
|
||||
|
||||
## Quality Gates
|
||||
|
||||
- pytest with async test support
|
||||
- mypy strict mode
|
||||
- ruff for linting
|
||||
- All models have type annotations
|
||||
Reference in New Issue
Block a user