Add unified post storage (#3)

Adds one post creation path for roots and replies, recursive thread loading, body-only updates, root listings, and root-only voting across PostgreSQL and the in-memory store.

Co-authored-by: codegirl-007 <s.raide@gmail.com>
This commit was merged in pull request #3.
This commit is contained in:
2026-08-27 07:43:25 +00:00
committed by codegirl007
parent c5ef15ae1f
commit e86c2072ea
11 changed files with 1764 additions and 30 deletions
+385
View File
@@ -0,0 +1,385 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.31.1
// source: posts.sql
package sqlc
import (
"context"
"database/sql"
)
const createPost = `-- name: CreatePost :exec
INSERT INTO posts (
id, parent_id, author_id, title, body, city, post_date, post_state, created_at, updated_at
)
VALUES (
$1,
$2,
$3,
$4,
$5,
$6,
$7,
$8,
$9,
$10
)
`
type CreatePostParams struct {
ID string
ParentID sql.NullString
AuthorID string
Title string
Body string
City string
PostDate string
PostState string
CreatedAt string
UpdatedAt string
}
func (q *Queries) CreatePost(ctx context.Context, arg CreatePostParams) error {
_, err := q.db.ExecContext(ctx, createPost,
arg.ID,
arg.ParentID,
arg.AuthorID,
arg.Title,
arg.Body,
arg.City,
arg.PostDate,
arg.PostState,
arg.CreatedAt,
arg.UpdatedAt,
)
return err
}
const deletePostVote = `-- name: DeletePostVote :exec
DELETE FROM post_votes
WHERE user_id = $1
AND post_id = $2
`
type DeletePostVoteParams struct {
UserID string
PostID string
}
func (q *Queries) DeletePostVote(ctx context.Context, arg DeletePostVoteParams) error {
_, err := q.db.ExecContext(ctx, deletePostVote, arg.UserID, arg.PostID)
return err
}
const getPost = `-- name: GetPost :one
SELECT
p.id, p.parent_id, p.author_id, u.name AS author_name, u.role AS author_role,
p.title, p.body, p.city, p.post_date, p.post_state, p.created_at, p.updated_at
FROM posts p
JOIN users u ON u.id = p.author_id
WHERE p.id = $1
`
type GetPostRow struct {
ID string
ParentID sql.NullString
AuthorID string
AuthorName string
AuthorRole string
Title string
Body string
City string
PostDate string
PostState string
CreatedAt string
UpdatedAt string
}
func (q *Queries) GetPost(ctx context.Context, id string) (GetPostRow, error) {
row := q.db.QueryRowContext(ctx, getPost, id)
var i GetPostRow
err := row.Scan(
&i.ID,
&i.ParentID,
&i.AuthorID,
&i.AuthorName,
&i.AuthorRole,
&i.Title,
&i.Body,
&i.City,
&i.PostDate,
&i.PostState,
&i.CreatedAt,
&i.UpdatedAt,
)
return i, err
}
const listPostThread = `-- name: ListPostThread :many
WITH RECURSIVE thread AS (
SELECT p.id, p.parent_id, p.author_id, p.title, p.body, p.city, p.post_date, p.post_state, p.created_at, p.updated_at
FROM posts p
WHERE p.id = $1 AND p.parent_id IS NULL
UNION ALL
SELECT child.id, child.parent_id, child.author_id, child.title, child.body, child.city, child.post_date, child.post_state, child.created_at, child.updated_at
FROM posts child
JOIN thread parent ON child.parent_id = parent.id
)
SELECT
thread.id, thread.parent_id, thread.author_id,
u.name AS author_name, u.role AS author_role,
thread.title, thread.body, thread.city, thread.post_date,
thread.post_state, thread.created_at, thread.updated_at
FROM thread
JOIN users u ON u.id = thread.author_id
ORDER BY thread.created_at, thread.id
`
type ListPostThreadRow struct {
ID string
ParentID sql.NullString
AuthorID string
AuthorName string
AuthorRole string
Title string
Body string
City string
PostDate string
PostState string
CreatedAt string
UpdatedAt string
}
func (q *Queries) ListPostThread(ctx context.Context, rootID string) ([]ListPostThreadRow, error) {
rows, err := q.db.QueryContext(ctx, listPostThread, rootID)
if err != nil {
return nil, err
}
defer rows.Close()
items := []ListPostThreadRow{}
for rows.Next() {
var i ListPostThreadRow
if err := rows.Scan(
&i.ID,
&i.ParentID,
&i.AuthorID,
&i.AuthorName,
&i.AuthorRole,
&i.Title,
&i.Body,
&i.City,
&i.PostDate,
&i.PostState,
&i.CreatedAt,
&i.UpdatedAt,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Close(); err != nil {
return nil, err
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const listRootPosts = `-- name: ListRootPosts :many
WITH RECURSIVE roots AS (
SELECT p.id, p.parent_id, p.author_id, p.title, p.body, p.city, p.post_date, p.post_state, p.created_at, p.updated_at
FROM posts p
WHERE p.parent_id IS NULL
AND p.post_date = $3
AND p.post_state <> $4
),
thread AS (
SELECT roots.id AS root_id, child.id AS post_id, child.author_id
FROM roots
JOIN posts child ON child.parent_id = roots.id
UNION ALL
SELECT thread.root_id, child.id, child.author_id
FROM thread
JOIN posts child ON child.parent_id = thread.post_id
),
answered AS (
SELECT DISTINCT thread.root_id
FROM thread
JOIN users u ON u.id = thread.author_id
WHERE u.role = 'admin'
),
scores AS (
SELECT votes.post_id, SUM(votes.value)::bigint AS score
FROM roots
JOIN post_votes votes ON votes.post_id = roots.id
GROUP BY votes.post_id
)
SELECT
roots.id, roots.parent_id, roots.author_id,
u.name AS author_name, u.role AS author_role,
roots.title, roots.body, roots.city, roots.post_date,
roots.post_state, roots.created_at, roots.updated_at,
COALESCE(scores.score, 0)::bigint AS score,
(answered.root_id IS NOT NULL)::bool AS answered,
COALESCE(viewer_vote.value, 0)::bigint AS user_vote
FROM roots
JOIN users u ON u.id = roots.author_id
LEFT JOIN scores ON scores.post_id = roots.id
LEFT JOIN answered ON answered.root_id = roots.id
LEFT JOIN post_votes viewer_vote
ON viewer_vote.user_id = $1
AND viewer_vote.post_id = roots.id
ORDER BY score DESC, roots.created_at, roots.id
LIMIT $2
`
type ListRootPostsParams struct {
ViewerID string
RowLimit int32
PostDate string
HiddenState string
}
type ListRootPostsRow struct {
ID string
ParentID sql.NullString
AuthorID string
AuthorName string
AuthorRole string
Title string
Body string
City string
PostDate string
PostState string
CreatedAt string
UpdatedAt string
Score int64
Answered bool
UserVote int64
}
func (q *Queries) ListRootPosts(ctx context.Context, arg ListRootPostsParams) ([]ListRootPostsRow, error) {
rows, err := q.db.QueryContext(ctx, listRootPosts,
arg.ViewerID,
arg.RowLimit,
arg.PostDate,
arg.HiddenState,
)
if err != nil {
return nil, err
}
defer rows.Close()
items := []ListRootPostsRow{}
for rows.Next() {
var i ListRootPostsRow
if err := rows.Scan(
&i.ID,
&i.ParentID,
&i.AuthorID,
&i.AuthorName,
&i.AuthorRole,
&i.Title,
&i.Body,
&i.City,
&i.PostDate,
&i.PostState,
&i.CreatedAt,
&i.UpdatedAt,
&i.Score,
&i.Answered,
&i.UserVote,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Close(); err != nil {
return nil, err
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const postIsVisibleRoot = `-- name: PostIsVisibleRoot :one
SELECT EXISTS(
SELECT 1
FROM posts
WHERE id = $1
AND parent_id IS NULL
AND post_state <> $2
)::bool
`
type PostIsVisibleRootParams struct {
ID string
HiddenState string
}
func (q *Queries) PostIsVisibleRoot(ctx context.Context, arg PostIsVisibleRootParams) (bool, error) {
row := q.db.QueryRowContext(ctx, postIsVisibleRoot, arg.ID, arg.HiddenState)
var column_1 bool
err := row.Scan(&column_1)
return column_1, err
}
const updatePost = `-- name: UpdatePost :execrows
UPDATE posts
SET
body = $1,
updated_at = $2
WHERE id = $3
`
type UpdatePostParams struct {
Body string
UpdatedAt string
ID string
}
func (q *Queries) UpdatePost(ctx context.Context, arg UpdatePostParams) (int64, error) {
result, err := q.db.ExecContext(ctx, updatePost, arg.Body, arg.UpdatedAt, arg.ID)
if err != nil {
return 0, err
}
return result.RowsAffected()
}
const upsertPostVoteOnVisibleRoot = `-- name: UpsertPostVoteOnVisibleRoot :execrows
INSERT INTO post_votes (user_id, post_id, value)
SELECT $1, $2, $3
FROM posts p
WHERE p.id = $2
AND p.parent_id IS NULL
AND p.post_state <> $4
ON CONFLICT (user_id, post_id) DO UPDATE
SET value = excluded.value
`
type UpsertPostVoteOnVisibleRootParams struct {
UserID string
PostID string
Value int32
HiddenState string
}
func (q *Queries) UpsertPostVoteOnVisibleRoot(ctx context.Context, arg UpsertPostVoteOnVisibleRootParams) (int64, error) {
result, err := q.db.ExecContext(ctx, upsertPostVoteOnVisibleRoot,
arg.UserID,
arg.PostID,
arg.Value,
arg.HiddenState,
)
if err != nil {
return 0, err
}
return result.RowsAffected()
}