api

package
v0.0.0-...-89be4af Latest Latest
Warning

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

Go to latest
Published: Oct 16, 2026 License: AGPL-3.0 Imports: 38 Imported by: 0

Documentation

Index

Constants

View Source
const CLIMinVersionSettingKey = "cli_min_version"

CLIMinVersionSettingKey is the settings key for the minimum CLI version.

View Source
const OrchestrationBotUsername = "orchestration-bot"

OrchestrationBotUsername is the forgejo login of the "orchestration-bot" account. The bot creates every course org and repo on the owner's behalf.

Variables

View Source
var (
	ErrTutorNotFound    = errors.New("tutor not found in this course")
	ErrTutorWrongCourse = errors.New("tutor does not belong to this course")
)

ErrTutorNotFound and ErrTutorWrongCourse are returned by the tutor assignment helpers when the tutor id does not exist or belongs to a different course. API handlers map both to 400, the UI to 422.

View Source
var ErrNoCourseTutors = errors.New("course has no tutors")

ErrNoCourseTutors is returned by AutoDistributeStudents when the course has no tutors to distribute students to.

Functions

func AdminAuditLogFacets

func AdminAuditLogFacets(gdb *gorm.DB) (actions, resources []string, err error)

AdminAuditLogFacets returns distinct action and resource values present in the audit log, used to populate filter dropdowns.

func AdminForgejoRequestFacets

func AdminForgejoRequestFacets(gdb *gorm.DB) (sources, methods []string, err error)

AdminForgejoRequestFacets returns distinct source and method values present in the Forgejo request log.

func AdminGetAuditLog

func AdminGetAuditLog(gdb *gorm.DB, id string) (db.AuditLog, error)

AdminGetAuditLog fetches a single audit log entry by ID.

func AdminGetCourse

func AdminGetCourse(gdb *gorm.DB, id string) (db.Course, error)

AdminGetCourse fetches a single course by ID (including archived courses via Unscoped, since the admin UI needs to show archived courses too).

func AdminGetForgejoRequest

func AdminGetForgejoRequest(gdb *gorm.DB, id string) (db.ForgejoRequest, error)

AdminGetForgejoRequest fetches a single Forgejo request by ID.

func AdminListStudents

func AdminListStudents(gdb *gorm.DB, courseID string) ([]db.Student, error)

AdminListStudents fetches students for a course with users preloaded.

func AdminListTutors

func AdminListTutors(gdb *gorm.DB, courseID string) ([]db.Tutor, error)

AdminListTutors fetches tutors for a course with users preloaded.

func AssignStudentsTutor

func AssignStudentsTutor(gdb *gorm.DB, courseID uint, userIDs []string, tutorID *uint) (int, error)

AssignStudentsTutor assigns or clears the tutor for multiple students of a course in one transaction. User ids without a matching student row in the course are skipped. The tutor must belong to the course (ErrTutorNotFound/ErrTutorWrongCourse). Returns the number of updated student rows.

func AutoDistributeStudents

func AutoDistributeStudents(gdb *gorm.DB, courseID uint) (assigned int, tutors int, err error)

AutoDistributeStudents distributes the unassigned students of a course evenly across the course's tutors. Existing assignments are kept; counts start from the currently assigned students. Each unassigned student (in id order) goes to the tutor with the lowest current count, ties broken by the lower tutor id. Everything runs in one transaction. Returns the number of updated student rows and the number of tutors considered.

func CanManageCourse

func CanManageCourse(gdb *gorm.DB, c *echo.Context, courseID any) bool

CanManageCourse reports whether the caller may manage the course identified by courseID. Admins always may; staff may for courses they own and for courses an admin granted them via course_accesses. Used by handlers (e.g. HTML fragments) that operate on a course by id and need a boolean guard rather than a query scope.

func CourseAccessScope

func CourseAccessScope(c *echo.Context) func(*gorm.DB) *gorm.DB

CourseAccessScope returns a GORM scope that filters courses based on the caller's role for *read* access. Admins see everything. Staff see joinable courses plus the courses they own. Everyone else (students and unauthenticated callers) sees only courses with allow_join = true.

func CourseGrantCounts

func CourseGrantCounts(gdb *gorm.DB, courseIDs []uint) map[uint]int64

CourseGrantCounts returns the number of course_accesses grants per course id for the given course ids. Missing ids map to no entry (zero count).

func CourseManageScope

func CourseManageScope(c *echo.Context) func(*gorm.DB) *gorm.DB

CourseManageScope returns a GORM scope that restricts course queries to the courses the caller may *manage* (edit, delete, assign tutors/students, grade). Admins may manage every course; staff may manage the courses they own plus the ones an admin granted them via course_accesses; everyone else matches nothing. Applied to mutating course endpoints so a staff member acting on someone else's course gets a 404 rather than silently touching it.

func CreateAPIKey

func CreateAPIKey(gdb *gorm.DB, userID, name string) (db.ApiKey, string, error)

CreateAPIKey generates a new API key for the given user, stores the hash, and returns the model + the plaintext key (only visible at creation time). Keys expire after 1 year.

func DeleteAPIKey

func DeleteAPIKey(gdb *gorm.DB, userID string, keyID uint) error

DeleteAPIKey deletes an API key, scoped to the given user.

func EchoContext

func EchoContext(ctx context.Context) *echo.Context

EchoContext returns the echo context for the current huma request. Register injects it before the handler runs so handlers can build echo-context dependent GORM scopes and record audit entries. Returns nil when the handler is invoked outside of a registered operation (tests).

func FetchCLIVersions

func FetchCLIVersions() []string

FetchCLIVersions fetches the available hopper-cli release versions from PyPI and returns them sorted descending (newest first) with a "v" prefix. Returns nil if PyPI is unreachable or the response cannot be parsed.

func HasCourseAccess

func HasCourseAccess(gdb *gorm.DB, courseID uint, userID string) bool

HasCourseAccess reports whether userID holds an admin-granted course access (a course_accesses row) for courseID. It says nothing about ownership, admin rights, or tutor slots; combine it with the other checks where those matter. Used by the per-record guards that bypass CanManageCourse (e.g. grade and tutor-assignment lookups).

func IsTutorAssigned

func IsTutorAssigned(gdb *gorm.DB, courseID, userID string) (bool, error)

IsTutorAssigned returns true if userID is a tutor of courseID.

func ListAPIKeys

func ListAPIKeys(gdb *gorm.DB, userID string) ([]db.ApiKey, error)

ListAPIKeys returns all API keys for the given user, newest first.

func ListExercisesForCourse

func ListExercisesForCourse(gdb *gorm.DB, courseID uint) ([]db.Exercise, error)

ListExercisesForCourse returns every exercise defined in a course, ordered by start date. Exported so tests and in-process callers can reuse the query without going through HTTP.

func LoadCLIMinVersion

func LoadCLIMinVersion(gdb *gorm.DB) string

LoadCLIMinVersion reads the cli_min_version setting from the database, returning the stored value (with a "v" prefix if missing) or an empty string when the setting does not exist.

func Register

func Register[I, O any](h *Huma, op huma.Operation, handler func(context.Context, *I) (*O, error), mw ...echo.MiddlewareFunc)

Register registers a huma operation with echo middleware applied as operation-level huma middleware, keeping route registration a one-liner:

api.Register(h, op, handler, a.Auth.RequireStaff())

The echo context of the current request is injected into the handler context and can be retrieved with EchoContext.

func ResolveUserID

func ResolveUserID(gdb *gorm.DB, identifier string) (string, error)

ResolveUserID resolves an arbitrary identifier to the canonical user PK (OIDC sub). Tries id, then preferred_username (case-insensitive), then student_id. Returns gorm.ErrRecordNotFound if nothing matches.

func RunTask

func RunTask(c *echo.Context, gdb *gorm.DB, name string) error

RunTask triggers the named task. Returns an error if the task isn't found or fails. Records an audit entry on success. Exported for use by the HTML fragment handlers.

func SetStudentTutor

func SetStudentTutor(gdb *gorm.DB, courseID uint, userID string, tutorID *uint) (*db.Student, error)

SetStudentTutor sets or clears the tutor of a single student in a course. userID is the enrolled student's user id; a nil tutorID clears the assignment. The student row must exist in the course (gorm.ErrRecordNotFound) and the tutor must belong to the course (ErrTutorNotFound/ErrTutorWrongCourse). Returns the updated student row with the user preloaded.

func SettingAccessScope

func SettingAccessScope(c *echo.Context) func(*gorm.DB) *gorm.DB

SettingAccessScope returns a GORM scope that filters settings by the caller's access level. Unauthenticated callers see only "public" entries. Regular authenticated users see "public" + "authed". Admins see everything.

func UserReadScope

func UserReadScope(c *echo.Context) func(*gorm.DB) *gorm.DB

UserReadScope returns a GORM scope that restricts a users query to the rows the caller is allowed to *read*. Admins see everyone. Any other authenticated caller sees:

  • themselves;
  • (as course owner or granted staff) every student and tutor enrolled in a course they own or hold a course_accesses grant for;
  • (as a course tutor) every student assigned to a tutor slot they hold.

The ownership branches are harmless for callers who own no courses and hold no tutor slots - their subqueries simply match nothing - so no role branch is needed beyond the admin short-circuit. Applied to the read endpoints so a staff member or tutor gets a scoped view instead of the whole user table, and a 404 (not a 403) for anyone outside their reach.

The whole predicate is a single grouped OR so it AND-combines cleanly with any search/filter scopes the list handler layers on top.

Types

type AssignTutorBody

type AssignTutorBody struct {
	UserID      string `json:"user_id"`
	Description string `json:"description" required:"false"`
}

AssignTutorBody is the body for POST /courses/{id}/tutors.

type AuditLogFacets

type AuditLogFacets struct {
	Actions   []string `json:"actions"`
	Resources []string `json:"resources"`
}

AuditLogFacets is the response body of GET /audit-log/facets. Each field carries the distinct values currently present in the audit_logs table for the matching column, sorted ascending. Clients use it to populate filter dropdowns so we don't have to hardcode the list of actions in the UI every time a new audit call-site is added on the backend.

type AuditLogFacetsInput

type AuditLogFacetsInput struct{}

AuditLogFacetsInput has no request parameters.

type AuditLogFacetsOutput

type AuditLogFacetsOutput struct {
	Body AuditLogFacets
}

AuditLogFacetsOutput holds the response body for AuditLogFacets.

type AuditLogGetInput

type AuditLogGetInput struct {
	ID string `path:"id"`
}

AuditLogGetInput holds the request parameters for AuditLogGet.

type AuditLogGetOutput

type AuditLogGetOutput struct {
	Body db.AuditLog
}

AuditLogGetOutput holds the response body for AuditLogGet.

type AuditLogListInput

type AuditLogListInput struct {
	AuditLogListQuery
}

AuditLogListInput holds the request parameters for AuditLogList.

type AuditLogListOutput

type AuditLogListOutput struct {
	Body query.PaginatedResponse[db.AuditLog]
}

AuditLogListOutput holds the response body for AuditLogList.

type AuditLogListParams

type AuditLogListParams struct {
	Search     string
	Action     string
	Resource   string
	ActorID    string
	ResourceID string
	Since      string
	Until      string
	SortBy     string
	SortDir    string
	Page       int
	PageSize   int
}

AuditLogListParams holds the filter/sort/pagination inputs for the audit log list. Shared by the JSON API handler, the HTML fragment handler, and the page handler so all three paths agree on how params are read.

func ParseAuditLogListParams

func ParseAuditLogListParams(c *echo.Context) AuditLogListParams

ParseAuditLogListParams extracts AuditLogListParams from query string.

type AuditLogListQuery

type AuditLogListQuery struct {
	Search     string `query:"search" required:"false"`
	Action     string `query:"action" required:"false"`
	Resource   string `query:"resource" required:"false"`
	ActorID    string `query:"actor_id" required:"false"`
	ResourceID string `query:"resource_id" required:"false"`
	Since      string `query:"since" required:"false"`
	Until      string `query:"until" required:"false"`
	SortBy     string `query:"sort_by" required:"false"`
	SortDir    string `query:"sort_dir" required:"false"`
	Page       int    `query:"page" default:"1"`
	PageSize   int    `query:"page_size" default:"25"`
}

AuditLogListQuery holds the filter/sort/pagination inputs for GET /audit-log. The query/default tags drive huma's request binding; the values are mapped into AuditLogListParams for the shared AdminListAuditLog query path.

type AuditLogListResult

type AuditLogListResult struct {
	Items      []db.AuditLog
	Total      int64
	Page       int
	PageSize   int
	TotalPages int
	HasNext    bool
	HasPrev    bool
}

AuditLogListResult holds the paginated result of an audit log list query.

func AdminListAuditLog

func AdminListAuditLog(gdb *gorm.DB, p AuditLogListParams, loc *time.Location) (AuditLogListResult, error)

AdminListAuditLog runs the audit log list query with filters, sort, and pagination. Used by both the JSON API handler and the HTML fragment handler, one query path, two response formats. loc is the app timezone, used to interpret zone-less since/until filter values.

type AutoDistributeResult

type AutoDistributeResult struct {
	Assigned int `json:"assigned"`
	Tutors   int `json:"tutors"`
}

AutoDistributeResult is the response body of the auto distribution.

type BulkAssignTutorBody

type BulkAssignTutorBody struct {
	UserIDs []string `json:"user_ids" required:"true"`
	TutorID *uint    `json:"tutor_id" required:"false"`
}

BulkAssignTutorBody is the body for POST /courses/{id}/students/tutor. An absent or null tutor_id clears the assignment for all listed users.

type BulkAssignTutorResult

type BulkAssignTutorResult struct {
	Assigned int `json:"assigned"`
}

BulkAssignTutorResult is the response body of the bulk tutor assignment.

type CLIVersionInput

type CLIVersionInput struct{}

CLIVersionInput holds the request parameters for CLIVersion (none).

type CLIVersionOutput

type CLIVersionOutput struct {
	Body cliVersionResponse
}

CLIVersionOutput holds the response body for CLIVersion.

type ChartsCSVOutput

type ChartsCSVOutput struct {
	ContentType        string `header:"Content-Type"`
	ContentDisposition string `header:"Content-Disposition"`
	Body               []byte
}

ChartsCSVOutput holds the response for ChartsCSV. The headers are bound via huma's output header fields so the raw CSV body keeps its content type.

type ChartsInput

type ChartsInput struct {
	CourseID uint64 `query:"course_id" required:"false"`
}

ChartsInput holds the request parameters for Charts and ChartsCSV.

type ChartsOutput

type ChartsOutput struct {
	Body ChartsResponse
}

ChartsOutput holds the response body for Charts.

type ChartsResponse

type ChartsResponse struct {
	UsersByFaculty        []LabelCount       `json:"users_by_faculty"`
	RegistrationsOverTime []TimeSeriesPoint  `json:"registrations_over_time"`
	LoginsByDay           []TimeSeriesPoint  `json:"logins_by_day"`
	LoginsByHour          []LabelCount       `json:"logins_by_hour"`
	LoginsByWeekday       []LabelCount       `json:"logins_by_weekday"`
	AuditActivityOverTime []TimeSeries       `json:"audit_activity_over_time"`
	ForgejoOverTime       []ForgejoTimePoint `json:"forgejo_over_time"`
	CoursesPerSemester    []LabelCount       `json:"courses_per_semester"`
	StudentsPerTutor      []LabelCount       `json:"students_per_tutor"`
	GradeDistPerExercise  []GradeBucket      `json:"grade_distribution_per_exercise"`
	AvgScorePerExercise   []LabelCount       `json:"average_score_per_exercise"`
	GradedVsUngraded      []GradedUngraded   `json:"graded_vs_ungraded_per_exercise"`
	GradesByWeek          []TimeSeriesPoint  `json:"grades_by_week"`
	AttendancePerWeek     []TimeSeriesPoint  `json:"attendance_per_week"`
	PresentedVsAttended   []LabelCount       `json:"presented_vs_attended"`
	EnrollmentsByFaculty  []LabelCount       `json:"enrollments_by_faculty"`
}

ChartsResponse contains all chart datasets for the stats page.

type CourseAccessForCourseGrantInput

type CourseAccessForCourseGrantInput struct {
	ID     string `path:"id"`
	UserID string `path:"userID"`
}

CourseAccessForCourseGrantInput holds the request parameters for CourseAccessForCourseGrant.

type CourseAccessForCourseGrantOutput

type CourseAccessForCourseGrantOutput struct {
	Body CourseAccessOut
}

CourseAccessForCourseGrantOutput holds the response body for CourseAccessForCourseGrant.

type CourseAccessForCourseListInput

type CourseAccessForCourseListInput struct {
	ID string `path:"id"`
}

CourseAccessForCourseListInput holds the request parameters for CourseAccessForCourseList.

type CourseAccessForCourseListOutput

type CourseAccessForCourseListOutput struct {
	Body []CourseAccessOut
}

CourseAccessForCourseListOutput holds the response body for CourseAccessForCourseList.

type CourseAccessForCourseRevokeInput

type CourseAccessForCourseRevokeInput struct {
	ID     string `path:"id"`
	UserID string `path:"userID"`
}

CourseAccessForCourseRevokeInput holds the request parameters for CourseAccessForCourseRevoke.

type CourseAccessForCourseRevokeOutput

type CourseAccessForCourseRevokeOutput struct {
	Status int
}

CourseAccessForCourseRevokeOutput holds the response status for CourseAccessForCourseRevoke.

type CourseAccessOut

type CourseAccessOut struct {
	ID        uint   `json:"id"`
	CourseID  uint   `json:"course_id"`
	UserID    string `json:"user_id"`
	GrantedBy string `json:"granted_by"`
}

CourseAccessOut is the public shape of a course_accesses grant.

type CourseBody

type CourseBody struct {
	Name         string `json:"name"`
	Semester     string `json:"semester"`
	DisplayName  string `json:"display_name" required:"false"`
	Website      string `json:"website" required:"false"`
	BuilderImage string `json:"builder_image" required:"false"`
	GraderImage  string `json:"grader_image" required:"false"`
	AllowPush    bool   `json:"allow_push" required:"false"`
	AllowJoin    bool   `json:"allow_join" required:"false"`
	RepoAccess   string `json:"repo_access" required:"false"`
	OwnerID      string `json:"owner_id" required:"false"`
}

CourseBody is the request body for creating and updating courses. The id and timestamp fields are server-managed and not part of the payload. owner_id is accepted on create (admins may set it) and ignored on update.

type CourseBrief

type CourseBrief struct {
	ID          uint   `json:"id"`
	Name        string `json:"name"`
	Semester    string `json:"semester"`
	DisplayName string `json:"display_name"`
}

CourseBrief is a minimal representation of a course for the admin UI.

func VisibleCourses

func VisibleCourses(gdb *gorm.DB, ownerID string, isAdmin bool) []CourseBrief

VisibleCourses returns the courses the caller can see, for filter dropdowns: all courses for admins, owned and granted courses for staff.

type CourseListQuery

type CourseListQuery struct {
	Search    string `query:"search" required:"false"`
	AllowJoin string `query:"allow_join" required:"false"`
	AllowPush string `query:"allow_push" required:"false"`
	Archived  string `query:"archived" required:"false"`
	Name      string `query:"name" required:"false"`
	Display   string `query:"display_name" required:"false"`
	Semester  string `query:"semester" required:"false"`
	SortBy    string `query:"sort_by" required:"false"`
	SortDir   string `query:"sort_dir" required:"false"`
	Page      int    `query:"page" default:"1"`
	PageSize  int    `query:"page_size" default:"25"`
}

CourseListQuery holds the filter/sort/pagination inputs for GET /courses. The query/default tags drive huma's request binding; the values are mapped into CoursesListParams for the shared AdminListCourses query path. The boolean filters are strings because huma does not support pointer query parameters; boolParam converts them below.

type CourseStudentGradesInput

type CourseStudentGradesInput struct {
	CourseSlug string `path:"courseSlug"`
	Username   string `path:"username"`
}

CourseStudentGradesInput holds the request parameters for CourseStudentGrades. The course is addressed by its UID (Semester + "-" + Name, the Forgejo org name) and the student by their Forgejo username (users.preferred_username, the repo name), matching the <org>/<repo> layout of the course repositories.

type CourseStudentGradesOutput

type CourseStudentGradesOutput struct {
	Body []GradeItem
}

CourseStudentGradesOutput holds the response body for CourseStudentGrades.

type CourseStudentListParams

type CourseStudentListParams struct {
	Search   string
	TutorID  string
	SortBy   string
	SortDir  string
	Page     int
	PageSize int
}

CourseStudentListParams holds the filter/sort/pagination inputs for the course students list. Shared by the huma handler and any other caller of ListCourseStudents.

func CourseStudentListParamsFromPost

func CourseStudentListParamsFromPost(c *echo.Context) CourseStudentListParams

courseStudentListParamsFromPost builds the params from an htmx POST body that included the filter form (#students-filters): the tab field marks the body as carrying filter state. The bulk_tutor_id and row_tutor_id fields are the assignment targets and are never read as filters; a GET query fallback covers clients that posted without the filter form.

func ParseCourseStudentListParams

func ParseCourseStudentListParams(c *echo.Context) CourseStudentListParams

ParseCourseStudentListParams extracts CourseStudentListParams from the query string, following the same pattern as ParseUsersAdminListParams.

type CourseStudentListQuery

type CourseStudentListQuery struct {
	Search   string `query:"search" required:"false"`
	TutorID  string `query:"tutor_id" required:"false"`
	SortBy   string `query:"sort_by" required:"false"`
	SortDir  string `query:"sort_dir" required:"false"`
	Page     int    `query:"page" default:"1"`
	PageSize int    `query:"page_size" default:"25"`
}

CourseStudentListQuery holds the pagination and filter inputs for the course students list endpoint. The query/default tags drive huma's request binding; the values are mapped into CourseStudentListParams for the shared ListCourseStudents query path.

type CourseStudentListResult

type CourseStudentListResult struct {
	Items      []StudentOut
	Page       int
	PageSize   int
	TotalItems int64
	TotalPages int
	HasNext    bool
	HasPrev    bool
}

CourseStudentListResult holds the paginated result of a course students list query.

func ListCourseStudents

func ListCourseStudents(gdb *gorm.DB, courseID uint, params CourseStudentListParams) (CourseStudentListResult, error)

ListCourseStudents runs the course students list query for one course and returns enriched items with pagination metadata. Raw joins are used so the search and sort fields can come from the users and tutors tables; students whose user row is soft-deleted still appear (their User brief is nil). Email is filled from the preloaded user map; empty for soft-deleted users.

type CourseTutorListQuery

type CourseTutorListQuery struct {
	SortBy   string `query:"sort_by" required:"false" sort:"id,created_at" default_sort:"id asc"`
	SortDir  string `query:"sort_dir" required:"false"`
	Page     int    `query:"page" default:"1"`
	PageSize int    `query:"page_size" default:"25"`
}

CourseTutorListQuery holds the pagination inputs for the course tutors list endpoint. The query/default tags drive huma's request binding; the sort/default_sort tags are ignored by huma and consumed by query.Scopes.

type CoursesCreateInput

type CoursesCreateInput struct {
	Body CourseBody
}

type CoursesCreateOutput

type CoursesCreateOutput struct {
	Body db.Course
}

CoursesCreateOutput holds the response body for CoursesCreate.

type CoursesDeleteInput

type CoursesDeleteInput struct {
	ID string `path:"id"`
}

type CoursesDeleteOutput

type CoursesDeleteOutput struct {
	Status int
}

CoursesDeleteOutput holds the response status for CoursesDelete.

type CoursesGetInput

type CoursesGetInput struct {
	ID string `path:"id"`
}

CoursesGetInput holds the request parameters for CoursesGet.

type CoursesGetOutput

type CoursesGetOutput struct {
	Body db.Course
}

CoursesGetOutput holds the response body for CoursesGet.

type CoursesHardDeleteInput

type CoursesHardDeleteInput struct {
	ID string `path:"id"`
}

type CoursesHardDeleteOutput

type CoursesHardDeleteOutput struct {
	Status int
}

CoursesHardDeleteOutput holds the response status for CoursesHardDelete.

type CoursesJoinInput

type CoursesJoinInput struct {
	ID string `path:"id"`
}

CoursesJoinInput holds the request parameters for CoursesJoin.

type CoursesJoinOutput

type CoursesJoinOutput struct {
	Body db.Student
}

CoursesJoinOutput holds the response body for CoursesJoin.

type CoursesListInput

type CoursesListInput struct {
	CourseListQuery
}

CoursesListInput holds the request parameters for CoursesList.

type CoursesListOutput

type CoursesListOutput struct {
	Body query.PaginatedResponse[db.Course]
}

CoursesListOutput holds the response body for CoursesList.

type CoursesListParams

type CoursesListParams struct {
	Search    string
	AllowJoin *bool
	AllowPush *bool
	Archived  *bool
	Name      string
	Display   string
	Semester  string
	SortBy    string
	SortDir   string
	Page      int
	PageSize  int
}

CoursesListParams holds the filter/sort/pagination inputs for the admin courses list. Shared by the JSON API handler, the HTML fragment handler, and the page handler so all three paths agree on how params are read.

func ParseCoursesListParams

func ParseCoursesListParams(c *echo.Context) CoursesListParams

ParseCoursesListParams extracts CoursesListParams from query string. Shared by the JSON API handler, the HTML fragment handler, and the page handler so all three paths agree on how params are read.

type CoursesListResult

type CoursesListResult struct {
	Items      []db.Course
	Total      int64
	Page       int
	PageSize   int
	TotalPages int
	HasNext    bool
	HasPrev    bool
}

CoursesListResult holds the paginated result of an admin courses list query.

func AdminListCourses

func AdminListCourses(gdb *gorm.DB, p CoursesListParams) (CoursesListResult, error)

AdminListCourses runs the courses list query with filters, sort, and pagination. Used by both the JSON API handler and the HTML fragment handler, one query path, two response formats. Always returns both active and archived courses (use Archived to filter).

type CoursesRestoreInput

type CoursesRestoreInput struct {
	ID string `path:"id"`
}

type CoursesRestoreOutput

type CoursesRestoreOutput struct {
	Body db.Course
}

CoursesRestoreOutput holds the response body for CoursesRestore.

type CoursesStudentsAssignTutorBulkInput

type CoursesStudentsAssignTutorBulkInput struct {
	ID   string `path:"id"`
	Body BulkAssignTutorBody
}

CoursesStudentsAssignTutorBulkInput holds the request parameters for CoursesStudentsAssignTutorBulk.

type CoursesStudentsAssignTutorBulkOutput

type CoursesStudentsAssignTutorBulkOutput struct {
	Body BulkAssignTutorResult
}

CoursesStudentsAssignTutorBulkOutput holds the response body for CoursesStudentsAssignTutorBulk.

type CoursesStudentsAutoDistributeInput

type CoursesStudentsAutoDistributeInput struct {
	ID string `path:"id"`
}

CoursesStudentsAutoDistributeInput holds the request parameters for CoursesStudentsAutoDistribute.

type CoursesStudentsAutoDistributeOutput

type CoursesStudentsAutoDistributeOutput struct {
	Body AutoDistributeResult
}

CoursesStudentsAutoDistributeOutput holds the response body for CoursesStudentsAutoDistribute.

type CoursesStudentsListInput

type CoursesStudentsListInput struct {
	ID string `path:"id"`
	CourseStudentListQuery
}

CoursesStudentsListInput holds the request parameters for CoursesStudentsList.

type CoursesStudentsListOutput

type CoursesStudentsListOutput struct {
	Body query.PaginatedResponse[StudentOut]
}

CoursesStudentsListOutput holds the response body for CoursesStudentsList.

type CoursesStudentsMineInput

type CoursesStudentsMineInput struct {
	ID string `path:"id"`
}

CoursesStudentsMineInput holds the request parameters for CoursesStudentsMine.

type CoursesStudentsMineOutput

type CoursesStudentsMineOutput struct {
	Body []StudentOut
}

CoursesStudentsMineOutput holds the response body for CoursesStudentsMine.

type CoursesStudentsSetTutorInput

type CoursesStudentsSetTutorInput struct {
	ID     string `path:"id"`
	UserID string `path:"userID"`
	Body   SetStudentTutorBody
}

CoursesStudentsSetTutorInput holds the request parameters for CoursesStudentsSetTutor.

type CoursesStudentsSetTutorOutput

type CoursesStudentsSetTutorOutput struct {
	Body StudentOut
}

CoursesStudentsSetTutorOutput holds the response body for CoursesStudentsSetTutor.

type CoursesStudentsUnassignInput

type CoursesStudentsUnassignInput struct {
	ID     string `path:"id"`
	UserID string `path:"userID"`
}

CoursesStudentsUnassignInput holds the request parameters for CoursesStudentsUnassign.

type CoursesStudentsUnassignOutput

type CoursesStudentsUnassignOutput struct {
	Status int
}

CoursesStudentsUnassignOutput holds the response status for CoursesStudentsUnassign.

type CoursesTutorForUserInput

type CoursesTutorForUserInput struct {
	CourseID string `path:"course_id"`
	UserID   string `path:"user_id"`
}

CoursesTutorForUserInput holds the request parameters for CoursesTutorForUser.

type CoursesTutorForUserOutput

type CoursesTutorForUserOutput struct {
	Body TutorAssignmentOut
}

CoursesTutorForUserOutput holds the response body for CoursesTutorForUser.

type CoursesTutorMineInput

type CoursesTutorMineInput struct {
	CourseID string `path:"course_id"`
}

CoursesTutorMineInput holds the request parameters for CoursesTutorMine.

type CoursesTutorMineOutput

type CoursesTutorMineOutput struct {
	Body TutorAssignmentOut
}

CoursesTutorMineOutput holds the response body for CoursesTutorMine.

type CoursesTutorsAssignInput

type CoursesTutorsAssignInput struct {
	ID   string `path:"id"`
	Body AssignTutorBody
}

CoursesTutorsAssignInput holds the request parameters for CoursesTutorsAssign.

type CoursesTutorsAssignOutput

type CoursesTutorsAssignOutput struct {
	Body TutorOut
}

CoursesTutorsAssignOutput holds the response body for CoursesTutorsAssign.

type CoursesTutorsListInput

type CoursesTutorsListInput struct {
	ID string `path:"id"`
	CourseTutorListQuery
}

CoursesTutorsListInput holds the request parameters for CoursesTutorsList.

type CoursesTutorsListOutput

type CoursesTutorsListOutput struct {
	Body query.PaginatedResponse[TutorOut]
}

CoursesTutorsListOutput holds the response body for CoursesTutorsList.

type CoursesTutorsUnassignInput

type CoursesTutorsUnassignInput struct {
	ID     string `path:"id"`
	UserID string `path:"userID"`
}

CoursesTutorsUnassignInput holds the request parameters for CoursesTutorsUnassign.

type CoursesTutorsUnassignOutput

type CoursesTutorsUnassignOutput struct {
	Status int
}

CoursesTutorsUnassignOutput holds the response status for CoursesTutorsUnassign.

type CoursesUpdateInput

type CoursesUpdateInput struct {
	ID   string `path:"id"`
	Body CourseBody
}

type CoursesUpdateOutput

type CoursesUpdateOutput struct {
	Body db.Course
}

CoursesUpdateOutput holds the response body for CoursesUpdate.

type CreateUserBody

type CreateUserBody struct {
	Email             string  `json:"email" required:"false"`
	Name              string  `json:"name"`
	Subject           string  `json:"subject" required:"false"`
	PreferredUsername string  `json:"preferred_username" required:"false"`
	StudentID         string  `json:"student_id" required:"false"`
	AccountType       string  `json:"account_type" required:"false"`
	Faculty           string  `json:"faculty" required:"false"`
	EnrolledSubject   string  `json:"enrolled_subject" required:"false"`
	ExtraClaims       string  `json:"extra_claims" required:"false"`
	Role              db.Role `json:"role" required:"false"`
}

CreateUserBody is the request body for creating a user. The id and the timestamp fields are server-managed and not part of the payload.

type CronJobResponse

type CronJobResponse struct {
	ID                 string      `json:"id"`
	Name               string      `json:"name"`
	Tags               []string    `json:"tags"`
	IsRunning          bool        `json:"is_running"`
	LastRunStartedAt   *time.Time  `json:"last_run_started_at,omitempty"`
	LastRunCompletedAt *time.Time  `json:"last_run_completed_at,omitempty"`
	NextRun            *time.Time  `json:"next_run,omitempty"`
	NextRuns           []time.Time `json:"next_runs,omitempty"`
	Schedule           any         `json:"schedule,omitempty"` // optional, see below
}

func ListCronJobs

func ListCronJobs(scheduler gocron.Scheduler) []CronJobResponse

ListCronJobs lists all jobs in the given scheduler, for use by the HTML fragment handlers (which have access to *app.App but not *api.Handler).

func RunCronJob

func RunCronJob(c *echo.Context, scheduler gocron.Scheduler, gdb *gorm.DB, id string) (CronJobResponse, error)

RunCronJob triggers the given job by ID string. Returns the refreshed job response and an error if the job couldn't be found or run. Records an audit entry on success.

type CronListInput

type CronListInput struct{}

CronListInput holds the request parameters for CronList (none).

type CronListOutput

type CronListOutput struct {
	Body []CronJobResponse
}

CronListOutput holds the response body for CronList.

type CronRunInput

type CronRunInput struct {
	ID string `path:"id"`
}

CronRunInput holds the request parameters for CronRun.

type CronRunOutput

type CronRunOutput struct {
	Body CronJobResponse
}

CronRunOutput holds the response body for CronRun.

type ExerciseAllRow

type ExerciseAllRow struct {
	ID            uint      `json:"id"`
	Name          string    `json:"name"`
	CourseID      uint      `json:"course_id"`
	CourseName    string    `json:"course_name"`
	Semester      string    `json:"semester"`
	Start         time.Time `json:"start"`
	End           time.Time `json:"end"`
	Points        *float64  `json:"points"`
	UngradedCount int64     `json:"ungraded_count"`
}

ExerciseAllRow is one exercise with its course and the number of submissions that still need grading (points IS NULL).

type ExerciseOut

type ExerciseOut struct {
	ID       uint      `json:"id"`
	Name     string    `json:"name"`
	Start    time.Time `json:"start"`
	End      time.Time `json:"end"`
	Points   *float64  `json:"points"`
	CourseID uint      `json:"course_id"`
}

ExerciseOut is the public shape of an exercise returned by the build/ grading lookup endpoint. It mirrors the subset of db.Exercise that an external consumer (the build/grading service) needs to decide whether a build is eligible and to pick a pre-deadline commit. It is a separate type from db.Exercise so we never accidentally leak internal fields.

type ExercisesAllListParams

type ExercisesAllListParams struct {
	Search   string
	CourseID string
	SortBy   string
	SortDir  string
	Page     int
	PageSize int
}

ExercisesAllListParams holds the filter/sort/pagination inputs for the cross-course exercises list. Shared by the page handler and the HTML fragment handler.

func ParseExercisesAllListParams

func ParseExercisesAllListParams(c *echo.Context) ExercisesAllListParams

ParseExercisesAllListParams extracts ExercisesAllListParams from the query string.

type ExercisesAllListResult

type ExercisesAllListResult struct {
	Items      []ExerciseAllRow
	Total      int64
	Page       int
	PageSize   int
	TotalPages int
	HasNext    bool
	HasPrev    bool
}

ExercisesAllListResult holds the paginated result of the cross-course exercises list.

func AdminListExercisesAll

func AdminListExercisesAll(gdb *gorm.DB, p ExercisesAllListParams) (ExercisesAllListResult, error)

AdminListExercisesAll lists exercises across all courses.

func ScopedListExercisesAll

func ScopedListExercisesAll(gdb *gorm.DB, p ExercisesAllListParams, ownerID string) (ExercisesAllListResult, error)

ScopedListExercisesAll lists exercises restricted to courses owned by the given staff member or granted to them via course_accesses.

type ExercisesListInput

type ExercisesListInput struct {
	ID string `path:"id"`
}

ExercisesListInput holds the request parameters for ExercisesList.

type ExercisesListOutput

type ExercisesListOutput struct {
	Body []ExerciseOut
}

ExercisesListOutput holds the response body for ExercisesList.

type ForgejoRequestFacets

type ForgejoRequestFacets struct {
	Sources []string `json:"sources"`
	Methods []string `json:"methods"`
}

ForgejoRequestFacets is the response body of GET /forgejo-requests/facets. Each field carries the distinct values currently present in the forgejo_requests table, sorted ascending; the UI uses them to populate filter dropdowns.

type ForgejoRequestListParams

type ForgejoRequestListParams struct {
	Search    string
	Source    string
	Method    string
	MinStatus string
	MaxStatus string
	Since     string
	Until     string
	SortBy    string
	SortDir   string
	Page      int
	PageSize  int
}

ForgejoRequestListParams holds the filter/sort/pagination inputs for the Forgejo request log list. Shared by the JSON API handler, the HTML fragment handler, and the page handler.

func ParseForgejoRequestListParams

func ParseForgejoRequestListParams(c *echo.Context) ForgejoRequestListParams

ParseForgejoRequestListParams extracts ForgejoRequestListParams from query string.

type ForgejoRequestListQuery

type ForgejoRequestListQuery struct {
	Search    string `query:"search" required:"false"`
	Source    string `query:"source" required:"false"`
	Method    string `query:"method" required:"false"`
	MinStatus string `query:"min_status" required:"false"`
	MaxStatus string `query:"max_status" required:"false"`
	Since     string `query:"since" required:"false"`
	Until     string `query:"until" required:"false"`
	SortBy    string `query:"sort_by" required:"false"`
	SortDir   string `query:"sort_dir" required:"false"`
	Page      int    `query:"page" default:"1"`
	PageSize  int    `query:"page_size" default:"25"`
}

ForgejoRequestListQuery holds the filter/sort/pagination inputs for GET /forgejo-requests. The query/default tags drive huma's request binding; the values are mapped into ForgejoRequestListParams for the shared AdminListForgejoRequests query path.

type ForgejoRequestListResult

type ForgejoRequestListResult struct {
	Items      []db.ForgejoRequest
	Total      int64
	Page       int
	PageSize   int
	TotalPages int
	HasNext    bool
	HasPrev    bool
}

ForgejoRequestListResult holds the paginated result of a Forgejo request list query.

func AdminListForgejoRequests

func AdminListForgejoRequests(gdb *gorm.DB, p ForgejoRequestListParams, loc *time.Location) (ForgejoRequestListResult, error)

AdminListForgejoRequests runs the Forgejo request list query with filters, sort, and pagination. Used by both the JSON API handler and the HTML fragment handler.

type ForgejoRequestsFacetsInput

type ForgejoRequestsFacetsInput struct{}

ForgejoRequestsFacetsInput has no request parameters.

type ForgejoRequestsFacetsOutput

type ForgejoRequestsFacetsOutput struct {
	Body ForgejoRequestFacets
}

ForgejoRequestsFacetsOutput holds the response body for ForgejoRequestsFacets.

type ForgejoRequestsListInput

type ForgejoRequestsListInput struct {
	ForgejoRequestListQuery
}

ForgejoRequestsListInput holds the request parameters for ForgejoRequestsList.

type ForgejoRequestsListOutput

type ForgejoRequestsListOutput struct {
	Body query.PaginatedResponse[db.ForgejoRequest]
}

ForgejoRequestsListOutput holds the response body for ForgejoRequestsList.

type ForgejoTimePoint

type ForgejoTimePoint struct {
	Date       string `json:"date"`
	Success    int64  `json:"success"`
	Error      int64  `json:"error"`
	AvgLatency int64  `json:"avg_latency_ms"`
}

ForgejoTimePoint combines request counts and latency for a single day.

type GradeBucket

type GradeBucket struct {
	Exercise string `json:"exercise"`
	Bucket   string `json:"bucket"`
	Count    int64  `json:"count"`
}

GradeBucket is one bucket of the grade-distribution histogram for an exercise.

type GradeItem

type GradeItem struct {
	ExerciseName string     `json:"exercise_name"`
	CourseName   string     `json:"course_name"`
	CourseSlug   string     `json:"course_slug"`
	CourseID     uint       `json:"course_id"`
	Points       *float64   `json:"points"`
	MaxPoints    *float64   `json:"max_points"`
	GradedAt     *time.Time `json:"graded_at"`
	Start        time.Time  `json:"start"`
	End          time.Time  `json:"end"`
}

GradeItem is one exercise entry for the /users/me/grades overview. Unlike RecentGradeItem it also covers exercises that have not been graded yet (no student_exercises row, or one with NULL points): for those points and graded_at are null. Start/End are the exercise's time window so the frontend can render countdowns to the deadline.

type GradedUngraded

type GradedUngraded struct {
	Exercise string `json:"exercise"`
	Graded   int64  `json:"graded"`
	Ungraded int64  `json:"ungraded"`
}

GradedUngraded pairs graded and ungraded counts for a single exercise.

type Handler

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

func NewHandler

func NewHandler(a *app.App, hu *Huma) *Handler

func (*Handler) AttendanceHandler

func (h *Handler) AttendanceHandler(c *echo.Context) error

AttendanceHandler handles POST /api/attendance. A tutor scans a student's QR code (which encodes the student's user ID); this logs a TutorialParticipation record for today. The caller must be a tutor of the given course.

Students assigned to this tutor can check in once per day. Students enrolled in the course but assigned to a different tutor (or no tutor) are "guests" and can attend once per course per week. Students not enrolled in the course at all are rejected.

func (*Handler) AuditLogFacets

func (h *Handler) AuditLogFacets(ctx context.Context, input *AuditLogFacetsInput) (*AuditLogFacetsOutput, error)

AuditLogFacets returns the distinct action and resource values currently present in the audit log, used to populate filter dropdowns (admin only).

func (*Handler) AuditLogGet

func (h *Handler) AuditLogGet(ctx context.Context, input *AuditLogGetInput) (*AuditLogGetOutput, error)

AuditLogGet returns a single audit log entry by ID (admin only).

func (*Handler) AuditLogList

func (h *Handler) AuditLogList(ctx context.Context, input *AuditLogListInput) (*AuditLogListOutput, error)

AuditLogList returns a paginated list of audit log entries, newest first, filterable by action, resource, actor and time range (admin only).

func (*Handler) CLIVersion

func (h *Handler) CLIVersion(ctx context.Context, input *CLIVersionInput) (*CLIVersionOutput, error)

CLIVersion returns the minimum required hopper-cli version from the "cli_min_version" setting. Public endpoint; no authentication required.

func (*Handler) Charts

func (h *Handler) Charts(ctx context.Context, input *ChartsInput) (*ChartsOutput, error)

Charts returns all chart datasets (overview, per-course, cross-dimensional) as JSON. When course_id is present and non-zero, per-course charts filter to that course; overview and cross-dimensional charts are always unfiltered.

func (*Handler) ChartsCSV

func (h *Handler) ChartsCSV(ctx context.Context, input *ChartsInput) (*ChartsCSVOutput, error)

ChartsCSV returns a CSV file containing all chart datasets, one section per chart separated by a blank line. When course_id is present and non-zero, per-course charts filter to that course.

func (*Handler) CourseAccessForCourseGrant

func (h *Handler) CourseAccessForCourseGrant(ctx context.Context, input *CourseAccessForCourseGrantInput) (*CourseAccessForCourseGrantOutput, error)

CourseAccessForCourseGrant grants a staff user full manage rights on the course (admin only). Re-granting an existing pair returns the existing grant unchanged (idempotent).

func (*Handler) CourseAccessForCourseList

func (h *Handler) CourseAccessForCourseList(ctx context.Context, input *CourseAccessForCourseListInput) (*CourseAccessForCourseListOutput, error)

CourseAccessForCourseList returns the course access grants on a course (admin only).

func (*Handler) CourseAccessForCourseRevoke

func (h *Handler) CourseAccessForCourseRevoke(ctx context.Context, input *CourseAccessForCourseRevokeInput) (*CourseAccessForCourseRevokeOutput, error)

CourseAccessForCourseRevoke revokes a course access grant (admin only).

func (*Handler) CourseStudentGrades

func (h *Handler) CourseStudentGrades(ctx context.Context, input *CourseStudentGradesInput) (*CourseStudentGradesOutput, error)

CourseStudentGrades returns all exercises of one course for one student, including ungraded ones (points and graded_at are null for those), with the same item shape as UsersMeGrades. The course is resolved by its UID and the student by their Forgejo username. Access is limited to the student themselves and course staff (course owner, admins, tutors of that course); anyone else signed in gets 403, unknown courses or students 404.

func (*Handler) CoursesCreate

func (h *Handler) CoursesCreate(ctx context.Context, input *CoursesCreateInput) (*CoursesCreateOutput, error)

CoursesCreate creates a new course (staff only).

func (*Handler) CoursesDelete

func (h *Handler) CoursesDelete(ctx context.Context, input *CoursesDeleteInput) (*CoursesDeleteOutput, error)

CoursesDelete deletes a course by ID (soft delete, staff only).

func (*Handler) CoursesGet

func (h *Handler) CoursesGet(ctx context.Context, input *CoursesGetInput) (*CoursesGetOutput, error)

CoursesGet returns a single course by ID. Non-admin users see only joinable courses.

func (*Handler) CoursesHardDelete

func (h *Handler) CoursesHardDelete(ctx context.Context, input *CoursesHardDeleteInput) (*CoursesHardDeleteOutput, error)

CoursesHardDelete permanently removes an archived course and all of its data from the database. Only courses that are already soft-deleted (archived) can be hard-deleted (admin only).

func (*Handler) CoursesJoin

func (h *Handler) CoursesJoin(ctx context.Context, input *CoursesJoinInput) (*CoursesJoinOutput, error)

CoursesJoin enrolls the authenticated user as a student in the given course. The course must have allow_join enabled. When Forgejo is configured, the user is required to have an account on the Forgejo instance (the local part of their email is used as the username); otherwise the request is rejected with 409.

func (*Handler) CoursesList

func (h *Handler) CoursesList(ctx context.Context, input *CoursesListInput) (*CoursesListOutput, error)

CoursesList returns a paginated list of courses. Non-admin users see only joinable courses.

func (*Handler) CoursesRestore

func (h *Handler) CoursesRestore(ctx context.Context, input *CoursesRestoreInput) (*CoursesRestoreOutput, error)

CoursesRestore restores a soft-deleted (archived) course by clearing its deleted_at (staff only).

func (*Handler) CoursesStudentsAssignTutorBulk

func (h *Handler) CoursesStudentsAssignTutorBulk(ctx context.Context, input *CoursesStudentsAssignTutorBulkInput) (*CoursesStudentsAssignTutorBulkOutput, error)

CoursesStudentsAssignTutorBulk assigns or clears the tutor for multiple students of a course in one transaction (staff only). User ids without a matching student row in the course are skipped. Returns the number of student rows updated.

func (*Handler) CoursesStudentsAutoDistribute

func (h *Handler) CoursesStudentsAutoDistribute(ctx context.Context, input *CoursesStudentsAutoDistributeInput) (*CoursesStudentsAutoDistributeOutput, error)

CoursesStudentsAutoDistribute evenly distributes the unassigned students of a course across its tutors (staff only). Existing assignments are kept.

func (*Handler) CoursesStudentsList

func (h *Handler) CoursesStudentsList(ctx context.Context, input *CoursesStudentsListInput) (*CoursesStudentsListOutput, error)

CoursesStudentsList returns a paginated list of students enrolled in a specific course. Only callers who may manage the course get access.

func (*Handler) CoursesStudentsMine

func (h *Handler) CoursesStudentsMine(ctx context.Context, input *CoursesStudentsMineInput) (*CoursesStudentsMineOutput, error)

CoursesStudentsMine returns the students assigned to the authenticated tutor in the given course. Tutors (including admins/owners who are assigned as tutors) see only their assigned students; admins and the course owner without a tutor assignment see all students. Each student includes a trimmed user profile (id, name, preferred_username; the Forgejo repo name).

func (*Handler) CoursesStudentsSetTutor

func (h *Handler) CoursesStudentsSetTutor(ctx context.Context, input *CoursesStudentsSetTutorInput) (*CoursesStudentsSetTutorOutput, error)

CoursesStudentsSetTutor sets or clears the tutor of a single student in a course (staff only). An absent or null tutor_id clears the assignment.

func (*Handler) CoursesStudentsUnassign

func (h *Handler) CoursesStudentsUnassign(ctx context.Context, input *CoursesStudentsUnassignInput) (*CoursesStudentsUnassignOutput, error)

CoursesStudentsUnassign removes a user's enrollment from the given course (staff only).

func (*Handler) CoursesTutorForUser

func (h *Handler) CoursesTutorForUser(ctx context.Context, input *CoursesTutorForUserInput) (*CoursesTutorForUserOutput, error)

CoursesTutorForUser returns the tutor assigned to the given user in the course. Only the user themselves and course staff (course owner, admins, tutors of the course) may look it up; anyone else signed in gets 403. The course is addressed by its numeric ID or its UID.

func (*Handler) CoursesTutorMine

func (h *Handler) CoursesTutorMine(ctx context.Context, input *CoursesTutorMineInput) (*CoursesTutorMineOutput, error)

CoursesTutorMine returns the tutor assigned to the authenticated user in the given course. The course is addressed by its numeric ID or its UID.

func (*Handler) CoursesTutorsAssign

func (h *Handler) CoursesTutorsAssign(ctx context.Context, input *CoursesTutorsAssignInput) (*CoursesTutorsAssignOutput, error)

CoursesTutorsAssign assigns an existing user as a tutor of the given course (staff only).

func (*Handler) CoursesTutorsList

func (h *Handler) CoursesTutorsList(ctx context.Context, input *CoursesTutorsListInput) (*CoursesTutorsListOutput, error)

CoursesTutorsList returns a paginated list of tutors assigned to a specific course. Only callers who may manage the course get access.

func (*Handler) CoursesTutorsUnassign

func (h *Handler) CoursesTutorsUnassign(ctx context.Context, input *CoursesTutorsUnassignInput) (*CoursesTutorsUnassignOutput, error)

CoursesTutorsUnassign removes a user as a tutor of the given course (staff only). In the same transaction the tutor's assigned students are unassigned (tutor_id set to NULL) before the tutor row is deleted, so no student row depends on the removed tutor afterwards.

func (*Handler) CoursesUpdate

func (h *Handler) CoursesUpdate(ctx context.Context, input *CoursesUpdateInput) (*CoursesUpdateOutput, error)

CoursesUpdate updates an existing course by ID. Staff may only update the courses they own.

func (*Handler) CronList

func (h *Handler) CronList(ctx context.Context, input *CronListInput) (*CronListOutput, error)

CronList returns a list of all jobs regularly run by the orchestration app.

func (*Handler) CronRun

func (h *Handler) CronRun(ctx context.Context, input *CronRunInput) (*CronRunOutput, error)

CronRun triggers the given job to run immediately without waiting for its next scheduled execution.

func (*Handler) ExercisesList

func (h *Handler) ExercisesList(ctx context.Context, input *ExercisesListInput) (*ExercisesListOutput, error)

ExercisesList returns the exercises defined in a course, ordered by start date. Used by the build/grading service to resolve exercise deadlines without duplicating the exercise table.

func (*Handler) ForgejoRequestsFacets

func (h *Handler) ForgejoRequestsFacets(ctx context.Context, input *ForgejoRequestsFacetsInput) (*ForgejoRequestsFacetsOutput, error)

ForgejoRequestsFacets returns the distinct source and method values currently present in the Forgejo request log, used to populate filter dropdowns (admin only).

func (*Handler) ForgejoRequestsList

func (h *Handler) ForgejoRequestsList(ctx context.Context, input *ForgejoRequestsListInput) (*ForgejoRequestsListOutput, error)

ForgejoRequestsList returns a paginated list of every outgoing Forgejo HTTP request the orchestrator has made (most recent first), with full request and response envelopes (admin only).

func (*Handler) GitPostRecieveHandler

func (h *Handler) GitPostRecieveHandler(c *echo.Context) error

func (*Handler) GitPreRecieveHandler

func (h *Handler) GitPreRecieveHandler(c *echo.Context) error

func (*Handler) HealthCheckRun

func (h *Handler) HealthCheckRun(ctx context.Context, input *HealthCheckRunInput) (*HealthCheckRunOutput, error)

HealthCheckRun triggers a single health check by name and returns the fresh result. Records an audit entry on success.

func (*Handler) HealthStatus

func (h *Handler) HealthStatus(ctx context.Context, input *HealthStatusInput) (*HealthStatusOutput, error)

HealthStatus returns the latest results of all health checks, the summary counts, the current Beszel metrics snapshot, and process system info.

func (*Handler) SettingsCreate

func (h *Handler) SettingsCreate(ctx context.Context, input *SettingsCreateInput) (*SettingsCreateOutput, error)

SettingsCreate creates a new setting (admin only).

func (*Handler) SettingsDelete

func (h *Handler) SettingsDelete(ctx context.Context, input *SettingsDeleteInput) (*SettingsDeleteOutput, error)

SettingsDelete deletes a setting by key (admin only).

func (*Handler) SettingsGet

func (h *Handler) SettingsGet(ctx context.Context, input *SettingsGetInput) (*SettingsGetOutput, error)

SettingsGet returns a single setting by key if visible to the caller.

func (*Handler) SettingsList

func (h *Handler) SettingsList(ctx context.Context, input *SettingsListInput) (*SettingsListOutput, error)

SettingsList returns a paginated list of settings visible to the caller.

func (*Handler) SettingsUpdate

func (h *Handler) SettingsUpdate(ctx context.Context, input *SettingsUpdateInput) (*SettingsUpdateOutput, error)

SettingsUpdate updates an existing setting by key (admin only).

func (*Handler) Stats

func (h *Handler) Stats(ctx context.Context, input *StatsInput) (*StatsOutput, error)

Stats returns the aggregate dashboard statistics (admin only).

func (*Handler) TaskGet

func (h *Handler) TaskGet(ctx context.Context, input *TaskGetInput) (*TaskGetOutput, error)

TaskGet returns details for a single registered task.

func (*Handler) TaskList

func (h *Handler) TaskList(ctx context.Context, input *TaskListInput) (*TaskListOutput, error)

TaskList returns a list of all registered tasks that can be run manually.

func (*Handler) TaskRun

func (h *Handler) TaskRun(ctx context.Context, input *TaskRunInput) (*TaskRunOutput, error)

TaskRun triggers a registered task to run immediately.

func (*Handler) UsersAdminList

func (h *Handler) UsersAdminList(ctx context.Context, input *UsersAdminListInput) (*UsersAdminListOutput, error)

UsersAdminList returns a paginated list of users with their session count, last login, recent sessions, and course relationships (admin only).

func (*Handler) UsersCreate

func (h *Handler) UsersCreate(ctx context.Context, input *UsersCreateInput) (*UsersCreateOutput, error)

UsersCreate creates a new user (admin only).

func (*Handler) UsersDelete

func (h *Handler) UsersDelete(ctx context.Context, input *UsersDeleteInput) (*UsersDeleteOutput, error)

UsersDelete deletes a user by ID (admin only).

func (*Handler) UsersDeleteSessions

func (h *Handler) UsersDeleteSessions(ctx context.Context, input *UsersDeleteSessionsInput) (*UsersDeleteSessionsOutput, error)

UsersDeleteSessions deletes all active OIDC sessions for the specified user, forcing them to re-authenticate on their next request (admin only).

func (*Handler) UsersGet

func (h *Handler) UsersGet(ctx context.Context, input *UsersGetInput) (*UsersGetOutput, error)

UsersGet returns a single user by ID. Non-admin callers may only read users they are related to (staff: members of their courses; tutors: their assigned students; anyone: themselves); others get 404.

func (*Handler) UsersList

func (h *Handler) UsersList(ctx context.Context, input *UsersListInput) (*UsersListOutput, error)

UsersList returns a paginated list of users matching the requested filters. Non-admin callers only see users they are related to: staff see the students and tutors of the courses they own, tutors see their assigned students, and everyone always sees themselves.

func (*Handler) UsersMe

func (h *Handler) UsersMe(ctx context.Context, input *UsersMeInput) (*UsersMeOutput, error)

UsersMe returns the authenticated user for the currently signed-in user.

func (*Handler) UsersMeCourses

func (h *Handler) UsersMeCourses(ctx context.Context, input *UsersMeCoursesInput) (*UsersMeCoursesOutput, error)

UsersMeCourses returns the courses in which the authenticated user is a student or a tutor. Each entry carries a `role` field ("student" or "tutor") so the caller can pick the right action. Archived (soft-deleted) courses are excluded.

func (*Handler) UsersMeGrades

func (h *Handler) UsersMeGrades(ctx context.Context, input *UsersMeGradesInput) (*UsersMeGradesOutput, error)

UsersMeGrades returns all exercises of the courses the authenticated user is enrolled in as a student, including ungraded ones (points and graded_at are null for those). Items are ordered by course name, then by exercise start, end, and name.

func (*Handler) UsersMeRecentGrades

func (h *Handler) UsersMeRecentGrades(ctx context.Context, input *UsersMeRecentGradesInput) (*UsersMeRecentGradesOutput, error)

UsersMeRecentGrades returns the 5 most recently graded exercises for the authenticated user.

func (*Handler) UsersUpdate

func (h *Handler) UsersUpdate(ctx context.Context, input *UsersUpdateInput) (*UsersUpdateOutput, error)

UsersUpdate updates an existing user by ID (admin only).

type HealthBeszelContainer

type HealthBeszelContainer struct {
	Name       string  `json:"name"`
	CPUPercent float64 `json:"cpu_percent"`
	MemMB      float64 `json:"mem_mb"`
	NetUpMBs   float64 `json:"net_up_mbs"`
	NetDownMBs float64 `json:"net_down_mbs"`
}

HealthBeszelContainer is one container's resource usage from Beszel.

type HealthBeszelInfo

type HealthBeszelInfo struct {
	HostStatus    string                  `json:"host_status"`
	HostUptimeSec float64                 `json:"host_uptime_sec"`
	AgentVersion  string                  `json:"agent_version"`
	LastHeartbeat time.Time               `json:"last_heartbeat"`
	CPUPercent    float64                 `json:"cpu_percent"`
	MemUsedGB     float64                 `json:"mem_used_gb"`
	MemTotalGB    float64                 `json:"mem_total_gb"`
	MemPercent    float64                 `json:"mem_percent"`
	DiskUsedGB    float64                 `json:"disk_used_gb"`
	DiskTotalGB   float64                 `json:"disk_total_gb"`
	DiskPercent   float64                 `json:"disk_percent"`
	LoadAvg       [3]float64              `json:"load_avg"`
	NetUpMBs      float64                 `json:"net_up_mbs"`
	NetDownMBs    float64                 `json:"net_down_mbs"`
	Containers    []HealthBeszelContainer `json:"containers"`
}

HealthBeszelInfo mirrors health.BeszelSnapshot in JSON-friendly form.

type HealthCheckItem

type HealthCheckItem struct {
	Name                string         `json:"name"`
	Group               string         `json:"group"`
	Status              string         `json:"status"`
	Message             string         `json:"message"`
	LatencyMS           int64          `json:"latency_ms"`
	ConsecutiveFailures int            `json:"consecutive_failures"`
	CheckedAt           time.Time      `json:"checked_at"`
	Detail              map[string]any `json:"detail,omitempty"`
}

HealthCheckItem is the API view of one health check's latest result.

type HealthCheckRunInput

type HealthCheckRunInput struct {
	Name string `path:"name"`
}

HealthCheckRunInput holds the request parameters for HealthCheckRun.

type HealthCheckRunOutput

type HealthCheckRunOutput struct {
	Body HealthCheckItem
}

HealthCheckRunOutput holds the response body for HealthCheckRun.

type HealthStatusInput

type HealthStatusInput struct{}

HealthStatusInput holds the request parameters for HealthStatus (none).

type HealthStatusOutput

type HealthStatusOutput struct {
	Body struct {
		Checks      []HealthCheckItem      `json:"checks"`
		Summary     HealthSummary          `json:"summary"`
		Beszel      *HealthBeszelInfo      `json:"beszel"`
		System      HealthSystemInfo       `json:"system"`
		Warnings    []config.ConfigWarning `json:"warnings"`
		GeneratedAt time.Time              `json:"generated_at"`
	}
}

HealthStatusOutput holds the response body for HealthStatus.

type HealthSummary

type HealthSummary struct {
	Total   int `json:"total"`
	OK      int `json:"ok"`
	Warn    int `json:"warn"`
	Fail    int `json:"fail"`
	Unknown int `json:"unknown"`
}

HealthSummary counts the latest results by status.

type HealthSystemInfo

type HealthSystemInfo struct {
	GoVersion string    `json:"go_version"`
	UptimeSec int64     `json:"uptime_sec"`
	StartedAt time.Time `json:"started_at"`
}

HealthSystemInfo describes the orchestration process itself.

type Huma

type Huma struct {
	API huma.API
	// contains filtered or unexported fields
}

Huma holds the huma API mounted on the shared echo instance.

func NewHuma

func NewHuma(e *echo.Echo, meta config.APIConfig) *Huma

NewHuma builds the huma API on top of the given echo instance. The API metadata comes from the resolved config (API_HOST, API_TERMS, API_SUPPORT_* env vars, read once at startup). Huma serves the generated spec itself at /api/openapi.json and /api/openapi.yaml and the docs UI at /api/docs. The schema endpoint is disabled because the spec embeds all schemas.

func (*Huma) UseAuth

func (h *Huma) UseAuth(mw ...echo.MiddlewareFunc) huma.Middlewares

UseAuth converts echo middleware (a.Auth.Require, RequireStaff, ...) into huma middleware for one operation. The middleware runs against the unwrapped echo context; if it short-circuits with an error, the error is rendered by echo's existing error handler unchanged and the huma handler is never invoked.

type LabelCount

type LabelCount struct {
	Label string `json:"label"`
	Count int64  `json:"count"`
}

LabelCount is a generic label/count pair for categorical chart data.

type MyCourseItem

type MyCourseItem struct {
	db.Course
	Role string `json:"role"` // "student" or "tutor"
}

MyCourseItem pairs a full course with the role the current user has in it (student or tutor). Used by /users/me/courses so the dashboard template can render the right action per role.

type RecentGradeItem

type RecentGradeItem struct {
	ExerciseName string    `json:"exercise_name"`
	CourseName   string    `json:"course_name"`
	CourseSlug   string    `json:"course_slug"`
	CourseID     uint      `json:"course_id"`
	Points       *float64  `json:"points"`
	MaxPoints    *float64  `json:"max_points"`
	GradedAt     time.Time `json:"graded_at"`
}

RecentGradeItem is a grade record for the dashboard "Recently Graded" list.

type SemesterCount

type SemesterCount struct {
	Semester string `json:"semester"`
	Count    int64  `json:"count"`
}

SemesterCount pairs a semester identifier with the number of courses in it.

type SessionBrief

type SessionBrief struct {
	ID        string    `json:"id"`
	CreatedAt time.Time `json:"created_at"`
	ExpiresAt time.Time `json:"expires_at"`
}

SessionBrief is a minimal representation of an OIDC session for the admin UI.

type SetStudentTutorBody

type SetStudentTutorBody struct {
	TutorID *uint `json:"tutor_id" required:"false"`
}

SetStudentTutorBody is the body for PUT /courses/{id}/students/{userID}/tutor. An absent or null tutor_id clears the assignment.

type SettingBody

type SettingBody struct {
	Key    string `json:"key"`
	Value  string `json:"value"`
	Type   string `json:"type" required:"false"`
	Access string `json:"access" required:"false"`
}

SettingBody is the request body for creating and updating settings. Timestamps are server-managed and not part of the payload.

type SettingListQuery

type SettingListQuery struct {
	Key      string `query:"key" required:"false" filter:"key"`
	Type     string `query:"type" required:"false" filter:"type"`
	Access   string `query:"access" required:"false" filter:"access"`
	Search   string `query:"search" required:"false" search:"key,value"`
	SortBy   string `query:"sort_by" required:"false" sort:"key,access,created_at,updated_at"`
	SortDir  string `query:"sort_dir" required:"false"`
	Page     int    `query:"page" default:"1"`
	PageSize int    `query:"page_size" default:"25"`
}

SettingListQuery holds the filter/sort/pagination inputs for GET /settings. The query/default tags drive huma's request binding; the filter/search/sort tags are ignored by huma and consumed by query.Scopes.

type SettingsCreateInput

type SettingsCreateInput struct {
	Body SettingBody
}

SettingsCreateInput holds the request parameters for SettingsCreate.

type SettingsCreateOutput

type SettingsCreateOutput struct {
	Body db.Setting
}

SettingsCreateOutput holds the response body for SettingsCreate.

type SettingsDeleteInput

type SettingsDeleteInput struct {
	Key string `path:"key"`
}

SettingsDeleteInput holds the request parameters for SettingsDelete.

type SettingsDeleteOutput

type SettingsDeleteOutput struct {
	Status int
}

SettingsDeleteOutput holds the response status for SettingsDelete.

type SettingsGetInput

type SettingsGetInput struct {
	Key string `path:"key"`
}

SettingsGetInput holds the request parameters for SettingsGet.

type SettingsGetOutput

type SettingsGetOutput struct {
	Body db.Setting
}

SettingsGetOutput holds the response body for SettingsGet.

type SettingsListInput

type SettingsListInput struct {
	SettingListQuery
}

SettingsListInput holds the request parameters for SettingsList.

type SettingsListOutput

type SettingsListOutput struct {
	Body query.PaginatedResponse[db.Setting]
}

SettingsListOutput holds the response body for SettingsList.

type SettingsUpdateInput

type SettingsUpdateInput struct {
	Key  string `path:"key"`
	Body SettingBody
}

SettingsUpdateInput holds the request parameters for SettingsUpdate.

type SettingsUpdateOutput

type SettingsUpdateOutput struct {
	Body db.Setting
}

SettingsUpdateOutput holds the response body for SettingsUpdate.

type StaffStatsResponse

type StaffStatsResponse struct {
	MyCourses         int64           `json:"my_courses"`
	JoinableCourses   int64           `json:"joinable_courses"`
	ArchivedCourses   int64           `json:"archived_courses"`
	SemesterBreakdown []SemesterCount `json:"semester_breakdown"`
	MyStudents        int64           `json:"my_students"`
	MyTutors          int64           `json:"my_tutors"`
	MyExercises       int64           `json:"my_exercises"`
}

StaffStatsResponse holds dashboard stats scoped to a single staff member.

func ComputeStaffStats

func ComputeStaffStats(gdb *gorm.DB, userID string) StaffStatsResponse

ComputeStaffStats aggregates dashboard statistics scoped to the courses owned by the given user ID or granted to them via course_accesses.

type StatsInput

type StatsInput struct{}

StatsInput holds the request parameters for Stats (none).

type StatsOutput

type StatsOutput struct {
	Body StatsResponse
}

StatsOutput holds the response body for Stats.

type StatsResponse

type StatsResponse struct {
	TotalUsers        int64           `json:"total_users"`
	StaffUsers        int64           `json:"total_staff"`
	AdminUsers        int64           `json:"admin_users"`
	TotalCourses      int64           `json:"total_courses"`
	JoinableCourses   int64           `json:"joinable_courses"`
	ArchivedCourses   int64           `json:"archived_courses"`
	TotalStudents     int64           `json:"total_students"`
	TotalTutors       int64           `json:"total_tutors"`
	TotalAuditLogs    int64           `json:"total_audit_logs"`
	TotalExercises    int64           `json:"total_exercises"`
	ActiveSessions    int64           `json:"active_sessions"`
	TotalTasks        int64           `json:"total_tasks"`
	TotalCronJobs     int64           `json:"total_cron_jobs"`
	OrphanOrgs        int64           `json:"orphan_orgs"`
	SemesterBreakdown []SemesterCount `json:"semester_breakdown"`
}

StatsResponse is the response body of GET /stats. It aggregates counts across the major tables and includes a Forgejo orphan-org count when the Forgejo client is configured.

func ComputeStats

func ComputeStats(gdb *gorm.DB, scheduler jobsLister) StatsResponse

ComputeStats aggregates dashboard statistics. Exported so the HTML fragment handler (which has *app.App but not *api.Handler) can call it. The orphan-org count needs the Forgejo client, which is a package-level singleton, so this function has no receiver.

type StudentOut

type StudentOut struct {
	ID             uint       `json:"id"`
	Matrikelnummer *int       `json:"matrikelnummer"`
	CourseID       uint       `json:"course_id"`
	UserID         string     `json:"user_id"`
	TutorID        *uint      `json:"tutor_id"`
	User           *UserBrief `json:"user,omitempty"`
	// Email is only populated by ListCourseStudents, which is reachable
	// exclusively by callers who may manage the course. Other constructors
	// (studentsToOut, the set-tutor handler) leave it empty, keeping the
	// tutor-facing endpoints trimmed to the UserBrief identity fields.
	Email string      `json:"email,omitempty"`
	Tutor *TutorBrief `json:"tutor,omitempty"`
}

StudentOut is the public shape of a course-student row. It mirrors the flat db.Student fields and embeds the preloaded user as a trimmed UserBrief. db.Student.User is tagged json:"-" in the model (repo-wide convention to avoid serialization loops), so the DTO carries the user explicitly and controls exactly which user fields leave the API.

type TaskGetInput

type TaskGetInput struct {
	Name string `path:"name"`
}

TaskGetInput holds the request parameters for TaskGet.

type TaskGetOutput

type TaskGetOutput struct {
	Body tasks.Task
}

TaskGetOutput holds the response body for TaskGet.

type TaskListInput

type TaskListInput struct{}

TaskListInput holds the request parameters for TaskList (none).

type TaskListOutput

type TaskListOutput struct {
	Body []tasks.Task
}

TaskListOutput holds the response body for TaskList.

type TaskRunInput

type TaskRunInput struct {
	Name string `path:"name"`
}

TaskRunInput holds the request parameters for TaskRun.

type TaskRunOutput

type TaskRunOutput struct {
	Body map[string]string
}

TaskRunOutput holds the response body for TaskRun.

type TimeSeries

type TimeSeries struct {
	Label  string            `json:"label"`
	Points []TimeSeriesPoint `json:"points"`
}

TimeSeries is a named series of time-series points.

type TimeSeriesPoint

type TimeSeriesPoint struct {
	Date  string `json:"date"`
	Count int64  `json:"count"`
}

TimeSeriesPoint is a single point in a time series.

type TutorAssignmentBrief

type TutorAssignmentBrief struct {
	Name              string `json:"name"`
	PreferredUsername string `json:"preferred_username"`
	Email             string `json:"email"`
}

TutorAssignmentBrief is the trimmed tutor identity returned by the tutor-assignment lookup endpoints. Name may be empty (the client falls back to preferred_username then). Email is included so clients can show a mailto link to the tutor.

type TutorAssignmentOut

type TutorAssignmentOut struct {
	Tutor *TutorAssignmentBrief `json:"tutor"`
}

TutorAssignmentOut is the response body of the tutor-assignment lookup endpoints. Tutor is null when the user has no students row in the course, no assigned tutor, or the tutor's user record is missing; the client hides the label then.

type TutorBrief

type TutorBrief struct {
	ID     uint   `json:"id"`
	UserID string `json:"user_id"`
	Name   string `json:"name"`
}

TutorBrief is a trimmed tutor representation for course-student list endpoints. Name is the tutor's user name (empty when the user is not loaded or soft-deleted).

type TutorOut

type TutorOut struct {
	ID           uint       `json:"id"`
	Description  string     `json:"description"`
	CreatedAt    time.Time  `json:"created_at"`
	UpdatedAt    time.Time  `json:"updated_at"`
	CourseID     uint       `json:"course_id"`
	UserID       string     `json:"user_id"`
	StudentCount int64      `json:"student_count"`
	User         *UserBrief `json:"user,omitempty"`
}

TutorOut is the public shape of a course-tutor row. It mirrors the flat db.Tutor fields and embeds the preloaded user as a trimmed UserBrief. db.Tutor.User is tagged json:"-" in the model (repo-wide convention to avoid serialization loops), so the DTO carries the user explicitly and controls exactly which user fields leave the API.

type TutorUserSearchResult

type TutorUserSearchResult struct {
	Items      []db.User
	Page       int
	PageSize   int
	TotalItems int64
	TotalPages int
	HasNext    bool
	HasPrev    bool
}

TutorUserSearchResult holds the paginated result of the tutor-assignment user search.

func SearchUsersForTutorAssignment

func SearchUsersForTutorAssignment(gdb *gorm.DB, search, matrikelnummer string, page, pageSize int, courseID uint) (TutorUserSearchResult, error)

SearchUsersForTutorAssignment returns a paginated list of users matching the search string (ILIKE on name, email, student_id, and preferred_username, mirroring AdminListUsers) and the matrikelnummer filter (ILIKE on student_id), excluding users who are already tutors of courseID. Used by the tutor-assignment page's user list so already-assigned tutors do not show up as add candidates.

type UpdateUserBody

type UpdateUserBody struct {
	Subject           string  `json:"subject" required:"false"`
	PreferredUsername string  `json:"preferred_username" required:"false"`
	StudentID         string  `json:"student_id" required:"false"`
	AccountType       string  `json:"account_type" required:"false"`
	Faculty           string  `json:"faculty" required:"false"`
	EnrolledSubject   string  `json:"enrolled_subject" required:"false"`
	Role              db.Role `json:"role" required:"false"`
}

UpdateUserBody is the request body for updating a user. name, email, and extra_claims are excluded because the update path never writes them. subject is still accepted so the identity-uniqueness check sees the same values the handler checks, but it is omitted from the update payload.

type UserAdminItem

type UserAdminItem struct {
	db.User `json:"user"`

	SessionCount int            `json:"session_count"`
	LastLogin    *time.Time     `json:"last_login"`
	Sessions     []SessionBrief `json:"sessions"`

	OwnedCourses    []CourseBrief `json:"owned_courses"`
	TutoredCourses  []CourseBrief `json:"tutored_courses"`
	EnrolledCourses []CourseBrief `json:"enrolled_courses"`
	GrantedCourses  []CourseBrief `json:"granted_courses"`

	APIKeys []db.ApiKey `json:"api_keys"`

	Grades []RecentGradeItem `json:"grades"`

	IsProtectedAdmin bool `json:"is_protected_admin"`
}

UserAdminItem is the full user representation for the admin users page, including session and course relationship data.

func AdminGetUser

func AdminGetUser(gdb *gorm.DB, id string) (UserAdminItem, error)

AdminGetUser fetches a single user with sessions and course relationships for the admin detail modal. Returns the enriched UserAdminItem.

func ScopedGetUser

func ScopedGetUser(gdb *gorm.DB, ownerID, targetUserID string) (UserAdminItem, error)

ScopedGetUser fetches a single user if they are within the caller's scope (self, a student/tutor in a course the caller owns or was granted via course_accesses, or a student assigned to a tutor slot the caller holds). Returns gorm.ErrRecordNotFound if the target is out of scope.

type UserBrief

type UserBrief struct {
	ID                string `json:"id"`
	Name              string `json:"name"`
	PreferredUsername string `json:"preferred_username"`
}

UserBrief is a trimmed user representation that is safe to embed in list endpoints reachable by non-admin staff (e.g. tutors viewing their students). It exposes only identity fields; email, OIDC subject, student ID (matrikelnummer), account type, faculty, enrolled subject, raw extra claims, and the global role are intentionally omitted.

type UserListQuery

type UserListQuery struct {
	ID        string `query:"id" required:"false" filter:"id"`
	Email     string `query:"email" required:"false" filter:"email"`
	Subject   string `query:"subject" required:"false" filter:"subject"`
	StudentID string `query:"student_id" required:"false" filter:"student_id"`
	Role      string `query:"role" required:"false" filter:"role"`
	Search    string `query:"search" required:"false" search:"name,email"`
	SortBy    string `query:"sort_by" required:"false" sort:"id,name,email,created_at,updated_at,student_id,role" default_sort:"created_at desc"`
	SortDir   string `query:"sort_dir" required:"false"`
	Page      int    `query:"page" default:"1"`
	PageSize  int    `query:"page_size" default:"25"`
}

UserListQuery holds the filter and pagination inputs for GET /users. The query/default tags drive huma's request binding; the filter/search/sort tags are ignored by huma and consumed by query.Scopes.

type UsersAdminListInput

type UsersAdminListInput struct {
	UsersAdminListQuery
}

UsersAdminListInput holds the request parameters for UsersAdminList.

type UsersAdminListOutput

type UsersAdminListOutput struct {
	Body query.PaginatedResponse[UserAdminItem]
}

UsersAdminListOutput holds the response body for UsersAdminList.

type UsersAdminListParams

type UsersAdminListParams struct {
	Search         string
	Role           string
	Matrikelnummer string
	Tutor          bool
	SortBy         string
	SortDir        string
	Page           int
	PageSize       int
}

UsersAdminListParams holds the filter/sort/pagination inputs for the admin users list. Shared by the JSON API handler and the HTML fragment handler.

func ParseUsersAdminListParams

func ParseUsersAdminListParams(c *echo.Context) UsersAdminListParams

ParseUsersAdminListParams extracts UsersAdminListParams from query string. Shared by the JSON API handler, the HTML fragment handler, and the page handler so all three paths agree on how params are read.

type UsersAdminListQuery

type UsersAdminListQuery struct {
	Search         string `query:"search" required:"false"`
	Role           string `query:"role" required:"false"`
	Matrikelnummer string `query:"matrikelnummer" required:"false"`
	Tutor          bool   `query:"tutor" required:"false"`
	SortBy         string `query:"sort_by" required:"false"`
	SortDir        string `query:"sort_dir" required:"false"`
	Page           int    `query:"page" default:"1"`
	PageSize       int    `query:"page_size" default:"25"`
}

UsersAdminListQuery holds the filter/sort/pagination inputs for the admin users list. The query/default tags drive huma's request binding; the values are mapped into UsersAdminListParams for the shared AdminListUsers query path.

type UsersAdminListResult

type UsersAdminListResult struct {
	Items      []UserAdminItem
	Total      int64
	Page       int
	PageSize   int
	TotalPages int
	HasNext    bool
	HasPrev    bool
}

UsersAdminListResult holds the paginated result of an admin users list query.

func AdminListUsers

func AdminListUsers(gdb *gorm.DB, p UsersAdminListParams) (UsersAdminListResult, error)

AdminListUsers runs the admin users list query and returns enriched items with pagination metadata. Used by both the JSON API handler and the HTML fragment handler: one query path, two response formats.

func ScopedListUsers

func ScopedListUsers(gdb *gorm.DB, p UsersAdminListParams, ownerID string) (UsersAdminListResult, error)

ScopedListUsers runs the admin users list query but restricts results to users visible to the given staff member: themselves, plus students and tutors of courses they own or were granted via course_accesses. Used by staff-scoped fragment handlers.

type UsersCreateInput

type UsersCreateInput struct {
	Body CreateUserBody
}

UsersCreateInput holds the request parameters for UsersCreate.

type UsersCreateOutput

type UsersCreateOutput struct {
	Body db.User
}

UsersCreateOutput holds the response body for UsersCreate.

type UsersDeleteInput

type UsersDeleteInput struct {
	ID string `path:"id"`
}

UsersDeleteInput holds the request parameters for UsersDelete.

type UsersDeleteOutput

type UsersDeleteOutput struct {
	Status int
}

UsersDeleteOutput holds the response status for UsersDelete.

type UsersDeleteSessionsInput

type UsersDeleteSessionsInput struct {
	ID string `path:"id"`
}

UsersDeleteSessionsInput holds the request parameters for UsersDeleteSessions.

type UsersDeleteSessionsOutput

type UsersDeleteSessionsOutput struct {
	Body struct {
		DeletedCount int64 `json:"deleted_count"`
	}
}

UsersDeleteSessionsOutput holds the response body for UsersDeleteSessions.

type UsersGetInput

type UsersGetInput struct {
	ID string `path:"id"`
}

UsersGetInput holds the request parameters for UsersGet.

type UsersGetOutput

type UsersGetOutput struct {
	Body db.User
}

UsersGetOutput holds the response body for UsersGet.

type UsersListInput

type UsersListInput struct {
	UserListQuery
}

UsersListInput holds the request parameters for UsersList.

type UsersListOutput

type UsersListOutput struct {
	Body query.PaginatedResponse[db.User]
}

UsersListOutput holds the response body for UsersList.

type UsersMeCoursesInput

type UsersMeCoursesInput struct{}

UsersMeCoursesInput holds the request parameters for UsersMeCourses (none).

type UsersMeCoursesOutput

type UsersMeCoursesOutput struct {
	Body []MyCourseItem
}

UsersMeCoursesOutput holds the response body for UsersMeCourses.

type UsersMeGradesInput

type UsersMeGradesInput struct{}

UsersMeGradesInput holds the request parameters for UsersMeGrades (none).

type UsersMeGradesOutput

type UsersMeGradesOutput struct {
	Body []GradeItem
}

UsersMeGradesOutput holds the response body for UsersMeGrades.

type UsersMeInput

type UsersMeInput struct{}

UsersMeInput holds the request parameters for UsersMe (none).

type UsersMeOutput

type UsersMeOutput struct {
	Body db.User
}

UsersMeOutput holds the response body for UsersMe.

type UsersMeRecentGradesInput

type UsersMeRecentGradesInput struct{}

UsersMeRecentGradesInput holds the request parameters for UsersMeRecentGrades (none).

type UsersMeRecentGradesOutput

type UsersMeRecentGradesOutput struct {
	Body []RecentGradeItem
}

UsersMeRecentGradesOutput holds the response body for UsersMeRecentGrades.

type UsersUpdateInput

type UsersUpdateInput struct {
	ID   string `path:"id"`
	Body UpdateUserBody
}

UsersUpdateInput holds the request parameters for UsersUpdate.

type UsersUpdateOutput

type UsersUpdateOutput struct {
	Body db.User
}

UsersUpdateOutput holds the response body for UsersUpdate.

Directories

Path Synopsis
Package query provides tag-driven request binding, GORM scopes, pagination, and data access helpers for the API layer.
Package query provides tag-driven request binding, GORM scopes, pagination, and data access helpers for the API layer.

Jump to

Keyboard shortcuts

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