feat: quiz and rbac

This commit is contained in:
Krispire Admin
2026-10-15 19:43:17 +05:30
parent 7d7ba2a002
commit 4b38047c26
26 changed files with 3270 additions and 75 deletions
+19 -17
View File
@@ -17,8 +17,10 @@ type AdminUser struct {
Plan string `json:"plan"`
EmailVerifiedAt *time.Time `json:"email_verified_at"`
BannedAt *time.Time `json:"banned_at"`
PostCount int `json:"post_count"`
CreatedAt time.Time `json:"created_at"`
// AdminRole is the staff role key, nil for ordinary accounts.
AdminRole *string `json:"admin_role,omitempty"`
PostCount int `json:"post_count"`
CreatedAt time.Time `json:"created_at"`
// Connected auth methods. HasPassword means email+password login is set up;
// Providers lists linked OAuth providers (e.g. "google", "facebook").
@@ -56,18 +58,18 @@ type AdminDisciple struct {
// AdminPost is a row in the admin posts table.
type AdminPost struct {
ID string `json:"id"`
UserID string `json:"user_id"`
Caption string `json:"caption"`
SourceTitle string `json:"source_title"`
ID string `json:"id"`
UserID string `json:"user_id"`
Caption string `json:"caption"`
SourceTitle string `json:"source_title"`
SourceURL string `json:"source_url"`
IsHidden bool `json:"is_hidden"`
DeletedAt *time.Time `json:"deleted_at"`
CreatedAt time.Time `json:"created_at"`
AuthorName string `json:"author_name"`
AuthorHandle string `json:"author_handle"`
LikesCount int `json:"likes_count"`
CommentsCount int `json:"comments_count"`
AuthorName string `json:"author_name"`
AuthorHandle string `json:"author_handle"`
LikesCount int `json:"likes_count"`
CommentsCount int `json:"comments_count"`
}
// AdminCollection is a row in the admin collections table.
@@ -85,18 +87,18 @@ type AdminCollection struct {
// AdminFeedback is a row in the admin feedback table (includes the reply).
type AdminFeedback struct {
ID string `json:"id"`
Category string `json:"category"`
Message string `json:"message"`
ID string `json:"id"`
Category string `json:"category"`
Message string `json:"message"`
ContactEmail *string `json:"contact_email"`
CreatedAt time.Time `json:"created_at"`
AcknowledgedAt *time.Time `json:"acknowledged_at"`
ResolvedAt *time.Time `json:"resolved_at"`
RepliedAt *time.Time `json:"replied_at"`
ReplyMessage *string `json:"reply_message"`
AuthorHandle *string `json:"author_handle"`
AuthorName *string `json:"author_name"`
AuthorEmail *string `json:"author_email"`
ReplyMessage *string `json:"reply_message"`
AuthorHandle *string `json:"author_handle"`
AuthorName *string `json:"author_name"`
AuthorEmail *string `json:"author_email"`
}
// AdminReport is a unified projection over the three report tables
+135
View File
@@ -0,0 +1,135 @@
package model
import "time"
// Quiz target kinds.
const (
QuizChapter = "chapter"
QuizLecture = "lecture"
QuizConversation = "conversation"
QuizStandalone = "standalone"
)
// Attempt states, as seen by the person taking the quiz.
const (
QuizNotStarted = "not_started"
QuizInProgress = "in_progress"
QuizCompleted = "completed"
)
// Quiz is a set of multiple-choice questions, optionally tied to a page.
type Quiz struct {
ID string `json:"id"`
Title string `json:"title"`
Description string `json:"description"`
TargetKind string `json:"target_kind"`
TargetPath *string `json:"target_path,omitempty"`
TargetTitle string `json:"target_title"`
PublishedAt *time.Time `json:"published_at,omitempty"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
QuestionCount int `json:"question_count"`
// Admin-only counters.
AttemptCount int `json:"attempt_count,omitempty"`
CompletedCount int `json:"completed_count,omitempty"`
Questions []QuizQuestion `json:"questions,omitempty"`
}
// QuizQuestion is one question. Prompt and option bodies carry the same
// formatting markers as the rest of the admin text.
type QuizQuestion struct {
ID string `json:"id"`
Ord int `json:"ord"`
Prompt string `json:"prompt"`
Explanation string `json:"explanation,omitempty"`
// Multi questions ask for every correct option ("select all that apply").
Multi bool `json:"multi"`
Options []QuizOption `json:"options"`
}
// QuizOption is one choice. IsCorrect is nil whenever the reader must not
// know it yet — every response to someone mid-attempt.
type QuizOption struct {
ID string `json:"id"`
Ord int `json:"ord"`
Body string `json:"body"`
IsCorrect *bool `json:"is_correct,omitempty"`
}
// QuizStatus is the caller's progress on a quiz.
type QuizStatus struct {
State string `json:"state"`
Answered int `json:"answered"`
Total int `json:"total"`
Score *int `json:"score,omitempty"`
}
// QuizCard is a quiz plus the caller's progress, for buttons and lists.
type QuizCard struct {
Quiz
My QuizStatus `json:"my"`
}
// QuizPlay is the next step of an attempt: the current question, or Done.
type QuizPlay struct {
QuizID string `json:"quiz_id"`
Title string `json:"title"`
Answered int `json:"answered"`
Total int `json:"total"`
Done bool `json:"done"`
Question *QuizQuestion `json:"question,omitempty"`
}
// QuizResultItem is one question on a results screen.
type QuizResultItem struct {
QuizQuestion
ChosenOptionIDs []string `json:"chosen_option_ids"`
IsCorrect bool `json:"is_correct"`
ResponseMs *int `json:"response_ms,omitempty"`
}
// QuizResult is a finished attempt.
type QuizResult struct {
Quiz Quiz `json:"quiz"`
Score int `json:"score"`
Total int `json:"total"`
StartedAt time.Time `json:"started_at"`
CompletedAt *time.Time `json:"completed_at,omitempty"`
TotalMs int `json:"total_ms"`
Items []QuizResultItem `json:"items"`
}
// QuizParticipant is one person's attempt, for the admin participants list.
type QuizParticipant struct {
UserID string `json:"user_id"`
Handle string `json:"handle"`
Name string `json:"name"`
AvatarURL *string `json:"avatar_url,omitempty"`
StartedAt time.Time `json:"started_at"`
CompletedAt *time.Time `json:"completed_at,omitempty"`
Answered int `json:"answered"`
Score int `json:"score"`
Total int `json:"total"`
TotalMs int `json:"total_ms"`
}
// QuizOptionStat is how often one option was chosen.
type QuizOptionStat struct {
QuizOption
Picks int `json:"picks"`
}
// QuizQuestionStat aggregates every response to one question.
type QuizQuestionStat struct {
ID string `json:"id"`
Ord int `json:"ord"`
Prompt string `json:"prompt"`
Multi bool `json:"multi"`
Answered int `json:"answered"`
Correct int `json:"correct"`
AvgMs int `json:"avg_ms"`
MedianMs int `json:"median_ms"`
Options []QuizOptionStat `json:"options"`
}
+37
View File
@@ -0,0 +1,37 @@
package model
import "time"
// AdminRole is a named set of admin-panel permissions.
type AdminRole struct {
ID string `json:"id"`
Key string `json:"key"`
Name string `json:"name"`
Description string `json:"description"`
IsSystem bool `json:"is_system"`
Permissions []string `json:"permissions"`
// MemberCount is how many staff hold the role, for the roles screen.
MemberCount int `json:"member_count"`
CreatedAt time.Time `json:"created_at"`
}
// StaffMember is one account with admin-panel access.
type StaffMember struct {
UserID string `json:"user_id"`
Handle string `json:"handle"`
Name string `json:"name"`
Email *string `json:"email,omitempty"`
AvatarURL *string `json:"avatar_url,omitempty"`
RoleID string `json:"role_id"`
RoleKey string `json:"role_key"`
RoleName string `json:"role_name"`
BlockedAt *time.Time `json:"blocked_at,omitempty"`
}
// StaffAccess is what the admin middleware needs about the caller.
type StaffAccess struct {
RoleKey string
RoleName string
Permissions []string
Blocked bool
}