Documentation
¶
Overview ¶
Package challenges is the challenge domain service: admin CRUD (with the admin/participant split enforced at the handler layer), attachment storage, and the participant-facing board with visibility and phase gating. Point values come from the pure scoring package.
Index ¶
- func CurrentPoints(c gen.Challenge, solves int) int
- func IsRegisteredType(id string) bool
- func RegisterType(id string, ct ChallengeType, override bool) error
- func TypeNames() []string
- type AdminListFilters
- type AdminListResult
- type AttachmentContent
- type BoardEntry
- type ChallengeType
- type ConfigValidation
- type CreateInput
- type Detail
- type FlagChecker
- type Full
- type Service
- func (s *Service) Create(ctx context.Context, in CreateInput) (gen.Challenge, error)
- func (s *Service) CurrentValue(ctx context.Context, challengeID uuid.UUID) (int, error)
- func (s *Service) Delete(ctx context.Context, id uuid.UUID, eventRunning, confirm bool) error
- func (s *Service) DeleteAttachment(ctx context.Context, challengeID, attachmentID uuid.UUID) error
- func (s *Service) GetAdmin(ctx context.Context, id uuid.UUID) (Full, error)
- func (s *Service) GetByID(ctx context.Context, id uuid.UUID) (gen.Challenge, error)
- func (s *Service) GetBySlug(ctx context.Context, slug string) (gen.Challenge, error)
- func (s *Service) GetVisibleDetail(ctx context.Context, slug string, teamID uuid.UUID) (Detail, error)
- func (s *Service) ListAdmin(ctx context.Context, p pagination.Params, f AdminListFilters) (AdminListResult, error)
- func (s *Service) ListVisible(ctx context.Context, teamID uuid.UUID) ([]BoardEntry, error)
- func (s *Service) OpenAttachment(ctx context.Context, challengeID, attachmentID uuid.UUID) (AttachmentContent, error)
- func (s *Service) Update(ctx context.Context, id uuid.UUID, in UpdateInput) (gen.Challenge, error)
- func (s *Service) UploadAttachment(ctx context.Context, challengeID uuid.UUID, filename, contentType string, ...) (gen.ChallengeAttachment, error)
- type TypeRegistry
- type UpdateInput
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func CurrentPoints ¶
CurrentPoints returns the challenge's current value given its solve count.
func IsRegisteredType ¶
IsRegisteredType reports whether a challenge-type id is registered in the default registry.
func RegisterType ¶
func RegisterType(id string, ct ChallengeType, override bool) error
RegisterType adds/replaces a challenge type in the default registry (the loader's entry).
Types ¶
type AdminListFilters ¶
AdminListFilters narrows the admin challenge list.
type AdminListResult ¶
AdminListResult is a page of challenges with attachments and solve counts.
type AttachmentContent ¶
type AttachmentContent struct {
Filename string
ContentType string
Size int64
Body io.ReadCloser
}
AttachmentContent streams an attachment's bytes with its metadata.
type BoardEntry ¶
BoardEntry is a participant-facing challenge with its current value and the caller team's solve state. It never carries the flag or description.
type ChallengeType ¶
type ChallengeType interface {
ID() string
// ValidateConfig checks a challenge's per-challenge type config at authoring time. OK=false
// with per-field messages rejects the write (422); a non-nil error means the type could not be
// reached to validate (its plugin is down) and the write fails CLOSED, rather than storing
// unvalidated config. The built-in types reach nothing and always accept. ctx bounds the dial.
ValidateConfig(ctx context.Context, cfg map[string]string) (ConfigValidation, error)
}
ChallengeType decides a challenge's author-time config validation and (via the optional FlagChecker below) its custom flag check. The built-in types (standard/container) implement only this interface and defer correctness to the platform's flag comparison; a plugin type additionally implements FlagChecker and owns correctness.
type ConfigValidation ¶
ConfigValidation is the result of author-time challenge-type config validation — the host mirror of the wire ValidateResponse and the SDK's sdk.ConfigValidation, so all three layers speak ONE reconciled shape. OK reports whether the config is usable; FieldErrors carries ONE message per rejected field (rendered into the 422 errors map an admin sees); Normalized is the canonicalised config the host stores in place of the raw author input. When Normalized is empty the host stores the author's input unchanged.
type CreateInput ¶
type CreateInput struct {
Slug *string
Title string
Category string
Description string
Difficulty *string
Kind string
Type string // challenge-type id; "" defaults to the built-in "standard"
TypeConfig map[string]string // per-challenge config for the type's plugin; validated at write time
Flag string
FlagCaseInsensitive bool
Scoring string
PointsInitial int
PointsMin *int
Decay *int
MaxAttempts *int
Visible bool
Image *string
InternalPort *int
MemLimitMB *int
CPUMillis *int
ContainerEnv map[string]string
ConnectionTemplate *string
Instancing string // "" -> "shared"
FlagMode string // "" -> "static"
InstanceTTLSeconds *int // nil -> default; 0 -> no TTL
Egress *bool // nil -> true
WritablePaths []string
}
CreateInput is the validated challenge creation payload.
type Detail ¶
type Detail struct {
BoardEntry
Attachments []gen.ChallengeAttachment
AttemptsUsed int
}
Detail is the full participant view of one challenge.
type FlagChecker ¶
type FlagChecker interface {
CheckFlag(ctx context.Context, submitted string, config, instance map[string]string) (correct bool, err error)
}
FlagChecker is implemented ONLY by a plugin-provided challenge-type: it decides correctness from the submitted guess + author config + per-instance metadata (never the real flag). The submission path type-asserts for it — a type that implements it takes the PRE-transaction plugin verdict path; a built-in (which does not) keeps the platform's in-transaction flag comparison, byte-identical to v0.2.
type Full ¶
type Full struct {
Challenge gen.Challenge
Attachments []gen.ChallengeAttachment
Solves int
}
Full is a challenge plus its attachments (instance summary is joined in the handler/M7).
type Service ¶
type Service struct {
// contains filtered or unexported fields
}
Service implements challenge operations.
func (*Service) CurrentValue ¶
CurrentValue returns a challenge's current point value by loading it and its solve count. Used by profile solve lists.
func (*Service) Delete ¶
Delete removes a challenge. Deleting a visible challenge during a running event requires confirm=true (it rewrites history). The caller passes eventRunning.
func (*Service) DeleteAttachment ¶
DeleteAttachment removes an attachment from storage and the database.
func (*Service) GetByID ¶
GetByID returns a raw challenge by id (used by the admin instance fleet view).
func (*Service) GetBySlug ¶
GetBySlug returns a raw challenge by slug (used by the submit path in M5).
func (*Service) GetVisibleDetail ¶
func (s *Service) GetVisibleDetail(ctx context.Context, slug string, teamID uuid.UUID) (Detail, error)
GetVisibleDetail returns a visible challenge's full participant detail by slug. Invisible or missing challenges are ErrNotFound (never leak existence).
func (*Service) ListAdmin ¶
func (s *Service) ListAdmin(ctx context.Context, p pagination.Params, f AdminListFilters) (AdminListResult, error)
ListAdmin returns a page of challenges (including invisible ones).
func (*Service) ListVisible ¶
ListVisible returns visible challenges with current point values and solve counts. teamID may be uuid.Nil for a teamless caller (SolvedByMe always false).
func (*Service) OpenAttachment ¶
func (s *Service) OpenAttachment(ctx context.Context, challengeID, attachmentID uuid.UUID) (AttachmentContent, error)
OpenAttachment resolves an attachment by challenge slug and attachment ID and opens its content for streaming. Visibility is enforced by the caller.
type TypeRegistry ¶
type TypeRegistry struct {
// contains filtered or unexported fields
}
TypeRegistry resolves a challenge-type id to its ChallengeType. Same atomic-swap model as the auth and scoring registries: an atomic pointer to an IMMUTABLE map — readers (Get/IsRegistered) are lock-free, Register copies-on-write and swaps under a writer mutex, so a swap is atomic from a reader's view. With only the built-ins registered it is behaviourally identical to v0.2.2 (every challenge is 'standard'/'container').
func DefaultTypeRegistry ¶
func DefaultTypeRegistry() *TypeRegistry
DefaultTypeRegistry returns the process-wide challenge-type registry — the one CRUD validation checks, plugins register into, and the submission path resolves against, so all three agree on which types exist.
func NewTypeRegistry ¶
func NewTypeRegistry(builtins ...ChallengeType) *TypeRegistry
NewTypeRegistry builds a registry with the given types registered as protected built-ins.
func (*TypeRegistry) Deregister ¶
func (r *TypeRegistry) Deregister(id string)
Deregister removes a plugin-registered type (revert-before-death when its plugin stops), restoring the built-in a plugin had overridden, or removing the entry outright otherwise. A bare built-in cannot "stop", so deregistering one is a no-op. Atomic swap (reader-safe). After this, the submission path finds the type unregistered and fails a submission to it closed.
func (*TypeRegistry) Get ¶
func (r *TypeRegistry) Get(id string) (ChallengeType, bool)
Get resolves a type by id. Lock-free.
func (*TypeRegistry) IsRegistered ¶
func (r *TypeRegistry) IsRegistered(id string) bool
IsRegistered reports whether a type id resolves. Lock-free.
func (*TypeRegistry) Names ¶
func (r *TypeRegistry) Names() []string
Names returns the registered type ids (for admin listing).
func (*TypeRegistry) Register ¶
func (r *TypeRegistry) Register(id string, ct ChallengeType, override bool) error
Register adds or replaces a type. A protected built-in is refused unless override is true. The map is swapped atomically.
type UpdateInput ¶
type UpdateInput struct {
Slug *string
Title *string
Category *string
Description *string
SetDifficulty bool
Difficulty *string
Flag *string
FlagCaseInsensitive *bool
Scoring *string
Type *string // challenge-type id; nil leaves it unchanged
TypeConfig map[string]string // nil = unchanged; non-nil = set (re-validated, fail-closed if plugin down)
PointsInitial *int
SetPointsMin bool
PointsMin *int
SetDecay bool
Decay *int
SetMaxAttempts bool
MaxAttempts *int
Visible *bool
SetImage bool
Image *string
SetInternalPort bool
InternalPort *int
MemLimitMB *int
CPUMillis *int
ContainerEnv map[string]string
SetConnectionTmpl bool
ConnectionTemplate *string
Instancing *string
FlagMode *string
SetInstanceTTL bool
InstanceTTLSeconds *int
Egress *bool
WritablePaths []string
}
UpdateInput is a partial challenge update; nil fields are unchanged and Set* flags distinguish "clear to null" from "leave unchanged".