Add nested posts UI (#5)

## Summary
- Cut hunt, question, submission, voting, hiding, and profile flows over to unified posts
- Render nested replies with permission-aware inline Reply/Edit controls and edited markers
- Add post profile queries and the author index migration they depend on

Co-authored-by: codegirl-007 <s.raide@gmail.com>
This commit was merged in pull request #5.
This commit is contained in:
2026-08-27 15:53:01 +00:00
committed by codegirl007
parent 4d994d5300
commit 7412069ca6
22 changed files with 1033 additions and 351 deletions
+207
View File
@@ -117,6 +117,34 @@ func (q *Queries) GetPost(ctx context.Context, id string) (GetPostRow, error) {
return i, err
}
const getRootPostVoteSummary = `-- name: GetRootPostVoteSummary :one
SELECT
COALESCE(SUM(value), 0)::bigint AS score,
COALESCE(
MAX(value) FILTER (WHERE user_id = $1),
0
)::bigint AS user_vote
FROM post_votes
WHERE post_id = $2
`
type GetRootPostVoteSummaryParams struct {
ViewerID string
RootID string
}
type GetRootPostVoteSummaryRow struct {
Score int64
UserVote int64
}
func (q *Queries) GetRootPostVoteSummary(ctx context.Context, arg GetRootPostVoteSummaryParams) (GetRootPostVoteSummaryRow, error) {
row := q.db.QueryRowContext(ctx, getRootPostVoteSummary, arg.ViewerID, arg.RootID)
var i GetRootPostVoteSummaryRow
err := row.Scan(&i.Score, &i.UserVote)
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
@@ -309,6 +337,162 @@ func (q *Queries) ListRootPosts(ctx context.Context, arg ListRootPostsParams) ([
return items, nil
}
const listRootPostsAnsweredBy = `-- name: ListRootPostsAnsweredBy :many
WITH RECURSIVE ancestors AS (
SELECT p.id, p.parent_id
FROM posts p
WHERE p.author_id = $3
AND p.parent_id IS NOT NULL
UNION
SELECT parent.id, parent.parent_id
FROM posts parent
JOIN ancestors child ON child.parent_id = parent.id
)
SELECT DISTINCT
root.id, root.parent_id, root.author_id,
u.name AS author_name, u.role AS author_role,
root.title, root.body, root.city, root.post_date,
root.post_state, root.created_at, root.updated_at
FROM posts root
JOIN ancestors ON ancestors.id = root.id
JOIN users u ON u.id = root.author_id
WHERE root.parent_id IS NULL
AND root.post_state <> $1
ORDER BY root.created_at DESC, root.id DESC
LIMIT $2
`
type ListRootPostsAnsweredByParams struct {
HiddenState string
RowLimit int32
AdminID string
}
type ListRootPostsAnsweredByRow 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) ListRootPostsAnsweredBy(ctx context.Context, arg ListRootPostsAnsweredByParams) ([]ListRootPostsAnsweredByRow, error) {
rows, err := q.db.QueryContext(ctx, listRootPostsAnsweredBy, arg.HiddenState, arg.RowLimit, arg.AdminID)
if err != nil {
return nil, err
}
defer rows.Close()
items := []ListRootPostsAnsweredByRow{}
for rows.Next() {
var i ListRootPostsAnsweredByRow
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 listRootPostsByAuthor = `-- name: ListRootPostsByAuthor :many
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.parent_id IS NULL
AND p.author_id = $1
AND p.post_state <> $2
ORDER BY p.created_at DESC, p.id DESC
LIMIT $3
`
type ListRootPostsByAuthorParams struct {
AuthorID string
HiddenState string
RowLimit int32
}
type ListRootPostsByAuthorRow 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) ListRootPostsByAuthor(ctx context.Context, arg ListRootPostsByAuthorParams) ([]ListRootPostsByAuthorRow, error) {
rows, err := q.db.QueryContext(ctx, listRootPostsByAuthor, arg.AuthorID, arg.HiddenState, arg.RowLimit)
if err != nil {
return nil, err
}
defer rows.Close()
items := []ListRootPostsByAuthorRow{}
for rows.Next() {
var i ListRootPostsByAuthorRow
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 postIsVisibleRoot = `-- name: PostIsVisibleRoot :one
SELECT EXISTS(
SELECT 1
@@ -353,6 +537,29 @@ func (q *Queries) UpdatePost(ctx context.Context, arg UpdatePostParams) (int64,
return result.RowsAffected()
}
const updateRootPostState = `-- name: UpdateRootPostState :execrows
UPDATE posts
SET
post_state = $1,
updated_at = $2
WHERE id = $3
AND parent_id IS NULL
`
type UpdateRootPostStateParams struct {
PostState string
UpdatedAt string
ID string
}
func (q *Queries) UpdateRootPostState(ctx context.Context, arg UpdateRootPostStateParams) (int64, error) {
result, err := q.db.ExecContext(ctx, updateRootPostState, arg.PostState, 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