一键导入
new-siteadmin-api
// Full pipeline for adding a new Site Admin API feature — from OpenAPI spec through implementation plan to working service. Use when adding a new endpoint or filling in real data for an existing stub.
// Full pipeline for adding a new Site Admin API feature — from OpenAPI spec through implementation plan to working service. Use when adding a new endpoint or filling in real data for an existing stub.
Update Authgear email templates using the correct source files, translation files, and commit order. Use when editing email wording, email structure, or subject lines.
Write or extend Go unit tests in this repo. Use when the user asks to add or update Go tests.
Draft or update detailed implementation plans for authgear-server specs, design changes, and docs/plans files. Use when Codex needs to turn a spec or outdated plan into a concrete implementation plan with exact files, exact methods, runtime call flow, compatibility requirements, test coverage, and atomic commit steps.
Write end-to-end (e2e) tests for authgear-server. Use when the user asks to write, add, or create e2e tests. The tests live in e2e/tests/ and are YAML-driven.
Set up a fresh authgear-server development environment from scratch. Use when onboarding a new contributor, setting up a new machine, or when the user says "set up local dev from scratch" / "first-time setup". Covers the asdf + Homebrew install path on macOS; Nix users should follow CONTRIBUTING.md directly.
Guidelines for updating or designing pages in the portal React frontend (portal/src). Covers component conventions, link rendering rules, i18n patterns, and common pitfalls.
| name | new-siteadmin-api |
| description | Full pipeline for adding a new Site Admin API feature — from OpenAPI spec through implementation plan to working service. Use when adding a new endpoint or filling in real data for an existing stub. |
Add a new Site Admin API feature: $ARGUMENTS
Follow the stages below in order. Confirm with the user before moving from one stage to the next.
Add the endpoint to docs/api/siteadmin-api.yaml:
paths: with appropriate HTTP method(s)$ref: "#/components/responses/BadRequest", Forbidden, NotFound for error responsescomponents/schemas:format: date for date stringsStop here. Present the spec diff to the user and wait for confirmation before proceeding.
make generate
Avoid external type imports:
pkg/siteadmin/model/oapi-codegen.yamlmapsformat: date→stringandformat: email→stringso that nogithub.com/oapi-codegen/runtime/typesimport is generated. If you add a new OpenAPI format that would otherwise pull in an external type, add a mapping tooutput-options.type-mappingin that file before regenerating.
Commit: "[Site Admin] Generate models for <feature>"
Create pkg/siteadmin/transport/handler_<name>.go. No dummy data — leave ServeHTTP as a stub that returns http.NotFound:
package transport
import "net/http"
func Configure<Name>Route(route httproute.Route) httproute.Route {
return route.WithMethods("OPTIONS", "GET"). // always include "OPTIONS" for CORS preflight
WithPathPattern("/api/v1/...")
}
type <Name>Handler struct {
// Service dependencies added in Stage 5
}
type <Name>Params struct {
// path and query params
}
func parse<Name>Params(r *http.Request) (<Name>Params, error) {
// parse path params via httproute.GetParam(r, "paramName")
// query params: use helpers from params.go (getIntParam, getDateParam, etc.)
// POST body: decode JSON, validate with validation.NewSimpleSchema
}
func (h *<Name>Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
_, err := parse<Name>Params(r)
if err != nil {
writeError(w, r, err)
return
}
http.NotFound(w, r)
}
Param validation rules:
"OPTIONS" in WithMethods for CORS preflight"OPTIONS" to avoid duplicate preflight registrations — conventionally the first route registered for that pathwriteError(w, r, err) for all error responsesparams.go: getIntParam, getDateParam, validateMonth, makeValidationErrorapierrors.NewBadRequest for query param errorsmakeValidationError in the transport layer — not the service layerend.After(start.AddDate(1, 0, 0)) — handles leap years; do NOT use end.Sub(start) > 365*24*time.HourWire the handler:
pkg/siteadmin/transport/deps.go — add:
wire.Struct(new(<Name>Handler), "*"),
pkg/siteadmin/wire.go — add injector:
func new<Name>Handler(p *deps.RequestProvider) http.Handler {
panic(wire.Build(DependencySet, wire.Bind(new(http.Handler), new(*transport.<Name>Handler))))
}
pkg/siteadmin/routes.go — register:
router.Add(transport.Configure<Name>Route(route), p.Handler(new<Name>Handler))
Then regenerate:
go generate ./pkg/siteadmin/...
go build ./pkg/siteadmin/...
Commit: "Add <name> handler scaffolding"
Create docs/plans/siteadmin-api/<N>-<name>.md using the /write-implementation-plan skill.
The plan must specify:
Review checklist before confirming:
toExclusive, toInclusive) when it is not obvious from contextfor {} loops — bounded iteration with early return on invalid rangeServeHTTP bodies) includes goanalysis, make sort-vettedpositions, make check-tidyStop here. Present the plan to the user and wait for confirmation before proceeding.
Follow the commit sequence below.
Read the actual source files — do not rely solely on plan code samples, which can drift:
pkg/siteadmin/service/deps.go and pkg/siteadmin/deps.go before touching wire setspkg/siteadmin/service/app_test.go for test package and fake patterns (use package service, not package service_test)SiteAdminAPISuccessResponse{Body: result}.WriteTo(w)
Not json.NewEncoder or manual w.WriteHeader + w.Write.
deps.go)wire.Struct(new(X), "Field1", "Field2") in pkg/siteadmin/deps.gowire.Bind goes in pkg/siteadmin/deps.go, not pkg/siteadmin/service/deps.goend.AddDate(0, 0, 1)time.Date(y, time.Month(m)+1, 1, ...) — no December special casetoEndTimeExclusivetotal := (endYear-startYear)*12 + (endMonth-startMonth) + 1
if total <= 0 {
return &T{Counts: nil}, nil
}
counts := make([]Item, 0, total)
for i := 0; i < total; i++ { ... }
Fakes must filter by the time range parameters (mirror real SQL):
func (f *fakeStore) FetchUsageRecordsInRange(_ context.Context, _ string, name RecordName, _ periodical.Type, from, toExclusive time.Time) ([]*UsageRecord, error) {
var out []*UsageRecord
for _, r := range f.byName[name] {
t := r.StartTime.UTC().Truncate(24 * time.Hour)
if !t.Before(from) && t.Before(toExclusive) {
out = append(out, r)
}
}
return out, nil
}
Always set StartTime on fake records when date-range filtering is involved.
After any deps.go change:
go generate ./pkg/siteadmin/...
go mod tidy
go build ./pkg/siteadmin/...
go build ./cmd/portal/...
.vettedpositionsEvery new r.Context() in pkg/siteadmin/transport/ is flagged. After adding ServeHTTP bodies:
go run ./devtools/goanalysis ./cmd/... ./pkg/...
# add each new position to .vettedpositions
make sort-vettedpositions
go run ./devtools/goanalysis ./cmd/... ./pkg/... # must be clean
make check-tidyRun once, on the last commit only (make check-tidy is slow — it regenerates everything). It reruns wire gen (globally) and make fmt — the output files will be dirty. Stage those regenerated/formatted files and include them in the final commit. Do not re-run make check-tidy after staging.
Commits must be atomic and ordered by dependency — each commit must build and test cleanly on its own. Do not mix content from two different logical parts in one commit. The number of commits will vary with complexity.
Typical ordering (split further if a step is large):
| Layer | Typical contents | Verification |
|---|---|---|
| Service | Service file + test + service/deps.go | go test ./pkg/siteadmin/service/... · make fmt |
| Transport interfaces | Narrow interfaces on handlers + Service fields (no ServeHTTP bodies yet) | go build ./pkg/siteadmin/... · make fmt |
| DI wiring | pkg/siteadmin/deps.go + wire gen + go mod tidy | build both packages · make fmt |
| Handler bodies | ServeHTTP implementations + .vettedpositions + make check-tidy output | go test ./pkg/siteadmin/... · make fmt · make lint · goanalysis · make sort-vettedpositions · make check-tidy |
If a feature requires additional layers (e.g. a new DB store method, a shared helper, a schema migration, a refactor of existing code), add commits for those between the layers above. Never combine, for example, the service layer and the DI wiring in one commit. Refactors to existing code always get their own commit.