Skip to content

Commit 47e1dbc

Browse files
authored
Merge pull request #8 from cantis/feature/code-improvement
feat: add Flask-WTF dependency for form handling
2 parents 5deced7 + 9276070 commit 47e1dbc

73 files changed

Lines changed: 6737 additions & 614 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.env.example

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,7 @@ SECRET_KEY=dev-secret-change-in-production
1515
# Flask debug mode (set to 1 or true to enable)
1616
# WARNING: Only enable for development, never in production
1717
DEBUG=false
18+
19+
# Require SECRET_KEY for server/Docker deployments (set to 1 in production)
20+
# REQUIRED automatically when /data exists (Docker). Optional for local desktop use.
21+
# REQUIRE_SECRET_KEY=1

.github/AGENTS.md

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
# MechBay Copilot Instructions
2+
3+
This repository uses scoped instruction files under `.github/instructions/`.
4+
Follow those files when their `applyTo` patterns match the files being changed.
5+
6+
## Important project references
7+
8+
- Read `docs/ARCHITECTURE.md` before changing service boundaries, session handling, route patterns, or model relationships.
9+
- Read `docs/TESTING.md` before adding or changing tests.
10+
- Read `docs/SECURITY.md` before touching CSRF configuration, file uploads, input validation, logging, or secret key handling.
11+
- Read `docs/DEVELOPMENT.md` for local setup, migration commands, Docker workflow, and environment variables.
12+
- Read `docs/DESIGN.md` before changing UI layout, templates, flash messages, or reusable components.
13+
14+
## Overview
15+
16+
Flask web app for managing BattleTech miniature inventories and organizing forces. Three main entities: **Miniatures** (individual models), **Forces** (collections of lances), and **Lance Templates** (reusable lance configurations).
17+
18+
- **Backend**: Flask 3.1+, SQLAlchemy 2.0+, SQLite, Waitress (production)
19+
- **Frontend**: Bootstrap 5.3, Font Awesome 6.4, SortableJS — all via CDN; server-rendered Jinja2
20+
- **Package manager**: `uv` — use PowerShell for all commands
21+
- **Python**: 3.13+ required
22+
23+
## Directory Layout
24+
25+
```
26+
app/
27+
__init__.py # Application factory: create_app()
28+
config.py # Config class; reads from environment / .env
29+
extensions.py # SQLAlchemy setup and session_scope()
30+
blueprints/ # Thin route controllers (one file per area)
31+
models/ # SQLAlchemy models (one file per model)
32+
services/ # Business logic (one file per domain)
33+
templates/ # Jinja2 templates; base.html + per-area folders
34+
static/ # Custom CSS and JS; libraries via CDN
35+
tests/ # pytest; conftest.py has all shared fixtures
36+
docs/ # Architecture, development, design, security docs
37+
main.py # Dev entry point (debug=True, port 5001)
38+
server.py # Production entry point (Waitress)
39+
Dockerfile # Docker image
40+
```
41+
42+
## Blueprint / Route Structure
43+
44+
| Blueprint | Prefix | File |
45+
|-------------------|--------------------|-------------------------------------|
46+
| `miniatures` | `/miniatures` | `app/blueprints/miniatures.py` |
47+
| `forces` | `/forces` | `app/blueprints/forces.py` |
48+
| `lance_templates` | `/lance-templates` | `app/blueprints/lance_templates.py` |
49+
| *(root)* | `/` | registered in `app/__init__.py` |
50+
51+
## Architecture Patterns
52+
53+
### Session Management
54+
55+
Always use `session_scope()` from `app/extensions.py`. Always `session.expunge()` objects before returning from service functions.
56+
57+
```python
58+
from ..extensions import session_scope
59+
60+
def get_force_by_id(force_id: int) -> Force | None:
61+
with session_scope() as session:
62+
force = session.get(Force, force_id)
63+
if force:
64+
for lance in force.lances:
65+
_ = lance.miniatures
66+
session.expunge(force) # Critical: prevents DetachedInstanceError
67+
return force
68+
```
69+
70+
### Dual-Mode Routes (JSON + Form)
71+
72+
Routes support both JSON (AJAX) and form submissions. Standard JSON envelope:
73+
74+
```python
75+
{"success": bool, "error": str | None, "data": dict | None}
76+
```
77+
78+
HTTP status codes: `200` success · `400` bad input · `404` not found · `409` conflict
79+
80+
Rules:
81+
1. Validate `int()` conversions in `try/except` at route level — return 400 on failure
82+
2. Set flash messages **before** the `if is_json` check — JS uses `setTimeout(() => location.reload(), 100)`
83+
3. Use `request.get_json(silent=True) or request.form``silent=True` prevents Content-Type errors
84+
4. Always include `"success"` key in every JSON response
85+
86+
### SQLAlchemy Delete Pattern
87+
88+
```python
89+
# ❌ Fails with joins
90+
session.query(ForceMiniature).join(Lance).filter(...).delete()
91+
92+
# ✅ Correct
93+
records = session.query(ForceMiniature).join(Lance).filter(...).all()
94+
for record in records:
95+
session.delete(record)
96+
```
97+
98+
## Key Model Relationships
99+
100+
- `Force` → many `Lance` (cascade delete)
101+
- `Lance` → many `ForceMiniature` (join table with position ordering)
102+
- `ForceMiniature` → one `Miniature` (reference, not cascade)
103+
- `LanceTemplate` → many `LanceTemplateMiniature` (chassis patterns for auto-matching)
104+
105+
**Active Force**: Only one force can be `is_active=True` at a time.
106+
107+
**Miniature naming**: Chassis (`"Warhammer"`), Prefix (`"WHM"`), Type (`"WHM-6R"`). Template matching uses chassis name substring.
108+
109+
## Development Workflow
110+
111+
```powershell
112+
uv sync # Install dependencies
113+
uv run python .\main.py # Start dev server (port 5001)
114+
uv run python -m app.migrations # Create/update schema
115+
uv run python -m app.seed # Load demo data
116+
uv run pytest -q # Run tests
117+
uv run ruff check . # Lint
118+
```
119+
120+
Tests use in-memory SQLite via `create_app({"DATABASE_URL": "sqlite+pysqlite:///:memory:"})`.
121+
122+
## Key Environment Variables
123+
124+
See `docs/DEVELOPMENT.md` for the full table.
125+
126+
- `SECRET_KEY` — Flask session/CSRF signing key (random ephemeral default; **set in production**)
127+
- `DATABASE_URL` — defaults to `%APPDATA%\MechBay\mechbay.db` (Windows) or `/data/mechbay.db` (Docker)
128+
- `DEBUG` — enables debug mode and colourized logs
129+
- `TRUST_PROXY_HEADERS` — set `true` only behind a trusted reverse proxy
130+
- `APPLICATION_ROOT` — path prefix for reverse-proxy deployments
131+
132+
## File Naming Conventions
133+
134+
Export files: `{EntityType}_YYYYMMDD_HHMMSS.json`
135+
Generate with: `datetime.now().strftime("%Y%m%d_%H%M%S")`

.github/copilot-instructions.md

Lines changed: 85 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -1,123 +1,136 @@
11
# MechBay Copilot Instructions
22

3-
## Project Overview
3+
This repository uses scoped instruction files under `.github/instructions/`.
4+
Follow those files when their `applyTo` patterns match the files being changed.
5+
6+
## Important project references
7+
8+
- Read `docs/ARCHITECTURE.md` before changing service boundaries, session handling, route patterns, or model relationships.
9+
- Read `docs/TESTING.md` before adding or changing tests.
10+
- Read `docs/SECURITY.md` before touching CSRF configuration, file uploads, input validation, logging, or secret key handling.
11+
- Read `docs/DEVELOPMENT.md` for local setup, migration commands, Docker workflow, and environment variables.
12+
- Read `docs/DESIGN.md` before changing colors, typography, spacing, or component visual styling (machine-readable design token spec).
13+
- Read `docs/UI-CONVENTIONS.md` before changing templates, Jinja structure, flash messages, icons, or JavaScript interaction patterns.
14+
15+
## Overview
16+
417
Flask web app for managing BattleTech miniature inventories and organizing forces. Three main entities: **Miniatures** (individual models), **Forces** (collections of lances), and **Lance Templates** (reusable lance configurations).
518

19+
- **Backend**: Flask 3.1+, SQLAlchemy 2.0+, SQLite, Waitress (production)
20+
- **Frontend**: Bootstrap 5.3, Font Awesome 6.4, SortableJS — all via CDN; server-rendered Jinja2
21+
- **Package manager**: `uv` — use PowerShell for all commands
22+
- **Python**: 3.13+ required
23+
24+
## Directory Layout
25+
26+
```
27+
app/
28+
__init__.py # Application factory: create_app()
29+
config.py # Config class; reads from environment / .env
30+
extensions.py # SQLAlchemy setup and session_scope()
31+
blueprints/ # Thin route controllers (one file per area)
32+
models/ # SQLAlchemy models (one file per model)
33+
services/ # Business logic (one file per domain)
34+
templates/ # Jinja2 templates; base.html + per-area folders
35+
static/ # Custom CSS and JS; libraries via CDN
36+
tests/ # pytest; conftest.py has all shared fixtures
37+
docs/ # Architecture, development, design, security docs
38+
main.py # Dev entry point (debug=True, port 5001)
39+
server.py # Production entry point (Waitress)
40+
Dockerfile # Docker image
41+
```
42+
43+
## Blueprint / Route Structure
44+
45+
| Blueprint | Prefix | File |
46+
|-------------------|--------------------|-------------------------------------|
47+
| `miniatures` | `/miniatures` | `app/blueprints/miniatures.py` |
48+
| `forces` | `/forces` | `app/blueprints/forces.py` |
49+
| `lance_templates` | `/lance-templates` | `app/blueprints/lance_templates.py` |
50+
| *(root)* | `/` | registered in `app/__init__.py` |
51+
652
## Architecture Patterns
753

854
### Session Management
9-
- Use `session_scope()` context manager from `app/extensions.py` for all DB operations
10-
- Always `session.expunge()` objects before returning from service functions to prevent detached instance errors
11-
- Service layer (`app/services/`) handles all business logic and DB transactions
12-
- Blueprints (`app/blueprints/`) are thin controllers that call services
1355

14-
Example:
56+
Always use `session_scope()` from `app/extensions.py`. Always `session.expunge()` objects before returning from service functions.
57+
1558
```python
1659
from ..extensions import session_scope
1760

1861
def get_force_by_id(force_id: int) -> Force | None:
1962
with session_scope() as session:
2063
force = session.get(Force, force_id)
2164
if force:
22-
# Eager load relationships within session
2365
for lance in force.lances:
2466
_ = lance.miniatures
25-
session.expunge(force) # Critical: make accessible outside session
67+
session.expunge(force) # Critical: prevents DetachedInstanceError
2668
return force
2769
```
2870

2971
### Dual-Mode Routes (JSON + Form)
30-
Routes support both JSON (AJAX) and form submissions. Detect request type and respond appropriately:
72+
73+
Routes support both JSON (AJAX) and form submissions. Standard JSON envelope:
3174

3275
```python
33-
@bp.route("/<int:id>/add-miniature", methods=["POST"])
34-
def add_miniature(id: int):
35-
is_json = request.is_json
36-
data = request.get_json(silent=True) or request.form # silent=True prevents Content-Type errors
37-
38-
# ... business logic ...
39-
40-
if is_json:
41-
return jsonify({"success": True}), 200
42-
else:
43-
flash("Miniature added to lance", "success")
44-
return redirect(url_for("miniatures.list_miniatures"))
76+
{"success": bool, "error": str | None, "data": dict | None}
4577
```
4678

47-
**Key**: Set flash messages BEFORE the `if is_json` check when using AJAX + page reload (see `forces.remove_miniature`). JavaScript uses `setTimeout(() => location.reload(), 100)` to allow flash message to persist.
79+
HTTP status codes: `200` success · `400` bad input · `404` not found · `409` conflict
80+
81+
Rules:
82+
1. Validate `int()` conversions in `try/except` at route level — return 400 on failure
83+
2. Set flash messages **before** the `if is_json` check — JS uses `setTimeout(() => location.reload(), 100)`
84+
3. Use `request.get_json(silent=True) or request.form``silent=True` prevents Content-Type errors
85+
4. Always include `"success"` key in every JSON response
4886

4987
### SQLAlchemy Delete Pattern
50-
Cannot call `.delete()` on queries with joins. Use this pattern instead:
5188

5289
```python
5390
# ❌ Fails with joins
5491
session.query(ForceMiniature).join(Lance).filter(...).delete()
5592

56-
# ✅ Correct pattern
93+
# ✅ Correct
5794
records = session.query(ForceMiniature).join(Lance).filter(...).all()
5895
for record in records:
5996
session.delete(record)
6097
```
6198

62-
## File Naming Conventions
63-
- Export files use timestamp format: `{EntityType}_YYYYMMDD_HHMMSS.json`
64-
- Example: `Miniature_Inventory_20251120_143025.json`
65-
- Generate with: `datetime.now().strftime("%Y%m%d_%H%M%S")`
66-
67-
## Frontend Patterns
99+
## Key Model Relationships
68100

69-
### SortableJS Drag-and-Drop
70-
Forces page uses SortableJS for miniature reordering between lances. On drop:
71-
1. Update UI immediately (optimistic)
72-
2. POST new positions to backend
73-
3. Backend validates and saves
101+
- `Force` → many `Lance` (cascade delete)
102+
- `Lance` → many `ForceMiniature` (join table with position ordering)
103+
- `ForceMiniature` → one `Miniature` (reference, not cascade)
104+
- `LanceTemplate` → many `LanceTemplateMiniature` (chassis patterns for auto-matching)
74105

75-
### Auto-Fading Flash Messages
76-
Flash messages auto-dismiss after 3 seconds using Bootstrap Alert component (see `app/templates/base.html`). Include close button with `alert-dismissible fade show` classes.
106+
**Active Force**: Only one force can be `is_active=True` at a time.
77107

78-
### Editable Elements
79-
Double-click elements with `.editable-lance-name` class to edit inline. Use `prompt()` for input, fetch API to save, update DOM on success.
108+
**Miniature naming**: Chassis (`"Warhammer"`), Prefix (`"WHM"`), Type (`"WHM-6R"`). Template matching uses chassis name substring.
80109

81110
## Development Workflow
82111

83-
### Running the App
84-
```powershell
85-
uv sync # Install dependencies
86-
uv run python .\main.py # Start dev server (debug=True)
87-
```
88-
89-
### Database Operations
90-
```powershell
91-
uv run python -m app.migrations # Create/update schema
92-
uv run python -m app.seed # Populate sample data (6 lance templates)
93-
```
94-
95-
### Testing
96112
```powershell
97-
uv run pytest -q # Run tests (uses in-memory SQLite)
98-
uv run ruff check . # Lint
113+
uv sync # Install dependencies
114+
uv run python .\main.py # Start dev server (port 5001)
115+
uv run python -m app.migrations # Create/update schema
116+
uv run python -m app.seed # Load demo data
117+
uv run pytest -q # Run tests
118+
uv run ruff check . # Lint
99119
```
100120

101-
Tests use `conftest.py` fixture that creates fresh in-memory DB per test via `create_app({"DATABASE_URL": "sqlite+pysqlite:///:memory:"})`.
102-
103-
## Key Relationships
104-
- `Force` → many `Lance` (cascade delete)
105-
- `Lance` → many `ForceMiniature` (join table with position ordering)
106-
- `ForceMiniature` → one `Miniature` (reference, not cascade)
107-
- `LanceTemplate` → many `LanceTemplateMiniature` (chassis patterns for auto-matching)
121+
Tests use in-memory SQLite via `create_app({"DATABASE_URL": "sqlite+pysqlite:///:memory:"})`.
108122

109-
**Active Force**: Only one force can be `is_active=True` at a time. Used for quick miniature assignment from inventory screen.
123+
## Key Environment Variables
110124

111-
### Miniature Naming Convention
112-
- **Chassis**: Base model name (e.g., "Warhammer")
113-
- **Prefix**: Chassis designator/acronym (e.g., "WHM")
114-
- **Type**: Variant code (e.g., "WHM-6R", "WHM-7M")
125+
See `docs/DEVELOPMENT.md` for the full table.
115126

116-
Lance template matching uses chassis name - "Warhammer" pattern matches both "Warhammer WHM-6R" and "Warhammer WHM-7M" variants.
127+
- `SECRET_KEY` — Flask session/CSRF signing key (random ephemeral default; **set in production**)
128+
- `DATABASE_URL` — defaults to `%APPDATA%\MechBay\mechbay.db` (Windows) or `/data/mechbay.db` (Docker)
129+
- `DEBUG` — enables debug mode and colourized logs
130+
- `TRUST_PROXY_HEADERS` — set `true` only behind a trusted reverse proxy
131+
- `APPLICATION_ROOT` — path prefix for reverse-proxy deployments
117132

118-
## Import/Export
119-
All import functions support merge mode (match on key field) or overwrite mode. Export generates timestamped filenames. See `app/services/*_service.py` for implementations.
133+
## File Naming Conventions
120134

121-
## Future Considerations
122-
- **Deployment**: Plans to migrate to Gunicorn + Docker for production
123-
- **Scope**: Currently focused on physical miniature inventory management, not game mechanics (pilot skills, special abilities, detailed loadouts)
135+
Export files: `{EntityType}_YYYYMMDD_HHMMSS.json`
136+
Generate with: `datetime.now().strftime("%Y%m%d_%H%M%S")`
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
---
2+
applyTo: "**/*.py"
3+
---
4+
5+
Repository-specific Python style and instructions
6+
7+
These instructions capture Python-specific conventions used in this repository. Keep them short and actionable.
8+
9+
- Naming and typing:
10+
- Follow PEP 8 naming for modules, functions, classes, and variables.
11+
- Add type annotations for all functions and variables. Prefer `X | None` for optionals (not `Optional[X]`).
12+
- Use modern built-in union types (e.g., `str | None`, `list[int]`).
13+
14+
- Imports and formatting:
15+
- Keep imports sorted and grouped as enforced by `ruff`.
16+
- Run linting and formatting with the project wrapper: `uv run ruff check .` and `uv run ruff format .`.
17+
18+
- Models and typing:
19+
- One model per file under `src/models/` (avoid a single `models.py`).
20+
- SQLAlchemy model classes should include explicit `__init__` methods to aid type checkers and IDEs.
21+
- Store datetimes as ISO 8601 strings with UTC timezone (use timezone-aware datetimes everywhere).
22+
23+
- Application structure and separation of concerns:
24+
- Use the application factory pattern in `src/app.py` and register Blueprints there.
25+
- Keep routes thin: push business logic into the `services/` layer. Services should be the primary place for complex logic and DB interactions.
26+
27+
- Logging and diagnostics:
28+
- Do not add application-level logging unless a reviewer or a task explicitly requests it.
29+
30+
- Tests:
31+
- Write tests using Arrange / Act / Assert structure.
32+
- Use `with app.app_context():` for DB operations in tests.
33+
- Import models inside test functions (to avoid circular imports).
34+
- Use direct route paths (e.g., `/followups`) in tests instead of `url_for()` to avoid Flask context issues.
35+
36+
- Miscellaneous:
37+
- Follow repository conventions from `.github/AGENTS.md` for commands and environment usage (PowerShell + `uv` wrapper, set `PYTHONPATH` as required when running tests or CLI tasks).
38+

0 commit comments

Comments
 (0)