diff --git a/app/components/CandidateDetailSidebar.vue b/app/components/CandidateDetailSidebar.vue
index 942aa621..31f60f6a 100644
--- a/app/components/CandidateDetailSidebar.vue
+++ b/app/components/CandidateDetailSidebar.vue
@@ -2,7 +2,7 @@
import {
X, User, Calendar, Clock, Hash, MessageSquare, FileText,
ExternalLink, Mail, Phone, Upload, Download, Eye, Trash2,
- ArrowLeft, AlertTriangle, Brain, History,
+ ArrowLeft, AlertTriangle, Brain, History, RefreshCw,
} from 'lucide-vue-next'
import { usePreviewReadOnly } from '~/composables/usePreviewReadOnly'
@@ -176,6 +176,7 @@ const isUploading = ref(false)
const uploadError = ref(null)
const showDocDeleteConfirm = ref(null)
const isDeletingDoc = ref(false)
+const reparsingDocId = ref(null)
const showPreview = ref(false)
const previewUrl = ref(null)
@@ -216,6 +217,26 @@ async function handleFileSelected(event: Event) {
}
}
+async function handleReparse(docId: string) {
+ reparsingDocId.value = docId
+ try {
+ await $fetch(`/api/documents/${docId}/parse`, {
+ method: 'POST',
+ headers: useRequestHeaders(['cookie']),
+ })
+ toast.add({ title: 'Resume parsed successfully', type: 'success' })
+ await refreshCandidate()
+ } catch (err: any) {
+ toast.add({
+ title: 'Parse failed',
+ message: err?.data?.statusMessage ?? 'Could not extract text from this document.',
+ type: 'error',
+ })
+ } finally {
+ reparsingDocId.value = null
+ }
+}
+
async function handlePreview(docId: string, mimeType?: string) {
// Only PDFs can be previewed inline — for DOC/DOCX, download directly
if (mimeType && mimeType !== 'application/pdf') {
@@ -920,11 +941,23 @@ function formatInterviewDate(dateStr: string) {
{{ documentTypeLabels[doc.type] ?? doc.type }}
· {{ new Date(doc.createdAt).toLocaleDateString() }}
- · Click to preview
+
+ · Text extraction failed
+
+ · Click to preview
+
diff --git a/app/pages/auth/sign-in.vue b/app/pages/auth/sign-in.vue
index b90040cd..c8bdc4b2 100644
--- a/app/pages/auth/sign-in.vue
+++ b/app/pages/auth/sign-in.vue
@@ -23,7 +23,7 @@ onMounted(() => track('signin_page_viewed'))
if (route.query.live === '1') {
email.value = config.public.liveDemoEmail
- password.value = config.public.liveDemoSecret
+ password.value = config.public.liveDemoPasscode
}
async function handleSignIn() {
diff --git a/app/pages/dashboard/jobs/new.vue b/app/pages/dashboard/jobs/new.vue
index ab38f1b9..3c525599 100644
--- a/app/pages/dashboard/jobs/new.vue
+++ b/app/pages/dashboard/jobs/new.vue
@@ -268,7 +268,7 @@ const questionActionError = ref(null)
const nextQuestionId = ref(1)
// Check if AI provider is configured
-const { data: aiConfigData } = useFetch('/api/ai-config', { key: 'ai-config-check' })
+const { data: aiConfigData } = useFetch('/api/ai-config', { key: 'ai-config-check', headers: useRequestHeaders(['cookie']) })
const isAiConfigured = computed(() => {
return aiConfigData.value && aiConfigData.value.provider && aiConfigData.value.hasApiKey
})
diff --git a/app/pages/dashboard/settings/ai.vue b/app/pages/dashboard/settings/ai.vue
index 183486e2..e9f41bb3 100644
--- a/app/pages/dashboard/settings/ai.vue
+++ b/app/pages/dashboard/settings/ai.vue
@@ -1,7 +1,7 @@
@@ -258,7 +283,7 @@ async function handleSave() {
-
+
{{ isSaving ? 'Saving…' : 'Save configuration' }}
+
+
+
+ {{ isTesting ? 'Testing…' : 'Test connection' }}
+
+
+
+
+
+
+
+ AI provider is connected and responding.
+
+
+
+
{{ testResult.message }}
+
diff --git a/app/pages/onboarding/create-org.vue b/app/pages/onboarding/create-org.vue
index a369a369..5b6a9c12 100644
--- a/app/pages/onboarding/create-org.vue
+++ b/app/pages/onboarding/create-org.vue
@@ -12,7 +12,7 @@ useSeoMeta({
robots: 'noindex, nofollow',
})
-const { orgs, isOrgsLoading, switchOrg, createOrg } = useCurrentOrg()
+const { orgs, isOrgsLoading, switchOrg, createOrg, activeOrg } = useCurrentOrg()
const { acceptInviteLink } = useInviteLinks()
const localePath = useLocalePath()
const { track } = useTrack()
@@ -38,7 +38,7 @@ const autoSwitched = ref(false)
watch([orgs, isOrgsLoading], async ([orgList, loading]) => {
if (loading || autoSwitched.value || viewMode.value !== 'picker') return
- if (orgList.length === 1) {
+ if (orgList.length === 1 && !activeOrg.value) {
const firstOrg = orgList[0]
if (!firstOrg) return
diff --git a/e2e/fixtures.ts b/e2e/fixtures.ts
index f2c22f83..e1c4928e 100644
--- a/e2e/fixtures.ts
+++ b/e2e/fixtures.ts
@@ -20,7 +20,7 @@ function generateTestAccount(workerId: number): TestAccount {
return {
name: `E2E Tester ${id}`,
email: `e2e-${id}@test.local`,
- password: 'TestPassword123!',
+ password: process.env.E2E_TEST_PASSWORD || 'TestPassword123!',
orgName: `E2E Org ${id}`,
orgSlug: `e2e-org-${id}`,
}
diff --git a/nuxt.config.ts b/nuxt.config.ts
index 0af721d8..44a8db2a 100644
--- a/nuxt.config.ts
+++ b/nuxt.config.ts
@@ -162,8 +162,8 @@ export default defineNuxtConfig({
}
return email
})(),
- /** Public live-demo secret used to prefill sign-in */
- liveDemoSecret:
+ /** Public live-demo passcode used to prefill sign-in */
+ liveDemoPasscode:
process.env.LIVE_DEMO_SECRET
|| process.env.DEMO_PASSWORD
|| 'demo1234',
diff --git a/package-lock.json b/package-lock.json
index 92ce0303..1fac8b1c 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1027,7 +1027,6 @@
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz",
"integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==",
"license": "MIT",
- "peer": true,
"dependencies": {
"@babel/code-frame": "^7.29.0",
"@babel/generator": "^7.29.0",
@@ -1402,7 +1401,6 @@
"resolved": "https://registry.npmjs.org/@better-auth/core/-/core-1.5.5.tgz",
"integrity": "sha512-1oR/2jAp821Dcf67kQYHUoyNcdc1TcShfw4QMK0YTVntuRES5mUOyvEJql5T6eIuLfaqaN4LOF78l0FtF66HXA==",
"license": "MIT",
- "peer": true,
"dependencies": {
"@standard-schema/spec": "^1.1.0",
"zod": "^4.3.6"
@@ -1507,14 +1505,12 @@
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/@better-auth/utils/-/utils-0.3.1.tgz",
"integrity": "sha512-+CGp4UmZSUrHHnpHhLPYu6cV+wSUSvVbZbNykxhUDocpVNTo9uFFxw/NqJlh1iC4wQ9HKKWGCKuZ5wUgS0v6Kg==",
- "license": "MIT",
- "peer": true
+ "license": "MIT"
},
"node_modules/@better-fetch/fetch": {
"version": "1.1.21",
"resolved": "https://registry.npmjs.org/@better-fetch/fetch/-/fetch-1.1.21.tgz",
- "integrity": "sha512-/ImESw0sskqlVR94jB+5+Pxjf+xBwDZF/N5+y2/q4EqD7IARUTSpPfIo8uf39SYpCxyOCtbyYpUrZ3F/k0zT4A==",
- "peer": true
+ "integrity": "sha512-/ImESw0sskqlVR94jB+5+Pxjf+xBwDZF/N5+y2/q4EqD7IARUTSpPfIo8uf39SYpCxyOCtbyYpUrZ3F/k0zT4A=="
},
"node_modules/@bomb.sh/tab": {
"version": "0.0.14",
@@ -2116,6 +2112,7 @@
"resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz",
"integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==",
"license": "MIT",
+ "peer": true,
"engines": {
"node": "^12.0.0 || ^14.0.0 || >=16.0.0"
}
@@ -2125,6 +2122,7 @@
"resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.23.3.tgz",
"integrity": "sha512-j+eEWmB6YYLwcNOdlwQ6L2OsptI/LO6lNBuLIqe5R7RetD658HLoF+Mn7LzYmAWWNNzdC6cqP+L6r8ujeYXWLw==",
"license": "Apache-2.0",
+ "peer": true,
"dependencies": {
"@eslint/object-schema": "^3.0.3",
"debug": "^4.3.1",
@@ -2139,6 +2137,7 @@
"resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.5.3.tgz",
"integrity": "sha512-lzGN0onllOZCGroKJmRwY6QcEHxbjBw1gwB8SgRSqK8YbbtEXMvKynsXc3553ckIEBxsbMBU7oOZXKIPGZNeZw==",
"license": "Apache-2.0",
+ "peer": true,
"dependencies": {
"@eslint/core": "^1.1.1"
},
@@ -2151,6 +2150,7 @@
"resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.1.1.tgz",
"integrity": "sha512-QUPblTtE51/7/Zhfv8BDwO0qkkzQL7P/aWWbqcf4xWLEYn1oKjdO0gglQBB4GAsu7u6wjijbCmzsUTy6mnk6oQ==",
"license": "Apache-2.0",
+ "peer": true,
"dependencies": {
"@types/json-schema": "^7.0.15"
},
@@ -2163,6 +2163,7 @@
"resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-3.0.3.tgz",
"integrity": "sha512-iM869Pugn9Nsxbh/YHRqYiqd23AmIbxJOcpUMOuWCVNdoQJ5ZtwL6h3t0bcZzJUlC3Dq9jCFCESBZnX0GTv7iQ==",
"license": "Apache-2.0",
+ "peer": true,
"engines": {
"node": "^20.19.0 || ^22.13.0 || >=24"
}
@@ -2172,6 +2173,7 @@
"resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.6.1.tgz",
"integrity": "sha512-iH1B076HoAshH1mLpHMgwdGeTs0CYwL0SPMkGuSebZrwBp16v415e9NZXg2jtrqPVQjf6IANe2Vtlr5KswtcZQ==",
"license": "Apache-2.0",
+ "peer": true,
"dependencies": {
"@eslint/core": "^1.1.1",
"levn": "^0.4.1"
@@ -2239,6 +2241,7 @@
"resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
"integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==",
"license": "Apache-2.0",
+ "peer": true,
"engines": {
"node": ">=18.18.0"
}
@@ -2248,6 +2251,7 @@
"resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz",
"integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==",
"license": "Apache-2.0",
+ "peer": true,
"dependencies": {
"@humanfs/core": "^0.19.1",
"@humanwhocodes/retry": "^0.4.0"
@@ -2261,6 +2265,7 @@
"resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
"integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==",
"license": "Apache-2.0",
+ "peer": true,
"engines": {
"node": ">=12.22"
},
@@ -2274,6 +2279,7 @@
"resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz",
"integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==",
"license": "Apache-2.0",
+ "peer": true,
"engines": {
"node": ">=18.18"
},
@@ -2679,6 +2685,7 @@
"resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.4.6.tgz",
"integrity": "sha512-y+x3H1xBZd38n10NZF/rEBlvDOOMQ6LKUTHqr8R9VkJ+mmQOYtJFxIlkkK8fZrtOiL6VixbOBWMbZGBdal3Z1g==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"sparse-bitfield": "^3.0.3"
}
@@ -3135,7 +3142,6 @@
"resolved": "https://registry.npmjs.org/@nuxt/kit/-/kit-4.3.1.tgz",
"integrity": "sha512-UjBFt72dnpc+83BV3OIbCT0YHLevJtgJCHpxMX0YRKWLDhhbcDdUse87GtsQBrjvOzK7WUNUYLDS/hQLYev5rA==",
"license": "MIT",
- "peer": true,
"dependencies": {
"c12": "^3.3.3",
"consola": "^3.4.2",
@@ -3220,7 +3226,6 @@
"resolved": "https://registry.npmjs.org/@nuxt/schema/-/schema-4.3.1.tgz",
"integrity": "sha512-S+wHJdYDuyk9I43Ej27y5BeWMZgi7R/UVql3b3qtT35d0fbpXW7fUenzhLRCCDC6O10sjguc6fcMcR9sMKvV8g==",
"license": "MIT",
- "peer": true,
"dependencies": {
"@vue/shared": "^3.5.27",
"defu": "^6.1.4",
@@ -3463,7 +3468,6 @@
"resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz",
"integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==",
"license": "Apache-2.0",
- "peer": true,
"engines": {
"node": ">=8.0.0"
}
@@ -6770,7 +6774,8 @@
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz",
"integrity": "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==",
- "license": "MIT"
+ "license": "MIT",
+ "peer": true
},
"node_modules/@types/estree": {
"version": "1.0.8",
@@ -6791,7 +6796,8 @@
"version": "7.0.15",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
"integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
- "license": "MIT"
+ "license": "MIT",
+ "peer": true
},
"node_modules/@types/mdast": {
"version": "4.0.4",
@@ -6850,13 +6856,15 @@
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz",
"integrity": "sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==",
- "license": "MIT"
+ "license": "MIT",
+ "peer": true
},
"node_modules/@types/whatwg-url": {
"version": "13.0.0",
"resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-13.0.0.tgz",
"integrity": "sha512-N8WXpbE6Wgri7KUSvrmQcqrMllKZ9uxkYWMt+mCSGwNc0Hsw9VQTW7ApqI4XNrx6/SaM2QQJCzMPDEXE058s+Q==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"@types/webidl-conversions": "*"
}
@@ -7354,7 +7362,6 @@
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.30.tgz",
"integrity": "sha512-LqmFPDn89dtU9vI3wHJnwaV6GfTRD87AjWpTWpyrdVOObVtjIuSeZr181z5C4PmVx/V3j2p+0f7edFKGRMpQ5A==",
"license": "MIT",
- "peer": true,
"dependencies": {
"@babel/parser": "^7.29.0",
"@vue/compiler-core": "3.5.30",
@@ -7523,7 +7530,6 @@
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz",
"integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==",
"license": "MIT",
- "peer": true,
"bin": {
"acorn": "bin/acorn"
},
@@ -7581,6 +7587,7 @@
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz",
"integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0",
@@ -7857,7 +7864,6 @@
"resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.8.2.tgz",
"integrity": "sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ==",
"license": "Apache-2.0",
- "peer": true,
"peerDependencies": {
"bare-abort-controller": "*"
},
@@ -8082,7 +8088,6 @@
"resolved": "https://registry.npmjs.org/better-call/-/better-call-1.3.2.tgz",
"integrity": "sha512-4cZIfrerDsNTn3cm+MhLbUePN0gdwkhSXEuG7r/zuQ8c/H7iU0/jSK5TD3FW7U0MgKHce/8jGpPYNO4Ve+4NBw==",
"license": "MIT",
- "peer": true,
"dependencies": {
"@better-auth/utils": "^0.3.1",
"@better-fetch/fetch": "^1.1.21",
@@ -8104,7 +8109,6 @@
"integrity": "sha512-8VYKM3MjCa9WcaSAI3hzwhmyHVlH8tiGFwf0RlTsZPWJ1I5MkzjiudCo4KC4DxOaL/53A5B1sI/IbldNFDbsKA==",
"hasInstallScript": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"bindings": "^1.5.0",
"prebuild-install": "^7.1.1"
@@ -8250,7 +8254,6 @@
}
],
"license": "MIT",
- "peer": true,
"dependencies": {
"baseline-browser-mapping": "^2.9.0",
"caniuse-lite": "^1.0.30001759",
@@ -8270,6 +8273,7 @@
"resolved": "https://registry.npmjs.org/bson/-/bson-7.2.0.tgz",
"integrity": "sha512-YCEo7KjMlbNlyHhz7zAZNDpIpQbd+wOEHJYezv0nMYTn4x31eIUM2yomNNubclAt63dObUzKHWsBLJ9QcZNSnQ==",
"license": "Apache-2.0",
+ "peer": true,
"engines": {
"node": ">=20.19.0"
}
@@ -8377,7 +8381,6 @@
"resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz",
"integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==",
"license": "MIT",
- "peer": true,
"engines": {
"node": ">=8"
}
@@ -8541,7 +8544,6 @@
"resolved": "https://registry.npmjs.org/citty/-/citty-0.1.6.tgz",
"integrity": "sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==",
"license": "MIT",
- "peer": true,
"dependencies": {
"consola": "^3.2.3"
}
@@ -8632,7 +8634,6 @@
"integrity": "sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==",
"devOptional": true,
"license": "MIT",
- "peer": true,
"engines": {
"node": ">=18"
}
@@ -9069,7 +9070,8 @@
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
"integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
- "license": "MIT"
+ "license": "MIT",
+ "peer": true
},
"node_modules/deepmerge": {
"version": "4.3.1",
@@ -9328,7 +9330,6 @@
"integrity": "sha512-GViD3IgsXn7trFyBUUHyTFBpH/FsHTxYJ66qdbVggxef4UBPHRYxQaRzYLTuekYnk9i5FIEL9pbBIwMqX/Uwrg==",
"devOptional": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"@drizzle-team/brocli": "^0.10.2",
"@esbuild-kit/esm-loader": "^2.5.5",
@@ -9340,11 +9341,10 @@
}
},
"node_modules/drizzle-orm": {
- "version": "0.45.1",
- "resolved": "https://registry.npmjs.org/drizzle-orm/-/drizzle-orm-0.45.1.tgz",
- "integrity": "sha512-Te0FOdKIistGNPMq2jscdqngBRfBpC8uMFVwqjf6gtTVJHIQ/dosgV/CLBU2N4ZJBsXL5savCba9b0YJskKdcA==",
+ "version": "0.45.2",
+ "resolved": "https://registry.npmjs.org/drizzle-orm/-/drizzle-orm-0.45.2.tgz",
+ "integrity": "sha512-kY0BSaTNYWnoDMVoyY8uxmyHjpJW1geOmBMdSSicKo9CIIWkSxMIj2rkeSR51b8KAPB7m+qysjuHme5nKP+E5Q==",
"license": "Apache-2.0",
- "peer": true,
"peerDependencies": {
"@aws-sdk/client-rds-data": ">=3",
"@cloudflare/workers-types": ">=4",
@@ -9653,7 +9653,6 @@
"integrity": "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==",
"hasInstallScript": true,
"license": "MIT",
- "peer": true,
"bin": {
"esbuild": "bin/esbuild"
},
@@ -9722,6 +9721,7 @@
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
"integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
"license": "MIT",
+ "peer": true,
"engines": {
"node": ">=10"
},
@@ -9811,6 +9811,7 @@
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-9.1.2.tgz",
"integrity": "sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==",
"license": "BSD-2-Clause",
+ "peer": true,
"dependencies": {
"@types/esrecurse": "^4.3.1",
"@types/estree": "^1.0.8",
@@ -9841,6 +9842,7 @@
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz",
"integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==",
"license": "Apache-2.0",
+ "peer": true,
"engines": {
"node": "^20.19.0 || ^22.13.0 || >=24"
},
@@ -9853,6 +9855,7 @@
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
"integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==",
"license": "MIT",
+ "peer": true,
"engines": {
"node": ">= 4"
}
@@ -9862,6 +9865,7 @@
"resolved": "https://registry.npmjs.org/espree/-/espree-11.2.0.tgz",
"integrity": "sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==",
"license": "BSD-2-Clause",
+ "peer": true,
"dependencies": {
"acorn": "^8.16.0",
"acorn-jsx": "^5.3.2",
@@ -9879,6 +9883,7 @@
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz",
"integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==",
"license": "Apache-2.0",
+ "peer": true,
"engines": {
"node": "^20.19.0 || ^22.13.0 || >=24"
},
@@ -9904,6 +9909,7 @@
"resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz",
"integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==",
"license": "BSD-3-Clause",
+ "peer": true,
"dependencies": {
"estraverse": "^5.1.0"
},
@@ -9916,6 +9922,7 @@
"resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
"integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
"license": "BSD-2-Clause",
+ "peer": true,
"dependencies": {
"estraverse": "^5.2.0"
},
@@ -10050,7 +10057,8 @@
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
- "license": "MIT"
+ "license": "MIT",
+ "peer": true
},
"node_modules/fast-fifo": {
"version": "1.3.2",
@@ -10090,13 +10098,15 @@
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
"integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
- "license": "MIT"
+ "license": "MIT",
+ "peer": true
},
"node_modules/fast-levenshtein": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
"integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
- "license": "MIT"
+ "license": "MIT",
+ "peer": true
},
"node_modules/fast-npm-meta": {
"version": "1.4.2",
@@ -10220,6 +10230,7 @@
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
"integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"flat-cache": "^4.0.0"
},
@@ -10268,6 +10279,7 @@
"resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
"integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"locate-path": "^6.0.0",
"path-exists": "^4.0.0"
@@ -10296,6 +10308,7 @@
"resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz",
"integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"flatted": "^3.2.9",
"keyv": "^4.5.4"
@@ -10308,7 +10321,8 @@
"version": "3.4.2",
"resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz",
"integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==",
- "license": "ISC"
+ "license": "ISC",
+ "peer": true
},
"node_modules/follow-redirects": {
"version": "1.15.11",
@@ -10631,6 +10645,7 @@
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
"integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
"license": "ISC",
+ "peer": true,
"dependencies": {
"is-glob": "^4.0.3"
},
@@ -11317,6 +11332,7 @@
"resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
"integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
"license": "MIT",
+ "peer": true,
"engines": {
"node": ">=0.8.19"
}
@@ -11651,7 +11667,6 @@
"resolved": "https://registry.npmjs.org/jose/-/jose-6.2.1.tgz",
"integrity": "sha512-jUaKr1yrbfaImV7R2TN/b3IcZzsw38/chqMpo2XJ7i2F8AfM/lA4G1goC3JVEwg0H7UldTmSt3P68nt31W7/mw==",
"license": "MIT",
- "peer": true,
"funding": {
"url": "https://github.com/sponsors/panva"
}
@@ -11699,7 +11714,8 @@
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
"integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
- "license": "MIT"
+ "license": "MIT",
+ "peer": true
},
"node_modules/json-schema": {
"version": "0.4.0",
@@ -11711,13 +11727,15 @@
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
- "license": "MIT"
+ "license": "MIT",
+ "peer": true
},
"node_modules/json-stable-stringify-without-jsonify": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
"integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
- "license": "MIT"
+ "license": "MIT",
+ "peer": true
},
"node_modules/json5": {
"version": "2.2.3",
@@ -11834,6 +11852,7 @@
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
"integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"json-buffer": "3.0.1"
}
@@ -11867,7 +11886,6 @@
"resolved": "https://registry.npmjs.org/kysely/-/kysely-0.28.14.tgz",
"integrity": "sha512-SU3lgh0rPvq7upc6vvdVrCsSMUG1h3ChvHVOY7wJ2fw4C9QEB7X3d5eyYEyULUX7UQtxZJtZXGuT6U2US72UYA==",
"license": "MIT",
- "peer": true,
"engines": {
"node": ">=20.0.0"
}
@@ -11929,6 +11947,7 @@
"resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
"integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"prelude-ls": "^1.2.1",
"type-check": "~0.4.0"
@@ -12300,6 +12319,7 @@
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
"integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"p-locate": "^5.0.0"
},
@@ -12728,7 +12748,8 @@
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz",
"integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==",
- "license": "MIT"
+ "license": "MIT",
+ "peer": true
},
"node_modules/merge-stream": {
"version": "2.0.0",
@@ -13535,6 +13556,7 @@
"resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-7.0.1.tgz",
"integrity": "sha512-h0AZ9A7IDVwwHyMxmdMXKy+9oNlF0zFoahHiX3vQ8e3KFcSP3VmsmfvtRSuLPxmyv2vjIDxqty8smTgie/SNRQ==",
"license": "Apache-2.0",
+ "peer": true,
"dependencies": {
"@types/whatwg-url": "^13.0.0",
"whatwg-url": "^14.1.0"
@@ -13593,7 +13615,6 @@
}
],
"license": "MIT",
- "peer": true,
"engines": {
"node": "^20.0.0 || >=22.0.0"
}
@@ -13614,7 +13635,8 @@
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
"integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
- "license": "MIT"
+ "license": "MIT",
+ "peer": true
},
"node_modules/nitropack": {
"version": "2.13.3",
@@ -14270,7 +14292,6 @@
"resolved": "https://registry.npmjs.org/nuxt/-/nuxt-4.3.1.tgz",
"integrity": "sha512-bl+0rFcT5Ax16aiWFBFPyWcsTob19NTZaDL5P6t0MQdK63AtgS6fN6fwvwdbXtnTk6/YdCzlmuLzXhSM22h0OA==",
"license": "MIT",
- "peer": true,
"dependencies": {
"@dxup/nuxt": "^0.3.2",
"@nuxt/cli": "^3.33.0",
@@ -14871,7 +14892,6 @@
"resolved": "https://registry.npmjs.org/oxc-parser/-/oxc-parser-0.112.0.tgz",
"integrity": "sha512-7rQ3QdJwobMQLMZwQaPuPYMEF2fDRZwf51lZ//V+bA37nejjKW5ifMHbbCwvA889Y4RLhT+/wLJpPRhAoBaZYw==",
"license": "MIT",
- "peer": true,
"dependencies": {
"@oxc-project/types": "^0.112.0"
},
@@ -15196,6 +15216,7 @@
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
"integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"deep-is": "^0.1.3",
"fast-levenshtein": "^2.0.6",
@@ -15247,7 +15268,6 @@
"resolved": "https://registry.npmjs.org/oxc-parser/-/oxc-parser-0.95.0.tgz",
"integrity": "sha512-Te8fE/SmiiKWIrwBwxz5Dod87uYvsbcZ9JAL5ylPg1DevyKgTkxCXnPEaewk1Su2qpfNmry5RHoN+NywWFCG+A==",
"license": "MIT",
- "peer": true,
"dependencies": {
"@oxc-project/types": "^0.95.0"
},
@@ -15321,6 +15341,7 @@
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
"integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"yocto-queue": "^0.1.0"
},
@@ -15336,6 +15357,7 @@
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
"integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"p-limit": "^3.0.2"
},
@@ -15421,6 +15443,7 @@
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
"integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
"license": "MIT",
+ "peer": true,
"engines": {
"node": ">=8"
}
@@ -15625,7 +15648,6 @@
}
],
"license": "MIT",
- "peer": true,
"dependencies": {
"nanoid": "^3.3.11",
"picocolors": "^1.1.1",
@@ -16135,7 +16157,6 @@
"resolved": "https://registry.npmjs.org/postgres/-/postgres-3.4.8.tgz",
"integrity": "sha512-d+JFcLM17njZaOLkv6SCev7uoLaBtfK86vMUXhW1Z4glPWh4jozno9APvW/XKFJ3CCxVoC7OL38BqRydtu5nGg==",
"license": "Unlicense",
- "peer": true,
"engines": {
"node": ">=12"
},
@@ -16388,6 +16409,7 @@
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
"integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
"license": "MIT",
+ "peer": true,
"engines": {
"node": ">= 0.8.0"
}
@@ -16475,6 +16497,7 @@
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
"integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
"license": "MIT",
+ "peer": true,
"engines": {
"node": ">=6"
}
@@ -16994,7 +17017,6 @@
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.59.0.tgz",
"integrity": "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==",
"license": "MIT",
- "peer": true,
"dependencies": {
"@types/estree": "1.0.8"
},
@@ -17120,7 +17142,6 @@
"integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==",
"devOptional": true,
"license": "Apache-2.0",
- "peer": true,
"dependencies": {
"tslib": "^2.1.0"
}
@@ -17684,6 +17705,7 @@
"resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz",
"integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"memory-pager": "^1.0.2"
}
@@ -17990,8 +18012,7 @@
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.2.1.tgz",
"integrity": "sha512-/tBrSQ36vCleJkAOsy9kbNTgaxvGbyOamC30PRePTQe/o1MFwEKHQk4Cn7BNGaPtjp+PuUrByJehM1hgxfq4sw==",
- "license": "MIT",
- "peer": true
+ "license": "MIT"
},
"node_modules/tapable": {
"version": "2.3.0",
@@ -18251,6 +18272,7 @@
"resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz",
"integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"punycode": "^2.3.1"
},
@@ -18357,6 +18379,7 @@
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
"integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"prelude-ls": "^1.2.1"
},
@@ -18390,7 +18413,6 @@
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"license": "Apache-2.0",
- "peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@@ -18960,6 +18982,7 @@
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
"integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
"license": "BSD-2-Clause",
+ "peer": true,
"dependencies": {
"punycode": "^2.1.0"
}
@@ -19032,11 +19055,10 @@
}
},
"node_modules/vite": {
- "version": "7.3.1",
- "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz",
- "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==",
+ "version": "7.3.2",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.2.tgz",
+ "integrity": "sha512-Bby3NOsna2jsjfLVOHKes8sGwgl4TT0E6vvpYgnAYDIF/tie7MRaFthmKuHx1NSXjiTueXH3do80FMQgvEktRg==",
"license": "MIT",
- "peer": true,
"dependencies": {
"esbuild": "^0.27.0",
"fdir": "^6.5.0",
@@ -19407,7 +19429,6 @@
"integrity": "sha512-YbDrMF9jM2Lqc++2530UourxZHmkKLxrs4+mYhEwqWS97WJ7wOYEkcr+QfRgJ3PW9wz3odRijLZjHEaRLTNbqw==",
"devOptional": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"@vitest/expect": "4.1.0",
"@vitest/mocker": "4.1.0",
@@ -19502,7 +19523,6 @@
"resolved": "https://registry.npmjs.org/vue/-/vue-3.5.30.tgz",
"integrity": "sha512-hTHLc6VNZyzzEH/l7PFGjpcTvUgiaPK5mdLkbjrTeWSRcEfxFrv56g/XckIYlE9ckuobsdwqd5mk2g1sBkMewg==",
"license": "MIT",
- "peer": true,
"dependencies": {
"@vue/compiler-dom": "3.5.30",
"@vue/compiler-sfc": "3.5.30",
@@ -19539,7 +19559,6 @@
"resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-11.3.0.tgz",
"integrity": "sha512-1J+xDfDJTLhDxElkd3+XUhT7FYSZd2b8pa7IRKGxhWH/8yt6PTvi3xmWhGwhYT5EaXdatui11pF2R6tL73/zPA==",
"license": "MIT",
- "peer": true,
"dependencies": {
"@intlify/core-base": "11.3.0",
"@intlify/devtools-types": "11.3.0",
@@ -19561,7 +19580,6 @@
"resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.6.4.tgz",
"integrity": "sha512-Hz9q5sa33Yhduglwz6g9skT8OBPii+4bFn88w6J+J4MfEo4KRRpmiNG/hHHkdbRFlLBOqxN8y8gf2Fb0MTUgVg==",
"license": "MIT",
- "peer": true,
"dependencies": {
"@vue/devtools-api": "^6.6.4"
},
@@ -19578,7 +19596,6 @@
"integrity": "sha512-/htfTCMluQ+P2FISGAooul8kO4JMheOTCbCy4M6dYnYYjqLe3BExZudAua6MSIKSFYQtFOYAll7XobYwcpokGA==",
"devOptional": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"@volar/typescript": "2.4.28",
"@vue/language-core": "3.2.5"
@@ -19640,6 +19657,7 @@
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz",
"integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==",
"license": "BSD-2-Clause",
+ "peer": true,
"engines": {
"node": ">=12"
}
@@ -19655,6 +19673,7 @@
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz",
"integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"tr46": "^5.1.0",
"webidl-conversions": "^7.0.0"
@@ -19729,6 +19748,7 @@
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
"integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==",
"license": "MIT",
+ "peer": true,
"engines": {
"node": ">=0.10.0"
}
@@ -19909,6 +19929,7 @@
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
"integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
"license": "MIT",
+ "peer": true,
"engines": {
"node": ">=10"
},
@@ -19964,7 +19985,6 @@
"resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz",
"integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==",
"license": "MIT",
- "peer": true,
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}
diff --git a/server/api/ai-config/test-connection.post.ts b/server/api/ai-config/test-connection.post.ts
new file mode 100644
index 00000000..3999fc44
--- /dev/null
+++ b/server/api/ai-config/test-connection.post.ts
@@ -0,0 +1,70 @@
+import { eq } from 'drizzle-orm'
+import { z } from 'zod'
+import { aiConfig } from '../../database/schema'
+import type { SupportedProvider } from '../../utils/ai/provider'
+import { generateStructuredOutput } from '../../utils/ai/provider'
+import { createRateLimiter } from '../../utils/rateLimit'
+
+const limiter = createRateLimiter({ windowMs: 60_000, maxRequests: 5, message: 'Too many test connection requests. Please wait before retrying.' })
+
+const testSchema = z.object({
+ ok: z.boolean(),
+})
+
+/**
+ * POST /api/ai-config/test-connection
+ * Test the AI provider connection by sending a minimal prompt.
+ * Returns { success: true } if the provider responds, or an error message.
+ */
+export default defineEventHandler(async (event) => {
+ await limiter(event)
+ const session = await requirePermission(event, { scoring: ['read'] })
+ const orgId = session.session.activeOrganizationId
+
+ const config = await db.query.aiConfig.findFirst({
+ where: eq(aiConfig.organizationId, orgId),
+ })
+
+ if (!config) {
+ throw createError({
+ statusCode: 422,
+ statusMessage: 'AI provider not configured. Set up your AI provider in Settings → AI first.',
+ })
+ }
+
+ try {
+ await generateStructuredOutput(
+ {
+ provider: config.provider as SupportedProvider,
+ model: config.model,
+ apiKeyEncrypted: config.apiKeyEncrypted,
+ baseUrl: config.baseUrl,
+ maxTokens: 20,
+ },
+ {
+ system: 'Respond with ok: true',
+ prompt: 'Test connection',
+ schema: testSchema,
+ schemaName: 'TestConnection',
+ },
+ )
+
+ return { success: true }
+ }
+ catch (err: any) {
+ const message = err?.data?.statusMessage ?? err?.message ?? 'Unknown error'
+
+ // If it's our own decryption error, give a more helpful message
+ if (message.includes('decrypt')) {
+ throw createError({
+ statusCode: 422,
+ statusMessage: 'Failed to decrypt API key. If you recently changed BETTER_AUTH_SECRET, you need to re-enter your API key.',
+ })
+ }
+
+ throw createError({
+ statusCode: 422,
+ statusMessage: `Connection test failed: ${message}`,
+ })
+ }
+})
diff --git a/server/api/applications/[id]/analyze.post.ts b/server/api/applications/[id]/analyze.post.ts
index ccdb932a..d8499898 100644
--- a/server/api/applications/[id]/analyze.post.ts
+++ b/server/api/applications/[id]/analyze.post.ts
@@ -68,6 +68,7 @@ export default defineEventHandler(async (event) => {
// Fetch candidate documents (resume text)
const docs = await db.select({
+ id: document.id,
parsedContent: document.parsedContent,
type: document.type,
})
@@ -81,9 +82,17 @@ export default defineEventHandler(async (event) => {
const resumeText = extractResumeText(resumeDoc?.parsedContent)
if (!resumeText) {
+ // Resume document exists but parsing failed or was incomplete
+ if (resumeDoc) {
+ throw createError({
+ statusCode: 422,
+ statusMessage: 'Resume was uploaded but text extraction failed. Try re-parsing the document.',
+ data: { code: 'PARSE_FAILED', documentId: resumeDoc.id },
+ })
+ }
throw createError({
statusCode: 422,
- statusMessage: 'No parsed resume found for this candidate. Upload a resume first.',
+ statusMessage: 'No resume found for this candidate. Upload a resume first.',
})
}
diff --git a/server/api/candidates/[id].get.ts b/server/api/candidates/[id].get.ts
index 1c4d869a..c481b78f 100644
--- a/server/api/candidates/[id].get.ts
+++ b/server/api/candidates/[id].get.ts
@@ -21,7 +21,7 @@ export default defineEventHandler(async (event) => {
orderBy: (application, { desc }) => [desc(application.createdAt)],
},
documents: {
- columns: { id: true, type: true, originalFilename: true, mimeType: true, createdAt: true },
+ columns: { id: true, type: true, originalFilename: true, mimeType: true, parsedContent: true, createdAt: true },
orderBy: (document, { desc }) => [desc(document.createdAt)],
},
},
@@ -31,5 +31,13 @@ export default defineEventHandler(async (event) => {
throw createError({ statusCode: 404, statusMessage: 'Not found' })
}
- return result
+ // Replace heavy parsedContent with a lightweight `parsed` boolean
+ const { documents, ...rest } = result
+ return {
+ ...rest,
+ documents: documents.map(({ parsedContent, ...doc }) => ({
+ ...doc,
+ parsed: parsedContent != null,
+ })),
+ }
})
diff --git a/tests/unit/ai-config-encryption.test.ts b/tests/unit/ai-config-encryption.test.ts
new file mode 100644
index 00000000..880157cb
--- /dev/null
+++ b/tests/unit/ai-config-encryption.test.ts
@@ -0,0 +1,51 @@
+import { describe, it, expect } from 'vitest'
+import { encrypt, decrypt } from '../../server/utils/encryption'
+
+/**
+ * Tests for AI config encryption — directly addresses issue #130:
+ * "AI settings do not survive the docker rebuild."
+ *
+ * If BETTER_AUTH_SECRET changes between Docker rebuilds,
+ * all encrypted API keys become unreadable.
+ */
+describe('AI config encryption', () => {
+ const secret = 'test-secret-that-is-long-enough'
+ const altSecret = 'different-secret-after-rebuild'
+
+ it('round-trips an API key through encrypt → decrypt', () => {
+ const apiKey = 'sk-test-1234567890'
+ const encrypted = encrypt(apiKey, secret)
+ const decrypted = decrypt(encrypted, secret)
+
+ expect(decrypted).toBe(apiKey)
+ })
+
+ it('returns null when decrypting with a different secret (simulates BETTER_AUTH_SECRET change)', () => {
+ const apiKey = 'sk-test-1234567890'
+ const encrypted = encrypt(apiKey, secret)
+ const decrypted = decrypt(encrypted, altSecret)
+
+ expect(decrypted).toBeNull()
+ })
+
+ it('returns null for corrupted ciphertext', () => {
+ const decrypted = decrypt('not-valid-base64!!', secret)
+ expect(decrypted).toBeNull()
+ })
+
+ it('returns null for too-short ciphertext', () => {
+ const decrypted = decrypt(Buffer.from('short').toString('base64'), secret)
+ expect(decrypted).toBeNull()
+ })
+
+ it('produces different ciphertexts for the same plaintext (unique IVs)', () => {
+ const apiKey = 'sk-test-1234567890'
+ const encrypted1 = encrypt(apiKey, secret)
+ const encrypted2 = encrypt(apiKey, secret)
+
+ expect(encrypted1).not.toBe(encrypted2)
+ // But both should decrypt to the same value
+ expect(decrypt(encrypted1, secret)).toBe(apiKey)
+ expect(decrypt(encrypted2, secret)).toBe(apiKey)
+ })
+})
diff --git a/tests/unit/ai-config-schema.test.ts b/tests/unit/ai-config-schema.test.ts
new file mode 100644
index 00000000..652d4be7
--- /dev/null
+++ b/tests/unit/ai-config-schema.test.ts
@@ -0,0 +1,70 @@
+import { describe, it, expect } from 'vitest'
+import { createAiConfigSchema } from '../../server/utils/schemas/scoring'
+
+/**
+ * Validates the AI config schema accepts all supported providers,
+ * especially 'openai_compatible' (issue #130).
+ */
+describe('createAiConfigSchema', () => {
+ it('accepts openai_compatible provider with baseUrl', () => {
+ const result = createAiConfigSchema.safeParse({
+ provider: 'openai_compatible',
+ model: 'llama-3.1-8b',
+ apiKey: 'test-key',
+ baseUrl: 'http://localhost:11434/v1',
+ maxTokens: 4096,
+ })
+
+ expect(result.success).toBe(true)
+ })
+
+ it('accepts openai_compatible without baseUrl', () => {
+ const result = createAiConfigSchema.safeParse({
+ provider: 'openai_compatible',
+ model: 'custom-model',
+ apiKey: 'test-key',
+ })
+
+ expect(result.success).toBe(true)
+ })
+
+ it('accepts standard openai provider', () => {
+ const result = createAiConfigSchema.safeParse({
+ provider: 'openai',
+ model: 'gpt-4.1-mini',
+ apiKey: 'sk-test123',
+ })
+
+ expect(result.success).toBe(true)
+ })
+
+ it('rejects unknown provider', () => {
+ const result = createAiConfigSchema.safeParse({
+ provider: 'ollama',
+ model: 'llama-3.1',
+ apiKey: 'test',
+ })
+
+ expect(result.success).toBe(false)
+ })
+
+ it('rejects SSRF-risky baseUrl targeting cloud metadata', () => {
+ const result = createAiConfigSchema.safeParse({
+ provider: 'openai_compatible',
+ model: 'test',
+ apiKey: 'test',
+ baseUrl: 'http://169.254.169.254/latest/meta-data/',
+ })
+
+ expect(result.success).toBe(false)
+ })
+
+ it('allows apiKey to be omitted (for updates with existing key)', () => {
+ const result = createAiConfigSchema.safeParse({
+ provider: 'openai',
+ model: 'gpt-4.1-mini',
+ })
+
+ expect(result.success).toBe(true)
+ })
+})
diff --git a/tests/unit/create-org-auto-switch.test.ts b/tests/unit/create-org-auto-switch.test.ts
new file mode 100644
index 00000000..3af7b4b3
--- /dev/null
+++ b/tests/unit/create-org-auto-switch.test.ts
@@ -0,0 +1,86 @@
+import { describe, it, expect } from 'vitest'
+
+/**
+ * Unit tests for the auto-switch guard logic in create-org.vue.
+ *
+ * The auto-switch watcher should ONLY redirect when:
+ * - User has exactly 1 org
+ * - No active organization is set in the session
+ *
+ * It must NOT redirect when an active org already exists,
+ * because that means the user intentionally navigated to create-org
+ * (e.g. via the org switcher's "+ Create organization" link).
+ *
+ * Regression test for: https://github.com/reqcore-inc/reqcore/issues/131
+ */
+
+interface AutoSwitchContext {
+ loading: boolean
+ autoSwitched: boolean
+ viewMode: 'picker' | 'create' | 'join'
+ orgCount: number
+ activeOrg: { id: string; name: string } | null | undefined
+}
+
+/**
+ * Mirrors the guard logic from create-org.vue watcher.
+ * Returns true when auto-switch should fire.
+ */
+function shouldAutoSwitch(ctx: AutoSwitchContext): boolean {
+ if (ctx.loading || ctx.autoSwitched || ctx.viewMode !== 'picker') return false
+ if (ctx.orgCount === 1 && !ctx.activeOrg) return true
+ return false
+}
+
+describe('create-org auto-switch guard', () => {
+ const base: AutoSwitchContext = {
+ loading: false,
+ autoSwitched: false,
+ viewMode: 'picker',
+ orgCount: 1,
+ activeOrg: null,
+ }
+
+ it('auto-switches when user has 1 org and no active org (invite flow)', () => {
+ expect(shouldAutoSwitch({ ...base })).toBe(true)
+ })
+
+ it('does NOT auto-switch when user already has an active org (#131)', () => {
+ expect(
+ shouldAutoSwitch({
+ ...base,
+ activeOrg: { id: 'org-1', name: 'My Org' },
+ }),
+ ).toBe(false)
+ })
+
+ it('does NOT auto-switch when still loading', () => {
+ expect(shouldAutoSwitch({ ...base, loading: true })).toBe(false)
+ })
+
+ it('does NOT auto-switch when already auto-switched', () => {
+ expect(shouldAutoSwitch({ ...base, autoSwitched: true })).toBe(false)
+ })
+
+ it('does NOT auto-switch when viewMode is "create"', () => {
+ expect(shouldAutoSwitch({ ...base, viewMode: 'create' })).toBe(false)
+ })
+
+ it('does NOT auto-switch when viewMode is "join"', () => {
+ expect(shouldAutoSwitch({ ...base, viewMode: 'join' })).toBe(false)
+ })
+
+ it('does NOT auto-switch when user has 0 orgs', () => {
+ expect(shouldAutoSwitch({ ...base, orgCount: 0 })).toBe(false)
+ })
+
+ it('does NOT auto-switch when user has multiple orgs', () => {
+ expect(shouldAutoSwitch({ ...base, orgCount: 2 })).toBe(false)
+ })
+
+ it('does NOT auto-switch with multiple orgs even without active org', () => {
+ expect(
+ shouldAutoSwitch({ ...base, orgCount: 3, activeOrg: null }),
+ ).toBe(false)
+ })
+})