challenges

package
v0.3.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 24, 2026 License: Apache-2.0 Imports: 17 Imported by: 0

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

Constants

This section is empty.

Variables

This section is empty.

Functions

func CurrentPoints

func CurrentPoints(c gen.Challenge, solves int) int

CurrentPoints returns the challenge's current value given its solve count.

func IsRegisteredType

func IsRegisteredType(id string) bool

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).

func TypeNames

func TypeNames() []string

TypeNames returns the registered type ids (default registry).

Types

type AdminListFilters

type AdminListFilters struct {
	Category *string
	Visible  *bool
	Kind     *string
}

AdminListFilters narrows the admin challenge list.

type AdminListResult

type AdminListResult struct {
	Items []Full
	Total int64
}

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

type BoardEntry struct {
	Challenge  gen.Challenge
	Points     int
	Solves     int
	SolvedByMe bool
}

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

type ConfigValidation struct {
	OK          bool
	FieldErrors map[string]string
	Normalized  map[string]string
}

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 New

func New(q *gen.Queries, store storage.ObjectStore) *Service

New builds the service.

func (*Service) Create

func (s *Service) Create(ctx context.Context, in CreateInput) (gen.Challenge, error)

Create validates and inserts a challenge.

func (*Service) CurrentValue

func (s *Service) CurrentValue(ctx context.Context, challengeID uuid.UUID) (int, error)

CurrentValue returns a challenge's current point value by loading it and its solve count. Used by profile solve lists.

func (*Service) Delete

func (s *Service) Delete(ctx context.Context, id uuid.UUID, eventRunning, confirm bool) error

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

func (s *Service) DeleteAttachment(ctx context.Context, challengeID, attachmentID uuid.UUID) error

DeleteAttachment removes an attachment from storage and the database.

func (*Service) GetAdmin

func (s *Service) GetAdmin(ctx context.Context, id uuid.UUID) (Full, error)

GetAdmin returns the full challenge with attachments and solve count.

func (*Service) GetByID

func (s *Service) GetByID(ctx context.Context, id uuid.UUID) (gen.Challenge, error)

GetByID returns a raw challenge by id (used by the admin instance fleet view).

func (*Service) GetBySlug

func (s *Service) GetBySlug(ctx context.Context, slug string) (gen.Challenge, error)

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

ListAdmin returns a page of challenges (including invisible ones).

func (*Service) ListVisible

func (s *Service) ListVisible(ctx context.Context, teamID uuid.UUID) ([]BoardEntry, error)

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.

func (*Service) Update

func (s *Service) Update(ctx context.Context, id uuid.UUID, in UpdateInput) (gen.Challenge, error)

Update applies a partial update after validating the resulting row.

func (*Service) UploadAttachment

func (s *Service) UploadAttachment(ctx context.Context, challengeID uuid.UUID, filename, contentType string, size int64, r io.Reader) (gen.ChallengeAttachment, error)

UploadAttachment stores the file in object storage and records it.

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".

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL