From 08c03ac79bfdc317ccc255f30f4841089b1901d5 Mon Sep 17 00:00:00 2001 From: "kiloconnect[bot]" <240665456+kiloconnect[bot]@users.noreply.github.com> Date: Wed, 1 Apr 2026 16:40:48 +0000 Subject: [PATCH 1/6] feat(admin): add safety identifier backfill panel --- .../safety-identifiers/openrouter/route.ts | 50 ++++++ src/app/admin/api/safety-identifiers/route.ts | 31 ++++ .../api/safety-identifiers/vercel/route.ts | 38 +++++ src/app/admin/components/AppSidebar.tsx | 6 + .../components/SafetyIdentifiersBackfill.tsx | 146 ++++++++++++++++++ src/app/admin/safety-identifiers/page.tsx | 24 +++ 6 files changed, 295 insertions(+) create mode 100644 src/app/admin/api/safety-identifiers/openrouter/route.ts create mode 100644 src/app/admin/api/safety-identifiers/route.ts create mode 100644 src/app/admin/api/safety-identifiers/vercel/route.ts create mode 100644 src/app/admin/components/SafetyIdentifiersBackfill.tsx create mode 100644 src/app/admin/safety-identifiers/page.tsx diff --git a/src/app/admin/api/safety-identifiers/openrouter/route.ts b/src/app/admin/api/safety-identifiers/openrouter/route.ts new file mode 100644 index 0000000000..c0935e276a --- /dev/null +++ b/src/app/admin/api/safety-identifiers/openrouter/route.ts @@ -0,0 +1,50 @@ +import { NextResponse } from 'next/server'; +import { getUserFromAuth } from '@/lib/user.server'; +import { db } from '@/lib/drizzle'; +import { kilocode_users } from '@kilocode/db'; +import { generateOpenRouterUpstreamSafetyIdentifier } from '@/lib/providerHash'; +import { isNull, desc, eq } from 'drizzle-orm'; + +export type BackfillBatchResponse = { + processed: number; + remaining: boolean; +}; + +export async function POST(): Promise> { + const { authFailedResponse } = await getUserFromAuth({ adminOnly: true }); + if (authFailedResponse) return authFailedResponse; + + const processed = await db.transaction(async tran => { + const rows = await tran + .select({ id: kilocode_users.id }) + .from(kilocode_users) + .where(isNull(kilocode_users.openrouter_upstream_safety_identifier)) + .orderBy(desc(kilocode_users.created_at)) + .limit(1000); + + for (const user of rows) { + const openrouter_upstream_safety_identifier = generateOpenRouterUpstreamSafetyIdentifier( + user.id + ); + if (openrouter_upstream_safety_identifier === null) { + return -1; + } + await tran + .update(kilocode_users) + .set({ openrouter_upstream_safety_identifier }) + .where(eq(kilocode_users.id, user.id)) + .execute(); + } + + return rows.length; + }); + + if (processed === -1) { + return NextResponse.json( + { error: 'OPENROUTER_ORG_ID is not configured on this server' }, + { status: 500 } + ); + } + + return NextResponse.json({ processed, remaining: processed === 1000 }); +} diff --git a/src/app/admin/api/safety-identifiers/route.ts b/src/app/admin/api/safety-identifiers/route.ts new file mode 100644 index 0000000000..d250de75c2 --- /dev/null +++ b/src/app/admin/api/safety-identifiers/route.ts @@ -0,0 +1,31 @@ +import { NextResponse } from 'next/server'; +import { getUserFromAuth } from '@/lib/user.server'; +import { db } from '@/lib/drizzle'; +import { kilocode_users } from '@kilocode/db'; +import { isNull, count } from 'drizzle-orm'; + +export type SafetyIdentifierCountsResponse = { + openrouterMissing: number; + vercelMissing: number; +}; + +export async function GET(): Promise> { + const { authFailedResponse } = await getUserFromAuth({ adminOnly: true }); + if (authFailedResponse) return authFailedResponse; + + const [openrouterResult, vercelResult] = await Promise.all([ + db + .select({ count: count() }) + .from(kilocode_users) + .where(isNull(kilocode_users.openrouter_upstream_safety_identifier)), + db + .select({ count: count() }) + .from(kilocode_users) + .where(isNull(kilocode_users.vercel_downstream_safety_identifier)), + ]); + + return NextResponse.json({ + openrouterMissing: openrouterResult[0]?.count ?? 0, + vercelMissing: vercelResult[0]?.count ?? 0, + }); +} diff --git a/src/app/admin/api/safety-identifiers/vercel/route.ts b/src/app/admin/api/safety-identifiers/vercel/route.ts new file mode 100644 index 0000000000..90d621b694 --- /dev/null +++ b/src/app/admin/api/safety-identifiers/vercel/route.ts @@ -0,0 +1,38 @@ +import { NextResponse } from 'next/server'; +import { getUserFromAuth } from '@/lib/user.server'; +import { db } from '@/lib/drizzle'; +import { kilocode_users } from '@kilocode/db'; +import { generateVercelDownstreamSafetyIdentifier } from '@/lib/providerHash'; +import { isNull, desc, eq } from 'drizzle-orm'; + +export type BackfillBatchResponse = { + processed: number; + remaining: boolean; +}; + +export async function POST(): Promise> { + const { authFailedResponse } = await getUserFromAuth({ adminOnly: true }); + if (authFailedResponse) return authFailedResponse; + + const processed = await db.transaction(async tran => { + const rows = await tran + .select({ id: kilocode_users.id }) + .from(kilocode_users) + .where(isNull(kilocode_users.vercel_downstream_safety_identifier)) + .orderBy(desc(kilocode_users.created_at)) + .limit(1000); + + for (const user of rows) { + const vercel_downstream_safety_identifier = generateVercelDownstreamSafetyIdentifier(user.id); + await tran + .update(kilocode_users) + .set({ vercel_downstream_safety_identifier }) + .where(eq(kilocode_users.id, user.id)) + .execute(); + } + + return rows.length; + }); + + return NextResponse.json({ processed, remaining: processed === 1000 }); +} diff --git a/src/app/admin/components/AppSidebar.tsx b/src/app/admin/components/AppSidebar.tsx index 0fc8480f80..8eddc3788e 100644 --- a/src/app/admin/components/AppSidebar.tsx +++ b/src/app/admin/components/AppSidebar.tsx @@ -23,6 +23,7 @@ import { Bell, Server, Network, + KeyRound, } from 'lucide-react'; import { useSession } from 'next-auth/react'; import type { Session } from 'next-auth'; @@ -79,6 +80,11 @@ const userManagementItems: MenuItem[] = [ url: '/admin/blacklisted-domains', icon: () => , }, + { + title: () => 'Safety Identifiers', + url: '/admin/safety-identifiers', + icon: () => , + }, ]; const financialItems: MenuItem[] = [ diff --git a/src/app/admin/components/SafetyIdentifiersBackfill.tsx b/src/app/admin/components/SafetyIdentifiersBackfill.tsx new file mode 100644 index 0000000000..d751197496 --- /dev/null +++ b/src/app/admin/components/SafetyIdentifiersBackfill.tsx @@ -0,0 +1,146 @@ +'use client'; + +import { useState } from 'react'; +import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; +import { Button } from '@/components/ui/button'; +import { Alert, AlertDescription } from '@/components/ui/alert'; +import { Badge } from '@/components/ui/badge'; +import type { SafetyIdentifierCountsResponse } from '../api/safety-identifiers/route'; +import type { BackfillBatchResponse } from '../api/safety-identifiers/openrouter/route'; + +type BatchLog = { + provider: string; + processed: number; + timestamp: Date; +}; + +function useBackfillMutation(provider: 'openrouter' | 'vercel', onBatch: (log: BatchLog) => void) { + const queryClient = useQueryClient(); + return useMutation({ + mutationFn: async () => { + const res = await fetch(`/admin/api/safety-identifiers/${provider}`, { method: 'POST' }); + if (!res.ok) { + const body = (await res.json()) as { error?: string }; + throw new Error(body.error ?? `HTTP ${res.status}`); + } + return res.json() as Promise; + }, + onSuccess: data => { + onBatch({ provider, processed: data.processed, timestamp: new Date() }); + void queryClient.invalidateQueries({ queryKey: ['safety-identifier-counts'] }); + }, + }); +} + +type ProviderCardProps = { + title: string; + description: string; + missing: number | undefined; + isLoading: boolean; + onBatch: (log: BatchLog) => void; + provider: 'openrouter' | 'vercel'; +}; + +function ProviderCard({ title, description, missing, isLoading, onBatch, provider }: ProviderCardProps) { + const mutation = useBackfillMutation(provider, onBatch); + const isDone = missing === 0; + + return ( +
+
+
+

{title}

+

{description}

+
+
+ {isLoading ? ( + Loading… + ) : isDone ? ( + All filled + ) : ( + {(missing ?? 0).toLocaleString()} missing + )} +
+
+ + {mutation.isError && ( + + {mutation.error.message} + + )} + + +
+ ); +} + +export function SafetyIdentifiersBackfill() { + const [logs, setLogs] = useState([]); + + const { data: counts, isLoading } = useQuery({ + queryKey: ['safety-identifier-counts'], + queryFn: async () => { + const res = await fetch('/admin/api/safety-identifiers'); + return res.json() as Promise; + }, + refetchInterval: false, + }); + + function addLog(log: BatchLog) { + setLogs(prev => [log, ...prev]); + } + + return ( +
+

+ Backfill safety identifiers for users that were created before these fields were + introduced. Each button processes up to 1 000 users per click. Click repeatedly + (or rapidly) until the counter reaches zero. +

+ +
+ + +
+ + {logs.length > 0 && ( +
+

Batch log

+
+ {logs.map((log, i) => ( +
+ {log.timestamp.toLocaleTimeString()} + [{log.provider}] + processed {log.processed.toLocaleString()} users +
+ ))} +
+
+ )} +
+ ); +} diff --git a/src/app/admin/safety-identifiers/page.tsx b/src/app/admin/safety-identifiers/page.tsx new file mode 100644 index 0000000000..f0ed5dbb0f --- /dev/null +++ b/src/app/admin/safety-identifiers/page.tsx @@ -0,0 +1,24 @@ +import { SafetyIdentifiersBackfill } from '../components/SafetyIdentifiersBackfill'; +import AdminPage from '../components/AdminPage'; +import { BreadcrumbItem, BreadcrumbPage } from '@/components/ui/breadcrumb'; + +const breadcrumbs = ( + <> + + Safety Identifiers + + +); + +export default function SafetyIdentifiersPage() { + return ( + +
+
+

Safety Identifier Backfill

+
+ +
+
+ ); +} From b42b00abc542298753d2ec74c58a4a1a6cb3df5a Mon Sep 17 00:00:00 2001 From: "kiloconnect[bot]" <240665456+kiloconnect[bot]@users.noreply.github.com> Date: Wed, 1 Apr 2026 19:34:01 +0000 Subject: [PATCH 2/6] feat(admin): show Vercel Downstream Safety Identifier in user admin UI --- .../components/UserAdmin/UserAdminAccountInfo.tsx | 15 +++++++++++++++ src/lib/user.test.ts | 2 ++ 2 files changed, 17 insertions(+) diff --git a/src/app/admin/components/UserAdmin/UserAdminAccountInfo.tsx b/src/app/admin/components/UserAdmin/UserAdminAccountInfo.tsx index 3566922e28..f60db21738 100644 --- a/src/app/admin/components/UserAdmin/UserAdminAccountInfo.tsx +++ b/src/app/admin/components/UserAdmin/UserAdminAccountInfo.tsx @@ -108,6 +108,21 @@ export function UserAdminAccountInfo(user: UserAdminAccountInfoProps) {

N/A

)} +
+

+ Vercel Downstream Safety Identifier +

+ {user.vercel_downstream_safety_identifier ? ( +
+

+ {user.vercel_downstream_safety_identifier} +

+ +
+ ) : ( +

N/A

+ )} +
diff --git a/src/lib/user.test.ts b/src/lib/user.test.ts index 04109844ee..612a005dc0 100644 --- a/src/lib/user.test.ts +++ b/src/lib/user.test.ts @@ -92,6 +92,7 @@ describe('User', () => { linkedin_url: 'https://linkedin.com/in/testuser', github_url: 'https://github.com/testuser', openrouter_upstream_safety_identifier: 'openrouter_upstream_safety_identifier', + vercel_downstream_safety_identifier: 'vercel_downstream_safety_identifier', customer_source: 'A YouTube video', is_admin: true, }); @@ -108,6 +109,7 @@ describe('User', () => { expect(softDeleted!.github_url).toBeNull(); expect(softDeleted!.discord_server_membership_verified_at).toBeNull(); expect(softDeleted!.openrouter_upstream_safety_identifier).toBeNull(); + expect(softDeleted!.vercel_downstream_safety_identifier).toBeNull(); expect(softDeleted!.customer_source).toBeNull(); expect(softDeleted!.api_token_pepper).toBeNull(); expect(softDeleted!.default_model).toBeNull(); From 49d9b4a0a2c4222b41698308d48e48da319c7655 Mon Sep 17 00:00:00 2001 From: "kiloconnect[bot]" <240665456+kiloconnect[bot]@users.noreply.github.com> Date: Wed, 1 Apr 2026 19:50:01 +0000 Subject: [PATCH 3/6] refactor(admin): merge safety identifier backfill into single API call --- .../safety-identifiers/openrouter/route.ts | 50 ----- src/app/admin/api/safety-identifiers/route.ts | 90 ++++++++- .../api/safety-identifiers/vercel/route.ts | 38 ---- .../components/SafetyIdentifiersBackfill.tsx | 181 ++++++++---------- 4 files changed, 171 insertions(+), 188 deletions(-) delete mode 100644 src/app/admin/api/safety-identifiers/openrouter/route.ts delete mode 100644 src/app/admin/api/safety-identifiers/vercel/route.ts diff --git a/src/app/admin/api/safety-identifiers/openrouter/route.ts b/src/app/admin/api/safety-identifiers/openrouter/route.ts deleted file mode 100644 index c0935e276a..0000000000 --- a/src/app/admin/api/safety-identifiers/openrouter/route.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { NextResponse } from 'next/server'; -import { getUserFromAuth } from '@/lib/user.server'; -import { db } from '@/lib/drizzle'; -import { kilocode_users } from '@kilocode/db'; -import { generateOpenRouterUpstreamSafetyIdentifier } from '@/lib/providerHash'; -import { isNull, desc, eq } from 'drizzle-orm'; - -export type BackfillBatchResponse = { - processed: number; - remaining: boolean; -}; - -export async function POST(): Promise> { - const { authFailedResponse } = await getUserFromAuth({ adminOnly: true }); - if (authFailedResponse) return authFailedResponse; - - const processed = await db.transaction(async tran => { - const rows = await tran - .select({ id: kilocode_users.id }) - .from(kilocode_users) - .where(isNull(kilocode_users.openrouter_upstream_safety_identifier)) - .orderBy(desc(kilocode_users.created_at)) - .limit(1000); - - for (const user of rows) { - const openrouter_upstream_safety_identifier = generateOpenRouterUpstreamSafetyIdentifier( - user.id - ); - if (openrouter_upstream_safety_identifier === null) { - return -1; - } - await tran - .update(kilocode_users) - .set({ openrouter_upstream_safety_identifier }) - .where(eq(kilocode_users.id, user.id)) - .execute(); - } - - return rows.length; - }); - - if (processed === -1) { - return NextResponse.json( - { error: 'OPENROUTER_ORG_ID is not configured on this server' }, - { status: 500 } - ); - } - - return NextResponse.json({ processed, remaining: processed === 1000 }); -} diff --git a/src/app/admin/api/safety-identifiers/route.ts b/src/app/admin/api/safety-identifiers/route.ts index d250de75c2..3f165b32b2 100644 --- a/src/app/admin/api/safety-identifiers/route.ts +++ b/src/app/admin/api/safety-identifiers/route.ts @@ -2,14 +2,26 @@ import { NextResponse } from 'next/server'; import { getUserFromAuth } from '@/lib/user.server'; import { db } from '@/lib/drizzle'; import { kilocode_users } from '@kilocode/db'; -import { isNull, count } from 'drizzle-orm'; +import { + generateOpenRouterUpstreamSafetyIdentifier, + generateVercelDownstreamSafetyIdentifier, +} from '@/lib/providerHash'; +import { isNull, count, desc, eq } from 'drizzle-orm'; export type SafetyIdentifierCountsResponse = { openrouterMissing: number; vercelMissing: number; }; -export async function GET(): Promise> { +export type BackfillBatchResponse = { + openrouterProcessed: number; + vercelProcessed: number; + remaining: boolean; +}; + +export async function GET(): Promise< + NextResponse +> { const { authFailedResponse } = await getUserFromAuth({ adminOnly: true }); if (authFailedResponse) return authFailedResponse; @@ -29,3 +41,77 @@ export async function GET(): Promise { + return db.transaction(async tran => { + const rows = await tran + .select({ id: kilocode_users.id }) + .from(kilocode_users) + .where(isNull(kilocode_users.openrouter_upstream_safety_identifier)) + .orderBy(desc(kilocode_users.created_at)) + .limit(1000); + + for (const user of rows) { + const openrouter_upstream_safety_identifier = generateOpenRouterUpstreamSafetyIdentifier( + user.id + ); + if (openrouter_upstream_safety_identifier === null) { + return null; + } + await tran + .update(kilocode_users) + .set({ openrouter_upstream_safety_identifier }) + .where(eq(kilocode_users.id, user.id)) + .execute(); + } + + return rows.length; + }); +} + +async function backfillVercel(): Promise { + return db.transaction(async tran => { + const rows = await tran + .select({ id: kilocode_users.id }) + .from(kilocode_users) + .where(isNull(kilocode_users.vercel_downstream_safety_identifier)) + .orderBy(desc(kilocode_users.created_at)) + .limit(1000); + + for (const user of rows) { + const vercel_downstream_safety_identifier = generateVercelDownstreamSafetyIdentifier(user.id); + await tran + .update(kilocode_users) + .set({ vercel_downstream_safety_identifier }) + .where(eq(kilocode_users.id, user.id)) + .execute(); + } + + return rows.length; + }); +} + +export async function POST(): Promise< + NextResponse +> { + const { authFailedResponse } = await getUserFromAuth({ adminOnly: true }); + if (authFailedResponse) return authFailedResponse; + + const [openrouterResult, vercelProcessed] = await Promise.all([ + backfillOpenRouter(), + backfillVercel(), + ]); + + if (openrouterResult === null) { + return NextResponse.json( + { error: 'OPENROUTER_ORG_ID is not configured on this server' }, + { status: 500 } + ); + } + + return NextResponse.json({ + openrouterProcessed: openrouterResult, + vercelProcessed, + remaining: openrouterResult === 1000 || vercelProcessed === 1000, + }); +} diff --git a/src/app/admin/api/safety-identifiers/vercel/route.ts b/src/app/admin/api/safety-identifiers/vercel/route.ts deleted file mode 100644 index 90d621b694..0000000000 --- a/src/app/admin/api/safety-identifiers/vercel/route.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { NextResponse } from 'next/server'; -import { getUserFromAuth } from '@/lib/user.server'; -import { db } from '@/lib/drizzle'; -import { kilocode_users } from '@kilocode/db'; -import { generateVercelDownstreamSafetyIdentifier } from '@/lib/providerHash'; -import { isNull, desc, eq } from 'drizzle-orm'; - -export type BackfillBatchResponse = { - processed: number; - remaining: boolean; -}; - -export async function POST(): Promise> { - const { authFailedResponse } = await getUserFromAuth({ adminOnly: true }); - if (authFailedResponse) return authFailedResponse; - - const processed = await db.transaction(async tran => { - const rows = await tran - .select({ id: kilocode_users.id }) - .from(kilocode_users) - .where(isNull(kilocode_users.vercel_downstream_safety_identifier)) - .orderBy(desc(kilocode_users.created_at)) - .limit(1000); - - for (const user of rows) { - const vercel_downstream_safety_identifier = generateVercelDownstreamSafetyIdentifier(user.id); - await tran - .update(kilocode_users) - .set({ vercel_downstream_safety_identifier }) - .where(eq(kilocode_users.id, user.id)) - .execute(); - } - - return rows.length; - }); - - return NextResponse.json({ processed, remaining: processed === 1000 }); -} diff --git a/src/app/admin/components/SafetyIdentifiersBackfill.tsx b/src/app/admin/components/SafetyIdentifiersBackfill.tsx index d751197496..7c28de3500 100644 --- a/src/app/admin/components/SafetyIdentifiersBackfill.tsx +++ b/src/app/admin/components/SafetyIdentifiersBackfill.tsx @@ -5,20 +5,33 @@ import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { Button } from '@/components/ui/button'; import { Alert, AlertDescription } from '@/components/ui/alert'; import { Badge } from '@/components/ui/badge'; -import type { SafetyIdentifierCountsResponse } from '../api/safety-identifiers/route'; -import type { BackfillBatchResponse } from '../api/safety-identifiers/openrouter/route'; +import type { + SafetyIdentifierCountsResponse, + BackfillBatchResponse, +} from '../api/safety-identifiers/route'; type BatchLog = { - provider: string; - processed: number; + openrouterProcessed: number; + vercelProcessed: number; timestamp: Date; }; -function useBackfillMutation(provider: 'openrouter' | 'vercel', onBatch: (log: BatchLog) => void) { +export function SafetyIdentifiersBackfill() { + const [logs, setLogs] = useState([]); const queryClient = useQueryClient(); - return useMutation({ + + const { data: counts, isLoading } = useQuery({ + queryKey: ['safety-identifier-counts'], + queryFn: async () => { + const res = await fetch('/admin/api/safety-identifiers'); + return res.json() as Promise; + }, + refetchInterval: false, + }); + + const mutation = useMutation({ mutationFn: async () => { - const res = await fetch(`/admin/api/safety-identifiers/${provider}`, { method: 'POST' }); + const res = await fetch('/admin/api/safety-identifiers', { method: 'POST' }); if (!res.ok) { const body = (await res.json()) as { error?: string }; throw new Error(body.error ?? `HTTP ${res.status}`); @@ -26,105 +39,76 @@ function useBackfillMutation(provider: 'openrouter' | 'vercel', onBatch: (log: B return res.json() as Promise; }, onSuccess: data => { - onBatch({ provider, processed: data.processed, timestamp: new Date() }); + setLogs(prev => [ + { openrouterProcessed: data.openrouterProcessed, vercelProcessed: data.vercelProcessed, timestamp: new Date() }, + ...prev, + ]); void queryClient.invalidateQueries({ queryKey: ['safety-identifier-counts'] }); }, }); -} - -type ProviderCardProps = { - title: string; - description: string; - missing: number | undefined; - isLoading: boolean; - onBatch: (log: BatchLog) => void; - provider: 'openrouter' | 'vercel'; -}; -function ProviderCard({ title, description, missing, isLoading, onBatch, provider }: ProviderCardProps) { - const mutation = useBackfillMutation(provider, onBatch); - const isDone = missing === 0; - - return ( -
-
-
-

{title}

-

{description}

-
-
- {isLoading ? ( - Loading… - ) : isDone ? ( - All filled - ) : ( - {(missing ?? 0).toLocaleString()} missing - )} -
-
- - {mutation.isError && ( - - {mutation.error.message} - - )} - - -
- ); -} - -export function SafetyIdentifiersBackfill() { - const [logs, setLogs] = useState([]); - - const { data: counts, isLoading } = useQuery({ - queryKey: ['safety-identifier-counts'], - queryFn: async () => { - const res = await fetch('/admin/api/safety-identifiers'); - return res.json() as Promise; - }, - refetchInterval: false, - }); - - function addLog(log: BatchLog) { - setLogs(prev => [log, ...prev]); - } + const isDone = counts?.openrouterMissing === 0 && counts?.vercelMissing === 0; return (

- Backfill safety identifiers for users that were created before these fields were - introduced. Each button processes up to 1 000 users per click. Click repeatedly - (or rapidly) until the counter reaches zero. + Backfill safety identifiers for users created before these fields were introduced. + Each click processes up to 1 000 users per provider simultaneously. Click repeatedly + until both counters reach zero.

-
- - +
+
+
+
+ OpenRouter upstream + {isLoading ? ( + Loading… + ) : counts?.openrouterMissing === 0 ? ( + All filled + ) : ( + {(counts?.openrouterMissing ?? 0).toLocaleString()} missing + )} +
+

+ openrouter_upstream_safety_identifier +

+
+ +
+
+ Vercel downstream + {isLoading ? ( + Loading… + ) : counts?.vercelMissing === 0 ? ( + All filled + ) : ( + {(counts?.vercelMissing ?? 0).toLocaleString()} missing + )} +
+

+ vercel_downstream_safety_identifier +

+
+
+ + {mutation.isError && ( + + {mutation.error.message} + + )} + +
{logs.length > 0 && ( @@ -134,8 +118,9 @@ export function SafetyIdentifiersBackfill() { {logs.map((log, i) => (
{log.timestamp.toLocaleTimeString()} - [{log.provider}] - processed {log.processed.toLocaleString()} users + + openrouter: {log.openrouterProcessed.toLocaleString()}, vercel: {log.vercelProcessed.toLocaleString()} +
))}
From d36fcfa79bc73e411220f8fd4275ac9f79bf1e6d Mon Sep 17 00:00:00 2001 From: "kiloconnect[bot]" <240665456+kiloconnect[bot]@users.noreply.github.com> Date: Wed, 1 Apr 2026 20:03:29 +0000 Subject: [PATCH 4/6] refactor(admin): single query for safety identifier backfill --- src/app/admin/api/safety-identifiers/route.ts | 90 ++++++------------- .../components/SafetyIdentifiersBackfill.tsx | 66 ++++---------- 2 files changed, 43 insertions(+), 113 deletions(-) diff --git a/src/app/admin/api/safety-identifiers/route.ts b/src/app/admin/api/safety-identifiers/route.ts index 3f165b32b2..1c6ed93c10 100644 --- a/src/app/admin/api/safety-identifiers/route.ts +++ b/src/app/admin/api/safety-identifiers/route.ts @@ -6,16 +6,19 @@ import { generateOpenRouterUpstreamSafetyIdentifier, generateVercelDownstreamSafetyIdentifier, } from '@/lib/providerHash'; -import { isNull, count, desc, eq } from 'drizzle-orm'; +import { isNull, count, or, desc, eq } from 'drizzle-orm'; + +const missingEither = or( + isNull(kilocode_users.openrouter_upstream_safety_identifier), + isNull(kilocode_users.vercel_downstream_safety_identifier) +); export type SafetyIdentifierCountsResponse = { - openrouterMissing: number; - vercelMissing: number; + missing: number; }; export type BackfillBatchResponse = { - openrouterProcessed: number; - vercelProcessed: number; + processed: number; remaining: boolean; }; @@ -25,29 +28,25 @@ export async function GET(): Promise< const { authFailedResponse } = await getUserFromAuth({ adminOnly: true }); if (authFailedResponse) return authFailedResponse; - const [openrouterResult, vercelResult] = await Promise.all([ - db - .select({ count: count() }) - .from(kilocode_users) - .where(isNull(kilocode_users.openrouter_upstream_safety_identifier)), - db - .select({ count: count() }) - .from(kilocode_users) - .where(isNull(kilocode_users.vercel_downstream_safety_identifier)), - ]); + const [result] = await db + .select({ count: count() }) + .from(kilocode_users) + .where(missingEither); - return NextResponse.json({ - openrouterMissing: openrouterResult[0]?.count ?? 0, - vercelMissing: vercelResult[0]?.count ?? 0, - }); + return NextResponse.json({ missing: result?.count ?? 0 }); } -async function backfillOpenRouter(): Promise { - return db.transaction(async tran => { +export async function POST(): Promise< + NextResponse +> { + const { authFailedResponse } = await getUserFromAuth({ adminOnly: true }); + if (authFailedResponse) return authFailedResponse; + + const processed = await db.transaction(async tran => { const rows = await tran .select({ id: kilocode_users.id }) .from(kilocode_users) - .where(isNull(kilocode_users.openrouter_upstream_safety_identifier)) + .where(missingEither) .orderBy(desc(kilocode_users.created_at)) .limit(1000); @@ -60,58 +59,23 @@ async function backfillOpenRouter(): Promise { } await tran .update(kilocode_users) - .set({ openrouter_upstream_safety_identifier }) - .where(eq(kilocode_users.id, user.id)) - .execute(); - } - - return rows.length; - }); -} - -async function backfillVercel(): Promise { - return db.transaction(async tran => { - const rows = await tran - .select({ id: kilocode_users.id }) - .from(kilocode_users) - .where(isNull(kilocode_users.vercel_downstream_safety_identifier)) - .orderBy(desc(kilocode_users.created_at)) - .limit(1000); - - for (const user of rows) { - const vercel_downstream_safety_identifier = generateVercelDownstreamSafetyIdentifier(user.id); - await tran - .update(kilocode_users) - .set({ vercel_downstream_safety_identifier }) + .set({ + openrouter_upstream_safety_identifier, + vercel_downstream_safety_identifier: generateVercelDownstreamSafetyIdentifier(user.id), + }) .where(eq(kilocode_users.id, user.id)) .execute(); } return rows.length; }); -} -export async function POST(): Promise< - NextResponse -> { - const { authFailedResponse } = await getUserFromAuth({ adminOnly: true }); - if (authFailedResponse) return authFailedResponse; - - const [openrouterResult, vercelProcessed] = await Promise.all([ - backfillOpenRouter(), - backfillVercel(), - ]); - - if (openrouterResult === null) { + if (processed === null) { return NextResponse.json( { error: 'OPENROUTER_ORG_ID is not configured on this server' }, { status: 500 } ); } - return NextResponse.json({ - openrouterProcessed: openrouterResult, - vercelProcessed, - remaining: openrouterResult === 1000 || vercelProcessed === 1000, - }); + return NextResponse.json({ processed, remaining: processed === 1000 }); } diff --git a/src/app/admin/components/SafetyIdentifiersBackfill.tsx b/src/app/admin/components/SafetyIdentifiersBackfill.tsx index 7c28de3500..1216fb7c09 100644 --- a/src/app/admin/components/SafetyIdentifiersBackfill.tsx +++ b/src/app/admin/components/SafetyIdentifiersBackfill.tsx @@ -11,8 +11,7 @@ import type { } from '../api/safety-identifiers/route'; type BatchLog = { - openrouterProcessed: number; - vercelProcessed: number; + processed: number; timestamp: Date; }; @@ -39,57 +38,30 @@ export function SafetyIdentifiersBackfill() { return res.json() as Promise; }, onSuccess: data => { - setLogs(prev => [ - { openrouterProcessed: data.openrouterProcessed, vercelProcessed: data.vercelProcessed, timestamp: new Date() }, - ...prev, - ]); + setLogs(prev => [{ processed: data.processed, timestamp: new Date() }, ...prev]); void queryClient.invalidateQueries({ queryKey: ['safety-identifier-counts'] }); }, }); - const isDone = counts?.openrouterMissing === 0 && counts?.vercelMissing === 0; + const isDone = counts?.missing === 0; return (

- Backfill safety identifiers for users created before these fields were introduced. - Each click processes up to 1 000 users per provider simultaneously. Click repeatedly - until both counters reach zero. + Backfill safety identifiers for users missing either field. Each click processes up + to 1 000 users. Click repeatedly until the counter reaches zero.

-
-
-
- OpenRouter upstream - {isLoading ? ( - Loading… - ) : counts?.openrouterMissing === 0 ? ( - All filled - ) : ( - {(counts?.openrouterMissing ?? 0).toLocaleString()} missing - )} -
-

- openrouter_upstream_safety_identifier -

-
- -
-
- Vercel downstream - {isLoading ? ( - Loading… - ) : counts?.vercelMissing === 0 ? ( - All filled - ) : ( - {(counts?.vercelMissing ?? 0).toLocaleString()} missing - )} -
-

- vercel_downstream_safety_identifier -

-
+
+ Users missing a safety identifier + {isLoading ? ( + Loading… + ) : isDone ? ( + All filled + ) : ( + {(counts?.missing ?? 0).toLocaleString()} missing + )}
{mutation.isError && ( @@ -103,11 +75,7 @@ export function SafetyIdentifiersBackfill() { disabled={isLoading || isDone || mutation.isPending} variant={isDone ? 'outline' : 'default'} > - {mutation.isPending - ? 'Backfilling…' - : isDone - ? 'Nothing to do' - : 'Backfill next 1 000'} + {mutation.isPending ? 'Backfilling…' : isDone ? 'Nothing to do' : 'Backfill next 1 000'}
@@ -118,9 +86,7 @@ export function SafetyIdentifiersBackfill() { {logs.map((log, i) => (
{log.timestamp.toLocaleTimeString()} - - openrouter: {log.openrouterProcessed.toLocaleString()}, vercel: {log.vercelProcessed.toLocaleString()} - + processed {log.processed.toLocaleString()} users
))}
From f05f6d3dbc33fc24c6f7e48f45fc1d80e02676ed Mon Sep 17 00:00:00 2001 From: Christiaan Arnoldus Date: Wed, 1 Apr 2026 22:19:53 +0200 Subject: [PATCH 5/6] fmt --- src/app/admin/api/safety-identifiers/route.ts | 9 ++------- src/app/admin/components/SafetyIdentifiersBackfill.tsx | 8 +++++--- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/src/app/admin/api/safety-identifiers/route.ts b/src/app/admin/api/safety-identifiers/route.ts index 1c6ed93c10..c9cb748672 100644 --- a/src/app/admin/api/safety-identifiers/route.ts +++ b/src/app/admin/api/safety-identifiers/route.ts @@ -28,17 +28,12 @@ export async function GET(): Promise< const { authFailedResponse } = await getUserFromAuth({ adminOnly: true }); if (authFailedResponse) return authFailedResponse; - const [result] = await db - .select({ count: count() }) - .from(kilocode_users) - .where(missingEither); + const [result] = await db.select({ count: count() }).from(kilocode_users).where(missingEither); return NextResponse.json({ missing: result?.count ?? 0 }); } -export async function POST(): Promise< - NextResponse -> { +export async function POST(): Promise> { const { authFailedResponse } = await getUserFromAuth({ adminOnly: true }); if (authFailedResponse) return authFailedResponse; diff --git a/src/app/admin/components/SafetyIdentifiersBackfill.tsx b/src/app/admin/components/SafetyIdentifiersBackfill.tsx index 1216fb7c09..5c7b8ed641 100644 --- a/src/app/admin/components/SafetyIdentifiersBackfill.tsx +++ b/src/app/admin/components/SafetyIdentifiersBackfill.tsx @@ -48,8 +48,8 @@ export function SafetyIdentifiersBackfill() { return (

- Backfill safety identifiers for users missing either field. Each click processes up - to 1 000 users. Click repeatedly until the counter reaches zero. + Backfill safety identifiers for users missing either field. Each click processes up to 1 000 + users. Click repeatedly until the counter reaches zero.

@@ -58,7 +58,9 @@ export function SafetyIdentifiersBackfill() { {isLoading ? ( Loading… ) : isDone ? ( - All filled + + All filled + ) : ( {(counts?.missing ?? 0).toLocaleString()} missing )} From 2394b56ab68baf2c9e449b1a9a46456ea186b20c Mon Sep 17 00:00:00 2001 From: Christiaan Arnoldus Date: Wed, 1 Apr 2026 22:35:35 +0200 Subject: [PATCH 6/6] Delete obsolete script --- .../openrouter/backfill-safety-identifier.ts | 40 ------------------- 1 file changed, 40 deletions(-) delete mode 100644 src/scripts/openrouter/backfill-safety-identifier.ts diff --git a/src/scripts/openrouter/backfill-safety-identifier.ts b/src/scripts/openrouter/backfill-safety-identifier.ts deleted file mode 100644 index c8e1f690c6..0000000000 --- a/src/scripts/openrouter/backfill-safety-identifier.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { db } from '@/lib/drizzle'; -import { generateOpenRouterUpstreamSafetyIdentifier } from '@/lib/providerHash'; -import { kilocode_users } from '@kilocode/db'; -import { isNull, desc, eq } from 'drizzle-orm'; - -export async function run() { - while (true) { - const count = await db.transaction(async tran => { - const rows = await tran - .select({ - id: kilocode_users.id, - }) - .from(kilocode_users) - .where(isNull(kilocode_users.openrouter_upstream_safety_identifier)) - .orderBy(desc(kilocode_users.created_at)) - .limit(1000); - if (rows.length === 0) { - return 0; - } - console.log(`Batch of ${rows.length} users`); - for (const user of rows) { - const openrouter_upstream_safety_identifier = generateOpenRouterUpstreamSafetyIdentifier( - user.id - ); - await tran - .update(kilocode_users) - .set({ - openrouter_upstream_safety_identifier, - }) - .where(eq(kilocode_users.id, user.id)) - .execute(); - } - console.log('Commit'); - return rows.length; - }); - if (count === 0) { - break; - } - } -}