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
- Copy the
ptpl/directory and rename it (e.g.myfeature/). - Rename the Go package declaration in every file from
package ptpltopackage myfeature. - Replace
PtplItemwith your own GORM model inmodel.go. - Update the route prefix in
routes.go(e.g./ptpl→/myfeature). - Implement your handler logic in
handlers.go. - Add the blank import to
cmd/main.go:_ "bamort/bmrt/myfeature" - 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 setsENVIRONMENT=test, callsdatabase.SetupTestDB(true, true), registers a cleanup withdatabase.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.