feat: refactor role system to is_admin flag, add Badge component, fix native dialogs

- Separate admin identity from role: viewer|model + is_admin boolean flag
- DB migration 0001_is_admin: adds column, migrates former admin role users
- Update ACL helpers, auth session, GraphQL types and all resolvers
- Admin layout guard and header links check is_admin instead of role
- Admin users table: show Admin badge next to name, remove admin from role select
- Admin user edit page: is_admin checkbox toggle
- Install shadcn Badge component; use in admin users table
- Fix duplicate photo keys in adminGetUser resolver
- Replace confirm() in /me recordings with Dialog component

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-06 16:14:00 +01:00
parent 9ef490c1e5
commit 670c18bcb7
19 changed files with 162 additions and 43 deletions

View File

@@ -2,7 +2,7 @@ import { builder } from "../builder";
import { ArticleType } from "../types/index";
import { articles, users } from "../../db/schema/index";
import { eq, and, lte, desc } from "drizzle-orm";
import { requireRole } from "../../lib/acl";
import { requireAdmin } from "../../lib/acl";
async function enrichArticle(db: any, article: any) {
let author = null;
@@ -78,7 +78,7 @@ builder.queryField("adminListArticles", (t) =>
t.field({
type: [ArticleType],
resolve: async (_root, _args, ctx) => {
requireRole(ctx, "admin");
requireAdmin(ctx);
const articleList = await ctx.db.select().from(articles).orderBy(desc(articles.publish_date));
return Promise.all(articleList.map((article: any) => enrichArticle(ctx.db, article)));
},
@@ -100,7 +100,7 @@ builder.mutationField("createArticle", (t) =>
publishDate: t.arg.string(),
},
resolve: async (_root, args, ctx) => {
requireRole(ctx, "admin");
requireAdmin(ctx);
const inserted = await ctx.db
.insert(articles)
.values({
@@ -138,7 +138,7 @@ builder.mutationField("updateArticle", (t) =>
publishDate: t.arg.string(),
},
resolve: async (_root, args, ctx) => {
requireRole(ctx, "admin");
requireAdmin(ctx);
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;
@@ -169,7 +169,7 @@ builder.mutationField("deleteArticle", (t) =>
id: t.arg.string({ required: true }),
},
resolve: async (_root, args, ctx) => {
requireRole(ctx, "admin");
requireAdmin(ctx);
await ctx.db.delete(articles).where(eq(articles.id, args.id));
return true;
},