Files
bamort/backend/bmrt/ptpl/README.md
2026-04-22 09:13:47 +02:00

4.5 KiB

ptpl — Package Template

ptpl is a copy-paste template for new Bamort backend modules. It contains the full set of required files and wiring so a new module can be added by copying this package, renaming it, and filling in domain logic.


Purpose

Every Bamort backend module must follow the same structure so the registry-based auto-registration works correctly. ptpl demonstrates this structure with minimal but complete sample code:

  • one public route (no authentication required)
  • one protected route group (requires a valid JWT)
  • a GORM model with auto-migration
  • handler tests covering happy paths, error paths, and user-isolation

File Structure

File Required Responsibility
model.go yes GORM entity definitions owned by this module
register.go yes init() — registers routes and migration with the central registry
routes.go yes RegisterRoutes / RegisterPublicRoutes — maps URL paths to handlers
handlers.go yes Gin handler functions (HTTP controllers)
database.go when needed MigrateStructure and other DB helpers specific to this module
handlers_test.go yes Tests for all handlers

Registration Flow

Modules register themselves via init() so main.go never needs to call module code directly — a single blank import is all that is needed.

main.go
  └─ _ "bamort/bmrt/ptpl"          ← blank import triggers init()
       └─ init()
            ├─ registry.RegisterRoutes(RegisterRoutes)         → /api/ptpl/*
            ├─ registry.RegisterPublicRoutes(RegisterPublicRoutes) → /public/ptpl/*
            └─ registry.RegisterMigration(MigrateStructure)   → AutoMigrate on startup

At startup main.go calls the registry run-functions which iterate over all registered callbacks:

registry.RunAllMigrations(db)     // calls MigrateStructure
registry.RunAllRoutes(protected)  // calls RegisterRoutes   (under /api, auth required)
registry.RunAllPublicRoutes(r)    // calls RegisterPublicRoutes (no auth)

Routes

Public (no authentication)

Method Path Handler Description
GET /public/ptpl/info GetPublicInfo Returns module status information

Protected (JWT required)

Method Path Handler Description
GET /api/ptpl ListItems Lists all items owned by the authenticated user
POST /api/ptpl CreateItem Creates a new item for the authenticated user
GET /api/ptpl/:id GetItem Returns a single item (only if owned by the caller)

Role-restricted sub-groups are applied in routes.go using middleware — never inside a handler:

admin := grp.Group("")
admin.Use(user.RequireAdmin())
admin.DELETE("/:id", DeleteItem)  // admin only

Adding Role Restrictions

Import bamort/user and apply middleware to a sub-group in routes.go:

import "bamort/user"

func RegisterRoutes(r *gin.RouterGroup) {
    grp := r.Group("/ptpl")
    grp.GET("", ListItems)          // all authenticated users

    maintainer := grp.Group("")
    maintainer.Use(user.RequireMaintainer())
    maintainer.POST("", CreateItem) // maintainer + admin only

    admin := grp.Group("")
    admin.Use(user.RequireAdmin())
    admin.DELETE("/:id", DeleteItem) // admin only
}

Role hierarchy (ascending): standard < maintainer < admin.


Creating a New Module from This Template

  1. Copy the ptpl/ directory and rename it (e.g. myfeature/).
  2. Rename the Go package declaration in every file from package ptpl to package myfeature.
  3. Replace PtplItem with your own GORM model in model.go.
  4. Update the route prefix in routes.go (e.g. /ptpl/myfeature).
  5. Implement your handler logic in handlers.go.
  6. Add the blank import to cmd/main.go:
    _ "bamort/bmrt/myfeature"
    
  7. Run go test ./bmrt/myfeature/ — all tests should pass before adding new ones.

Testing Conventions

Every test file must:

  • define a local setupTestEnvironment(t *testing.T) that sets ENVIRONMENT=test, calls database.SetupTestDB(true, true), registers a cleanup with database.ResetTestDB, and sets Gin to test mode.
  • call setupTestEnvironment(t) as the first statement of every top-level test function.
  • cover the happy path, the error / not-found path, and user-isolation (data from another user must not be returned) for each handler.
  • use t.Run("scenario", ...) subtests for multiple input variants of the same handler.