2026-03-04 18:42:58 +01:00
|
|
|
import { builder } from "../builder";
|
feat: add server-side pagination, search, and filtering to all collection and admin pages
- Public pages (videos, magazine, models): URL-driven search, sort, category/duration
filters, and Prev/Next pagination (page size 24)
- Admin tables (videos, articles): search input, toggle filters, and pagination (page size 50)
- Tags page: tag filtering now done server-side via DB arrayContains query instead of
fetching all items and filtering client-side
- Backend resolvers updated for videos, articles, models with paginated { items, total }
responses and filter/sort/tag args
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-07 10:43:26 +01:00
|
|
|
import { ModelType, ModelListType } from "../types/index";
|
2026-03-04 18:42:58 +01:00
|
|
|
import { users, user_photos, files } from "../../db/schema/index";
|
2026-03-07 19:25:04 +01:00
|
|
|
import { eq, and, desc, asc, ilike, count, arrayContains, type SQL } from "drizzle-orm";
|
|
|
|
|
import type { DB } from "../../db/connection";
|
2026-03-04 18:07:18 +01:00
|
|
|
|
2026-03-07 19:25:04 +01:00
|
|
|
async function enrichModel(db: DB, user: typeof users.$inferSelect) {
|
2026-03-04 18:07:18 +01:00
|
|
|
// Fetch photos
|
|
|
|
|
const photoRows = await db
|
|
|
|
|
.select({ id: files.id, filename: files.filename })
|
|
|
|
|
.from(user_photos)
|
|
|
|
|
.leftJoin(files, eq(user_photos.file_id, files.id))
|
|
|
|
|
.where(eq(user_photos.user_id, user.id))
|
|
|
|
|
.orderBy(user_photos.sort);
|
|
|
|
|
|
2026-03-07 10:54:29 +01:00
|
|
|
const seen = new Set<string>();
|
|
|
|
|
const photos = photoRows
|
2026-03-07 19:33:16 +01:00
|
|
|
.filter((p) => p.id !== null && !seen.has(p.id!) && seen.add(p.id!))
|
|
|
|
|
.map((p) => ({ id: p.id!, filename: p.filename! }));
|
2026-03-07 10:54:29 +01:00
|
|
|
|
|
|
|
|
return { ...user, photos };
|
2026-03-04 18:07:18 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
builder.queryField("models", (t) =>
|
|
|
|
|
t.field({
|
feat: add server-side pagination, search, and filtering to all collection and admin pages
- Public pages (videos, magazine, models): URL-driven search, sort, category/duration
filters, and Prev/Next pagination (page size 24)
- Admin tables (videos, articles): search input, toggle filters, and pagination (page size 50)
- Tags page: tag filtering now done server-side via DB arrayContains query instead of
fetching all items and filtering client-side
- Backend resolvers updated for videos, articles, models with paginated { items, total }
responses and filter/sort/tag args
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-07 10:43:26 +01:00
|
|
|
type: ModelListType,
|
2026-03-04 18:07:18 +01:00
|
|
|
args: {
|
|
|
|
|
featured: t.arg.boolean(),
|
|
|
|
|
limit: t.arg.int(),
|
feat: add server-side pagination, search, and filtering to all collection and admin pages
- Public pages (videos, magazine, models): URL-driven search, sort, category/duration
filters, and Prev/Next pagination (page size 24)
- Admin tables (videos, articles): search input, toggle filters, and pagination (page size 50)
- Tags page: tag filtering now done server-side via DB arrayContains query instead of
fetching all items and filtering client-side
- Backend resolvers updated for videos, articles, models with paginated { items, total }
responses and filter/sort/tag args
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-07 10:43:26 +01:00
|
|
|
search: t.arg.string(),
|
|
|
|
|
offset: t.arg.int(),
|
|
|
|
|
sortBy: t.arg.string(),
|
|
|
|
|
tag: t.arg.string(),
|
2026-03-04 18:07:18 +01:00
|
|
|
},
|
|
|
|
|
resolve: async (_root, args, ctx) => {
|
feat: add server-side pagination, search, and filtering to all collection and admin pages
- Public pages (videos, magazine, models): URL-driven search, sort, category/duration
filters, and Prev/Next pagination (page size 24)
- Admin tables (videos, articles): search input, toggle filters, and pagination (page size 50)
- Tags page: tag filtering now done server-side via DB arrayContains query instead of
fetching all items and filtering client-side
- Backend resolvers updated for videos, articles, models with paginated { items, total }
responses and filter/sort/tag args
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-07 10:43:26 +01:00
|
|
|
const pageSize = args.limit ?? 24;
|
|
|
|
|
const offset = args.offset ?? 0;
|
|
|
|
|
|
2026-03-07 19:25:04 +01:00
|
|
|
const conditions: SQL<unknown>[] = [eq(users.role, "model")];
|
feat: add server-side pagination, search, and filtering to all collection and admin pages
- Public pages (videos, magazine, models): URL-driven search, sort, category/duration
filters, and Prev/Next pagination (page size 24)
- Admin tables (videos, articles): search input, toggle filters, and pagination (page size 50)
- Tags page: tag filtering now done server-side via DB arrayContains query instead of
fetching all items and filtering client-side
- Backend resolvers updated for videos, articles, models with paginated { items, total }
responses and filter/sort/tag args
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-07 10:43:26 +01:00
|
|
|
if (args.search) conditions.push(ilike(users.artist_name, `%${args.search}%`));
|
|
|
|
|
if (args.tag) conditions.push(arrayContains(users.tags, [args.tag]));
|
2026-03-04 18:07:18 +01:00
|
|
|
|
feat: add server-side pagination, search, and filtering to all collection and admin pages
- Public pages (videos, magazine, models): URL-driven search, sort, category/duration
filters, and Prev/Next pagination (page size 24)
- Admin tables (videos, articles): search input, toggle filters, and pagination (page size 50)
- Tags page: tag filtering now done server-side via DB arrayContains query instead of
fetching all items and filtering client-side
- Backend resolvers updated for videos, articles, models with paginated { items, total }
responses and filter/sort/tag args
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-07 10:43:26 +01:00
|
|
|
const order = args.sortBy === "recent" ? desc(users.date_created) : asc(users.artist_name);
|
2026-03-04 18:07:18 +01:00
|
|
|
|
feat: add server-side pagination, search, and filtering to all collection and admin pages
- Public pages (videos, magazine, models): URL-driven search, sort, category/duration
filters, and Prev/Next pagination (page size 24)
- Admin tables (videos, articles): search input, toggle filters, and pagination (page size 50)
- Tags page: tag filtering now done server-side via DB arrayContains query instead of
fetching all items and filtering client-side
- Backend resolvers updated for videos, articles, models with paginated { items, total }
responses and filter/sort/tag args
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-07 10:43:26 +01:00
|
|
|
const where = and(...conditions);
|
|
|
|
|
const [modelList, totalRows] = await Promise.all([
|
|
|
|
|
ctx.db.select().from(users).where(where).orderBy(order).limit(pageSize).offset(offset),
|
|
|
|
|
ctx.db.select({ total: count() }).from(users).where(where),
|
|
|
|
|
]);
|
2026-03-07 19:25:04 +01:00
|
|
|
const items = await Promise.all(modelList.map((m) => enrichModel(ctx.db, m)));
|
feat: add server-side pagination, search, and filtering to all collection and admin pages
- Public pages (videos, magazine, models): URL-driven search, sort, category/duration
filters, and Prev/Next pagination (page size 24)
- Admin tables (videos, articles): search input, toggle filters, and pagination (page size 50)
- Tags page: tag filtering now done server-side via DB arrayContains query instead of
fetching all items and filtering client-side
- Backend resolvers updated for videos, articles, models with paginated { items, total }
responses and filter/sort/tag args
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-07 10:43:26 +01:00
|
|
|
return { items, total: totalRows[0]?.total ?? 0 };
|
2026-03-04 18:07:18 +01:00
|
|
|
},
|
|
|
|
|
}),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
builder.queryField("model", (t) =>
|
|
|
|
|
t.field({
|
|
|
|
|
type: ModelType,
|
|
|
|
|
nullable: true,
|
|
|
|
|
args: {
|
|
|
|
|
slug: t.arg.string({ required: true }),
|
|
|
|
|
},
|
|
|
|
|
resolve: async (_root, args, ctx) => {
|
|
|
|
|
const model = await ctx.db
|
|
|
|
|
.select()
|
|
|
|
|
.from(users)
|
|
|
|
|
.where(and(eq(users.slug, args.slug), eq(users.role, "model")))
|
|
|
|
|
.limit(1);
|
|
|
|
|
|
|
|
|
|
if (!model[0]) return null;
|
|
|
|
|
return enrichModel(ctx.db, model[0]);
|
|
|
|
|
},
|
|
|
|
|
}),
|
|
|
|
|
);
|