Adopt sqlc for typed Postgres queries behind store entities.
This commit is contained in:
+61
-80
@@ -10,6 +10,7 @@ import (
|
||||
"github.com/google/uuid"
|
||||
|
||||
"plumber/internal/pacific"
|
||||
"plumber/internal/store/sqlc"
|
||||
)
|
||||
|
||||
// RankedQuestion is a question row with score / vote annotations for lists.
|
||||
@@ -51,9 +52,15 @@ func (q *RankedQuestion) Create(ctx context.Context) error {
|
||||
if q.CreatedAt == "" {
|
||||
q.CreatedAt = time.Now().UTC().Format(time.RFC3339)
|
||||
}
|
||||
_, err := q.db.ExecContext(ctx, `INSERT INTO questions (id, author_id, title, body, city, hunt_date, hidden, created_at) VALUES ($1, $2, $3, $4, $5, $6, 0, $7)`,
|
||||
q.ID, q.AuthorID, q.Title, q.Body, q.City, q.HuntDate, q.CreatedAt)
|
||||
return err
|
||||
return sqlc.New(q.db).CreateQuestion(ctx, sqlc.CreateQuestionParams{
|
||||
ID: q.ID,
|
||||
AuthorID: q.AuthorID,
|
||||
Title: q.Title,
|
||||
Body: q.Body,
|
||||
City: q.City,
|
||||
HuntDate: q.HuntDate,
|
||||
CreatedAt: q.CreatedAt,
|
||||
})
|
||||
}
|
||||
|
||||
// Hide marks the question hidden.
|
||||
@@ -61,108 +68,82 @@ func (q *RankedQuestion) Hide(ctx context.Context) error {
|
||||
if q == nil || q.db == nil {
|
||||
return fmt.Errorf("question: no database")
|
||||
}
|
||||
_, err := q.db.ExecContext(ctx, `UPDATE questions SET hidden = 1 WHERE id = $1`, q.ID)
|
||||
if err == nil {
|
||||
q.Hidden = true
|
||||
if err := sqlc.New(q.db).HideQuestion(ctx, q.ID); err != nil {
|
||||
return err
|
||||
}
|
||||
q.Hidden = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func rankedFrom(
|
||||
db *sql.DB,
|
||||
id, authorID, authorName, title, body, city, huntDate, createdAt string,
|
||||
hidden int32, score, answered, userVote int64,
|
||||
) RankedQuestion {
|
||||
return RankedQuestion{
|
||||
ID: id,
|
||||
AuthorID: authorID,
|
||||
AuthorName: authorName,
|
||||
Title: title,
|
||||
Body: body,
|
||||
City: city,
|
||||
HuntDate: huntDate,
|
||||
Hidden: hidden != 0,
|
||||
CreatedAt: createdAt,
|
||||
Score: int(score),
|
||||
Answered: answered != 0,
|
||||
UserVote: int(userVote),
|
||||
db: db,
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func ListHunt(ctx context.Context, db *sql.DB, huntDate, viewerID string) ([]RankedQuestion, error) {
|
||||
rows, err := db.QueryContext(ctx, `
|
||||
SELECT q.id, q.author_id, u.name, q.title, q.body, q.city, q.hunt_date, q.hidden, q.created_at,
|
||||
COALESCE(SUM(v.value), 0) AS score,
|
||||
CASE WHEN a.question_id IS NULL THEN 0 ELSE 1 END AS answered,
|
||||
COALESCE((SELECT value FROM votes WHERE user_id = $1 AND question_id = q.id), 0) AS user_vote
|
||||
FROM questions q
|
||||
JOIN users u ON u.id = q.author_id
|
||||
LEFT JOIN votes v ON v.question_id = q.id
|
||||
LEFT JOIN answers a ON a.question_id = q.id
|
||||
WHERE q.hunt_date = $2 AND q.hidden = 0
|
||||
GROUP BY q.id, q.author_id, u.name, q.title, q.body, q.city, q.hunt_date, q.hidden, q.created_at, a.question_id
|
||||
ORDER BY score DESC, q.created_at ASC`, viewerID, huntDate)
|
||||
rows, err := sqlc.New(db).ListHunt(ctx, sqlc.ListHuntParams{
|
||||
UserID: viewerID,
|
||||
HuntDate: huntDate,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
return scanRankedList(db, rows)
|
||||
out := make([]RankedQuestion, 0, len(rows))
|
||||
for _, r := range rows {
|
||||
out = append(out, rankedFrom(db, r.ID, r.AuthorID, r.AuthorName, r.Title, r.Body, r.City, r.HuntDate, r.CreatedAt, r.Hidden, r.Score, r.Answered, r.UserVote))
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func GetQuestion(ctx context.Context, db *sql.DB, id, viewerID string) (*RankedQuestion, error) {
|
||||
row := db.QueryRowContext(ctx, `
|
||||
SELECT q.id, q.author_id, u.name, q.title, q.body, q.city, q.hunt_date, q.hidden, q.created_at,
|
||||
COALESCE((SELECT SUM(value) FROM votes WHERE question_id = q.id), 0) AS score,
|
||||
CASE WHEN a.question_id IS NULL THEN 0 ELSE 1 END AS answered,
|
||||
COALESCE((SELECT value FROM votes WHERE user_id = $1 AND question_id = q.id), 0) AS user_vote
|
||||
FROM questions q
|
||||
JOIN users u ON u.id = q.author_id
|
||||
LEFT JOIN answers a ON a.question_id = q.id
|
||||
WHERE q.id = $2`, viewerID, id)
|
||||
q, err := scanRanked(db, row)
|
||||
r, err := sqlc.New(db).GetQuestion(ctx, sqlc.GetQuestionParams{
|
||||
UserID: viewerID,
|
||||
ID: id,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
q := rankedFrom(db, r.ID, r.AuthorID, r.AuthorName, r.Title, r.Body, r.City, r.HuntDate, r.CreatedAt, r.Hidden, r.Score, r.Answered, r.UserVote)
|
||||
return &q, nil
|
||||
}
|
||||
|
||||
func ListQuestionsByAuthor(ctx context.Context, db *sql.DB, authorID string) ([]RankedQuestion, error) {
|
||||
rows, err := db.QueryContext(ctx, `
|
||||
SELECT q.id, q.author_id, u.name, q.title, q.body, q.city, q.hunt_date, q.hidden, q.created_at,
|
||||
COALESCE((SELECT SUM(value) FROM votes WHERE question_id = q.id), 0) AS score,
|
||||
CASE WHEN a.question_id IS NULL THEN 0 ELSE 1 END AS answered,
|
||||
0 AS user_vote
|
||||
FROM questions q
|
||||
JOIN users u ON u.id = q.author_id
|
||||
LEFT JOIN answers a ON a.question_id = q.id
|
||||
WHERE q.author_id = $1 AND q.hidden = 0
|
||||
ORDER BY q.created_at DESC`, authorID)
|
||||
rows, err := sqlc.New(db).ListQuestionsByAuthor(ctx, authorID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
return scanRankedList(db, rows)
|
||||
out := make([]RankedQuestion, 0, len(rows))
|
||||
for _, r := range rows {
|
||||
out = append(out, rankedFrom(db, r.ID, r.AuthorID, r.AuthorName, r.Title, r.Body, r.City, r.HuntDate, r.CreatedAt, r.Hidden, r.Score, r.Answered, r.UserVote))
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func ListQuestionsAnsweredBy(ctx context.Context, db *sql.DB, adminID string) ([]RankedQuestion, error) {
|
||||
rows, err := db.QueryContext(ctx, `
|
||||
SELECT q.id, q.author_id, u.name, q.title, q.body, q.city, q.hunt_date, q.hidden, q.created_at,
|
||||
COALESCE((SELECT SUM(value) FROM votes WHERE question_id = q.id), 0) AS score,
|
||||
1 AS answered,
|
||||
0 AS user_vote
|
||||
FROM answers ans
|
||||
JOIN questions q ON q.id = ans.question_id
|
||||
JOIN users u ON u.id = q.author_id
|
||||
WHERE ans.author_id = $1 AND q.hidden = 0
|
||||
ORDER BY ans.updated_at DESC`, adminID)
|
||||
rows, err := sqlc.New(db).ListQuestionsAnsweredBy(ctx, adminID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
return scanRankedList(db, rows)
|
||||
}
|
||||
|
||||
type scanned interface {
|
||||
Scan(dest ...any) error
|
||||
}
|
||||
|
||||
func scanRanked(db *sql.DB, rows scanned) (RankedQuestion, error) {
|
||||
var q RankedQuestion
|
||||
var hidden, answered int
|
||||
err := rows.Scan(&q.ID, &q.AuthorID, &q.AuthorName, &q.Title, &q.Body, &q.City, &q.HuntDate, &hidden, &q.CreatedAt, &q.Score, &answered, &q.UserVote)
|
||||
q.Hidden = hidden != 0
|
||||
q.Answered = answered != 0
|
||||
q.db = db
|
||||
return q, err
|
||||
}
|
||||
|
||||
func scanRankedList(db *sql.DB, rows *sql.Rows) ([]RankedQuestion, error) {
|
||||
var out []RankedQuestion
|
||||
for rows.Next() {
|
||||
q, err := scanRanked(db, rows)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out = append(out, q)
|
||||
out := make([]RankedQuestion, 0, len(rows))
|
||||
for _, r := range rows {
|
||||
out = append(out, rankedFrom(db, r.ID, r.AuthorID, r.AuthorName, r.Title, r.Body, r.City, r.HuntDate, r.CreatedAt, r.Hidden, r.Score, r.Answered, r.UserVote))
|
||||
}
|
||||
return out, rows.Err()
|
||||
return out, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user