app

package
v3.42.1 Latest Latest
Warning

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

Go to latest
Published: Aug 5, 2026 License: BSD-3-Clause Imports: 26 Imported by: 0

Documentation

Overview

Package app is glabs-web's core: the layer the GraphQL resolvers delegate to, holding the database and enforcing that every request acts only as its own user. Resolvers stay thin — auth gate plus a call into here — so the rules live in one place rather than scattered across the schema.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type App

type App struct {
	// contains filtered or unexported fields
}

func New

func New(database *db.DB, sealer *secrets.Sealer, gitlabHost string, mailer Mailer, mailDryRun bool, admins []string) *App

func (*App) ActivityLog added in v3.39.0

func (a *App) ActivityLog(ctx context.Context) ([]*db.ActivityEntry, error)

ActivityLog returns the caller's complete activity log across all their courses, newest first — the audit-log dump behind the activity page and its JSON download.

func (*App) Assignment added in v3.4.0

func (a *App) Assignment(ctx context.Context, course, name string) (*AssignmentView, error)

Assignment returns the source values and resolved preview for one assignment of one of the caller's own courses. It returns nil (no error) when the course exists but has no assignment by that name; ErrCourseNotFound propagates when the course itself is not the caller's.

func (*App) AssignmentActivity added in v3.25.0

func (a *App) AssignmentActivity(ctx context.Context, course, name string) ([]*db.ActivityEntry, error)

AssignmentActivity returns the activity log of one of the caller's assignments, newest first — what mutating operations have run against it through the web.

func (*App) AssignmentReport added in v3.16.0

func (a *App) AssignmentReport(ctx context.Context, course, name string) (*report.Reports, error)

AssignmentReport fetches a live report over the repositories of one assignment of one of the caller's courses, using the caller's stored GitLab token. It returns nil (no error) when there is no such assignment or it cannot be resolved (e.g. an abstract base); it errors when no token is stored or GitLab is unreachable.

func (*App) AssignmentURLs added in v3.15.0

func (a *App) AssignmentURLs(ctx context.Context, course, name string) (*AssignmentURLs, error)

AssignmentURLs returns the repository URLs for one assignment of one of the caller's own courses. It returns nil (no error) when the course has no such assignment, or when the assignment cannot be resolved (e.g. an abstract base) — in both cases there simply are no URLs. ErrCourseNotFound propagates when the course itself is not the caller's.

func (*App) CancelScheduledJob added in v3.26.0

func (a *App) CancelScheduledJob(ctx context.Context, id string) (*db.ScheduledJob, error)

CancelScheduledJob cancels one of the caller's pending jobs.

func (*App) CheckCourse added in v3.19.0

func (a *App) CheckCourse(ctx context.Context, courseName string) (*gitlab.CheckResult, error)

CheckCourse resolves the course roster against GitLab (one-shot, no progress), using the caller's stored token.

func (*App) ConfigureSummary added in v3.40.0

func (a *App) ConfigureSummary(hour int, recipients []string)

ConfigureSummary stores the nightly-summary recipients and hour so both the scheduler and the on-demand "send now" path use the same target.

func (*App) CopyAssignment added in v3.23.0

func (a *App) CopyAssignment(ctx context.Context, course, from, newName string) (*AssignmentView, error)

CopyAssignment duplicates one of the caller's assignments under a new name. Only the name has to be unique — the copy is otherwise identical to the source, including its `extends`. It validates the new name (it becomes a GitLab path segment), rejects a name that already exists, then persists — re-marshalling the whole course YAML. The persisted round-trip (encode → store → decode) makes the copy fully independent of the source.

func (*App) Course added in v3.2.0

func (a *App) Course(ctx context.Context, name string) (*db.StoredCourse, error)

Course returns one of the caller's own courses.

func (*App) CourseActivity added in v3.25.0

func (a *App) CourseActivity(ctx context.Context, course string) ([]*db.ActivityEntry, error)

CourseActivity returns the activity log across a whole course of the caller's, newest first — the course page groups it by assignment for a per-assignment status.

func (*App) CourseLint added in v3.2.0

func (a *App) CourseLint(ctx context.Context, name string) ([]config.Finding, error)

CourseLint returns the lint findings for one of the caller's own courses.

func (*App) CourseRepoOverview added in v3.35.0

func (a *App) CourseRepoOverview(ctx context.Context, course string) ([]*AssignmentRepos, error)

CourseRepoOverview reports, per assignment of one of the caller's courses, which target repositories actually exist in GitLab — so the owner can see at a glance what has been generated and who is missing. It uses one group listing per assignment (not a lookup per student) and queries assignments concurrently. It needs the caller's stored GitLab token.

func (*App) CourseYAML added in v3.2.0

func (a *App) CourseYAML(ctx context.Context, name string) ([]byte, error)

CourseYAML returns the course as a downloadable YAML file: the original bytes if they are still current, otherwise a re-encoding of the stored source.

func (*App) Courses added in v3.2.0

func (a *App) Courses(ctx context.Context) ([]*db.StoredCourse, error)

Courses returns the caller's own courses.

func (*App) CreateCourse added in v3.9.0

func (a *App) CreateCourse(ctx context.Context, name, coursePath, semesterPath string, useCoursenameAsPrefix, useEmailDomainAsSuffix bool) (*db.StoredCourse, error)

CreateCourse creates a new, empty course from scratch for the caller. It fails if the caller already has a course by that name — assignments are added afterwards with SetAssignment.

func (*App) DeleteAssignment added in v3.9.0

func (a *App) DeleteAssignment(ctx context.Context, course, name string) (bool, error)

DeleteAssignment removes one assignment from one of the caller's courses and re-marshals the course YAML. It returns false when there was no such assignment (and does not touch the course).

func (*App) DeleteCourse added in v3.2.0

func (a *App) DeleteCourse(ctx context.Context, name string) error

DeleteCourse removes one of the caller's own courses.

func (*App) GitLabTokenStatus added in v3.3.0

func (a *App) GitLabTokenStatus(ctx context.Context) (*GitLabTokenStatus, error)

GitLabTokenStatus reports whether the caller has a stored token, without decrypting or returning it.

func (*App) ImportAssignmentYAML added in v3.17.0

func (a *App) ImportAssignmentYAML(ctx context.Context, course, assignmentYAML string) (*AssignmentView, error)

ImportAssignmentYAML upserts a single assignment into one of the caller's courses from a YAML snippet in the same keyed form it has in a course file:

blatt3:
  per: student
  accesslevel: developer
  startercode:
    url: git@gitlab.lrz.de:...

The snippet's single top-level key is the assignment name. The block is merged into the stored course, decoded and validated through the same resolver the editor uses (a concrete assignment that does not resolve is rejected), then persisted — re-marshalling the course YAML, exactly like SetAssignment.

func (*App) ImportCourseYAML added in v3.2.0

func (a *App) ImportCourseYAML(ctx context.Context, yaml string) (*db.StoredCourse, error)

ImportCourseYAML parses an uploaded course file and stores it for the caller, keeping the original bytes so a later download returns exactly what was uploaded. Importing a course whose name already exists replaces it.

An inline seeder signKey is rejected: it is a private key, and storing it would mean sealing it, hiding it in GraphQL, and excluding it from dumps — four leaks for a feature no course file uses. signKeyFile (a path) is fine.

func (*App) IsAdmin added in v3.40.0

func (a *App) IsAdmin(ctx context.Context) bool

IsAdmin reports whether the request's user is a platform admin (on the config admins list). Admins see the monitoring page and receive the nightly summary; there is no other privilege — ordinary data access stays strictly owner-scoped.

func (*App) IsAdminEmail added in v3.40.0

func (a *App) IsAdminEmail(email string) bool

IsAdminEmail reports whether the given email is on the config admins list.

func (*App) LocalDevUser

func (a *App) LocalDevUser() *model.User

LocalDevUser is the identity used when auth is disabled (local development). It is never consulted when auth is enabled.

func (*App) NoteLogin added in v3.40.0

func (a *App) NoteLogin(ctx context.Context, email, name, department string)

NoteLogin records that a user was active, throttled to at most one event per loginThrottle so the log stays a "who was here" list rather than a request trace. Called from the auth middleware for every allowed request; the throttle state is in-memory (per process), so a restart re-arms it — acceptable for a monitoring signal.

func (*App) NoteRejectedLogin added in v3.40.0

func (a *App) NoteRejectedLogin(ctx context.Context, email, department, reason string)

NoteRejectedLogin records a refused request: no identity at all, or an email not on the allowlist. These are rare and security-relevant, so each is logged (no throttle) as a warning.

func (*App) PlanOp added in v3.20.0

func (a *App) PlanOp(ctx context.Context, op, course, assignment string, params map[string]string, onlyFor []string) (*OpPlan, error)

PlanOp resolves an assignment and returns the plan for a mutating operation — which repositories it would touch and any warnings — plus a confirm token. It does NOT touch GitLab; it is a pure, token-free preview. A missing/unresolvable assignment or an unknown op is an error.

func (*App) PlatformEvents added in v3.40.0

func (a *App) PlatformEvents(ctx context.Context, since time.Time) ([]*db.Event, error)

PlatformEvents returns the most recent events since `since`, newest first — the admin page's live feed. Admin-only.

func (*App) PlatformSummary added in v3.40.0

func (a *App) PlatformSummary(ctx context.Context, since, until time.Time) (*Summary, error)

PlatformSummary aggregates the events in [since, until) into the digest — the same view the nightly mail sends, exposed so the admin page can show it live. Admin-only.

func (*App) RemoveGitLabToken added in v3.3.0

func (a *App) RemoveGitLabToken(ctx context.Context) (*GitLabTokenStatus, error)

RemoveGitLabToken deletes the caller's stored GitLab PAT.

func (*App) RenameAssignment added in v3.24.0

func (a *App) RenameAssignment(ctx context.Context, course, oldName, newName string) (*AssignmentView, error)

RenameAssignment renames one of the caller's assignments within a course and re-marshals the course YAML. Any sibling that inherits from the old name via `extends` is repointed to the new name (so, unlike editing plain YAML by hand, no inheritance chain silently breaks). It fails if the new name is not path-safe or already exists.

func (*App) RenameCourse added in v3.24.0

func (a *App) RenameCourse(ctx context.Context, oldName, newName string) (*db.StoredCourse, error)

RenameCourse renames one of the caller's own courses. The course name is the YAML file's top-level key, so the raw bytes are re-encoded under the new name. It fails if the new name is not path-safe or the caller already has a course by that name. Assignments, students and groups are carried over unchanged.

func (*App) RunOp added in v3.21.0

func (a *App) RunOp(ctx context.Context, token, confirmPhrase string) (<-chan RunLine, error)

RunOp validates a plan token and runs the mutating GitLab operation it describes, streaming its output line by line. The pre-flight checks (token, config drift, seeder, confirm phrase, exclusive lock, GitLab token) fail synchronously with an error; once the operation starts, failures are streamed as an ERROR line.

The operation runs on a context DETACHED from the subscription (context.WithoutCancel): closing the browser tab cancels the subscription (so the reporter stops streaming) but the operation keeps running to completion.

func (*App) ScheduleOp added in v3.26.0

func (a *App) ScheduleOp(ctx context.Context, token string, runAt time.Time, graceMinutes *int, confirmPhrase string) (*db.ScheduledJob, error)

ScheduleOp queues a mutating operation to run at runAt. It reuses the SAME confirm token as RunOp: planning, running now, and scheduling for later are one flow. The token proves the plan is fresh (it carries the config hash, re-checked when the job fires) and names the operation, so scheduling needs no new preview. Scheduling presupposes a stored GitLab token — the runner has no way to ask for one later.

func (*App) ScheduledJob added in v3.26.0

func (a *App) ScheduledJob(ctx context.Context, id string) (*db.ScheduledJob, error)

ScheduledJob returns one of the caller's jobs.

func (*App) ScheduledJobs added in v3.26.0

func (a *App) ScheduledJobs(ctx context.Context, statuses []string) ([]*db.ScheduledJob, error)

ScheduledJobs returns the caller's jobs, newest scheduled first, optionally filtered to the given statuses.

func (*App) SendSummaryNow added in v3.40.0

func (a *App) SendSummaryNow(ctx context.Context) error

SendSummaryNow sends the digest for the last 24h on demand (the admin page's "send now" button), without moving the nightly marker — it is a preview, not the scheduled run. Admin-only.

func (*App) ServerInfo

func (a *App) ServerInfo() *model.ServerInfo

ServerInfo returns the build metadata main stashed in viper (ldflags at release, VCS info otherwise), so the GUI can show which build is running.

func (*App) SetAssignment added in v3.6.0

func (a *App) SetAssignment(ctx context.Context, course, name string, draft map[string]string) (*AssignmentView, error)

SetAssignment applies a draft to one of the caller's assignments: it validates, rejects a concrete draft that does not resolve, then persists — re-marshalling the whole course YAML (a real edit gives up the verbatim original bytes).

func (*App) SetCourse added in v3.10.0

func (a *App) SetCourse(ctx context.Context, name, coursePath, semesterPath string, useCoursenameAsPrefix, useEmailDomainAsSuffix bool) (*db.StoredCourse, error)

SetCourse updates the course-level settings of one of the caller's courses, leaving assignments, students and groups untouched, and re-marshals the course YAML.

func (*App) SetCourseGroups added in v3.12.0

func (a *App) SetCourseGroups(ctx context.Context, name string, groups map[string][]string) (*db.StoredCourse, error)

SetCourseGroups replaces the course-level groups of one of the caller's courses. Groups with a blank name or no members after cleaning are dropped.

func (*App) SetCourseStudents added in v3.12.0

func (a *App) SetCourseStudents(ctx context.Context, name string, students []string) (*db.StoredCourse, error)

SetCourseStudents replaces the course-level students of one of the caller's courses (the GUI merges additively before calling this).

func (*App) SetGitLabToken added in v3.3.0

func (a *App) SetGitLabToken(ctx context.Context, token string) (*GitLabTokenStatus, error)

SetGitLabToken stores the caller's GitLab PAT, AES-256-GCM encrypted. Write-only: the token is never returned by any query, and the plaintext is never persisted or logged. Fails closed if no secrets.key is configured.

func (*App) StartJobRunner added in v3.27.0

func (a *App) StartJobRunner(ctx context.Context)

StartJobRunner runs the scheduled-job poll loop until ctx is cancelled. Each tick it drains every due job, claiming them one at a time so exactly one runner ever owns a given job. It is safe to run one per instance against a shared database. Jobs run synchronously in the loop, so a shutdown between ticks waits for the current job to finish.

func (*App) StartSummaryMailer added in v3.40.0

func (a *App) StartSummaryMailer(ctx context.Context)

StartSummaryMailer sends the nightly digest at the configured hour (local time) until ctx is cancelled. It is a no-op without a mailer or recipients. The window is (last-sent, now], persisted in Mongo, so a restart never double-sends and a missed night is folded into the next run.

func (*App) StreamAssignmentReport added in v3.18.0

func (a *App) StreamAssignmentReport(ctx context.Context, course, name string) (<-chan ReportEvent, error)

StreamAssignmentReport generates the report for one assignment and streams the GitLab client's progress as it goes, ending with exactly one done event that carries the finished report (or an error). The returned channel is closed when generation finishes or ctx is cancelled.

Like AssignmentReport, a missing or unresolvable assignment yields a done event with a nil report (no error); a missing token or an unreachable GitLab yields a done event whose Error is set — surfaced to the client rather than tearing down the subscription.

func (*App) StreamCheckCourse added in v3.19.0

func (a *App) StreamCheckCourse(ctx context.Context, courseName string) (<-chan CheckEvent, error)

StreamCheckCourse checks the course roster against GitLab and streams progress per student, ending with one done event carrying the result. A missing token or an unreachable GitLab yields a done event whose Error is set.

func (*App) StreamCourseRepoOverview added in v3.36.0

func (a *App) StreamCourseRepoOverview(ctx context.Context, course string) (<-chan RepoOverviewEvent, error)

StreamCourseRepoOverview runs the same overview as CourseRepoOverview but emits each assignment as soon as it is checked, so the GUI shows progress instead of a frozen page. The channel is always returned; a missing token surfaces as a final Done event carrying Error rather than a synchronous failure.

func (*App) ValidateAssignmentDraft added in v3.6.0

func (a *App) ValidateAssignmentDraft(ctx context.Context, course, name string, draft map[string]string) (*ValidationResult, error)

ValidateAssignmentDraft validates a draft against the real resolver without saving.

type AssignmentRepos added in v3.35.0

type AssignmentRepos struct {
	Name     string
	Per      string
	Targets  int
	Existing int
	Repos    []RepoStatus
	Note     string
}

AssignmentRepos summarises, for one assignment, how many of its target repos have been generated. Note carries a reason when the assignment could not be checked (abstract/unresolvable, or its GitLab group does not exist yet).

type AssignmentURLs added in v3.15.0

type AssignmentURLs struct {
	// Per is "student" or "group".
	Per string
	// GroupURL is the assignment-level group URL where all the repos live.
	GroupURL string
	// Repos is one URL per student/group repository.
	Repos []config.RepoURL
}

AssignmentURLs are the repository URLs for one assignment: the assignment-level group URL plus one entry per student or per group. Derived purely from the resolved configuration — no GitLab token or API call is involved.

type AssignmentView added in v3.4.0

type AssignmentView struct {
	Course   string
	Name     string
	Extends  string
	Abstract bool
	// ExtendsOptions lists the names of the sibling assignments this one may
	// inherit from (all assignments in the same course except this one, sorted).
	// `extends` is course-internal, so these are exactly the valid choices for
	// the editor's dropdown.
	ExtendsOptions []string
	// Own holds the assignment's own (source) field values, keyed by
	// FieldMeta.key, for pre-filling the editor form.
	Own []FieldValue
	// Resolved is the Show() rendering (may contain ANSI). Empty when the
	// assignment cannot be resolved (then ResolveError explains why).
	Resolved string
	// ResolveError is set when resolution fails — e.g. an abstract base, a
	// missing parent, or an `extends` cycle. Not a fault: an abstract base is a
	// valid document that simply has no resolved form.
	ResolveError string
}

AssignmentView is one assignment in source (own) form plus its resolved preview — the same rendering `glabs show` produces. It makes the inheritance visible in the browser exactly as the CLI's confirmation gate does.

type CheckEvent added in v3.19.0

type CheckEvent struct {
	Message string
	Done    bool
	Result  *gitlab.CheckResult
	Error   string
}

CheckEvent is one item in the course-check stream: a progress message while the roster is checked, or the single final done event carrying the result (or an error, e.g. no token stored).

type EventLine added in v3.40.0

type EventLine struct {
	At         time.Time
	Type       string
	Severity   string
	Actor      string
	Course     string
	Assignment string
	Op         string
	Detail     string
}

EventLine is one event projected for display in a digest section.

type FieldKind added in v3.4.0

type FieldKind string

FieldKind is the input shape the GUI should render for a field.

const (
	KindString     FieldKind = "STRING"
	KindBool       FieldKind = "BOOL"
	KindEnum       FieldKind = "ENUM"
	KindInt        FieldKind = "INT"
	KindStringList FieldKind = "STRINGLIST"
)

type FieldMeta added in v3.4.0

type FieldMeta struct {
	Key         string
	Label       string
	Description string
	// Group is the section the field belongs to ("" for the top-level group,
	// e.g. "Startercode" for the startercode block), so the GUI can render
	// grouped sections. Nested keys are dotted, e.g. "startercode.url".
	Group      string
	Kind       FieldKind
	Required   bool
	Deprecated bool
	Example    string
	Options    []FieldOption
}

FieldMeta describes one editable assignment field.

func AssignmentApprovalRuleSchema added in v3.14.0

func AssignmentApprovalRuleSchema() []FieldMeta

AssignmentApprovalRuleSchema returns the metadata for one approval rule — a row of the repeat-group `approvals.rules` list.

func AssignmentApprovalSettingsSchema added in v3.14.0

func AssignmentApprovalSettingsSchema() []FieldMeta

AssignmentApprovalSettingsSchema returns the metadata for the mergeRequest approval settings. These are tri-state: an ENUM whose empty option means "not set" (inherit), so `an`/`aus` map to *bool true/false and "" to nil.

func AssignmentBranchSchema added in v3.13.0

func AssignmentBranchSchema() []FieldMeta

AssignmentBranchSchema returns the metadata for one branch rule — a row of the repeat-group `branches` list. The GUI renders one such control set per row.

func AssignmentSchema added in v3.4.0

func AssignmentSchema() []FieldMeta

AssignmentSchema returns the metadata for the assignment editor's fields.

type FieldOption added in v3.4.0

type FieldOption struct {
	Value       string
	Label       string
	Description string
}

FieldOption is one choice of an ENUM field — a dropdown entry with its own short description.

type FieldValue added in v3.5.0

type FieldValue struct {
	Key   string
	Value string
}

FieldValue is one field's own (source) value, stringified and keyed by the same key as FieldMeta so the GUI can pre-fill the schema-driven form.

type GitLabTokenStatus added in v3.3.0

type GitLabTokenStatus struct {
	Set       bool
	UpdatedAt *time.Time
}

GitLabTokenStatus reports whether the caller has stored a GitLab token and when — never the token itself.

type LabelCount added in v3.40.0

type LabelCount struct {
	Label string
	Count int
}

LabelCount is a labelled tally (interactive ops by kind).

type Mailer added in v3.29.0

type Mailer interface {
	Send(dryRun bool, to, subject string, text, html []byte) error
}

Mailer sends a rendered notification. *mail.Sender implements it; the App holds one only when SMTP is configured (nil otherwise, so notifications are skipped).

type OpPlan added in v3.20.0

type OpPlan struct {
	Op            string
	Course        string
	Assignment    string
	Resolved      string
	Targets       []PlannedTarget
	Warnings      []string
	Destructive   bool
	ConfirmPhrase string
	Token         string
	ExpiresAt     time.Time
}

OpPlan is the preview of a mutating operation before it runs: the resolved config, the repositories it would touch, warnings, and an opaque confirm token carrying a hash of the resolved config — so a later runOp can reject a plan whose config changed underneath it (strictly stronger than the CLI's Scanln gate).

type PlannedTarget added in v3.20.0

type PlannedTarget struct {
	For  string
	Repo string
	URL  string
}

PlannedTarget is one repository an operation would touch.

type RejectedLogin added in v3.40.0

type RejectedLogin struct {
	Email      string
	Department string
	Count      int
}

RejectedLogin is one refused identity with its attempt count.

type RepoOverviewEvent added in v3.36.0

type RepoOverviewEvent struct {
	Assignment *AssignmentRepos
	Total      int
	Done       bool
	Error      string
}

RepoOverviewEvent is one streamed item of a course repo overview: a completed assignment as it finishes, then a final Done event. Error is set (with Done) when the whole overview cannot start — e.g. no stored GitLab token.

type RepoStatus added in v3.35.0

type RepoStatus struct {
	For    string
	Repo   string
	URL    string
	Exists bool
}

RepoStatus is one target repository and whether it actually exists in GitLab.

type ReportEvent added in v3.18.0

type ReportEvent struct {
	Message string
	Done    bool
	Report  *report.Reports
	Error   string
}

ReportEvent is one item in the assignment-report stream: either a progress message while the report is being fetched, or the single final event carrying the finished report (or an error).

type RunLine added in v3.21.0

type RunLine struct {
	Level string
	Text  string
}

RunLine is one line of an operation's streamed output: a level (INFO, WARN, ERROR, PROGRESS, RESULT, DONE) plus text (may contain ANSI, though the reporter strips it).

type Summary added in v3.40.0

type Summary struct {
	From        time.Time
	Until       time.Time
	TotalEvents int
	Quiet       bool // nothing happened in the window

	ActiveUsers    []UserActivity  // who was active, busiest first
	RejectedLogins []RejectedLogin // refused sign-ins, most attempts first (security)

	ScheduledJobs []EventLine // operations queued during the window
	JobDone       int
	JobFailed     int
	JobExpired    int
	JobCancelled  int
	JobsRun       int         // done+failed+expired+cancelled
	JobFailures   []EventLine // failed/expired runs, with the error

	OpDone     int
	OpFailed   int
	OpsByType  []LabelCount // interactive ops grouped by kind
	OpFailures []EventLine

	CourseCreated int
	CourseDeleted int
	TokenSaved    int
	TokenDeleted  int

	Problems []EventLine // everything at warning/error severity, newest first
}

Summary is the aggregated, human-readable digest of a period's events — the shape both the nightly mail template and the admin page's summary render. It is deliberately pre-digested (counts and short lists), never raw log lines, which is exactly what the operator asked for.

type UserActivity added in v3.40.0

type UserActivity struct {
	Email      string
	Name       string
	Department string
	Logins     int
}

UserActivity is one active user with how often they were seen in the window.

type ValidationResult added in v3.6.0

type ValidationResult struct {
	OK           bool
	Errors       []string
	Resolved     string
	ResolveError string
}

ValidationResult reports whether a draft assignment is saveable and — when it resolves — its preview.

Jump to

Keyboard shortcuts

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