fix(guides) : make ordering predictable
CI / Verify (push) Successful in 1m23s
CI / Build immutable images and deploy (push) Successful in 1m49s

This commit is contained in:
2026-09-09 23:40:30 +07:00 Unverified
parent 289c17808f
commit 00f59e6e85
4 changed files with 25 additions and 5 deletions
+8
View File
@@ -0,0 +1,8 @@
WITH ranked_guides AS (
SELECT "id", (row_number() OVER (ORDER BY "created_at", "id") * 10)::integer AS "sort_order"
FROM "guides"."guide"
)
UPDATE "guides"."guide" AS "guide"
SET "sort_order" = ranked_guides."sort_order"
FROM ranked_guides
WHERE "guide"."id" = ranked_guides."id";
+8 -1
View File
@@ -281,6 +281,13 @@
"when": 1788794054894,
"tag": "0039_flowery_smiling_tiger",
"breakpoints": true
},
{
"idx": 40,
"version": "7",
"when": 1788915600000,
"tag": "0040_ordered_guides",
"breakpoints": true
}
]
}
}
+5
View File
@@ -77,12 +77,17 @@ export async function createGuide(input: unknown) {
const slug = slugCollision ? `${baseSlug}-${character.key.toLowerCase()}` : baseSlug;
return db.transaction(async (tx) => {
await tx.execute(sql`select pg_advisory_xact_lock(hashtext('guides-sort-order'))`);
const [order] = await tx
.select({ maximum: sql<number>`coalesce(max(${guides.sortOrder}), 0)` })
.from(guides);
const [guide] = await tx.insert(guides).values({
characterKey: character.key,
slug,
name: character.name,
overview: parsed.overview,
coverMediaId: parsed.coverMediaId,
sortOrder: order.maximum + 10,
}).returning();
await tx.insert(guideSections).values(DEFAULT_SECTIONS.map((section) => ({ ...section, guideId: guide.id })));
await tx.insert(artifactGroups).values([
+4 -4
View File
@@ -1,6 +1,6 @@
import "server-only";
import { and, asc, eq, inArray, isNotNull, isNull, ne } from "drizzle-orm";
import { and, asc, desc, eq, inArray, isNotNull, isNull, ne } from "drizzle-orm";
import * as z from "zod";
import { getDb } from "@/db";
@@ -127,7 +127,7 @@ export async function listGuideCards(includeTrash = false) {
.from(guides)
.innerJoin(media, eq(media.id, guides.coverMediaId))
.where(includeTrash ? isNotNull(guides.trashedAt) : isNull(guides.trashedAt))
.orderBy(asc(guides.sortOrder), asc(guides.name));
.orderBy(desc(guides.sortOrder), desc(guides.createdAt), asc(guides.name));
return rows.map(({ coverObjectKey, ...guide }) => ({
...guide,
coverUrl: publicMediaUrl(coverObjectKey),
@@ -146,7 +146,7 @@ export async function listAdminTeamImportGuides(excludeGuideId: string) {
.from(guides)
.innerJoin(media, eq(media.id, guides.coverMediaId))
.where(and(isNull(guides.trashedAt), ne(guides.id, excludeGuideId)))
.orderBy(asc(guides.sortOrder), asc(guides.name));
.orderBy(desc(guides.sortOrder), desc(guides.createdAt), asc(guides.name));
return rows.map(({ coverObjectKey, ...guide }) => ({
...guide,
@@ -221,7 +221,7 @@ export async function listPublicGuideCards() {
.from(guides)
.innerJoin(media, eq(media.id, guides.coverMediaId))
.where(and(eq(guides.isPublic, true), isNull(guides.trashedAt)))
.orderBy(asc(guides.sortOrder), asc(guides.name)), getDb().select().from(glossaryAliases)]);
.orderBy(desc(guides.sortOrder), desc(guides.createdAt), asc(guides.name)), getDb().select().from(glossaryAliases)]);
const grouped = aliasesByItem(aliases);
return rows.map(({ coverObjectKey, ...guide }) => ({
...guide,