diff --git a/drizzle/0040_ordered_guides.sql b/drizzle/0040_ordered_guides.sql new file mode 100644 index 0000000..9e1bc24 --- /dev/null +++ b/drizzle/0040_ordered_guides.sql @@ -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"; diff --git a/drizzle/meta/_journal.json b/drizzle/meta/_journal.json index 8abea9c..375719c 100644 --- a/drizzle/meta/_journal.json +++ b/drizzle/meta/_journal.json @@ -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 } ] -} \ No newline at end of file +} diff --git a/lib/guides/mutations.ts b/lib/guides/mutations.ts index 2f50a5e..b79e0b3 100644 --- a/lib/guides/mutations.ts +++ b/lib/guides/mutations.ts @@ -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`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([ diff --git a/lib/guides/queries.ts b/lib/guides/queries.ts index 21bd37b..843a15a 100644 --- a/lib/guides/queries.ts +++ b/lib/guides/queries.ts @@ -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,