added desired module layout
and debugging information
This commit is contained in:
@@ -15,48 +15,65 @@ Bamort is a role-playing game character management system (MOAM replacement) wit
|
||||
## Backend Architecture (`backend/`)
|
||||
|
||||
### Module Structure
|
||||
Each domain module follows this pattern (e.g., `character/`, `pdfrender/`, `equipment/`):
|
||||
```
|
||||
module/
|
||||
handlers.go # HTTP handlers (Gin controllers)
|
||||
routes.go # Route registration: RegisterRoutes(r *gin.RouterGroup)
|
||||
*_test.go # Tests with setupTestEnvironment(t)
|
||||
```
|
||||
Every backend module must contain exactly these files:
|
||||
|
||||
**Key conventions:**
|
||||
- **Entry point**: `cmd/main.go` registers all modules via `RegisterRoutes(protected)`
|
||||
- **Models**: `models/` contains GORM entities (e.g., `Char`, `SkFertigkeit`, `EqWaffe`)
|
||||
- **Database**: Shared `database.DB` instance, use `models.MigrateStructure(db)` for migrations
|
||||
- **Configuration**: `config.Cfg` loaded from env vars (see `config/config.go`)
|
||||
- `TEMPLATES_DIR` for PDF templates (default: `./templates`)
|
||||
- `ENVIRONMENT=test|development|production`
|
||||
- `DATABASE_TYPE=mysql|sqlite`
|
||||
| File | Required | Content |
|
||||
|---|---|---|
|
||||
| `handlers.go` | yes | All HTTP handler functions for the module (Gin controllers)|
|
||||
| `routes.go` | yes | Route definitions; calls `RegisterRoutes` |
|
||||
| `register.go` | yes | `init()` — registers routes, models, and migrations with the application |
|
||||
| `model.go` | yes | GORM entity definitions owned by this module |
|
||||
| `*_test.go` | yes | At least one test file; more files allowed by sub-domain |
|
||||
| `database.go` | when needed | Module-specific DB query helpers |
|
||||
|
||||
Handler files may be split by sub-domain (e.g. `lerncost_handler.go`, `share_handlers.go`) when `handlers.go` grows large. The split files stay in the same package.
|
||||
|
||||
**Models**: each module defines its own GORM entities in `model.go`. The shared `models/` package contains only types that are referenced across multiple modules.
|
||||
|
||||
**Configuration**: `config.Cfg` loaded from env vars — `ENVIRONMENT`, `DATABASE_TYPE`, `TEMPLATES_DIR`, etc.
|
||||
|
||||
### Route Ownership Map
|
||||
New endpoints must be added to the module that owns their prefix. the prefixes can be foundin the routes.go files.
|
||||
|
||||
Use `RequireAdmin()` or `RequireMaintainer()` middleware on sub-routes that need elevated roles — never check roles inside a handler.
|
||||
|
||||
### Module Dependencies
|
||||
Modules should be self contained as far as possible. If a module needs to reference another module's data, it should import that module's package and use its exported functions — do not duplicate logic or data structures. If this means circular references, consider whether the shared logic should be moved to a common package (e.g. `utils/` or `models/`) of an exeption for duplicating logic or data structures must be made.
|
||||
|
||||
### Testing Requirements
|
||||
- **NEVER** create test files with `main()` functions
|
||||
- **ALWAYS** use `_test.go` suffix
|
||||
- **ALWAYS** call `setupTestEnvironment(t)` at start of each test:
|
||||
```go
|
||||
func setupTestEnvironment(t *testing.T) {
|
||||
original := os.Getenv("ENVIRONMENT")
|
||||
os.Setenv("ENVIRONMENT", "test")
|
||||
t.Cleanup(func() { /* restore */ })
|
||||
}
|
||||
```
|
||||
- Use `testutils.SetupTestDB()` for database tests (creates SQLite test DB)
|
||||
- Test character ID 18 (Fanjo Vetrani) exists in test database
|
||||
- **NEVER** create test files with `main()` — always use `_test.go`
|
||||
- **ALWAYS** call `setupTestEnvironment(t)` at the top of every test function
|
||||
- Use `testutils.SetupTestDB()` for any test that touches the database (creates an isolated SQLite DB)
|
||||
- Test character ID 18 (Fanjo Vetrani) is pre-seeded in the test DB
|
||||
|
||||
**Every handler must have tests covering:**
|
||||
1. Happy path — expected 2xx response with correct body
|
||||
2. Error / not-found path — expected 4xx response
|
||||
3. Unauthorized access — expected 401 (no token) or 403 (wrong role) for protected routes
|
||||
|
||||
Use `t.Run("scenario", ...)` subtests when a handler has multiple input variants. Group related scenarios in one table-driven test rather than writing separate top-level test functions.
|
||||
|
||||
### Role Checking
|
||||
Roles in ascending order: `standard` < `maintainer` < `admin`.
|
||||
|
||||
- `user.RequireAdmin()` — allows only `admin`; returns 403 for all other roles
|
||||
- `user.RequireMaintainer()` — allows `maintainer` and `admin`; returns 403 otherwise
|
||||
- **Apply these as Gin middleware in `routes.go` on a sub-group — never read the role inside a handler**
|
||||
|
||||
```go
|
||||
func RegisterRoutes(r *gin.RouterGroup) {
|
||||
group := r.Group("/maintenance")
|
||||
group.GET("/skills", listSkills) // all authenticated users
|
||||
|
||||
protected := group.Group("")
|
||||
protected.Use(user.RequireMaintainer())
|
||||
protected.POST("/skills", createSkill) // maintainer + admin only
|
||||
}
|
||||
```
|
||||
|
||||
### API Patterns
|
||||
- Protected routes under `/api` prefix require JWT authentication
|
||||
- Use `respondWithError(c, status, message)` for error responses
|
||||
- Route registration example:
|
||||
```go
|
||||
func RegisterRoutes(r *gin.RouterGroup) {
|
||||
group := r.Group("/module")
|
||||
group.GET("/list", ListHandler)
|
||||
group.POST("/create", CreateHandler)
|
||||
}
|
||||
```
|
||||
- Protected routes under `/api` prefix require JWT — `user.AuthMiddleware()` is applied globally
|
||||
- Use `respondWithError(c, status, message)` for all error responses
|
||||
|
||||
## Docker Development Workflow
|
||||
|
||||
@@ -97,6 +114,33 @@ go test -v ./character/
|
||||
- HMR auto-reloads on file save
|
||||
- Check browser console and `docker logs bamort-frontend-dev`
|
||||
|
||||
## Debugging & Bugfixing
|
||||
|
||||
Both Docker containers are always running. Use them directly — no restart needed.
|
||||
|
||||
**Read live logs (Air/Vite output, compile errors, runtime panics):**
|
||||
```bash
|
||||
docker logs bamort-backend-dev -f --tail=50
|
||||
docker logs bamort-frontend-dev -f --tail=20
|
||||
```
|
||||
|
||||
**Test API endpoints directly:**
|
||||
```bash
|
||||
# Public — no token needed
|
||||
curl -s http://localhost:8180/api/public/version
|
||||
|
||||
# Authenticated — copy token from browser DevTools → Application → localStorage → 'token'
|
||||
curl -s -H "Authorization: Bearer <token>" http://localhost:8180/api/characters
|
||||
```
|
||||
|
||||
**Inspect database:** phpMyAdmin at http://localhost:8082
|
||||
|
||||
**Backend test failures** run against an isolated SQLite DB (`testutils.SetupTestDB()`) — they are independent of the running MariaDB container.
|
||||
|
||||
**Air** recompiles the backend automatically on file save — compile errors appear immediately in `docker logs bamort-backend-dev`.
|
||||
|
||||
**Vite HMR** reloads the frontend on file save — build errors appear in `docker logs bamort-frontend-dev` and the browser console.
|
||||
|
||||
## PDF Rendering Module (`pdfrender/`)
|
||||
|
||||
- Uses `chromedp` for HTML→PDF (requires Chromium in Docker)
|
||||
|
||||
Reference in New Issue
Block a user