Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
109 changes: 109 additions & 0 deletions internal/core/repositories/postgres/answer_repo.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
package postgres

import (
"context"
"database/sql"
"encoding/json"
"fmt"

"CodeSCE/internal/core/models"
"CodeSCE/internal/core/repositories"
)

type answerRepository struct {
db *sql.DB
}

func NewAnswerRepository(db *sql.DB) repositories.AnswerRepository {
return &answerRepository{db: db}
}

func (r *answerRepository) Upsert(ctx context.Context, answer *models.Answer) error {
const query = `
INSERT INTO answers (attempt_id, question_id, response, score_awarded)
VALUES ($1, $2, $3, $4)
ON CONFLICT (attempt_id, question_id) DO UPDATE
SET response = EXCLUDED.response,
score_awarded = EXCLUDED.score_awarded,
updated_at = NOW()
RETURNING id, created_at, updated_at
`
if err := r.db.QueryRowContext(
ctx, query,
answer.AttemptID,
answer.QuestionID,
jsonRawArg(answer.Response),
answer.ScoreAwarded,
).Scan(&answer.ID, &answer.CreatedAt, &answer.UpdatedAt); err != nil {
return fmt.Errorf("upsert answer: %w", err)
}
return nil
}

func (r *answerRepository) GetByAttemptID(ctx context.Context, attemptID int64) ([]models.Answer, error) {
const query = `
SELECT id, attempt_id, question_id, response, score_awarded, created_at, updated_at
FROM answers
WHERE attempt_id = $1
ORDER BY id
`
rows, err := r.db.QueryContext(ctx, query, attemptID)
if err != nil {
return nil, fmt.Errorf("list answers: %w", err)
}
defer rows.Close()

var answers []models.Answer
for rows.Next() {
var (
a models.Answer
response []byte
)
if err := rows.Scan(
&a.ID,
&a.AttemptID,
&a.QuestionID,
&response,
&a.ScoreAwarded,
&a.CreatedAt,
&a.UpdatedAt,
); err != nil {
return nil, fmt.Errorf("scan answer: %w", err)
}
if response != nil {
a.Response = json.RawMessage(response)
}
answers = append(answers, a)
}
if err := rows.Err(); err != nil {
return nil, fmt.Errorf("iterate answers: %w", err)
}
return answers, nil
}

func (r *answerRepository) SetScore(ctx context.Context, answerID int64, score int) error {
const query = `
UPDATE answers
SET score_awarded = $2, updated_at = NOW()
WHERE id = $1
`
res, err := r.db.ExecContext(ctx, query, answerID, score)
if err != nil {
return fmt.Errorf("set answer score: %w", err)
}
rowsAffected, err := res.RowsAffected()
if err != nil {
return fmt.Errorf("set answer score rows affected: %w", err)
}
if rowsAffected == 0 {
return repositories.ErrNotFound
}
return nil
}

func jsonRawArg(raw json.RawMessage) any {
if len(raw) == 0 {
return nil
}
return []byte(raw)
}
89 changes: 89 additions & 0 deletions internal/core/repositories/postgres/assessment_repo.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package postgres

import (
"context"
"database/sql"
"errors"
"fmt"

"CodeSCE/internal/core/models"
"CodeSCE/internal/core/repositories"
)

type assessmentRepository struct {
db *sql.DB
}

func NewAssessmentRepository(db *sql.DB) repositories.AssessmentRepository {
return &assessmentRepository{db: db}
}

func (r *assessmentRepository) Create(ctx context.Context, a *models.AssessmentTemplate) error {
const query = `
INSERT INTO assessment_templates (title, description, duration_minutes)
VALUES ($1, $2, $3)
RETURNING id, created_at, updated_at
`
if err := r.db.QueryRowContext(ctx, query, a.Title, a.Description, a.DurationMinutes).
Scan(&a.ID, &a.CreatedAt, &a.UpdatedAt); err != nil {
return fmt.Errorf("create assessment template: %w", err)
}
return nil
}

func (r *assessmentRepository) List(ctx context.Context) ([]models.AssessmentTemplate, error) {
const query = `
SELECT id, title, description, duration_minutes, created_at, updated_at
FROM assessment_templates
ORDER BY id
`
rows, err := r.db.QueryContext(ctx, query)
if err != nil {
return nil, fmt.Errorf("list assessment templates: %w", err)
}
defer rows.Close()

var templates []models.AssessmentTemplate
for rows.Next() {
var t models.AssessmentTemplate
if err := rows.Scan(
&t.ID,
&t.Title,
&t.Description,
&t.DurationMinutes,
&t.CreatedAt,
&t.UpdatedAt,
); err != nil {
return nil, fmt.Errorf("scan assessment template: %w", err)
}
templates = append(templates, t)
}
if err := rows.Err(); err != nil {
return nil, fmt.Errorf("iterate assessment templates: %w", err)
}
return templates, nil
}

func (r *assessmentRepository) GetByID(ctx context.Context, id int64) (*models.AssessmentTemplate, error) {
const query = `
SELECT id, title, description, duration_minutes, created_at, updated_at
FROM assessment_templates
WHERE id = $1
`
var t models.AssessmentTemplate
err := r.db.QueryRowContext(ctx, query, id).Scan(
&t.ID,
&t.Title,
&t.Description,
&t.DurationMinutes,
&t.CreatedAt,
&t.UpdatedAt,
)
if errors.Is(err, sql.ErrNoRows) {
return nil, repositories.ErrNotFound
}
if err != nil {
return nil, fmt.Errorf("get assessment template by id: %w", err)
}
return &t, nil
}
148 changes: 148 additions & 0 deletions internal/core/repositories/postgres/attempt_repo.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
package postgres

import (
"context"
"database/sql"
"errors"
"fmt"
"time"

"CodeSCE/internal/core/models"
"CodeSCE/internal/core/repositories"
)

type attemptRepository struct {
db *sql.DB
}

func NewAttemptRepository(db *sql.DB) repositories.AttemptRepository {
return &attemptRepository{db: db}
}

func (r *attemptRepository) Create(ctx context.Context, attempt *models.AssessmentAttempt) error {
const query = `
INSERT INTO assessment_attempts (
invite_id, started_at, completed_at, status, total_score
)
VALUES ($1, $2, $3, COALESCE(NULLIF($4, ''), 'in_progress'), $5)
RETURNING id, status, created_at, updated_at
`
if err := r.db.QueryRowContext(
ctx, query,
attempt.InviteID,
attempt.StartedAt,
attempt.CompletedAt,
attempt.Status,
attempt.TotalScore,
).Scan(&attempt.ID, &attempt.Status, &attempt.CreatedAt, &attempt.UpdatedAt); err != nil {
return fmt.Errorf("create assessment attempt: %w", err)
}
return nil
}

func (r *attemptRepository) GetByID(ctx context.Context, id int64) (*models.AssessmentAttempt, error) {
const query = `
SELECT id, invite_id, started_at, completed_at, status, total_score, created_at, updated_at
FROM assessment_attempts
WHERE id = $1
`
var attempt models.AssessmentAttempt
err := r.db.QueryRowContext(ctx, query, id).Scan(
&attempt.ID,
&attempt.InviteID,
&attempt.StartedAt,
&attempt.CompletedAt,
&attempt.Status,
&attempt.TotalScore,
&attempt.CreatedAt,
&attempt.UpdatedAt,
)
if errors.Is(err, sql.ErrNoRows) {
return nil, repositories.ErrNotFound
}
if err != nil {
return nil, fmt.Errorf("get assessment attempt by id: %w", err)
}
return &attempt, nil
}

func (r *attemptRepository) ListByAssessmentID(ctx context.Context, assessmentID int64) ([]models.AssessmentAttempt, error) {
// Attempts link to assessments via assessment_invites.assessment_template_id.
const query = `
SELECT a.id, a.invite_id, a.started_at, a.completed_at, a.status,
a.total_score, a.created_at, a.updated_at
FROM assessment_attempts a
JOIN assessment_invites i ON i.id = a.invite_id
WHERE i.assessment_template_id = $1
ORDER BY a.id
`
rows, err := r.db.QueryContext(ctx, query, assessmentID)
if err != nil {
return nil, fmt.Errorf("list assessment attempts: %w", err)
}
defer rows.Close()

var attempts []models.AssessmentAttempt
for rows.Next() {
var attempt models.AssessmentAttempt
if err := rows.Scan(
&attempt.ID,
&attempt.InviteID,
&attempt.StartedAt,
&attempt.CompletedAt,
&attempt.Status,
&attempt.TotalScore,
&attempt.CreatedAt,
&attempt.UpdatedAt,
); err != nil {
return nil, fmt.Errorf("scan assessment attempt: %w", err)
}
attempts = append(attempts, attempt)
}
if err := rows.Err(); err != nil {
return nil, fmt.Errorf("iterate assessment attempts: %w", err)
}
return attempts, nil
}

func (r *attemptRepository) UpdateStatus(ctx context.Context, id int64, status string) error {
const query = `
UPDATE assessment_attempts
SET status = $2, updated_at = NOW()
WHERE id = $1
`
return r.execAffectingOne(ctx, query, "update assessment attempt status", id, status)
}

func (r *attemptRepository) SetScore(ctx context.Context, id int64, score int) error {
const query = `
UPDATE assessment_attempts
SET total_score = $2, updated_at = NOW()
WHERE id = $1
`
return r.execAffectingOne(ctx, query, "set assessment attempt score", id, score)
}

func (r *attemptRepository) SetCompletedAt(ctx context.Context, id int64, completedAt time.Time) error {
const query = `
UPDATE assessment_attempts
SET completed_at = $2, updated_at = NOW()
WHERE id = $1
`
return r.execAffectingOne(ctx, query, "set assessment attempt completed_at", id, completedAt)
}

func (r *attemptRepository) execAffectingOne(ctx context.Context, query, op string, args ...any) error {
res, err := r.db.ExecContext(ctx, query, args...)
if err != nil {
return fmt.Errorf("%s: %w", op, err)
}
rowsAffected, err := res.RowsAffected()
if err != nil {
return fmt.Errorf("%s rows affected: %w", op, err)
}
if rowsAffected == 0 {
return repositories.ErrNotFound
}
return nil
}
Loading