Add unified post storage #3
+39
-28
@@ -2,7 +2,18 @@
|
|||||||
INSERT INTO posts (
|
INSERT INTO posts (
|
||||||
id, parent_id, author_id, title, body, city, post_date, hidden, created_at, updated_at
|
id, parent_id, author_id, title, body, city, post_date, hidden, created_at, updated_at
|
||||||
)
|
)
|
||||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10);
|
VALUES (
|
||||||
|
sqlc.arg(id),
|
||||||
|
sqlc.arg(parent_id),
|
||||||
|
sqlc.arg(author_id),
|
||||||
|
sqlc.arg(title),
|
||||||
|
sqlc.arg(body),
|
||||||
|
sqlc.arg(city),
|
||||||
|
sqlc.arg(post_date),
|
||||||
|
sqlc.arg(hidden),
|
||||||
|
sqlc.arg(created_at),
|
||||||
|
sqlc.arg(updated_at)
|
||||||
|
);
|
||||||
|
|
||||||
-- name: GetPost :one
|
-- name: GetPost :one
|
||||||
SELECT
|
SELECT
|
||||||
@@ -10,13 +21,13 @@ SELECT
|
|||||||
p.title, p.body, p.city, p.post_date, p.hidden, p.created_at, p.updated_at
|
p.title, p.body, p.city, p.post_date, p.hidden, p.created_at, p.updated_at
|
||||||
FROM posts p
|
FROM posts p
|
||||||
JOIN users u ON u.id = p.author_id
|
JOIN users u ON u.id = p.author_id
|
||||||
WHERE p.id = $1;
|
WHERE p.id = sqlc.arg(id);
|
||||||
|
|
||||||
-- name: ListPostThread :many
|
-- name: ListPostThread :many
|
||||||
WITH RECURSIVE thread AS (
|
WITH RECURSIVE thread AS (
|
||||||
SELECT p.*
|
SELECT p.*
|
||||||
FROM posts p
|
FROM posts p
|
||||||
WHERE p.id = $1 AND p.parent_id IS NULL
|
WHERE p.id = sqlc.arg(root_id) AND p.parent_id IS NULL
|
||||||
|
|
||||||
UNION ALL
|
UNION ALL
|
||||||
|
|
||||||
@@ -35,8 +46,10 @@ ORDER BY thread.created_at, thread.id;
|
|||||||
|
|
||||||
-- name: UpdatePost :execrows
|
-- name: UpdatePost :execrows
|
||||||
UPDATE posts
|
UPDATE posts
|
||||||
SET body = $2, updated_at = $3
|
SET
|
||||||
WHERE id = $1;
|
body = sqlc.arg(body),
|
||||||
|
updated_at = sqlc.arg(updated_at)
|
||||||
|
WHERE id = sqlc.arg(id);
|
||||||
|
|
||||||
-- name: ListRootPosts :many
|
-- name: ListRootPosts :many
|
||||||
WITH RECURSIVE roots AS (
|
WITH RECURSIVE roots AS (
|
||||||
@@ -47,8 +60,9 @@ WITH RECURSIVE roots AS (
|
|||||||
AND p.hidden = 0
|
AND p.hidden = 0
|
||||||
),
|
),
|
||||||
thread AS (
|
thread AS (
|
||||||
SELECT roots.id AS root_id, roots.id AS post_id, roots.author_id
|
SELECT roots.id AS root_id, child.id AS post_id, child.author_id
|
||||||
FROM roots
|
FROM roots
|
||||||
|
JOIN posts child ON child.parent_id = roots.id
|
||||||
|
|
||||||
UNION ALL
|
UNION ALL
|
||||||
|
|
||||||
@@ -57,15 +71,16 @@ thread AS (
|
|||||||
JOIN posts child ON child.parent_id = thread.post_id
|
JOIN posts child ON child.parent_id = thread.post_id
|
||||||
),
|
),
|
||||||
answered AS (
|
answered AS (
|
||||||
SELECT thread.root_id, bool_or(u.role = 'admin' AND thread.post_id <> thread.root_id) AS answered
|
SELECT DISTINCT thread.root_id
|
||||||
FROM thread
|
FROM thread
|
||||||
JOIN users u ON u.id = thread.author_id
|
JOIN users u ON u.id = thread.author_id
|
||||||
GROUP BY thread.root_id
|
WHERE u.role = 'admin'
|
||||||
),
|
),
|
||||||
scores AS (
|
scores AS (
|
||||||
SELECT post_id, COALESCE(SUM(value), 0)::bigint AS score
|
SELECT votes.post_id, SUM(votes.value)::bigint AS score
|
||||||
FROM post_votes
|
FROM roots
|
||||||
GROUP BY post_id
|
JOIN post_votes votes ON votes.post_id = roots.id
|
||||||
|
GROUP BY votes.post_id
|
||||||
)
|
)
|
||||||
SELECT
|
SELECT
|
||||||
roots.id, roots.parent_id, roots.author_id,
|
roots.id, roots.parent_id, roots.author_id,
|
||||||
@@ -73,17 +88,15 @@ SELECT
|
|||||||
roots.title, roots.body, roots.city, roots.post_date,
|
roots.title, roots.body, roots.city, roots.post_date,
|
||||||
roots.hidden, roots.created_at, roots.updated_at,
|
roots.hidden, roots.created_at, roots.updated_at,
|
||||||
COALESCE(scores.score, 0)::bigint AS score,
|
COALESCE(scores.score, 0)::bigint AS score,
|
||||||
COALESCE(answered.answered, false)::bool AS answered,
|
(answered.root_id IS NOT NULL)::bool AS answered,
|
||||||
COALESCE((
|
COALESCE(viewer_vote.value, 0)::bigint AS user_vote
|
||||||
SELECT post_votes.value
|
|
||||||
FROM post_votes
|
|
||||||
WHERE post_votes.user_id = sqlc.arg(viewer_id)
|
|
||||||
AND post_votes.post_id = roots.id
|
|
||||||
), 0)::bigint AS user_vote
|
|
||||||
FROM roots
|
FROM roots
|
||||||
JOIN users u ON u.id = roots.author_id
|
JOIN users u ON u.id = roots.author_id
|
||||||
LEFT JOIN scores ON scores.post_id = roots.id
|
LEFT JOIN scores ON scores.post_id = roots.id
|
||||||
LEFT JOIN answered ON answered.root_id = roots.id
|
LEFT JOIN answered ON answered.root_id = roots.id
|
||||||
|
LEFT JOIN post_votes viewer_vote
|
||||||
|
ON viewer_vote.user_id = sqlc.arg(viewer_id)
|
||||||
|
AND viewer_vote.post_id = roots.id
|
||||||
ORDER BY score DESC, roots.created_at, roots.id
|
ORDER BY score DESC, roots.created_at, roots.id
|
||||||
LIMIT sqlc.arg(row_limit);
|
LIMIT sqlc.arg(row_limit);
|
||||||
|
|
||||||
@@ -91,22 +104,20 @@ LIMIT sqlc.arg(row_limit);
|
|||||||
SELECT EXISTS(
|
SELECT EXISTS(
|
||||||
SELECT 1
|
SELECT 1
|
||||||
FROM posts
|
FROM posts
|
||||||
WHERE id = $1 AND parent_id IS NULL AND hidden = 0
|
WHERE id = sqlc.arg(id) AND parent_id IS NULL AND hidden = 0
|
||||||
)::bool;
|
)::bool;
|
||||||
|
|
||||||
-- name: DeletePostVote :exec
|
-- name: DeletePostVote :exec
|
||||||
DELETE FROM post_votes
|
DELETE FROM post_votes
|
||||||
WHERE user_id = $1 AND post_id = $2;
|
WHERE user_id = sqlc.arg(user_id)
|
||||||
|
AND post_id = sqlc.arg(post_id);
|
||||||
|
|
||||||
-- name: UpsertPostVoteOnVisibleRoot :execrows
|
-- name: UpsertPostVoteOnVisibleRoot :execrows
|
||||||
INSERT INTO post_votes (user_id, post_id, value)
|
INSERT INTO post_votes (user_id, post_id, value)
|
||||||
SELECT $1, $2, $3
|
SELECT sqlc.arg(user_id), sqlc.arg(post_id), sqlc.arg(value)
|
||||||
FROM posts p
|
FROM posts p
|
||||||
WHERE p.id = $2 AND p.parent_id IS NULL AND p.hidden = 0
|
WHERE p.id = sqlc.arg(post_id)
|
||||||
|
AND p.parent_id IS NULL
|
||||||
|
AND p.hidden = 0
|
||||||
ON CONFLICT (user_id, post_id) DO UPDATE
|
ON CONFLICT (user_id, post_id) DO UPDATE
|
||||||
SET value = excluded.value
|
SET value = excluded.value;
|
||||||
WHERE EXISTS (
|
|
||||||
SELECT 1
|
|
||||||
FROM posts p2
|
|
||||||
WHERE p2.id = excluded.post_id AND p2.parent_id IS NULL AND p2.hidden = 0
|
|
||||||
);
|
|
||||||
|
|||||||
@@ -104,6 +104,55 @@ ON CONFLICT (user_id, post_id) DO NOTHING`},
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func migratePostVoteIndex(ctx context.Context, exec execContext) error {
|
||||||
|
if _, err := exec.ExecContext(ctx, `
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_post_votes_post_id
|
||||||
|
ON post_votes(post_id)`); err != nil {
|
||||||
|
return fmt.Errorf("idx_post_votes_post_id: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func migratePostDate(ctx context.Context, exec execContext) error {
|
||||||
|
steps := []struct {
|
||||||
|
name string
|
||||||
|
sql string
|
||||||
|
}{
|
||||||
|
{"rename post date", `
|
||||||
|
DO $migration$
|
||||||
|
BEGIN
|
||||||
|
IF EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM information_schema.columns
|
||||||
|
WHERE table_schema = current_schema()
|
||||||
|
AND table_name = 'posts'
|
||||||
|
AND column_name = 'hunt_date'
|
||||||
|
) AND NOT EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM information_schema.columns
|
||||||
|
WHERE table_schema = current_schema()
|
||||||
|
AND table_name = 'posts'
|
||||||
|
AND column_name = 'post_date'
|
||||||
|
) THEN
|
||||||
|
ALTER TABLE posts RENAME COLUMN hunt_date TO post_date;
|
||||||
|
END IF;
|
||||||
|
END
|
||||||
|
$migration$`},
|
||||||
|
{"drop legacy root date index", `
|
||||||
|
DROP INDEX IF EXISTS idx_posts_root_hunt`},
|
||||||
|
{"create root date index", `
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_posts_root_date
|
||||||
|
ON posts(post_date, hidden)
|
||||||
|
WHERE parent_id IS NULL`},
|
||||||
|
}
|
||||||
|
for _, step := range steps {
|
||||||
|
if _, err := exec.ExecContext(ctx, step.sql); err != nil {
|
||||||
|
return fmt.Errorf("%s: %w", step.name, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
type execContext interface {
|
type execContext interface {
|
||||||
ExecContext(ctx context.Context, query string, args ...any) (sql.Result, error)
|
ExecContext(ctx context.Context, query string, args ...any) (sql.Result, error)
|
||||||
QueryContext(ctx context.Context, query string, args ...any) (*sql.Rows, error)
|
QueryContext(ctx context.Context, query string, args ...any) (*sql.Rows, error)
|
||||||
@@ -151,6 +200,8 @@ CREATE TABLE IF NOT EXISTS schema_migrations (
|
|||||||
{"002_user_profile_columns", migrateUserProfileColumns},
|
{"002_user_profile_columns", migrateUserProfileColumns},
|
||||||
{"003_user_email", migrateUserEmail},
|
{"003_user_email", migrateUserEmail},
|
||||||
{"004_posts", migratePosts},
|
{"004_posts", migratePosts},
|
||||||
|
{"005_post_vote_post_id_index", migratePostVoteIndex},
|
||||||
|
{"006_post_date", migratePostDate},
|
||||||
}
|
}
|
||||||
for _, m := range migrations {
|
for _, m := range migrations {
|
||||||
if applied[m.version] {
|
if applied[m.version] {
|
||||||
|
|||||||
@@ -97,6 +97,12 @@ VALUES ('homeowner', 'question-1', 1);`); err != nil {
|
|||||||
if err := migratePosts(ctx, conn); err != nil {
|
if err := migratePosts(ctx, conn); err != nil {
|
||||||
t.Fatalf("migration is not idempotent: %v", err)
|
t.Fatalf("migration is not idempotent: %v", err)
|
||||||
}
|
}
|
||||||
|
if err := migratePostVoteIndex(ctx, conn); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := migratePostVoteIndex(ctx, conn); err != nil {
|
||||||
|
t.Fatalf("post vote index migration is not idempotent: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
var postCount, voteCount, legacyQuestionCount, legacyAnswerCount int
|
var postCount, voteCount, legacyQuestionCount, legacyAnswerCount int
|
||||||
if err := conn.QueryRowContext(ctx, "SELECT count(*) FROM posts").Scan(&postCount); err != nil {
|
if err := conn.QueryRowContext(ctx, "SELECT count(*) FROM posts").Scan(&postCount); err != nil {
|
||||||
@@ -120,6 +126,23 @@ VALUES ('homeowner', 'question-1', 1);`); err != nil {
|
|||||||
legacyAnswerCount,
|
legacyAnswerCount,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
var postVoteIndexCount int
|
||||||
|
if err := conn.QueryRowContext(ctx, `
|
||||||
|
SELECT count(*)
|
||||||
|
FROM pg_indexes
|
||||||
|
WHERE schemaname = current_schema()
|
||||||
|
AND tablename = 'post_votes'
|
||||||
|
AND indexname = 'idx_post_votes_post_id'`).Scan(&postVoteIndexCount); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if postVoteIndexCount != 1 {
|
||||||
|
t.Fatalf("post vote index count = %d, want 1", postVoteIndexCount)
|
||||||
|
}
|
||||||
|
if _, err := conn.ExecContext(ctx, `
|
||||||
|
INSERT INTO post_votes (user_id, post_id, value)
|
||||||
|
VALUES ('homeowner', 'question-1', -1)`); err == nil {
|
||||||
|
t.Fatal("duplicate user/post vote unexpectedly succeeded")
|
||||||
|
}
|
||||||
|
|
||||||
var rootParent sql.NullString
|
var rootParent sql.NullString
|
||||||
var rootAuthor, title, rootBody, city, postDate, rootCreated, rootUpdated string
|
var rootAuthor, title, rootBody, city, postDate, rootCreated, rootUpdated string
|
||||||
@@ -210,6 +233,13 @@ INSERT INTO posts (
|
|||||||
thread[2].ID != "follow-up" {
|
thread[2].ID != "follow-up" {
|
||||||
t.Fatalf("recursive thread = %+v", thread)
|
t.Fatalf("recursive thread = %+v", thread)
|
||||||
}
|
}
|
||||||
|
nonRootThread, err := queries.ListPostThread(ctx, "answer:question-1")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(nonRootThread) != 0 {
|
||||||
|
t.Fatalf("non-root thread lookup returned %+v", nonRootThread)
|
||||||
|
}
|
||||||
if n, err := queries.UpdatePost(ctx, sqlc.UpdatePostParams{
|
if n, err := queries.UpdatePost(ctx, sqlc.UpdatePostParams{
|
||||||
ID: "follow-up",
|
ID: "follow-up",
|
||||||
Body: "The drip continues.",
|
Body: "The drip continues.",
|
||||||
@@ -238,6 +268,60 @@ INSERT INTO posts (
|
|||||||
roots[0].UserVote != 1 {
|
roots[0].UserVote != 1 {
|
||||||
t.Fatalf("root annotations = %+v", roots)
|
t.Fatalf("root annotations = %+v", roots)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if _, err := conn.ExecContext(ctx, `
|
||||||
|
DROP INDEX idx_posts_root_date;
|
||||||
|
ALTER TABLE posts RENAME COLUMN post_date TO hunt_date;
|
||||||
|
CREATE INDEX idx_posts_root_hunt
|
||||||
|
ON posts(hunt_date, hidden)
|
||||||
|
WHERE parent_id IS NULL;`); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := migratePostDate(ctx, conn); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := migratePostDate(ctx, conn); err != nil {
|
||||||
|
t.Fatalf("post date migration is not idempotent: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var postDateColumnCount, huntDateColumnCount, rootDateIndexCount, legacyIndexCount int
|
||||||
|
if err := conn.QueryRowContext(ctx, `
|
||||||
|
SELECT
|
||||||
|
count(*) FILTER (WHERE column_name = 'post_date'),
|
||||||
|
count(*) FILTER (WHERE column_name = 'hunt_date')
|
||||||
|
FROM information_schema.columns
|
||||||
|
WHERE table_schema = current_schema()
|
||||||
|
AND table_name = 'posts'`).Scan(&postDateColumnCount, &huntDateColumnCount); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := conn.QueryRowContext(ctx, `
|
||||||
|
SELECT
|
||||||
|
count(*) FILTER (WHERE indexname = 'idx_posts_root_date'),
|
||||||
|
count(*) FILTER (WHERE indexname = 'idx_posts_root_hunt')
|
||||||
|
FROM pg_indexes
|
||||||
|
WHERE schemaname = current_schema()
|
||||||
|
AND tablename = 'posts'`).Scan(&rootDateIndexCount, &legacyIndexCount); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
var migratedPostDate string
|
||||||
|
if err := conn.QueryRowContext(ctx, `
|
||||||
|
SELECT post_date FROM posts WHERE id = 'question-1'`).Scan(&migratedPostDate); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if postDateColumnCount != 1 ||
|
||||||
|
huntDateColumnCount != 0 ||
|
||||||
|
rootDateIndexCount != 1 ||
|
||||||
|
legacyIndexCount != 0 ||
|
||||||
|
migratedPostDate != "2026-08-26" {
|
||||||
|
t.Fatalf(
|
||||||
|
"post date migration columns=%d legacy_columns=%d indexes=%d legacy_indexes=%d date=%q",
|
||||||
|
postDateColumnCount,
|
||||||
|
huntDateColumnCount,
|
||||||
|
rootDateIndexCount,
|
||||||
|
legacyIndexCount,
|
||||||
|
migratedPostDate,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMigratePostsReportsStep(t *testing.T) {
|
func TestMigratePostsReportsStep(t *testing.T) {
|
||||||
|
|||||||
@@ -14,7 +14,18 @@ const createPost = `-- name: CreatePost :exec
|
|||||||
INSERT INTO posts (
|
INSERT INTO posts (
|
||||||
id, parent_id, author_id, title, body, city, post_date, hidden, created_at, updated_at
|
id, parent_id, author_id, title, body, city, post_date, hidden, created_at, updated_at
|
||||||
)
|
)
|
||||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
|
VALUES (
|
||||||
|
$1,
|
||||||
|
$2,
|
||||||
|
$3,
|
||||||
|
$4,
|
||||||
|
$5,
|
||||||
|
$6,
|
||||||
|
$7,
|
||||||
|
$8,
|
||||||
|
$9,
|
||||||
|
$10
|
||||||
|
)
|
||||||
`
|
`
|
||||||
|
|
||||||
type CreatePostParams struct {
|
type CreatePostParams struct {
|
||||||
@@ -48,7 +59,8 @@ func (q *Queries) CreatePost(ctx context.Context, arg CreatePostParams) error {
|
|||||||
|
|
||||||
const deletePostVote = `-- name: DeletePostVote :exec
|
const deletePostVote = `-- name: DeletePostVote :exec
|
||||||
DELETE FROM post_votes
|
DELETE FROM post_votes
|
||||||
WHERE user_id = $1 AND post_id = $2
|
WHERE user_id = $1
|
||||||
|
AND post_id = $2
|
||||||
`
|
`
|
||||||
|
|
||||||
type DeletePostVoteParams struct {
|
type DeletePostVoteParams struct {
|
||||||
@@ -142,8 +154,8 @@ type ListPostThreadRow struct {
|
|||||||
UpdatedAt string
|
UpdatedAt string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Queries) ListPostThread(ctx context.Context, id string) ([]ListPostThreadRow, error) {
|
func (q *Queries) ListPostThread(ctx context.Context, rootID string) ([]ListPostThreadRow, error) {
|
||||||
rows, err := q.db.QueryContext(ctx, listPostThread, id)
|
rows, err := q.db.QueryContext(ctx, listPostThread, rootID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -187,8 +199,9 @@ WITH RECURSIVE roots AS (
|
|||||||
AND p.hidden = 0
|
AND p.hidden = 0
|
||||||
),
|
),
|
||||||
thread AS (
|
thread AS (
|
||||||
SELECT roots.id AS root_id, roots.id AS post_id, roots.author_id
|
SELECT roots.id AS root_id, child.id AS post_id, child.author_id
|
||||||
FROM roots
|
FROM roots
|
||||||
|
JOIN posts child ON child.parent_id = roots.id
|
||||||
|
|
||||||
UNION ALL
|
UNION ALL
|
||||||
|
|
||||||
@@ -197,15 +210,16 @@ thread AS (
|
|||||||
JOIN posts child ON child.parent_id = thread.post_id
|
JOIN posts child ON child.parent_id = thread.post_id
|
||||||
),
|
),
|
||||||
answered AS (
|
answered AS (
|
||||||
SELECT thread.root_id, bool_or(u.role = 'admin' AND thread.post_id <> thread.root_id) AS answered
|
SELECT DISTINCT thread.root_id
|
||||||
FROM thread
|
FROM thread
|
||||||
JOIN users u ON u.id = thread.author_id
|
JOIN users u ON u.id = thread.author_id
|
||||||
GROUP BY thread.root_id
|
WHERE u.role = 'admin'
|
||||||
),
|
),
|
||||||
scores AS (
|
scores AS (
|
||||||
SELECT post_id, COALESCE(SUM(value), 0)::bigint AS score
|
SELECT votes.post_id, SUM(votes.value)::bigint AS score
|
||||||
FROM post_votes
|
FROM roots
|
||||||
GROUP BY post_id
|
JOIN post_votes votes ON votes.post_id = roots.id
|
||||||
|
GROUP BY votes.post_id
|
||||||
)
|
)
|
||||||
SELECT
|
SELECT
|
||||||
roots.id, roots.parent_id, roots.author_id,
|
roots.id, roots.parent_id, roots.author_id,
|
||||||
@@ -213,17 +227,15 @@ SELECT
|
|||||||
roots.title, roots.body, roots.city, roots.post_date,
|
roots.title, roots.body, roots.city, roots.post_date,
|
||||||
roots.hidden, roots.created_at, roots.updated_at,
|
roots.hidden, roots.created_at, roots.updated_at,
|
||||||
COALESCE(scores.score, 0)::bigint AS score,
|
COALESCE(scores.score, 0)::bigint AS score,
|
||||||
COALESCE(answered.answered, false)::bool AS answered,
|
(answered.root_id IS NOT NULL)::bool AS answered,
|
||||||
COALESCE((
|
COALESCE(viewer_vote.value, 0)::bigint AS user_vote
|
||||||
SELECT post_votes.value
|
|
||||||
FROM post_votes
|
|
||||||
WHERE post_votes.user_id = $1
|
|
||||||
AND post_votes.post_id = roots.id
|
|
||||||
), 0)::bigint AS user_vote
|
|
||||||
FROM roots
|
FROM roots
|
||||||
JOIN users u ON u.id = roots.author_id
|
JOIN users u ON u.id = roots.author_id
|
||||||
LEFT JOIN scores ON scores.post_id = roots.id
|
LEFT JOIN scores ON scores.post_id = roots.id
|
||||||
LEFT JOIN answered ON answered.root_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
|
ORDER BY score DESC, roots.created_at, roots.id
|
||||||
LIMIT $2
|
LIMIT $2
|
||||||
`
|
`
|
||||||
@@ -308,18 +320,20 @@ func (q *Queries) PostIsVisibleRoot(ctx context.Context, id string) (bool, error
|
|||||||
|
|
||||||
const updatePost = `-- name: UpdatePost :execrows
|
const updatePost = `-- name: UpdatePost :execrows
|
||||||
UPDATE posts
|
UPDATE posts
|
||||||
SET body = $2, updated_at = $3
|
SET
|
||||||
WHERE id = $1
|
body = $1,
|
||||||
|
updated_at = $2
|
||||||
|
WHERE id = $3
|
||||||
`
|
`
|
||||||
|
|
||||||
type UpdatePostParams struct {
|
type UpdatePostParams struct {
|
||||||
ID string
|
|
||||||
Body string
|
Body string
|
||||||
UpdatedAt string
|
UpdatedAt string
|
||||||
|
ID string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Queries) UpdatePost(ctx context.Context, arg UpdatePostParams) (int64, error) {
|
func (q *Queries) UpdatePost(ctx context.Context, arg UpdatePostParams) (int64, error) {
|
||||||
result, err := q.db.ExecContext(ctx, updatePost, arg.ID, arg.Body, arg.UpdatedAt)
|
result, err := q.db.ExecContext(ctx, updatePost, arg.Body, arg.UpdatedAt, arg.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
@@ -330,14 +344,11 @@ const upsertPostVoteOnVisibleRoot = `-- name: UpsertPostVoteOnVisibleRoot :execr
|
|||||||
INSERT INTO post_votes (user_id, post_id, value)
|
INSERT INTO post_votes (user_id, post_id, value)
|
||||||
SELECT $1, $2, $3
|
SELECT $1, $2, $3
|
||||||
FROM posts p
|
FROM posts p
|
||||||
WHERE p.id = $2 AND p.parent_id IS NULL AND p.hidden = 0
|
WHERE p.id = $2
|
||||||
|
AND p.parent_id IS NULL
|
||||||
|
AND p.hidden = 0
|
||||||
ON CONFLICT (user_id, post_id) DO UPDATE
|
ON CONFLICT (user_id, post_id) DO UPDATE
|
||||||
SET value = excluded.value
|
SET value = excluded.value
|
||||||
WHERE EXISTS (
|
|
||||||
SELECT 1
|
|
||||||
FROM posts p2
|
|
||||||
WHERE p2.id = excluded.post_id AND p2.parent_id IS NULL AND p2.hidden = 0
|
|
||||||
)
|
|
||||||
`
|
`
|
||||||
|
|
||||||
type UpsertPostVoteOnVisibleRootParams struct {
|
type UpsertPostVoteOnVisibleRootParams struct {
|
||||||
|
|||||||
@@ -74,6 +74,9 @@ CREATE TABLE IF NOT EXISTS post_votes (
|
|||||||
PRIMARY KEY (user_id, post_id)
|
PRIMARY KEY (user_id, post_id)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_post_votes_post_id
|
||||||
|
ON post_votes(post_id);
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS sessions (
|
CREATE TABLE IF NOT EXISTS sessions (
|
||||||
token TEXT PRIMARY KEY,
|
token TEXT PRIMARY KEY,
|
||||||
data BYTEA NOT NULL,
|
data BYTEA NOT NULL,
|
||||||
|
|||||||
Reference in New Issue
Block a user