docs: comprehensive documentation overhaul
Add complete documentation structure: - Frontend architecture documentation - Database schema documentation - Deployment guides (Docker, Traefik, Authentik, Environment) - Development guides (Setup, Testing, Contributing, Quality Gates) - Deployment architecture documentation - Updated docs README with complete navigation All new features and APIs are now documented. Quality gates: docs only, no code changes
This commit is contained in:
@@ -0,0 +1,72 @@
|
||||
# Headquarter Documentation
|
||||
|
||||
Welcome to the Headquarter documentation. This is your central hub for understanding, using, and contributing to the platform.
|
||||
|
||||
## Quick Links
|
||||
|
||||
- [Features](features/) - Learn about available features
|
||||
- [API Reference](api/) - API endpoint documentation
|
||||
- [Architecture](architecture/) - System design and architecture
|
||||
- [Deployment](deployment/) - Setup and deployment guides
|
||||
- [Development](development/) - Development and contributing guides
|
||||
|
||||
## Getting Started
|
||||
|
||||
New to Headquarter? Start here:
|
||||
|
||||
1. [Quick Start](../README.md#quick-start) - Get up and running
|
||||
2. [Features](features/) - Discover what you can do
|
||||
3. [Architecture](architecture/) - Understand the system
|
||||
|
||||
## Documentation Structure
|
||||
|
||||
### [Features](features/)
|
||||
User guides for each feature:
|
||||
- [Projects](features/projects.md) - Project management
|
||||
- [Repositories](features/repositories.md) - Git repository management
|
||||
- [Workspace](features/workspace.md) - Repository workspace
|
||||
- [Git History](features/git-history.md) - History visualization
|
||||
- [Authentication](features/auth.md) - Login and user management
|
||||
- [Settings](features/settings.md) - User preferences
|
||||
- [Tool Types](features/tool-types.md) - Development tool management
|
||||
- [SSH Keys](features/ssh-keys.md) - SSH key management
|
||||
|
||||
### [API Reference](api/)
|
||||
Complete API documentation:
|
||||
- [Auth](api/auth.md) - Authentication endpoints
|
||||
- [Projects](api/projects.md) - Project endpoints
|
||||
- [Repositories](api/repositories.md) - Repository endpoints
|
||||
- [Users](api/users.md) - User endpoints
|
||||
- [Tool Types](api/tool-types.md) - Tool type endpoints
|
||||
- [SSH Keys](api/ssh-keys.md) - SSH key endpoints
|
||||
|
||||
### [Architecture](architecture/)
|
||||
System architecture documentation:
|
||||
- [Backend](architecture/backend.md) - Backend architecture
|
||||
- [Frontend](architecture/frontend.md) - Frontend architecture
|
||||
- [Database](architecture/database.md) - Database schema
|
||||
- [Deployment](architecture/deployment.md) - Deployment architecture
|
||||
|
||||
### [Deployment](deployment/)
|
||||
Guides for deploying Headquarter:
|
||||
- [Docker](deployment/docker.md) - Docker setup
|
||||
- [Traefik](deployment/traefik.md) - Traefik configuration
|
||||
- [Authentik](deployment/authentik.md) - Authentik setup
|
||||
- [Environment](deployment/environment.md) - Environment variables
|
||||
|
||||
### [Development](development/)
|
||||
Guides for developers:
|
||||
- [Setup](development/setup.md) - Development environment
|
||||
- [Testing](development/testing.md) - Testing strategy
|
||||
- [Contributing](development/contributing.md) - How to contribute
|
||||
- [Quality Gates](development/quality-gates.md) - Code quality
|
||||
|
||||
## Contributing to Documentation
|
||||
|
||||
When adding new features, please document them following our [templates](templates/):
|
||||
|
||||
- [Feature Documentation Template](templates/feature-doc.md)
|
||||
- [API Documentation Template](templates/api-endpoint.md)
|
||||
- [Architecture Documentation Template](templates/architecture.md)
|
||||
|
||||
See the [Contributing Guide](development/contributing.md) for more details.
|
||||
@@ -0,0 +1,51 @@
|
||||
# API Documentation
|
||||
|
||||
## Overview
|
||||
|
||||
The Headquarter API is a RESTful API built with FastAPI. All endpoints (except auth) require authentication via session cookie.
|
||||
|
||||
## Base URL
|
||||
|
||||
- Development: `http://localhost:8000`
|
||||
- Production: `https://api.yourdomain.com`
|
||||
|
||||
## Authentication
|
||||
|
||||
Most endpoints require authentication. Include the session cookie in requests:
|
||||
|
||||
```bash
|
||||
curl http://api.example.com/endpoint \
|
||||
-H "Cookie: session=your_session_cookie"
|
||||
```
|
||||
|
||||
## Response Format
|
||||
|
||||
All responses are JSON. Error responses follow this format:
|
||||
|
||||
```json
|
||||
{
|
||||
"detail": "Error message"
|
||||
}
|
||||
```
|
||||
|
||||
## API Sections
|
||||
|
||||
- [Auth](auth.md) - Authentication endpoints
|
||||
- [Projects](projects.md) - Project management
|
||||
- [Repositories](repositories.md) - Git repositories and file operations
|
||||
- [Users](users.md) - User management and settings
|
||||
- [Tool Types](tool-types.md) - Tool type management
|
||||
- [SSH Keys](ssh-keys.md) - SSH key management
|
||||
|
||||
## Testing
|
||||
|
||||
Interactive API documentation is available at `/docs` when running the backend:
|
||||
|
||||
```
|
||||
http://localhost:8000/docs
|
||||
```
|
||||
|
||||
This provides:
|
||||
- Interactive endpoint testing
|
||||
- Request/response schemas
|
||||
- Authentication via "Authorize" button
|
||||
@@ -0,0 +1,105 @@
|
||||
# Auth API
|
||||
|
||||
Authentication endpoints for OAuth2 login via Authentik.
|
||||
|
||||
## Authentication
|
||||
|
||||
These endpoints handle the OAuth2 flow. No prior authentication is required for `/auth/login` and `/auth/callback`.
|
||||
|
||||
---
|
||||
|
||||
## GET /auth/login
|
||||
|
||||
**Description:** Initiate OAuth2 login flow. Redirects to Authentik.
|
||||
|
||||
### Request
|
||||
|
||||
#### Query Parameters
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|------|----------|-------------|
|
||||
| `next` | `string` | No | URL to redirect to after login |
|
||||
|
||||
### Response
|
||||
|
||||
#### Success (307 Temporary Redirect)
|
||||
|
||||
Redirects to Authentik OAuth2 authorization URL.
|
||||
|
||||
---
|
||||
|
||||
## GET /auth/callback
|
||||
|
||||
**Description:** Handle OAuth2 callback from Authentik.
|
||||
|
||||
### Request
|
||||
|
||||
#### Query Parameters
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|------|----------|-------------|
|
||||
| `code` | `string` | Yes | Authorization code from Authentik |
|
||||
| `state` | `string` | Yes | State parameter for CSRF protection |
|
||||
|
||||
### Response
|
||||
|
||||
#### Success (307 Temporary Redirect)
|
||||
|
||||
Sets session cookie and redirects to frontend.
|
||||
|
||||
#### Error (400 Bad Request)
|
||||
|
||||
```json
|
||||
{
|
||||
"detail": "Invalid state parameter"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## GET /auth/me
|
||||
|
||||
**Description:** Get current authenticated user.
|
||||
|
||||
**Auth:** Required (session cookie)
|
||||
|
||||
### Response
|
||||
|
||||
#### Success (200 OK)
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "uuid",
|
||||
"email": "user@example.com",
|
||||
"name": "User Name",
|
||||
"avatar_url": "https://..."
|
||||
}
|
||||
```
|
||||
|
||||
#### Error (401 Unauthorized)
|
||||
|
||||
```json
|
||||
{
|
||||
"detail": "Not authenticated"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## POST /auth/logout
|
||||
|
||||
**Description:** Log out current user.
|
||||
|
||||
**Auth:** Required (session cookie)
|
||||
|
||||
### Response
|
||||
|
||||
#### Success (200 OK)
|
||||
|
||||
Clears session cookie.
|
||||
|
||||
```json
|
||||
{
|
||||
"message": "Logged out"
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,163 @@
|
||||
# Projects API
|
||||
|
||||
Project management endpoints.
|
||||
|
||||
## Authentication
|
||||
|
||||
All endpoints require authentication (session cookie).
|
||||
|
||||
---
|
||||
|
||||
## GET /projects
|
||||
|
||||
**Description:** List all projects for the current user.
|
||||
|
||||
### Response
|
||||
|
||||
#### Success (200 OK)
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"id": "uuid",
|
||||
"name": "My Project",
|
||||
"description": "Project description",
|
||||
"created_at": "2024-01-01T00:00:00Z",
|
||||
"updated_at": "2024-01-01T00:00:00Z"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## POST /projects
|
||||
|
||||
**Description:** Create a new project.
|
||||
|
||||
### Request
|
||||
|
||||
#### Request Body
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "My Project",
|
||||
"description": "Optional description"
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `name` | `string` | Yes | Project name (max 255 chars) |
|
||||
| `description` | `string` | No | Project description |
|
||||
|
||||
### Response
|
||||
|
||||
#### Success (201 Created)
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "uuid",
|
||||
"name": "My Project",
|
||||
"description": "Optional description",
|
||||
"created_at": "2024-01-01T00:00:00Z",
|
||||
"updated_at": "2024-01-01T00:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
#### Error (422 Validation Error)
|
||||
|
||||
```json
|
||||
{
|
||||
"detail": [
|
||||
{
|
||||
"loc": ["body", "name"],
|
||||
"msg": "field required",
|
||||
"type": "value_error.missing"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## GET /projects/{id}
|
||||
|
||||
**Description:** Get project details.
|
||||
|
||||
### Response
|
||||
|
||||
#### Success (200 OK)
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "uuid",
|
||||
"name": "My Project",
|
||||
"description": "Project description",
|
||||
"created_at": "2024-01-01T00:00:00Z",
|
||||
"updated_at": "2024-01-01T00:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
#### Error (404 Not Found)
|
||||
|
||||
```json
|
||||
{
|
||||
"detail": "Project not found"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## PUT /projects/{id}
|
||||
|
||||
**Description:** Update a project.
|
||||
|
||||
### Request
|
||||
|
||||
#### Request Body
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "Updated Name",
|
||||
"description": "Updated description"
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `name` | `string` | No | New project name |
|
||||
| `description` | `string` | No | New description |
|
||||
|
||||
### Response
|
||||
|
||||
#### Success (200 OK)
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "uuid",
|
||||
"name": "Updated Name",
|
||||
"description": "Updated description",
|
||||
"created_at": "2024-01-01T00:00:00Z",
|
||||
"updated_at": "2024-01-02T00:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## DELETE /projects/{id}
|
||||
|
||||
**Description:** Delete a project and all associated repositories.
|
||||
|
||||
### Response
|
||||
|
||||
#### Success (204 No Content)
|
||||
|
||||
#### Error (404 Not Found)
|
||||
|
||||
```json
|
||||
{
|
||||
"detail": "Project not found"
|
||||
}
|
||||
```
|
||||
|
||||
**Warning:** This also deletes all repositories and their data. Cannot be undone.
|
||||
@@ -0,0 +1,349 @@
|
||||
# Repositories API
|
||||
|
||||
Git repository and file management endpoints.
|
||||
|
||||
## Authentication
|
||||
|
||||
All endpoints require authentication (session cookie).
|
||||
|
||||
---
|
||||
|
||||
## GET /projects/{project_id}/repositories
|
||||
|
||||
**Description:** List repositories in a project.
|
||||
|
||||
### Response
|
||||
|
||||
#### Success (200 OK)
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"id": "uuid",
|
||||
"name": "my-repo",
|
||||
"clone_url": "https://github.com/user/repo.git",
|
||||
"is_mirror": true,
|
||||
"project_id": "uuid",
|
||||
"created_at": "2024-01-01T00:00:00Z"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## POST /projects/{project_id}/repositories
|
||||
|
||||
**Description:** Create a new repository.
|
||||
|
||||
### Request
|
||||
|
||||
#### Request Body
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "my-repo",
|
||||
"remote_url": "https://github.com/user/repo.git",
|
||||
"is_mirror": false
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `name` | `string` | Yes | Repository name |
|
||||
| `remote_url` | `string` | No | Remote URL to clone from |
|
||||
| `is_mirror` | `boolean` | No | Create mirror clone (default: false) |
|
||||
|
||||
### Response
|
||||
|
||||
#### Success (201 Created)
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "uuid",
|
||||
"name": "my-repo",
|
||||
"clone_url": "https://github.com/user/repo.git",
|
||||
"is_mirror": false,
|
||||
"project_id": "uuid",
|
||||
"created_at": "2024-01-01T00:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
#### URL Parsing Suggestion (422 Unprocessable Entity)
|
||||
|
||||
If the URL appears to be a browser URL:
|
||||
|
||||
```json
|
||||
{
|
||||
"detail": "URL appears to be a browser URL, not a git clone URL",
|
||||
"suggested_url": "https://github.com/user/repo.git",
|
||||
"original_url": "https://github.com/user/repo/tree/main",
|
||||
"error_code": "URL_NEEDS_PARSING"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## DELETE /projects/{project_id}/repositories/{id}
|
||||
|
||||
**Description:** Delete a repository.
|
||||
|
||||
### Response
|
||||
|
||||
#### Success (204 No Content)
|
||||
|
||||
**Warning:** Permanently deletes the repository from disk. Cannot be undone.
|
||||
|
||||
---
|
||||
|
||||
## POST /projects/{project_id}/repositories/parse-url
|
||||
|
||||
**Description:** Parse and validate a git URL.
|
||||
|
||||
### Request
|
||||
|
||||
#### Request Body
|
||||
|
||||
```json
|
||||
{
|
||||
"url": "https://github.com/user/repo/tree/main"
|
||||
}
|
||||
```
|
||||
|
||||
### Response
|
||||
|
||||
#### Success (200 OK)
|
||||
|
||||
```json
|
||||
{
|
||||
"original_url": "https://github.com/user/repo/tree/main",
|
||||
"base_url": "https://github.com/user/repo.git",
|
||||
"is_valid_clone_url": false,
|
||||
"needs_parsing": true,
|
||||
"host": "github.com",
|
||||
"message": "This URL contains a branch path. The repository URL is: https://github.com/user/repo.git"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## GET /projects/{project_id}/repositories/{id}/files
|
||||
|
||||
**Description:** List files in a directory.
|
||||
|
||||
### Request
|
||||
|
||||
#### Query Parameters
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|------|----------|-------------|
|
||||
| `branch` | `string` | No | Branch name (default: repository default) |
|
||||
| `path` | `string` | No | Directory path (default: root) |
|
||||
|
||||
### Response
|
||||
|
||||
#### Success (200 OK)
|
||||
|
||||
```json
|
||||
{
|
||||
"path": "src",
|
||||
"branch": "main",
|
||||
"entries": [
|
||||
{
|
||||
"name": "components",
|
||||
"type": "directory",
|
||||
"path": "src/components"
|
||||
},
|
||||
{
|
||||
"name": "main.py",
|
||||
"type": "file",
|
||||
"path": "src/main.py",
|
||||
"size": 1234,
|
||||
"last_commit": {
|
||||
"hash": "abc123",
|
||||
"message": "Initial commit",
|
||||
"author": "John Doe",
|
||||
"date": "2024-01-01T00:00:00Z"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## GET /projects/{project_id}/repositories/{id}/files/content
|
||||
|
||||
**Description:** Get file content.
|
||||
|
||||
### Request
|
||||
|
||||
#### Query Parameters
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|------|----------|-------------|
|
||||
| `branch` | `string` | Yes | Branch name |
|
||||
| `path` | `string` | Yes | File path |
|
||||
|
||||
### Response
|
||||
|
||||
#### Success (200 OK)
|
||||
|
||||
```json
|
||||
{
|
||||
"path": "src/main.py",
|
||||
"branch": "main",
|
||||
"content": "function hello() {\n return 'world';\n}",
|
||||
"size": 42,
|
||||
"encoding": "utf-8",
|
||||
"language": "python",
|
||||
"is_binary": false
|
||||
}
|
||||
```
|
||||
|
||||
#### Binary File (200 OK)
|
||||
|
||||
```json
|
||||
{
|
||||
"path": "image.png",
|
||||
"branch": "main",
|
||||
"content": null,
|
||||
"size": 12345,
|
||||
"encoding": null,
|
||||
"language": null,
|
||||
"is_binary": true
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## POST /projects/{project_id}/repositories/{id}/files/content
|
||||
|
||||
**Description:** Update file content and commit.
|
||||
|
||||
### Request
|
||||
|
||||
#### Request Body
|
||||
|
||||
```json
|
||||
{
|
||||
"path": "src/main.py",
|
||||
"branch": "main",
|
||||
"content": "new content",
|
||||
"commit_message": "Update file",
|
||||
"author_name": "User",
|
||||
"author_email": "user@example.com"
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `path` | `string` | Yes | File path |
|
||||
| `branch` | `string` | Yes | Branch name |
|
||||
| `content` | `string` | Yes | New file content |
|
||||
| `commit_message` | `string` | Yes | Commit message |
|
||||
| `author_name` | `string` | Yes | Author name |
|
||||
| `author_email` | `string` | Yes | Author email |
|
||||
|
||||
### Response
|
||||
|
||||
#### Success (200 OK)
|
||||
|
||||
```json
|
||||
{
|
||||
"commit_hash": "def789",
|
||||
"message": "Update file",
|
||||
"branch": "main"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## GET /projects/{project_id}/repositories/{id}/branches
|
||||
|
||||
**Description:** List branches.
|
||||
|
||||
### Response
|
||||
|
||||
#### Success (200 OK)
|
||||
|
||||
```json
|
||||
{
|
||||
"branches": [
|
||||
{
|
||||
"name": "main",
|
||||
"is_default": true,
|
||||
"last_commit": {
|
||||
"hash": "abc123",
|
||||
"message": "Initial commit",
|
||||
"date": "2024-01-01T00:00:00Z"
|
||||
}
|
||||
}
|
||||
],
|
||||
"default_branch": "main"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## GET /projects/{project_id}/repositories/{id}/history
|
||||
|
||||
**Description:** Get commit history.
|
||||
|
||||
### Request
|
||||
|
||||
#### Query Parameters
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|------|----------|-------------|
|
||||
| `branch` | `string` | No | Branch name (default: all branches) |
|
||||
| `max_count` | `integer` | No | Maximum commits to return (default: 1000) |
|
||||
|
||||
### Response
|
||||
|
||||
#### Success (200 OK)
|
||||
|
||||
```json
|
||||
{
|
||||
"commits": [
|
||||
{
|
||||
"hash": "abc123",
|
||||
"author_name": "John Doe",
|
||||
"author_email": "john@example.com",
|
||||
"author_date": "2024-01-01T00:00:00Z",
|
||||
"message": "Initial commit",
|
||||
"refs": ["HEAD", "main"]
|
||||
}
|
||||
],
|
||||
"branch": "main",
|
||||
"total_count": 50
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## GET /projects/{project_id}/repositories/{id}/commits/{hash}
|
||||
|
||||
**Description:** Get commit details.
|
||||
|
||||
### Response
|
||||
|
||||
#### Success (200 OK)
|
||||
|
||||
```json
|
||||
{
|
||||
"hash": "abc123",
|
||||
"author_name": "John Doe",
|
||||
"author_email": "john@example.com",
|
||||
"author_date": "2024-01-01T00:00:00Z",
|
||||
"committer_name": "John Doe",
|
||||
"committer_email": "john@example.com",
|
||||
"commit_date": "2024-01-01T00:00:00Z",
|
||||
"message": "Initial commit",
|
||||
"stats": {
|
||||
"files_changed": 2,
|
||||
"insertions": 10,
|
||||
"deletions": 0
|
||||
},
|
||||
"diff": "diff --git a/file.txt b/file.txt\n..."
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,77 @@
|
||||
# SSH Keys API
|
||||
|
||||
SSH key management endpoints.
|
||||
|
||||
## Authentication
|
||||
|
||||
All endpoints require authentication (session cookie).
|
||||
|
||||
---
|
||||
|
||||
## GET /ssh-keys
|
||||
|
||||
**Description:** List all SSH keys for the current user.
|
||||
|
||||
### Response
|
||||
|
||||
#### Success (200 OK)
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"id": "uuid",
|
||||
"name": "GitHub Work",
|
||||
"public_key": "ssh-ed25519 AAAAC3NzaC... user@example.com",
|
||||
"created_at": "2024-01-01T00:00:00Z"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
**Note:** Private keys are never returned.
|
||||
|
||||
---
|
||||
|
||||
## POST /ssh-keys
|
||||
|
||||
**Description:** Generate a new SSH key pair.
|
||||
|
||||
### Request
|
||||
|
||||
#### Request Body
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "GitHub Personal"
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `name` | `string` | Yes | Key name/label |
|
||||
|
||||
### Response
|
||||
|
||||
#### Success (201 Created)
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "uuid",
|
||||
"name": "GitHub Personal",
|
||||
"public_key": "ssh-ed25519 AAAAC3NzaC... user@example.com",
|
||||
"created_at": "2024-01-01T00:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
**Note:** The private key is generated and stored securely. It is not returned in the response.
|
||||
|
||||
---
|
||||
|
||||
## DELETE /ssh-keys/{id}
|
||||
|
||||
**Description:** Delete an SSH key.
|
||||
|
||||
### Response
|
||||
|
||||
#### Success (204 No Content)
|
||||
|
||||
**Note:** This permanently deletes both public and private keys.
|
||||
@@ -0,0 +1,154 @@
|
||||
# Tool Types API
|
||||
|
||||
Tool type management endpoints.
|
||||
|
||||
## Authentication
|
||||
|
||||
All endpoints require authentication (session cookie).
|
||||
|
||||
---
|
||||
|
||||
## GET /tool-types
|
||||
|
||||
**Description:** List all tool types.
|
||||
|
||||
### Response
|
||||
|
||||
#### Success (200 OK)
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"id": "uuid",
|
||||
"name": "code-server",
|
||||
"display_name": "VS Code Server",
|
||||
"description": "VS Code in the browser",
|
||||
"is_builtin": true,
|
||||
"created_at": "2024-01-01T00:00:00Z"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## POST /tool-types
|
||||
|
||||
**Description:** Create a new tool type.
|
||||
|
||||
### Request
|
||||
|
||||
#### Request Body
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "my-tool",
|
||||
"display_name": "My Custom Tool",
|
||||
"description": "A custom development tool",
|
||||
"compose_template": "version: \"3.8\"\nservices:\n tool:\n image: my-image:latest\n container_name: {{TOOL_NAME}}\n volumes:\n - {{REPO_PATH}}:/workspace",
|
||||
"required_variables": ["TOOL_NAME", "REPO_PATH"]
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `name` | `string` | Yes | Unique identifier |
|
||||
| `display_name` | `string` | Yes | Human-readable name |
|
||||
| `description` | `string` | No | Description |
|
||||
| `compose_template` | `string` | Yes | Docker Compose YAML |
|
||||
| `required_variables` | `array` | Yes | Required template variables |
|
||||
|
||||
### Response
|
||||
|
||||
#### Success (201 Created)
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "uuid",
|
||||
"name": "my-tool",
|
||||
"display_name": "My Custom Tool",
|
||||
"description": "A custom development tool",
|
||||
"compose_template": "...",
|
||||
"required_variables": ["TOOL_NAME", "REPO_PATH"],
|
||||
"is_builtin": false,
|
||||
"created_at": "2024-01-01T00:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
#### Error (400 Bad Request)
|
||||
|
||||
```json
|
||||
{
|
||||
"detail": "Invalid YAML in compose template"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## GET /tool-types/{id}
|
||||
|
||||
**Description:** Get tool type details.
|
||||
|
||||
### Response
|
||||
|
||||
#### Success (200 OK)
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "uuid",
|
||||
"name": "code-server",
|
||||
"display_name": "VS Code Server",
|
||||
"description": "VS Code in the browser",
|
||||
"compose_template": "...",
|
||||
"required_variables": ["TOOL_NAME", "REPO_PATH"],
|
||||
"is_builtin": true,
|
||||
"created_at": "2024-01-01T00:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## PUT /tool-types/{id}
|
||||
|
||||
**Description:** Update a tool type.
|
||||
|
||||
**Note:** Built-in tool types cannot be modified.
|
||||
|
||||
### Request
|
||||
|
||||
#### Request Body
|
||||
|
||||
Same as POST /tool-types.
|
||||
|
||||
### Response
|
||||
|
||||
#### Success (200 OK)
|
||||
|
||||
Returns updated tool type.
|
||||
|
||||
#### Error (403 Forbidden)
|
||||
|
||||
```json
|
||||
{
|
||||
"detail": "Cannot modify built-in tool types"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## DELETE /tool-types/{id}
|
||||
|
||||
**Description:** Delete a tool type.
|
||||
|
||||
**Note:** Built-in tool types cannot be deleted.
|
||||
|
||||
### Response
|
||||
|
||||
#### Success (204 No Content)
|
||||
|
||||
#### Error (403 Forbidden)
|
||||
|
||||
```json
|
||||
{
|
||||
"detail": "Cannot delete built-in tool types"
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,168 @@
|
||||
# Users API
|
||||
|
||||
User profile and settings management.
|
||||
|
||||
## Authentication
|
||||
|
||||
All endpoints require authentication (session cookie).
|
||||
|
||||
---
|
||||
|
||||
## GET /users/me
|
||||
|
||||
**Description:** Get current user profile.
|
||||
|
||||
### Response
|
||||
|
||||
#### Success (200 OK)
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "uuid",
|
||||
"email": "user@example.com",
|
||||
"name": "User Name",
|
||||
"avatar_url": "https://...",
|
||||
"authentik_id": "authentik-uuid",
|
||||
"created_at": "2024-01-01T00:00:00Z",
|
||||
"updated_at": "2024-01-01T00:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## PUT /users/me
|
||||
|
||||
**Description:** Update user profile.
|
||||
|
||||
### Request
|
||||
|
||||
#### Request Body
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "New Name",
|
||||
"email": "newemail@example.com"
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `name` | `string` | No | New display name |
|
||||
| `email` | `string` | No | New email address |
|
||||
|
||||
### Response
|
||||
|
||||
#### Success (200 OK)
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "uuid",
|
||||
"email": "newemail@example.com",
|
||||
"name": "New Name",
|
||||
"avatar_url": "https://...",
|
||||
"updated_at": "2024-01-02T00:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## POST /users/me/avatar
|
||||
|
||||
**Description:** Upload avatar image.
|
||||
|
||||
### Request
|
||||
|
||||
#### Request Body
|
||||
|
||||
Multipart form data with `file` field.
|
||||
|
||||
**Requirements:**
|
||||
- Format: PNG or JPEG
|
||||
- Max size: 2MB
|
||||
|
||||
### Response
|
||||
|
||||
#### Success (200 OK)
|
||||
|
||||
```json
|
||||
{
|
||||
"avatar_url": "/uploads/avatars/uuid.png"
|
||||
}
|
||||
```
|
||||
|
||||
#### Error (400 Bad Request)
|
||||
|
||||
```json
|
||||
{
|
||||
"detail": "Invalid file format. Only PNG and JPEG are allowed."
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## GET /users/me/config
|
||||
|
||||
**Description:** Get user settings.
|
||||
|
||||
### Response
|
||||
|
||||
#### Success (200 OK)
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "uuid",
|
||||
"user_id": "uuid",
|
||||
"config": {
|
||||
"theme": "dark",
|
||||
"git_identity": {
|
||||
"name": "User Name",
|
||||
"email": "user@example.com"
|
||||
},
|
||||
"default_editor": "code-server"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## PATCH /users/me/config
|
||||
|
||||
**Description:** Update user settings.
|
||||
|
||||
### Request
|
||||
|
||||
#### Request Body
|
||||
|
||||
```json
|
||||
{
|
||||
"theme": "light",
|
||||
"git_identity": {
|
||||
"name": "New Name",
|
||||
"email": "new@example.com"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `theme` | `string` | No | "system", "light", or "dark" |
|
||||
| `git_identity` | `object` | No | `{name, email}` |
|
||||
| `default_editor` | `string` | No | Preferred editor |
|
||||
|
||||
### Response
|
||||
|
||||
#### Success (200 OK)
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "uuid",
|
||||
"user_id": "uuid",
|
||||
"config": {
|
||||
"theme": "light",
|
||||
"git_identity": {
|
||||
"name": "New Name",
|
||||
"email": "new@example.com"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,212 @@
|
||||
# Backend Architecture
|
||||
|
||||
## Overview
|
||||
|
||||
The Headquarter backend is built with **FastAPI** and follows a layered architecture pattern.
|
||||
|
||||
## Architecture Diagram
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ FastAPI App │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ Middleware: CORS → Request Logging → Exception Logging │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ API Layer (src/api/) │
|
||||
│ ┌─────────┐ ┌──────────┐ ┌────────┐ ┌──────────┐ │
|
||||
│ │ Auth │ │ Projects │ │ Users │ │ Git │ │
|
||||
│ │ Routes │ │ Routes │ │ Routes │ │ Repos │ │
|
||||
│ └────┬────┘ └────┬─────┘ └───┬────┘ └────┬─────┘ │
|
||||
├───────┼───────────┼───────────┼───────────┼─────────────────┤
|
||||
│ │ │ │ │ │
|
||||
│ Auth │ Project │ User │ Git │ │
|
||||
│ Layer │ Service │ Service │ Service │ │
|
||||
│ │ │ │ │ │
|
||||
├───────┴───────────┴───────────┴───────────┴─────────────────┤
|
||||
│ Data Layer │
|
||||
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
|
||||
│ │ Models │ │ Database │ │ Config │ │
|
||||
│ │(SQLAlch) │ │(AsyncPG) │ │(Pydantic)│ │
|
||||
│ └──────────┘ └──────────┘ └──────────┘ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Directory Structure
|
||||
|
||||
```
|
||||
src/
|
||||
├── api/ # API Routes
|
||||
│ ├── auth.py # Authentication endpoints
|
||||
│ ├── projects.py # Project endpoints
|
||||
│ ├── git_repositories.py # Repository endpoints
|
||||
│ ├── users.py # User endpoints
|
||||
│ ├── tool_types.py # Tool type endpoints
|
||||
│ ├── ssh_keys.py # SSH key endpoints
|
||||
│ └── dashboard.py # Dashboard endpoints
|
||||
├── auth/ # Authentication
|
||||
│ ├── session.py # Session management
|
||||
│ ├── oidc.py # OAuth2 client
|
||||
│ ├── dependencies.py # Auth dependencies
|
||||
│ └── cookies.py # Cookie utilities
|
||||
├── models/ # Database Models
|
||||
│ ├── user.py # User model
|
||||
│ ├── project.py # Project model
|
||||
│ ├── git_repository.py # Repository model
|
||||
│ ├── tool_type.py # Tool type model
|
||||
│ ├── ssh_key.py # SSH key model
|
||||
│ └── user_config.py # User config model
|
||||
├── utils/ # Utilities
|
||||
│ ├── git_url_parser.py # URL parsing
|
||||
│ ├── git_files.py # Git file operations
|
||||
│ └── git_history.py # Git history operations
|
||||
├── config.py # Configuration
|
||||
├── database.py # Database setup
|
||||
└── main.py # Application entry point
|
||||
```
|
||||
|
||||
## Layers
|
||||
|
||||
### 1. API Layer (`src/api/`)
|
||||
|
||||
**Responsibilities:**
|
||||
- Define HTTP endpoints
|
||||
- Parse request parameters
|
||||
- Return HTTP responses
|
||||
- Use dependencies for auth and DB
|
||||
|
||||
**Pattern:**
|
||||
```python
|
||||
@router.get("/projects")
|
||||
async def list_projects(
|
||||
session: AsyncSession = Depends(get_db_session),
|
||||
user_id: str = Depends(get_current_user_id),
|
||||
):
|
||||
# Call service layer
|
||||
projects = await project_service.list(session, user_id)
|
||||
return projects
|
||||
```
|
||||
|
||||
### 2. Auth Layer (`src/auth/`)
|
||||
|
||||
**Responsibilities:**
|
||||
- Session management (create, verify, expire)
|
||||
- OAuth2 flow (login, callback)
|
||||
- User authentication dependencies
|
||||
|
||||
**Key Components:**
|
||||
- `session.py`: HMAC-SHA256 signed cookies
|
||||
- `oidc.py`: OAuth2 token exchange
|
||||
- `dependencies.py`: FastAPI dependencies for auth
|
||||
|
||||
### 3. Data Layer (`src/models/`, `src/database.py`)
|
||||
|
||||
**Responsibilities:**
|
||||
- Database schema definition
|
||||
- Async database sessions
|
||||
- Connection management
|
||||
|
||||
**Technology:**
|
||||
- SQLAlchemy 2.0 with async support
|
||||
- asyncpg driver for PostgreSQL
|
||||
- Alembic for migrations
|
||||
|
||||
### 4. Utility Layer (`src/utils/`)
|
||||
|
||||
**Responsibilities:**
|
||||
- Git operations (file browsing, history)
|
||||
- URL parsing
|
||||
- Helper functions
|
||||
|
||||
## Data Flow
|
||||
|
||||
### Request Lifecycle
|
||||
|
||||
```
|
||||
1. Request arrives at FastAPI
|
||||
2. Middleware processes (CORS, logging)
|
||||
3. Auth dependency verifies session
|
||||
4. Route handler processes request
|
||||
5. Database session executes queries
|
||||
6. Response returned to client
|
||||
```
|
||||
|
||||
### Authentication Flow
|
||||
|
||||
```
|
||||
1. User clicks login
|
||||
2. Backend redirects to Authentik
|
||||
3. User authenticates
|
||||
4. Authentik redirects with code
|
||||
5. Backend exchanges code for token
|
||||
6. Backend fetches user info
|
||||
7. Backend creates session cookie
|
||||
8. User is authenticated
|
||||
```
|
||||
|
||||
## Dependencies
|
||||
|
||||
### Database Session
|
||||
|
||||
```python
|
||||
async def get_db_session() -> AsyncGenerator[AsyncSession, None]:
|
||||
async with SessionLocal() as session:
|
||||
yield session
|
||||
```
|
||||
|
||||
### Current User
|
||||
|
||||
```python
|
||||
async def get_current_user_id(
|
||||
request: Request,
|
||||
settings: Settings = Depends(get_settings),
|
||||
) -> str:
|
||||
# Verify session cookie
|
||||
# Return user_id
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
Configuration is managed via Pydantic Settings:
|
||||
|
||||
```python
|
||||
class Settings(BaseSettings):
|
||||
app_env: str = "development"
|
||||
database_url: str = "..."
|
||||
authentik_domain: str = "..."
|
||||
# ...
|
||||
```
|
||||
|
||||
Environment variables are automatically loaded from `.env` files.
|
||||
|
||||
## Error Handling
|
||||
|
||||
Errors are handled at multiple levels:
|
||||
|
||||
1. **Validation**: Pydantic validates request bodies
|
||||
2. **HTTP Exceptions**: FastAPI HTTPException for client errors
|
||||
3. **Middleware**: ExceptionLoggingMiddleware logs server errors
|
||||
4. **Database**: SQLAlchemy errors converted to HTTP responses
|
||||
|
||||
## Testing
|
||||
|
||||
- **Unit tests**: SQLite in-memory database
|
||||
- **Integration tests**: PostgreSQL with transaction rollback
|
||||
- **Fixtures**: Shared in `conftest.py`
|
||||
|
||||
## Technology Stack
|
||||
|
||||
| Component | Technology | Version |
|
||||
|-----------|-----------|---------|
|
||||
| Web Framework | FastAPI | ^0.104 |
|
||||
| ORM | SQLAlchemy | ^2.0 |
|
||||
| Database Driver | asyncpg | ^0.29 |
|
||||
| Validation | Pydantic | ^2.0 |
|
||||
| Migrations | Alembic | ^1.12 |
|
||||
| HTTP Client | httpx | ^0.25 |
|
||||
| Testing | pytest | ^7.4 |
|
||||
|
||||
## Related Documentation
|
||||
|
||||
- [Database Schema](database.md)
|
||||
- [Frontend Architecture](../frontend.md)
|
||||
- [API Documentation](../api/)
|
||||
@@ -0,0 +1,231 @@
|
||||
# Database Schema
|
||||
|
||||
## Overview
|
||||
|
||||
Headquarter uses PostgreSQL with SQLAlchemy ORM and Alembic for migrations. All tables use UUID primary keys and include `created_at`/`updated_at` timestamps.
|
||||
|
||||
## Entity Relationship Diagram
|
||||
|
||||
```
|
||||
┌──────────────┐ ┌─────────────────┐ ┌──────────────┐
|
||||
│ users │ │ git_repository │ │ project │
|
||||
├──────────────┤ ├─────────────────┤ ├──────────────┤
|
||||
│ id (PK) │ │ id (PK) │ │ id (PK) │
|
||||
│ email │ │ project_id (FK) │──┐ │ name │
|
||||
│ name │ │ name │ │ │ description │
|
||||
│ authentik_id │ │ remote_url │ │ │ created_by_id│──┐
|
||||
│ avatar_url │ │ local_path │ │ │ │ │
|
||||
│ created_at │ │ is_mirror │ │ │ │ │
|
||||
│ updated_at │ │ created_by_id │──┤ │ │ │
|
||||
└──────────────┘ │ created_at │ │ └──────────────┘ │
|
||||
│ │ updated_at │ │ ▲ │
|
||||
│ └─────────────────┘ │ │ │
|
||||
│ │ │ │ │
|
||||
│ ┌───────┘ │ │ │
|
||||
│ │ │ │ │
|
||||
┌───────▼──────┐ ┌▼────────────────┐ │ │ │
|
||||
│ ssh_keys │ │ user_config │ │ │ │
|
||||
├──────────────┤ ├─────────────────┤ │ │ │
|
||||
│ id (PK) │ │ user_id (FK) │───┘ │ │
|
||||
│ user_id (FK) │ │ theme │ │ │
|
||||
│ name │ │ git_name │ │ │
|
||||
│ public_key │ │ git_email │ │ │
|
||||
│ private_key │ │ default_editor │ │ │
|
||||
│ created_at │ │ created_at │ │ │
|
||||
│ updated_at │ │ updated_at │ │ │
|
||||
└──────────────┘ └─────────────────┘ │ │
|
||||
│ │
|
||||
┌───────────────────────┘ │
|
||||
│ │
|
||||
▼ │
|
||||
┌─────────────────┐ │
|
||||
│ tool_types │ │
|
||||
├─────────────────┤ │
|
||||
│ id (PK) │ │
|
||||
│ name │ │
|
||||
│ display_name │ │
|
||||
│ description │ │
|
||||
│ compose_template│ │
|
||||
│ required_vars │ │
|
||||
│ is_builtin │ │
|
||||
│ created_by_id │─────────────────────────┘
|
||||
│ created_at │
|
||||
│ updated_at │
|
||||
└─────────────────┘
|
||||
```
|
||||
|
||||
## Table Definitions
|
||||
|
||||
### users
|
||||
|
||||
Stores user accounts synchronized from Authentik.
|
||||
|
||||
| Column | Type | Constraints | Description |
|
||||
|--------|------|-------------|-------------|
|
||||
| id | UUID | PK | Unique identifier |
|
||||
| email | VARCHAR(255) | NOT NULL, UNIQUE | User email |
|
||||
| name | VARCHAR(255) | | Display name |
|
||||
| authentik_id | VARCHAR(255) | UNIQUE | Authentik user ID |
|
||||
| avatar_url | VARCHAR(500) | | Profile avatar URL |
|
||||
| created_at | TIMESTAMP | DEFAULT now() | Creation timestamp |
|
||||
| updated_at | TIMESTAMP | DEFAULT now() | Last update timestamp |
|
||||
|
||||
**Relationships**:
|
||||
- One-to-Many: `users` → `git_repository` (created_by_id)
|
||||
- One-to-Many: `users` → `project` (created_by_id)
|
||||
- One-to-Many: `users` → `ssh_keys` (user_id)
|
||||
- One-to-One: `users` → `user_config` (user_id)
|
||||
- One-to-Many: `users` → `tool_types` (created_by_id)
|
||||
|
||||
### project
|
||||
|
||||
Organizes repositories into logical groups.
|
||||
|
||||
| Column | Type | Constraints | Description |
|
||||
|--------|------|-------------|-------------|
|
||||
| id | UUID | PK | Unique identifier |
|
||||
| name | VARCHAR(255) | NOT NULL | Project name |
|
||||
| description | TEXT | | Project description |
|
||||
| created_by_id | UUID | FK → users.id | Creator |
|
||||
| created_at | TIMESTAMP | DEFAULT now() | Creation timestamp |
|
||||
| updated_at | TIMESTAMP | DEFAULT now() | Last update timestamp |
|
||||
|
||||
**Relationships**:
|
||||
- One-to-Many: `project` → `git_repository` (project_id)
|
||||
|
||||
### git_repository
|
||||
|
||||
Git repositories (bare/mirror clones).
|
||||
|
||||
| Column | Type | Constraints | Description |
|
||||
|--------|------|-------------|-------------|
|
||||
| id | UUID | PK | Unique identifier |
|
||||
| project_id | UUID | FK → project.id, NOT NULL | Parent project |
|
||||
| name | VARCHAR(255) | NOT NULL | Repository name |
|
||||
| remote_url | VARCHAR(500) | NOT NULL | Remote git URL |
|
||||
| local_path | VARCHAR(500) | | Local filesystem path |
|
||||
| is_mirror | BOOLEAN | DEFAULT false | Is mirror clone |
|
||||
| created_by_id | UUID | FK → users.id | Creator |
|
||||
| created_at | TIMESTAMP | DEFAULT now() | Creation timestamp |
|
||||
| updated_at | TIMESTAMP | DEFAULT now() | Last update timestamp |
|
||||
|
||||
**Indexes**:
|
||||
- `idx_repo_project`: (project_id)
|
||||
- `idx_repo_name`: (project_id, name) - UNIQUE
|
||||
|
||||
### ssh_keys
|
||||
|
||||
User SSH keys for git authentication.
|
||||
|
||||
| Column | Type | Constraints | Description |
|
||||
|--------|------|-------------|-------------|
|
||||
| id | UUID | PK | Unique identifier |
|
||||
| user_id | UUID | FK → users.id, NOT NULL | Owner |
|
||||
| name | VARCHAR(255) | NOT NULL | Key name |
|
||||
| public_key | TEXT | NOT NULL | Public key |
|
||||
| private_key | TEXT | NOT NULL, ENCRYPTED | Encrypted private key |
|
||||
| created_at | TIMESTAMP | DEFAULT now() | Creation timestamp |
|
||||
| updated_at | TIMESTAMP | DEFAULT now() | Last update timestamp |
|
||||
|
||||
**Indexes**:
|
||||
- `idx_ssh_user`: (user_id)
|
||||
|
||||
### user_config
|
||||
|
||||
User preferences and settings.
|
||||
|
||||
| Column | Type | Constraints | Description |
|
||||
|--------|------|-------------|-------------|
|
||||
| id | UUID | PK | Unique identifier |
|
||||
| user_id | UUID | FK → users.id, NOT NULL, UNIQUE | Owner |
|
||||
| theme | VARCHAR(50) | DEFAULT 'system' | UI theme (system/light/dark) |
|
||||
| git_name | VARCHAR(255) | | Git user name |
|
||||
| git_email | VARCHAR(255) | | Git user email |
|
||||
| default_editor | VARCHAR(50) | DEFAULT 'vscode' | Preferred editor |
|
||||
| created_at | TIMESTAMP | DEFAULT now() | Creation timestamp |
|
||||
| updated_at | TIMESTAMP | DEFAULT now() | Last update timestamp |
|
||||
|
||||
### tool_types
|
||||
|
||||
Types of development tools that can be spawned.
|
||||
|
||||
| Column | Type | Constraints | Description |
|
||||
|--------|------|-------------|-------------|
|
||||
| id | UUID | PK | Unique identifier |
|
||||
| name | VARCHAR(255) | NOT NULL, UNIQUE | Machine name |
|
||||
| display_name | VARCHAR(255) | NOT NULL | Human-readable name |
|
||||
| description | TEXT | | Description |
|
||||
| compose_template | TEXT | NOT NULL | Docker Compose template |
|
||||
| required_variables | JSONB | NOT NULL | Template variables |
|
||||
| is_builtin | BOOLEAN | DEFAULT false | Built-in type |
|
||||
| created_by_id | UUID | FK → users.id | Creator (null for built-in) |
|
||||
| created_at | TIMESTAMP | DEFAULT now() | Creation timestamp |
|
||||
| updated_at | TIMESTAMP | DEFAULT now() | Last update timestamp |
|
||||
|
||||
**Indexes**:
|
||||
- `idx_tool_builtin`: (is_builtin)
|
||||
|
||||
## Migration History
|
||||
|
||||
| Version | Date | Description |
|
||||
|---------|------|-------------|
|
||||
| 0001_initial_schema | 2024-01-XX | Initial tables: users, projects, git_repositories |
|
||||
| 0002_refresh_tokens | 2024-01-XX | Added refresh_tokens table |
|
||||
| 0003_user_configs | 2024-05-18 | Added user_config table |
|
||||
| 0004_tool_types | 2024-05-18 | Added tool_types table |
|
||||
|
||||
## Data Types
|
||||
|
||||
### PostgreSQL Types
|
||||
- **UUID**: `uuid` - All primary keys
|
||||
- **Timestamps**: `TIMESTAMP WITH TIME ZONE`
|
||||
- **JSONB**: `JSONB` - For flexible config (user_config, tool_types)
|
||||
- **Strings**: `VARCHAR(n)` - With appropriate length limits
|
||||
- **Text**: `TEXT` - For unbounded content
|
||||
- **Boolean**: `BOOLEAN` - True/false flags
|
||||
|
||||
### SQLAlchemy Configuration
|
||||
```python
|
||||
# Base model features
|
||||
class Base:
|
||||
id: UUID = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||
created_at: datetime = Column(DateTime(timezone=True), server_default=func.now())
|
||||
updated_at: datetime = Column(DateTime(timezone=True), onupdate=func.now())
|
||||
```
|
||||
|
||||
## Backup Strategy
|
||||
|
||||
### Automated Backups
|
||||
- **Frequency**: Daily at 2 AM
|
||||
- **Retention**: 7 daily, 4 weekly, 12 monthly
|
||||
- **Method**: `pg_dump` to S3/object storage
|
||||
- **Encryption**: AES-256 encrypted backups
|
||||
|
||||
### Manual Backup
|
||||
```bash
|
||||
# Full backup
|
||||
pg_dump -Fc -f headquarter_backup.dump postgresql://user:pass@host/db
|
||||
|
||||
# Restore
|
||||
pg_restore -d postgresql://user:pass@host/db headquarter_backup.dump
|
||||
```
|
||||
|
||||
## Performance
|
||||
|
||||
### Query Optimization
|
||||
- All foreign keys indexed
|
||||
- Frequently queried columns indexed
|
||||
- Composite indexes for multi-column queries
|
||||
|
||||
### Connection Pooling
|
||||
- SQLAlchemy async pool: 5-20 connections
|
||||
- PgBouncer for production: transaction mode
|
||||
|
||||
## Future Schema Changes
|
||||
|
||||
Planned additions:
|
||||
- [ ] **teams** table - Group users into teams
|
||||
- [ ] **team_memberships** table - Link users to teams
|
||||
- [ ] **tool_instances** table - Running tool containers
|
||||
- [ ] **audit_logs** table - Track important actions
|
||||
- [ ] **notifications** table - User notifications
|
||||
@@ -0,0 +1,357 @@
|
||||
# Deployment Architecture
|
||||
|
||||
## Overview
|
||||
|
||||
Headquarter is designed for containerized deployment using Docker, with support for both development and production environments.
|
||||
|
||||
## System Architecture
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Internet │
|
||||
└──────────────────────┬──────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Traefik (Reverse Proxy) │
|
||||
│ SSL/TLS termination, routing │
|
||||
│ │
|
||||
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
|
||||
│ │ app.domain │ │ api.domain │ │ auth.domain │ │
|
||||
│ │ (Frontend) │ │ (Backend) │ │ (Authentik) │ │
|
||||
│ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │
|
||||
└─────────┼─────────────────┼─────────────────┼──────────────┘
|
||||
│ │ │
|
||||
▼ ▼ ▼
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Docker Host / Server │
|
||||
│ │
|
||||
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
|
||||
│ │ Frontend │ │ API │ │ Authentik │ │
|
||||
│ │ (nginx) │ │ (FastAPI) │ │ (OAuth2) │ │
|
||||
│ │ Port 80 │ │ Port 8000 │ │ Port 9443 │ │
|
||||
│ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │
|
||||
│ │ │ │ │
|
||||
│ └─────────────────┼─────────────────┘ │
|
||||
│ │ │
|
||||
│ ┌────────┴────────┐ │
|
||||
│ │ │ │
|
||||
│ ┌───────▼───────┐ ┌──────▼───────┐ │
|
||||
│ │ PostgreSQL │ │ Redis │ │
|
||||
│ │ Port 5432 │ │ Port 6379 │ │
|
||||
│ └───────────────┘ └──────────────┘ │
|
||||
│ │
|
||||
│ ┌──────────────────────────────────────────────────────┐ │
|
||||
│ │ Volumes │ │
|
||||
│ │ postgres_data │ repo_data │ avatar_uploads │ │
|
||||
│ └──────────────────────────────────────────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Components
|
||||
|
||||
### Frontend (Web)
|
||||
|
||||
- **Technology**: React + Vite + nginx
|
||||
- **Port**: 80 (internal)
|
||||
- **Role**: User interface
|
||||
- **Scaling**: Static files, easily scaled horizontally
|
||||
|
||||
### Backend (API)
|
||||
|
||||
- **Technology**: FastAPI + Python 3.11
|
||||
- **Port**: 8000 (internal)
|
||||
- **Role**: Business logic, API endpoints
|
||||
- **Scaling**: Stateless, can scale horizontally
|
||||
|
||||
### Database (PostgreSQL)
|
||||
|
||||
- **Technology**: PostgreSQL 15
|
||||
- **Port**: 5432 (internal)
|
||||
- **Role**: Persistent data storage
|
||||
- **Scaling**: Vertical or read replicas
|
||||
|
||||
### Cache (Redis)
|
||||
|
||||
- **Technology**: Redis 7
|
||||
- **Port**: 6379 (internal)
|
||||
- **Role**: Session storage, caching
|
||||
- **Scaling**: Redis Cluster for high availability
|
||||
|
||||
### Identity Provider (Authentik)
|
||||
|
||||
- **Technology**: Authentik (self-hosted)
|
||||
- **Port**: 9443 (external), 9000 (internal)
|
||||
- **Role**: OAuth2/OIDC authentication
|
||||
- **Note**: Can be external service
|
||||
|
||||
## Network Architecture
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Networks │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ External Network (traefik) │
|
||||
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
|
||||
│ │ Traefik │ │ Frontend │ │ API │ │
|
||||
│ │ (proxy) │ │ (web) │ │ (api) │ │
|
||||
│ └──────────────┘ └──────────────┘ └──────────────┘ │
|
||||
│ │
|
||||
│ Internal Network (backend) │
|
||||
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
|
||||
│ │ API │ │ PostgreSQL │ │ Redis │ │
|
||||
│ │ (api) │ │ (postgres) │ │ (redis) │ │
|
||||
│ └──────────────┘ └──────────────┘ └──────────────┘ │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**Network Security**:
|
||||
- External network: Exposes services to Traefik
|
||||
- Internal network: Database and cache only accessible by API
|
||||
- No direct database access from external network
|
||||
|
||||
## Data Flow
|
||||
|
||||
### Authentication Flow
|
||||
|
||||
```
|
||||
User → Frontend → API → Authentik
|
||||
↓
|
||||
OAuth2
|
||||
↓
|
||||
User ← Frontend ← API ← Authentik
|
||||
↓
|
||||
Session Cookie
|
||||
```
|
||||
|
||||
### API Request Flow
|
||||
|
||||
```
|
||||
User → Frontend → Traefik → API → Database
|
||||
↓
|
||||
Redis (cache)
|
||||
```
|
||||
|
||||
### Git Operations Flow
|
||||
|
||||
```
|
||||
User → Frontend → API → Git Repository (filesystem)
|
||||
↓
|
||||
Git History/Files
|
||||
```
|
||||
|
||||
## Deployment Patterns
|
||||
|
||||
### Single Server
|
||||
|
||||
All services on one host:
|
||||
- Simple to manage
|
||||
- Suitable for small teams
|
||||
- Single point of failure
|
||||
|
||||
### Multi-Server (HA)
|
||||
|
||||
Separate services across hosts:
|
||||
- Database server
|
||||
- Application servers (API + Frontend)
|
||||
- Load balancer (Traefik)
|
||||
- Higher availability
|
||||
|
||||
### Kubernetes (Future)
|
||||
|
||||
Container orchestration:
|
||||
- Auto-scaling
|
||||
- Self-healing
|
||||
- Rolling updates
|
||||
- Resource management
|
||||
|
||||
## Scaling Strategy
|
||||
|
||||
### Horizontal Scaling
|
||||
|
||||
**Stateless Services** (easy to scale):
|
||||
- Frontend: Multiple nginx instances
|
||||
- API: Multiple FastAPI instances
|
||||
|
||||
**Stateful Services** (require care):
|
||||
- Database: Read replicas, connection pooling
|
||||
- Redis: Cluster mode
|
||||
|
||||
### Vertical Scaling
|
||||
|
||||
Increase resources for:
|
||||
- Database server (CPU, RAM, I/O)
|
||||
- API server (CPU for git operations)
|
||||
|
||||
## Backup Strategy
|
||||
|
||||
### Automated Backups
|
||||
|
||||
```
|
||||
Daily at 2 AM
|
||||
├── PostgreSQL dump
|
||||
├── Repository filesystem
|
||||
├── User uploads (avatars)
|
||||
└── Configuration files
|
||||
```
|
||||
|
||||
### Backup Retention
|
||||
|
||||
- Daily: 7 days
|
||||
- Weekly: 4 weeks
|
||||
- Monthly: 12 months
|
||||
- Yearly: 3 years
|
||||
|
||||
### Disaster Recovery
|
||||
|
||||
1. Restore database from backup
|
||||
2. Restore repositories from backup
|
||||
3. Verify application functionality
|
||||
4. Update DNS if needed
|
||||
|
||||
## Security Considerations
|
||||
|
||||
### Network Security
|
||||
|
||||
- Internal services not exposed externally
|
||||
- Database only accessible from API
|
||||
- Redis only accessible from API
|
||||
- SSL/TLS for all external traffic
|
||||
|
||||
### Data Security
|
||||
|
||||
- Encrypted database connections
|
||||
- Encrypted backups
|
||||
- SSH keys encrypted at rest
|
||||
- Session cookies httpOnly + Secure
|
||||
|
||||
### Access Control
|
||||
|
||||
- OAuth2 authentication
|
||||
- Role-based access (future)
|
||||
- API rate limiting
|
||||
- Audit logging (future)
|
||||
|
||||
## Monitoring
|
||||
|
||||
### Health Checks
|
||||
|
||||
```
|
||||
API: GET /health
|
||||
Database: pg_isready
|
||||
Redis: redis-cli ping
|
||||
```
|
||||
|
||||
### Metrics
|
||||
|
||||
- Request rate and latency
|
||||
- Error rate
|
||||
- Database connections
|
||||
- Disk usage
|
||||
- Memory usage
|
||||
|
||||
### Logging
|
||||
|
||||
- Application logs (structured JSON)
|
||||
- Access logs (Traefik)
|
||||
- Error logs (centralized)
|
||||
- Audit logs (future)
|
||||
|
||||
## Performance Optimization
|
||||
|
||||
### Database
|
||||
|
||||
- Connection pooling (PgBouncer)
|
||||
- Query optimization
|
||||
- Proper indexing
|
||||
- Regular VACUUM
|
||||
|
||||
### API
|
||||
|
||||
- Async operations
|
||||
- Caching (Redis)
|
||||
- Git operation optimization
|
||||
- File streaming
|
||||
|
||||
### Frontend
|
||||
|
||||
- Code splitting
|
||||
- Lazy loading
|
||||
- Asset optimization
|
||||
- CDN (future)
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
**High Memory Usage**:
|
||||
```bash
|
||||
# Check container stats
|
||||
docker stats
|
||||
|
||||
# Restart API if needed
|
||||
docker compose restart api
|
||||
```
|
||||
|
||||
**Database Connection Issues**:
|
||||
```bash
|
||||
# Check PostgreSQL logs
|
||||
docker compose logs postgres
|
||||
|
||||
# Verify connection
|
||||
docker compose exec api pg_isready -h postgres
|
||||
```
|
||||
|
||||
**Git Operations Slow**:
|
||||
```bash
|
||||
# Check disk I/O
|
||||
iostat -x 1
|
||||
|
||||
# Check repository size
|
||||
du -sh /data/repos/*
|
||||
```
|
||||
|
||||
## Migration Strategy
|
||||
|
||||
### Version Updates
|
||||
|
||||
1. Backup data
|
||||
2. Update images
|
||||
3. Run migrations
|
||||
4. Verify functionality
|
||||
5. Rollback if needed
|
||||
|
||||
### Database Migrations
|
||||
|
||||
```bash
|
||||
# Check current version
|
||||
alembic current
|
||||
|
||||
# Upgrade
|
||||
alembic upgrade head
|
||||
|
||||
# Downgrade if needed
|
||||
alembic downgrade -1
|
||||
```
|
||||
|
||||
## Future Architecture
|
||||
|
||||
### Planned Improvements
|
||||
|
||||
- [ ] Kubernetes deployment
|
||||
- [ ] Microservices split
|
||||
- [ ] Event-driven architecture
|
||||
- [ ] Real-time WebSocket updates
|
||||
- [ ] Multi-region deployment
|
||||
- [ ] CDN integration
|
||||
- [ ] Advanced monitoring (Prometheus/Grafana)
|
||||
|
||||
### Scalability Roadmap
|
||||
|
||||
1. **Phase 1**: Single server (current)
|
||||
2. **Phase 2**: Separate database server
|
||||
3. **Phase 3**: Load balanced API servers
|
||||
4. **Phase 4**: Kubernetes cluster
|
||||
5. **Phase 5**: Multi-region
|
||||
@@ -0,0 +1,297 @@
|
||||
# Frontend Architecture
|
||||
|
||||
## Overview
|
||||
|
||||
The Headquarter frontend is a React-based single-page application (SPA) built with modern tooling and designed for modularity and maintainability.
|
||||
|
||||
## Tech Stack
|
||||
|
||||
| Layer | Technology | Version |
|
||||
|-------|-----------|---------|
|
||||
| Framework | React | ^18.2.0 |
|
||||
| Router | React Router | ^6.20.0 |
|
||||
| Bundler | Vite | ^5.0.0 |
|
||||
| Language | TypeScript | ^5.3.0 |
|
||||
| Styling | CSS3 with CSS Variables | - |
|
||||
| Testing | Vitest + React Testing Library | ^4.1.6 |
|
||||
|
||||
## Directory Structure
|
||||
|
||||
```
|
||||
apps/web/src/
|
||||
├── api/ # API clients
|
||||
│ ├── auth.ts # Authentication API
|
||||
│ ├── projects.ts # Project API
|
||||
│ ├── git_repositories.ts # Repository API
|
||||
│ ├── ssh_keys.ts # SSH key API
|
||||
│ ├── tool_types.ts # Tool type API
|
||||
│ ├── users.ts # User API
|
||||
│ └── settings.ts # Settings API
|
||||
├── components/ # Reusable components
|
||||
│ ├── app-shell.tsx # Main app layout
|
||||
│ ├── protected-route.tsx # Auth guard
|
||||
│ └── [more...]
|
||||
├── context/ # React contexts
|
||||
│ └── auth.tsx # Auth state management
|
||||
├── hooks/ # Custom hooks
|
||||
│ ├── use-auth.ts # Auth hook
|
||||
│ └── use-theme.ts # Theme hook
|
||||
├── pages/ # Page components (routes)
|
||||
│ ├── dashboard.tsx # Dashboard
|
||||
│ ├── projects.tsx # Project list
|
||||
│ ├── repo-workspace.tsx # Repository workspace
|
||||
│ ├── git-history.tsx # Git history
|
||||
│ ├── git-repositories.tsx # Repository management
|
||||
│ ├── profile.tsx # User profile
|
||||
│ ├── settings.tsx # User settings
|
||||
│ ├── tool-types.tsx # Tool types
|
||||
│ ├── ssh-keys.tsx # SSH keys
|
||||
│ └── [more...]
|
||||
├── styles/ # Global styles
|
||||
│ ├── index.css # Main stylesheet
|
||||
│ └── [more...]
|
||||
├── types.ts # Shared TypeScript types
|
||||
├── router.tsx # Route definitions
|
||||
└── main.tsx # Entry point
|
||||
```
|
||||
|
||||
## Architecture Patterns
|
||||
|
||||
### 1. Component Architecture
|
||||
|
||||
**Page Components**: Top-level components mapped to routes
|
||||
- Own data fetching
|
||||
- Manage page-level state
|
||||
- Compose reusable components
|
||||
|
||||
**Reusable Components**: Shared UI elements
|
||||
- No data fetching
|
||||
- Receive data via props
|
||||
- Emit events via callbacks
|
||||
|
||||
**Example**:
|
||||
```typescript
|
||||
// Page component
|
||||
const RepoWorkspace = () => {
|
||||
const [files, setFiles] = useState([]);
|
||||
// ... fetch data, manage state
|
||||
return (
|
||||
<div className="workspace">
|
||||
<FileTree files={files} onFileClick={handleFileClick} />
|
||||
<FileViewer content={content} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// Reusable component
|
||||
const FileTree = ({ files, onFileClick }: FileTreeProps) => {
|
||||
return (
|
||||
<ul>
|
||||
{files.map(file => (
|
||||
<li onClick={() => onFileClick(file)}>{file.name}</li>
|
||||
))}
|
||||
</ul>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
### 2. State Management
|
||||
|
||||
**URL State**: Shareable, bookmarkable state
|
||||
```typescript
|
||||
// Sync selections to URL
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
// ?repo=123&branch=main&path=src/main.py
|
||||
```
|
||||
|
||||
**React Context**: Global auth state
|
||||
```typescript
|
||||
// Auth context provides user, login, logout
|
||||
const { user, isAuthenticated } = useAuth();
|
||||
```
|
||||
|
||||
**Local State**: Component-specific state
|
||||
```typescript
|
||||
const [isEditing, setIsEditing] = useState(false);
|
||||
```
|
||||
|
||||
### 3. API Client Pattern
|
||||
|
||||
Centralized API clients with type safety:
|
||||
|
||||
```typescript
|
||||
// api/git_repositories.ts
|
||||
export const getRepositories = async (projectId: string) => {
|
||||
const response = await fetch(`${API_BASE_URL}/projects/${projectId}/repositories`, {
|
||||
credentials: 'include',
|
||||
});
|
||||
return response.json();
|
||||
};
|
||||
|
||||
// Usage in component
|
||||
const repos = await getRepositories(projectId);
|
||||
```
|
||||
|
||||
### 4. Authentication Flow
|
||||
|
||||
```
|
||||
User clicks Login
|
||||
→ Redirect to /auth/login (backend)
|
||||
→ Backend redirects to Authentik OAuth
|
||||
→ User authenticates with Authentik
|
||||
→ Authentik redirects to /auth/callback
|
||||
→ Backend creates session cookie
|
||||
→ Backend redirects to frontend
|
||||
→ Frontend checks /auth/me
|
||||
→ User is authenticated!
|
||||
```
|
||||
|
||||
**Auth State**:
|
||||
```typescript
|
||||
interface AuthState {
|
||||
user: User | null;
|
||||
isAuthenticated: boolean;
|
||||
isLoading: boolean;
|
||||
}
|
||||
```
|
||||
|
||||
### 5. Routing Structure
|
||||
|
||||
```typescript
|
||||
// router.tsx
|
||||
<Route path="/" element={<Dashboard />} />
|
||||
<Route path="/projects" element={<ProjectsPage />} />
|
||||
<Route path="/projects/:projectId" element={<RepoWorkspace />} />
|
||||
<Route path="/projects/:projectId/repositories" element={<GitRepositories />} />
|
||||
<Route path="/projects/:projectId/repositories/:repoId/history" element={<GitHistory />} />
|
||||
<Route path="/profile" element={<ProfilePage />} />
|
||||
<Route path="/settings" element={<SettingsPage />} />
|
||||
<Route path="/ssh-keys" element={<SSHKeysPage />} />
|
||||
<Route path="/tool-types" element={<ToolTypesPage />} />
|
||||
```
|
||||
|
||||
## Data Flow
|
||||
|
||||
### Repository Workspace Example
|
||||
|
||||
```
|
||||
1. User clicks project
|
||||
→ Navigate to /projects/:id
|
||||
|
||||
2. RepoWorkspace mounts
|
||||
→ Fetch project repositories
|
||||
→ Select first repo (or from URL)
|
||||
|
||||
3. Repo selected
|
||||
→ Fetch branches
|
||||
→ Fetch file tree (default branch)
|
||||
|
||||
4. User clicks file
|
||||
→ Fetch file content
|
||||
→ Display in viewer
|
||||
→ Update URL: ?path=src/main.py
|
||||
|
||||
5. User switches branch
|
||||
→ Fetch file tree for branch
|
||||
→ Re-fetch current file if viewing
|
||||
→ Update URL: ?branch=develop
|
||||
```
|
||||
|
||||
## Component Communication
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────┐
|
||||
│ RepoWorkspace │
|
||||
│ ┌──────────┐ ┌──────────────┐ │
|
||||
│ │ FileTree │───▶│ FileViewer │ │
|
||||
│ │ │ │ │ │
|
||||
│ │ onFileClick │ content │ │
|
||||
│ │ │ │ onEdit │ │
|
||||
│ └──────────┘ └──────────────┘ │
|
||||
│ ▲ │
|
||||
│ │ │
|
||||
│ ┌──────────┐ │
|
||||
│ │ Branch │───▶ fetch tree │
|
||||
│ │ Selector │ │
|
||||
│ └──────────┘ │
|
||||
└─────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Styling Strategy
|
||||
|
||||
### CSS Variables (Design Tokens)
|
||||
```css
|
||||
:root {
|
||||
--color-primary: #007bff;
|
||||
--color-bg: #ffffff;
|
||||
--color-text: #333333;
|
||||
--sidebar-width: 250px;
|
||||
--border-radius: 4px;
|
||||
}
|
||||
|
||||
[data-theme="dark"] {
|
||||
--color-bg: #1a1a1a;
|
||||
--color-text: #e0e0e0;
|
||||
}
|
||||
```
|
||||
|
||||
### Component Styles
|
||||
- Each page/component has scoped CSS
|
||||
- Global utilities in `styles/index.css`
|
||||
- No CSS-in-JS library (keep it simple)
|
||||
|
||||
## Testing Strategy
|
||||
|
||||
### Unit Tests (Vitest)
|
||||
```typescript
|
||||
// Component test
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { FileTree } from './file-tree';
|
||||
|
||||
test('renders file list', () => {
|
||||
const files = [{ name: 'test.py', type: 'file' }];
|
||||
render(<FileTree files={files} onFileClick={() => {}} />);
|
||||
expect(screen.getByText('test.py')).toBeInTheDocument();
|
||||
});
|
||||
```
|
||||
|
||||
### Test Coverage
|
||||
- Component rendering
|
||||
- User interactions
|
||||
- Auth state changes
|
||||
- API mocking
|
||||
|
||||
## Performance Considerations
|
||||
|
||||
1. **Code Splitting**: Vite handles automatic chunking
|
||||
2. **Lazy Loading**: React.lazy() for heavy pages
|
||||
3. **Debouncing**: URL updates debounced (300ms)
|
||||
4. **Caching**: Browser caches API responses (ETags)
|
||||
5. **Optimistic UI**: Immediate feedback before API response
|
||||
|
||||
## Future Improvements
|
||||
|
||||
- [ ] Add React Query for server state management
|
||||
- [ ] Implement virtual scrolling for large file trees
|
||||
- [ ] Add service worker for offline support
|
||||
- [ ] Implement real-time updates (WebSocket)
|
||||
- [ ] Add error boundary components
|
||||
|
||||
## Development Workflow
|
||||
|
||||
```bash
|
||||
# Start dev server
|
||||
cd apps/web && npm run dev
|
||||
|
||||
# Run tests
|
||||
npm run test
|
||||
|
||||
# Type check
|
||||
npm run typecheck
|
||||
|
||||
# Lint
|
||||
npm run lint
|
||||
|
||||
# Build for production
|
||||
npm run build
|
||||
```
|
||||
@@ -0,0 +1,201 @@
|
||||
# Authentik OAuth Configuration
|
||||
|
||||
## Overview
|
||||
|
||||
Headquarter uses Authentik as its OAuth2 provider for authentication. This guide covers setting up Authentik and configuring Headquarter to work with it.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Running Authentik instance
|
||||
- Admin access to Authentik
|
||||
- Headquarter deployed and accessible
|
||||
|
||||
## Authentik Setup
|
||||
|
||||
### Step 1: Create Application
|
||||
|
||||
1. Log in to Authentik Admin interface
|
||||
2. Navigate to **Applications** → **Applications**
|
||||
3. Click **Create**
|
||||
4. Fill in:
|
||||
- **Name**: Headquarter
|
||||
- **Slug**: `headquarter-web` (or your preferred slug)
|
||||
- **Provider**: Create new
|
||||
|
||||
### Step 2: Create OAuth Provider
|
||||
|
||||
1. In the provider creation form:
|
||||
- **Name**: Headquarter OAuth
|
||||
- **Authentication flow**: `default-authentication-flow`
|
||||
- **Authorization flow**: `default-provider-authorization-explicit-consent`
|
||||
- **Client type**: Confidential
|
||||
- **Client ID**: Generate or use your own UUID
|
||||
- **Client Secret**: Generate strong secret
|
||||
- **Redirect URIs**: `https://api.yourdomain.com/auth/callback`
|
||||
|
||||
2. **Advanced protocol settings**:
|
||||
- **Signing Key**: Select a signing key (required)
|
||||
- **Access Token validity**: Minutes (default: 5)
|
||||
- **Refresh Token validity**: Days (default: 30)
|
||||
|
||||
3. Save provider
|
||||
|
||||
### Step 3: Configure Application
|
||||
|
||||
1. Return to Application configuration
|
||||
2. Select the created provider
|
||||
3. Save application
|
||||
|
||||
### Step 4: Verify URLs
|
||||
|
||||
Note these URLs from your Authentik instance:
|
||||
- **Authorize URL**: `https://auth.yourdomain.com/application/o/authorize/`
|
||||
- **Token URL**: `https://auth.yourdomain.com/application/o/token/`
|
||||
- **UserInfo URL**: `https://auth.yourdomain.com/application/o/userinfo/`
|
||||
- **JWKS URL**: `https://auth.yourdomain.com/application/o/headquarter-web/jwks/`
|
||||
|
||||
## Headquarter Configuration
|
||||
|
||||
### Environment Variables
|
||||
|
||||
Add to your `.env` file:
|
||||
|
||||
```bash
|
||||
# Authentik Configuration
|
||||
AUTHENTIK_DOMAIN=auth.yourdomain.com
|
||||
AUTHENTIK_CLIENT_ID=your-client-id-uuid
|
||||
AUTHENTIK_CLIENT_SECRET=your-generated-secret
|
||||
AUTHENTIK_APPLICATION_SLUG=headquarter-web
|
||||
AUTHENTIK_AUDIENCE=your-client-id-uuid
|
||||
|
||||
# Optional: Override default URLs if needed
|
||||
# AUTHENTIK_AUTHORIZE_URL=https://auth.yourdomain.com/application/o/authorize/
|
||||
# AUTHENTIK_TOKEN_URL=https://auth.yourdomain.com/application/o/token/
|
||||
# AUTHENTIK_JWKS_URL=https://auth.yourdomain.com/application/o/headquarter-web/jwks/
|
||||
# AUTHENTIK_ISSUER=https://auth.yourdomain.com/application/o/headquarter-web/
|
||||
```
|
||||
|
||||
### Important Notes
|
||||
|
||||
- **AUTHENTIK_CLIENT_ID**: The UUID from Authentik (used for OAuth)
|
||||
- **AUTHENTIK_APPLICATION_SLUG**: The URL-friendly name (e.g., `headquarter-web`)
|
||||
- **AUTHENTIK_AUDIENCE**: Usually same as Client ID
|
||||
|
||||
## User Synchronization
|
||||
|
||||
On first login, Headquarter creates a local user record:
|
||||
|
||||
```python
|
||||
user = User(
|
||||
email="user@example.com",
|
||||
name="User Name",
|
||||
authentik_id="authentik-user-id",
|
||||
)
|
||||
```
|
||||
|
||||
### Synced Fields
|
||||
|
||||
| Authentik Field | Headquarter Field |
|
||||
|----------------|-------------------|
|
||||
| email | email |
|
||||
| name | name |
|
||||
| sub (user ID) | authentik_id |
|
||||
| groups | (future: team membership) |
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Redirect URI Error
|
||||
|
||||
**Problem**: "Redirect URI Error" from Authentik
|
||||
|
||||
**Solution**:
|
||||
1. Check redirect URI in Authentik matches exactly
|
||||
2. Must include protocol: `https://api.yourdomain.com/auth/callback`
|
||||
3. No trailing slash difference
|
||||
|
||||
### Invalid Client
|
||||
|
||||
**Problem**: "invalid_client" error
|
||||
|
||||
**Solution**:
|
||||
1. Verify Client ID matches
|
||||
2. Verify Client Secret is correct
|
||||
3. Check application slug in URLs
|
||||
|
||||
### Missing Refresh Token
|
||||
|
||||
**Problem**: Authentik doesn't return refresh token
|
||||
|
||||
**Solution**:
|
||||
This is normal. Headquarter creates its own session cookies and doesn't need refresh tokens from Authentik.
|
||||
|
||||
### CORS Errors
|
||||
|
||||
**Problem**: CORS errors in browser
|
||||
|
||||
**Solution**:
|
||||
1. Ensure API domain is in CORS origins
|
||||
2. Check `WEB_BASE_URL` environment variable
|
||||
3. Verify cookies have correct domain
|
||||
|
||||
## Security Best Practices
|
||||
|
||||
1. **Use HTTPS** - Never use HTTP in production
|
||||
2. **Strong Client Secret** - Use generated secret, don't reuse
|
||||
3. **Short Token Lifetime** - Keep access tokens short-lived
|
||||
4. **Validate State** - Always verify state parameter
|
||||
5. **Secure Cookies** - Use httpOnly, Secure, SameSite
|
||||
|
||||
## Advanced Configuration
|
||||
|
||||
### Custom Claims
|
||||
|
||||
To add custom claims to the token:
|
||||
|
||||
1. In Authentik, go to **Customization** → **Property Mappings**
|
||||
2. Create new **Scope Mapping**
|
||||
3. Add custom attributes
|
||||
4. Assign to provider
|
||||
|
||||
### Group Mapping
|
||||
|
||||
For team/organization support:
|
||||
|
||||
1. Configure group property mapping in Authentik
|
||||
2. Headquarter will sync groups on login
|
||||
3. Use groups for authorization
|
||||
|
||||
### Multiple Applications
|
||||
|
||||
If running multiple environments:
|
||||
|
||||
1. Create separate applications in Authentik
|
||||
2. Use different client IDs
|
||||
3. Configure environment-specific redirect URIs
|
||||
|
||||
## Testing
|
||||
|
||||
### Manual Test
|
||||
|
||||
1. Visit `https://app.yourdomain.com`
|
||||
2. Click "Login"
|
||||
3. Should redirect to Authentik
|
||||
4. Login with Authentik credentials
|
||||
5. Should redirect back to app, logged in
|
||||
|
||||
### API Test
|
||||
|
||||
```bash
|
||||
# Check auth endpoint
|
||||
curl https://api.yourdomain.com/auth/me
|
||||
# Should return 401 (not authenticated)
|
||||
|
||||
# After login, should return user data
|
||||
curl https://api.yourdomain.com/auth/me --cookie "session=..."
|
||||
```
|
||||
|
||||
## Resources
|
||||
|
||||
- [Authentik Documentation](https://goauthentik.io/docs/)
|
||||
- [OAuth2 Provider Setup](https://goauthentik.io/docs/providers/oauth2/)
|
||||
- [Headquarter Auth Documentation](../features/auth.md)
|
||||
@@ -0,0 +1,266 @@
|
||||
# Docker Deployment Guide
|
||||
|
||||
## Overview
|
||||
|
||||
Headquarter is deployed as a multi-container Docker application using Docker Compose. The stack includes:
|
||||
|
||||
- **API** (FastAPI/Python)
|
||||
- **Web** (React/Vite)
|
||||
- **PostgreSQL** (Database)
|
||||
- **Redis** (Cache)
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- Docker Engine 20.10+
|
||||
- Docker Compose 2.0+
|
||||
- 2GB RAM minimum
|
||||
- 10GB disk space
|
||||
|
||||
### Development Deployment
|
||||
|
||||
```bash
|
||||
# Clone repository
|
||||
git clone https://github.com/your-org/headquarter.git
|
||||
cd headquarter
|
||||
|
||||
# Copy environment file
|
||||
cp .env.example .env
|
||||
# Edit .env with your settings
|
||||
|
||||
# Start all services
|
||||
docker compose up -d
|
||||
|
||||
# Run database migrations
|
||||
docker compose exec api alembic upgrade head
|
||||
|
||||
# Access the app
|
||||
# Frontend: http://localhost:5173
|
||||
# API: http://localhost:8000
|
||||
```
|
||||
|
||||
### Production Deployment
|
||||
|
||||
```bash
|
||||
# Use production compose file
|
||||
cp .env.example .env
|
||||
# Configure production values in .env
|
||||
|
||||
docker compose -f docker-compose.traefik.yml up -d
|
||||
|
||||
# Run migrations
|
||||
docker compose -f docker-compose.traefik.yml exec api alembic upgrade head
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
### Environment Variables
|
||||
|
||||
Required variables:
|
||||
|
||||
```bash
|
||||
# Domains
|
||||
API_DOMAIN=api.yourdomain.com
|
||||
WEB_DOMAIN=app.yourdomain.com
|
||||
|
||||
# Database
|
||||
POSTGRES_USER=headquarter
|
||||
POSTGRES_PASSWORD=secure-password
|
||||
POSTGRES_DB=headquarter
|
||||
|
||||
# Authentik OAuth
|
||||
AUTHENTIK_DOMAIN=auth.yourdomain.com
|
||||
AUTHENTIK_CLIENT_ID=your-client-id
|
||||
AUTHENTIK_CLIENT_SECRET=your-client-secret
|
||||
AUTHENTIK_APPLICATION_SLUG=headquarter
|
||||
|
||||
# Session
|
||||
SESSION_SECRET=your-session-secret-min-32-chars
|
||||
|
||||
# Storage
|
||||
REPO_BASE_PATH=/data/repos
|
||||
```
|
||||
|
||||
### Volume Mounts
|
||||
|
||||
| Volume | Container Path | Purpose |
|
||||
|--------|---------------|---------|
|
||||
| postgres_data | /var/lib/postgresql/data | Database persistence |
|
||||
| repo_data | /data/repos | Git repositories |
|
||||
| avatar_uploads | /app/uploads | User avatars |
|
||||
|
||||
## Docker Compose Files
|
||||
|
||||
### docker-compose.yml (Development)
|
||||
|
||||
Standard development setup with:
|
||||
- Hot reload for API
|
||||
- Vite dev server for frontend
|
||||
- Direct port access
|
||||
- Local PostgreSQL
|
||||
|
||||
### docker-compose.traefik.yml (Production)
|
||||
|
||||
Production setup with:
|
||||
- Traefik reverse proxy
|
||||
- Let's Encrypt SSL
|
||||
- External Traefik network
|
||||
- Optimized builds
|
||||
|
||||
## SSL/TLS
|
||||
|
||||
### Development
|
||||
Self-signed certificates or HTTP only.
|
||||
|
||||
### Production
|
||||
Automatic Let's Encrypt certificates via Traefik.
|
||||
|
||||
```bash
|
||||
# Traefik labels for SSL
|
||||
traefik.http.routers.api.tls=true
|
||||
traefik.http.routers.api.tls.certresolver=letsencrypt
|
||||
```
|
||||
|
||||
## Health Checks
|
||||
|
||||
All services include health checks:
|
||||
|
||||
```yaml
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
```
|
||||
|
||||
## Backup and Restore
|
||||
|
||||
### Database Backup
|
||||
|
||||
```bash
|
||||
# Automated daily backup
|
||||
docker compose exec postgres pg_dump -U headquarter headquarter > backup.sql
|
||||
|
||||
# Restore
|
||||
docker compose exec -T postgres psql -U headquarter < backup.sql
|
||||
```
|
||||
|
||||
### Repository Backup
|
||||
|
||||
```bash
|
||||
# Backup repos volume
|
||||
docker run --rm -v headquarter_repo_data:/data -v $(pwd):/backup alpine tar czf /backup/repos.tar.gz /data
|
||||
|
||||
# Restore
|
||||
docker run --rm -v headquarter_repo_data:/data -v $(pwd):/backup alpine tar xzf /backup/repos.tar.gz -C /
|
||||
```
|
||||
|
||||
## Monitoring
|
||||
|
||||
### Logs
|
||||
|
||||
```bash
|
||||
# All services
|
||||
docker compose logs -f
|
||||
|
||||
# Specific service
|
||||
docker compose logs -f api
|
||||
|
||||
# Last 100 lines
|
||||
docker compose logs --tail=100 api
|
||||
```
|
||||
|
||||
### Resource Usage
|
||||
|
||||
```bash
|
||||
# Container stats
|
||||
docker stats
|
||||
|
||||
# Disk usage
|
||||
docker system df -v
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
**Database connection failed**
|
||||
```bash
|
||||
# Check PostgreSQL is running
|
||||
docker compose ps
|
||||
|
||||
# Check logs
|
||||
docker compose logs postgres
|
||||
|
||||
# Verify credentials in .env match
|
||||
```
|
||||
|
||||
**Migrations failing**
|
||||
```bash
|
||||
# Check current migration version
|
||||
docker compose exec api alembic current
|
||||
|
||||
# Manual upgrade
|
||||
docker compose exec api alembic upgrade head
|
||||
```
|
||||
|
||||
**Permission denied on repos**
|
||||
```bash
|
||||
# Fix permissions
|
||||
docker compose exec api chown -R appuser:appuser /data/repos
|
||||
```
|
||||
|
||||
## Updates
|
||||
|
||||
### Rolling Update
|
||||
|
||||
```bash
|
||||
# Pull latest images
|
||||
docker compose pull
|
||||
|
||||
# Restart with new images
|
||||
docker compose up -d
|
||||
|
||||
# Run migrations if needed
|
||||
docker compose exec api alembic upgrade head
|
||||
```
|
||||
|
||||
### Zero-Downtime Update
|
||||
|
||||
```bash
|
||||
# Scale API to 2 instances
|
||||
docker compose up -d --scale api=2
|
||||
|
||||
# Update one instance at a time
|
||||
# (Requires load balancer configuration)
|
||||
```
|
||||
|
||||
## Security Best Practices
|
||||
|
||||
1. **Use strong passwords** for database and session secret
|
||||
2. **Enable HTTPS** in production
|
||||
3. **Keep images updated** with security patches
|
||||
4. **Use read-only volumes** where possible
|
||||
5. **Limit container capabilities**
|
||||
6. **Use secrets management** for sensitive data
|
||||
|
||||
```yaml
|
||||
# Example security hardening
|
||||
api:
|
||||
read_only: true
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
cap_drop:
|
||||
- ALL
|
||||
cap_add:
|
||||
- CHOWN
|
||||
- SETGID
|
||||
- SETUID
|
||||
```
|
||||
|
||||
## Resources
|
||||
|
||||
- [Docker Documentation](https://docs.docker.com/)
|
||||
- [Docker Compose Reference](https://docs.docker.com/compose/)
|
||||
- [Traefik Documentation](https://doc.traefik.io/traefik/)
|
||||
@@ -0,0 +1,268 @@
|
||||
# Environment Variables Reference
|
||||
|
||||
## Overview
|
||||
|
||||
This document describes all environment variables used by Headquarter. Variables are categorized by component and purpose.
|
||||
|
||||
## Required Variables
|
||||
|
||||
These variables must be set for the application to function:
|
||||
|
||||
### Domains
|
||||
|
||||
| Variable | Default | Description |
|
||||
|----------|---------|-------------|
|
||||
| `API_DOMAIN` | `localhost` | API server domain (e.g., `api.example.com`) |
|
||||
| `WEB_DOMAIN` | `localhost` | Web frontend domain (e.g., `app.example.com`) |
|
||||
|
||||
### Database
|
||||
|
||||
| Variable | Default | Description |
|
||||
|----------|---------|-------------|
|
||||
| `POSTGRES_USER` | `headquarter` | PostgreSQL username |
|
||||
| `POSTGRES_PASSWORD` | `headquarter` | PostgreSQL password (change in production!) |
|
||||
| `POSTGRES_DB` | `headquarter` | PostgreSQL database name |
|
||||
|
||||
### Authentication
|
||||
|
||||
| Variable | Default | Description |
|
||||
|----------|---------|-------------|
|
||||
| `AUTHENTIK_DOMAIN` | `authentik.local` | Authentik server domain |
|
||||
| `AUTHENTIK_CLIENT_ID` | `headquarter-web` | OAuth client ID from Authentik |
|
||||
| `AUTHENTIK_CLIENT_SECRET` | `change-me` | OAuth client secret (change immediately!) |
|
||||
| `AUTHENTIK_APPLICATION_SLUG` | `headquarter-web` | Authentik application slug |
|
||||
| `SESSION_SECRET` | `change-me-session-secret` | Secret for signing session cookies (min 32 chars) |
|
||||
|
||||
## Optional Variables
|
||||
|
||||
### Application
|
||||
|
||||
| Variable | Default | Description |
|
||||
|----------|---------|-------------|
|
||||
| `APP_ENV` | `development` | Environment: `development`, `staging`, `production` |
|
||||
| `LOG_LEVEL` | `INFO` | Logging level: `DEBUG`, `INFO`, `WARNING`, `ERROR` |
|
||||
|
||||
### Database
|
||||
|
||||
| Variable | Default | Description |
|
||||
|----------|---------|-------------|
|
||||
| `DATABASE_URL` | (constructed) | Full PostgreSQL connection URL |
|
||||
| `POSTGRES_HOST` | `postgres` | PostgreSQL hostname |
|
||||
| `POSTGRES_PORT` | `5432` | PostgreSQL port |
|
||||
|
||||
When `DATABASE_URL` is not set, it's constructed from:
|
||||
```
|
||||
postgresql+asyncpg://POSTGRES_USER:POSTGRES_PASSWORD@POSTGRES_HOST:POSTGRES_PORT/POSTGRES_DB
|
||||
```
|
||||
|
||||
### URLs
|
||||
|
||||
| Variable | Default | Description |
|
||||
|----------|---------|-------------|
|
||||
| `API_PUBLIC_URL` | (constructed) | Public API URL (e.g., `https://api.example.com`) |
|
||||
| `WEB_PUBLIC_URL` | (constructed) | Public web URL (e.g., `https://app.example.com`) |
|
||||
|
||||
Constructed from domains when not set:
|
||||
```
|
||||
API: https://API_DOMAIN (production) or http://API_DOMAIN:8000 (development)
|
||||
WEB: https://WEB_DOMAIN (production) or http://WEB_DOMAIN:5173 (development)
|
||||
```
|
||||
|
||||
### Authentik Overrides
|
||||
|
||||
| Variable | Default | Description |
|
||||
|----------|---------|-------------|
|
||||
| `AUTHENTIK_AUTHORIZE_URL` | (constructed) | OAuth authorization endpoint |
|
||||
| `AUTHENTIK_TOKEN_URL` | (constructed) | OAuth token endpoint |
|
||||
| `AUTHENTIK_JWKS_URL` | (constructed) | JWKS endpoint |
|
||||
| `AUTHENTIK_ISSUER` | (constructed) | OAuth issuer URL |
|
||||
| `AUTHENTIK_AUDIENCE` | `headquarter-web` | Token audience |
|
||||
|
||||
Constructed URLs:
|
||||
```
|
||||
https://AUTHENTIK_DOMAIN/application/o/authorize/
|
||||
https://AUTHENTIK_DOMAIN/application/o/token/
|
||||
https://AUTHENTIK_DOMAIN/application/o/AUTHENTIK_APPLICATION_SLUG/jwks/
|
||||
https://AUTHENTIK_DOMAIN/application/o/AUTHENTIK_APPLICATION_SLUG/
|
||||
```
|
||||
|
||||
### Session
|
||||
|
||||
| Variable | Default | Description |
|
||||
|----------|---------|-------------|
|
||||
| `SESSION_TTL_HOURS` | `24` | Session cookie lifetime in hours |
|
||||
| `COOKIE_DOMAIN` | (none) | Cookie domain (set for cross-subdomain) |
|
||||
| `COOKIE_SECURE` | `true` (prod) | Secure cookie flag |
|
||||
| `COOKIE_SAMESITE` | `lax` | SameSite cookie attribute |
|
||||
|
||||
### Storage
|
||||
|
||||
| Variable | Default | Description |
|
||||
|----------|---------|-------------|
|
||||
| `REPO_BASE_PATH` | `/data/repos` | Base path for git repositories |
|
||||
| `UPLOAD_DIR` | `uploads` | Directory for file uploads |
|
||||
|
||||
### Development
|
||||
|
||||
| Variable | Default | Description |
|
||||
|----------|---------|-------------|
|
||||
| `VITE_API_BASE_URL` | `http://localhost:8000` | Frontend API URL |
|
||||
|
||||
## Docker Compose Variables
|
||||
|
||||
### Traefik
|
||||
|
||||
| Variable | Default | Description |
|
||||
|----------|---------|-------------|
|
||||
| `TRAEFIK_NETWORK` | `traefik` | Docker network name for Traefik |
|
||||
| `TRAEFIK_CERT_RESOLVER` | `letsencrypt` | Certificate resolver name |
|
||||
|
||||
### Docker Specific
|
||||
|
||||
| Variable | Default | Description |
|
||||
|----------|---------|-------------|
|
||||
| `COMPOSE_PROJECT_NAME` | `headquarter` | Docker Compose project name |
|
||||
|
||||
## Configuration Examples
|
||||
|
||||
### Development
|
||||
|
||||
```bash
|
||||
APP_ENV=development
|
||||
API_DOMAIN=localhost
|
||||
WEB_DOMAIN=localhost
|
||||
DATABASE_URL=postgresql+asyncpg://headquarter:headquarter@localhost:5432/headquarter
|
||||
AUTHENTIK_DOMAIN=authentik.local
|
||||
AUTHENTIK_CLIENT_ID=headquarter-web
|
||||
AUTHENTIK_CLIENT_SECRET=dev-secret
|
||||
SESSION_SECRET=dev-session-secret-change-in-production
|
||||
VITE_API_BASE_URL=http://localhost:8000
|
||||
```
|
||||
|
||||
### Production
|
||||
|
||||
```bash
|
||||
APP_ENV=production
|
||||
API_DOMAIN=api.headquarter.example.com
|
||||
WEB_DOMAIN=app.headquarter.example.com
|
||||
POSTGRES_PASSWORD=very-secure-password-here
|
||||
AUTHENTIK_DOMAIN=auth.example.com
|
||||
AUTHENTIK_CLIENT_ID=your-uuid-from-authentik
|
||||
AUTHENTIK_CLIENT_SECRET=your-secret-from-authentik
|
||||
AUTHENTIK_APPLICATION_SLUG=headquarter-web
|
||||
SESSION_SECRET=minimum-32-characters-long-secret-key
|
||||
SESSION_TTL_HOURS=24
|
||||
COOKIE_DOMAIN=.headquarter.example.com
|
||||
TRAEFIK_NETWORK=web
|
||||
TRAEFIK_CERT_RESOLVER=letsencrypt
|
||||
REPO_BASE_PATH=/data/repos
|
||||
```
|
||||
|
||||
### Testing
|
||||
|
||||
```bash
|
||||
APP_ENV=testing
|
||||
DATABASE_URL=postgresql+asyncpg://headquarter:headquarter@localhost:5432/headquarter_test
|
||||
SESSION_SECRET=test-secret
|
||||
AUTHENTIK_CLIENT_ID=test-client
|
||||
AUTHENTIK_CLIENT_SECRET=test-secret
|
||||
```
|
||||
|
||||
## Security Checklist
|
||||
|
||||
Before deploying to production, verify:
|
||||
|
||||
- [ ] `POSTGRES_PASSWORD` is strong and unique
|
||||
- [ ] `AUTHENTIK_CLIENT_SECRET` is kept secret
|
||||
- [ ] `SESSION_SECRET` is at least 32 characters
|
||||
- [ ] `APP_ENV` is set to `production`
|
||||
- [ ] `COOKIE_SECURE` is enabled
|
||||
- [ ] `COOKIE_DOMAIN` is set for your domain
|
||||
- [ ] No default secrets in production
|
||||
- [ ] `.env` file is not committed to git
|
||||
- [ ] `.env` file has restricted permissions (600)
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Variable Not Set
|
||||
|
||||
```bash
|
||||
# Error: "Environment variable not set"
|
||||
# Solution: Export the variable or add to .env file
|
||||
export SESSION_SECRET="your-secret-here"
|
||||
```
|
||||
|
||||
### Invalid URL
|
||||
|
||||
```bash
|
||||
# Error: "Invalid URL"
|
||||
# Solution: Check domain variables don't include protocol
|
||||
# Bad: API_DOMAIN=https://api.example.com
|
||||
# Good: API_DOMAIN=api.example.com
|
||||
```
|
||||
|
||||
### Database Connection Failed
|
||||
|
||||
```bash
|
||||
# Check DATABASE_URL or individual components
|
||||
# Verify PostgreSQL is running
|
||||
# Check credentials match
|
||||
```
|
||||
|
||||
## Migration from Old Config
|
||||
|
||||
If upgrading from older versions:
|
||||
|
||||
1. `JWT_SECRET` → Removed (not needed with session auth)
|
||||
2. `ACCESS_TOKEN_TTL_MINUTES` → Removed
|
||||
3. `REFRESH_TOKEN_TTL_DAYS` → Removed
|
||||
4. `AUTHENTIK_AUDIENCE` → Now defaults to `AUTHENTIK_CLIENT_ID`
|
||||
5. `AUTHENTIK_APPLICATION_SLUG` → New variable for URL construction
|
||||
|
||||
## Environment Files
|
||||
|
||||
### Files Structure
|
||||
|
||||
```
|
||||
headquarter/
|
||||
├── .env # Main environment (not committed)
|
||||
├── .env.example # Example/template
|
||||
├── apps/
|
||||
│ ├── api/
|
||||
│ │ └── .env # API-specific overrides
|
||||
│ └── web/
|
||||
│ └── .env # Frontend-specific overrides
|
||||
└── docker-compose.traefik.yml # References .env
|
||||
```
|
||||
|
||||
### Loading Order
|
||||
|
||||
1. System environment variables
|
||||
2. `.env` file in project root
|
||||
3. Component-specific `.env` files
|
||||
4. Default values in code
|
||||
|
||||
Later values override earlier ones.
|
||||
|
||||
## Validation
|
||||
|
||||
The application validates required variables on startup:
|
||||
|
||||
```python
|
||||
# Missing critical variable
|
||||
if not settings.session_secret or settings.session_secret == "change-me":
|
||||
logger.warning("SESSION_SECRET not configured properly!")
|
||||
|
||||
# Invalid configuration
|
||||
if settings.app_env == "production" and "localhost" in settings.api_domain:
|
||||
logger.warning("Using localhost in production!")
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Never commit `.env` files**
|
||||
2. **Use strong passwords** for database and secrets
|
||||
3. **Rotate secrets** regularly
|
||||
4. **Use different secrets** per environment
|
||||
5. **Document custom variables** in this file
|
||||
6. **Validate configuration** before deployment
|
||||
@@ -0,0 +1,305 @@
|
||||
# Traefik Reverse Proxy Setup
|
||||
|
||||
## Overview
|
||||
|
||||
Traefik serves as the reverse proxy and load balancer for Headquarter in production, handling:
|
||||
- SSL/TLS termination
|
||||
- Automatic HTTPS via Let's Encrypt
|
||||
- Route-based traffic distribution
|
||||
- WebSocket support
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
Internet
|
||||
↓
|
||||
Traefik (443)
|
||||
├───▶ api.yourdomain.com → Headquarter API (8000)
|
||||
└───▶ app.yourdomain.com → Headquarter Web (80)
|
||||
```
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Docker Compose installed
|
||||
- DNS records pointing to your server:
|
||||
- `api.yourdomain.com` → Server IP
|
||||
- `app.yourdomain.com` → Server IP
|
||||
- Ports 80 and 443 open in firewall
|
||||
|
||||
## Configuration
|
||||
|
||||
### 1. Environment Variables
|
||||
|
||||
```bash
|
||||
# .env file
|
||||
API_DOMAIN=api.headquarter.example.com
|
||||
WEB_DOMAIN=app.headquarter.example.com
|
||||
|
||||
# Traefik network (shared with other Traefik instances)
|
||||
TRAEFIK_NETWORK=web
|
||||
|
||||
# SSL Certificate resolver
|
||||
TRAEFIK_CERT_RESOLVER=letsencrypt
|
||||
```
|
||||
|
||||
### 2. Traefik Labels
|
||||
|
||||
Services are configured via Docker labels:
|
||||
|
||||
```yaml
|
||||
# API Service labels
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.headquarter-api.rule=Host(`api.headquarter.example.com`)"
|
||||
- "traefik.http.routers.headquarter-api.entrypoints=websecure"
|
||||
- "traefik.http.routers.headquarter-api.tls=true"
|
||||
- "traefik.http.routers.headquarter-api.tls.certresolver=${TRAEFIK_CERT_RESOLVER:-letsencrypt}"
|
||||
- "traefik.http.services.headquarter-api.loadbalancer.server.port=8000"
|
||||
|
||||
# Web Service labels
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.headquarter-frontend.rule=Host(`app.headquarter.example.com`)"
|
||||
- "traefik.http.routers.headquarter-frontend.entrypoints=websecure"
|
||||
- "traefik.http.routers.headquarter-frontend.tls=true"
|
||||
- "traefik.http.routers.headquarter-frontend.tls.certresolver=${TRAEFIK_CERT_RESOLVER:-letsencrypt}"
|
||||
- "traefik.http.services.headquarter-frontend.loadbalancer.server.port=80"
|
||||
```
|
||||
|
||||
### 3. External Network
|
||||
|
||||
Connect to existing Traefik instance:
|
||||
|
||||
```yaml
|
||||
networks:
|
||||
traefik:
|
||||
external: true
|
||||
name: ${TRAEFIK_NETWORK:-traefik}
|
||||
```
|
||||
|
||||
## Deployment Steps
|
||||
|
||||
### Step 1: Verify DNS
|
||||
|
||||
Ensure DNS records resolve to your server:
|
||||
|
||||
```bash
|
||||
nslookup api.headquarter.example.com
|
||||
nslookup app.headquarter.example.com
|
||||
```
|
||||
|
||||
### Step 2: Start Services
|
||||
|
||||
```bash
|
||||
# Start with Traefik compose file
|
||||
docker compose -f docker-compose.traefik.yml up -d
|
||||
|
||||
# Verify containers are running
|
||||
docker compose -f docker-compose.traefik.yml ps
|
||||
```
|
||||
|
||||
### Step 3: Check SSL Certificates
|
||||
|
||||
```bash
|
||||
# View Traefik logs
|
||||
docker compose -f docker-compose.traefik.yml logs -f
|
||||
|
||||
# Check certificate status
|
||||
curl -v https://api.headquarter.example.com/health
|
||||
```
|
||||
|
||||
### Step 4: Run Migrations
|
||||
|
||||
```bash
|
||||
docker compose -f docker-compose.traefik.yml exec api alembic upgrade head
|
||||
```
|
||||
|
||||
## SSL Configuration
|
||||
|
||||
### Let's Encrypt (Default)
|
||||
|
||||
Automatic certificate generation and renewal:
|
||||
|
||||
```yaml
|
||||
labels:
|
||||
- "traefik.http.routers.headquarter-api.tls.certresolver=letsencrypt"
|
||||
```
|
||||
|
||||
### Custom Certificates
|
||||
|
||||
For custom or wildcard certificates:
|
||||
|
||||
```yaml
|
||||
labels:
|
||||
- "traefik.http.routers.headquarter-api.tls=true"
|
||||
- "traefik.http.routers.headquarter-api.tls.certresolver=myresolver"
|
||||
```
|
||||
|
||||
### Self-Signed (Development)
|
||||
|
||||
```yaml
|
||||
labels:
|
||||
- "traefik.http.routers.headquarter-api.tls=true"
|
||||
- "traefik.http.routers.headquarter-api.tls.certresolver=selfsigned"
|
||||
```
|
||||
|
||||
## Advanced Configuration
|
||||
|
||||
### Rate Limiting
|
||||
|
||||
```yaml
|
||||
labels:
|
||||
- "traefik.http.middlewares.ratelimit.ratelimit.average=100"
|
||||
- "traefik.http.routers.headquarter-api.middlewares=ratelimit"
|
||||
```
|
||||
|
||||
### Basic Auth (for staging)
|
||||
|
||||
```yaml
|
||||
labels:
|
||||
- "traefik.http.middlewares.auth.basicauth.users=admin:$$apr1$$H6uskkkW$$IgXLP6ewTrSuBkTrqE8wj/"
|
||||
- "traefik.http.routers.headquarter-api.middlewares=auth"
|
||||
```
|
||||
|
||||
### CORS Headers
|
||||
|
||||
```yaml
|
||||
labels:
|
||||
- "traefik.http.middlewares.cors.headers.accesscontrolalloworiginlist=*"
|
||||
- "traefik.http.routers.headquarter-api.middlewares=cors"
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Certificate Issues
|
||||
|
||||
**Problem**: Certificate not generated
|
||||
```bash
|
||||
# Check Traefik logs
|
||||
docker compose logs traefik
|
||||
|
||||
# Verify DNS resolution
|
||||
nslookup your-domain.com
|
||||
|
||||
# Check port 80 is accessible (required for HTTP challenge)
|
||||
curl -I http://your-domain.com
|
||||
```
|
||||
|
||||
**Problem**: Certificate expired
|
||||
```bash
|
||||
# Force renewal
|
||||
docker compose restart traefik
|
||||
|
||||
# Or delete acme.json and restart
|
||||
rm acme.json
|
||||
docker compose restart traefik
|
||||
```
|
||||
|
||||
### Routing Issues
|
||||
|
||||
**Problem**: 404 errors
|
||||
```bash
|
||||
# Check Traefik dashboard (if enabled)
|
||||
# http://traefik.yourdomain.com
|
||||
|
||||
# Verify labels are correct
|
||||
docker compose -f docker-compose.traefik.yml config
|
||||
|
||||
# Check container is on correct network
|
||||
docker network inspect ${TRAEFIK_NETWORK:-traefik}
|
||||
```
|
||||
|
||||
**Problem**: Services not detected
|
||||
```bash
|
||||
# Verify traefik.enable label
|
||||
docker compose -f docker-compose.traefik.yml exec api labels
|
||||
|
||||
# Check Docker provider in Traefik
|
||||
docker compose logs traefik | grep "Provider connection established"
|
||||
```
|
||||
|
||||
## Maintenance
|
||||
|
||||
### Update Traefik
|
||||
|
||||
```bash
|
||||
# Pull latest Traefik image
|
||||
docker compose -f docker-compose.traefik.yml pull traefik
|
||||
|
||||
# Restart
|
||||
docker compose -f docker-compose.traefik.yml up -d traefik
|
||||
```
|
||||
|
||||
### View Dashboard
|
||||
|
||||
Enable Traefik dashboard (secure it in production):
|
||||
|
||||
```yaml
|
||||
# traefik.yml
|
||||
dashboard:
|
||||
enabled: true
|
||||
|
||||
# Add to docker-compose.traefik.yml
|
||||
labels:
|
||||
- "traefik.http.routers.dashboard.rule=Host(`traefik.yourdomain.com`)"
|
||||
- "traefik.http.routers.dashboard.tls=true"
|
||||
- "traefik.http.routers.dashboard.tls.certresolver=letsencrypt"
|
||||
```
|
||||
|
||||
## Security Considerations
|
||||
|
||||
1. **Always use HTTPS** in production (redirect HTTP to HTTPS)
|
||||
2. **Secure Traefik dashboard** with authentication
|
||||
3. **Use strong certificate resolver** (Let's Encrypt production)
|
||||
4. **Keep Traefik updated** for security patches
|
||||
5. **Restrict Docker socket access** if using Docker provider
|
||||
|
||||
## Example Complete Configuration
|
||||
|
||||
```yaml
|
||||
# docker-compose.traefik.yml
|
||||
services:
|
||||
api:
|
||||
build:
|
||||
context: ./apps/api
|
||||
environment:
|
||||
API_DOMAIN: ${API_DOMAIN}
|
||||
WEB_DOMAIN: ${WEB_DOMAIN}
|
||||
# ... other env vars
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.headquarter-api.rule=Host(`${API_DOMAIN}`)"
|
||||
- "traefik.http.routers.headquarter-api.entrypoints=websecure"
|
||||
- "traefik.http.routers.headquarter-api.tls=true"
|
||||
- "traefik.http.routers.headquarter-api.tls.certresolver=${TRAEFIK_CERT_RESOLVER:-letsencrypt}"
|
||||
- "traefik.http.services.headquarter-api.loadbalancer.server.port=8000"
|
||||
networks:
|
||||
- backend
|
||||
- traefik
|
||||
|
||||
web:
|
||||
build:
|
||||
context: ./apps/web
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.headquarter-frontend.rule=Host(`${WEB_DOMAIN}`)"
|
||||
- "traefik.http.routers.headquarter-frontend.entrypoints=websecure"
|
||||
- "traefik.http.routers.headquarter-frontend.tls=true"
|
||||
- "traefik.http.routers.headquarter-frontend.tls.certresolver=${TRAEFIK_CERT_RESOLVER:-letsencrypt}"
|
||||
- "traefik.http.services.headquarter-frontend.loadbalancer.server.port=80"
|
||||
networks:
|
||||
- traefik
|
||||
|
||||
networks:
|
||||
traefik:
|
||||
external: true
|
||||
name: ${TRAEFIK_NETWORK:-traefik}
|
||||
backend:
|
||||
internal: true
|
||||
```
|
||||
|
||||
## Resources
|
||||
|
||||
- [Traefik Documentation](https://doc.traefik.io/traefik/)
|
||||
- [Docker Compose Integration](https://doc.traefik.io/traefik/providers/docker/)
|
||||
- [Let's Encrypt Configuration](https://doc.traefik.io/traefik/https/acme/)
|
||||
@@ -0,0 +1,261 @@
|
||||
# Contributing Guide
|
||||
|
||||
## Welcome
|
||||
|
||||
Thank you for your interest in contributing to Headquarter! This document provides guidelines and workflows for contributing.
|
||||
|
||||
## Getting Started
|
||||
|
||||
1. Fork the repository
|
||||
2. Clone your fork: `git clone https://github.com/your-username/headquarter.git`
|
||||
3. Set up development environment (see [Setup Guide](./setup.md))
|
||||
4. Create a branch: `git checkout -b feature/your-feature`
|
||||
|
||||
## Development Workflow
|
||||
|
||||
### 1. Find or Create an Issue
|
||||
|
||||
- Check existing issues for something to work on
|
||||
- Create an issue to discuss new features before implementing
|
||||
- Comment on issues to claim them
|
||||
|
||||
### 2. Create a Branch
|
||||
|
||||
```bash
|
||||
# Feature branch
|
||||
git checkout -b feature/description
|
||||
|
||||
# Bug fix branch
|
||||
git checkout -b fix/description
|
||||
|
||||
# Documentation branch
|
||||
git checkout -b docs/description
|
||||
```
|
||||
|
||||
### 3. Make Changes
|
||||
|
||||
- Write clear, concise code
|
||||
- Follow existing patterns and conventions
|
||||
- Add tests for new functionality
|
||||
- Update documentation as needed
|
||||
|
||||
### 4. Run Quality Gates
|
||||
|
||||
```bash
|
||||
# Backend
|
||||
cd apps/api
|
||||
ruff check src/ tests/
|
||||
mypy src/
|
||||
pytest
|
||||
|
||||
# Frontend
|
||||
cd apps/web
|
||||
npm run lint
|
||||
npm run typecheck
|
||||
npm run test
|
||||
npm run build
|
||||
```
|
||||
|
||||
### 5. Commit Changes
|
||||
|
||||
We use conventional commits:
|
||||
|
||||
```bash
|
||||
# Format: type(scope): description
|
||||
|
||||
# Examples:
|
||||
git commit -m "feat(auth): add OAuth2 login"
|
||||
git commit -m "fix(api): handle missing user gracefully"
|
||||
git commit -m "docs(readme): update installation instructions"
|
||||
git commit -m "test(git): add URL parsing tests"
|
||||
git commit -m "refactor(models): extract base repository"
|
||||
```
|
||||
|
||||
**Types**:
|
||||
- `feat`: New feature
|
||||
- `fix`: Bug fix
|
||||
- `docs`: Documentation
|
||||
- `style`: Formatting (no code change)
|
||||
- `refactor`: Code restructuring
|
||||
- `test`: Adding tests
|
||||
- `chore`: Maintenance tasks
|
||||
|
||||
### 6. Push and Create Pull Request
|
||||
|
||||
```bash
|
||||
git push origin feature/description
|
||||
```
|
||||
|
||||
**PR Description should include**:
|
||||
- What changed and why
|
||||
- How to test
|
||||
- Screenshots (for UI changes)
|
||||
- Link to related issue
|
||||
|
||||
## Code Standards
|
||||
|
||||
### Python (Backend)
|
||||
|
||||
**Style**: Follow PEP 8 and project conventions
|
||||
|
||||
```python
|
||||
# Function naming: snake_case
|
||||
def get_user_by_id(user_id: str) -> User | None:
|
||||
pass
|
||||
|
||||
# Class naming: PascalCase
|
||||
class GitRepositoryService:
|
||||
pass
|
||||
|
||||
# Constants: UPPER_SNAKE_CASE
|
||||
MAX_FILE_SIZE = 1024 * 1024 # 1MB
|
||||
|
||||
# Type hints required
|
||||
def process_data(data: dict[str, Any]) -> ProcessedResult:
|
||||
pass
|
||||
```
|
||||
|
||||
**Docstrings**: Google style
|
||||
```python
|
||||
def extract_base_repo_url(url: str) -> str | None:
|
||||
"""Extract base repository URL from a browser URL.
|
||||
|
||||
Args:
|
||||
url: The URL to parse, may be a browser URL or git URL.
|
||||
|
||||
Returns:
|
||||
The base repository URL with .git suffix, or None if parsing fails.
|
||||
|
||||
Examples:
|
||||
>>> extract_base_repo_url("https://github.com/user/repo/tree/main")
|
||||
'https://github.com/user/repo.git'
|
||||
|
||||
>>> extract_base_repo_url("https://github.com/user/repo.git")
|
||||
'https://github.com/user/repo.git'
|
||||
"""
|
||||
pass
|
||||
```
|
||||
|
||||
### TypeScript (Frontend)
|
||||
|
||||
**Style**: Follow existing patterns
|
||||
|
||||
```typescript
|
||||
// Interface naming: PascalCase
|
||||
interface User {
|
||||
id: string;
|
||||
email: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
// Function naming: camelCase
|
||||
function getUserById(userId: string): Promise<User> {
|
||||
return api.get(`/users/${userId}`);
|
||||
}
|
||||
|
||||
// Component naming: PascalCase
|
||||
const UserProfile: React.FC<UserProfileProps> = ({ user }) => {
|
||||
return <div>{user.name}</div>;
|
||||
};
|
||||
```
|
||||
|
||||
## Testing Requirements
|
||||
|
||||
### New Features
|
||||
|
||||
- Unit tests for business logic
|
||||
- Integration tests for API endpoints
|
||||
- Component tests for UI components
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Regression test that would catch the bug
|
||||
- Verify fix with the test
|
||||
|
||||
### Example
|
||||
|
||||
```python
|
||||
# Backend test
|
||||
def test_extract_github_browser_url():
|
||||
url = "https://github.com/user/repo/tree/main"
|
||||
result = extract_base_repo_url(url)
|
||||
assert result == "https://github.com/user/repo.git"
|
||||
|
||||
# Frontend test
|
||||
test('shows file tree', () => {
|
||||
render(<FileTree files={mockFiles} onFileClick={() => {}} />);
|
||||
expect(screen.getByText('src')).toBeInTheDocument();
|
||||
});
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
||||
Update documentation when:
|
||||
- Adding new features
|
||||
- Changing API endpoints
|
||||
- Modifying configuration
|
||||
- Adding environment variables
|
||||
|
||||
**Documentation locations**:
|
||||
- `README.md` - Project overview
|
||||
- `docs/features/` - Feature documentation
|
||||
- `docs/api/` - API documentation
|
||||
- `docs/deployment/` - Deployment guides
|
||||
|
||||
## Review Process
|
||||
|
||||
1. **Automated checks** must pass (CI/CD)
|
||||
2. **Code review** by at least one maintainer
|
||||
3. **Approval** required before merge
|
||||
4. **Squash merge** to keep history clean
|
||||
|
||||
### Review Checklist
|
||||
|
||||
**For Authors**:
|
||||
- [ ] Tests pass locally
|
||||
- [ ] Quality gates pass
|
||||
- [ ] Documentation updated
|
||||
- [ ] PR description is clear
|
||||
|
||||
**For Reviewers**:
|
||||
- [ ] Code makes sense
|
||||
- [ ] Tests cover changes
|
||||
- [ ] No security issues
|
||||
- [ ] Follows conventions
|
||||
|
||||
## Release Process
|
||||
|
||||
1. Update version in `pyproject.toml` and `package.json`
|
||||
2. Update `CHANGELOG.md`
|
||||
3. Create git tag: `git tag v1.2.3`
|
||||
4. Push tag: `git push origin v1.2.3`
|
||||
5. Create GitHub release with notes
|
||||
|
||||
## Community
|
||||
|
||||
### Communication Channels
|
||||
|
||||
- GitHub Issues: Bug reports and feature requests
|
||||
- GitHub Discussions: Questions and ideas
|
||||
- Pull Requests: Code contributions
|
||||
|
||||
### Code of Conduct
|
||||
|
||||
- Be respectful and inclusive
|
||||
- Welcome newcomers
|
||||
- Focus on constructive feedback
|
||||
- Respect different viewpoints
|
||||
|
||||
## Questions?
|
||||
|
||||
- Check existing documentation
|
||||
- Search closed issues
|
||||
- Ask in GitHub Discussions
|
||||
- Join community chat (if available)
|
||||
|
||||
## Resources
|
||||
|
||||
- [Development Setup](./setup.md)
|
||||
- [Testing Guide](./testing.md)
|
||||
- [Quality Gates](./quality-gates.md)
|
||||
- [Project README](../../README.md)
|
||||
@@ -0,0 +1,365 @@
|
||||
# Quality Gates
|
||||
|
||||
## Overview
|
||||
|
||||
All code changes must pass quality gates before being merged. These gates ensure code consistency, type safety, and prevent common issues.
|
||||
|
||||
## Backend Quality Gates
|
||||
|
||||
### 1. Code Formatting (ruff)
|
||||
|
||||
**Purpose**: Enforce consistent code style
|
||||
|
||||
```bash
|
||||
cd apps/api
|
||||
ruff check src/ tests/
|
||||
ruff format src/ tests/
|
||||
```
|
||||
|
||||
**Configuration** (`pyproject.toml`):
|
||||
```toml
|
||||
[tool.ruff]
|
||||
line-length = 100
|
||||
target-version = "py311"
|
||||
select = ["E", "F", "I", "W", "UP"]
|
||||
ignore = ["E501"]
|
||||
|
||||
[tool.ruff.lint.pydocstyle]
|
||||
convention = "google"
|
||||
```
|
||||
|
||||
**Pre-commit hook**:
|
||||
```bash
|
||||
# Install pre-commit
|
||||
pip install pre-commit
|
||||
pre-commit install
|
||||
|
||||
# Run manually
|
||||
pre-commit run --all-files
|
||||
```
|
||||
|
||||
### 2. Type Checking (mypy)
|
||||
|
||||
**Purpose**: Catch type errors before runtime
|
||||
|
||||
```bash
|
||||
cd apps/api
|
||||
mypy src/
|
||||
```
|
||||
|
||||
**Configuration** (`pyproject.toml`):
|
||||
```toml
|
||||
[tool.mypy]
|
||||
python_version = "3.11"
|
||||
warn_return_any = true
|
||||
warn_unused_ignores = true
|
||||
disallow_untyped_defs = true
|
||||
disallow_incomplete_defs = true
|
||||
check_untyped_defs = true
|
||||
```
|
||||
|
||||
**Common issues**:
|
||||
- Missing type hints on function parameters
|
||||
- Returning wrong type
|
||||
- None checks needed
|
||||
|
||||
### 3. Unit Tests (pytest)
|
||||
|
||||
**Purpose**: Verify functionality works as expected
|
||||
|
||||
```bash
|
||||
cd apps/api
|
||||
pytest -v
|
||||
```
|
||||
|
||||
**Requirements**:
|
||||
- All tests must pass
|
||||
- New code should have tests
|
||||
- Coverage should not decrease
|
||||
|
||||
### 4. Security Checks
|
||||
|
||||
**Bandit** (security linter):
|
||||
```bash
|
||||
bandit -r src/
|
||||
```
|
||||
|
||||
**Safety** (dependency vulnerabilities):
|
||||
```bash
|
||||
safety check
|
||||
```
|
||||
|
||||
## Frontend Quality Gates
|
||||
|
||||
### 1. Type Checking (TypeScript)
|
||||
|
||||
**Purpose**: Catch type errors at build time
|
||||
|
||||
```bash
|
||||
cd apps/web
|
||||
npm run typecheck
|
||||
```
|
||||
|
||||
**Configuration** (`tsconfig.json`):
|
||||
```json
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
||||
"module": "ESNext",
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noFallthroughCasesInSwitch": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Linting (ESLint)
|
||||
|
||||
**Purpose**: Enforce code style and catch issues
|
||||
|
||||
```bash
|
||||
cd apps/web
|
||||
npm run lint
|
||||
npm run lint:fix
|
||||
```
|
||||
|
||||
**Configuration** (`.eslintrc.cjs`):
|
||||
```javascript
|
||||
module.exports = {
|
||||
root: true,
|
||||
env: { browser: true, es2020: true },
|
||||
extends: [
|
||||
'eslint:recommended',
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
'plugin:react-hooks/recommended',
|
||||
],
|
||||
parser: '@typescript-eslint/parser',
|
||||
plugins: ['react-refresh'],
|
||||
rules: {
|
||||
'react-refresh/only-export-components': [
|
||||
'warn',
|
||||
{ allowConstantExport: true },
|
||||
],
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Build Check
|
||||
|
||||
**Purpose**: Ensure production build succeeds
|
||||
|
||||
```bash
|
||||
cd apps/web
|
||||
npm run build
|
||||
```
|
||||
|
||||
**Requirements**:
|
||||
- No TypeScript errors
|
||||
- No build warnings
|
||||
- Bundle size reasonable
|
||||
|
||||
### 4. Unit Tests (Vitest)
|
||||
|
||||
```bash
|
||||
cd apps/web
|
||||
npm run test
|
||||
```
|
||||
|
||||
**Requirements**:
|
||||
- All tests pass
|
||||
- No test failures
|
||||
- Coverage maintained
|
||||
|
||||
## Running All Gates
|
||||
|
||||
### Backend
|
||||
|
||||
```bash
|
||||
cd apps/api
|
||||
|
||||
# Run all gates
|
||||
ruff check src/ tests/ && \
|
||||
mypy src/ && \
|
||||
pytest
|
||||
|
||||
# Or use Makefile
|
||||
make lint # ruff + mypy
|
||||
make test # pytest
|
||||
make check # All backend gates
|
||||
```
|
||||
|
||||
### Frontend
|
||||
|
||||
```bash
|
||||
cd apps/web
|
||||
|
||||
# Run all gates
|
||||
npm run lint && \
|
||||
npm run typecheck && \
|
||||
npm run test && \
|
||||
npm run build
|
||||
|
||||
# Or use package.json scripts
|
||||
npm run check # All frontend gates
|
||||
```
|
||||
|
||||
### Full Project
|
||||
|
||||
```bash
|
||||
# From root
|
||||
make check-all # Run all backend and frontend gates
|
||||
```
|
||||
|
||||
## Continuous Integration
|
||||
|
||||
All quality gates run automatically on:
|
||||
- Every Pull Request
|
||||
- Every push to main branch
|
||||
|
||||
**CI Pipeline**:
|
||||
```yaml
|
||||
stages:
|
||||
- lint
|
||||
- test
|
||||
- build
|
||||
|
||||
backend-lint:
|
||||
stage: lint
|
||||
script:
|
||||
- cd apps/api && ruff check src/ tests/
|
||||
- cd apps/api && mypy src/
|
||||
|
||||
backend-test:
|
||||
stage: test
|
||||
script:
|
||||
- cd apps/api && pytest -v
|
||||
|
||||
frontend-lint:
|
||||
stage: lint
|
||||
script:
|
||||
- cd apps/web && npm run lint
|
||||
- cd apps/web && npm run typecheck
|
||||
|
||||
frontend-test:
|
||||
stage: test
|
||||
script:
|
||||
- cd apps/web && npm run test
|
||||
|
||||
frontend-build:
|
||||
stage: build
|
||||
script:
|
||||
- cd apps/web && npm run build
|
||||
```
|
||||
|
||||
## Fixing Common Issues
|
||||
|
||||
### Backend
|
||||
|
||||
**ruff: Line too long**
|
||||
```python
|
||||
# Bad
|
||||
result = some_very_long_function_name(with_many_parameters, that_make_the_line_too_long)
|
||||
|
||||
# Good
|
||||
result = some_very_long_function_name(
|
||||
with_many_parameters,
|
||||
that_make_the_line_too_long,
|
||||
)
|
||||
```
|
||||
|
||||
**mypy: Missing return type**
|
||||
```python
|
||||
# Bad
|
||||
def get_user(user_id):
|
||||
return User.query.get(user_id)
|
||||
|
||||
# Good
|
||||
def get_user(user_id: str) -> User | None:
|
||||
return User.query.get(user_id)
|
||||
```
|
||||
|
||||
### Frontend
|
||||
|
||||
**TypeScript: Implicit any**
|
||||
```typescript
|
||||
// Bad
|
||||
function processData(data) {
|
||||
return data.map(item => item.name);
|
||||
}
|
||||
|
||||
// Good
|
||||
function processData(data: DataItem[]) {
|
||||
return data.map(item => item.name);
|
||||
}
|
||||
```
|
||||
|
||||
**ESLint: Unused variable**
|
||||
```typescript
|
||||
// Bad
|
||||
const [count, setCount] = useState(0);
|
||||
// count is never used
|
||||
|
||||
// Good
|
||||
const [count] = useState(0);
|
||||
// Or remove if not needed
|
||||
```
|
||||
|
||||
## IDE Integration
|
||||
|
||||
### VS Code
|
||||
|
||||
**Settings** (`.vscode/settings.json`):
|
||||
```json
|
||||
{
|
||||
"python.linting.enabled": true,
|
||||
"python.linting.mypyEnabled": true,
|
||||
"python.formatting.provider": "ruff",
|
||||
"editor.formatOnSave": true,
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll.eslint": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### PyCharm
|
||||
|
||||
1. **File** → **Settings** → **Tools** → **External Tools**
|
||||
2. Add ruff and mypy as external tools
|
||||
3. Set up pre-commit hooks
|
||||
|
||||
## Pre-commit Hooks
|
||||
|
||||
`.pre-commit-config.yaml`:
|
||||
```yaml
|
||||
repos:
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: v0.1.0
|
||||
hooks:
|
||||
- id: ruff
|
||||
args: [--fix, --exit-non-zero-on-fix]
|
||||
- id: ruff-format
|
||||
|
||||
- repo: https://github.com/pre-commit/mirrors-mypy
|
||||
rev: v1.7.0
|
||||
hooks:
|
||||
- id: mypy
|
||||
additional_dependencies: [types-all]
|
||||
|
||||
- repo: local
|
||||
hooks:
|
||||
- id: frontend-lint
|
||||
name: Frontend Lint
|
||||
entry: bash -c 'cd apps/web && npm run lint'
|
||||
language: system
|
||||
files: ^apps/web/
|
||||
```
|
||||
|
||||
## Resources
|
||||
|
||||
- [ruff Documentation](https://docs.astral.sh/ruff/)
|
||||
- [mypy Documentation](https://mypy.readthedocs.io/)
|
||||
- [ESLint Documentation](https://eslint.org/)
|
||||
- [TypeScript Handbook](https://www.typescriptlang.org/docs/)
|
||||
@@ -0,0 +1,299 @@
|
||||
# Development Setup Guide
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Python 3.11+
|
||||
- Node.js 18+
|
||||
- PostgreSQL 15+
|
||||
- Redis 7+
|
||||
- Git
|
||||
|
||||
## Quick Setup
|
||||
|
||||
### 1. Clone Repository
|
||||
|
||||
```bash
|
||||
git clone https://github.com/your-org/headquarter.git
|
||||
cd headquarter
|
||||
```
|
||||
|
||||
### 2. Backend Setup
|
||||
|
||||
```bash
|
||||
cd apps/api
|
||||
|
||||
# Create virtual environment
|
||||
python -m venv .venv
|
||||
source .venv/bin/activate # Linux/Mac
|
||||
# .venv\Scripts\activate # Windows
|
||||
|
||||
# Install dependencies
|
||||
pip install -e ".[dev]"
|
||||
|
||||
# Copy environment file
|
||||
cp .env.example .env
|
||||
# Edit .env with your settings
|
||||
```
|
||||
|
||||
### 3. Frontend Setup
|
||||
|
||||
```bash
|
||||
cd apps/web
|
||||
|
||||
# Install dependencies
|
||||
npm install
|
||||
|
||||
# Copy environment file
|
||||
cp .env.example .env
|
||||
# Edit .env with your settings
|
||||
```
|
||||
|
||||
### 4. Database Setup
|
||||
|
||||
```bash
|
||||
# Start PostgreSQL and Redis
|
||||
# (Using Docker or local installation)
|
||||
docker run -d -p 5432:5432 -e POSTGRES_PASSWORD=headquarter postgres:15
|
||||
docker run -d -p 6379:6379 redis:7
|
||||
|
||||
# Create database
|
||||
createdb headquarter
|
||||
|
||||
# Run migrations
|
||||
cd apps/api
|
||||
alembic upgrade head
|
||||
|
||||
# (Optional) Seed data
|
||||
python -m src.scripts.seed
|
||||
```
|
||||
|
||||
### 5. Start Development Servers
|
||||
|
||||
Terminal 1 - Backend:
|
||||
```bash
|
||||
cd apps/api
|
||||
source .venv/bin/activate
|
||||
python -m uvicorn src.main:app --reload --port 8000
|
||||
```
|
||||
|
||||
Terminal 2 - Frontend:
|
||||
```bash
|
||||
cd apps/web
|
||||
npm run dev
|
||||
```
|
||||
|
||||
Terminal 3 - (Optional) Authentik:
|
||||
```bash
|
||||
# If using local Authentik
|
||||
docker compose -f docker-compose.authentik.yml up -d
|
||||
```
|
||||
|
||||
## Development Environment Variables
|
||||
|
||||
Create `apps/api/.env`:
|
||||
|
||||
```bash
|
||||
APP_ENV=development
|
||||
DATABASE_URL=postgresql+asyncpg://headquarter:headquarter@localhost:5432/headquarter
|
||||
REDIS_URL=redis://localhost:6379/0
|
||||
SESSION_SECRET=dev-session-secret-change-me
|
||||
API_DOMAIN=localhost
|
||||
WEB_DOMAIN=localhost
|
||||
|
||||
# Authentik (optional for local dev)
|
||||
AUTHENTIK_DOMAIN=authentik.local
|
||||
AUTHENTIK_CLIENT_ID=headquarter-web
|
||||
AUTHENTIK_CLIENT_SECRET=change-me
|
||||
AUTHENTIK_APPLICATION_SLUG=headquarter-web
|
||||
```
|
||||
|
||||
Create `apps/web/.env`:
|
||||
|
||||
```bash
|
||||
VITE_API_BASE_URL=http://localhost:8000
|
||||
```
|
||||
|
||||
## IDE Setup
|
||||
|
||||
### VS Code Extensions
|
||||
|
||||
Recommended extensions:
|
||||
- Python (ms-python.python)
|
||||
- Pylance (ms-python.vscode-pylance)
|
||||
- ESLint (dbaeumer.vscode-eslint)
|
||||
- Prettier (esbenp.prettier-vscode)
|
||||
- TypeScript Importer (pmneo.tsimporter)
|
||||
|
||||
### PyCharm/IntelliJ
|
||||
|
||||
1. Open `apps/api` as project
|
||||
2. Set Python interpreter to `.venv`
|
||||
3. Enable Django/Flask support for FastAPI
|
||||
|
||||
## Debugging
|
||||
|
||||
### Backend Debugging
|
||||
|
||||
**VS Code launch.json**:
|
||||
```json
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Python: FastAPI",
|
||||
"type": "python",
|
||||
"request": "launch",
|
||||
"module": "uvicorn",
|
||||
"args": ["src.main:app", "--reload", "--port", "8000"],
|
||||
"jinja": true,
|
||||
"justMyCode": true
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**PyCharm**:
|
||||
1. Run → Edit Configurations
|
||||
2. Add Python configuration
|
||||
3. Module name: `uvicorn`
|
||||
4. Parameters: `src.main:app --reload --port 8000`
|
||||
|
||||
### Frontend Debugging
|
||||
|
||||
**VS Code launch.json**:
|
||||
```json
|
||||
{
|
||||
"type": "chrome",
|
||||
"request": "launch",
|
||||
"name": "Launch Chrome against localhost",
|
||||
"url": "http://localhost:5173",
|
||||
"webRoot": "${workspaceFolder}/apps/web/src"
|
||||
}
|
||||
```
|
||||
|
||||
## Common Tasks
|
||||
|
||||
### Database Migrations
|
||||
|
||||
```bash
|
||||
cd apps/api
|
||||
|
||||
# Create new migration
|
||||
alembic revision --autogenerate -m "description"
|
||||
|
||||
# Run migrations
|
||||
alembic upgrade head
|
||||
|
||||
# Downgrade
|
||||
alembic downgrade -1
|
||||
|
||||
# Current version
|
||||
alembic current
|
||||
|
||||
# History
|
||||
alembic history
|
||||
```
|
||||
|
||||
### Adding Dependencies
|
||||
|
||||
**Backend**:
|
||||
```bash
|
||||
cd apps/api
|
||||
# Add production dependency
|
||||
pip install package-name
|
||||
# Add to pyproject.toml [project.dependencies]
|
||||
|
||||
# Add dev dependency
|
||||
pip install -e ".[dev]"
|
||||
# Add to pyproject.toml [project.optional-dependencies.dev]
|
||||
```
|
||||
|
||||
**Frontend**:
|
||||
```bash
|
||||
cd apps/web
|
||||
npm install package-name
|
||||
npm install -D package-name # dev dependency
|
||||
```
|
||||
|
||||
### Git Workflow
|
||||
|
||||
1. Create feature branch: `git checkout -b feature/name`
|
||||
2. Make changes
|
||||
3. Run quality gates (see below)
|
||||
4. Commit: `git commit -m "feat: description"`
|
||||
5. Push: `git push origin feature/name`
|
||||
6. Create Pull Request
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
headquarter/
|
||||
├── apps/
|
||||
│ ├── api/ # Backend (FastAPI)
|
||||
│ │ ├── src/
|
||||
│ │ │ ├── api/ # API routes
|
||||
│ │ │ ├── auth/ # Authentication
|
||||
│ │ │ ├── models/ # Database models
|
||||
│ │ │ ├── utils/ # Utilities
|
||||
│ │ │ └── main.py # Entry point
|
||||
│ │ ├── tests/ # Test suites
|
||||
│ │ ├── alembic/ # Migrations
|
||||
│ │ └── pyproject.toml # Dependencies
|
||||
│ └── web/ # Frontend (React)
|
||||
│ ├── src/
|
||||
│ │ ├── api/ # API clients
|
||||
│ │ ├── components/ # React components
|
||||
│ │ ├── pages/ # Page components
|
||||
│ │ └── styles/ # CSS
|
||||
│ └── package.json # Dependencies
|
||||
├── docs/ # Documentation
|
||||
├── docker-compose.yml # Dev setup
|
||||
└── Makefile # Common commands
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Database Connection Errors
|
||||
|
||||
```bash
|
||||
# Check PostgreSQL is running
|
||||
pg_isready -h localhost -p 5432
|
||||
|
||||
# Check credentials
|
||||
psql postgresql://headquarter:headquarter@localhost:5432/headquarter -c "SELECT 1"
|
||||
```
|
||||
|
||||
### Port Conflicts
|
||||
|
||||
```bash
|
||||
# Find process using port 8000
|
||||
lsof -i :8000
|
||||
|
||||
# Kill process
|
||||
kill -9 <PID>
|
||||
```
|
||||
|
||||
### Node Modules Issues
|
||||
|
||||
```bash
|
||||
cd apps/web
|
||||
rm -rf node_modules package-lock.json
|
||||
npm install
|
||||
```
|
||||
|
||||
### Python Environment Issues
|
||||
|
||||
```bash
|
||||
cd apps/api
|
||||
rm -rf .venv
|
||||
python -m venv .venv
|
||||
source .venv/bin/activate
|
||||
pip install -e ".[dev]"
|
||||
```
|
||||
|
||||
## Resources
|
||||
|
||||
- [FastAPI Documentation](https://fastapi.tiangolo.com/)
|
||||
- [React Documentation](https://react.dev/)
|
||||
- [SQLAlchemy Documentation](https://docs.sqlalchemy.org/)
|
||||
- [Alembic Documentation](https://alembic.sqlalchemy.org/)
|
||||
@@ -0,0 +1,365 @@
|
||||
# Testing Guide
|
||||
|
||||
## Overview
|
||||
|
||||
Headquarter has comprehensive testing for both backend and frontend to ensure reliability and catch regressions.
|
||||
|
||||
## Backend Testing
|
||||
|
||||
### Test Framework
|
||||
|
||||
- **pytest**: Test runner
|
||||
- **pytest-asyncio**: Async test support
|
||||
- **httpx**: HTTP client for API tests
|
||||
- **factory-boy**: Test data generation (recommended)
|
||||
|
||||
### Test Structure
|
||||
|
||||
```
|
||||
apps/api/tests/
|
||||
├── conftest.py # Shared fixtures
|
||||
├── test_auth_api.py # Auth endpoint tests
|
||||
├── test_auth_services.py # Auth service tests
|
||||
├── test_models.py # Database model tests
|
||||
├── test_projects_api.py # Project endpoint tests
|
||||
├── test_git_repositories.py # Repository tests
|
||||
└── ...
|
||||
```
|
||||
|
||||
### Running Tests
|
||||
|
||||
```bash
|
||||
cd apps/api
|
||||
|
||||
# Run all tests
|
||||
pytest
|
||||
|
||||
# Run with coverage
|
||||
pytest --cov=src --cov-report=html
|
||||
|
||||
# Run specific test file
|
||||
pytest tests/test_auth_api.py
|
||||
|
||||
# Run specific test
|
||||
pytest tests/test_auth_api.py::test_login_redirect
|
||||
|
||||
# Run with verbose output
|
||||
pytest -v
|
||||
|
||||
# Run async tests
|
||||
pytest --asyncio-mode=auto
|
||||
```
|
||||
|
||||
### Writing Tests
|
||||
|
||||
**Unit Test Example**:
|
||||
```python
|
||||
import pytest
|
||||
from src.utils.git_url_parser import extract_base_repo_url
|
||||
|
||||
def test_extract_github_url():
|
||||
url = "https://github.com/user/repo/tree/main"
|
||||
result = extract_base_repo_url(url)
|
||||
assert result == "https://github.com/user/repo.git"
|
||||
|
||||
def test_valid_git_url_unchanged():
|
||||
url = "https://github.com/user/repo.git"
|
||||
result = extract_base_repo_url(url)
|
||||
assert result == url
|
||||
```
|
||||
|
||||
**Async Test Example**:
|
||||
```python
|
||||
import pytest
|
||||
from httpx import AsyncClient
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_projects(client: AsyncClient):
|
||||
response = await client.get("/projects")
|
||||
assert response.status_code == 200
|
||||
assert isinstance(response.json(), list)
|
||||
```
|
||||
|
||||
**API Integration Test**:
|
||||
```python
|
||||
@pytest.mark.asyncio
|
||||
async def test_create_project(client: AsyncClient, auth_headers):
|
||||
response = await client.post(
|
||||
"/projects",
|
||||
json={"name": "Test Project"},
|
||||
headers=auth_headers
|
||||
)
|
||||
assert response.status_code == 201
|
||||
data = response.json()
|
||||
assert data["name"] == "Test Project"
|
||||
assert "id" in data
|
||||
```
|
||||
|
||||
### Test Fixtures
|
||||
|
||||
**conftest.py** provides:
|
||||
|
||||
```python
|
||||
import pytest
|
||||
from httpx import AsyncClient
|
||||
|
||||
@pytest.fixture
|
||||
async def client():
|
||||
from src.main import app
|
||||
async with AsyncClient(app=app, base_url="http://test") as client:
|
||||
yield client
|
||||
|
||||
@pytest.fixture
|
||||
async def auth_headers(client):
|
||||
# Login and return auth headers
|
||||
response = await client.post("/auth/login")
|
||||
# ... setup session
|
||||
return {"Cookie": "session=..."}
|
||||
```
|
||||
|
||||
### Test Database
|
||||
|
||||
Tests use a separate database:
|
||||
|
||||
```bash
|
||||
# Test database URL (from .env)
|
||||
TEST_DATABASE_URL=postgresql+asyncpg://headquarter:headquarter@localhost:5432/headquarter_test
|
||||
|
||||
# Run tests with test DB
|
||||
TEST_DATABASE_URL=... pytest
|
||||
```
|
||||
|
||||
## Frontend Testing
|
||||
|
||||
### Test Framework
|
||||
|
||||
- **Vitest**: Test runner
|
||||
- **React Testing Library**: Component testing
|
||||
- **jsdom**: DOM environment
|
||||
|
||||
### Test Structure
|
||||
|
||||
```
|
||||
apps/web/src/
|
||||
├── components/
|
||||
│ └── protected-route.test.tsx
|
||||
├── pages/
|
||||
│ ├── dashboard.test.tsx
|
||||
│ └── projects.test.tsx
|
||||
└── test/
|
||||
└── setup.ts # Test setup
|
||||
```
|
||||
|
||||
### Running Tests
|
||||
|
||||
```bash
|
||||
cd apps/web
|
||||
|
||||
# Run all tests
|
||||
npm run test
|
||||
|
||||
# Run in watch mode
|
||||
npm run test -- --watch
|
||||
|
||||
# Run with coverage
|
||||
npm run test -- --coverage
|
||||
|
||||
# Run specific file
|
||||
npm run test -- protected-route
|
||||
```
|
||||
|
||||
### Writing Tests
|
||||
|
||||
**Component Test Example**:
|
||||
```typescript
|
||||
import { render, screen, fireEvent } from '@testing-library/react';
|
||||
import { FileTree } from '../components/file-tree';
|
||||
|
||||
describe('FileTree', () => {
|
||||
const mockFiles = [
|
||||
{ name: 'src', type: 'directory', path: 'src' },
|
||||
{ name: 'main.py', type: 'file', path: 'src/main.py' },
|
||||
];
|
||||
|
||||
it('renders file list', () => {
|
||||
render(<FileTree files={mockFiles} onFileClick={() => {}} />);
|
||||
|
||||
expect(screen.getByText('src')).toBeInTheDocument();
|
||||
expect(screen.getByText('main.py')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('calls onFileClick when file clicked', () => {
|
||||
const handleClick = vi.fn();
|
||||
render(<FileTree files={mockFiles} onFileClick={handleClick} />);
|
||||
|
||||
fireEvent.click(screen.getByText('main.py'));
|
||||
expect(handleClick).toHaveBeenCalledWith(mockFiles[1]);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
**Async Test Example**:
|
||||
```typescript
|
||||
import { render, screen, waitFor } from '@testing-library/react';
|
||||
import { ProjectsPage } from '../pages/projects';
|
||||
|
||||
describe('ProjectsPage', () => {
|
||||
it('loads and displays projects', async () => {
|
||||
render(<ProjectsPage />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('My Projects')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
## E2E Testing (Future)
|
||||
|
||||
### Playwright Setup
|
||||
|
||||
```bash
|
||||
cd apps/web
|
||||
npm install -D @playwright/test
|
||||
npx playwright install
|
||||
```
|
||||
|
||||
**Example E2E Test**:
|
||||
```typescript
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
test('user can login', async ({ page }) => {
|
||||
await page.goto('http://localhost:5173');
|
||||
await page.click('text=Login');
|
||||
|
||||
// Authentik login
|
||||
await page.fill('[name="username"]', 'test@example.com');
|
||||
await page.fill('[name="password"]', 'password');
|
||||
await page.click('text=Sign In');
|
||||
|
||||
// Should redirect back to app
|
||||
await expect(page).toHaveURL('http://localhost:5173/dashboard');
|
||||
});
|
||||
```
|
||||
|
||||
## Test Data
|
||||
|
||||
### Factories (Recommended)
|
||||
|
||||
Use factory-boy for test data:
|
||||
|
||||
```python
|
||||
# tests/factories.py
|
||||
import factory
|
||||
from src.models.user import User
|
||||
|
||||
class UserFactory(factory.Factory):
|
||||
class Meta:
|
||||
model = User
|
||||
|
||||
email = factory.Faker('email')
|
||||
name = factory.Faker('name')
|
||||
authentik_id = factory.Faker('uuid4')
|
||||
```
|
||||
|
||||
### Fixtures
|
||||
|
||||
```python
|
||||
@pytest.fixture
|
||||
async def test_user(db_session):
|
||||
user = UserFactory()
|
||||
db_session.add(user)
|
||||
await db_session.commit()
|
||||
return user
|
||||
```
|
||||
|
||||
## Coverage Goals
|
||||
|
||||
| Component | Target Coverage |
|
||||
|-----------|----------------|
|
||||
| Backend API | 80%+ |
|
||||
| Backend Services | 90%+ |
|
||||
| Backend Models | 90%+ |
|
||||
| Frontend Components | 70%+ |
|
||||
| Frontend Pages | 60%+ |
|
||||
|
||||
## Continuous Integration
|
||||
|
||||
### GitHub Actions (Example)
|
||||
|
||||
```yaml
|
||||
name: Tests
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
backend:
|
||||
runs-on: ubuntu-latest
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:15
|
||||
env:
|
||||
POSTGRES_PASSWORD: headquarter
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: '3.11'
|
||||
- run: pip install -e ".[dev]"
|
||||
- run: pytest --cov=src --cov-report=xml
|
||||
|
||||
frontend:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: '18'
|
||||
- run: cd apps/web && npm ci
|
||||
- run: cd apps/web && npm run test
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Backend
|
||||
1. **Test isolation**: Each test should be independent
|
||||
2. **Use fixtures**: Don't repeat setup code
|
||||
3. **Mock external services**: Authentik, git operations
|
||||
4. **Test edge cases**: Empty lists, invalid inputs, errors
|
||||
5. **Async properly**: Use `pytest.mark.asyncio` and async fixtures
|
||||
|
||||
### Frontend
|
||||
1. **Test behavior, not implementation**: Check what user sees
|
||||
2. **Use data-testid**: For stable selectors
|
||||
3. **Mock API calls**: Don't hit real backend
|
||||
4. **Test accessibility**: Use `screen.getByRole`
|
||||
5. **Snapshot sparingly**: Only for complex UIs
|
||||
|
||||
## Debugging Tests
|
||||
|
||||
### Backend
|
||||
```bash
|
||||
# Run with debugger
|
||||
pytest --pdb
|
||||
|
||||
# Run specific test with verbose
|
||||
pytest -v -s test_file.py::test_name
|
||||
|
||||
# Show print statements
|
||||
pytest -s
|
||||
```
|
||||
|
||||
### Frontend
|
||||
```bash
|
||||
# Debug mode
|
||||
npm run test -- --reporter=verbose
|
||||
|
||||
# Show browser (for E2E)
|
||||
npx playwright test --headed
|
||||
```
|
||||
|
||||
## Resources
|
||||
|
||||
- [pytest Documentation](https://docs.pytest.org/)
|
||||
- [React Testing Library](https://testing-library.com/docs/react-testing-library/intro/)
|
||||
- [Vitest Documentation](https://vitest.dev/)
|
||||
- [Playwright Documentation](https://playwright.dev/)
|
||||
@@ -0,0 +1,100 @@
|
||||
# Authentication
|
||||
|
||||
## Overview
|
||||
|
||||
Headquarter uses OAuth2 authentication via Authentik. Users log in through their Authentik identity provider and receive a session cookie for authenticated access.
|
||||
|
||||
## How to Use
|
||||
|
||||
### Logging In
|
||||
|
||||
1. Navigate to the application
|
||||
2. Click the **"Login"** button in the header
|
||||
3. You will be redirected to **Authentik**
|
||||
4. Enter your Authentik credentials
|
||||
5. You will be redirected back to Headquarter, now logged in
|
||||
|
||||
### User Profile
|
||||
|
||||
After logging in, you can view your profile:
|
||||
|
||||
1. Click your **name** in the header
|
||||
2. Select **"Profile"** from the dropdown
|
||||
3. View and edit:
|
||||
- Display name
|
||||
- Email
|
||||
- Avatar (upload or change)
|
||||
|
||||
### Logging Out
|
||||
|
||||
1. Click your **name** in the header
|
||||
2. Select **"Logout"**
|
||||
3. Your session will be cleared
|
||||
4. You will be redirected to the login page
|
||||
|
||||
## Authentication Flow
|
||||
|
||||
```
|
||||
User → Click Login → Authentik Login → OAuth2 Callback → Session Cookie → Authenticated
|
||||
```
|
||||
|
||||
### Technical Details
|
||||
|
||||
**Session Management:**
|
||||
- Uses signed session cookies
|
||||
- Cookie is `HttpOnly` and `Secure` (in production)
|
||||
- Session expires after configurable TTL (default: 24 hours)
|
||||
|
||||
**OAuth2 Flow:**
|
||||
1. User clicks login
|
||||
2. Backend redirects to Authentik authorize URL
|
||||
3. User authenticates with Authentik
|
||||
4. Authentik redirects back with authorization code
|
||||
5. Backend exchanges code for access token
|
||||
6. Backend fetches user info from Authentik
|
||||
7. Backend creates/updates local user record
|
||||
8. Backend sets session cookie
|
||||
9. User is authenticated
|
||||
|
||||
## API Reference
|
||||
|
||||
### Endpoints
|
||||
|
||||
- `GET /auth/login` - Initiate login (redirects to Authentik)
|
||||
- `GET /auth/callback` - OAuth2 callback
|
||||
- `GET /auth/me` - Get current user
|
||||
- `POST /auth/logout` - Logout (clears session)
|
||||
|
||||
See [Auth API](../api/auth.md) for detailed endpoint documentation.
|
||||
|
||||
## Configuration
|
||||
|
||||
| Variable | Description | Default |
|
||||
|----------|-------------|---------|
|
||||
| `AUTHENTIK_DOMAIN` | Authentik server domain | - |
|
||||
| `AUTHENTIK_CLIENT_ID` | OAuth client ID | - |
|
||||
| `AUTHENTIK_CLIENT_SECRET` | OAuth client secret | - |
|
||||
| `AUTHENTIK_APPLICATION_SLUG` | Application slug for URLs | `headquarter-web` |
|
||||
| `SESSION_SECRET` | Session cookie signing secret | `change-me` |
|
||||
| `SESSION_TTL_HOURS` | Session duration | `24` |
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Login Loop
|
||||
|
||||
**Issue:** After logging in, you're redirected back to login
|
||||
**Solution:** Check that cookie domain matches your domain configuration
|
||||
|
||||
### "Invalid State" Error
|
||||
|
||||
**Issue:** Error about invalid state parameter
|
||||
**Solution:** Clear cookies and try again. If persistent, check Authentik configuration.
|
||||
|
||||
### Session Expired
|
||||
|
||||
**Issue:** "Session expired" message
|
||||
**Solution:** Log in again. Session duration is configurable via `SESSION_TTL_HOURS`.
|
||||
|
||||
## Related Features
|
||||
|
||||
- [User Settings](settings.md) - Configure user preferences
|
||||
@@ -0,0 +1,106 @@
|
||||
# Git History Visualization
|
||||
|
||||
## Overview
|
||||
|
||||
The Git History page provides a visual representation of a repository's commit history, including a branch graph and detailed commit information.
|
||||
|
||||
## How to Use
|
||||
|
||||
### Accessing History
|
||||
|
||||
1. Navigate to a **project workspace**
|
||||
2. Select a **repository** from the dropdown
|
||||
3. Click the **"History"** button on the repository card
|
||||
|
||||
Or:
|
||||
|
||||
1. Go to the **Repositories** page
|
||||
2. Click **"History"** on any repository card
|
||||
|
||||
### History View Layout
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────┐
|
||||
│ [Branch Selector ▼] [Repository Name] │
|
||||
├─────────────────┬───────────────────────────┤
|
||||
│ Commit List │ Commit Details │
|
||||
│ │ │
|
||||
│ ●─●─○─● │ Commit: abc1234 │
|
||||
│ │ └─○ │ Author: John Doe │
|
||||
│ │ │ Date: 2024-01-01 │
|
||||
│ ● │ │
|
||||
│ │ │ Message: │
|
||||
│ ○ │ Fix bug in parser │
|
||||
│ │ │
|
||||
│ │ Stats: │
|
||||
│ │ +15 -3 lines │
|
||||
│ │ │
|
||||
│ │ Diff: │
|
||||
│ │ ```diff │
|
||||
│ │ + new line │
|
||||
│ │ - old line │
|
||||
│ │ ``` │
|
||||
└─────────────────┴───────────────────────────┘
|
||||
```
|
||||
|
||||
### Commit Graph
|
||||
|
||||
The left panel shows:
|
||||
- **Commit hashes** (abbreviated)
|
||||
- **Branch/merge indicators** (lines connecting commits)
|
||||
- **Commit messages** (first line)
|
||||
- **Author** and **date**
|
||||
- **Branch tags** (colored labels)
|
||||
|
||||
**Graph symbols:**
|
||||
- `●` - Regular commit
|
||||
- Branch lines show merge history
|
||||
- Different colors indicate different branches
|
||||
|
||||
### Commit Details
|
||||
|
||||
Click any commit to see details:
|
||||
|
||||
**Metadata:**
|
||||
- Full commit hash
|
||||
- Author name and email
|
||||
- Commit date and time
|
||||
- Complete commit message
|
||||
|
||||
**Statistics:**
|
||||
- Files changed
|
||||
- Lines added (+)
|
||||
- Lines removed (-)
|
||||
|
||||
**Diff:**
|
||||
- Syntax-highlighted diff
|
||||
- Added lines in green
|
||||
- Removed lines in red
|
||||
- Context lines for reference
|
||||
|
||||
### Branch Filtering
|
||||
|
||||
Use the **Branch Selector** to filter commits:
|
||||
- Select **"All"** to see all branches
|
||||
- Select a specific branch to see only that branch's history
|
||||
- The graph updates to show only relevant commits
|
||||
|
||||
### Navigation
|
||||
|
||||
- **Click** a commit to view details
|
||||
- **Scroll** the commit list to see older commits
|
||||
- The view shows up to 10,000 commits (loads all at once)
|
||||
|
||||
## API Reference
|
||||
|
||||
### Endpoints
|
||||
|
||||
- `GET /projects/{id}/repositories/{id}/history` - Get commit history
|
||||
- `GET /projects/{id}/repositories/{id}/commits/{hash}` - Get commit details
|
||||
|
||||
See [Repositories API](../api/repositories.md) for detailed endpoint documentation.
|
||||
|
||||
## Related Features
|
||||
|
||||
- [Git Repositories](repositories.md) - Manage repositories
|
||||
- [Repository Workspace](workspace.md) - Browse files
|
||||
@@ -0,0 +1,73 @@
|
||||
# Project Management
|
||||
|
||||
## Overview
|
||||
|
||||
Projects are the top-level organizational unit in Headquarter. Each project can contain multiple git repositories and serves as a workspace for related development work.
|
||||
|
||||
## How to Use
|
||||
|
||||
### Creating a Project
|
||||
|
||||
1. Navigate to the **Projects** page from the sidebar
|
||||
2. Click the **"New Project"** button
|
||||
3. Enter a **name** for your project (required)
|
||||
4. Optionally add a **description**
|
||||
5. Click **"Create Project"**
|
||||
|
||||
### Viewing Projects
|
||||
|
||||
The Projects page displays all your projects in a card layout showing:
|
||||
- Project name
|
||||
- Description (if set)
|
||||
- Creation date
|
||||
- Associated repositories count
|
||||
|
||||
### Opening a Project Workspace
|
||||
|
||||
Click on any project card to open its **workspace**. The workspace is the default view for a project and shows:
|
||||
- Repository file browser
|
||||
- Branch selector
|
||||
- File viewer
|
||||
|
||||
### Editing a Project
|
||||
|
||||
1. From the Projects page, click the **menu icon** (⋮) on a project card
|
||||
2. Select **"Edit"**
|
||||
3. Update the name or description
|
||||
4. Click **"Save"**
|
||||
|
||||
### Deleting a Project
|
||||
|
||||
1. From the Projects page, click the **menu icon** (⋮) on a project card
|
||||
2. Select **"Delete"**
|
||||
3. Confirm the deletion
|
||||
|
||||
**Note:** Deleting a project also deletes all associated repositories and their data. This action cannot be undone.
|
||||
|
||||
## Project Workspace
|
||||
|
||||
The project workspace is the default view when you click on a project. It provides:
|
||||
|
||||
- **Repository Browser**: Navigate files and directories
|
||||
- **File Viewer**: View file contents with syntax highlighting
|
||||
- **Branch Management**: Switch between branches
|
||||
- **Repository Switcher**: Switch between project repositories
|
||||
|
||||
See [Repository Workspace](workspace.md) for detailed documentation.
|
||||
|
||||
## API Reference
|
||||
|
||||
### Endpoints
|
||||
|
||||
- `GET /projects` - List all projects
|
||||
- `POST /projects` - Create a new project
|
||||
- `GET /projects/{id}` - Get project details
|
||||
- `PUT /projects/{id}` - Update a project
|
||||
- `DELETE /projects/{id}` - Delete a project
|
||||
|
||||
See [Projects API](../api/projects.md) for detailed endpoint documentation.
|
||||
|
||||
## Related Features
|
||||
|
||||
- [Repository Workspace](workspace.md) - Browse and edit repository files
|
||||
- [Git Repositories](repositories.md) - Manage project repositories
|
||||
@@ -0,0 +1,91 @@
|
||||
# Git Repositories
|
||||
|
||||
## Overview
|
||||
|
||||
Git repositories are managed within projects. You can create bare repositories for new projects or clone existing repositories from remote sources.
|
||||
|
||||
## How to Use
|
||||
|
||||
### Creating a Repository
|
||||
|
||||
1. Navigate to a **project workspace** or the **Repositories** page
|
||||
2. Click the **"New Repository"** button
|
||||
3. Fill in the form:
|
||||
- **Name**: Repository name (required)
|
||||
- **Remote URL**: For cloning (optional)
|
||||
- **Mirror Clone**: Toggle for mirror clones
|
||||
4. Click **"Create Repository"**
|
||||
|
||||
#### Bare Repository (No Remote URL)
|
||||
|
||||
Creates a new bare git repository. Use this for:
|
||||
- New projects
|
||||
- Local-only repositories
|
||||
- Repositories that will be pushed to later
|
||||
|
||||
#### Clone from Remote
|
||||
|
||||
Enter a git URL to clone from:
|
||||
- `https://github.com/user/repo.git`
|
||||
- `git@github.com:user/repo.git`
|
||||
- `https://gitlab.com/user/repo.git`
|
||||
|
||||
**Smart URL Parsing:** If you paste a browser URL (like `https://github.com/user/repo/tree/main`), the system will automatically suggest the correct git URL.
|
||||
|
||||
#### Mirror Clone
|
||||
|
||||
Enable **"Mirror Clone"** to create a full mirror of a remote repository:
|
||||
- Clones all branches and tags
|
||||
- Sets up remote tracking
|
||||
- Updates can be fetched later
|
||||
|
||||
### Smart URL Parsing
|
||||
|
||||
When pasting URLs, the system automatically detects browser URLs and suggests the correct git clone URL:
|
||||
|
||||
**Examples:**
|
||||
- `https://github.com/user/repo/tree/main` → `https://github.com/user/repo.git`
|
||||
- `https://github.com/user/repo/blob/main/README.md` → `https://github.com/user/repo.git`
|
||||
- `https://gitlab.com/user/repo/-/tree/develop` → `https://gitlab.com/user/repo.git`
|
||||
|
||||
You can accept the suggestion or proceed with the original URL.
|
||||
|
||||
### Viewing Repositories
|
||||
|
||||
The Repositories page shows all repositories in a project:
|
||||
- Repository name
|
||||
- Clone URL
|
||||
- Mirror status
|
||||
- Creation date
|
||||
|
||||
### Repository Actions
|
||||
|
||||
Each repository card provides:
|
||||
- **History**: View commit history and branch graph
|
||||
- **Browse**: Open in workspace file browser
|
||||
- **Delete**: Remove the repository
|
||||
|
||||
### Deleting a Repository
|
||||
|
||||
1. Click the **menu icon** (⋮) on a repository card
|
||||
2. Select **"Delete"**
|
||||
3. Confirm the deletion
|
||||
|
||||
**Note:** This permanently deletes the repository from disk. This action cannot be undone.
|
||||
|
||||
## API Reference
|
||||
|
||||
### Endpoints
|
||||
|
||||
- `GET /projects/{id}/repositories` - List repositories
|
||||
- `POST /projects/{id}/repositories` - Create repository
|
||||
- `DELETE /projects/{id}/repositories/{id}` - Delete repository
|
||||
- `POST /projects/{id}/repositories/parse-url` - Parse and validate URL
|
||||
|
||||
See [Repositories API](../api/repositories.md) for detailed endpoint documentation.
|
||||
|
||||
## Related Features
|
||||
|
||||
- [Repository Workspace](workspace.md) - Browse repository files
|
||||
- [Git History](git-history.md) - View commit history
|
||||
- [Smart Git URL Parsing](repositories.md#smart-url-parsing) - Automatic URL correction
|
||||
@@ -0,0 +1,56 @@
|
||||
# User Settings
|
||||
|
||||
## Overview
|
||||
|
||||
User settings allow you to customize your Headquarter experience, including theme preferences and git identity.
|
||||
|
||||
## How to Use
|
||||
|
||||
### Accessing Settings
|
||||
|
||||
1. Click your **name** in the header
|
||||
2. Select **"Settings"** from the dropdown
|
||||
|
||||
### Theme Selection
|
||||
|
||||
Choose your preferred theme:
|
||||
|
||||
- **System** - Follows your operating system preference
|
||||
- **Light** - Light color scheme
|
||||
- **Dark** - Dark color scheme
|
||||
|
||||
Changes are applied immediately and persist across sessions.
|
||||
|
||||
### Git Identity
|
||||
|
||||
Configure your git identity for commits made through the workspace:
|
||||
|
||||
- **Name**: Your display name for git commits
|
||||
- **Email**: Your email for git commits
|
||||
|
||||
This information is used when you make quick edits in the repository workspace.
|
||||
|
||||
### Default Editor
|
||||
|
||||
Choose your preferred editor for quick edits:
|
||||
|
||||
- Options depend on available tool types
|
||||
- Used when opening files for editing
|
||||
|
||||
## Configuration
|
||||
|
||||
Settings are stored per-user in the database and persist across sessions.
|
||||
|
||||
## API Reference
|
||||
|
||||
### Endpoints
|
||||
|
||||
- `GET /users/me/config` - Get user settings
|
||||
- `PATCH /users/me/config` - Update user settings
|
||||
|
||||
See [Users API](../api/users.md) for detailed endpoint documentation.
|
||||
|
||||
## Related Features
|
||||
|
||||
- [Authentication](auth.md) - User authentication
|
||||
- [Repository Workspace](workspace.md) - Edit files with git identity
|
||||
@@ -0,0 +1,70 @@
|
||||
# SSH Key Management
|
||||
|
||||
## Overview
|
||||
|
||||
Manage SSH key pairs for authenticating with git remotes. Keys are generated and stored securely, with the private key encrypted.
|
||||
|
||||
## How to Use
|
||||
|
||||
### Generating a Key Pair
|
||||
|
||||
1. Navigate to **Settings** → **SSH Keys**
|
||||
2. Click **"Generate New Key"**
|
||||
3. Enter a **name** for the key (e.g., "GitHub Work Account")
|
||||
4. Click **"Generate"**
|
||||
|
||||
The system will:
|
||||
- Generate an Ed25519 key pair
|
||||
- Encrypt the private key
|
||||
- Store both keys securely
|
||||
- Display the public key
|
||||
|
||||
### Copying Public Key
|
||||
|
||||
1. Find the key in the list
|
||||
2. Click the **"Copy"** button next to the public key
|
||||
3. Paste into your git host (GitHub, GitLab, etc.)
|
||||
|
||||
### Adding to Git Hosts
|
||||
|
||||
#### GitHub
|
||||
1. Go to Settings → SSH and GPG keys
|
||||
2. Click "New SSH key"
|
||||
3. Paste the public key
|
||||
4. Give it a title
|
||||
5. Click "Add SSH key"
|
||||
|
||||
#### GitLab
|
||||
1. Go to Preferences → SSH Keys
|
||||
2. Paste the public key
|
||||
3. Set expiration (optional)
|
||||
4. Click "Add key"
|
||||
|
||||
### Deleting Keys
|
||||
|
||||
1. Find the key in the list
|
||||
2. Click the **"Delete"** button
|
||||
3. Confirm deletion
|
||||
|
||||
**Note:** Deleting a key removes both public and private keys. This cannot be undone.
|
||||
|
||||
## Security
|
||||
|
||||
- **Ed25519 algorithm**: Modern, secure key type
|
||||
- **Encrypted storage**: Private keys are encrypted at rest
|
||||
- **No export**: Private keys cannot be exported
|
||||
- **One-way generation**: Keys are generated server-side, never transmitted
|
||||
|
||||
## API Reference
|
||||
|
||||
### Endpoints
|
||||
|
||||
- `GET /ssh-keys` - List SSH keys
|
||||
- `POST /ssh-keys` - Generate new key
|
||||
- `DELETE /ssh-keys/{id}` - Delete key
|
||||
|
||||
See [SSH Keys API](../api/ssh-keys.md) for detailed endpoint documentation.
|
||||
|
||||
## Related Features
|
||||
|
||||
- [Git Repositories](repositories.md) - Use SSH keys for repository access
|
||||
@@ -0,0 +1,104 @@
|
||||
# Tool Types
|
||||
|
||||
## Overview
|
||||
|
||||
Tool types define development tools that can be spawned for projects. Headquarter includes built-in types and supports creating custom tool types with Docker Compose templates.
|
||||
|
||||
## How to Use
|
||||
|
||||
### Built-in Tool Types
|
||||
|
||||
Headquarter includes these built-in tool types:
|
||||
|
||||
#### VS Code Server
|
||||
- **Image**: `lscr.io/linuxserver/code-server:latest`
|
||||
- **Purpose**: Full VS Code in the browser
|
||||
- **Features**: Extensions, terminal, debugging
|
||||
- **Access**: Port 8443
|
||||
|
||||
#### Jupyter Notebook
|
||||
- **Image**: `jupyter/scipy-notebook:latest`
|
||||
- **Purpose**: Interactive Python development
|
||||
- **Features**: Notebooks, data visualization
|
||||
- **Access**: Port 8888
|
||||
|
||||
### Managing Tool Types
|
||||
|
||||
#### Viewing Tool Types
|
||||
|
||||
1. Navigate to **Settings** → **Tool Types**
|
||||
2. See a list of all tool types
|
||||
3. Built-in types are marked with a badge
|
||||
|
||||
#### Creating Custom Tool Types
|
||||
|
||||
1. Click **"New Tool Type"**
|
||||
2. Fill in the form:
|
||||
- **Name**: Unique identifier (e.g., `my-custom-tool`)
|
||||
- **Display Name**: Human-readable name
|
||||
- **Description**: What this tool does
|
||||
- **Compose Template**: Docker Compose YAML
|
||||
3. Click **"Create"**
|
||||
|
||||
#### Compose Template Format
|
||||
|
||||
The compose template uses Docker Compose syntax with template variables:
|
||||
|
||||
```yaml
|
||||
version: "3.8"
|
||||
services:
|
||||
my-tool:
|
||||
image: my-image:latest
|
||||
container_name: {{TOOL_NAME}}
|
||||
environment:
|
||||
- VARIABLE=value
|
||||
volumes:
|
||||
- {{REPO_PATH}}:/workspace
|
||||
ports:
|
||||
- "8080:8080"
|
||||
```
|
||||
|
||||
**Required Variables:**
|
||||
- `{{TOOL_NAME}}` - Unique name for the container
|
||||
- `{{REPO_PATH}}` - Path to the repository
|
||||
|
||||
#### Validating Templates
|
||||
|
||||
The system validates templates:
|
||||
- Must be valid YAML
|
||||
- Must contain a `services` section
|
||||
- Must use all required variables
|
||||
- Invalid templates will be rejected
|
||||
|
||||
#### Editing Tool Types
|
||||
|
||||
1. Find the tool type in the list
|
||||
2. Click **"Edit"**
|
||||
3. Update fields
|
||||
4. Click **"Save"**
|
||||
|
||||
**Note:** Built-in tool types cannot be modified or deleted.
|
||||
|
||||
#### Deleting Tool Types
|
||||
|
||||
1. Find the tool type in the list
|
||||
2. Click **"Delete"**
|
||||
3. Confirm deletion
|
||||
|
||||
**Note:** Built-in tool types cannot be deleted.
|
||||
|
||||
## API Reference
|
||||
|
||||
### Endpoints
|
||||
|
||||
- `GET /tool-types` - List tool types
|
||||
- `POST /tool-types` - Create tool type
|
||||
- `GET /tool-types/{id}` - Get tool type details
|
||||
- `PUT /tool-types/{id}` - Update tool type
|
||||
- `DELETE /tool-types/{id}` - Delete tool type
|
||||
|
||||
See [Tool Types API](../api/tool-types.md) for detailed endpoint documentation.
|
||||
|
||||
## Related Features
|
||||
|
||||
- [Tool Instances](tool-instances.md) - Spawn and manage tool instances
|
||||
@@ -0,0 +1,101 @@
|
||||
# Repository Workspace
|
||||
|
||||
## Overview
|
||||
|
||||
The Repository Workspace is the default view when you open a project. It provides a file browser and viewer for exploring repository contents, similar to GitHub or GitLab's file browser.
|
||||
|
||||
## How to Use
|
||||
|
||||
### Workspace Layout
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────┐
|
||||
│ [Repo Selector ▼] [Branch Selector ▼] │
|
||||
├─────────────────┬───────────────────────────┤
|
||||
│ File Tree │ Main Content │
|
||||
│ │ │
|
||||
│ 📁 src/ │ Breadcrumbs: src > main │
|
||||
│ 📁 tests/ │ │
|
||||
│ 📄 README.md │ [Edit] [History] │
|
||||
│ 📄 .gitignore │ │
|
||||
│ │ File content here... │
|
||||
│ │ │
|
||||
└─────────────────┴───────────────────────────┘
|
||||
```
|
||||
|
||||
### Repository Selection
|
||||
|
||||
If a project has multiple repositories:
|
||||
1. Use the **Repository Selector** dropdown at the top
|
||||
2. Choose the repository you want to browse
|
||||
3. The file tree updates automatically
|
||||
|
||||
### Branch Selection
|
||||
|
||||
1. Use the **Branch Selector** dropdown
|
||||
2. Select a branch from the list
|
||||
3. The file tree refreshes to show that branch's contents
|
||||
|
||||
### Browsing Files
|
||||
|
||||
**Navigate directories:**
|
||||
- Click on a folder (📁) to expand it
|
||||
- Click again to collapse
|
||||
- The file tree shows the full directory structure
|
||||
|
||||
**View file contents:**
|
||||
- Click on a file (📄) to open it
|
||||
- The file viewer shows:
|
||||
- File path breadcrumbs
|
||||
- File content with syntax highlighting
|
||||
- File metadata (size, last commit)
|
||||
|
||||
### File Viewer
|
||||
|
||||
The file viewer supports:
|
||||
- **Syntax highlighting** for common languages
|
||||
- **Line numbers**
|
||||
- **Breadcrumb navigation** (click any path segment)
|
||||
|
||||
### Quick Editing
|
||||
|
||||
For small changes:
|
||||
|
||||
1. Open a file in the viewer
|
||||
2. Click the **"Edit"** button
|
||||
3. Make your changes in the text area
|
||||
4. Enter a **commit message**
|
||||
5. Click **"Save"**
|
||||
|
||||
The system will:
|
||||
- Commit the changes to the current branch
|
||||
- Show the new commit hash
|
||||
- Refresh the file view
|
||||
|
||||
**Note:** This creates a real git commit. Make sure your commit message describes the change.
|
||||
|
||||
### Binary Files
|
||||
|
||||
Binary files (images, compiled code, etc.) cannot be viewed or edited in the workspace. The viewer will show "Binary file - cannot display."
|
||||
|
||||
## Keyboard Navigation
|
||||
|
||||
- **Click** folder to expand/collapse
|
||||
- **Click** file to view
|
||||
- **Click** breadcrumb to navigate up
|
||||
|
||||
## API Reference
|
||||
|
||||
### Endpoints
|
||||
|
||||
- `GET /projects/{id}/repositories/{id}/files` - List files in directory
|
||||
- `GET /projects/{id}/repositories/{id}/files/content` - Get file content
|
||||
- `POST /projects/{id}/repositories/{id}/files/content` - Update file
|
||||
- `GET /projects/{id}/repositories/{id}/branches` - List branches
|
||||
|
||||
See [Repositories API](../api/repositories.md) for detailed endpoint documentation.
|
||||
|
||||
## Related Features
|
||||
|
||||
- [Git Repositories](repositories.md) - Manage repositories
|
||||
- [Git History](git-history.md) - View commit history
|
||||
Vendored
+97
@@ -0,0 +1,97 @@
|
||||
# API Endpoint Documentation Template
|
||||
|
||||
Use this template when documenting API endpoints.
|
||||
|
||||
## Endpoint Group Name
|
||||
|
||||
Brief description of what this group of endpoints does.
|
||||
|
||||
### Authentication
|
||||
|
||||
Describe authentication requirements (e.g., "Requires valid session cookie").
|
||||
|
||||
---
|
||||
|
||||
## METHOD /path/to/endpoint
|
||||
|
||||
**Description:** What this endpoint does.
|
||||
|
||||
### Request
|
||||
|
||||
#### Headers
|
||||
|
||||
| Header | Required | Description |
|
||||
|--------|----------|-------------|
|
||||
| `Authorization` | Yes/No | Description |
|
||||
| `Content-Type` | Yes/No | Description |
|
||||
|
||||
#### Query Parameters
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|------|----------|-------------|
|
||||
| `param_name` | `string` | Yes/No | Description |
|
||||
|
||||
#### Request Body
|
||||
|
||||
```json
|
||||
{
|
||||
"field_name": "string",
|
||||
"field_name": "number"
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `field_name` | `string` | Yes/No | Description |
|
||||
|
||||
### Response
|
||||
|
||||
#### Success (200 OK)
|
||||
|
||||
```json
|
||||
{
|
||||
"field_name": "value",
|
||||
"field_name": "value"
|
||||
}
|
||||
```
|
||||
|
||||
#### Error Responses
|
||||
|
||||
**400 Bad Request**
|
||||
```json
|
||||
{
|
||||
"detail": "Error message"
|
||||
}
|
||||
```
|
||||
|
||||
**401 Unauthorized**
|
||||
```json
|
||||
{
|
||||
"detail": "Authentication required"
|
||||
}
|
||||
```
|
||||
|
||||
### Example
|
||||
|
||||
#### Request
|
||||
```bash
|
||||
curl -X METHOD http://api.example.com/path/to/endpoint \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Cookie: session=your_session_cookie" \
|
||||
-d '{
|
||||
"field_name": "value"
|
||||
}'
|
||||
```
|
||||
|
||||
#### Response
|
||||
```json
|
||||
{
|
||||
"field_name": "value"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## METHOD /path/to/another/endpoint
|
||||
|
||||
[Repeat the same structure for each endpoint]
|
||||
Vendored
+69
@@ -0,0 +1,69 @@
|
||||
# Architecture Documentation Template
|
||||
|
||||
Use this template when documenting system architecture.
|
||||
|
||||
## System/Component Name
|
||||
|
||||
## Overview
|
||||
|
||||
Provide a high-level description of this system or component.
|
||||
|
||||
## Architecture Diagram
|
||||
|
||||
```
|
||||
[ASCII or Mermaid diagram showing components and relationships]
|
||||
```
|
||||
|
||||
## Components
|
||||
|
||||
### Component 1
|
||||
|
||||
**Purpose:** What this component does
|
||||
|
||||
**Responsibilities:**
|
||||
- Responsibility 1
|
||||
- Responsibility 2
|
||||
|
||||
**Dependencies:**
|
||||
- Dependency 1
|
||||
- Dependency 2
|
||||
|
||||
**Key Files:**
|
||||
- `path/to/file1.py`
|
||||
- `path/to/file2.py`
|
||||
|
||||
### Component 2
|
||||
|
||||
[Repeat for each component]
|
||||
|
||||
## Data Flow
|
||||
|
||||
Describe how data flows through the system.
|
||||
|
||||
```
|
||||
[Step 1] → [Step 2] → [Step 3]
|
||||
```
|
||||
|
||||
## Technology Stack
|
||||
|
||||
| Component | Technology | Purpose |
|
||||
|-----------|-----------|---------|
|
||||
| Component 1 | Technology 1 | Purpose |
|
||||
| Component 2 | Technology 2 | Purpose |
|
||||
|
||||
## Configuration
|
||||
|
||||
Describe relevant configuration options.
|
||||
|
||||
## Security Considerations
|
||||
|
||||
List security considerations for this architecture.
|
||||
|
||||
## Scaling Considerations
|
||||
|
||||
Describe how this scales (or doesn't).
|
||||
|
||||
## Related Documentation
|
||||
|
||||
- [Link to related doc 1](link.md)
|
||||
- [Link to related doc 2](link.md)
|
||||
Vendored
+65
@@ -0,0 +1,65 @@
|
||||
# Feature Documentation Template
|
||||
|
||||
Use this template when documenting a new feature.
|
||||
|
||||
## Feature Name
|
||||
|
||||
## Overview
|
||||
|
||||
Provide a 1-2 sentence description of what this feature does and why it exists.
|
||||
|
||||
## How to Use
|
||||
|
||||
### Prerequisites
|
||||
|
||||
List any prerequisites (e.g., must be logged in, must have project created).
|
||||
|
||||
### Step-by-Step Guide
|
||||
|
||||
1. **Step 1**: Description of first step
|
||||
- Details
|
||||
- Screenshots if applicable
|
||||
|
||||
2. **Step 2**: Description of second step
|
||||
- Details
|
||||
|
||||
3. **Step 3**: Description of third step
|
||||
- Details
|
||||
|
||||
## Screenshots
|
||||
|
||||
Include screenshots or diagrams if they help understanding.
|
||||
|
||||
## API Reference
|
||||
|
||||
List related API endpoints with links to API docs.
|
||||
|
||||
### Endpoints
|
||||
|
||||
- `METHOD /endpoint/path` - Brief description
|
||||
- `METHOD /endpoint/path` - Brief description
|
||||
|
||||
## Configuration
|
||||
|
||||
List any relevant configuration options (environment variables, settings).
|
||||
|
||||
| Option | Description | Default |
|
||||
|--------|-------------|---------|
|
||||
| `VAR_NAME` | What this controls | `default_value` |
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
**Issue**: Description of problem
|
||||
**Solution**: How to fix it
|
||||
|
||||
## Related Features
|
||||
|
||||
- [Feature Name](link.md) - How this relates
|
||||
- [Feature Name](link.md) - How this relates
|
||||
|
||||
## See Also
|
||||
|
||||
- [API Documentation](../api/feature.md)
|
||||
- [Architecture Documentation](../architecture/feature.md)
|
||||
Reference in New Issue
Block a user