Skip to content

Commit 05ac6b8

Browse files
committed
Implement repository layer
1 parent 9c6ee45 commit 05ac6b8

9 files changed

Lines changed: 953 additions & 0 deletions

File tree

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
package postgres
2+
3+
import (
4+
"context"
5+
"database/sql"
6+
"encoding/json"
7+
"fmt"
8+
9+
"CodeSCE/internal/core/models"
10+
"CodeSCE/internal/core/repositories"
11+
)
12+
13+
type answerRepository struct {
14+
db *sql.DB
15+
}
16+
17+
func NewAnswerRepository(db *sql.DB) repositories.AnswerRepository {
18+
return &answerRepository{db: db}
19+
}
20+
21+
func (r *answerRepository) Upsert(ctx context.Context, answer *models.Answer) error {
22+
const query = `
23+
INSERT INTO answers (attempt_id, question_id, response, score_awarded)
24+
VALUES ($1, $2, $3, $4)
25+
ON CONFLICT (attempt_id, question_id) DO UPDATE
26+
SET response = EXCLUDED.response,
27+
score_awarded = EXCLUDED.score_awarded,
28+
updated_at = NOW()
29+
RETURNING id, created_at, updated_at
30+
`
31+
if err := r.db.QueryRowContext(
32+
ctx, query,
33+
answer.AttemptID,
34+
answer.QuestionID,
35+
jsonRawArg(answer.Response),
36+
answer.ScoreAwarded,
37+
).Scan(&answer.ID, &answer.CreatedAt, &answer.UpdatedAt); err != nil {
38+
return fmt.Errorf("upsert answer: %w", err)
39+
}
40+
return nil
41+
}
42+
43+
func (r *answerRepository) GetByAttemptID(ctx context.Context, attemptID int64) ([]models.Answer, error) {
44+
const query = `
45+
SELECT id, attempt_id, question_id, response, score_awarded, created_at, updated_at
46+
FROM answers
47+
WHERE attempt_id = $1
48+
ORDER BY id
49+
`
50+
rows, err := r.db.QueryContext(ctx, query, attemptID)
51+
if err != nil {
52+
return nil, fmt.Errorf("list answers: %w", err)
53+
}
54+
defer rows.Close()
55+
56+
var answers []models.Answer
57+
for rows.Next() {
58+
var (
59+
a models.Answer
60+
response []byte
61+
)
62+
if err := rows.Scan(
63+
&a.ID,
64+
&a.AttemptID,
65+
&a.QuestionID,
66+
&response,
67+
&a.ScoreAwarded,
68+
&a.CreatedAt,
69+
&a.UpdatedAt,
70+
); err != nil {
71+
return nil, fmt.Errorf("scan answer: %w", err)
72+
}
73+
if response != nil {
74+
a.Response = json.RawMessage(response)
75+
}
76+
answers = append(answers, a)
77+
}
78+
if err := rows.Err(); err != nil {
79+
return nil, fmt.Errorf("iterate answers: %w", err)
80+
}
81+
return answers, nil
82+
}
83+
84+
func (r *answerRepository) SetScore(ctx context.Context, answerID int64, score int) error {
85+
const query = `
86+
UPDATE answers
87+
SET score_awarded = $2, updated_at = NOW()
88+
WHERE id = $1
89+
`
90+
res, err := r.db.ExecContext(ctx, query, answerID, score)
91+
if err != nil {
92+
return fmt.Errorf("set answer score: %w", err)
93+
}
94+
rowsAffected, err := res.RowsAffected()
95+
if err != nil {
96+
return fmt.Errorf("set answer score rows affected: %w", err)
97+
}
98+
if rowsAffected == 0 {
99+
return repositories.ErrNotFound
100+
}
101+
return nil
102+
}
103+
104+
func jsonRawArg(raw json.RawMessage) any {
105+
if len(raw) == 0 {
106+
return nil
107+
}
108+
return []byte(raw)
109+
}
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
package postgres
2+
3+
import (
4+
"context"
5+
"database/sql"
6+
"errors"
7+
"fmt"
8+
9+
"CodeSCE/internal/core/models"
10+
"CodeSCE/internal/core/repositories"
11+
)
12+
13+
type assessmentRepository struct {
14+
db *sql.DB
15+
}
16+
17+
func NewAssessmentRepository(db *sql.DB) repositories.AssessmentRepository {
18+
return &assessmentRepository{db: db}
19+
}
20+
21+
func (r *assessmentRepository) Create(ctx context.Context, a *models.AssessmentTemplate) error {
22+
const query = `
23+
INSERT INTO assessment_templates (title, description, duration_minutes)
24+
VALUES ($1, $2, $3)
25+
RETURNING id, created_at, updated_at
26+
`
27+
if err := r.db.QueryRowContext(ctx, query, a.Title, a.Description, a.DurationMinutes).
28+
Scan(&a.ID, &a.CreatedAt, &a.UpdatedAt); err != nil {
29+
return fmt.Errorf("create assessment template: %w", err)
30+
}
31+
return nil
32+
}
33+
34+
func (r *assessmentRepository) List(ctx context.Context) ([]models.AssessmentTemplate, error) {
35+
const query = `
36+
SELECT id, title, description, duration_minutes, created_at, updated_at
37+
FROM assessment_templates
38+
ORDER BY id
39+
`
40+
rows, err := r.db.QueryContext(ctx, query)
41+
if err != nil {
42+
return nil, fmt.Errorf("list assessment templates: %w", err)
43+
}
44+
defer rows.Close()
45+
46+
var templates []models.AssessmentTemplate
47+
for rows.Next() {
48+
var t models.AssessmentTemplate
49+
if err := rows.Scan(
50+
&t.ID,
51+
&t.Title,
52+
&t.Description,
53+
&t.DurationMinutes,
54+
&t.CreatedAt,
55+
&t.UpdatedAt,
56+
); err != nil {
57+
return nil, fmt.Errorf("scan assessment template: %w", err)
58+
}
59+
templates = append(templates, t)
60+
}
61+
if err := rows.Err(); err != nil {
62+
return nil, fmt.Errorf("iterate assessment templates: %w", err)
63+
}
64+
return templates, nil
65+
}
66+
67+
func (r *assessmentRepository) GetByID(ctx context.Context, id int64) (*models.AssessmentTemplate, error) {
68+
const query = `
69+
SELECT id, title, description, duration_minutes, created_at, updated_at
70+
FROM assessment_templates
71+
WHERE id = $1
72+
`
73+
var t models.AssessmentTemplate
74+
err := r.db.QueryRowContext(ctx, query, id).Scan(
75+
&t.ID,
76+
&t.Title,
77+
&t.Description,
78+
&t.DurationMinutes,
79+
&t.CreatedAt,
80+
&t.UpdatedAt,
81+
)
82+
if errors.Is(err, sql.ErrNoRows) {
83+
return nil, repositories.ErrNotFound
84+
}
85+
if err != nil {
86+
return nil, fmt.Errorf("get assessment template by id: %w", err)
87+
}
88+
return &t, nil
89+
}
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
package postgres
2+
3+
import (
4+
"context"
5+
"database/sql"
6+
"errors"
7+
"fmt"
8+
"time"
9+
10+
"CodeSCE/internal/core/models"
11+
"CodeSCE/internal/core/repositories"
12+
)
13+
14+
type attemptRepository struct {
15+
db *sql.DB
16+
}
17+
18+
func NewAttemptRepository(db *sql.DB) repositories.AttemptRepository {
19+
return &attemptRepository{db: db}
20+
}
21+
22+
func (r *attemptRepository) Create(ctx context.Context, attempt *models.AssessmentAttempt) error {
23+
const query = `
24+
INSERT INTO assessment_attempts (
25+
invite_id, started_at, completed_at, status, total_score
26+
)
27+
VALUES ($1, $2, $3, COALESCE(NULLIF($4, ''), 'in_progress'), $5)
28+
RETURNING id, status, created_at, updated_at
29+
`
30+
if err := r.db.QueryRowContext(
31+
ctx, query,
32+
attempt.InviteID,
33+
attempt.StartedAt,
34+
attempt.CompletedAt,
35+
attempt.Status,
36+
attempt.TotalScore,
37+
).Scan(&attempt.ID, &attempt.Status, &attempt.CreatedAt, &attempt.UpdatedAt); err != nil {
38+
return fmt.Errorf("create assessment attempt: %w", err)
39+
}
40+
return nil
41+
}
42+
43+
func (r *attemptRepository) GetByID(ctx context.Context, id int64) (*models.AssessmentAttempt, error) {
44+
const query = `
45+
SELECT id, invite_id, started_at, completed_at, status, total_score, created_at, updated_at
46+
FROM assessment_attempts
47+
WHERE id = $1
48+
`
49+
var attempt models.AssessmentAttempt
50+
err := r.db.QueryRowContext(ctx, query, id).Scan(
51+
&attempt.ID,
52+
&attempt.InviteID,
53+
&attempt.StartedAt,
54+
&attempt.CompletedAt,
55+
&attempt.Status,
56+
&attempt.TotalScore,
57+
&attempt.CreatedAt,
58+
&attempt.UpdatedAt,
59+
)
60+
if errors.Is(err, sql.ErrNoRows) {
61+
return nil, repositories.ErrNotFound
62+
}
63+
if err != nil {
64+
return nil, fmt.Errorf("get assessment attempt by id: %w", err)
65+
}
66+
return &attempt, nil
67+
}
68+
69+
func (r *attemptRepository) ListByAssessmentID(ctx context.Context, assessmentID int64) ([]models.AssessmentAttempt, error) {
70+
// Attempts link to assessments via assessment_invites.assessment_template_id.
71+
const query = `
72+
SELECT a.id, a.invite_id, a.started_at, a.completed_at, a.status,
73+
a.total_score, a.created_at, a.updated_at
74+
FROM assessment_attempts a
75+
JOIN assessment_invites i ON i.id = a.invite_id
76+
WHERE i.assessment_template_id = $1
77+
ORDER BY a.id
78+
`
79+
rows, err := r.db.QueryContext(ctx, query, assessmentID)
80+
if err != nil {
81+
return nil, fmt.Errorf("list assessment attempts: %w", err)
82+
}
83+
defer rows.Close()
84+
85+
var attempts []models.AssessmentAttempt
86+
for rows.Next() {
87+
var attempt models.AssessmentAttempt
88+
if err := rows.Scan(
89+
&attempt.ID,
90+
&attempt.InviteID,
91+
&attempt.StartedAt,
92+
&attempt.CompletedAt,
93+
&attempt.Status,
94+
&attempt.TotalScore,
95+
&attempt.CreatedAt,
96+
&attempt.UpdatedAt,
97+
); err != nil {
98+
return nil, fmt.Errorf("scan assessment attempt: %w", err)
99+
}
100+
attempts = append(attempts, attempt)
101+
}
102+
if err := rows.Err(); err != nil {
103+
return nil, fmt.Errorf("iterate assessment attempts: %w", err)
104+
}
105+
return attempts, nil
106+
}
107+
108+
func (r *attemptRepository) UpdateStatus(ctx context.Context, id int64, status string) error {
109+
const query = `
110+
UPDATE assessment_attempts
111+
SET status = $2, updated_at = NOW()
112+
WHERE id = $1
113+
`
114+
return r.execAffectingOne(ctx, query, "update assessment attempt status", id, status)
115+
}
116+
117+
func (r *attemptRepository) SetScore(ctx context.Context, id int64, score int) error {
118+
const query = `
119+
UPDATE assessment_attempts
120+
SET total_score = $2, updated_at = NOW()
121+
WHERE id = $1
122+
`
123+
return r.execAffectingOne(ctx, query, "set assessment attempt score", id, score)
124+
}
125+
126+
func (r *attemptRepository) SetCompletedAt(ctx context.Context, id int64, completedAt time.Time) error {
127+
const query = `
128+
UPDATE assessment_attempts
129+
SET completed_at = $2, updated_at = NOW()
130+
WHERE id = $1
131+
`
132+
return r.execAffectingOne(ctx, query, "set assessment attempt completed_at", id, completedAt)
133+
}
134+
135+
func (r *attemptRepository) execAffectingOne(ctx context.Context, query, op string, args ...any) error {
136+
res, err := r.db.ExecContext(ctx, query, args...)
137+
if err != nil {
138+
return fmt.Errorf("%s: %w", op, err)
139+
}
140+
rowsAffected, err := res.RowsAffected()
141+
if err != nil {
142+
return fmt.Errorf("%s rows affected: %w", op, err)
143+
}
144+
if rowsAffected == 0 {
145+
return repositories.ErrNotFound
146+
}
147+
return nil
148+
}

0 commit comments

Comments
 (0)