diff --git a/README.md b/README.md index 7f465ff..5889d61 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ npm run db-fill-clean Now open the [.env](web/.env) file and fill in the missing values. -> Default users are created with tokens: `admin`, `verifier`, `verified`, `normal` (configurable via env vars) +> Default users are created with tokens: `admin`, `moderator`, `verified`, `normal` (configurable via env vars) ### Running the project diff --git a/web/README.md b/web/README.md index adef8cd..3d80ca6 100644 --- a/web/README.md +++ b/web/README.md @@ -26,4 +26,4 @@ All commands are run from the root of the project, from a terminal: > **Note**: `db-fill` and `db-fill-clean` support the `-- --services=n` flag, where n is the number of fake services to add. It defaults to 10. For example, `npm run db-fill -- --services=5` will add 5 fake services. -> **Note**: `db-fill` and `db-fill-clean` create default users with tokens: `admin`, `verifier`, `verified`, `normal` (override with `DEV_*****_USER_SECRET_TOKEN` env vars) +> **Note**: `db-fill` and `db-fill-clean` create default users with tokens: `admin`, `moderator`, `verified`, `normal` (override with `DEV_*****_USER_SECRET_TOKEN` env vars) diff --git a/web/astro.config.mjs b/web/astro.config.mjs index eb228e8..d7f293a 100644 --- a/web/astro.config.mjs +++ b/web/astro.config.mjs @@ -131,11 +131,11 @@ export default defineConfig({ min: 1, default: 'admin', }), - DEV_VERIFIER_USER_SECRET_TOKEN: envField.string({ + DEV_MODERATOR_USER_SECRET_TOKEN: envField.string({ context: 'server', access: 'secret', min: 1, - default: 'verifier', + default: 'moderator', }), DEV_VERIFIED_USER_SECRET_TOKEN: envField.string({ context: 'server', diff --git a/web/prisma/migrations/20250523181004_rename_migration/migration.sql b/web/prisma/migrations/20250523181004_rename_migration/migration.sql new file mode 100644 index 0000000..f2cedfd --- /dev/null +++ b/web/prisma/migrations/20250523181004_rename_migration/migration.sql @@ -0,0 +1,13 @@ +/* + Manully edited to be a rename migration. +*/ +-- AlterEnum +BEGIN; +ALTER TYPE "AccountStatusChange" RENAME VALUE 'VERIFIER_TRUE' TO 'MODERATOR_TRUE'; +ALTER TYPE "AccountStatusChange" RENAME VALUE 'VERIFIER_FALSE' TO 'MODERATOR_FALSE'; +COMMIT; + +-- AlterTable +ALTER TABLE "User" +RENAME COLUMN "verifier" TO "moderator" + diff --git a/web/prisma/schema.prisma b/web/prisma/schema.prisma index 94dc7c6..eb17ec9 100644 --- a/web/prisma/schema.prisma +++ b/web/prisma/schema.prisma @@ -120,8 +120,8 @@ enum AccountStatusChange { ADMIN_FALSE VERIFIED_TRUE VERIFIED_FALSE - VERIFIER_TRUE - VERIFIER_FALSE + MODERATOR_TRUE + MODERATOR_FALSE SPAMMER_TRUE SPAMMER_FALSE } @@ -464,7 +464,7 @@ model User { spammer Boolean @default(false) verified Boolean @default(false) admin Boolean @default(false) - verifier Boolean @default(false) + moderator Boolean @default(false) verifiedLink String? secretTokenHash String @unique /// Computed via trigger. Do not update through prisma. diff --git a/web/prisma/triggers/10_notifications_user_status_change.sql b/web/prisma/triggers/10_notifications_user_status_change.sql index d968880..3a25991 100644 --- a/web/prisma/triggers/10_notifications_user_status_change.sql +++ b/web/prisma/triggers/10_notifications_user_status_change.sql @@ -25,12 +25,12 @@ BEGIN VALUES (NEW.id, 'ACCOUNT_STATUS_CHANGE', status_change); END IF; - -- Check for verifier status change - IF OLD.verifier IS DISTINCT FROM NEW.verifier THEN - IF NEW.verifier = true THEN - status_change := 'VERIFIER_TRUE'; + -- Check for moderator status change + IF OLD.moderator IS DISTINCT FROM NEW.moderator THEN + IF NEW.moderator = true THEN + status_change := 'MODERATOR_TRUE'; ELSE - status_change := 'VERIFIER_FALSE'; + status_change := 'MODERATOR_FALSE'; END IF; INSERT INTO "Notification" ("userId", "type", "aboutAccountStatusChange") VALUES (NEW.id, 'ACCOUNT_STATUS_CHANGE', status_change); @@ -57,6 +57,6 @@ DROP TRIGGER IF EXISTS user_status_change_notifications_trigger ON "User"; -- Create the trigger to fire after updates on specific status columns CREATE TRIGGER user_status_change_notifications_trigger - AFTER UPDATE OF admin, verified, verifier, spammer ON "User" + AFTER UPDATE OF admin, verified, moderator, spammer ON "User" FOR EACH ROW EXECUTE FUNCTION trigger_user_status_change_notifications(); diff --git a/web/scripts/faker.ts b/web/scripts/faker.ts index 7fbc39d..20937de 100755 --- a/web/scripts/faker.ts +++ b/web/scripts/faker.ts @@ -85,7 +85,7 @@ async function createAccount(preGeneratedToken?: string) { verifiedLink, verified: !!verifiedLink, admin: faker.datatype.boolean({ probability: 0.1 }), - verifier: faker.datatype.boolean({ probability: 0.1 }), + moderator: faker.datatype.boolean({ probability: 0.1 }), }, include: { serviceAffiliations: true, @@ -899,19 +899,19 @@ const specialUsersData = { envToken: 'DEV_ADMIN_USER_SECRET_TOKEN', defaultToken: 'admin', admin: true, - verifier: true, + moderator: true, verified: true, verifiedLink: 'https://kycnot.me', totalKarma: 1001, link: 'https://kycnot.me', picture: 'https://comments.kycnot.me/api/users/549f290e-0542-4c18-b437-5b64b35758f0/avatar?size=L', }, - verifier: { - name: 'verifier_dev', - envToken: 'DEV_VERIFIER_USER_SECRET_TOKEN', - defaultToken: 'verifier', + moderator: { + name: 'moderator_dev', + envToken: 'DEV_MODERATOR_USER_SECRET_TOKEN', + defaultToken: 'moderator', admin: false, - verifier: true, + moderator: true, verified: true, verifiedLink: 'https://kycnot.me', totalKarma: 1001, @@ -923,7 +923,7 @@ const specialUsersData = { envToken: 'DEV_VERIFIED_USER_SECRET_TOKEN', defaultToken: 'verified', admin: false, - verifier: false, + moderator: false, verified: true, verifiedLink: 'https://kycnot.me', totalKarma: 1001, @@ -933,7 +933,7 @@ const specialUsersData = { envToken: 'DEV_NORMAL_USER_SECRET_TOKEN', defaultToken: 'normal', admin: false, - verifier: false, + moderator: false, verified: false, }, spam: { @@ -941,7 +941,7 @@ const specialUsersData = { envToken: 'DEV_SPAM_USER_SECRET_TOKEN', defaultToken: 'spam', admin: false, - verifier: false, + moderator: false, verified: false, totalKarma: -100, spammer: true, @@ -1306,7 +1306,7 @@ async function runFaker() { tx.internalUserNote.create({ data: generateFakeInternalNote( user.id, - faker.helpers.arrayElement([specialUsers.admin.id, specialUsers.verifier.id]) + faker.helpers.arrayElement([specialUsers.admin.id, specialUsers.moderator.id]) ), }) ) @@ -1323,7 +1323,7 @@ async function runFaker() { tx.internalUserNote.create({ data: generateFakeInternalNote( user.id, - faker.helpers.arrayElement([specialUsers.admin.id, specialUsers.verifier.id]) + faker.helpers.arrayElement([specialUsers.admin.id, specialUsers.moderator.id]) ), }) ) diff --git a/web/src/actions/admin/user.ts b/web/src/actions/admin/user.ts index 8a60840..0d70da5 100644 --- a/web/src/actions/admin/user.ts +++ b/web/src/actions/admin/user.ts @@ -16,7 +16,7 @@ const selectUserReturnFields = { picture: true, admin: true, verified: true, - verifier: true, + moderator: true, verifiedLink: true, secretTokenHash: true, totalKarma: true, @@ -55,7 +55,7 @@ export const adminUserActions = { .default(null) // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing .transform((val) => val || null), pictureFile: z.instanceof(File).optional(), - type: z.array(z.enum(['admin', 'verifier', 'spammer'])), + type: z.array(z.enum(['admin', 'moderator', 'spammer'])), verifiedLink: z .string() .url('Invalid URL') @@ -101,7 +101,7 @@ export const adminUserActions = { verified: !!valuesToUpdate.verifiedLink, picture: pictureUrl, admin: type.includes('admin'), - verifier: type.includes('verifier'), + moderator: type.includes('moderator'), spammer: type.includes('spammer'), }, select: selectUserReturnFields, diff --git a/web/src/actions/comment.ts b/web/src/actions/comment.ts index cedabc7..b2b0b1f 100644 --- a/web/src/actions/comment.ts +++ b/web/src/actions/comment.ts @@ -331,7 +331,7 @@ export const commentActions = { }), moderate: defineProtectedAction({ - permissions: ['admin', 'verifier'], + permissions: ['admin', 'moderator'], input: z.object({ commentId: z.number(), userId: z.number(), diff --git a/web/src/components/CommentItem.astro b/web/src/components/CommentItem.astro index bb04d10..9ae825a 100644 --- a/web/src/components/CommentItem.astro +++ b/web/src/components/CommentItem.astro @@ -61,8 +61,8 @@ const isHighlighted = comment.id === highlightedCommentId const userVote = user ? comment.votes.find((v) => v.userId === user.id) : null const isAuthor = user?.id === comment.author.id -const isAdminOrVerifier = !!user && (user.admin || user.verifier) -const isAuthorOrPrivileged = isAuthor || isAdminOrVerifier +const isAdminOrModerator = !!user && (user.admin || user.moderator) +const isAuthorOrPrivileged = isAuthor || isAdminOrModerator // Check if user is new (less than 1 week old) const isNewUser = @@ -75,7 +75,7 @@ const isRatingActive = !comment.suspicious && (comment.status === 'APPROVED' || comment.status === 'VERIFIED') -// Skip rendering if comment is not approved/verified and user is not the author or admin/verifier +// Skip rendering if comment is not approved/verified and user is not the author or admin/moderator const shouldShow = comment.status === 'APPROVED' || comment.status === 'VERIFIED' || @@ -164,10 +164,10 @@ const commentUrl = makeCommentUrl({ serviceSlug, commentId: comment.id, origin: /> { - (comment.author.verified || comment.author.admin || comment.author.verifier) && ( + (comment.author.verified || comment.author.admin || comment.author.moderator) && ( ) } { - authorUnlocks.highKarmaBadge && !comment.author.admin && !comment.author.verifier && ( + authorUnlocks.highKarmaBadge && !comment.author.admin && !comment.author.moderator && (
Internal note: @@ -391,7 +391,7 @@ const commentUrl = makeCommentUrl({ serviceSlug, commentId: comment.id, origin: } { - user && (user.admin || user.verifier) && comment.privateContext && ( + user && (user.admin || user.moderator) && comment.privateContext && (
Private context: diff --git a/web/src/components/CommentModeration.astro b/web/src/components/CommentModeration.astro index 91e8be9..4d8eeb3 100644 --- a/web/src/components/CommentModeration.astro +++ b/web/src/components/CommentModeration.astro @@ -28,8 +28,8 @@ const { comment, class: className, ...divProps } = Astro.props const user = Astro.locals.user -// Only render for admin/verifier users -if (!user || !user.admin || !user.verifier) return null +// Only render for admin/moderator users +if (!user || !user.admin || !user.moderator) return null ---
diff --git a/web/src/components/CommentSection.astro b/web/src/components/CommentSection.astro index bddf8db..49422f0 100644 --- a/web/src/components/CommentSection.astro +++ b/web/src/components/CommentSection.astro @@ -202,7 +202,7 @@ function makeReplySchema(comment: CommentWithRepliesPopulated): Comment { Most Upvotes { - user && (user.admin || user.verifier) && ( + user && (user.admin || user.moderator) && ( { defaultToken: 'admin', }, { - envToken: 'DEV_VERIFIER_USER_SECRET_TOKEN', - defaultToken: 'verifier', + envToken: 'DEV_MODERATOR_USER_SECRET_TOKEN', + defaultToken: 'moderator', }, { envToken: 'DEV_VERIFIED_USER_SECRET_TOKEN', diff --git a/web/src/pages/account/index.astro b/web/src/pages/account/index.astro index 0f6dcd2..8c479d7 100644 --- a/web/src/pages/account/index.astro +++ b/web/src/pages/account/index.astro @@ -44,7 +44,7 @@ const user = await Astro.locals.banners.try('user', async () => { spammer: true, verified: true, admin: true, - verifier: true, + moderator: true, verifiedLink: true, totalKarma: true, createdAt: true, @@ -206,7 +206,7 @@ if (!user) return Astro.rewrite('/404') {user.displayName &&

{user.name}

} { - (user.admin || user.verified || user.verifier) && ( + (user.admin || user.verified || user.moderator) && (
{user.admin && ( @@ -218,9 +218,9 @@ if (!user) return Astro.rewrite('/404') verified )} - {user.verifier && ( + {user.moderator && ( - verifier + moderator )}
@@ -356,14 +356,14 @@ if (!user) return Astro.rewrite('/404') ) } { - user.verifier && ( + user.moderator && ( - Verifier + Moderator ) } { - !user.admin && !user.verified && !user.verifier && ( + !user.admin && !user.verified && !user.moderator && ( Standard User diff --git a/web/src/pages/admin/comments.astro b/web/src/pages/admin/comments.astro index 96a4a07..ae6fec8 100644 --- a/web/src/pages/admin/comments.astro +++ b/web/src/pages/admin/comments.astro @@ -20,7 +20,7 @@ import { prisma } from '../../lib/prisma' import { urlWithParams } from '../../lib/urls' const user = Astro.locals.user -if (!user || (!user.admin && !user.verifier)) { +if (!user || (!user.admin && !user.moderator)) { return Astro.rewrite('/404') } diff --git a/web/src/pages/admin/index.astro b/web/src/pages/admin/index.astro index 49592dc..151a152 100644 --- a/web/src/pages/admin/index.astro +++ b/web/src/pages/admin/index.astro @@ -9,7 +9,6 @@ type AdminLink = { icon: ComponentProps['name'] title: string href: string - description: string } const adminLinks: AdminLink[] = [ @@ -17,37 +16,36 @@ const adminLinks: AdminLink[] = [ icon: 'ri:box-3-line', title: 'Services', href: '/admin/services', - description: 'Manage your available services', }, { icon: 'ri:file-list-3-line', title: 'Attributes', href: '/admin/attributes', - description: 'Configure service attributes', }, { icon: 'ri:user-3-line', title: 'Users', href: '/admin/users', - description: 'Manage user accounts', }, { icon: 'ri:chat-settings-line', title: 'Comments', href: '/admin/comments', - description: 'Moderate user comments', }, { icon: 'ri:lightbulb-line', title: 'Service suggestions', href: '/admin/service-suggestions', - description: 'Review and manage service suggestions', }, { icon: 'ri:megaphone-line', title: 'Announcements', href: '/admin/announcements', - description: 'Manage site announcements', + }, + { + icon: 'ri:database-2-line', + title: 'Database', + href: 'https://db.kycnot.me', }, ] --- @@ -58,23 +56,17 @@ const adminLinks: AdminLink[] = [ Admin Dashboard -
+
{ adminLinks.map((link) => ( -
- -

- {link.title} -

-
-

{link.description}

+ + + {link.title} +
)) } diff --git a/web/src/pages/admin/users/[username].astro b/web/src/pages/admin/users/[username].astro index 5f843a4..680b1ea 100644 --- a/web/src/pages/admin/users/[username].astro +++ b/web/src/pages/admin/users/[username].astro @@ -52,7 +52,7 @@ const [user, allServices] = await Astro.locals.banners.tryMany([ link: true, admin: true, verified: true, - verifier: true, + moderator: true, spammer: true, verifiedLink: true, internalNotes: { @@ -141,7 +141,7 @@ if (!user) return Astro.rewrite('/404')
{user.admin && } {user.verified && } - {user.verifier && } + {user.moderator && } {user.spammer && }
@@ -226,7 +226,7 @@ if (!user) return Astro.rewrite('/404') label="Type" options={[ { label: 'Admin', value: 'admin', icon: 'ri:shield-star-fill' }, - { label: 'Moderator', value: 'verifier', icon: 'ri:graduation-cap-fill' }, + { label: 'Moderator', value: 'moderator', icon: 'ri:graduation-cap-fill' }, { label: 'Spammer', value: 'spammer', icon: 'ri:alert-fill' }, { label: 'Verified', @@ -239,7 +239,7 @@ if (!user) return Astro.rewrite('/404') selectedValue={[ user.admin ? 'admin' : null, user.verified ? 'verified' : null, - user.verifier ? 'verifier' : null, + user.moderator ? 'moderator' : null, user.spammer ? 'spammer' : null, ].filter((v) => v !== null)} required diff --git a/web/src/pages/admin/users/index.astro b/web/src/pages/admin/users/index.astro index 8e8ccc2..197487b 100644 --- a/web/src/pages/admin/users/index.astro +++ b/web/src/pages/admin/users/index.astro @@ -21,7 +21,7 @@ const { data: filters } = zodParseQueryParamsStoringErrors( 'sort-by': z.enum(['name', 'role', 'createdAt', 'karma']).default('createdAt'), 'sort-order': z.enum(['asc', 'desc']).default('desc'), search: z.string().optional(), - role: z.enum(['user', 'admin', 'verifier', 'verified', 'spammer']).optional(), + role: z.enum(['user', 'admin', 'moderator', 'verified', 'spammer']).optional(), }, Astro ) @@ -46,7 +46,7 @@ if (filters.role) { switch (filters.role) { case 'user': { whereClause.admin = false - whereClause.verifier = false + whereClause.moderator = false whereClause.verified = false whereClause.spammer = false break @@ -55,8 +55,8 @@ if (filters.role) { whereClause.admin = true break } - case 'verifier': { - whereClause.verifier = true + case 'moderator': { + whereClause.moderator = true break } case 'verified': { @@ -80,7 +80,7 @@ const dbUsers = await prisma.user.findMany({ picture: true, verified: true, admin: true, - verifier: true, + moderator: true, spammer: true, totalKarma: true, createdAt: true, @@ -147,7 +147,7 @@ const makeSortUrl = (sortBy: NonNullable<(typeof filters)['sort-by']>) => { - + @@ -277,10 +277,10 @@ const makeSortUrl = (sortBy: NonNullable<(typeof filters)['sort-by']>) => { Verified )} - {user.verifier && ( + {user.moderator && ( - Verifier + Moderator )}
diff --git a/web/src/pages/u/[username].astro b/web/src/pages/u/[username].astro index aaee6f5..15ed981 100644 --- a/web/src/pages/u/[username].astro +++ b/web/src/pages/u/[username].astro @@ -43,7 +43,7 @@ const user = await Astro.locals.banners.try('user', async () => { spammer: true, verified: true, admin: true, - verifier: true, + moderator: true, verifiedLink: true, totalKarma: true, createdAt: true, @@ -208,7 +208,7 @@ const isCurrentUser = !!Astro.locals.user && user.id === Astro.locals.user.id sameAs: user.link ? [user.link] : undefined, description: `User profile page for ${user.displayName ?? user.name} on KYCnot.me`, identifier: [user.name, user.id.toString()], - jobTitle: user.admin ? 'Administrator' : user.verifier ? 'Moderator' : undefined, + jobTitle: user.admin ? 'Administrator' : user.moderator ? 'Moderator' : undefined, memberOf: KYCNOTME_SCHEMA_MINI, interactionStatistic: [ { @@ -281,9 +281,9 @@ const isCurrentUser = !!Astro.locals.user && user.id === Astro.locals.user.id ) } { - user.verifier && ( + user.moderator && ( - verifier + moderator ) } @@ -415,14 +415,14 @@ const isCurrentUser = !!Astro.locals.user && user.id === Astro.locals.user.id ) } { - user.verifier && ( + user.moderator && ( - Verifier + Moderator ) } { - !user.admin && !user.verified && !user.verifier && ( + !user.admin && !user.verified && !user.moderator && ( Standard User