Files
summercms/cabana/admin_openapi.go
Jakub Zych fe04dbc89e feat(10-02): relation field options and relation saves with labels
- FieldRelationContract/FieldRelationProvider bind every type: relation
  field to a belongsTo foreign key or a belongsToMany pivot; activation
  fails naming plugin, controller and field on a missing or broken contract
- GET /{vendor}/{plugin}/{controller}/fields/{field}/options serves
  {value, label} pages scoped by pact.RelationExtendOptionsQuery, behind
  the controller permission; read-only and non-relation fields are 404
- Saves apply present relation keys after the Before hook: ids are
  revalidated through the same scoped query (422 and full rollback
  otherwise), belongsTo sets the foreign key, belongsToMany replaces pivot
  rows in submitted order with the order column set to the index
- Show, create and update return relation values in data and meta.labels
- A belongsTo on a protected fill key is read-only (D-26)
- One six-segment GET pattern dispatches relation lists and field options,
  which ServeMux cannot register side by side
- Admin OpenAPI documents the options route and RecordEnvelope
2026-09-27 16:00:52 +02:00

462 lines
15 KiB
Go

package cabana
// @title SummerCMS Admin API
// @version 1
// @description Framework admin API consumed by the embedded admin SPA. Every path is relative to {backend.uri}/api/v1 (for example /backend/api/v1). The SPA authenticates with the HttpOnly summer_admin cookie set by a login that sends X-Requested-With: XMLHttpRequest, and sends that header on every request; CLI clients and tests send the BackendBearer Authorization header instead.
// @BasePath /
// @securityDefinitions.apikey BackendBearer
// @in header
// @name Authorization
// @description Backend admin bearer token. Send "Bearer {access_token}".
// Admin API annotations. scripts/check-admin-openapi.sh reads them with swag
// to produce admin/openapi/admin.json, the document the SPA's TypeScript types
// are generated from (D-15). The functions are not mounted; service.mount in
// http.go is the runtime route table, and TestPhase09PermissionMatrix plus
// TestPhase09ContractInventory fail if the two lists diverge.
// ErrorBody is one D-10 error object.
type ErrorBody struct {
Code string `json:"code"`
Message string `json:"message"`
Details map[string]any `json:"details"`
}
// ErrorEnvelope is the D-10 error envelope.
type ErrorEnvelope struct {
Error ErrorBody `json:"error"`
}
// SuccessMeta is the D-10 meta object.
type SuccessMeta struct {
Locale string `json:"locale,omitempty"`
Page int `json:"page,omitempty"`
PerPage int `json:"per_page,omitempty"`
Total int `json:"total,omitempty"`
LastPage int `json:"last_page,omitempty"`
}
// SuccessEnvelope is the D-10 success envelope.
type SuccessEnvelope struct {
Data any `json:"data"`
Meta SuccessMeta `json:"meta"`
}
// AdminLoginData is the admin login and refresh payload. Bearer transport
// carries access_token; cookie transport (X-Requested-With: XMLHttpRequest)
// carries token_type "cookie" and expires_in, never the token.
type AdminLoginData struct {
AccessToken string `json:"access_token,omitempty"`
TokenType string `json:"token_type"`
ExpiresIn int `json:"expires_in,omitempty"`
}
// Envelope is the typed D-10 success envelope.
type Envelope[T any] struct {
Data T `json:"data"`
Meta SuccessMeta `json:"meta"`
}
// ListEnvelope is the typed D-10 paginated envelope (Phase 9 D-11 meta).
type ListEnvelope[T any] struct {
Data T `json:"data"`
Meta ListMeta `json:"meta"`
}
// AdminRecord is one admin record: a string-keyed map read through its
// list or form schema (D-16).
type AdminRecord map[string]any
// AdminLoginRequest is the admin login body. Either login or email
// identifies the backend user.
type AdminLoginRequest struct {
Login string `json:"login,omitempty"`
Email string `json:"email,omitempty"`
Password string `json:"password"`
}
// AdminRoleSummary is the role attached to an admin profile.
type AdminRoleSummary struct {
ID uint `json:"id"`
Code string `json:"code"`
Name string `json:"name"`
}
// AdminProfile is the GET /auth/me payload.
type AdminProfile struct {
ID uint `json:"id"`
Login string `json:"login"`
Email string `json:"email"`
FirstName string `json:"first_name"`
LastName string `json:"last_name"`
IsSuperuser bool `json:"is_superuser"`
Role *AdminRoleSummary `json:"role,omitempty"`
}
// AdminLogin documents POST /auth/login.
//
// @Summary Admin login
// @Tags admin
// @Accept json
// @Produce json
// @Param body body AdminLoginRequest true "Credentials"
// @Param X-Requested-With header string false "XMLHttpRequest selects cookie transport"
// @Success 200 {object} Envelope[AdminLoginData]
// @Failure 401 {object} ErrorEnvelope
// @Router /auth/login [post]
func AdminLogin() {}
// AdminRefresh documents POST /auth/refresh.
//
// @Summary Refresh an admin token
// @Tags admin
// @Accept json
// @Produce json
// @Param X-Requested-With header string false "XMLHttpRequest; required unless a Bearer token is sent"
// @Success 200 {object} Envelope[AdminLoginData]
// @Failure 403 {object} ErrorEnvelope
// @Failure 401 {object} ErrorEnvelope
// @Router /auth/refresh [post]
func AdminRefresh() {}
// AdminLogout documents POST /auth/logout.
//
// @Summary Admin logout
// @Tags admin
// @Produce json
// @Security BackendBearer
// @Success 200 {object} SuccessEnvelope
// @Failure 401 {object} ErrorEnvelope
// @Router /auth/logout [post]
func AdminLogout() {}
// AdminMe documents GET /auth/me.
//
// @Summary Current admin
// @Tags admin
// @Produce json
// @Security BackendBearer
// @Success 200 {object} Envelope[AdminProfile]
// @Failure 401 {object} ErrorEnvelope
// @Router /auth/me [get]
func AdminMe() {}
// AdminNavigation documents GET /navigation.
//
// @Summary Admin navigation
// @Tags admin
// @Produce json
// @Security BackendBearer
// @Success 200 {object} Envelope[[]NavigationEntry]
// @Failure 401 {object} ErrorEnvelope
// @Failure 403 {object} ErrorEnvelope
// @Router /navigation [get]
func AdminNavigation() {}
// AdminSettingsList documents GET /settings.
//
// @Summary List admin settings
// @Tags admin
// @Produce json
// @Security BackendBearer
// @Success 200 {object} SuccessEnvelope
// @Failure 401 {object} ErrorEnvelope
// @Failure 403 {object} ErrorEnvelope
// @Router /settings [get]
func AdminSettingsList() {}
// AdminSettingsSchema documents GET /settings/{code}/schema.
//
// @Summary Admin settings schema
// @Tags admin
// @Produce json
// @Security BackendBearer
// @Param code path string true "Settings code"
// @Success 200 {object} SuccessEnvelope
// @Failure 401 {object} ErrorEnvelope
// @Failure 403 {object} ErrorEnvelope
// @Failure 404 {object} ErrorEnvelope
// @Router /settings/{code}/schema [get]
func AdminSettingsSchema() {}
// AdminSettingsGet documents GET /settings/{code}.
//
// @Summary Read admin settings
// @Tags admin
// @Produce json
// @Security BackendBearer
// @Param code path string true "Settings code"
// @Success 200 {object} SuccessEnvelope
// @Failure 401 {object} ErrorEnvelope
// @Failure 403 {object} ErrorEnvelope
// @Failure 404 {object} ErrorEnvelope
// @Router /settings/{code} [get]
func AdminSettingsGet() {}
// AdminSettingsPut documents PUT /settings/{code}.
//
// @Summary Update admin settings
// @Tags admin
// @Accept json
// @Produce json
// @Security BackendBearer
// @Param code path string true "Settings code"
// @Success 200 {object} SuccessEnvelope
// @Failure 401 {object} ErrorEnvelope
// @Failure 403 {object} ErrorEnvelope
// @Failure 422 {object} ErrorEnvelope
// @Router /settings/{code} [put]
func AdminSettingsPut() {}
// AdminListSchema documents the list schema route.
//
// @Summary Admin list schema
// @Tags admin
// @Produce json
// @Security BackendBearer
// @Param vendor path string true "Vendor"
// @Param plugin path string true "Plugin"
// @Param controller path string true "Controller"
// @Success 200 {object} Envelope[ListSchema]
// @Failure 401 {object} ErrorEnvelope
// @Failure 403 {object} ErrorEnvelope
// @Failure 404 {object} ErrorEnvelope
// @Router /{vendor}/{plugin}/{controller}/schema/list [get]
func AdminListSchema() {}
// AdminFormSchema documents the form schema route.
//
// @Summary Admin form schema
// @Tags admin
// @Produce json
// @Security BackendBearer
// @Param vendor path string true "Vendor"
// @Param plugin path string true "Plugin"
// @Param controller path string true "Controller"
// @Success 200 {object} SuccessEnvelope
// @Failure 401 {object} ErrorEnvelope
// @Failure 403 {object} ErrorEnvelope
// @Failure 404 {object} ErrorEnvelope
// @Router /{vendor}/{plugin}/{controller}/schema/form [get]
func AdminFormSchema() {}
// AdminRelationSchema documents the relation schema route.
//
// @Summary Admin relation schema
// @Tags admin
// @Produce json
// @Security BackendBearer
// @Param vendor path string true "Vendor"
// @Param plugin path string true "Plugin"
// @Param controller path string true "Controller"
// @Param name path string true "Relation name"
// @Success 200 {object} SuccessEnvelope
// @Failure 401 {object} ErrorEnvelope
// @Failure 403 {object} ErrorEnvelope
// @Failure 404 {object} ErrorEnvelope
// @Router /{vendor}/{plugin}/{controller}/schema/relation/{name} [get]
func AdminRelationSchema() {}
// AdminFieldOptions documents the relation field options route (D-17).
//
// @Summary Relation field options
// @Description Choices for a writable `type: relation` field: value is the related id, label its nameFrom column. Read-only and non-relation fields answer 404.
// @Tags admin
// @Produce json
// @Security BackendBearer
// @Param vendor path string true "Vendor"
// @Param plugin path string true "Plugin"
// @Param controller path string true "Controller"
// @Param field path string true "Relation field name"
// @Param search query string false "Case-insensitive label search"
// @Param page query integer false "Page"
// @Param per_page query integer false "Options per page (1-100, default 20)"
// @Success 200 {object} ListEnvelope[[]RelationOption]
// @Failure 401 {object} ErrorEnvelope
// @Failure 403 {object} ErrorEnvelope
// @Failure 404 {object} ErrorEnvelope
// @Failure 422 {object} ErrorEnvelope
// @Router /{vendor}/{plugin}/{controller}/fields/{field}/options [get]
func AdminFieldOptions() {}
// AdminList documents the record list route.
//
// @Summary List admin records
// @Tags admin
// @Produce json
// @Security BackendBearer
// @Param vendor path string true "Vendor"
// @Param plugin path string true "Plugin"
// @Param controller path string true "Controller"
// @Param search query string false "Search term"
// @Param sort query string false "Sort column"
// @Param dir query string false "Sort direction (asc or desc)"
// @Param page query integer false "Page"
// @Param per_page query integer false "Records per page"
// @Success 200 {object} ListEnvelope[[]AdminRecord]
// @Failure 401 {object} ErrorEnvelope
// @Failure 403 {object} ErrorEnvelope
// @Failure 422 {object} ErrorEnvelope
// @Router /{vendor}/{plugin}/{controller} [get]
func AdminList() {}
// AdminCreate documents the record create route.
//
// @Summary Create an admin record
// @Description Relation fields are sent by field name with ids ({"genre": 3, "artists": [4, 9]}); the response carries the same shape plus meta.labels.
// @Tags admin
// @Accept json
// @Produce json
// @Security BackendBearer
// @Param vendor path string true "Vendor"
// @Param plugin path string true "Plugin"
// @Param controller path string true "Controller"
// @Success 201 {object} RecordEnvelope
// @Failure 401 {object} ErrorEnvelope
// @Failure 403 {object} ErrorEnvelope
// @Failure 422 {object} ErrorEnvelope
// @Router /{vendor}/{plugin}/{controller} [post]
func AdminCreate() {}
// AdminBulkDelete documents the bulk delete route.
//
// @Summary Bulk-delete admin records
// @Tags admin
// @Accept json
// @Produce json
// @Security BackendBearer
// @Param vendor path string true "Vendor"
// @Param plugin path string true "Plugin"
// @Param controller path string true "Controller"
// @Success 200 {object} SuccessEnvelope
// @Failure 401 {object} ErrorEnvelope
// @Failure 403 {object} ErrorEnvelope
// @Failure 422 {object} ErrorEnvelope
// @Router /{vendor}/{plugin}/{controller}/bulk-delete [post]
func AdminBulkDelete() {}
// AdminShow documents the record show route.
//
// @Summary Show an admin record
// @Tags admin
// @Produce json
// @Security BackendBearer
// @Param vendor path string true "Vendor"
// @Param plugin path string true "Plugin"
// @Param controller path string true "Controller"
// @Param id path integer true "Record id"
// @Success 200 {object} RecordEnvelope
// @Failure 401 {object} ErrorEnvelope
// @Failure 403 {object} ErrorEnvelope
// @Failure 404 {object} ErrorEnvelope
// @Router /{vendor}/{plugin}/{controller}/{id} [get]
func AdminShow() {}
// AdminUpdate documents the record update route.
//
// @Summary Update an admin record
// @Tags admin
// @Accept json
// @Produce json
// @Security BackendBearer
// @Param vendor path string true "Vendor"
// @Param plugin path string true "Plugin"
// @Param controller path string true "Controller"
// @Param id path integer true "Record id"
// @Success 200 {object} RecordEnvelope
// @Failure 401 {object} ErrorEnvelope
// @Failure 403 {object} ErrorEnvelope
// @Failure 404 {object} ErrorEnvelope
// @Failure 422 {object} ErrorEnvelope
// @Router /{vendor}/{plugin}/{controller}/{id} [put]
func AdminUpdate() {}
// AdminDelete documents the record delete route.
//
// @Summary Delete an admin record
// @Tags admin
// @Produce json
// @Security BackendBearer
// @Param vendor path string true "Vendor"
// @Param plugin path string true "Plugin"
// @Param controller path string true "Controller"
// @Param id path integer true "Record id"
// @Success 200 {object} SuccessEnvelope
// @Failure 401 {object} ErrorEnvelope
// @Failure 403 {object} ErrorEnvelope
// @Failure 404 {object} ErrorEnvelope
// @Router /{vendor}/{plugin}/{controller}/{id} [delete]
func AdminDelete() {}
// AdminRelationLinked documents the linked-relation route.
//
// @Summary List linked relation records
// @Tags admin
// @Produce json
// @Security BackendBearer
// @Param vendor path string true "Vendor"
// @Param plugin path string true "Plugin"
// @Param controller path string true "Controller"
// @Param id path integer true "Owner id"
// @Param name path string true "Relation name"
// @Success 200 {object} SuccessEnvelope
// @Failure 401 {object} ErrorEnvelope
// @Failure 403 {object} ErrorEnvelope
// @Router /{vendor}/{plugin}/{controller}/{id}/relations/{name} [get]
func AdminRelationLinked() {}
// AdminRelationCandidates documents the relation candidate route.
//
// @Summary List relation candidates
// @Tags admin
// @Produce json
// @Security BackendBearer
// @Param vendor path string true "Vendor"
// @Param plugin path string true "Plugin"
// @Param controller path string true "Controller"
// @Param id path integer true "Owner id"
// @Param name path string true "Relation name"
// @Success 200 {object} SuccessEnvelope
// @Failure 401 {object} ErrorEnvelope
// @Failure 403 {object} ErrorEnvelope
// @Router /{vendor}/{plugin}/{controller}/{id}/relations/{name}/candidates [get]
func AdminRelationCandidates() {}
// AdminRelationLink documents the relation link route.
//
// @Summary Link relation records
// @Tags admin
// @Accept json
// @Produce json
// @Security BackendBearer
// @Param vendor path string true "Vendor"
// @Param plugin path string true "Plugin"
// @Param controller path string true "Controller"
// @Param id path integer true "Owner id"
// @Param name path string true "Relation name"
// @Success 200 {object} SuccessEnvelope
// @Failure 401 {object} ErrorEnvelope
// @Failure 403 {object} ErrorEnvelope
// @Failure 422 {object} ErrorEnvelope
// @Router /{vendor}/{plugin}/{controller}/{id}/relations/{name}/link [post]
func AdminRelationLink() {}
// AdminRelationUnlink documents the relation unlink route.
//
// @Summary Unlink relation records
// @Tags admin
// @Accept json
// @Produce json
// @Security BackendBearer
// @Param vendor path string true "Vendor"
// @Param plugin path string true "Plugin"
// @Param controller path string true "Controller"
// @Param id path integer true "Owner id"
// @Param name path string true "Relation name"
// @Success 200 {object} SuccessEnvelope
// @Failure 401 {object} ErrorEnvelope
// @Failure 403 {object} ErrorEnvelope
// @Failure 422 {object} ErrorEnvelope
// @Router /{vendor}/{plugin}/{controller}/{id}/relations/{name}/unlink [post]
func AdminRelationUnlink() {}