用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
直接命令不会经过审查 Prompt;运行前请先检查来源。
npx skills add https://github.com/ubaniak/scoreboard --skill backend-style命令会保持在同一行。复制前请横向滚动并检查完整内容。
想先保存到本地?可下载 SkillsMP 当前能够提供的文件。
基于 SOC 职业分类
正在显示 SKILL.md
| name | backend-style |
| description | When developing backend components follow this style guideline |
You are helping the user add or modify Go backend code in the scoreboard project. When invoked, apply the patterns below exactly as described. Read relevant existing files before writing anything new.
Every domain lives under internal/<domain>/ and follows this exact three-file structure:
internal/<domain>/
app.go ← HTTP handlers + route registration
usecase.go ← business logic (UseCase interface + usecase struct)
storage.go ← Storage interface + NewSqlite constructor shim
entities/
entities.go ← plain Go structs, no GORM tags
storage/
<domain>.go ← GORM model struct
sqlite.go ← Sqlite struct, ToGormModel, ToEntity, CRUD methods
storage.go — interface shimThe package-level Storage interface and a thin NewSqlite wrapper that delegates to the concrete type:
type Storage interface {
Save(…) (uint, error)
List(…) ([]*entities.Foo, error)
Get(…) (*entities.Foo, error)
Delete(…) error
Update(…) error
}
func NewSqlite(db *gorm.DB) (Storage, error) {
return storage.NewSqlite(db)
}
storage/<domain>.go — GORM modelPlain GORM struct with gorm.Model embedded. No business logic.
type Foo struct {
gorm.Model
Name string `gorm:"not null"`
Status string `gorm:"not null"`
}
storage/sqlite.go — Sqlite implementationNewSqlite runs db.AutoMigrate and returns (*Sqlite, error)ToGormModel and ToEntity are value-receiver methods that convert between entity and GORM model*Sqlitetype Sqlite struct { db *gorm.DB }
func NewSqlite(db *gorm.DB) (*Sqlite, error) {
if err := db.AutoMigrate(&Foo{}); err != nil {
return nil, err
}
return &Sqlite{db: db}, nil
}
func (*Sqlite) ToGormModel(e *entities.Foo) *Foo { … }
func (*Sqlite) ToEntity(m Foo) *entities.Foo { … }
usecase.go — business logicUseCase interface listing every operationuseCase struct implements itNewUseCase returns the interface, not the structtype UseCase interface {
Create(…) error
Get(id uint) (*entities.Foo, error)
}
type useCase struct {
storage Storage
}
func NewUseCase(storage Storage) UseCase {
return &useCase{storage: storage}
}
app.go — HTTP layerApp struct holds a UseCase and any cross-domain interfaces (narrow interfaces, not full packages)type CardQuerier interface {
GetNumberOfJudges(cardId uint) (int, error)
}
RegisterRoutes registers every route on the passed-in *rbac.RouteBuilderpresenters.NewHTTPPresenter[T]:
func (a *App) Get(w http.ResponseWriter, r *http.Request) {
id, err := muxutils.ParseIntVar(r, "id")
…
result, err := a.useCase.Get(uint(id))
presenters.NewHTTPPresenter[*entities.Foo](r, w).
WithData(result).
WithError(err).
Present()
}
rbac.Admin or a judge role constant from internal/rbac/roles.goentities/entities.go — plain structsNo GORM tags. No JSON tags on internal domain entities (those go on the DTO/response structs in app.go).
cmd/main.gofooStorage, err := foo.NewSqlite(db)fooUseCase := foo.NewUseCase(fooStorage)fooApp := foo.NewApp(fooUseCase)register.Add(fooApp) under the appropriate subrouter| Thing | Convention |
|---|---|
| GORM model | Foo (same as entity, lives in storage/ package) |
| Entity | Foo (lives in entities/ package) |
| Interface | UseCase, Storage (not IFoo) |
| Constructor | NewFoo, NewUseCase, NewSqlite |
| HTTP handler method | PascalCase verb: Create, List, Get, Update, Delete |
| Route label | "<domain>.<action>" e.g. "bouts.create" |
app.goNewUseCase or NewSqlite at the package boundary — always return the interfaceapp.go — middleware belongs in cmd/main.go or rbac