Skip to content

Commit 83d3607

Browse files
committed
Fix Quiz etc via challenge
1 parent 8e94cd9 commit 83d3607

File tree

10 files changed

+269
-97
lines changed

10 files changed

+269
-97
lines changed

backend/internal/cache/keys.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,11 @@ func QuizKey(quizID string) string {
233233
return PrefixQuiz + quizID
234234
}
235235

236+
// QuizByChallengeKey builds a cache key for a quiz by challenge ID
237+
func QuizByChallengeKey(challengeID string) string {
238+
return fmt.Sprintf("%schallenge:%s", PrefixQuiz, challengeID)
239+
}
240+
236241
// QuizzesByProjectKey builds a cache key for quizzes in a project
237242
func QuizzesByProjectKey(projectID string) string {
238243
return fmt.Sprintf("%s:project:%s", PrefixQuiz, projectID)

backend/internal/database/queries/quizzes.sql

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,11 @@ WHERE project_id = ANY(@project_ids::text[])
1717
ORDER BY project_id, published_at DESC;
1818

1919
-- name: GetQuizzesByChallengeIDs :many
20+
-- Batch version of GetQuizByChallengeID for dataloader
2021
SELECT id, project_id, challenge_id, name, description, image_url, timeout_seconds, randomize_questions, reveal_correct_answers, allow_retakes, completion_points, published_at, end_time, created_at, updated_at
2122
FROM quizzes
2223
WHERE challenge_id = ANY(@challenge_ids::text[])
23-
AND published_at IS NOT NULL
24-
AND published_at <= NOW()
25-
ORDER BY challenge_id, published_at DESC;
24+
ORDER BY challenge_id;
2625

2726
-- name: GetQuizzesFilteredCursor :many
2827
SELECT id, project_id, challenge_id, name, description, image_url, timeout_seconds, randomize_questions, reveal_correct_answers, allow_retakes, completion_points, published_at, end_time, created_at, updated_at

backend/internal/database/sqlc/quizzes.sql.go

Lines changed: 2 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

backend/internal/graph/api/challenges.resolvers.go

Lines changed: 7 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

backend/internal/graph/api/quizzes.resolvers.go

Lines changed: 2 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

backend/internal/graph/api/resolver.go

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@ package api
33
import (
44
"context"
55
"fmt"
6+
"time"
67

78
"github.com/bcc-media/wayfarer/internal/cache"
89
"github.com/bcc-media/wayfarer/internal/database"
10+
"github.com/bcc-media/wayfarer/internal/graph/api/model"
911
"github.com/bcc-media/wayfarer/internal/graph/scalars"
1012
"github.com/bcc-media/wayfarer/internal/loaders"
1113
"github.com/bcc-media/wayfarer/internal/middleware"
@@ -61,3 +63,54 @@ func (r *Resolver) getUserChallengeEnrolledAt(ctx context.Context, challengeID s
6163
}
6264
return &scalars.DateTime{Time: *ts}, nil
6365
}
66+
67+
// LoadQuizWithVisibility loads a quiz and enforces visibility rules for non-admins.
68+
// Admins can see all quizzes, non-admins can only see published quizzes.
69+
func (r *Resolver) LoadQuizWithVisibility(ctx context.Context, quizID string) (*model.Quiz, error) {
70+
thunk := r.Loaders.QuizByIDLoader.Load(ctx, quizID)
71+
quiz, err := thunk()
72+
if err != nil {
73+
return nil, err
74+
}
75+
if quiz == nil {
76+
return nil, fmt.Errorf("quiz not found")
77+
}
78+
79+
// Admins can see all quizzes
80+
userID, _ := middleware.GetUserID(ctx)
81+
if userID != "" && r.RoleService.CanManageProject(ctx, userID, quiz.ProjectID) {
82+
return quiz, nil
83+
}
84+
85+
// Non-admins can only see published quizzes
86+
if quiz.PublishedAt == nil || quiz.PublishedAt.Time.After(time.Now()) {
87+
return nil, fmt.Errorf("quiz not found")
88+
}
89+
90+
return quiz, nil
91+
}
92+
93+
// LoadChallengeWithVisibility loads a challenge and enforces visibility rules for non-admins.
94+
// Admins can see all challenges, non-admins can only see published challenges.
95+
func (r *Resolver) LoadChallengeWithVisibility(ctx context.Context, challengeID string) (model.Challenge, error) {
96+
thunk := r.Loaders.ChallengeByIDLoader.Load(ctx, challengeID)
97+
challenge, err := thunk()
98+
if err != nil {
99+
return nil, err
100+
}
101+
102+
// Admins can see all challenges
103+
userID, _ := middleware.GetUserID(ctx)
104+
projectID := getChallengeProjectID(challenge)
105+
if userID != "" && r.RoleService.CanManageProject(ctx, userID, projectID) {
106+
return challenge, nil
107+
}
108+
109+
// Non-admins can only see published challenges
110+
publishedAt := getChallengePublishedAt(challenge)
111+
if publishedAt == nil || publishedAt.Time.After(time.Now()) {
112+
return nil, fmt.Errorf("challenge not found")
113+
}
114+
115+
return challenge, nil
116+
}

backend/internal/graph/api/shared.resolvers.go

Lines changed: 24 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)