Documentation
¶
Overview ¶
Package db is glabs-web's MongoDB layer. It owns the connection and the collection access; everything above it works with the decoded documents.
Index ¶
- Constants
- Variables
- type ActivityEntry
- type DB
- func (db *DB) ActivityFor(ctx context.Context, owner, course, assignment string) ([]*ActivityEntry, error)
- func (db *DB) AllActivityFor(ctx context.Context, owner string) ([]*ActivityEntry, error)
- func (db *DB) CancelJob(ctx context.Context, owner, id string) (*ScheduledJob, error)
- func (db *DB) ClaimDueJob(ctx context.Context, workerID string, now time.Time) (*ScheduledJob, error)
- func (db *DB) CourseActivityFor(ctx context.Context, owner, course string) ([]*ActivityEntry, error)
- func (db *DB) CourseOf(ctx context.Context, owner, name string) (*StoredCourse, error)
- func (db *DB) CoursesOf(ctx context.Context, owner string) ([]*StoredCourse, error)
- func (db *DB) DeleteCourse(ctx context.Context, owner, name string) error
- func (db *DB) DeleteUserGitLabToken(ctx context.Context, owner string) error
- func (db *DB) Disconnect(ctx context.Context) error
- func (db *DB) EnsureActivityIndexes(ctx context.Context) error
- func (db *DB) EnsureCourseIndexes(ctx context.Context) error
- func (db *DB) EnsureEventIndexes(ctx context.Context) error
- func (db *DB) EnsureJobIndexes(ctx context.Context) error
- func (db *DB) EnsureUserSecretIndexes(ctx context.Context) error
- func (db *DB) EventsBetween(ctx context.Context, since, until time.Time) ([]*Event, error)
- func (db *DB) FinishJob(ctx context.Context, id, status, logText, errText string) error
- func (db *DB) GetUserSecret(ctx context.Context, owner string) (*UserSecret, error)
- func (db *DB) JobOf(ctx context.Context, owner, id string) (*ScheduledJob, error)
- func (db *DB) JobsOf(ctx context.Context, owner string, statuses []string) ([]*ScheduledJob, error)
- func (db *DB) MarkNotified(ctx context.Context, id string) error
- func (db *DB) RecentEvents(ctx context.Context, since time.Time, limit int64) ([]*Event, error)
- func (db *DB) RecordActivity(ctx context.Context, e *ActivityEntry) error
- func (db *DB) RecordEvent(ctx context.Context, e *Event) error
- func (db *DB) SaveCourse(ctx context.Context, course *StoredCourse) error
- func (db *DB) SaveJob(ctx context.Context, job *ScheduledJob) error
- func (db *DB) SaveUserGitLabToken(ctx context.Context, owner string, sealed secrets.SealedValue, ...) error
- func (db *DB) SetSummarySentAt(ctx context.Context, at time.Time) error
- func (db *DB) SystemState(ctx context.Context) (*SystemState, error)
- func (db *DB) UnnotifiedTerminalJobs(ctx context.Context) ([]*ScheduledJob, error)
- type Event
- type ScheduledJob
- type StoredCourse
- type SystemState
- type UserSecret
Constants ¶
const ( EventLogin = "login" // a user was active (throttled: at most once per window) EventLoginRejected = "login-rejected" // an unauthenticated/not-allowlisted request was refused EventJobScheduled = "job-scheduled" // an operation was queued to run later EventJobDone = "job-done" EventJobFailed = "job-failed" EventJobExpired = "job-expired" EventJobCancelled = "job-cancelled" EventOpDone = "op-done" // an interactive (run-now) operation finished EventOpFailed = "op-failed" EventCourseCreated = "course-created" EventCourseDeleted = "course-deleted" EventTokenSaved = "token-saved" EventTokenDeleted = "token-deleted" )
Event types. Unlike the owner-scoped activity log (which records only the six mutating GitLab ops of a single user), the event log is the platform-wide monitoring trail an operator reads: who signed in, which jobs were scheduled and how they ended, and anything else worth watching. It is deliberately NOT owner-scoped — an admin reads across all users.
const ( SeverityInfo = "info" SeverityWarning = "warning" SeverityError = "error" )
Event severities, used to highlight what matters in the digest and the admin page. They live here (not in config) to stay independent of the linter's Severity type.
const ( JobPending = "pending" JobRunning = "running" JobDone = "done" JobFailed = "failed" JobExpired = "expired" JobCancelled = "cancelled" )
Job status values. A job starts pending, is claimed to running, and ends in exactly one terminal state.
Variables ¶
var ErrCourseNotFound = errors.New("course not found")
ErrCourseNotFound is returned when a course does not exist for the given owner. It deliberately does not distinguish "does not exist" from "belongs to someone else": to one user, another user's course simply is not there.
var ErrJobNotFound = errors.New("scheduled job not found")
ErrJobNotFound is returned when a job does not exist for the given owner (or is no longer in a state that permits the requested change).
var ErrNoDueJob = errors.New("no due job")
ErrNoDueJob is returned by ClaimDueJob when there is nothing to run.
Functions ¶
This section is empty.
Types ¶
type ActivityEntry ¶ added in v3.25.0
type ActivityEntry struct {
Owner string `bson:"owner"`
Course string `bson:"course"`
Assignment string `bson:"assignment"`
Op string `bson:"op"`
Params map[string]string `bson:"params,omitempty"`
// Status is the terminal outcome — "done" or "failed".
Status string `bson:"status"`
// Detail is a short human summary: the repository count on success, the error
// message on failure.
Detail string `bson:"detail,omitempty"`
At time.Time `bson:"at"`
}
ActivityEntry records one mutating operation performed through the web against an assignment: what ran, when, and how it ended. It is the web's stand-in for the shell history the CLI leaves behind — the course page reads it to show, per assignment, what has already been done (setaccess, protect, archive, delete; later generate). Ownership is strict, exactly like courses: an entry belongs to the user who caused it and no other user can see it.
type DB ¶
type DB struct {
// contains filtered or unexported fields
}
func Connect ¶
Connect opens the connection and verifies it with a ping, so a bad URI fails at startup rather than on the first query.
UseLocalTimeZone decodes the UTC that Mongo stores back into time.Local, which main sets to Europe/Berlin — so timestamps read out in the zone they were written in.
func (*DB) ActivityFor ¶ added in v3.25.0
func (db *DB) ActivityFor(ctx context.Context, owner, course, assignment string) ([]*ActivityEntry, error)
ActivityFor returns the log entries of one assignment, newest first.
func (*DB) AllActivityFor ¶ added in v3.39.0
AllActivityFor returns the owner's complete log across all their courses, newest first — the audit-log dump. Unlike the GUI's per-assignment/per-course reads it is UNCAPPED (limit 0): a dump must be complete, and one user's audit trail is bounded in practice.
func (*DB) CancelJob ¶ added in v3.26.0
CancelJob cancels one of the owner's pending jobs. A job that is already running or finished cannot be cancelled, and another user's job is invisible: both cases return ErrJobNotFound.
func (*DB) ClaimDueJob ¶ added in v3.26.0
func (db *DB) ClaimDueJob(ctx context.Context, workerID string, now time.Time) (*ScheduledJob, error)
ClaimDueJob atomically claims the oldest pending job whose time has come, flipping it to running so no other runner can take it. It returns ErrNoDueJob when nothing is due. This single atomic step is what replaces a distributed lock: exactly one runner ever owns a given job.
func (*DB) CourseActivityFor ¶ added in v3.25.0
func (db *DB) CourseActivityFor(ctx context.Context, owner, course string) ([]*ActivityEntry, error)
CourseActivityFor returns the log entries across a whole course, newest first — the course page groups them by assignment to show each one's latest status.
func (*DB) CourseOf ¶ added in v3.2.0
CourseOf returns one course owned by the given user, or ErrCourseNotFound.
func (*DB) CoursesOf ¶ added in v3.2.0
CoursesOf returns the courses owned by the given user, sorted by name.
func (*DB) DeleteCourse ¶ added in v3.2.0
DeleteCourse removes a course owned by the given user. Deleting a course that does not exist for that owner is ErrCourseNotFound, not a silent success — so a delete of another user's course reports "not found" rather than pretending it worked.
func (*DB) DeleteUserGitLabToken ¶ added in v3.3.0
DeleteUserGitLabToken removes only the GitLab PAT from a user's secrets.
func (*DB) EnsureActivityIndexes ¶ added in v3.25.0
EnsureActivityIndexes indexes the log for the two reads the GUI makes: the newest entries of one assignment, and the newest across a whole course.
func (*DB) EnsureCourseIndexes ¶ added in v3.2.0
EnsureCourseIndexes makes (owner, name) unique — a user has at most one course of a given name, and the pair is how every query is keyed.
func (*DB) EnsureEventIndexes ¶ added in v3.40.0
EnsureEventIndexes indexes the log for the newest-first reads (the digest window and the admin page) and a filter-by-type, plus a 180-day TTL.
func (*DB) EnsureJobIndexes ¶ added in v3.26.0
EnsureJobIndexes indexes the collection for the claim ({status, runAt}), the owner's GUI list ({owner, runAt desc}), and a 30-day TTL on finished jobs.
func (*DB) EnsureUserSecretIndexes ¶ added in v3.3.0
EnsureUserSecretIndexes makes owner unique — one secrets document per user.
func (*DB) EventsBetween ¶ added in v3.40.0
EventsBetween returns every event in [since, until), oldest first — the window the nightly digest aggregates. It is cross-user by design.
func (*DB) FinishJob ¶ added in v3.26.0
FinishJob records a terminal state (done/failed/expired) with its log and error.
func (*DB) GetUserSecret ¶ added in v3.3.0
GetUserSecret returns the stored secrets for a user, or nil when none exist.
func (*DB) JobsOf ¶ added in v3.26.0
JobsOf returns the owner's jobs, newest scheduled first, optionally filtered to the given statuses. Like courses, there is no read without an owner filter.
func (*DB) MarkNotified ¶ added in v3.26.0
MarkNotified flags that the terminal-state email for a job has been sent, so a restart mid-notification does not send it twice.
func (*DB) RecentEvents ¶ added in v3.40.0
RecentEvents returns events at or after since, newest first, capped at limit — the admin page's live feed. Cross-user by design.
func (*DB) RecordActivity ¶ added in v3.25.0
func (db *DB) RecordActivity(ctx context.Context, e *ActivityEntry) error
RecordActivity appends one entry to the log. The owner, course and assignment on the entry are set by the caller from the authenticated principal.
func (*DB) RecordEvent ¶ added in v3.40.0
RecordEvent appends one event to the log. Callers set At and Severity; a missing severity defaults to info so a forgotten field never hides an event.
func (*DB) SaveCourse ¶ added in v3.2.0
func (db *DB) SaveCourse(ctx context.Context, course *StoredCourse) error
SaveCourse inserts or replaces a course for its owner. The owner and name on the document are the key; a document can never be written under a different owner than the one on it.
func (*DB) SaveJob ¶ added in v3.26.0
func (db *DB) SaveJob(ctx context.Context, job *ScheduledJob) error
SaveJob inserts a new job, assigning it an id if it has none.
func (*DB) SaveUserGitLabToken ¶ added in v3.3.0
func (db *DB) SaveUserGitLabToken(ctx context.Context, owner string, sealed secrets.SealedValue, updatedAt time.Time) error
SaveUserGitLabToken upserts the sealed GitLab PAT for a user, touching only the gitlab fields so it never clobbers other secrets on the document.
func (*DB) SetSummarySentAt ¶ added in v3.40.0
SetSummarySentAt records when the nightly summary was last sent.
func (*DB) SystemState ¶ added in v3.40.0
func (db *DB) SystemState(ctx context.Context) (*SystemState, error)
SystemState returns the single state document, or a zero-valued one (never nil) if it does not exist yet.
func (*DB) UnnotifiedTerminalJobs ¶ added in v3.29.0
func (db *DB) UnnotifiedTerminalJobs(ctx context.Context) ([]*ScheduledJob, error)
UnnotifiedTerminalJobs returns finished jobs (done/failed/expired) whose notification email has not been sent yet, across all owners — the runner's notify sweep. Cancelled jobs are excluded (there is no cancellation mail). This is what makes "email on every terminal state" survive a crash between finishing a job and mailing it: on restart the job is terminal and still unnotified, so the mail is sent (once — MarkNotified then guards against a resend).
type Event ¶ added in v3.40.0
type Event struct {
At time.Time `bson:"at"`
Type string `bson:"type"`
// Actor is the acting user's email (lowercased); empty for an anonymous
// rejected login (no identity header at all).
Actor string `bson:"actor,omitempty"`
ActorName string `bson:"actorName,omitempty"`
Department string `bson:"department,omitempty"`
Course string `bson:"course,omitempty"`
Assignment string `bson:"assignment,omitempty"`
Op string `bson:"op,omitempty"`
Severity string `bson:"severity"`
Detail string `bson:"detail,omitempty"`
JobID string `bson:"jobID,omitempty"`
}
Event is one thing that happened on the platform, worth an operator's attention. Most fields are optional and depend on the type: a login carries an actor (and maybe a department) but no course; a job event carries course, assignment and op. Ownership is NOT enforced — this collection exists precisely to be read across all users by an admin.
type ScheduledJob ¶ added in v3.26.0
type ScheduledJob struct {
ID string `bson:"_id"`
Owner string `bson:"owner"`
Op string `bson:"op"`
Course string `bson:"course"`
Assignment string `bson:"assignment"`
OnlyFor []string `bson:"onlyFor,omitempty"`
Params map[string]string `bson:"params,omitempty"`
RunAt time.Time `bson:"runAt"`
// ConfigHash is copied from the confirm token; the runner re-resolves and
// compares it at fire time, refusing a job whose config drifted since planning.
ConfigHash string `bson:"configHash"`
Status string `bson:"status"`
GraceMin int `bson:"graceMinutes"`
CreatedAt time.Time `bson:"createdAt"`
StartedAt *time.Time `bson:"startedAt,omitempty"`
FinishedAt *time.Time `bson:"finishedAt,omitempty"`
Log string `bson:"log,omitempty"`
Err string `bson:"err,omitempty"`
Notified bool `bson:"notified"`
WorkerID string `bson:"workerID,omitempty"`
}
ScheduledJob is one mutating operation queued to run at a wall-clock time. It is the persistent unit the poll-runner claims and executes; because it lives in Mongo, jobs survive restarts, missed runs are caught up after downtime, and an atomic claim keeps two runners from firing the same job. Ownership is strict, exactly like courses.
type StoredCourse ¶ added in v3.2.0
type StoredCourse struct {
Owner string `bson:"owner"`
Name string `bson:"name"`
Source *config.CourseSource `bson:"source"`
RawYAML []byte `bson:"rawYAML,omitempty"`
ImportedAt time.Time `bson:"importedAt"`
UpdatedAt time.Time `bson:"updatedAt"`
}
StoredCourse is a course as saved by one user. Ownership is strict: a course belongs to the user who imported it, and no other user can see or touch it.
RawYAML is kept verbatim alongside the parsed Source so a download can return exactly what was uploaded — comments and key order and all — as long as the course has not been edited through the web. Re-encoding Source would lose them.
type SystemState ¶ added in v3.40.0
type SystemState struct {
ID string `bson:"_id"`
SummarySentAt *time.Time `bson:"summarySentAt,omitempty"`
}
SystemState holds server-wide bookkeeping. Today it records only when the last nightly summary was sent, so the digest window survives restarts and a summary is never sent twice for the same period.
type UserSecret ¶ added in v3.3.0
type UserSecret struct {
Owner string `bson:"owner"`
GitLab *secrets.SealedValue `bson:"gitlab,omitempty"`
GitLabUpdatedAt *time.Time `bson:"gitlabUpdatedAt,omitempty"`
}
UserSecret holds a user's encrypted per-user secrets, keyed by the owner's email — here, the GitLab personal access token. The value is AES-256-GCM sealed; the plaintext never touches the database. This document is never exposed over GraphQL, only a "set / when" status is.