Skip to main content

dashboard

import "github.com/Ask-Atlas/AskAtlas/api/internal/dashboard"

Package dashboard implements the GET /api/me/dashboard service surface (ASK-155): the aggregated home-page payload combining enrolled courses for the current term, recently updated study guides, practice stats + recent sessions, and file totals + recent files.

The endpoint is deliberately a single round-trip with ~10 underlying queries because the home page is the most-loaded authenticated surface and we want one network hop to render it. Each section is independent; a DB error in any one section fails the whole request (per spec) rather than returning partial data that the frontend would have to render around.

Soft-deleted entities are excluded everywhere -- counts, lists, and aggregate sums all filter on the same predicates the rest of the API uses (study_guides.deleted_at IS NULL, files.deletion_status IS NULL).

Index

Constants

const (
// RecentGuidesLimit caps the number of guides returned in the
// study_guides.recent array. Matches the spec's "Hardcoded
// limits" section.
RecentGuidesLimit int32 = 5
// RecentSessionsLimit caps the number of sessions returned in
// the practice.recent_sessions array.
RecentSessionsLimit int32 = 5
// RecentFilesLimit caps the number of files returned in the
// files.recent array.
RecentFilesLimit int32 = 5
// MaxCourses caps the number of courses returned in the
// courses.courses array. The dashboard is for the *current
// term* and most users have <10 courses per term, so the cap
// is a safety floor rather than a real product limit.
MaxCourses int32 = 10
)

type DashboardCourseSummary

DashboardCourseSummary mirrors api.DashboardCourseSummary on the wire. Includes the viewer's role + the section's term so the home page can render "CPTS 322 (Spring 2026, student)" without follow-up requests.

type DashboardCourseSummary struct {
ID uuid.UUID
Department string
Number string
Title string
Role MemberRole
SectionTerm string
}

type DashboardCoursesSection

DashboardCoursesSection is the courses block of the dashboard payload. CurrentTerm is *string to encode "no enrollments at all" as JSON null; CurrentTerm being non-nil but EnrolledCount==0 is not a valid state (the term resolver only returns a term when the user has at least one enrollment).

type DashboardCoursesSection struct {
EnrolledCount int32
CurrentTerm *string
Courses []DashboardCourseSummary
}

type DashboardData

DashboardData is the assembled payload returned by Service.GetDashboard. All four sections are always present (never nil); their inner list fields default to empty slices, count fields to 0.

type DashboardData struct {
Courses DashboardCoursesSection
StudyGuides DashboardStudyGuidesSection
Practice DashboardPracticeSection
Files DashboardFilesSection
}

type DashboardFileSummary

DashboardFileSummary mirrors api.DashboardFileSummary on the wire.

type DashboardFileSummary struct {
ID uuid.UUID
Name string
MimeType string
UpdatedAt time.Time
}

type DashboardFilesSection

DashboardFilesSection is the files block of the dashboard payload. TotalSize is bytes (int64) because file sizes can exceed int32 limits in aggregate.

type DashboardFilesSection struct {
TotalCount int32
TotalSize int64
Recent []DashboardFileSummary
}

type DashboardPracticeSection

DashboardPracticeSection is the practice block of the dashboard payload. All three integer fields are 0 when the user has no completed sessions; RecentSessions is an empty slice in that case (never nil).

type DashboardPracticeSection struct {
SessionsCompleted int32
TotalQuestionsAnswered int32
OverallAccuracy int32
RecentSessions []DashboardSessionSummary
}

type DashboardSessionSummary

DashboardSessionSummary mirrors api.DashboardSessionSummary on the wire. ScorePercentage is the rounded per-session accuracy (correct_answers / total_questions * 100) computed in Go so the SQL stays simple; if total_questions is 0 the percentage is 0 rather than divide-by-zero (mirroring the overall_accuracy behavior).

type DashboardSessionSummary struct {
ID uuid.UUID
QuizTitle string
StudyGuideTitle string
ScorePercentage int32
CompletedAt time.Time
}

type DashboardStudyGuideSummary

DashboardStudyGuideSummary mirrors api.DashboardStudyGuideSummary on the wire.

type DashboardStudyGuideSummary struct {
ID uuid.UUID
Title string
CourseDepartment string
CourseNumber string
UpdatedAt time.Time
}

type DashboardStudyGuidesSection

DashboardStudyGuidesSection is the study_guides block of the dashboard payload.

type DashboardStudyGuidesSection struct {
CreatedCount int32
Recent []DashboardStudyGuideSummary
}

type GetDashboardParams

GetDashboardParams is the input to Service.GetDashboard. ViewerID is resolved from the Clerk JWT in the handler before this struct is constructed; the service never sees the JWT directly. No filters or pagination on this endpoint -- the home page wants the same shape every render.

type GetDashboardParams struct {
ViewerID uuid.UUID
}

type MemberRole

MemberRole names the values stored in the course_role enum and surfaced via the dashboard's per-course summary. The string values match the openapi enum exactly so the mapper can cast directly without a switch.

type MemberRole string
const (
MemberRoleStudent MemberRole = "student"
MemberRoleTA MemberRole = "ta"
MemberRoleInstructor MemberRole = "instructor"
)

type Repository

Repository is the data-access surface required by the dashboard service. The 3 term-resolver methods propagate sql.ErrNoRows directly (without mapping to apperrors.ErrNotFound) because the service treats "no row" as a control-flow signal -- step 1 falls through to step 2, etc. -- not as an end-user error.

type Repository interface {
// Term-resolution waterfall.
ResolveCurrentTermActive(ctx context.Context, viewerID pgtype.UUID) (string, error)
ResolveCurrentTermLastEnded(ctx context.Context, viewerID pgtype.UUID) (string, error)
ResolveCurrentTermLexLatest(ctx context.Context, viewerID pgtype.UUID) (string, error)

// Courses section.
ListEnrolledCoursesForTerm(ctx context.Context, arg db.ListEnrolledCoursesForTermParams) ([]db.ListEnrolledCoursesForTermRow, error)

// Study-guides section.
CountUserStudyGuides(ctx context.Context, viewerID pgtype.UUID) (int64, error)
ListRecentUserStudyGuides(ctx context.Context, arg db.ListRecentUserStudyGuidesParams) ([]db.ListRecentUserStudyGuidesRow, error)

// Practice section.
GetUserPracticeStats(ctx context.Context, viewerID pgtype.UUID) (db.GetUserPracticeStatsRow, error)
CountUserAnsweredQuestions(ctx context.Context, viewerID pgtype.UUID) (int64, error)
ListRecentUserSessions(ctx context.Context, arg db.ListRecentUserSessionsParams) ([]db.ListRecentUserSessionsRow, error)

// Files section.
GetUserFileStats(ctx context.Context, viewerID pgtype.UUID) (db.GetUserFileStatsRow, error)
ListRecentUserFiles(ctx context.Context, arg db.ListRecentUserFilesParams) ([]db.ListRecentUserFilesRow, error)
}

func NewSQLCRepository

func NewSQLCRepository(queries *db.Queries) Repository

NewSQLCRepository returns a Repository backed by sqlc-generated Postgres queries.

type Service

Service implements the GET /api/me/dashboard business logic.

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

func NewService

func NewService(repo Repository) *Service

NewService wires a dashboard Service over the given repository.

func (*Service) GetDashboard

func (s *Service) GetDashboard(ctx context.Context, p GetDashboardParams) (DashboardData, error)

GetDashboard assembles the full dashboard payload for the viewer (ASK-155).

Strategy:

  • Resolve the current term via the 3-step waterfall (active -> last-ended -> lex-latest). When the user has no enrollments at all every step returns sql.ErrNoRows; the resolved term is nil and the courses section ships as {enrolled_count: 0, current_term: null, courses: []}.
  • For each of the 4 sections fan out to per-section queries and assemble in-process. Sequential rather than goroutine fan-out: each query is sub-2ms on indexed reads at MVP scale, and the connection-pool churn from 10 concurrent queries would outweigh the parallelism win for what is already a single-client request.
  • Any per-query error fails the entire request (per spec -- never return partial data). Wrapped with section context so a 500 logs identify the failing section.

Generated by gomarkdoc