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 { ArticleType, ArticleListType, AdminArticleListType } from "../types/index";
|
2026-03-04 18:42:58 +01:00
|
|
|
import { articles, users } from "../../db/schema/index";
|
2026-03-07 19:25:04 +01:00
|
|
|
import { eq, and, lte, desc, asc, ilike, or, count, arrayContains, type SQL } from "drizzle-orm";
|
2026-03-06 16:14:00 +01:00
|
|
|
import { requireAdmin } from "../../lib/acl";
|
2026-03-07 19:25:04 +01:00
|
|
|
import type { DB } from "../../db/connection";
|
feat: role-based ACL + admin management UI
Backend:
- Add acl.ts with requireAuth/requireRole/requireOwnerOrAdmin helpers
- Gate premium videos from unauthenticated users in videos query/resolver
- Fix updateVideoPlay to verify ownership before updating
- Add admin mutations: adminListUsers, adminUpdateUser, adminDeleteUser
- Add admin mutations: createVideo, updateVideo, deleteVideo, setVideoModels, adminListVideos
- Add admin mutations: createArticle, updateArticle, deleteArticle, adminListArticles
- Add deleteComment mutation (owner or admin only)
- Add AdminUserListType to GraphQL types
- Fix featured filter on articles query
Frontend:
- Install marked for markdown rendering
- Add /admin/* section with sidebar layout and admin-only guard
- Admin users page: paginated table with search, role filter, inline role change, delete
- Admin videos pages: list, create form, edit form with file upload and model assignment
- Admin articles pages: list, create form, edit form with split-pane markdown editor
- Add admin nav link in header (desktop + mobile) for admin users
- Render article content through marked in magazine detail page
- Add all admin GraphQL service functions to services.ts
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-06 12:31:33 +01:00
|
|
|
|
2026-03-07 19:25:04 +01:00
|
|
|
async function enrichArticle(db: DB, article: typeof articles.$inferSelect) {
|
feat: role-based ACL + admin management UI
Backend:
- Add acl.ts with requireAuth/requireRole/requireOwnerOrAdmin helpers
- Gate premium videos from unauthenticated users in videos query/resolver
- Fix updateVideoPlay to verify ownership before updating
- Add admin mutations: adminListUsers, adminUpdateUser, adminDeleteUser
- Add admin mutations: createVideo, updateVideo, deleteVideo, setVideoModels, adminListVideos
- Add admin mutations: createArticle, updateArticle, deleteArticle, adminListArticles
- Add deleteComment mutation (owner or admin only)
- Add AdminUserListType to GraphQL types
- Fix featured filter on articles query
Frontend:
- Install marked for markdown rendering
- Add /admin/* section with sidebar layout and admin-only guard
- Admin users page: paginated table with search, role filter, inline role change, delete
- Admin videos pages: list, create form, edit form with file upload and model assignment
- Admin articles pages: list, create form, edit form with split-pane markdown editor
- Add admin nav link in header (desktop + mobile) for admin users
- Render article content through marked in magazine detail page
- Add all admin GraphQL service functions to services.ts
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-06 12:31:33 +01:00
|
|
|
let author = null;
|
|
|
|
|
if (article.author) {
|
|
|
|
|
const authorUser = await db
|
|
|
|
|
.select({
|
2026-03-06 16:31:41 +01:00
|
|
|
id: users.id,
|
|
|
|
|
artist_name: users.artist_name,
|
|
|
|
|
slug: users.slug,
|
feat: role-based ACL + admin management UI
Backend:
- Add acl.ts with requireAuth/requireRole/requireOwnerOrAdmin helpers
- Gate premium videos from unauthenticated users in videos query/resolver
- Fix updateVideoPlay to verify ownership before updating
- Add admin mutations: adminListUsers, adminUpdateUser, adminDeleteUser
- Add admin mutations: createVideo, updateVideo, deleteVideo, setVideoModels, adminListVideos
- Add admin mutations: createArticle, updateArticle, deleteArticle, adminListArticles
- Add deleteComment mutation (owner or admin only)
- Add AdminUserListType to GraphQL types
- Fix featured filter on articles query
Frontend:
- Install marked for markdown rendering
- Add /admin/* section with sidebar layout and admin-only guard
- Admin users page: paginated table with search, role filter, inline role change, delete
- Admin videos pages: list, create form, edit form with file upload and model assignment
- Admin articles pages: list, create form, edit form with split-pane markdown editor
- Add admin nav link in header (desktop + mobile) for admin users
- Render article content through marked in magazine detail page
- Add all admin GraphQL service functions to services.ts
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-06 12:31:33 +01:00
|
|
|
avatar: users.avatar,
|
2026-03-07 19:02:53 +01:00
|
|
|
description: users.description,
|
feat: role-based ACL + admin management UI
Backend:
- Add acl.ts with requireAuth/requireRole/requireOwnerOrAdmin helpers
- Gate premium videos from unauthenticated users in videos query/resolver
- Fix updateVideoPlay to verify ownership before updating
- Add admin mutations: adminListUsers, adminUpdateUser, adminDeleteUser
- Add admin mutations: createVideo, updateVideo, deleteVideo, setVideoModels, adminListVideos
- Add admin mutations: createArticle, updateArticle, deleteArticle, adminListArticles
- Add deleteComment mutation (owner or admin only)
- Add AdminUserListType to GraphQL types
- Fix featured filter on articles query
Frontend:
- Install marked for markdown rendering
- Add /admin/* section with sidebar layout and admin-only guard
- Admin users page: paginated table with search, role filter, inline role change, delete
- Admin videos pages: list, create form, edit form with file upload and model assignment
- Admin articles pages: list, create form, edit form with split-pane markdown editor
- Add admin nav link in header (desktop + mobile) for admin users
- Render article content through marked in magazine detail page
- Add all admin GraphQL service functions to services.ts
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-06 12:31:33 +01:00
|
|
|
})
|
|
|
|
|
.from(users)
|
|
|
|
|
.where(eq(users.id, article.author))
|
|
|
|
|
.limit(1);
|
|
|
|
|
author = authorUser[0] || null;
|
|
|
|
|
}
|
|
|
|
|
return { ...article, author };
|
|
|
|
|
}
|
2026-03-04 18:07:18 +01:00
|
|
|
|
|
|
|
|
builder.queryField("articles", (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: ArticleListType,
|
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(),
|
|
|
|
|
category: 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;
|
feat: role-based ACL + admin management UI
Backend:
- Add acl.ts with requireAuth/requireRole/requireOwnerOrAdmin helpers
- Gate premium videos from unauthenticated users in videos query/resolver
- Fix updateVideoPlay to verify ownership before updating
- Add admin mutations: adminListUsers, adminUpdateUser, adminDeleteUser
- Add admin mutations: createVideo, updateVideo, deleteVideo, setVideoModels, adminListVideos
- Add admin mutations: createArticle, updateArticle, deleteArticle, adminListArticles
- Add deleteComment mutation (owner or admin only)
- Add AdminUserListType to GraphQL types
- Fix featured filter on articles query
Frontend:
- Install marked for markdown rendering
- Add /admin/* section with sidebar layout and admin-only guard
- Admin users page: paginated table with search, role filter, inline role change, delete
- Admin videos pages: list, create form, edit form with file upload and model assignment
- Admin articles pages: list, create form, edit form with split-pane markdown editor
- Add admin nav link in header (desktop + mobile) for admin users
- Render article content through marked in magazine detail page
- Add all admin GraphQL service functions to services.ts
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-06 12:31:33 +01:00
|
|
|
|
2026-03-07 19:25:04 +01:00
|
|
|
const conditions: SQL<unknown>[] = [lte(articles.publish_date, new Date())];
|
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.featured !== null && args.featured !== undefined) {
|
|
|
|
|
conditions.push(eq(articles.featured, args.featured));
|
|
|
|
|
}
|
|
|
|
|
if (args.category) conditions.push(eq(articles.category, args.category));
|
|
|
|
|
if (args.tag) conditions.push(arrayContains(articles.tags, [args.tag]));
|
|
|
|
|
if (args.search) {
|
|
|
|
|
conditions.push(
|
|
|
|
|
or(
|
|
|
|
|
ilike(articles.title, `%${args.search}%`),
|
|
|
|
|
ilike(articles.excerpt, `%${args.search}%`),
|
2026-03-07 19:25:04 +01:00
|
|
|
) as SQL<unknown>,
|
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
|
|
|
);
|
2026-03-04 18:07:18 +01:00
|
|
|
}
|
|
|
|
|
|
2026-03-07 19:25:04 +01:00
|
|
|
const where = and(...conditions);
|
|
|
|
|
const baseQuery = ctx.db.select().from(articles).where(where);
|
|
|
|
|
const ordered =
|
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
|
|
|
args.sortBy === "name"
|
2026-03-07 19:25:04 +01:00
|
|
|
? baseQuery.orderBy(asc(articles.title))
|
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
|
|
|
: args.sortBy === "featured"
|
2026-03-07 19:25:04 +01:00
|
|
|
? baseQuery.orderBy(desc(articles.featured), desc(articles.publish_date))
|
|
|
|
|
: baseQuery.orderBy(desc(articles.publish_date));
|
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 [articleList, totalRows] = await Promise.all([
|
2026-03-07 19:25:04 +01:00
|
|
|
ordered.limit(pageSize).offset(offset),
|
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
|
|
|
ctx.db.select({ total: count() }).from(articles).where(where),
|
|
|
|
|
]);
|
2026-03-07 19:25:04 +01:00
|
|
|
const items = await Promise.all(articleList.map((article) => enrichArticle(ctx.db, article)));
|
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("article", (t) =>
|
|
|
|
|
t.field({
|
|
|
|
|
type: ArticleType,
|
|
|
|
|
nullable: true,
|
|
|
|
|
args: {
|
|
|
|
|
slug: t.arg.string({ required: true }),
|
|
|
|
|
},
|
|
|
|
|
resolve: async (_root, args, ctx) => {
|
|
|
|
|
const article = await ctx.db
|
|
|
|
|
.select()
|
|
|
|
|
.from(articles)
|
|
|
|
|
.where(and(eq(articles.slug, args.slug), lte(articles.publish_date, new Date())))
|
|
|
|
|
.limit(1);
|
|
|
|
|
|
|
|
|
|
if (!article[0]) return null;
|
feat: role-based ACL + admin management UI
Backend:
- Add acl.ts with requireAuth/requireRole/requireOwnerOrAdmin helpers
- Gate premium videos from unauthenticated users in videos query/resolver
- Fix updateVideoPlay to verify ownership before updating
- Add admin mutations: adminListUsers, adminUpdateUser, adminDeleteUser
- Add admin mutations: createVideo, updateVideo, deleteVideo, setVideoModels, adminListVideos
- Add admin mutations: createArticle, updateArticle, deleteArticle, adminListArticles
- Add deleteComment mutation (owner or admin only)
- Add AdminUserListType to GraphQL types
- Fix featured filter on articles query
Frontend:
- Install marked for markdown rendering
- Add /admin/* section with sidebar layout and admin-only guard
- Admin users page: paginated table with search, role filter, inline role change, delete
- Admin videos pages: list, create form, edit form with file upload and model assignment
- Admin articles pages: list, create form, edit form with split-pane markdown editor
- Add admin nav link in header (desktop + mobile) for admin users
- Render article content through marked in magazine detail page
- Add all admin GraphQL service functions to services.ts
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-06 12:31:33 +01:00
|
|
|
return enrichArticle(ctx.db, article[0]);
|
|
|
|
|
},
|
|
|
|
|
}),
|
|
|
|
|
);
|
2026-03-04 18:07:18 +01:00
|
|
|
|
2026-03-07 11:05:21 +01:00
|
|
|
builder.queryField("adminGetArticle", (t) =>
|
|
|
|
|
t.field({
|
|
|
|
|
type: ArticleType,
|
|
|
|
|
nullable: true,
|
|
|
|
|
args: {
|
|
|
|
|
id: t.arg.string({ required: true }),
|
|
|
|
|
},
|
|
|
|
|
resolve: async (_root, args, ctx) => {
|
|
|
|
|
requireAdmin(ctx);
|
2026-03-07 11:29:48 +01:00
|
|
|
const article = await ctx.db.select().from(articles).where(eq(articles.id, args.id)).limit(1);
|
2026-03-07 11:05:21 +01:00
|
|
|
if (!article[0]) return null;
|
|
|
|
|
return enrichArticle(ctx.db, article[0]);
|
|
|
|
|
},
|
|
|
|
|
}),
|
|
|
|
|
);
|
|
|
|
|
|
feat: role-based ACL + admin management UI
Backend:
- Add acl.ts with requireAuth/requireRole/requireOwnerOrAdmin helpers
- Gate premium videos from unauthenticated users in videos query/resolver
- Fix updateVideoPlay to verify ownership before updating
- Add admin mutations: adminListUsers, adminUpdateUser, adminDeleteUser
- Add admin mutations: createVideo, updateVideo, deleteVideo, setVideoModels, adminListVideos
- Add admin mutations: createArticle, updateArticle, deleteArticle, adminListArticles
- Add deleteComment mutation (owner or admin only)
- Add AdminUserListType to GraphQL types
- Fix featured filter on articles query
Frontend:
- Install marked for markdown rendering
- Add /admin/* section with sidebar layout and admin-only guard
- Admin users page: paginated table with search, role filter, inline role change, delete
- Admin videos pages: list, create form, edit form with file upload and model assignment
- Admin articles pages: list, create form, edit form with split-pane markdown editor
- Add admin nav link in header (desktop + mobile) for admin users
- Render article content through marked in magazine detail page
- Add all admin GraphQL service functions to services.ts
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-06 12:31:33 +01:00
|
|
|
// ─── Admin queries & mutations ────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
builder.queryField("adminListArticles", (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: AdminArticleListType,
|
|
|
|
|
args: {
|
|
|
|
|
search: t.arg.string(),
|
|
|
|
|
category: t.arg.string(),
|
|
|
|
|
featured: t.arg.boolean(),
|
|
|
|
|
limit: t.arg.int(),
|
|
|
|
|
offset: t.arg.int(),
|
|
|
|
|
},
|
|
|
|
|
resolve: async (_root, args, ctx) => {
|
2026-03-06 16:14:00 +01:00
|
|
|
requireAdmin(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 limit = args.limit ?? 50;
|
|
|
|
|
const offset = args.offset ?? 0;
|
|
|
|
|
|
2026-03-07 19:25:04 +01:00
|
|
|
const conditions: SQL<unknown>[] = [];
|
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(
|
|
|
|
|
or(
|
|
|
|
|
ilike(articles.title, `%${args.search}%`),
|
|
|
|
|
ilike(articles.excerpt, `%${args.search}%`),
|
2026-03-07 19:25:04 +01:00
|
|
|
) as SQL<unknown>,
|
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.category) conditions.push(eq(articles.category, args.category));
|
|
|
|
|
if (args.featured !== null && args.featured !== undefined)
|
|
|
|
|
conditions.push(eq(articles.featured, args.featured));
|
|
|
|
|
|
|
|
|
|
const where = conditions.length > 0 ? and(...conditions) : undefined;
|
|
|
|
|
const [articleList, totalRows] = await Promise.all([
|
|
|
|
|
ctx.db
|
|
|
|
|
.select()
|
|
|
|
|
.from(articles)
|
|
|
|
|
.where(where)
|
|
|
|
|
.orderBy(desc(articles.publish_date))
|
|
|
|
|
.limit(limit)
|
|
|
|
|
.offset(offset),
|
|
|
|
|
ctx.db.select({ total: count() }).from(articles).where(where),
|
|
|
|
|
]);
|
2026-03-07 19:25:04 +01:00
|
|
|
const items = await Promise.all(articleList.map((article) => enrichArticle(ctx.db, article)));
|
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 };
|
feat: role-based ACL + admin management UI
Backend:
- Add acl.ts with requireAuth/requireRole/requireOwnerOrAdmin helpers
- Gate premium videos from unauthenticated users in videos query/resolver
- Fix updateVideoPlay to verify ownership before updating
- Add admin mutations: adminListUsers, adminUpdateUser, adminDeleteUser
- Add admin mutations: createVideo, updateVideo, deleteVideo, setVideoModels, adminListVideos
- Add admin mutations: createArticle, updateArticle, deleteArticle, adminListArticles
- Add deleteComment mutation (owner or admin only)
- Add AdminUserListType to GraphQL types
- Fix featured filter on articles query
Frontend:
- Install marked for markdown rendering
- Add /admin/* section with sidebar layout and admin-only guard
- Admin users page: paginated table with search, role filter, inline role change, delete
- Admin videos pages: list, create form, edit form with file upload and model assignment
- Admin articles pages: list, create form, edit form with split-pane markdown editor
- Add admin nav link in header (desktop + mobile) for admin users
- Render article content through marked in magazine detail page
- Add all admin GraphQL service functions to services.ts
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-06 12:31:33 +01:00
|
|
|
},
|
|
|
|
|
}),
|
|
|
|
|
);
|
2026-03-04 18:07:18 +01:00
|
|
|
|
feat: role-based ACL + admin management UI
Backend:
- Add acl.ts with requireAuth/requireRole/requireOwnerOrAdmin helpers
- Gate premium videos from unauthenticated users in videos query/resolver
- Fix updateVideoPlay to verify ownership before updating
- Add admin mutations: adminListUsers, adminUpdateUser, adminDeleteUser
- Add admin mutations: createVideo, updateVideo, deleteVideo, setVideoModels, adminListVideos
- Add admin mutations: createArticle, updateArticle, deleteArticle, adminListArticles
- Add deleteComment mutation (owner or admin only)
- Add AdminUserListType to GraphQL types
- Fix featured filter on articles query
Frontend:
- Install marked for markdown rendering
- Add /admin/* section with sidebar layout and admin-only guard
- Admin users page: paginated table with search, role filter, inline role change, delete
- Admin videos pages: list, create form, edit form with file upload and model assignment
- Admin articles pages: list, create form, edit form with split-pane markdown editor
- Add admin nav link in header (desktop + mobile) for admin users
- Render article content through marked in magazine detail page
- Add all admin GraphQL service functions to services.ts
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-06 12:31:33 +01:00
|
|
|
builder.mutationField("createArticle", (t) =>
|
|
|
|
|
t.field({
|
|
|
|
|
type: ArticleType,
|
|
|
|
|
args: {
|
|
|
|
|
title: t.arg.string({ required: true }),
|
|
|
|
|
slug: t.arg.string({ required: true }),
|
|
|
|
|
excerpt: t.arg.string(),
|
|
|
|
|
content: t.arg.string(),
|
|
|
|
|
imageId: t.arg.string(),
|
|
|
|
|
tags: t.arg.stringList(),
|
|
|
|
|
category: t.arg.string(),
|
|
|
|
|
featured: t.arg.boolean(),
|
|
|
|
|
publishDate: t.arg.string(),
|
|
|
|
|
},
|
|
|
|
|
resolve: async (_root, args, ctx) => {
|
2026-03-06 16:14:00 +01:00
|
|
|
requireAdmin(ctx);
|
feat: role-based ACL + admin management UI
Backend:
- Add acl.ts with requireAuth/requireRole/requireOwnerOrAdmin helpers
- Gate premium videos from unauthenticated users in videos query/resolver
- Fix updateVideoPlay to verify ownership before updating
- Add admin mutations: adminListUsers, adminUpdateUser, adminDeleteUser
- Add admin mutations: createVideo, updateVideo, deleteVideo, setVideoModels, adminListVideos
- Add admin mutations: createArticle, updateArticle, deleteArticle, adminListArticles
- Add deleteComment mutation (owner or admin only)
- Add AdminUserListType to GraphQL types
- Fix featured filter on articles query
Frontend:
- Install marked for markdown rendering
- Add /admin/* section with sidebar layout and admin-only guard
- Admin users page: paginated table with search, role filter, inline role change, delete
- Admin videos pages: list, create form, edit form with file upload and model assignment
- Admin articles pages: list, create form, edit form with split-pane markdown editor
- Add admin nav link in header (desktop + mobile) for admin users
- Render article content through marked in magazine detail page
- Add all admin GraphQL service functions to services.ts
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-06 12:31:33 +01:00
|
|
|
const inserted = await ctx.db
|
|
|
|
|
.insert(articles)
|
|
|
|
|
.values({
|
|
|
|
|
title: args.title,
|
|
|
|
|
slug: args.slug,
|
|
|
|
|
excerpt: args.excerpt || null,
|
|
|
|
|
content: args.content || null,
|
|
|
|
|
image: args.imageId || null,
|
|
|
|
|
tags: args.tags || [],
|
|
|
|
|
category: args.category || null,
|
|
|
|
|
featured: args.featured ?? false,
|
|
|
|
|
publish_date: args.publishDate ? new Date(args.publishDate) : new Date(),
|
|
|
|
|
author: ctx.currentUser!.id,
|
|
|
|
|
})
|
|
|
|
|
.returning();
|
|
|
|
|
return enrichArticle(ctx.db, inserted[0]);
|
|
|
|
|
},
|
|
|
|
|
}),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
builder.mutationField("updateArticle", (t) =>
|
|
|
|
|
t.field({
|
|
|
|
|
type: ArticleType,
|
|
|
|
|
nullable: true,
|
|
|
|
|
args: {
|
|
|
|
|
id: t.arg.string({ required: true }),
|
|
|
|
|
title: t.arg.string(),
|
|
|
|
|
slug: t.arg.string(),
|
|
|
|
|
excerpt: t.arg.string(),
|
|
|
|
|
content: t.arg.string(),
|
|
|
|
|
imageId: t.arg.string(),
|
2026-03-06 16:31:41 +01:00
|
|
|
authorId: t.arg.string(),
|
feat: role-based ACL + admin management UI
Backend:
- Add acl.ts with requireAuth/requireRole/requireOwnerOrAdmin helpers
- Gate premium videos from unauthenticated users in videos query/resolver
- Fix updateVideoPlay to verify ownership before updating
- Add admin mutations: adminListUsers, adminUpdateUser, adminDeleteUser
- Add admin mutations: createVideo, updateVideo, deleteVideo, setVideoModels, adminListVideos
- Add admin mutations: createArticle, updateArticle, deleteArticle, adminListArticles
- Add deleteComment mutation (owner or admin only)
- Add AdminUserListType to GraphQL types
- Fix featured filter on articles query
Frontend:
- Install marked for markdown rendering
- Add /admin/* section with sidebar layout and admin-only guard
- Admin users page: paginated table with search, role filter, inline role change, delete
- Admin videos pages: list, create form, edit form with file upload and model assignment
- Admin articles pages: list, create form, edit form with split-pane markdown editor
- Add admin nav link in header (desktop + mobile) for admin users
- Render article content through marked in magazine detail page
- Add all admin GraphQL service functions to services.ts
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-06 12:31:33 +01:00
|
|
|
tags: t.arg.stringList(),
|
|
|
|
|
category: t.arg.string(),
|
|
|
|
|
featured: t.arg.boolean(),
|
|
|
|
|
publishDate: t.arg.string(),
|
|
|
|
|
},
|
|
|
|
|
resolve: async (_root, args, ctx) => {
|
2026-03-06 16:14:00 +01:00
|
|
|
requireAdmin(ctx);
|
feat: role-based ACL + admin management UI
Backend:
- Add acl.ts with requireAuth/requireRole/requireOwnerOrAdmin helpers
- Gate premium videos from unauthenticated users in videos query/resolver
- Fix updateVideoPlay to verify ownership before updating
- Add admin mutations: adminListUsers, adminUpdateUser, adminDeleteUser
- Add admin mutations: createVideo, updateVideo, deleteVideo, setVideoModels, adminListVideos
- Add admin mutations: createArticle, updateArticle, deleteArticle, adminListArticles
- Add deleteComment mutation (owner or admin only)
- Add AdminUserListType to GraphQL types
- Fix featured filter on articles query
Frontend:
- Install marked for markdown rendering
- Add /admin/* section with sidebar layout and admin-only guard
- Admin users page: paginated table with search, role filter, inline role change, delete
- Admin videos pages: list, create form, edit form with file upload and model assignment
- Admin articles pages: list, create form, edit form with split-pane markdown editor
- Add admin nav link in header (desktop + mobile) for admin users
- Render article content through marked in magazine detail page
- Add all admin GraphQL service functions to services.ts
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-06 12:31:33 +01:00
|
|
|
const updates: Record<string, unknown> = { date_updated: new Date() };
|
|
|
|
|
if (args.title !== undefined && args.title !== null) updates.title = args.title;
|
|
|
|
|
if (args.slug !== undefined && args.slug !== null) updates.slug = args.slug;
|
|
|
|
|
if (args.excerpt !== undefined) updates.excerpt = args.excerpt;
|
|
|
|
|
if (args.content !== undefined) updates.content = args.content;
|
|
|
|
|
if (args.imageId !== undefined) updates.image = args.imageId;
|
2026-03-06 16:31:41 +01:00
|
|
|
if (args.authorId !== undefined) updates.author = args.authorId;
|
feat: role-based ACL + admin management UI
Backend:
- Add acl.ts with requireAuth/requireRole/requireOwnerOrAdmin helpers
- Gate premium videos from unauthenticated users in videos query/resolver
- Fix updateVideoPlay to verify ownership before updating
- Add admin mutations: adminListUsers, adminUpdateUser, adminDeleteUser
- Add admin mutations: createVideo, updateVideo, deleteVideo, setVideoModels, adminListVideos
- Add admin mutations: createArticle, updateArticle, deleteArticle, adminListArticles
- Add deleteComment mutation (owner or admin only)
- Add AdminUserListType to GraphQL types
- Fix featured filter on articles query
Frontend:
- Install marked for markdown rendering
- Add /admin/* section with sidebar layout and admin-only guard
- Admin users page: paginated table with search, role filter, inline role change, delete
- Admin videos pages: list, create form, edit form with file upload and model assignment
- Admin articles pages: list, create form, edit form with split-pane markdown editor
- Add admin nav link in header (desktop + mobile) for admin users
- Render article content through marked in magazine detail page
- Add all admin GraphQL service functions to services.ts
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-06 12:31:33 +01:00
|
|
|
if (args.tags !== undefined && args.tags !== null) updates.tags = args.tags;
|
|
|
|
|
if (args.category !== undefined) updates.category = args.category;
|
|
|
|
|
if (args.featured !== undefined && args.featured !== null) updates.featured = args.featured;
|
|
|
|
|
if (args.publishDate !== undefined && args.publishDate !== null)
|
|
|
|
|
updates.publish_date = new Date(args.publishDate);
|
|
|
|
|
|
|
|
|
|
const updated = await ctx.db
|
|
|
|
|
.update(articles)
|
2026-03-07 19:25:04 +01:00
|
|
|
.set(updates as Partial<typeof articles.$inferInsert>)
|
feat: role-based ACL + admin management UI
Backend:
- Add acl.ts with requireAuth/requireRole/requireOwnerOrAdmin helpers
- Gate premium videos from unauthenticated users in videos query/resolver
- Fix updateVideoPlay to verify ownership before updating
- Add admin mutations: adminListUsers, adminUpdateUser, adminDeleteUser
- Add admin mutations: createVideo, updateVideo, deleteVideo, setVideoModels, adminListVideos
- Add admin mutations: createArticle, updateArticle, deleteArticle, adminListArticles
- Add deleteComment mutation (owner or admin only)
- Add AdminUserListType to GraphQL types
- Fix featured filter on articles query
Frontend:
- Install marked for markdown rendering
- Add /admin/* section with sidebar layout and admin-only guard
- Admin users page: paginated table with search, role filter, inline role change, delete
- Admin videos pages: list, create form, edit form with file upload and model assignment
- Admin articles pages: list, create form, edit form with split-pane markdown editor
- Add admin nav link in header (desktop + mobile) for admin users
- Render article content through marked in magazine detail page
- Add all admin GraphQL service functions to services.ts
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-06 12:31:33 +01:00
|
|
|
.where(eq(articles.id, args.id))
|
|
|
|
|
.returning();
|
|
|
|
|
if (!updated[0]) return null;
|
|
|
|
|
return enrichArticle(ctx.db, updated[0]);
|
|
|
|
|
},
|
|
|
|
|
}),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
builder.mutationField("deleteArticle", (t) =>
|
|
|
|
|
t.field({
|
|
|
|
|
type: "Boolean",
|
|
|
|
|
args: {
|
|
|
|
|
id: t.arg.string({ required: true }),
|
|
|
|
|
},
|
|
|
|
|
resolve: async (_root, args, ctx) => {
|
2026-03-06 16:14:00 +01:00
|
|
|
requireAdmin(ctx);
|
feat: role-based ACL + admin management UI
Backend:
- Add acl.ts with requireAuth/requireRole/requireOwnerOrAdmin helpers
- Gate premium videos from unauthenticated users in videos query/resolver
- Fix updateVideoPlay to verify ownership before updating
- Add admin mutations: adminListUsers, adminUpdateUser, adminDeleteUser
- Add admin mutations: createVideo, updateVideo, deleteVideo, setVideoModels, adminListVideos
- Add admin mutations: createArticle, updateArticle, deleteArticle, adminListArticles
- Add deleteComment mutation (owner or admin only)
- Add AdminUserListType to GraphQL types
- Fix featured filter on articles query
Frontend:
- Install marked for markdown rendering
- Add /admin/* section with sidebar layout and admin-only guard
- Admin users page: paginated table with search, role filter, inline role change, delete
- Admin videos pages: list, create form, edit form with file upload and model assignment
- Admin articles pages: list, create form, edit form with split-pane markdown editor
- Add admin nav link in header (desktop + mobile) for admin users
- Render article content through marked in magazine detail page
- Add all admin GraphQL service functions to services.ts
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-06 12:31:33 +01:00
|
|
|
await ctx.db.delete(articles).where(eq(articles.id, args.id));
|
|
|
|
|
return true;
|
2026-03-04 18:07:18 +01:00
|
|
|
},
|
|
|
|
|
}),
|
|
|
|
|
);
|