@consenti/api 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/README.md +143 -11
  2. package/dist/chunk-7NKW7UTJ.js +2 -0
  3. package/dist/chunk-7NKW7UTJ.js.map +1 -0
  4. package/dist/dashboard/assets/{ApiConfig-dmDLJNfI.js → ApiConfig-DLPkgWsM.js} +1 -1
  5. package/dist/dashboard/assets/{ArchivedProfileList-ChWp1RHE.js → ArchivedProfileList-fRvXAaWy.js} +1 -1
  6. package/dist/dashboard/assets/{AuditLog-3lekUOk8.js → AuditLog-9flkPiEQ.js} +1 -1
  7. package/dist/dashboard/assets/{ChangePassword-BYlF08cE.js → ChangePassword-D--mNLLR.js} +1 -1
  8. package/dist/dashboard/assets/{ConfirmDialog-sMBVcbiI.js → ConfirmDialog-CJAqYRfE.js} +1 -1
  9. package/dist/dashboard/assets/{ConsentList-DLDBSjxO.js → ConsentList-CyddMOrW.js} +1 -1
  10. package/dist/dashboard/assets/{ConsentTemplateEditor-0iB6G95-.js → ConsentTemplateEditor-DZGFzkTA.js} +1 -1
  11. package/dist/dashboard/assets/{ConsentTemplateList-BnU_nL8h.js → ConsentTemplateList-CwNVxHyZ.js} +1 -1
  12. package/dist/dashboard/assets/{HeadingTagInput-7owwn9Vj.js → HeadingTagInput-DGKZYLTR.js} +1 -1
  13. package/dist/dashboard/assets/{HowItWorks-B05y-6xX.js → HowItWorks-10CyO4Kp.js} +1 -1
  14. package/dist/dashboard/assets/{PermissionGate-D45f3WrO.js → PermissionGate-CkaZ9DKx.js} +1 -1
  15. package/dist/dashboard/assets/PreviewPane-ArCfR0c9.js +38 -0
  16. package/dist/dashboard/assets/{ProfileEditor-CuMRNzdM.js → ProfileEditor-BNUxXKMk.js} +1 -1
  17. package/dist/dashboard/assets/{ProfileList-CFZ_i1zk.js → ProfileList-Ti5ZML_L.js} +1 -1
  18. package/dist/dashboard/assets/{ProfileVersionHistory-40HtQSrd.js → ProfileVersionHistory-D2KtUROF.js} +1 -1
  19. package/dist/dashboard/assets/{RecordDetailModal-hpJgK02E.js → RecordDetailModal-4OyHjtux.js} +1 -1
  20. package/dist/dashboard/assets/{Reports-BlvcycZ8.js → Reports-CHpF_j2k.js} +1 -1
  21. package/dist/dashboard/assets/{RoleList-CTeQdx6f.js → RoleList-BICQW5gU.js} +1 -1
  22. package/dist/dashboard/assets/{Select-CeC6GRyb.js → Select-C7q2vevu.js} +1 -1
  23. package/dist/dashboard/assets/{Settings-DLsvGtMh.js → Settings-BlOcU0wL.js} +1 -1
  24. package/dist/dashboard/assets/{SetupWizard-BVzGrBm2.js → SetupWizard-DReZ5DWr.js} +1 -1
  25. package/dist/dashboard/assets/{Table-CcBP7eXS.js → Table-C59yMXY2.js} +1 -1
  26. package/dist/dashboard/assets/{TenantList-DLgPnlVv.js → TenantList-Bbwcxyo8.js} +1 -1
  27. package/dist/dashboard/assets/{UITemplateEditor-B9EBcvOB.js → UITemplateEditor-CR7TnHBH.js} +1 -1
  28. package/dist/dashboard/assets/{UITemplateList-CGYRRlCh.js → UITemplateList-NKwDQJAw.js} +1 -1
  29. package/dist/dashboard/assets/{UserList-BbpiUrVr.js → UserList-DMk0_UF0.js} +1 -1
  30. package/dist/dashboard/assets/{VendorList-mlOlW7ht.js → VendorList-BawJ8Kyc.js} +1 -1
  31. package/dist/dashboard/assets/{VisitorList-Dne7xkNx.js → VisitorList-BVSFRtul.js} +1 -1
  32. package/dist/dashboard/assets/check-BAoHs1bd.js +1 -0
  33. package/dist/dashboard/assets/{copy-FTaGH7-z.js → copy-DjZpSOQM.js} +1 -1
  34. package/dist/dashboard/assets/{external-link-5y29qnhn.js → external-link-CFayXpKz.js} +1 -1
  35. package/dist/dashboard/assets/{index-BaYVhd1M.js → index-D7qdwIxs.js} +2 -2
  36. package/dist/dashboard/assets/{profiles-D-hvl_fK.js → profiles-Jb6uV-0U.js} +1 -1
  37. package/dist/dashboard/assets/{roles-DZyZwZDr.js → roles-ChXEt79v.js} +1 -1
  38. package/dist/dashboard/assets/{rotate-ccw-clock-C5cVu2_f.js → rotate-ccw-clock-503u3yEE.js} +1 -1
  39. package/dist/dashboard/assets/{search-mdMD-WTj.js → search-B05qPv24.js} +1 -1
  40. package/dist/dashboard/assets/{square-pen-BoEcHaSW.js → square-pen-BX2H18m3.js} +1 -1
  41. package/dist/dashboard/assets/{templates-DLMsZs7d.js → templates-DalUjETH.js} +1 -1
  42. package/dist/dashboard/assets/{trash-BVDX09Xe.js → trash-Bfqti-Og.js} +1 -1
  43. package/dist/dashboard/assets/{useHashQuery-wXzkY-tk.js → useHashQuery-DzYBqPIg.js} +1 -1
  44. package/dist/dashboard/assets/{users-BLTbK4tk.js → users-CDdO4hYQ.js} +1 -1
  45. package/dist/dashboard/assets/x-Clw5rGTJ.js +1 -0
  46. package/dist/dashboard/index.html +1 -1
  47. package/dist/dist-MNR4WETI.js +2 -0
  48. package/dist/index.cjs +77 -75
  49. package/dist/index.cjs.map +1 -1
  50. package/dist/index.d.cts +45 -9
  51. package/dist/index.d.ts +45 -9
  52. package/dist/index.js +76 -74
  53. package/dist/index.js.map +1 -1
  54. package/dist/widget/widget.js +1 -1
  55. package/package.json +3 -2
  56. package/scripts/migrate-to-consenti-prefix.mjs +196 -0
  57. package/dist/chunk-COR72NFQ.js +0 -2
  58. package/dist/chunk-COR72NFQ.js.map +0 -1
  59. package/dist/dashboard/assets/PreviewPane-Czax39la.js +0 -38
  60. package/dist/dashboard/assets/check-IM5PInrX.js +0 -1
  61. package/dist/dashboard/assets/x-CtCy0YcJ.js +0 -1
  62. package/dist/dist-QJYW5KUK.js +0 -2
  63. /package/dist/{dist-QJYW5KUK.js.map → dist-MNR4WETI.js.map} +0 -0
package/dist/index.js CHANGED
@@ -1,48 +1,45 @@
1
- import{A as Gn,F as en,G as Kn,I as Yn,f as Xt,g as Xe,k as Zt,s as Ae,t as Ze,u as Wn,v as Jn,w as qn}from"./chunk-COR72NFQ.js";import"./chunk-S2IEYESR.js";import{EventEmitter as Dc}from"events";import{randomBytes as Uc}from"crypto";import{readFile as Ge}from"fs/promises";import{join as ae,extname as Gt,dirname as Lc}from"path";import{fileURLToPath as Hr}from"url";var et=class{plugins=[];register(e){this.plugins=e}async initialize(e){for(let t of this.plugins)try{await t.initialize?.(e)}catch(n){console.warn(`[Consenti] Plugin "${t.name}" initialize failed:`,n)}}async destroy(){for(let e of this.plugins)try{await e.destroy?.()}catch(t){console.warn(`[Consenti] Plugin "${e.name}" destroy failed:`,t)}}async runBeforeConsentSave(e){let t=e;for(let n of this.plugins)if(n.beforeConsentSave)try{t=await n.beforeConsentSave(t)}catch(s){console.warn(`[Consenti] Plugin "${n.name}" beforeConsentSave failed:`,s)}return t}async runAfterConsentSave(e){for(let t of this.plugins)if(t.afterConsentSave)try{await t.afterConsentSave(e)}catch(n){console.warn(`[Consenti] Plugin "${t.name}" afterConsentSave failed:`,n)}}async runBeforeConsentUpdate(e){let t=e;for(let n of this.plugins)if(n.beforeConsentUpdate)try{t=await n.beforeConsentUpdate(t)}catch(s){console.warn(`[Consenti] Plugin "${n.name}" beforeConsentUpdate failed:`,s)}return t}async runAfterConsentUpdate(e){for(let t of this.plugins)if(t.afterConsentUpdate)try{await t.afterConsentUpdate(e)}catch(n){console.warn(`[Consenti] Plugin "${t.name}" afterConsentUpdate failed:`,n)}}async runAfterProfileFetch(e){let t=e;for(let n of this.plugins)if(n.afterProfileFetch)try{t=await n.afterProfileFetch(t)}catch(s){console.warn(`[Consenti] Plugin "${n.name}" afterProfileFetch failed:`,s)}return t}async runBeforeUserCreate(e){let t=e;for(let n of this.plugins)if(n.beforeUserCreate)try{t=await n.beforeUserCreate(t)}catch(s){console.warn(`[Consenti] Plugin "${n.name}" beforeUserCreate failed:`,s)}return t}async runAfterUserCreate(e){for(let t of this.plugins)if(t.afterUserCreate)try{await t.afterUserCreate(e)}catch(n){console.warn(`[Consenti] Plugin "${t.name}" afterUserCreate failed:`,n)}}};import{copyFileSync as Gc,existsSync as Kc,mkdirSync as Yc}from"fs";import{extname as Qc,join as Xc}from"path";function zn(i,e,t={},n={},s="local"){let r={};return n.type!==void 0&&(r.type=n.type),n.gpc!==void 0&&(r.gpc=n.gpc),{appName:t.appName??"Consenti",appLogoPath:t?.appLogoPath??"/logo-dark.svg",hidePoweredBy:t.hidePoweredBy??!1,basePath:e,compliance:r,authMode:s}}function Qn(i){return`<script>(function(){function d(o){if(o&&typeof o==='object')Object.keys(o).forEach(function(k){d(o[k])});return Object.freeze(o)}Object.defineProperty(window,'__CONSENTI_CONFIG__',{value:d(${JSON.stringify(i).replace(/<\/script>/gi,"<\\/script>")}),writable:false,configurable:false,enumerable:true})})()</script>`}function ve(i,e){let t=Buffer.from(i).toString("utf-8");return t.includes("</head>")?t.replace("</head>",`${e}</head>`):t.replace("</body>",`${e}</body>`)}var Qr="https://vendor-list.consensu.org/v3/vendor-list.json";var pe=null,Xn=0,Pe=null;function Xr(i){return typeof i=="object"&&i!==null&&"vendors"in i&&"purposes"in i&&typeof i.vendors=="object"&&typeof i.purposes=="object"}async function me(){if(pe&&Date.now()-Xn<6048e5)return pe;try{let i=await fetch(Qr,{signal:AbortSignal.timeout(15e3)});if(!i.ok)return pe;let e=await i.json();if(!Xr(e))return pe;let t=e;return pe={gvlSpecificationVersion:t.gvlSpecificationVersion,vendorListVersion:t.vendorListVersion,tcfPolicyVersion:t.tcfPolicyVersion,vendors:t.vendors,purposes:t.purposes,raw:t},Xn=Date.now(),pe}catch{return pe}}function Zn(){Pe||(me(),Pe=setInterval(()=>{me()},6048e5))}function es(){Pe&&(clearInterval(Pe),Pe=null)}var Zr="https://vendor-list.consensu.org/v3/cmp-list.json";var ge=null,ts=0,Oe=null;function eo(i){return typeof i=="object"&&i!==null&&"cmps"in i&&typeof i.cmps=="object"}async function be(i=!1){if(!i&&ge&&Date.now()-ts<6048e5)return ge;try{let e=await fetch(Zr,{signal:AbortSignal.timeout(15e3)});if(!e.ok)return ge;let t=await e.json();if(!eo(t))return ge;let n=t;return ge={cmpListVersion:n.cmpListVersion,lastUpdated:n.lastUpdated,cmps:n.cmps},ts=Date.now(),ge}catch{return ge}}function ns(){Oe||(be(),Oe=setInterval(()=>{be()},6048e5))}function ss(){Oe&&(clearInterval(Oe),Oe=null)}var is={ConsentRecord:{type:"object",properties:{id:{type:"string"},visitorId:{type:"string"},profileId:{type:"string"},locale:{type:"string"},consentJson:{type:"object",additionalProperties:{type:"string",enum:["granted","denied","objected"]}},gpcDetected:{type:"boolean"},source:{type:"string",enum:["banner","api","import"]},ageVerified:{type:"boolean"},tcfString:{type:"string"},createdAt:{type:"string",format:"date-time"},updatedAt:{type:"string",format:"date-time"}}},CreateConsentInput:{type:"object",required:["visitorId","profileId","consentJson"],properties:{visitorId:{type:"string"},profileId:{type:"string"},consentJson:{type:"object",additionalProperties:{type:"string"}},gpcDetected:{type:"boolean",default:!1},source:{type:"string",enum:["banner","api","import"],default:"banner"},ageVerified:{type:"boolean"},parentalConsentToken:{type:"string"},tcfString:{type:"string"}}}},tn={openapi:"3.1.0",info:{title:"Consenti Public API",version:"0.1.0",description:"Visitor-facing endpoints for reading consent profiles and submitting/verifying consent. Rate-limited; no authentication required.",license:{name:"Apache 2.0",url:"https://www.apache.org/licenses/LICENSE-2.0"}},servers:[{url:"/consenti/api",description:"Consenti Public API"}],paths:{"/v1/profiles/{id}":{get:{tags:["Profile"],summary:"Get profile in its default locale",description:"Returns the resolved profile with banner and modal content for the profile's default locale. Includes `currentLocale`, `locales`, `cookies`, `mainBanner`, `preferenceModal`, and optionally `gpcBanner`.",parameters:[{name:"id",in:"path",required:!0,schema:{type:"string"}}],responses:{200:{description:"Resolved profile for the default locale"},404:{description:"Profile not found"}}}},"/v1/profiles/{id}/{locale}":{get:{tags:["Profile"],summary:"Get profile resolved for a specific locale",description:"Same response shape as `GET /v1/profiles/{id}` but resolved for the requested locale, falling back to the default locale for any missing translations.",parameters:[{name:"id",in:"path",required:!0,schema:{type:"string"}},{name:"locale",in:"path",required:!0,schema:{type:"string",example:"fr"}}],responses:{200:{description:"Resolved profile for the requested locale"},404:{description:"Profile not found"}}}},"/v1/consent":{post:{tags:["Consent"],summary:"Submit consent",requestBody:{required:!0,content:{"application/json":{schema:{$ref:"#/components/schemas/CreateConsentInput"}}}},responses:{200:{description:"Consent created or updated",content:{"application/json":{schema:{$ref:"#/components/schemas/ConsentRecord"}}}},400:{description:"Validation error"}}}},"/v1/consent/{visitorId}":{get:{tags:["Consent"],summary:"Get consent for visitor",parameters:[{name:"visitorId",in:"path",required:!0,schema:{type:"string"}}],responses:{200:{description:"Consent record",content:{"application/json":{schema:{$ref:"#/components/schemas/ConsentRecord"}}}},404:{description:"Not found"}}},put:{tags:["Consent"],summary:"Update consent",parameters:[{name:"visitorId",in:"path",required:!0,schema:{type:"string"}}],responses:{200:{description:"Updated consent"}}},delete:{tags:["Consent"],summary:"GDPR erasure \u2014 delete all consent data for visitor",parameters:[{name:"visitorId",in:"path",required:!0,schema:{type:"string"}}],responses:{200:{description:"Erased successfully"}}}},"/v1/consent/{visitorId}/verify":{get:{tags:["Consent"],summary:"Verify consent validity",parameters:[{name:"visitorId",in:"path",required:!0,schema:{type:"string"}}],responses:{200:{description:"Verification result"}}}},"/v1/consent/{visitorId}/parental-consent-request":{post:{tags:["Consent"],summary:"Request a parental-consent token (stateless hook)",description:"Issues a token \u2014 signed with `compliance.dataSigningHash` when configured, otherwise unsigned \u2014 for a host's own out-of-band parental-verification flow. Emits `consent.parentalConsentRequired`. Not by itself sufficient verifiable parental consent (COPPA-style VPC is left to the host).",parameters:[{name:"visitorId",in:"path",required:!0,schema:{type:"string"}}],requestBody:{required:!0,content:{"application/json":{schema:{type:"object",required:["profileId"],properties:{profileId:{type:"string"}}}}}},responses:{200:{description:"Token issued",content:{"application/json":{schema:{type:"object",properties:{token:{type:"string"}}}}}},400:{description:"Validation error"},403:{description:"Visitor ownership check failed"},404:{description:"Profile not found"}}}},"/v1/consent/parental-consent-resolve":{post:{tags:["Consent"],summary:"Resolve a parental-consent token",description:"Recovers `visitorId`/`profileId` from a token issued by the request route above and emits `consent.parentalConsentGranted` \u2014 the host's own backend listener decides what \"granted\" means for stored consent. No visitor-ownership cookie required, since this is typically called from the parent's own device. Token replay is not prevented (stateless by design).",requestBody:{required:!0,content:{"application/json":{schema:{type:"object",required:["token"],properties:{token:{type:"string"}}}}}},responses:{200:{description:"Token resolved",content:{"application/json":{schema:{type:"object",properties:{visitorId:{type:"string"},profileId:{type:"string"}}}}}},400:{description:"Validation error"},403:{description:"Invalid, tampered, or expired token"}}}},"/v1/notice-shown":{post:{tags:["Consent"],summary:"Record proof that the consent banner was shown",description:"Lightweight, fire-and-forget ping recording that the banner was rendered to a visitor, independent of whether they ever interact with it. Called automatically by `@consenti/ui` on banner mount when `api.enabled` is configured.",requestBody:{required:!0,content:{"application/json":{schema:{type:"object",required:["visitorId","profileId","locale"],properties:{visitorId:{type:"string"},profileId:{type:"string"},locale:{type:"string"}}}}}},responses:{201:{description:"Recorded"},400:{description:"Validation error"}}}}},components:{schemas:is}},nn={openapi:"3.1.0",info:{title:"Consenti Admin API",version:"0.1.0",description:"Authenticated endpoints for managing profiles, consent records, users, analytics, and exports. All routes (except `/auth/login`) require `Authorization: Bearer <JWT>`. Obtain a token via `POST /auth/login`.",license:{name:"Apache 2.0",url:"https://www.apache.org/licenses/LICENSE-2.0"}},servers:[{url:"/consenti/admin/v1",description:"Consenti Admin API"}],paths:{"/auth/login":{post:{tags:["Auth"],summary:"Login with email + password",requestBody:{required:!0,content:{"application/json":{schema:{type:"object",properties:{email:{type:"string"},password:{type:"string"}}}}}},responses:{200:{description:"JWT token"}}}},"/auth/me":{get:{tags:["Auth"],summary:"Get current user",security:[{BearerAuth:[]}],responses:{200:{description:"Current admin user info"}}}},"/auth/logout":{post:{tags:["Auth"],summary:"Invalidate session",security:[{BearerAuth:[]}],responses:{200:{description:"Logged out"}}}},"/auth/refresh":{post:{tags:["Auth"],summary:"Reissue a fresh token from the current one, extending the session",description:"Used by the dashboard on user activity to implement a sliding inactivity timeout instead of a flat expiry from login.",security:[{BearerAuth:[]}],responses:{200:{description:"New JWT token"},401:{description:"Current token is missing, invalid, or already expired"}}}},"/auth/oidc/authorize":{get:{tags:["Auth"],summary:"Start OIDC authorization (PKCE)",responses:{302:{description:"Redirect to OIDC provider"}}}},"/auth/oidc/callback":{get:{tags:["Auth"],summary:"OIDC callback \u2014 exchange code for token",responses:{200:{description:"JWT token"}}}},"/auth/saml/metadata":{get:{tags:["Auth"],summary:"SAML SP metadata XML",responses:{200:{description:"SAML metadata XML",content:{"application/xml":{}}}}}},"/auth/saml/acs":{post:{tags:["Auth"],summary:"SAML Assertion Consumer Service",responses:{200:{description:"JWT token"}}}},"/auth/totp/setup":{post:{tags:["Auth"],summary:"Setup TOTP MFA",security:[{BearerAuth:[]}],responses:{200:{description:"TOTP secret and QR URL"}}}},"/auth/totp/verify":{post:{tags:["Auth"],summary:"Verify and enable TOTP",security:[{BearerAuth:[]}],responses:{200:{description:"TOTP enabled"}}}},"/profiles":{get:{tags:["Profiles"],summary:"List profiles",security:[{BearerAuth:[]}],responses:{200:{description:"Array of profiles"}}},post:{tags:["Profiles"],summary:"Create profile",security:[{BearerAuth:[]}],responses:{201:{description:"Created profile"}}}},"/consent-templates":{get:{tags:["Consent Templates"],summary:"List consent templates",description:"A Consent Template groups parameters (`cookies`, keyed by id) with the categories that own their legal basis (`categories`, keyed by id).",security:[{BearerAuth:[]}],responses:{200:{description:"Array of consent templates"}}},post:{tags:["Consent Templates"],summary:"Create consent template",security:[{BearerAuth:[]}],requestBody:{required:!0,content:{"application/json":{schema:{type:"object",required:["name","cookies","categories"],properties:{name:{type:"string"},cookies:{type:"object",description:"Keyed by parameter id."},categories:{type:"object",description:"Keyed by category id. Every parameter id must appear in exactly one category."}}}}}},responses:{201:{description:"Created consent template"},400:{description:"Validation error"}}}},"/consent-templates/{id}":{get:{tags:["Consent Templates"],summary:"Get consent template",security:[{BearerAuth:[]}],parameters:[{name:"id",in:"path",required:!0,schema:{type:"string"}}],responses:{200:{description:"Consent template"},404:{description:"Not found"}}},put:{tags:["Consent Templates"],summary:"Update consent template",security:[{BearerAuth:[]}],parameters:[{name:"id",in:"path",required:!0,schema:{type:"string"}}],responses:{200:{description:"Updated consent template"},422:{description:"Change would break compliance for profiles using this template"}}},delete:{tags:["Consent Templates"],summary:"Delete consent template",security:[{BearerAuth:[]}],parameters:[{name:"id",in:"path",required:!0,schema:{type:"string"}}],responses:{200:{description:"Deleted"},422:{description:"Template is still used by profiles"}}}},"/consent-templates/{id}/copy":{post:{tags:["Consent Templates"],summary:"Duplicate a consent template",security:[{BearerAuth:[]}],parameters:[{name:"id",in:"path",required:!0,schema:{type:"string"}}],responses:{201:{description:"Copied consent template"}}}},"/consent-templates/{id}/profile-usage":{get:{tags:["Consent Templates"],summary:"List profiles using this consent template",security:[{BearerAuth:[]}],parameters:[{name:"id",in:"path",required:!0,schema:{type:"string"}}],responses:{200:{description:"Array of profile summaries"}}}},"/consents":{get:{tags:["Consents"],summary:"List consent records",security:[{BearerAuth:[]}],parameters:[{name:"page",in:"query",schema:{type:"integer"}},{name:"profileId",in:"query",schema:{type:"string"}},{name:"from",in:"query",schema:{type:"string",format:"date"}},{name:"to",in:"query",schema:{type:"string",format:"date"}},{name:"q",in:"query",schema:{type:"string"},description:"Free-text search across visitorId, profileId, locale, source"}],responses:{200:{description:"Paginated consent records"}}}},"/stats/overview":{get:{tags:["Stats"],summary:"Overview statistics",security:[{BearerAuth:[]}],responses:{200:{description:"Totals and acceptance rates"}}}},"/stats/timeline":{get:{tags:["Stats"],summary:"Daily consent timeline",security:[{BearerAuth:[]}],parameters:[{name:"days",in:"query",schema:{type:"integer",default:30}}],responses:{200:{description:"Array of date+count"}}}},"/stats/countries":{get:{tags:["Stats"],summary:"Consent by country",security:[{BearerAuth:[]}],responses:{200:{description:"Country counts"}}}},"/stats/gpc":{get:{tags:["Stats"],summary:"GPC detection stats",security:[{BearerAuth:[]}],responses:{200:{description:"GPC rate and counts"}}}},"/export/consents":{get:{tags:["Export"],summary:"Export consent records",security:[{BearerAuth:[]}],parameters:[{name:"format",in:"query",schema:{type:"string",enum:["csv","json"]}},{name:"from",in:"query",schema:{type:"string",format:"date"}},{name:"to",in:"query",schema:{type:"string",format:"date"}}],responses:{200:{description:"CSV or JSON download"}}}},"/tenants":{get:{tags:["Sites"],summary:"List all sites/tenants",security:[{BearerAuth:[]}],responses:{200:{description:"Array of tenants"}}},post:{tags:["Sites"],summary:"Create a site/tenant",security:[{BearerAuth:[]}],requestBody:{required:!0,content:{"application/json":{schema:{type:"object",properties:{name:{type:"string"},slug:{type:"string"}},required:["name","slug"]}}}},responses:{201:{description:"Created tenant"}}}},"/tenants/{id}":{put:{tags:["Sites"],summary:"Update a site/tenant",security:[{BearerAuth:[]}],parameters:[{name:"id",in:"path",required:!0,schema:{type:"string"}}],responses:{200:{description:"Updated tenant"}}},delete:{tags:["Sites"],summary:"Delete a site/tenant",security:[{BearerAuth:[]}],parameters:[{name:"id",in:"path",required:!0,schema:{type:"string"}}],responses:{200:{description:"Deleted"}}}},"/setup/status":{get:{tags:["Setup"],summary:"Whether the first-run setup wizard has been completed for this tenant",security:[{BearerAuth:[]}],responses:{200:{description:"{ completed: boolean }"}}}},"/setup/config":{get:{tags:["Setup"],summary:"Resolved server config (secrets redacted) plus production-readiness flags",description:"The same merged `DEFAULT_CONFIG` + user config `createConsenti` computes at boot. `auth.adminPassword`, `auth.masterSecret`, `compliance.dataSigningHash`, `compliance.dataSigningHash`, storage credentials, and OIDC/SAML secrets are replaced with a redaction marker.",security:[{BearerAuth:[]}],responses:{200:{description:"{ config: object, usingJsonStorage: boolean, usingDefaultCredentials: boolean }"}}}},"/setup/compliance-groups":{get:{tags:["Setup"],summary:"The 8 built-in compliance groups with label/description/regulation metadata",security:[{BearerAuth:[]}],responses:{200:{description:"Array of compliance group descriptors"}}}},"/setup/seed-profiles":{post:{tags:["Setup"],summary:"Seed default profiles for the selected compliance groups",description:"Idempotent \u2014 skips any group that already has an active profile. `groups` must be a subset of the 8 built-in compliance group ids.",security:[{BearerAuth:[]}],requestBody:{required:!0,content:{"application/json":{schema:{type:"object",properties:{groups:{type:"array",items:{type:"string"}}},required:["groups"]}}}},responses:{200:{description:"{ seeded: string[] }"},400:{description:"groups is missing, not an array of strings, or contains an unknown compliance group id"},409:{description:"Setup has already been completed for this tenant"}}}},"/setup/complete":{post:{tags:["Setup"],summary:"Mark the first-run setup wizard as complete for this tenant",description:"Called on both wizard finish and skip. Never reset from the dashboard once true.",security:[{BearerAuth:[]}],responses:{200:{description:"Updated tenant settings"},409:{description:"Setup has already been completed for this tenant"}}}}},components:{securitySchemes:{BearerAuth:{type:"http",scheme:"bearer",bearerFormat:"JWT"}},schemas:is}};import{createHash as os,createHmac as to,randomUUID as A,scryptSync as as,randomBytes as cs,timingSafeEqual as sn}from"crypto";var rs="0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",no=16;function so(i){let e=cs(i),t="";for(let n=0;n<i;n++)t+=rs[e[n]%rs.length];return t}function De(i){return`${i}_${so(no)}`}function V(){return De("prof")}function H(){return De("visi")}function W(){return De("cons")}function J(){return De("ctem")}function q(){return De("utem")}function io(i){if(i.includes(":")){let t=i.split(":");for(;t.length<8;)t.push("0");return t.slice(0,3).concat(["0","0","0","0","0"]).join(":")}let e=i.split(".");return e.length===4?(e[3]="0",e.join(".")):i}function ls(i,e=""){return os("sha256").update(e+io(i)).digest("hex")}function ds(i){return os("sha256").update(i).digest("hex")}function Z(i,e){return to("sha256",e).update(i).digest("hex")}function tt(i,e,t){let n=Buffer.from(Z(i,t),"hex"),s=Buffer.from(e,"hex");return n.length!==s.length?!1:sn(n,s)}function Ue(i){let e=cs(16).toString("hex"),t=as(i,e,64).toString("hex");return`${e}:${t}`}function us(i,e){let[t,n]=e.split(":");if(!t||!n)return!1;let s=as(i,t,64).toString("hex");return sn(Buffer.from(n,"hex"),Buffer.from(s,"hex"))}function rn(i,e,t=3600){let n=Math.floor(Date.now()/1e3),s=btoa(JSON.stringify({alg:"HS256",typ:"JWT"})).replace(/=/g,""),r=btoa(JSON.stringify({...i,iat:n,exp:n+t})).replace(/=/g,""),o=Z(`${s}.${r}`,e).match(/.{2}/g).map(c=>String.fromCharCode(parseInt(c,16))).join(""),a=btoa(o).replace(/\+/g,"-").replace(/\//g,"_").replace(/=/g,"");return`${s}.${r}.${a}`}function nt(i,e){let t=i.split(".");if(t.length!==3)return null;let[n,s,r]=t;if(!n||!s||!r)return null;let o=Z(`${n}.${s}`,e),a=atob(r.replace(/-/g,"+").replace(/_/g,"/")),c=Array.from(a).map(u=>u.charCodeAt(0).toString(16).padStart(2,"0")).join(""),l=Buffer.from(o,"hex"),d=Buffer.from(c,"hex");if(l.length!==d.length||!sn(l,d))return null;try{let u=JSON.parse(atob(s)),p=u.exp;return typeof p=="number"&&Date.now()/1e3>p?null:u}catch{return null}}import{existsSync as ro,accessSync as oo,constants as ao,mkdirSync as st}from"fs";import{resolve as ps,extname as co,dirname as ms,join as it}from"path";import{fileURLToPath as lo}from"url";var uo={"node:sqlite":"consenti.db","better-sqlite3":"consenti.db","node-sqlite3-wasm":"consenti.db",sqlite:"consenti.db",json:"consenti-data.json",mongodb:"",mysql:"",postgresql:""},po=ms(lo(import.meta.url));function mo(i){let e=i.includes("node_modules"),t=i.startsWith(ps(po,"..",".."));return e||t}function ne(i,e){let t=ps(i),n=co(t);if(n&&(console.warn(`[consenti] storage.path "${i}" looks like a file path (has extension "${n}"). Treating parent directory as storage root. Update your config to pass a directory path.`),t=ms(t)),mo(t))throw new Error(`[consenti] storage.path "${t}" is inside the package installation directory. Choose a writable directory outside node_modules.`);ro(t)||st(t,{recursive:!0});try{oo(t,ao.W_OK)}catch{throw new Error(`[consenti] storage.path "${t}" is not writable.`)}let s=it(t,"db"),r=it(t,"profiles"),o=it(t,"logs");st(s,{recursive:!0}),st(r,{recursive:!0}),st(o,{recursive:!0});let a=uo[e]??"consenti.db",c=a?it(s,a):"";return{basePath:t,dbPath:c,profilesDir:r,logsDir:o}}var hs={name:"tenants",cols:[{n:"id",t:"pk"},{n:"name",t:"text"},{n:"slug",t:"text",unique:!0},{n:"created_at",t:"ts",def:"$now"},{n:"updated_at",t:"ts",def:"$now",mysqlUpd:!0}]},ys={name:"profiles",cols:[{n:"id",t:"pk"},{n:"tenant_id",t:"text",ref:"tenants"},{n:"name",t:"text"},{n:"default_locale",t:"text_sm",def:"en"},{n:"version",t:"int",def:1},{n:"profile_json",t:"json_ordered"},{n:"created_at",t:"ts",def:"$now"},{n:"updated_at",t:"ts",def:"$now",mysqlUpd:!0}],idx:[{cols:["tenant_id"]}]},ws={name:"consent_records",cols:[{n:"id",t:"pk"},{n:"tenant_id",t:"text",ref:"tenants"},{n:"visitor_id",t:"text"},{n:"profile_id",t:"text",ref:"profiles"},{n:"locale",t:"text_sm"},{n:"consent_json",t:"json"},{n:"gpc_detected",t:"bool",def:!1},{n:"source",t:"text_sm"},{n:"age_verified",t:"bool",def:!1},{n:"parental_consent_token",t:"longtext",null:!0},{n:"tcf_string",t:"longtext",null:!0},{n:"gpp_string",t:"longtext",null:!0},{n:"signature",t:"longtext",null:!0},{n:"created_at",t:"ts",def:"$now"},{n:"updated_at",t:"ts",def:"$now",mysqlUpd:!0}],uq:[{cols:["visitor_id","profile_id"]}],idx:[{cols:["tenant_id","created_at"]},{cols:["tenant_id","profile_id","created_at"]},{cols:["locale"]},{cols:["source"]}]},Es={name:"consent_history",cols:[{n:"id",t:"pk"},{n:"tenant_id",t:"text",ref:"tenants"},{n:"consent_record_id",t:"text",ref:"consent_records"},{n:"visitor_id",t:"text"},{n:"old_json",t:"json",null:!0},{n:"new_json",t:"json"},{n:"action",t:"text_sm"},{n:"created_at",t:"ts",def:"$now"}],idx:[{cols:["visitor_id"]}]},Rs={name:"visitors",cols:[{n:"id",t:"pk"},{n:"tenant_id",t:"text",ref:"tenants"},{n:"visitor_id",t:"text",unique:!0},{n:"country",t:"text_geo",null:!0},{n:"region",t:"text_md",null:!0},{n:"city",t:"text_md",null:!0},{n:"ip_hash",t:"text_hash",null:!0},{n:"user_agent_hash",t:"text_hash",null:!0},{n:"first_seen",t:"ts",def:"$now"},{n:"last_seen",t:"ts",def:"$now",mysqlUpd:!0}],idx:[{cols:["tenant_id","first_seen"]},{cols:["country"]}]},Ss={name:"notice_shown",cols:[{n:"id",t:"pk"},{n:"tenant_id",t:"text",ref:"tenants"},{n:"visitor_id",t:"text"},{n:"profile_id",t:"text",ref:"profiles"},{n:"locale",t:"text_sm"},{n:"created_at",t:"ts",def:"$now"}],idx:[{cols:["visitor_id"]}]},on={name:"users",cols:[{n:"id",t:"pk"},{n:"tenant_id",t:"text",ref:"tenants"},{n:"name",t:"text"},{n:"email",t:"text",unique:!0},{n:"password_hash",t:"text"},{n:"is_active",t:"bool",def:!0},{n:"totp_secret",t:"longtext",null:!0},{n:"totp_enabled",t:"bool",def:!1},{n:"created_at",t:"ts",def:"$now"},{n:"updated_at",t:"ts",def:"$now",mysqlUpd:!0}]},go={...on,cols:[...on.cols,{n:"allowed_tenants",t:"json",null:!0}]},Cs={name:"roles",cols:[{n:"id",t:"pk"},{n:"tenant_id",t:"text",ref:"tenants"},{n:"name",t:"text_md"},{n:"description",t:"text",null:!0}]},Is={name:"permissions",cols:[{n:"id",t:"pk"},{n:"name",t:"text_md",unique:!0},{n:"description",t:"text",null:!0}]},Ts={name:"user_roles",cols:[{n:"user_id",t:"text",ref:"users"},{n:"role_id",t:"text",ref:"roles"},{n:"tenant_id",t:"text",def:"default"}],pk:["user_id","role_id","tenant_id"],idx:[{cols:["user_id"]}]},As={name:"role_permissions",cols:[{n:"role_id",t:"text",ref:"roles"},{n:"permission_id",t:"text",ref:"permissions"}],pk:["role_id","permission_id"],idx:[{cols:["role_id"]}]},vs={name:"audit_logs",cols:[{n:"id",t:"pk"},{n:"tenant_id",t:"text",ref:"tenants"},{n:"user_id",t:"text",null:!0},{n:"action",t:"text_md"},{n:"resource_type",t:"text_md"},{n:"resource_id",t:"text",null:!0},{n:"old_data",t:"json",null:!0},{n:"new_data",t:"json",null:!0},{n:"created_at",t:"ts",def:"$now"}],idx:[{cols:["tenant_id","created_at"]},{cols:["action"]},{cols:["resource_type"]},{cols:["resource_id"]},{cols:["user_id"]}]},Ps={name:"api_keys",cols:[{n:"id",t:"pk"},{n:"tenant_id",t:"text",ref:"tenants",onDel:"CASCADE"},{n:"key_hash",t:"text_hash",unique:!0},{n:"name",t:"text"},{n:"is_active",t:"bool",def:!0},{n:"created_by",t:"text",null:!0},{n:"expire_by",t:"ts",null:!0},{n:"created_at",t:"ts",def:"$now"},{n:"updated_at",t:"ts",def:"$now",mysqlUpd:!0}]},Os={name:"tenant_settings",cols:[{n:"tenant_id",t:"text",ref:"tenants",onDel:"CASCADE"},{n:"allowed_origins_json",t:"json",def:"$arr"},{n:"admin_allowed_origins_json",t:"json",def:"$arr"},{n:"setup_completed",t:"bool",def:!1},{n:"profiles_seeded",t:"bool",def:!1},{n:"tcf_confirmation_json",t:"json",null:!0},{n:"gpp_confirmation_json",t:"json",null:!0},{n:"updated_at",t:"ts",def:"$now",mysqlUpd:!0}],pk:["tenant_id"]},bs={name:"consent_templates",cols:[{n:"id",t:"pk"},{n:"tenant_id",t:"text",ref:"tenants",onDel:"CASCADE"},{n:"name",t:"text"},{n:"cookies_json",t:"json_ordered",def:"$obj"},{n:"categories_json",t:"json_ordered",def:"$obj"},{n:"created_at",t:"ts",def:"$now"},{n:"updated_at",t:"ts",def:"$now",mysqlUpd:!0}]},Ds={name:"ui_templates",cols:[{n:"id",t:"pk"},{n:"tenant_id",t:"text",ref:"tenants",onDel:"CASCADE"},{n:"name",t:"text"},{n:"settings_json",t:"json_ordered",def:"$obj"},{n:"created_at",t:"ts",def:"$now"},{n:"updated_at",t:"ts",def:"$now",mysqlUpd:!0}]},fo=[hs,ys,ws,Es,Rs,go,Cs,Is,Ts,As,vs,Ps,bs,Ds,Os,Ss],an=[hs,ys,ws,Es,Rs,on,Cs,Is,Ts,As,vs,Ps,bs,Ds,Os,Ss];function _o(i){return i==="$now"?"DEFAULT (datetime('now'))":i==="$arr"?"DEFAULT '[]'":i==="$obj"?"DEFAULT '{}'":i===!0?"DEFAULT 1":i===!1?"DEFAULT 0":typeof i=="string"?`DEFAULT '${i}'`:`DEFAULT ${i}`}function ho(i){if(i.t==="pk")return`${i.n} TEXT PRIMARY KEY`;let e=i.t==="int"||i.t==="bool"?"INTEGER":"TEXT",t=[`${i.n} ${e}`];return i.null||t.push("NOT NULL"),i.def!==void 0&&t.push(_o(i.def)),i.unique&&t.push("UNIQUE"),i.ref&&(t.push(`REFERENCES ${i.ref}(id)`),i.onDel&&t.push(`ON DELETE ${i.onDel}`)),t.join(" ")}function gs(i){let e=i.cols.map(t=>` ${ho(t)}`);if(i.pk&&e.push(` PRIMARY KEY (${i.pk.join(", ")})`),i.uq)for(let t of i.uq)e.push(` UNIQUE(${t.cols.join(", ")})`);return`CREATE TABLE IF NOT EXISTS ${i.name} (
1
+ import{B as ds,G as cn,H as us,J as ps,f as on,g as it,k as an,s as De,t as rt,u as os,v as as,w as cs,x as ls}from"./chunk-7NKW7UTJ.js";import"./chunk-S2IEYESR.js";import{EventEmitter as cl}from"events";import{randomBytes as ll}from"crypto";import{readFile as Ze}from"fs/promises";import{join as le,extname as en,dirname as dl}from"path";import{fileURLToPath as go}from"url";var ot=class{plugins=[];register(e){this.plugins=e}async initialize(e){for(let t of this.plugins)try{await t.initialize?.(e)}catch(n){console.warn(`[Consenti] Plugin "${t.name}" initialize failed:`,n)}}async destroy(){for(let e of this.plugins)try{await e.destroy?.()}catch(t){console.warn(`[Consenti] Plugin "${e.name}" destroy failed:`,t)}}async runBeforeConsentSave(e){let t=e;for(let n of this.plugins)if(n.beforeConsentSave)try{t=await n.beforeConsentSave(t)}catch(s){console.warn(`[Consenti] Plugin "${n.name}" beforeConsentSave failed:`,s)}return t}async runAfterConsentSave(e){for(let t of this.plugins)if(t.afterConsentSave)try{await t.afterConsentSave(e)}catch(n){console.warn(`[Consenti] Plugin "${t.name}" afterConsentSave failed:`,n)}}async runBeforeConsentUpdate(e){let t=e;for(let n of this.plugins)if(n.beforeConsentUpdate)try{t=await n.beforeConsentUpdate(t)}catch(s){console.warn(`[Consenti] Plugin "${n.name}" beforeConsentUpdate failed:`,s)}return t}async runAfterConsentUpdate(e){for(let t of this.plugins)if(t.afterConsentUpdate)try{await t.afterConsentUpdate(e)}catch(n){console.warn(`[Consenti] Plugin "${t.name}" afterConsentUpdate failed:`,n)}}async runAfterProfileFetch(e){let t=e;for(let n of this.plugins)if(n.afterProfileFetch)try{t=await n.afterProfileFetch(t)}catch(s){console.warn(`[Consenti] Plugin "${n.name}" afterProfileFetch failed:`,s)}return t}async runBeforeUserCreate(e){let t=e;for(let n of this.plugins)if(n.beforeUserCreate)try{t=await n.beforeUserCreate(t)}catch(s){console.warn(`[Consenti] Plugin "${n.name}" beforeUserCreate failed:`,s)}return t}async runAfterUserCreate(e){for(let t of this.plugins)if(t.afterUserCreate)try{await t.afterUserCreate(e)}catch(n){console.warn(`[Consenti] Plugin "${t.name}" afterUserCreate failed:`,n)}}};import{copyFileSync as Il,existsSync as Tl,mkdirSync as Al}from"fs";import{extname as Pl,join as Ol}from"path";function ms(i,e,t={},n={},s="local"){let r={};return n.type!==void 0&&(r.type=n.type),n.gpc!==void 0&&(r.gpc=n.gpc),{appName:t.appName??"Consenti",appLogoPath:t?.appLogoPath??"/logo-dark.svg",hidePoweredBy:t.hidePoweredBy??!1,basePath:e,compliance:r,authMode:s}}function gs(i){return`<script>(function(){function d(o){if(o&&typeof o==='object')Object.keys(o).forEach(function(k){d(o[k])});return Object.freeze(o)}Object.defineProperty(window,'__CONSENTI_CONFIG__',{value:d(${JSON.stringify(i).replace(/<\/script>/gi,"<\\/script>")}),writable:false,configurable:false,enumerable:true})})()</script>`}function Le(i,e){let t=Buffer.from(i).toString("utf-8");return t.includes("</head>")?t.replace("</head>",`${e}</head>`):t.replace("</body>",`${e}</body>`)}var Io="https://vendor-list.consensu.org/v3/vendor-list.json";var fe=null,fs=0,Ue=null;function To(i){return typeof i=="object"&&i!==null&&"vendors"in i&&"purposes"in i&&typeof i.vendors=="object"&&typeof i.purposes=="object"}async function _e(){if(fe&&Date.now()-fs<6048e5)return fe;try{let i=await fetch(Io,{signal:AbortSignal.timeout(15e3)});if(!i.ok)return fe;let e=await i.json();if(!To(e))return fe;let t=e;return fe={gvlSpecificationVersion:t.gvlSpecificationVersion,vendorListVersion:t.vendorListVersion,tcfPolicyVersion:t.tcfPolicyVersion,vendors:t.vendors,purposes:t.purposes,raw:t},fs=Date.now(),fe}catch{return fe}}function _s(){Ue||(_e(),Ue=setInterval(()=>{_e()},6048e5))}function hs(){Ue&&(clearInterval(Ue),Ue=null)}var Ao="https://vendor-list.consensu.org/v3/cmp-list.json";var he=null,ys=0,Ne=null;function vo(i){return typeof i=="object"&&i!==null&&"cmps"in i&&typeof i.cmps=="object"}async function ke(i=!1){if(!i&&he&&Date.now()-ys<6048e5)return he;try{let e=await fetch(Ao,{signal:AbortSignal.timeout(15e3)});if(!e.ok)return he;let t=await e.json();if(!vo(t))return he;let n=t;return he={cmpListVersion:n.cmpListVersion,lastUpdated:n.lastUpdated,cmps:n.cmps},ys=Date.now(),he}catch{return he}}function ws(){Ne||(ke(),Ne=setInterval(()=>{ke()},6048e5))}function Es(){Ne&&(clearInterval(Ne),Ne=null)}var Rs={ConsentRecord:{type:"object",properties:{id:{type:"string"},visitorId:{type:"string"},profileId:{type:"string"},profileVersion:{type:"integer",description:"Profile version in force when this consent was last given; with `profileId` it pins the exact on-disk snapshot (the banner text shown). Absent on records created before it was tracked."},locale:{type:"string"},consentJson:{type:"object",additionalProperties:{type:"string",enum:["granted","denied","objected"]}},gpcDetected:{type:"boolean"},source:{type:"string",enum:["banner","api","import"]},ageVerified:{type:"boolean"},tcfString:{type:"string"},createdAt:{type:"string",format:"date-time"},updatedAt:{type:"string",format:"date-time"}}},CreateConsentInput:{type:"object",required:["visitorId","profileId","consentJson"],properties:{visitorId:{type:"string"},profileId:{type:"string"},consentJson:{type:"object",additionalProperties:{type:"string"}},gpcDetected:{type:"boolean",default:!1},source:{type:"string",enum:["banner","api","import"],default:"banner"},ageVerified:{type:"boolean"},parentalConsentToken:{type:"string"},tcfString:{type:"string"}}}},ln={openapi:"3.1.0",info:{title:"Consenti Public API",version:"0.1.0",description:"Visitor-facing endpoints for reading consent profiles and submitting/verifying consent. Rate-limited; no authentication required.",license:{name:"Apache 2.0",url:"https://www.apache.org/licenses/LICENSE-2.0"}},servers:[{url:"/consenti/api",description:"Consenti Public API"}],paths:{"/v1/profiles/{id}":{get:{tags:["Profile"],summary:"Get profile in its default locale",description:"Returns the resolved profile with banner and modal content for the profile's default locale. Includes `currentLocale`, `locales`, `cookies`, `mainBanner`, `preferenceModal`, and optionally `gpcBanner`.",parameters:[{name:"id",in:"path",required:!0,schema:{type:"string"}}],responses:{200:{description:"Resolved profile for the default locale"},404:{description:"Profile not found"}}}},"/v1/profiles/{id}/{locale}":{get:{tags:["Profile"],summary:"Get profile resolved for a specific locale",description:"Same response shape as `GET /v1/profiles/{id}` but resolved for the requested locale, falling back to the default locale for any missing translations.",parameters:[{name:"id",in:"path",required:!0,schema:{type:"string"}},{name:"locale",in:"path",required:!0,schema:{type:"string",example:"fr"}}],responses:{200:{description:"Resolved profile for the requested locale"},404:{description:"Profile not found"}}}},"/v1/consent":{post:{tags:["Consent"],summary:"Submit consent",requestBody:{required:!0,content:{"application/json":{schema:{$ref:"#/components/schemas/CreateConsentInput"}}}},responses:{200:{description:"Consent created or updated",content:{"application/json":{schema:{$ref:"#/components/schemas/ConsentRecord"}}}},400:{description:"Validation error"}}}},"/v1/consent/{visitorId}":{get:{tags:["Consent"],summary:"Get consent for visitor",parameters:[{name:"visitorId",in:"path",required:!0,schema:{type:"string"}}],responses:{200:{description:"Consent record",content:{"application/json":{schema:{$ref:"#/components/schemas/ConsentRecord"}}}},404:{description:"Not found"}}},put:{tags:["Consent"],summary:"Update consent",parameters:[{name:"visitorId",in:"path",required:!0,schema:{type:"string"}}],responses:{200:{description:"Updated consent"}}},delete:{tags:["Consent"],summary:"GDPR erasure \u2014 delete all consent data for visitor",parameters:[{name:"visitorId",in:"path",required:!0,schema:{type:"string"}}],responses:{200:{description:"Erased successfully"}}}},"/v1/consent/{visitorId}/verify":{get:{tags:["Consent"],summary:"Verify consent validity",parameters:[{name:"visitorId",in:"path",required:!0,schema:{type:"string"}}],responses:{200:{description:"Verification result"}}}},"/v1/consent/{visitorId}/parental-consent-request":{post:{tags:["Consent"],summary:"Request a parental-consent token (stateless hook)",description:"Issues a token \u2014 signed with `compliance.dataSigningHash` when configured, otherwise unsigned \u2014 for a host's own out-of-band parental-verification flow. Emits `consent.parentalConsentRequired`. Not by itself sufficient verifiable parental consent (COPPA-style VPC is left to the host).",parameters:[{name:"visitorId",in:"path",required:!0,schema:{type:"string"}}],requestBody:{required:!0,content:{"application/json":{schema:{type:"object",required:["profileId"],properties:{profileId:{type:"string"}}}}}},responses:{200:{description:"Token issued",content:{"application/json":{schema:{type:"object",properties:{token:{type:"string"}}}}}},400:{description:"Validation error"},403:{description:"Visitor ownership check failed"},404:{description:"Profile not found"}}}},"/v1/consent/parental-consent-resolve":{post:{tags:["Consent"],summary:"Resolve a parental-consent token",description:"Recovers `visitorId`/`profileId` from a token issued by the request route above and emits `consent.parentalConsentGranted` \u2014 the host's own backend listener decides what \"granted\" means for stored consent. No visitor-ownership cookie required, since this is typically called from the parent's own device. Token replay is not prevented (stateless by design).",requestBody:{required:!0,content:{"application/json":{schema:{type:"object",required:["token"],properties:{token:{type:"string"}}}}}},responses:{200:{description:"Token resolved",content:{"application/json":{schema:{type:"object",properties:{visitorId:{type:"string"},profileId:{type:"string"}}}}}},400:{description:"Validation error"},403:{description:"Invalid, tampered, or expired token"}}}},"/v1/notice-shown":{post:{tags:["Consent"],summary:"Record proof that the consent banner was shown",description:"Lightweight, fire-and-forget ping recording that the banner was rendered to a visitor, independent of whether they ever interact with it. Called automatically by `@consenti/ui` on banner mount when `api.enabled` is configured.",requestBody:{required:!0,content:{"application/json":{schema:{type:"object",required:["visitorId","profileId","locale"],properties:{visitorId:{type:"string"},profileId:{type:"string"},locale:{type:"string"}}}}}},responses:{201:{description:"Recorded"},400:{description:"Validation error"}}}}},components:{schemas:Rs}},dn={openapi:"3.1.0",info:{title:"Consenti Admin API",version:"0.1.0",description:"Authenticated endpoints for managing profiles, consent records, users, analytics, and exports. All routes (except `/auth/login`) require `Authorization: Bearer <JWT>`. Obtain a token via `POST /auth/login`.",license:{name:"Apache 2.0",url:"https://www.apache.org/licenses/LICENSE-2.0"}},servers:[{url:"/consenti/admin/v1",description:"Consenti Admin API"}],paths:{"/auth/login":{post:{tags:["Auth"],summary:"Login with email + password",requestBody:{required:!0,content:{"application/json":{schema:{type:"object",properties:{email:{type:"string"},password:{type:"string"}}}}}},responses:{200:{description:"JWT token"}}}},"/auth/me":{get:{tags:["Auth"],summary:"Get current user",security:[{BearerAuth:[]}],responses:{200:{description:"Current admin user info"}}}},"/auth/logout":{post:{tags:["Auth"],summary:"Invalidate session",security:[{BearerAuth:[]}],responses:{200:{description:"Logged out"}}}},"/auth/refresh":{post:{tags:["Auth"],summary:"Reissue a fresh token from the current one, extending the session",description:"Used by the dashboard on user activity to implement a sliding inactivity timeout instead of a flat expiry from login.",security:[{BearerAuth:[]}],responses:{200:{description:"New JWT token"},401:{description:"Current token is missing, invalid, or already expired"}}}},"/auth/oidc/authorize":{get:{tags:["Auth"],summary:"Start OIDC authorization (PKCE)",responses:{302:{description:"Redirect to OIDC provider"}}}},"/auth/oidc/callback":{get:{tags:["Auth"],summary:"OIDC callback \u2014 exchange code for token",responses:{200:{description:"JWT token"}}}},"/auth/saml/metadata":{get:{tags:["Auth"],summary:"SAML SP metadata XML",responses:{200:{description:"SAML metadata XML",content:{"application/xml":{}}}}}},"/auth/saml/acs":{post:{tags:["Auth"],summary:"SAML Assertion Consumer Service",responses:{200:{description:"JWT token"}}}},"/auth/totp/setup":{post:{tags:["Auth"],summary:"Setup TOTP MFA",security:[{BearerAuth:[]}],responses:{200:{description:"TOTP secret and QR URL"}}}},"/auth/totp/verify":{post:{tags:["Auth"],summary:"Verify and enable TOTP",security:[{BearerAuth:[]}],responses:{200:{description:"TOTP enabled"}}}},"/profiles":{get:{tags:["Profiles"],summary:"List profiles",security:[{BearerAuth:[]}],responses:{200:{description:"Array of profiles"}}},post:{tags:["Profiles"],summary:"Create profile",security:[{BearerAuth:[]}],responses:{201:{description:"Created profile"}}}},"/consent-templates":{get:{tags:["Consent Templates"],summary:"List consent templates",description:"A Consent Template groups parameters (`cookies`, keyed by id) with the categories that own their legal basis (`categories`, keyed by id).",security:[{BearerAuth:[]}],responses:{200:{description:"Array of consent templates"}}},post:{tags:["Consent Templates"],summary:"Create consent template",security:[{BearerAuth:[]}],requestBody:{required:!0,content:{"application/json":{schema:{type:"object",required:["name","cookies","categories"],properties:{name:{type:"string"},cookies:{type:"object",description:"Keyed by parameter id."},categories:{type:"object",description:"Keyed by category id. Every parameter id must appear in exactly one category."}}}}}},responses:{201:{description:"Created consent template"},400:{description:"Validation error"}}}},"/consent-templates/{id}":{get:{tags:["Consent Templates"],summary:"Get consent template",security:[{BearerAuth:[]}],parameters:[{name:"id",in:"path",required:!0,schema:{type:"string"}}],responses:{200:{description:"Consent template"},404:{description:"Not found"}}},put:{tags:["Consent Templates"],summary:"Update consent template",security:[{BearerAuth:[]}],parameters:[{name:"id",in:"path",required:!0,schema:{type:"string"}}],responses:{200:{description:"Updated consent template"},422:{description:"Change would break compliance for profiles using this template"}}},delete:{tags:["Consent Templates"],summary:"Delete consent template",security:[{BearerAuth:[]}],parameters:[{name:"id",in:"path",required:!0,schema:{type:"string"}}],responses:{200:{description:"Deleted"},422:{description:"Template is still used by profiles"}}}},"/consent-templates/{id}/copy":{post:{tags:["Consent Templates"],summary:"Duplicate a consent template",security:[{BearerAuth:[]}],parameters:[{name:"id",in:"path",required:!0,schema:{type:"string"}}],responses:{201:{description:"Copied consent template"}}}},"/consent-templates/{id}/profile-usage":{get:{tags:["Consent Templates"],summary:"List profiles using this consent template",security:[{BearerAuth:[]}],parameters:[{name:"id",in:"path",required:!0,schema:{type:"string"}}],responses:{200:{description:"Array of profile summaries"}}}},"/consents":{get:{tags:["Consents"],summary:"List consent records",security:[{BearerAuth:[]}],parameters:[{name:"page",in:"query",schema:{type:"integer"}},{name:"profileId",in:"query",schema:{type:"string"}},{name:"from",in:"query",schema:{type:"string",format:"date"}},{name:"to",in:"query",schema:{type:"string",format:"date"}},{name:"q",in:"query",schema:{type:"string"},description:"Free-text search across visitorId, profileId, locale, source"}],responses:{200:{description:"Paginated consent records"}}}},"/stats/overview":{get:{tags:["Stats"],summary:"Overview statistics",security:[{BearerAuth:[]}],responses:{200:{description:"Totals and acceptance rates"}}}},"/stats/timeline":{get:{tags:["Stats"],summary:"Daily consent timeline",security:[{BearerAuth:[]}],parameters:[{name:"days",in:"query",schema:{type:"integer",default:30}}],responses:{200:{description:"Array of date+count"}}}},"/stats/countries":{get:{tags:["Stats"],summary:"Consent by country",security:[{BearerAuth:[]}],responses:{200:{description:"Country counts"}}}},"/stats/gpc":{get:{tags:["Stats"],summary:"GPC detection stats",security:[{BearerAuth:[]}],responses:{200:{description:"GPC rate and counts"}}}},"/export/consents":{get:{tags:["Export"],summary:"Export consent records",security:[{BearerAuth:[]}],parameters:[{name:"format",in:"query",schema:{type:"string",enum:["csv","json"]}},{name:"from",in:"query",schema:{type:"string",format:"date"}},{name:"to",in:"query",schema:{type:"string",format:"date"}}],responses:{200:{description:"CSV or JSON download"}}}},"/tenants":{get:{tags:["Sites"],summary:"List all sites/tenants",security:[{BearerAuth:[]}],responses:{200:{description:"Array of tenants"}}},post:{tags:["Sites"],summary:"Create a site/tenant",security:[{BearerAuth:[]}],requestBody:{required:!0,content:{"application/json":{schema:{type:"object",properties:{name:{type:"string"},slug:{type:"string"}},required:["name","slug"]}}}},responses:{201:{description:"Created tenant"}}}},"/tenants/{id}":{put:{tags:["Sites"],summary:"Update a site/tenant",security:[{BearerAuth:[]}],parameters:[{name:"id",in:"path",required:!0,schema:{type:"string"}}],responses:{200:{description:"Updated tenant"}}},delete:{tags:["Sites"],summary:"Delete a site/tenant",security:[{BearerAuth:[]}],parameters:[{name:"id",in:"path",required:!0,schema:{type:"string"}}],responses:{200:{description:"Deleted"}}}},"/setup/status":{get:{tags:["Setup"],summary:"Whether the first-run setup wizard has been completed for this tenant",security:[{BearerAuth:[]}],responses:{200:{description:"{ completed: boolean }"}}}},"/setup/config":{get:{tags:["Setup"],summary:"Resolved server config (secrets redacted) plus production-readiness flags",description:"The same merged `DEFAULT_CONFIG` + user config `createConsenti` computes at boot. `auth.adminPassword`, `auth.masterSecret`, `compliance.dataSigningHash`, `compliance.dataSigningHash`, storage credentials, and OIDC/SAML secrets are replaced with a redaction marker.",security:[{BearerAuth:[]}],responses:{200:{description:"{ config: object, usingJsonStorage: boolean, usingDefaultCredentials: boolean }"}}}},"/setup/compliance-groups":{get:{tags:["Setup"],summary:"The 8 built-in compliance groups with label/description/regulation metadata",security:[{BearerAuth:[]}],responses:{200:{description:"Array of compliance group descriptors"}}}},"/setup/seed-profiles":{post:{tags:["Setup"],summary:"Seed default profiles for the selected compliance groups",description:"Idempotent \u2014 skips any group that already has an active profile. `groups` must be a subset of the 8 built-in compliance group ids.",security:[{BearerAuth:[]}],requestBody:{required:!0,content:{"application/json":{schema:{type:"object",properties:{groups:{type:"array",items:{type:"string"}}},required:["groups"]}}}},responses:{200:{description:"{ seeded: string[] }"},400:{description:"groups is missing, not an array of strings, or contains an unknown compliance group id"},409:{description:"Setup has already been completed for this tenant"}}}},"/setup/complete":{post:{tags:["Setup"],summary:"Mark the first-run setup wizard as complete for this tenant",description:"Called on both wizard finish and skip. Never reset from the dashboard once true.",security:[{BearerAuth:[]}],responses:{200:{description:"Updated tenant settings"},409:{description:"Setup has already been completed for this tenant"}}}}},components:{securitySchemes:{BearerAuth:{type:"http",scheme:"bearer",bearerFormat:"JWT"}},schemas:Rs}};import{createHash as Cs,createHmac as Po,randomUUID as A,scryptSync as Is,randomBytes as Ts,timingSafeEqual as un}from"crypto";var Ss="0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",Oo=16;function bo(i){let e=Ts(i),t="";for(let n=0;n<i;n++)t+=Ss[e[n]%Ss.length];return t}function Me(i){return`${i}_${bo(Oo)}`}function W(){return Me("prof")}function H(){return Me("visi")}function J(){return Me("cons")}function G(){return Me("ctem")}function q(){return Me("utem")}function Do(i){if(i.includes(":")){let t=i.split(":");for(;t.length<8;)t.push("0");return t.slice(0,3).concat(["0","0","0","0","0"]).join(":")}let e=i.split(".");return e.length===4?(e[3]="0",e.join(".")):i}function As(i,e=""){return Cs("sha256").update(e+Do(i)).digest("hex")}function vs(i){return Cs("sha256").update(i).digest("hex")}function te(i,e){return Po("sha256",e).update(i).digest("hex")}function at(i,e,t){let n=Buffer.from(te(i,t),"hex"),s=Buffer.from(e,"hex");return n.length!==s.length?!1:un(n,s)}function xe(i){let e=Ts(16).toString("hex"),t=Is(i,e,64).toString("hex");return`${e}:${t}`}function Ps(i,e){let[t,n]=e.split(":");if(!t||!n)return!1;let s=Is(i,t,64).toString("hex");return un(Buffer.from(n,"hex"),Buffer.from(s,"hex"))}function pn(i,e,t=3600){let n=Math.floor(Date.now()/1e3),s=btoa(JSON.stringify({alg:"HS256",typ:"JWT"})).replace(/=/g,""),r=btoa(JSON.stringify({...i,iat:n,exp:n+t})).replace(/=/g,""),o=te(`${s}.${r}`,e).match(/.{2}/g).map(c=>String.fromCharCode(parseInt(c,16))).join(""),a=btoa(o).replace(/\+/g,"-").replace(/\//g,"_").replace(/=/g,"");return`${s}.${r}.${a}`}function ct(i,e){let t=i.split(".");if(t.length!==3)return null;let[n,s,r]=t;if(!n||!s||!r)return null;let o=te(`${n}.${s}`,e),a=atob(r.replace(/-/g,"+").replace(/_/g,"/")),c=Array.from(a).map(u=>u.charCodeAt(0).toString(16).padStart(2,"0")).join(""),l=Buffer.from(o,"hex"),d=Buffer.from(c,"hex");if(l.length!==d.length||!un(l,d))return null;try{let u=JSON.parse(atob(s)),p=u.exp;return typeof p=="number"&&Date.now()/1e3>p?null:u}catch{return null}}import{existsSync as Lo,accessSync as Uo,constants as No,mkdirSync as lt}from"fs";import{resolve as Os,extname as ko,dirname as bs,join as dt}from"path";import{fileURLToPath as Mo}from"url";var xo={"node:sqlite":"consenti.db","better-sqlite3":"consenti.db","node-sqlite3-wasm":"consenti.db",sqlite:"consenti.db",json:"consenti-data.json",mongodb:"",mysql:"",postgresql:""},jo=bs(Mo(import.meta.url));function $o(i){let e=i.includes("node_modules"),t=i.startsWith(Os(jo,"..",".."));return e||t}function ie(i,e){let t=Os(i),n=ko(t);if(n&&(console.warn(`[consenti] storage.path "${i}" looks like a file path (has extension "${n}"). Treating parent directory as storage root. Update your config to pass a directory path.`),t=bs(t)),$o(t))throw new Error(`[consenti] storage.path "${t}" is inside the package installation directory. Choose a writable directory outside node_modules.`);Lo(t)||lt(t,{recursive:!0});try{Uo(t,No.W_OK)}catch{throw new Error(`[consenti] storage.path "${t}" is not writable.`)}let s=dt(t,"db"),r=dt(t,"profiles"),o=dt(t,"logs");lt(s,{recursive:!0}),lt(r,{recursive:!0}),lt(o,{recursive:!0});let a=xo[e]??"consenti.db",c=a?dt(s,a):"";return{basePath:t,dbPath:c,profilesDir:r,logsDir:o}}var Ls={name:"tenants",cols:[{n:"id",t:"pk"},{n:"name",t:"text"},{n:"slug",t:"text",unique:!0},{n:"created_at",t:"ts",def:"$now"},{n:"updated_at",t:"ts",def:"$now",mysqlUpd:!0}]},Us={name:"profiles",cols:[{n:"id",t:"pk"},{n:"tenant_id",t:"text",ref:"tenants"},{n:"name",t:"text"},{n:"default_locale",t:"text_sm",def:"en"},{n:"version",t:"int",def:1},{n:"profile_json",t:"json_ordered"},{n:"created_at",t:"ts",def:"$now"},{n:"updated_at",t:"ts",def:"$now",mysqlUpd:!0}],idx:[{cols:["tenant_id"]}]},Ns={name:"consent_records",cols:[{n:"id",t:"pk"},{n:"tenant_id",t:"text",ref:"tenants"},{n:"visitor_id",t:"text"},{n:"profile_id",t:"text"},{n:"profile_version",t:"int",null:!0},{n:"locale",t:"text_sm"},{n:"consent_json",t:"json"},{n:"gpc_detected",t:"bool",def:!1},{n:"source",t:"text_sm"},{n:"age_verified",t:"bool",def:!1},{n:"parental_consent_token",t:"longtext",null:!0},{n:"tcf_string",t:"longtext",null:!0},{n:"gpp_string",t:"longtext",null:!0},{n:"signature",t:"longtext",null:!0},{n:"created_at",t:"ts",def:"$now"},{n:"updated_at",t:"ts",def:"$now",mysqlUpd:!0}],uq:[{cols:["visitor_id","profile_id"]}],idx:[{cols:["tenant_id","created_at"]},{cols:["tenant_id","profile_id","created_at"]},{cols:["locale"]},{cols:["source"]}]},ks={name:"consent_history",cols:[{n:"id",t:"pk"},{n:"tenant_id",t:"text",ref:"tenants"},{n:"consent_record_id",t:"text",ref:"consent_records"},{n:"visitor_id",t:"text"},{n:"profile_version",t:"int",null:!0},{n:"old_json",t:"json",null:!0},{n:"new_json",t:"json"},{n:"action",t:"text_sm"},{n:"created_at",t:"ts",def:"$now"}],idx:[{cols:["visitor_id"]}]},Ms={name:"visitors",cols:[{n:"id",t:"pk"},{n:"tenant_id",t:"text",ref:"tenants"},{n:"visitor_id",t:"text",unique:!0},{n:"country",t:"text_geo",null:!0},{n:"region",t:"text_md",null:!0},{n:"city",t:"text_md",null:!0},{n:"ip_hash",t:"text_hash",null:!0},{n:"user_agent_hash",t:"text_hash",null:!0},{n:"first_seen",t:"ts",def:"$now"},{n:"last_seen",t:"ts",def:"$now",mysqlUpd:!0}],idx:[{cols:["tenant_id","first_seen"]},{cols:["country"]}]},xs={name:"notice_shown",cols:[{n:"id",t:"pk"},{n:"tenant_id",t:"text",ref:"tenants"},{n:"visitor_id",t:"text"},{n:"profile_id",t:"text"},{n:"locale",t:"text_sm"},{n:"created_at",t:"ts",def:"$now"}],idx:[{cols:["visitor_id"]}]},mn={name:"users",cols:[{n:"id",t:"pk"},{n:"tenant_id",t:"text",ref:"tenants"},{n:"name",t:"text"},{n:"email",t:"text",unique:!0},{n:"password_hash",t:"text"},{n:"is_active",t:"bool",def:!0},{n:"totp_secret",t:"longtext",null:!0},{n:"totp_enabled",t:"bool",def:!1},{n:"created_at",t:"ts",def:"$now"},{n:"updated_at",t:"ts",def:"$now",mysqlUpd:!0}]},Fo={...mn,cols:[...mn.cols,{n:"allowed_tenants",t:"json",null:!0}]},js={name:"roles",cols:[{n:"id",t:"pk"},{n:"tenant_id",t:"text",ref:"tenants"},{n:"name",t:"text_md"},{n:"description",t:"text",null:!0}]},$s={name:"permissions",cols:[{n:"id",t:"pk"},{n:"name",t:"text_md",unique:!0},{n:"description",t:"text",null:!0}]},Fs={name:"user_roles",cols:[{n:"user_id",t:"text",ref:"users"},{n:"role_id",t:"text",ref:"roles"},{n:"tenant_id",t:"text",def:"default"}],pk:["user_id","role_id","tenant_id"],idx:[{cols:["user_id"]}]},Hs={name:"role_permissions",cols:[{n:"role_id",t:"text",ref:"roles"},{n:"permission_id",t:"text",ref:"permissions"}],pk:["role_id","permission_id"],idx:[{cols:["role_id"]}]},Bs={name:"audit_logs",cols:[{n:"id",t:"pk"},{n:"tenant_id",t:"text",ref:"tenants"},{n:"user_id",t:"text",null:!0},{n:"action",t:"text_md"},{n:"resource_type",t:"text_md"},{n:"resource_id",t:"text",null:!0},{n:"old_data",t:"json",null:!0},{n:"new_data",t:"json",null:!0},{n:"created_at",t:"ts",def:"$now"}],idx:[{cols:["tenant_id","created_at"]},{cols:["action"]},{cols:["resource_type"]},{cols:["resource_id"]},{cols:["user_id"]}]},Vs={name:"api_keys",cols:[{n:"id",t:"pk"},{n:"tenant_id",t:"text",ref:"tenants",onDel:"CASCADE"},{n:"key_hash",t:"text_hash",unique:!0},{n:"name",t:"text"},{n:"is_active",t:"bool",def:!0},{n:"created_by",t:"text",null:!0},{n:"expire_by",t:"ts",null:!0},{n:"created_at",t:"ts",def:"$now"},{n:"updated_at",t:"ts",def:"$now",mysqlUpd:!0}]},Ws={name:"tenant_settings",cols:[{n:"tenant_id",t:"text",ref:"tenants",onDel:"CASCADE"},{n:"allowed_origins_json",t:"json",def:"$arr"},{n:"admin_allowed_origins_json",t:"json",def:"$arr"},{n:"setup_completed",t:"bool",def:!1},{n:"profiles_seeded",t:"bool",def:!1},{n:"tcf_confirmation_json",t:"json",null:!0},{n:"gpp_confirmation_json",t:"json",null:!0},{n:"updated_at",t:"ts",def:"$now",mysqlUpd:!0}],pk:["tenant_id"]},Js={name:"consent_templates",cols:[{n:"id",t:"pk"},{n:"tenant_id",t:"text",ref:"tenants",onDel:"CASCADE"},{n:"name",t:"text"},{n:"cookies_json",t:"json_ordered",def:"$obj"},{n:"categories_json",t:"json_ordered",def:"$obj"},{n:"created_at",t:"ts",def:"$now"},{n:"updated_at",t:"ts",def:"$now",mysqlUpd:!0}]},Gs={name:"ui_templates",cols:[{n:"id",t:"pk"},{n:"tenant_id",t:"text",ref:"tenants",onDel:"CASCADE"},{n:"name",t:"text"},{n:"settings_json",t:"json_ordered",def:"$obj"},{n:"created_at",t:"ts",def:"$now"},{n:"updated_at",t:"ts",def:"$now",mysqlUpd:!0}]},ee=2,gn="consenti_";function Z(i){return gn+i}var ue=["schema_version","tenants","profiles","consent_records"];function Re(i){return new Error(`[consenti] This database has Consenti ${i} from an earlier version, named without the "${gn}" prefix (tenants, profiles, consent_records, \u2026). Consenti now only reads and writes "${gn}*" ${i}, so starting would create an empty set next to your data. Back up the database, then rename them once with: node node_modules/@consenti/api/scripts/migrate-to-consenti-prefix.mjs --apply (it reads the same CONSENTI_DB_* environment variables; run it without --apply first to see the plan). Details: CHANGELOG 0.6.0.`)}var _n=[Ls,Us,Ns,ks,Ms,Fo,js,$s,Fs,Hs,Bs,Vs,Js,Gs,Ws,xs],hn=[Ls,Us,Ns,ks,Ms,mn,js,$s,Fs,Hs,Bs,Vs,Js,Gs,Ws,xs];function Ho(i){return i==="$now"?"DEFAULT (datetime('now'))":i==="$arr"?"DEFAULT '[]'":i==="$obj"?"DEFAULT '{}'":i===!0?"DEFAULT 1":i===!1?"DEFAULT 0":typeof i=="string"?`DEFAULT '${i}'`:`DEFAULT ${i}`}function Bo(i){if(i.t==="pk")return`${i.n} TEXT PRIMARY KEY`;let e=i.t==="int"||i.t==="bool"?"INTEGER":"TEXT",t=[`${i.n} ${e}`];return i.null||t.push("NOT NULL"),i.def!==void 0&&t.push(Ho(i.def)),i.unique&&t.push("UNIQUE"),i.ref&&(t.push(`REFERENCES ${i.ref}(id)`),i.onDel&&t.push(`ON DELETE ${i.onDel}`)),t.join(" ")}function fn(i){let e=i.cols.map(t=>` ${Bo(t)}`);if(i.pk&&e.push(` PRIMARY KEY (${i.pk.join(", ")})`),i.uq)for(let t of i.uq)e.push(` UNIQUE(${t.cols.join(", ")})`);return`CREATE TABLE IF NOT EXISTS ${i.name} (
2
2
  ${e.join(`,
3
3
  `)}
4
- );`}function cn(i,e){return`idx_${i.name}_${e.join("_")}`}function yo(i){return(i.idx??[]).map(e=>`CREATE INDEX IF NOT EXISTS ${cn(i,e.cols)} ON ${i.name} (${e.cols.join(", ")});`).join(`
5
- `)}function wo(i){return i==="$now"?"DEFAULT NOW()":i==="$arr"?"DEFAULT '[]'":i==="$obj"?"DEFAULT '{}'":i===!0?"DEFAULT TRUE":i===!1?"DEFAULT FALSE":typeof i=="string"?`DEFAULT '${i}'`:`DEFAULT ${i}`}function Eo(i){if(i.t==="pk")return`${i.n} TEXT PRIMARY KEY`;let e=i.t==="json"?"JSONB":i.t==="json_ordered"?"JSON":i.t==="int"?"INTEGER":i.t==="bool"?"BOOLEAN":i.t==="ts"?"TIMESTAMPTZ":"TEXT",t=[`${i.n} ${e}`];return i.null||t.push("NOT NULL"),i.def!==void 0&&t.push(wo(i.def)),i.unique&&t.push("UNIQUE"),i.ref&&(t.push(`REFERENCES ${i.ref}(id)`),i.onDel&&t.push(`ON DELETE ${i.onDel}`)),t.join(" ")}function fs(i){let e=i.cols.map(t=>` ${Eo(t)}`);if(i.pk&&e.push(` PRIMARY KEY (${i.pk.join(", ")})`),i.uq)for(let t of i.uq)e.push(` UNIQUE (${t.cols.join(", ")})`);return`CREATE TABLE IF NOT EXISTS ${i.name} (
4
+ );`}function yn(i,e){return`idx_${i}_${e.join("_")}`}function qs(i){return(i.idx??[]).map(e=>`CREATE INDEX IF NOT EXISTS ${yn(i.name,e.cols)} ON ${i.name} (${e.cols.join(", ")});`).join(`
5
+ `)}function Vo(i){return i==="$now"?"DEFAULT NOW()":i==="$arr"?"DEFAULT '[]'":i==="$obj"?"DEFAULT '{}'":i===!0?"DEFAULT TRUE":i===!1?"DEFAULT FALSE":typeof i=="string"?`DEFAULT '${i}'`:`DEFAULT ${i}`}function Wo(i){if(i.t==="pk")return`${i.n} TEXT PRIMARY KEY`;let e=i.t==="json"?"JSONB":i.t==="json_ordered"?"JSON":i.t==="int"?"INTEGER":i.t==="bool"?"BOOLEAN":i.t==="ts"?"TIMESTAMPTZ":"TEXT",t=[`${i.n} ${e}`];return i.null||t.push("NOT NULL"),i.def!==void 0&&t.push(Vo(i.def)),i.unique&&t.push("UNIQUE"),i.ref&&(t.push(`REFERENCES ${Z(i.ref)}(id)`),i.onDel&&t.push(`ON DELETE ${i.onDel}`)),t.join(" ")}function Ds(i){let e=i.cols.map(t=>` ${Wo(t)}`);if(i.pk&&e.push(` PRIMARY KEY (${i.pk.join(", ")})`),i.uq)for(let t of i.uq)e.push(` UNIQUE (${t.cols.join(", ")})`);return`CREATE TABLE IF NOT EXISTS ${Z(i.name)} (
6
6
  ${e.join(`,
7
7
  `)}
8
- );`}function Ro(i){return(i.idx??[]).map(e=>`CREATE INDEX IF NOT EXISTS ${cn(i,e.cols)} ON ${i.name} (${e.cols.join(", ")});`).join(`
9
- `)}var So={text:"VARCHAR(255)",text_sm:"VARCHAR(20)",text_md:"VARCHAR(100)",text_geo:"VARCHAR(10)",text_hash:"VARCHAR(64)",longtext:"LONGTEXT",json:"LONGTEXT",json_ordered:"LONGTEXT"};function Co(i){return i==="$now"?"DEFAULT CURRENT_TIMESTAMP":i==="$arr"?"DEFAULT ('[]')":i==="$obj"?"DEFAULT ('{}')":i===!0?"DEFAULT 1":i===!1?"DEFAULT 0":typeof i=="string"?`DEFAULT '${i}'`:`DEFAULT ${i}`}function Io(i){if(i.t==="pk")return`${i.n} VARCHAR(64) PRIMARY KEY`;let e=i.t==="int"?"INT":i.t==="bool"?"TINYINT(1)":i.t==="ts"?"DATETIME":So[i.t]??"TEXT",t=[`${i.n} ${e}`];return i.null||t.push("NOT NULL"),i.def!==void 0&&t.push(Co(i.def)),i.mysqlUpd&&t.push("ON UPDATE CURRENT_TIMESTAMP"),i.unique&&t.push("UNIQUE"),i.ref&&(t.push(`REFERENCES ${i.ref}(id)`),i.onDel&&t.push(`ON DELETE ${i.onDel}`)),t.join(" ")}function _s(i){let e=i.cols.map(t=>` ${Io(t)}`);if(i.pk&&e.push(` PRIMARY KEY (${i.pk.join(", ")})`),i.uq)for(let t of i.uq)e.push(` UNIQUE KEY uq_${t.cols.join("_")} (${t.cols.join(", ")})`);return`CREATE TABLE IF NOT EXISTS ${i.name} (
8
+ );`}function Jo(i){return(i.idx??[]).map(e=>`CREATE INDEX IF NOT EXISTS ${yn(Z(i.name),e.cols)} ON ${Z(i.name)} (${e.cols.join(", ")});`).join(`
9
+ `)}var Go={text:"VARCHAR(255)",text_sm:"VARCHAR(20)",text_md:"VARCHAR(100)",text_geo:"VARCHAR(10)",text_hash:"VARCHAR(64)",longtext:"LONGTEXT",json:"LONGTEXT",json_ordered:"LONGTEXT"};function qo(i){return i==="$now"?"DEFAULT CURRENT_TIMESTAMP":i==="$arr"?"DEFAULT ('[]')":i==="$obj"?"DEFAULT ('{}')":i===!0?"DEFAULT 1":i===!1?"DEFAULT 0":typeof i=="string"?`DEFAULT '${i}'`:`DEFAULT ${i}`}function Ko(i){if(i.t==="pk")return`${i.n} VARCHAR(64) PRIMARY KEY`;let e=i.t==="int"?"INT":i.t==="bool"?"TINYINT(1)":i.t==="ts"?"DATETIME":Go[i.t]??"TEXT",t=[`${i.n} ${e}`];return i.null||t.push("NOT NULL"),i.def!==void 0&&t.push(qo(i.def)),i.mysqlUpd&&t.push("ON UPDATE CURRENT_TIMESTAMP"),i.unique&&t.push("UNIQUE"),i.ref&&(t.push(`REFERENCES ${Z(i.ref)}(id)`),i.onDel&&t.push(`ON DELETE ${i.onDel}`)),t.join(" ")}function Yo(i){let e=i.cols.map(n=>` ${Ko(n)}`);if(i.pk&&e.push(` PRIMARY KEY (${i.pk.join(", ")})`),i.uq)for(let n of i.uq)e.push(` UNIQUE KEY uq_${n.cols.join("_")} (${n.cols.join(", ")})`);let t=Z(i.name);for(let n of i.idx??[])e.push(` INDEX ${yn(t,n.cols)} (${n.cols.join(", ")})`);return`CREATE TABLE IF NOT EXISTS ${t} (
10
10
  ${e.join(`,
11
11
  `)}
12
- ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;`}function To(i){return(i.idx??[]).map(e=>`CREATE INDEX ${cn(i,e.cols)} ON ${i.name} (${e.cols.join(", ")});`).join(`
13
- `)}function Ao(i){let e=i.map(yo).filter(Boolean).join(`
14
- `);return e?`${i.map(gs).join(`
12
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;`}function zo(i){let e=i.map(qs).filter(Boolean).join(`
13
+ `);return e?`${i.map(fn).join(`
15
14
  `)}
16
- ${e}`:i.map(gs).join(`
17
- `)}function vo(i){let e=i.map(Ro).filter(Boolean).join(`
18
- `);return e?`${i.map(fs).join(`
15
+ ${e}`:i.map(fn).join(`
16
+ `)}function Qo(i){let e=i.map(Jo).filter(Boolean).join(`
17
+ `);return e?`${i.map(Ds).join(`
19
18
  `)}
20
- ${e}`:i.map(fs).join(`
21
- `)}function Po(i){let e=i.map(To).filter(Boolean).join(`
22
- `);return e?`${i.map(_s).join(`
23
- `)}
24
- ${e}`:i.map(_s).join(`
25
- `)}function Oo(i){let e=[];for(let t of i){for(let n of t.uq??[])e.push({table:t.name,cols:n.cols,unique:!0});for(let n of t.cols)n.unique&&e.push({table:t.name,cols:[n.n],unique:!0});for(let n of t.idx??[])e.push({table:t.name,cols:n.cols})}return e}var Us=Oo(an),ye=Ao(fo),Ls=vo(an)+`
26
- CREATE TABLE IF NOT EXISTS schema_version (version INTEGER PRIMARY KEY);
27
- `,Ns=Po(an)+`
28
- CREATE TABLE IF NOT EXISTS schema_version (
19
+ ${e}`:i.map(Ds).join(`
20
+ `)}function Xo(i){return i.map(Yo).join(`
21
+ `)}var je=["consent_records","notice_shown"];function Ks(i){let e=_n.find(t=>t.name===i);if(!e)throw new Error(`Unknown table ${i}`);return e.cols.map(t=>t.n)}function Ys(i,e){let t=_n.find(s=>s.name===i);if(!t)throw new Error(`Unknown table ${i}`);let n=e.join(", ");return{create:fn({...t,name:`${i}_v2`}),copy:`INSERT INTO ${i}_v2 (${n}) SELECT ${n} FROM ${i};`,swap:[`DROP TABLE ${i};`,`ALTER TABLE ${i}_v2 RENAME TO ${i};`,qs(t)].filter(Boolean).join(`
22
+ `)}}function Zo(i){let e=[];for(let t of i){for(let n of t.uq??[])e.push({table:t.name,cols:n.cols,unique:!0});for(let n of t.cols)n.unique&&e.push({table:t.name,cols:[n.n],unique:!0});for(let n of t.idx??[])e.push({table:t.name,cols:n.cols})}return e}var zs=Zo(hn),Se=zo(_n),Qs=Qo(hn)+`
23
+ CREATE TABLE IF NOT EXISTS ${Z("schema_version")} (version INTEGER PRIMARY KEY);
24
+ `,Xs=Xo(hn)+`
25
+ CREATE TABLE IF NOT EXISTS ${Z("schema_version")} (
29
26
  version INT NOT NULL PRIMARY KEY
30
27
  ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
31
- `,K={id:"default",name:"Default",slug:"default"},G=[{id:"perm_profile_create",name:"profile:create",description:"Create profiles"},{id:"perm_profile_update",name:"profile:update",description:"Update profiles"},{id:"perm_profile_delete",name:"profile:delete",description:"Delete profiles"},{id:"perm_profile_view",name:"profile:view",description:"View profiles"},{id:"perm_consent_view",name:"consent:view",description:"View consent records"},{id:"perm_visitor_view",name:"visitor:view",description:"View visitor records"},{id:"perm_user_create",name:"user:create",description:"Create users"},{id:"perm_user_update",name:"user:update",description:"Update users"},{id:"perm_user_delete",name:"user:delete",description:"Delete users"},{id:"perm_user_view",name:"user:view",description:"View users"},{id:"perm_role_create",name:"role:create",description:"Create roles"},{id:"perm_role_update",name:"role:update",description:"Update roles"},{id:"perm_role_delete",name:"role:delete",description:"Delete roles"},{id:"perm_role_view",name:"role:view",description:"View roles"},{id:"perm_audit_view",name:"audit:view",description:"View audit logs"},{id:"perm_settings_update",name:"settings:update",description:"Update settings"},{id:"perm_export_run",name:"export:run",description:"Run data exports"},{id:"perm_stats_view",name:"stats:view",description:"View reporting and analytics"}],se=[{id:"role_super_admin",tenantId:"default",name:"super_admin",description:"Full system access"},{id:"role_admin",tenantId:"default",name:"admin",description:"Administrative access"},{id:"role_compliance_officer",tenantId:"default",name:"compliance_officer",description:"Compliance read access"},{id:"role_viewer",tenantId:"default",name:"viewer",description:"Read-only access"}],bo=new Set(["role:delete","user:delete"]),Do=new Set(["consent:view","visitor:view","audit:view","profile:view","export:run","stats:view"]),Uo=new Set(["consent:view","visitor:view","profile:view","stats:view"]),ie=[...G.map(i=>({roleId:"role_super_admin",permissionId:i.id})),...G.filter(i=>!bo.has(i.name)).map(i=>({roleId:"role_admin",permissionId:i.id})),...G.filter(i=>Do.has(i.name)).map(i=>({roleId:"role_compliance_officer",permissionId:i.id})),...G.filter(i=>Uo.has(i.name)).map(i=>({roleId:"role_viewer",permissionId:i.id}))];function Q(i){return`'${i.replace(/'/g,"''")}'`}function Lo(){let{id:i,name:e,slug:t}=K,n=`INSERT OR IGNORE INTO tenants (id, name, slug) VALUES (${Q(i)},${Q(e)},${Q(t)});`,s=`INSERT OR IGNORE INTO permissions (id, name, description) VALUES
32
- ${G.map(c=>` (${Q(c.id)},${Q(c.name)},${Q(c.description)})`).join(`,
28
+ `,Y={id:"default",name:"Default",slug:"default"},K=[{id:"perm_profile_create",name:"profile:create",description:"Create profiles"},{id:"perm_profile_update",name:"profile:update",description:"Update profiles"},{id:"perm_profile_delete",name:"profile:delete",description:"Delete profiles"},{id:"perm_profile_view",name:"profile:view",description:"View profiles"},{id:"perm_consent_view",name:"consent:view",description:"View consent records"},{id:"perm_visitor_view",name:"visitor:view",description:"View visitor records"},{id:"perm_user_create",name:"user:create",description:"Create users"},{id:"perm_user_update",name:"user:update",description:"Update users"},{id:"perm_user_delete",name:"user:delete",description:"Delete users"},{id:"perm_user_view",name:"user:view",description:"View users"},{id:"perm_role_create",name:"role:create",description:"Create roles"},{id:"perm_role_update",name:"role:update",description:"Update roles"},{id:"perm_role_delete",name:"role:delete",description:"Delete roles"},{id:"perm_role_view",name:"role:view",description:"View roles"},{id:"perm_audit_view",name:"audit:view",description:"View audit logs"},{id:"perm_settings_update",name:"settings:update",description:"Update settings"},{id:"perm_export_run",name:"export:run",description:"Run data exports"},{id:"perm_stats_view",name:"stats:view",description:"View reporting and analytics"}],re=[{id:"role_super_admin",tenantId:"default",name:"super_admin",description:"Full system access"},{id:"role_admin",tenantId:"default",name:"admin",description:"Administrative access"},{id:"role_compliance_officer",tenantId:"default",name:"compliance_officer",description:"Compliance read access"},{id:"role_viewer",tenantId:"default",name:"viewer",description:"Read-only access"}],ea=new Set(["role:delete","user:delete"]),ta=new Set(["consent:view","visitor:view","audit:view","profile:view","export:run","stats:view"]),na=new Set(["consent:view","visitor:view","profile:view","stats:view"]),oe=[...K.map(i=>({roleId:"role_super_admin",permissionId:i.id})),...K.filter(i=>!ea.has(i.name)).map(i=>({roleId:"role_admin",permissionId:i.id})),...K.filter(i=>ta.has(i.name)).map(i=>({roleId:"role_compliance_officer",permissionId:i.id})),...K.filter(i=>na.has(i.name)).map(i=>({roleId:"role_viewer",permissionId:i.id}))];function X(i){return`'${i.replace(/'/g,"''")}'`}function sa(){let{id:i,name:e,slug:t}=Y,n=`INSERT OR IGNORE INTO tenants (id, name, slug) VALUES (${X(i)},${X(e)},${X(t)});`,s=`INSERT OR IGNORE INTO permissions (id, name, description) VALUES
29
+ ${K.map(c=>` (${X(c.id)},${X(c.name)},${X(c.description)})`).join(`,
33
30
  `)};`,r=`INSERT OR IGNORE INTO roles (id, tenant_id, name, description) VALUES
34
- ${se.map(c=>` (${Q(c.id)},${Q(c.tenantId)},${Q(c.name)},${Q(c.description)})`).join(`,
35
- `)};`,o=new Map;for(let c of ie)o.has(c.roleId)||o.set(c.roleId,[]),o.get(c.roleId).push(c.permissionId);let a=[...o.entries()].map(([c,l])=>`INSERT OR IGNORE INTO role_permissions (role_id, permission_id) VALUES
36
- ${l.map(d=>` (${Q(c)},${Q(d)})`).join(`,
31
+ ${re.map(c=>` (${X(c.id)},${X(c.tenantId)},${X(c.name)},${X(c.description)})`).join(`,
32
+ `)};`,o=new Map;for(let c of oe)o.has(c.roleId)||o.set(c.roleId,[]),o.get(c.roleId).push(c.permissionId);let a=[...o.entries()].map(([c,l])=>`INSERT OR IGNORE INTO role_permissions (role_id, permission_id) VALUES
33
+ ${l.map(d=>` (${X(c)},${X(d)})`).join(`,
37
34
  `)};`).join(`
38
35
  `);return[n,s,r,a].join(`
39
- `)}var we=Lo();function j(i){return`${i.replace(/[\\%_]/g,e=>`\\${e}`)}%`}var ln=500,Ee=class i{constructor(e){this.config=e}config;db;cache=new Map;stmt(e){let t=this.cache.get(e);return t||(t=this.db.prepare(e),this.cache.set(e,t)),t}async connect(){let e=await import("better-sqlite3").catch(()=>{throw new Error("driver 'better-sqlite3' requires: npm install better-sqlite3")}),{dbPath:t}=ne(this.config.path??"./consenti-data","better-sqlite3");this.db=new e.default(t),this.db.exec("PRAGMA journal_mode=WAL"),this.db.exec("PRAGMA foreign_keys=ON"),this.db.exec("PRAGMA synchronous=NORMAL"),await this.migrate()}async disconnect(){this.cache.clear(),this.db.close()}async migrate(){this.db.prepare("PRAGMA user_version").get().user_version<1&&(this.db.exec(ye),this.db.exec(we),this.db.exec("PRAGMA user_version = 1")),this.db.exec(`
36
+ `)}var Ce=sa();import{copyFileSync as ia,existsSync as ra}from"fs";function Zs(i,e){return i.all(`PRAGMA table_info(${e})`).map(t=>String(t.name))}function wn(i,e){return Number(i.all(`SELECT COUNT(*) AS n FROM ${e}`)[0]?.n)}function Ie(i,e){let t=new Map;for(let s of je){let r=Zs(i,s),o=new Set(Ks(s)),a=r.filter(c=>!o.has(c));if(a.length>0)throw new Error(`Cannot upgrade ${s} to schema v2: it has column(s) ${a.join(", ")} that this version doesn't know, and rebuilding the table would delete them. Nothing was changed.`);t.set(s,r)}if(e&&ra(e)){i.exec("PRAGMA wal_checkpoint(TRUNCATE)");let s=`${e}.pre-v2-${new Date().toISOString().replace(/[-:T]/g,"").slice(0,14)}.bak`;ia(e,s),console.warn(`[Consenti] Upgrading the SQLite schema to v2. A copy of the database was saved to ${s} \u2014 delete it once you are happy with the upgrade.`)}let n=new Map(je.map(s=>[s,wn(i,s)]));i.exec("PRAGMA foreign_keys=OFF");try{i.exec("BEGIN");for(let s of je){let{create:r,copy:o,swap:a}=Ys(s,t.get(s));i.exec(r),i.exec(o);let c=wn(i,`${s}_v2`);if(c!==n.get(s))throw new Error(`Schema v2 upgrade aborted: ${s} had ${n.get(s)} rows but the copy has ${c}. Nothing was changed.`);i.exec(a)}Zs(i,"consent_history").includes("profile_version")||i.exec("ALTER TABLE consent_history ADD COLUMN profile_version INTEGER");for(let s of je){let r=wn(i,s);if(r!==n.get(s))throw new Error(`Schema v2 upgrade aborted: ${s} has ${r} rows after the upgrade, expected ${n.get(s)}. Nothing was changed.`)}i.exec("PRAGMA user_version = 2"),i.exec("COMMIT")}catch(s){try{i.exec("ROLLBACK")}catch{}throw s}finally{i.exec("PRAGMA foreign_keys=ON")}}function x(i){return`${i.replace(/[\\%_]/g,e=>`\\${e}`)}%`}var En=500,Te=class i{constructor(e){this.config=e}config;db;cache=new Map;dbPath="";stmt(e){let t=this.cache.get(e);return t||(t=this.db.prepare(e),this.cache.set(e,t)),t}async connect(){let e=await import("better-sqlite3").catch(()=>{throw new Error("driver 'better-sqlite3' requires: npm install better-sqlite3")}),{dbPath:t}=ie(this.config.path??"./consenti-data","better-sqlite3");this.dbPath=t,this.db=new e.default(t),this.db.exec("PRAGMA journal_mode=WAL"),this.db.exec("PRAGMA foreign_keys=ON"),this.db.exec("PRAGMA synchronous=NORMAL"),await this.migrate()}async disconnect(){this.cache.clear(),this.db.close()}async migrate(){let e=this.db.prepare("PRAGMA user_version").get();e.user_version<1?(this.db.exec(Se),this.db.exec(Ce),this.db.exec(`PRAGMA user_version = ${ee}`)):e.user_version<2&&this.migrateToV2(),this.db.exec(`
40
37
  CREATE INDEX IF NOT EXISTS idx_profiles_tenant_compliance_active
41
38
  ON profiles (tenant_id, json_extract(profile_json,'$.complianceGroup'), json_extract(profile_json,'$.isActive'));
42
- `)}mapProfile(e){return{id:e.id,tenantId:e.tenant_id,name:e.name,defaultLocale:e.default_locale,version:e.version,profileJson:JSON.parse(e.profile_json),createdAt:e.created_at,updatedAt:e.updated_at}}async createProfile(e){let t=V(),n=new Date().toISOString();this.stmt("INSERT INTO profiles (id,tenant_id,name,default_locale,version,profile_json,created_at,updated_at) VALUES (?,?,?,?,?,?,?,?)").run(t,e.tenantId,e.name,e.defaultLocale,1,JSON.stringify(e.profileJson),n,n);let s=await this.getProfile(t);if(!s)throw new Error("Failed to create profile");return s}async updateProfile(e,t){if(!await this.getProfile(e))throw new Error(`Profile ${e} not found`);let s=new Date().toISOString(),r=["updated_at = ?"],o=[s];t.name!=null&&(r.push("name = ?"),o.push(t.name)),t.defaultLocale!=null&&(r.push("default_locale = ?"),o.push(t.defaultLocale)),t.profileJson!=null&&(r.push("profile_json = ?"),o.push(JSON.stringify(t.profileJson))),t.version!=null&&(r.push("version = ?"),o.push(t.version)),o.push(e),this.stmt(`UPDATE profiles SET ${r.join(", ")} WHERE id = ?`).run(...o);let a=await this.getProfile(e);if(!a)throw new Error("Failed to update profile");return a}async deleteProfile(e){this.stmt("DELETE FROM profiles WHERE id = ?").run(e)}async getProfile(e){let t=this.stmt("SELECT * FROM profiles WHERE id = ?").get(e);return t?this.mapProfile(t):null}async getProfiles(e){return this.stmt("SELECT * FROM profiles WHERE tenant_id = ? ORDER BY created_at ASC").all(e).map(n=>this.mapProfile(n))}async findActiveProfileByComplianceGroup(e,t){let n=this.stmt(`SELECT * FROM profiles WHERE tenant_id = ?
39
+ `)}migrateToV2(){Ie({exec:e=>this.db.exec(e),all:e=>this.db.prepare(e).all()},this.dbPath)}mapProfile(e){return{id:e.id,tenantId:e.tenant_id,name:e.name,defaultLocale:e.default_locale,version:e.version,profileJson:JSON.parse(e.profile_json),createdAt:e.created_at,updatedAt:e.updated_at}}async createProfile(e){let t=W(),n=new Date().toISOString();this.stmt("INSERT INTO profiles (id,tenant_id,name,default_locale,version,profile_json,created_at,updated_at) VALUES (?,?,?,?,?,?,?,?)").run(t,e.tenantId,e.name,e.defaultLocale,1,JSON.stringify(e.profileJson),n,n);let s=await this.getProfile(t);if(!s)throw new Error("Failed to create profile");return s}async updateProfile(e,t){if(!await this.getProfile(e))throw new Error(`Profile ${e} not found`);let s=new Date().toISOString(),r=["updated_at = ?"],o=[s];t.name!=null&&(r.push("name = ?"),o.push(t.name)),t.defaultLocale!=null&&(r.push("default_locale = ?"),o.push(t.defaultLocale)),t.profileJson!=null&&(r.push("profile_json = ?"),o.push(JSON.stringify(t.profileJson))),t.version!=null&&(r.push("version = ?"),o.push(t.version)),o.push(e),this.stmt(`UPDATE profiles SET ${r.join(", ")} WHERE id = ?`).run(...o);let a=await this.getProfile(e);if(!a)throw new Error("Failed to update profile");return a}async deleteProfile(e){this.stmt("DELETE FROM profiles WHERE id = ?").run(e)}async getProfile(e){let t=this.stmt("SELECT * FROM profiles WHERE id = ?").get(e);return t?this.mapProfile(t):null}async getProfiles(e){return this.stmt("SELECT * FROM profiles WHERE tenant_id = ? ORDER BY created_at ASC").all(e).map(n=>this.mapProfile(n))}async findActiveProfileByComplianceGroup(e,t){let n=this.stmt(`SELECT * FROM profiles WHERE tenant_id = ?
43
40
  AND (json_extract(profile_json, '$.complianceGroup') = ? OR json_extract(profile_json, '$.customComplianceGroup') = ?)
44
- AND json_extract(profile_json, '$.isActive') = 1 LIMIT 1`).get(e,t,t);return n?this.mapProfile(n):null}mapConsent(e){return{id:e.id,tenantId:e.tenant_id,visitorId:e.visitor_id,profileId:e.profile_id,locale:e.locale,consentJson:JSON.parse(e.consent_json),gpcDetected:e.gpc_detected===1,source:e.source,...e.age_verified!=null&&e.age_verified!==0?{ageVerified:!0}:{},...e.parental_consent_token!=null?{parentalConsentToken:e.parental_consent_token}:{},...e.tcf_string!=null?{tcfString:e.tcf_string}:{},...e.gpp_string!=null?{gppString:e.gpp_string}:{},...e.signature!=null?{signature:e.signature}:{},createdAt:e.created_at,updatedAt:e.updated_at}}static CONSENT_SUMMARY_COLS="id, tenant_id, visitor_id, profile_id, locale, gpc_detected, source, age_verified, created_at, updated_at";mapConsentSummary(e){return{id:e.id,tenantId:e.tenant_id,visitorId:e.visitor_id,profileId:e.profile_id,locale:e.locale,gpcDetected:e.gpc_detected===1,source:e.source,...e.age_verified!=null&&e.age_verified!==0?{ageVerified:!0}:{},createdAt:e.created_at,updatedAt:e.updated_at}}async createConsent(e){let t=W(),n=new Date().toISOString();this.stmt("INSERT INTO consent_records (id,tenant_id,visitor_id,profile_id,locale,consent_json,gpc_detected,source,age_verified,parental_consent_token,tcf_string,gpp_string,signature,created_at,updated_at) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)").run(t,e.tenantId,e.visitorId,e.profileId,e.locale,JSON.stringify(e.consentJson),e.gpcDetected?1:0,e.source,e.ageVerified?1:0,e.parentalConsentToken??null,e.tcfString??null,e.gppString??null,e.signature??null,n,n);let s=A();this.stmt("INSERT INTO consent_history (id,tenant_id,consent_record_id,visitor_id,old_json,new_json,action,created_at) VALUES (?,?,?,?,?,?,?,?)").run(s,e.tenantId,t,e.visitorId,null,JSON.stringify(e.consentJson),"created",n);let r=await this.getConsent(e.visitorId);if(!r)throw new Error("Failed to create consent");return r}async updateConsent(e,t){let n=await this.getConsent(e);if(!n)throw new Error(`Consent for visitor ${e} not found`);let s=new Date().toISOString(),r=["updated_at = ?","consent_json = ?"],o=[s,JSON.stringify(t.consentJson)];t.locale!=null&&(r.push("locale = ?"),o.push(t.locale)),t.gpcDetected!=null&&(r.push("gpc_detected = ?"),o.push(t.gpcDetected?1:0)),t.signature!=null&&(r.push("signature = ?"),o.push(t.signature)),o.push(e),this.stmt(`UPDATE consent_records SET ${r.join(", ")} WHERE visitor_id = ?`).run(...o);let a=A();this.stmt("INSERT INTO consent_history (id,tenant_id,consent_record_id,visitor_id,old_json,new_json,action,created_at) VALUES (?,?,?,?,?,?,?,?)").run(a,n.tenantId,n.id,e,JSON.stringify(n.consentJson),JSON.stringify(t.consentJson),"updated",s);let c=await this.getConsent(e);if(!c)throw new Error("Failed to update consent");return c}async deleteConsent(e){this.stmt("DELETE FROM consent_history WHERE visitor_id = ?").run(e),this.stmt("DELETE FROM consent_records WHERE visitor_id = ?").run(e)}async getConsent(e){let t=this.stmt("SELECT * FROM consent_records WHERE visitor_id = ?").get(e);return t?this.mapConsent(t):null}async getConsents(e){let t=["tenant_id = ?"],n=[e.tenantId];if(e.profileId&&(t.push("profile_id = ?"),n.push(e.profileId)),e.from&&(t.push("created_at >= ?"),n.push(e.from)),e.to&&(t.push("created_at <= ?"),n.push(e.to)),e.q){t.push("(visitor_id LIKE ? OR profile_id LIKE ? OR locale LIKE ? OR source LIKE ?) ESCAPE '\\'");let d=j(e.q);n.push(d,d,d,d)}let s=t.join(" AND "),r=e.page??1,o=Math.min(e.limit??50,ln),a=(r-1)*o,c=this.stmt(`SELECT ${i.CONSENT_SUMMARY_COLS} FROM consent_records WHERE ${s} ORDER BY created_at DESC LIMIT ? OFFSET ?`).all(...n,o,a),l=this.stmt(`SELECT COUNT(*) AS total FROM consent_records WHERE ${s}`).get(...n);return{items:c.map(d=>this.mapConsentSummary(d)),total:l.total,page:r,limit:o}}async*streamConsents(e){let n=0;for(;;){let s=this.stmt("SELECT * FROM consent_records WHERE tenant_id = ? LIMIT ? OFFSET ?").all(e.tenantId,500,n);if(s.length===0)break;for(let r of s)yield this.mapConsent(r);n+=500}}async getConsentHistory(e){return this.stmt("SELECT * FROM consent_history WHERE visitor_id = ? ORDER BY created_at ASC").all(e).map(n=>({id:n.id,tenantId:n.tenant_id,consentRecordId:n.consent_record_id,visitorId:n.visitor_id,oldJson:n.old_json!=null?JSON.parse(n.old_json):null,newJson:JSON.parse(n.new_json),action:n.action,createdAt:n.created_at}))}async createNoticeShown(e){let t=A(),n=new Date().toISOString();return this.stmt("INSERT INTO notice_shown (id,tenant_id,visitor_id,profile_id,locale,created_at) VALUES (?,?,?,?,?,?)").run(t,e.tenantId,e.visitorId,e.profileId,e.locale,n),{id:t,tenantId:e.tenantId,visitorId:e.visitorId,profileId:e.profileId,locale:e.locale,createdAt:n}}async getNoticeShownForVisitor(e){return this.stmt("SELECT * FROM notice_shown WHERE visitor_id = ? ORDER BY created_at DESC").all(e).map(n=>({id:n.id,tenantId:n.tenant_id,visitorId:n.visitor_id,profileId:n.profile_id,locale:n.locale,createdAt:n.created_at}))}mapVisitor(e){return{id:e.id,tenantId:e.tenant_id,visitorId:e.visitor_id,firstSeen:e.first_seen,lastSeen:e.last_seen,...e.country!=null?{country:e.country}:{},...e.region!=null?{region:e.region}:{},...e.city!=null?{city:e.city}:{},...e.ip_hash!=null?{ipHash:e.ip_hash}:{},...e.user_agent_hash!=null?{userAgentHash:e.user_agent_hash}:{}}}async createVisitor(e){let t=H(),n=new Date().toISOString();this.stmt("INSERT INTO visitors (id,tenant_id,visitor_id,country,region,city,ip_hash,user_agent_hash,first_seen,last_seen) VALUES (?,?,?,?,?,?,?,?,?,?)").run(t,e.tenantId,e.visitorId,e.country??null,e.region??null,e.city??null,e.ipHash??null,e.userAgentHash??null,n,n);let s=await this.getVisitor(e.visitorId);if(!s)throw new Error("Failed to create visitor");return s}async updateVisitor(e,t){let n=new Date().toISOString(),s=["last_seen = ?"],r=[t.lastSeen??n];t.country!=null&&(s.push("country = ?"),r.push(t.country)),t.region!=null&&(s.push("region = ?"),r.push(t.region)),t.city!=null&&(s.push("city = ?"),r.push(t.city)),r.push(e),this.stmt(`UPDATE visitors SET ${s.join(", ")} WHERE visitor_id = ?`).run(...r);let o=await this.getVisitor(e);if(!o)throw new Error(`Visitor ${e} not found`);return o}async deleteVisitor(e){this.stmt("DELETE FROM visitors WHERE visitor_id = ?").run(e)}async getVisitor(e){let t=this.stmt("SELECT * FROM visitors WHERE visitor_id = ?").get(e);return t?this.mapVisitor(t):null}async getVisitors(e){let t=["tenant_id = ?"],n=[e.tenantId];if(e.from&&(t.push("first_seen >= ?"),n.push(e.from)),e.to&&(t.push("first_seen <= ?"),n.push(e.to)),e.q){t.push("(visitor_id LIKE ? OR country LIKE ?) ESCAPE '\\'");let d=j(e.q);n.push(d,d)}let s=t.join(" AND "),r=e.page??1,o=Math.min(e.limit??50,ln),a=(r-1)*o,c=this.stmt(`SELECT * FROM visitors WHERE ${s} ORDER BY last_seen DESC LIMIT ? OFFSET ?`).all(...n,o,a),l=this.stmt(`SELECT COUNT(*) AS total FROM visitors WHERE ${s}`).get(...n);return{items:c.map(d=>this.mapVisitor(d)),total:l.total,page:r,limit:o}}mapUser(e){let t=e.allowed_tenants?JSON.parse(e.allowed_tenants):[];return{id:e.id,tenantId:e.tenant_id,name:e.name,email:e.email,passwordHash:e.password_hash,isActive:e.is_active===1,totpEnabled:e.totp_enabled===1,...e.totp_secret!=null?{totpSecret:e.totp_secret}:{},allowedTenants:t,createdAt:e.created_at,updatedAt:e.updated_at}}async createUser(e){let t=A(),n=new Date().toISOString(),s=e.allowedTenants?.length?JSON.stringify(e.allowedTenants):null;this.stmt("INSERT INTO users (id,tenant_id,name,email,password_hash,allowed_tenants,created_at,updated_at) VALUES (?,?,?,?,?,?,?,?)").run(t,e.tenantId,e.name,e.email,e.passwordHash,s,n,n);let r=await this.getUserByEmail(e.email);if(!r)throw new Error("Failed to create user");return r}async updateUser(e,t){let n=new Date().toISOString(),s=["updated_at = ?"],r=[n];t.name!=null&&(s.push("name = ?"),r.push(t.name)),t.email!=null&&(s.push("email = ?"),r.push(t.email)),t.passwordHash!=null&&(s.push("password_hash = ?"),r.push(t.passwordHash)),t.isActive!=null&&(s.push("is_active = ?"),r.push(t.isActive?1:0)),"totpSecret"in t&&(s.push("totp_secret = ?"),r.push(t.totpSecret??null)),t.totpEnabled!=null&&(s.push("totp_enabled = ?"),r.push(t.totpEnabled?1:0)),"allowedTenants"in t&&(s.push("allowed_tenants = ?"),r.push(t.allowedTenants?.length?JSON.stringify(t.allowedTenants):null)),r.push(e),this.stmt(`UPDATE users SET ${s.join(", ")} WHERE id = ?`).run(...r);let o=this.stmt("SELECT * FROM users WHERE id = ?").get(e);if(!o)throw new Error(`User ${e} not found`);return this.mapUser(o)}async deleteUser(e){this.stmt("DELETE FROM user_roles WHERE user_id = ?").run(e),this.stmt("DELETE FROM users WHERE id = ?").run(e)}async getUserById(e){let t=this.stmt("SELECT * FROM users WHERE id = ?").get(e);return t?this.mapUser(t):null}async getUserByEmail(e){let t=this.stmt("SELECT * FROM users WHERE email = ?").get(e);return t?this.mapUser(t):null}async getUsers(e){return this.stmt("SELECT * FROM users WHERE tenant_id = ? ORDER BY created_at ASC").all(e).map(n=>this.mapUser(n))}async countUsers(e){return(e?this.stmt("SELECT COUNT(*) as n FROM users WHERE tenant_id = ?").get(e):this.stmt("SELECT COUNT(*) as n FROM users").get())?.n??0}async getRoles(e){return this.stmt("SELECT * FROM roles WHERE tenant_id = ?").all(e).map(n=>({id:n.id,tenantId:n.tenant_id,name:n.name,...n.description!=null?{description:n.description}:{}}))}async createRole(e){let t=A();this.stmt("INSERT INTO roles (id,tenant_id,name,description) VALUES (?,?,?,?)").run(t,e.tenantId,e.name,e.description??null);let n=this.stmt("SELECT * FROM roles WHERE id = ?").get(t);if(!n)throw new Error("Failed to create role");return{id:n.id,tenantId:n.tenant_id,name:n.name,...n.description!=null?{description:n.description}:{}}}async updateRole(e,t){let n=[],s=[];t.name!=null&&(n.push("name = ?"),s.push(t.name)),t.description!=null&&(n.push("description = ?"),s.push(t.description)),n.length>0&&(s.push(e),this.stmt(`UPDATE roles SET ${n.join(", ")} WHERE id = ?`).run(...s));let r=this.stmt("SELECT * FROM roles WHERE id = ?").get(e);if(!r)throw new Error(`Role ${e} not found`);return{id:r.id,tenantId:r.tenant_id,name:r.name,...r.description!=null?{description:r.description}:{}}}async deleteRole(e){this.stmt("DELETE FROM role_permissions WHERE role_id = ?").run(e),this.stmt("DELETE FROM user_roles WHERE role_id = ?").run(e),this.stmt("DELETE FROM roles WHERE id = ?").run(e)}async getAllPermissions(){return this.stmt("SELECT * FROM permissions ORDER BY name ASC").all().map(t=>({id:t.id,name:t.name,...t.description!=null?{description:t.description}:{}}))}async assignPermissionToRole(e,t){this.stmt("INSERT OR IGNORE INTO role_permissions (role_id,permission_id) VALUES (?,?)").run(e,t)}async revokePermissionFromRole(e,t){this.stmt("DELETE FROM role_permissions WHERE role_id = ? AND permission_id = ?").run(e,t)}async getPermissionsForRole(e){return this.stmt("SELECT p.* FROM permissions p JOIN role_permissions rp ON rp.permission_id = p.id WHERE rp.role_id = ?").all(e).map(n=>({id:n.id,name:n.name,...n.description!=null?{description:n.description}:{}}))}async getUserRoles(e,t){return(t?this.stmt("SELECT r.* FROM roles r JOIN user_roles ur ON ur.role_id = r.id WHERE ur.user_id = ? AND ur.tenant_id = ?").all(e,t):this.stmt("SELECT r.* FROM roles r JOIN user_roles ur ON ur.role_id = r.id WHERE ur.user_id = ?").all(e)).map(s=>({id:s.id,tenantId:s.tenant_id,name:s.name,...s.description!=null?{description:s.description}:{}}))}async assignRole(e,t,n="default"){this.stmt("INSERT OR IGNORE INTO user_roles (user_id, role_id, tenant_id) VALUES (?,?,?)").run(e,t,n)}async revokeRole(e,t,n){n?this.stmt("DELETE FROM user_roles WHERE user_id = ? AND role_id = ? AND tenant_id = ?").run(e,t,n):this.stmt("DELETE FROM user_roles WHERE user_id = ? AND role_id = ?").run(e,t)}async createLog(e){let t=A(),n=new Date().toISOString();this.stmt("INSERT INTO audit_logs (id,tenant_id,user_id,action,resource_type,resource_id,old_data,new_data,created_at) VALUES (?,?,?,?,?,?,?,?,?)").run(t,e.tenantId,e.userId??null,e.action,e.resourceType,e.resourceId??null,e.oldData!=null?JSON.stringify(e.oldData):null,e.newData!=null?JSON.stringify(e.newData):null,n)}async getLogs(e){let t=["tenant_id = ?"],n=[e.tenantId];if(e.action&&(t.push("action = ?"),n.push(e.action)),e.resourceType&&(t.push("resource_type = ?"),n.push(e.resourceType)),e.from&&(t.push("created_at >= ?"),n.push(e.from)),e.to&&(t.push("created_at <= ?"),n.push(e.to)),e.q){t.push("(action LIKE ? OR resource_type LIKE ? OR resource_id LIKE ? OR user_id LIKE ?) ESCAPE '\\'");let d=j(e.q);n.push(d,d,d,d)}let s=t.join(" AND "),r=e.page??1,o=Math.min(e.limit??50,ln),a=(r-1)*o,c=this.stmt(`SELECT ${i.AUDIT_SUMMARY_COLS} FROM audit_logs WHERE ${s} ORDER BY created_at DESC LIMIT ? OFFSET ?`).all(...n,o,a),l=this.stmt(`SELECT COUNT(*) AS total FROM audit_logs WHERE ${s}`).get(...n);return{items:c.map(d=>this.mapAuditSummary(d)),total:l.total,page:r,limit:o}}async getAuditLogById(e){let t=this.stmt("SELECT * FROM audit_logs WHERE id = ?").get(e);return t?this.mapAuditLog(t):null}static AUDIT_SUMMARY_COLS="id, tenant_id, user_id, action, resource_type, resource_id, created_at";mapAuditSummary(e){return{id:e.id,tenantId:e.tenant_id,action:e.action,resourceType:e.resource_type,...e.user_id!=null?{userId:e.user_id}:{},...e.resource_id!=null?{resourceId:e.resource_id}:{},createdAt:e.created_at}}mapAuditLog(e){return{id:e.id,tenantId:e.tenant_id,action:e.action,resourceType:e.resource_type,createdAt:e.created_at,...e.user_id!=null?{userId:e.user_id}:{},...e.resource_id!=null?{resourceId:e.resource_id}:{},...e.old_data!=null?{oldData:JSON.parse(e.old_data)}:{},...e.new_data!=null?{newData:JSON.parse(e.new_data)}:{}}}async*streamAuditLogs(e){let t=["tenant_id = ?"],n=[e.tenantId];e.action&&(t.push("action = ?"),n.push(e.action)),e.from&&(t.push("created_at >= ?"),n.push(e.from)),e.to&&(t.push("created_at <= ?"),n.push(e.to));let s=500,r=0;for(;;){let o=this.stmt(`SELECT * FROM audit_logs WHERE ${t.join(" AND ")} ORDER BY created_at DESC LIMIT ? OFFSET ?`).all(...n,s,r);if(o.length===0)break;for(let a of o)yield this.mapAuditLog(a);r+=s}}async getOverviewStats(e){let t=this.stmt("SELECT COUNT(*) as n FROM consent_records WHERE tenant_id = ?").get(e),n=this.stmt("SELECT COUNT(*) as n FROM consent_records WHERE tenant_id = ? AND gpc_detected = 1").get(e),s=this.stmt("SELECT COUNT(*) as n FROM visitors WHERE tenant_id = ?").get(e),r=t?.n??0,o=this.stmt("SELECT consent_json FROM consent_records WHERE tenant_id = ? ORDER BY created_at DESC LIMIT 500").all(e),a=0,c=0;for(let d of o){let u=Object.values(JSON.parse(d.consent_json)),p=u.length>0?u.filter(g=>g==="granted").length/u.length:0;p>=.8?a++:p<=.2&&c++}let l=o.length||1;return{totalConsents:r,gpcDetectedCount:n?.n??0,totalVisitors:s?.n??0,acceptedPct:Math.round(a/l*100),rejectedPct:Math.round(c/l*100)}}async getCategoryStats(e){let t=this.stmt("SELECT consent_json FROM consent_records WHERE tenant_id = ? LIMIT 5000").all(e),n={};for(let s of t){let r=JSON.parse(s.consent_json);for(let[o,a]of Object.entries(r))n[o]||(n[o]={granted:0,denied:0,objected:0}),a==="granted"?n[o].granted++:a==="denied"?n[o].denied++:a==="objected"&&n[o].objected++}return n}async getTimeline(e,t=30){return this.stmt("SELECT date(created_at) as date, COUNT(*) as count FROM consent_records WHERE tenant_id = ? AND created_at >= date('now', ?) GROUP BY date(created_at) ORDER BY date ASC").all(e,`-${t} days`).map(s=>({date:s.date,count:s.count}))}async getCountries(e){return this.stmt("SELECT v.country, COUNT(*) as count FROM consent_records cr JOIN visitors v ON v.visitor_id = cr.visitor_id WHERE cr.tenant_id = ? AND v.country IS NOT NULL GROUP BY v.country ORDER BY count DESC LIMIT 50").all(e).map(n=>({country:n.country,count:n.count}))}async getGpcStats(e){let t=this.stmt("SELECT COUNT(*) as n FROM consent_records WHERE tenant_id = ?").get(e),n=this.stmt("SELECT COUNT(*) as n FROM consent_records WHERE tenant_id = ? AND gpc_detected = 1").get(e),s=t?.n??0,r=n?.n??0;return{detected:r,total:s,rate:s>0?Math.round(r/s*100):0}}async getTenants(){return this.stmt("SELECT * FROM tenants ORDER BY name ASC").all().map(t=>({id:t.id,name:t.name,slug:t.slug,createdAt:t.created_at,updatedAt:t.updated_at}))}async createTenant(e){let t=A(),n=new Date().toISOString();this.stmt("INSERT INTO tenants (id,name,slug,created_at,updated_at) VALUES (?,?,?,?,?)").run(t,e.name,e.slug,n,n);let s=this.stmt("SELECT * FROM tenants WHERE id = ?").get(t);if(!s)throw new Error("Failed to create tenant");return{id:s.id,name:s.name,slug:s.slug,createdAt:s.created_at,updatedAt:s.updated_at}}async updateTenant(e,t){let n=new Date().toISOString(),s=["updated_at = ?"],r=[n];t.name!=null&&(s.push("name = ?"),r.push(t.name)),t.slug!=null&&(s.push("slug = ?"),r.push(t.slug)),r.push(e),this.stmt(`UPDATE tenants SET ${s.join(", ")} WHERE id = ?`).run(...r);let o=this.stmt("SELECT * FROM tenants WHERE id = ?").get(e);if(!o)throw new Error(`Tenant ${e} not found`);return{id:o.id,name:o.name,slug:o.slug,createdAt:o.created_at,updatedAt:o.updated_at}}async deleteTenant(e){this.stmt("DELETE FROM tenants WHERE id = ?").run(e)}async getSettings(e){let t=this.stmt("SELECT allowed_origins_json, admin_allowed_origins_json, setup_completed, profiles_seeded, tcf_confirmation_json, gpp_confirmation_json FROM tenant_settings WHERE tenant_id = ?").get(e);return t?{allowedOrigins:JSON.parse(t.allowed_origins_json),adminAllowedOrigins:JSON.parse(t.admin_allowed_origins_json),setupCompleted:!!t.setup_completed,profilesSeeded:!!t.profiles_seeded,tcfConfirmation:t.tcf_confirmation_json?JSON.parse(t.tcf_confirmation_json):void 0,gppConfirmation:t.gpp_confirmation_json?JSON.parse(t.gpp_confirmation_json):void 0}:{}}async updateSettings(e,t){let s={...await this.getSettings(e),...t};return this.stmt(`INSERT INTO tenant_settings (tenant_id, allowed_origins_json, admin_allowed_origins_json, setup_completed, profiles_seeded, tcf_confirmation_json, gpp_confirmation_json, updated_at) VALUES (?,?,?,?,?,?,?,?)
45
- ON CONFLICT(tenant_id) DO UPDATE SET allowed_origins_json = excluded.allowed_origins_json, admin_allowed_origins_json = excluded.admin_allowed_origins_json, setup_completed = excluded.setup_completed, profiles_seeded = excluded.profiles_seeded, tcf_confirmation_json = excluded.tcf_confirmation_json, gpp_confirmation_json = excluded.gpp_confirmation_json, updated_at = excluded.updated_at`).run(e,JSON.stringify(s.allowedOrigins??[]),JSON.stringify(s.adminAllowedOrigins??[]),s.setupCompleted?1:0,s.profilesSeeded?1:0,s.tcfConfirmation?JSON.stringify(s.tcfConfirmation):null,s.gppConfirmation?JSON.stringify(s.gppConfirmation):null,new Date().toISOString()),this.getSettings(e)}async purgeExpiredConsents(e){let t=new Date(Date.now()-e*864e5).toISOString(),n=this.stmt("SELECT visitor_id FROM consent_records WHERE created_at < ?").all(t);for(let{visitor_id:s}of n)this.stmt("DELETE FROM consent_history WHERE visitor_id = ?").run(s),this.stmt("DELETE FROM consent_records WHERE visitor_id = ?").run(s);return n.length}mapApiKey(e){return{id:e.id,tenantId:e.tenant_id,keyHash:e.key_hash,name:e.name,isActive:e.is_active===1,...e.created_by!==null?{createdBy:e.created_by}:{},...e.expire_by!==null?{expireBy:e.expire_by}:{},createdAt:e.created_at,...e.updated_at!==null?{updatedAt:e.updated_at}:{}}}async createApiKey(e){let t=A(),n=new Date().toISOString();this.stmt("INSERT INTO api_keys (id,tenant_id,key_hash,name,created_by,expire_by,created_at,updated_at) VALUES (?,?,?,?,?,?,?,?)").run(t,e.tenantId,e.keyHash,e.name,e.createdBy??null,e.expireBy??null,n,n);let s=this.stmt("SELECT * FROM api_keys WHERE id = ?").get(t);if(!s)throw new Error("Failed to create API key");return this.mapApiKey(s)}async getApiKeyByHash(e){let t=this.stmt("SELECT * FROM api_keys WHERE key_hash = ? AND is_active = 1").get(e);return t?this.mapApiKey(t):null}async revokeApiKey(e){this.stmt("UPDATE api_keys SET is_active = 0, updated_at = ? WHERE id = ?").run(new Date().toISOString(),e)}async reactivateApiKey(e){let t=new Date().toISOString();this.stmt("UPDATE api_keys SET is_active = 1, updated_at = ?, expire_by = CASE WHEN expire_by <= ? THEN NULL ELSE expire_by END WHERE id = ?").run(t,t,e)}async deleteApiKey(e){this.stmt("DELETE FROM api_keys WHERE id = ?").run(e)}async getApiKeys(e){return this.stmt("SELECT * FROM api_keys WHERE tenant_id = ? ORDER BY created_at DESC").all(e).map(n=>this.mapApiKey(n))}mapConsentTemplate(e){return{id:e.id,tenantId:e.tenant_id,name:e.name,cookies:JSON.parse(e.cookies_json),categories:JSON.parse(e.categories_json),createdAt:e.created_at,updatedAt:e.updated_at}}async createConsentTemplate(e){let t=J(),n=new Date().toISOString();return this.stmt("INSERT INTO consent_templates (id,tenant_id,name,cookies_json,categories_json,created_at,updated_at) VALUES (?,?,?,?,?,?,?)").run(t,e.tenantId,e.name,JSON.stringify(e.cookies),JSON.stringify(e.categories),n,n),this.mapConsentTemplate(this.stmt("SELECT * FROM consent_templates WHERE id = ?").get(t))}async updateConsentTemplate(e,t){let n=new Date().toISOString();t.name!==void 0&&this.stmt("UPDATE consent_templates SET name = ?, updated_at = ? WHERE id = ?").run(t.name,n,e),t.cookies!==void 0&&this.stmt("UPDATE consent_templates SET cookies_json = ?, updated_at = ? WHERE id = ?").run(JSON.stringify(t.cookies),n,e),t.categories!==void 0&&this.stmt("UPDATE consent_templates SET categories_json = ?, updated_at = ? WHERE id = ?").run(JSON.stringify(t.categories),n,e);let s=this.stmt("SELECT * FROM consent_templates WHERE id = ?").get(e);if(!s)throw new Error("Consent template not found");return this.mapConsentTemplate(s)}async deleteConsentTemplate(e){this.stmt("DELETE FROM consent_templates WHERE id = ?").run(e)}async getConsentTemplate(e){let t=this.stmt("SELECT * FROM consent_templates WHERE id = ?").get(e);return t?this.mapConsentTemplate(t):null}async getConsentTemplates(e){return this.stmt("SELECT * FROM consent_templates WHERE tenant_id = ? ORDER BY name ASC").all(e).map(n=>this.mapConsentTemplate(n))}async copyConsentTemplate(e,t){let n=await this.getConsentTemplate(e);if(!n)throw new Error("Consent template not found");return this.createConsentTemplate({tenantId:n.tenantId,name:t,cookies:n.cookies,categories:n.categories})}mapUITemplate(e){let t=JSON.parse(e.settings_json);return{id:e.id,tenantId:e.tenant_id,name:e.name,...t,createdAt:e.created_at,updatedAt:e.updated_at}}async createUITemplate(e){let t=q(),n=new Date().toISOString(),{tenantId:s,name:r,...o}=e;return this.stmt("INSERT INTO ui_templates (id,tenant_id,name,settings_json,created_at,updated_at) VALUES (?,?,?,?,?,?)").run(t,s,r,JSON.stringify(o),n,n),this.mapUITemplate(this.stmt("SELECT * FROM ui_templates WHERE id = ?").get(t))}async updateUITemplate(e,t){let n=new Date().toISOString(),s=this.stmt("SELECT * FROM ui_templates WHERE id = ?").get(e);if(!s)throw new Error("UI template not found");t.name!==void 0&&this.stmt("UPDATE ui_templates SET name = ?, updated_at = ? WHERE id = ?").run(t.name,n,e);let{name:r,...o}=t;if(Object.keys(o).length>0){let c={...JSON.parse(s.settings_json),...o};this.stmt("UPDATE ui_templates SET settings_json = ?, updated_at = ? WHERE id = ?").run(JSON.stringify(c),n,e)}return this.mapUITemplate(this.stmt("SELECT * FROM ui_templates WHERE id = ?").get(e))}async deleteUITemplate(e){this.stmt("DELETE FROM ui_templates WHERE id = ?").run(e)}async getUITemplate(e){let t=this.stmt("SELECT * FROM ui_templates WHERE id = ?").get(e);return t?this.mapUITemplate(t):null}async getUITemplates(e){return this.stmt("SELECT * FROM ui_templates WHERE tenant_id = ? ORDER BY name ASC").all(e).map(n=>this.mapUITemplate(n))}async copyUITemplate(e,t){let n=await this.getUITemplate(e);if(!n)throw new Error("UI template not found");let{tenantId:s,mainBanner:r,gpcBanner:o,preferenceModal:a}=n;return this.createUITemplate({tenantId:s,name:t,mainBanner:r,gpcBanner:o,preferenceModal:a})}mapProfileSummary(e){return{id:e.id,name:e.name,defaultLocale:e.default_locale,complianceGroup:e.compliance_group??null,customComplianceGroup:e.custom_compliance_group??null,isActive:e.is_active===1,consentTemplateName:e.consent_template_name,uiTemplateName:e.ui_template_name,createdAt:e.created_at,updatedAt:e.updated_at}}PROFILE_SUMMARY_SQL=`
41
+ AND json_extract(profile_json, '$.isActive') = 1 LIMIT 1`).get(e,t,t);return n?this.mapProfile(n):null}mapConsent(e){return{id:e.id,tenantId:e.tenant_id,visitorId:e.visitor_id,profileId:e.profile_id,...e.profile_version!=null?{profileVersion:e.profile_version}:{},locale:e.locale,consentJson:JSON.parse(e.consent_json),gpcDetected:e.gpc_detected===1,source:e.source,...e.age_verified!=null&&e.age_verified!==0?{ageVerified:!0}:{},...e.parental_consent_token!=null?{parentalConsentToken:e.parental_consent_token}:{},...e.tcf_string!=null?{tcfString:e.tcf_string}:{},...e.gpp_string!=null?{gppString:e.gpp_string}:{},...e.signature!=null?{signature:e.signature}:{},createdAt:e.created_at,updatedAt:e.updated_at}}static CONSENT_SUMMARY_COLS="id, tenant_id, visitor_id, profile_id, locale, gpc_detected, source, age_verified, created_at, updated_at";mapConsentSummary(e){return{id:e.id,tenantId:e.tenant_id,visitorId:e.visitor_id,profileId:e.profile_id,locale:e.locale,gpcDetected:e.gpc_detected===1,source:e.source,...e.age_verified!=null&&e.age_verified!==0?{ageVerified:!0}:{},createdAt:e.created_at,updatedAt:e.updated_at}}async createConsent(e){let t=J(),n=new Date().toISOString();this.stmt("INSERT INTO consent_records (id,tenant_id,visitor_id,profile_id,profile_version,locale,consent_json,gpc_detected,source,age_verified,parental_consent_token,tcf_string,gpp_string,signature,created_at,updated_at) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)").run(t,e.tenantId,e.visitorId,e.profileId,e.profileVersion??null,e.locale,JSON.stringify(e.consentJson),e.gpcDetected?1:0,e.source,e.ageVerified?1:0,e.parentalConsentToken??null,e.tcfString??null,e.gppString??null,e.signature??null,n,n);let s=A();this.stmt("INSERT INTO consent_history (id,tenant_id,consent_record_id,visitor_id,profile_version,old_json,new_json,action,created_at) VALUES (?,?,?,?,?,?,?,?,?)").run(s,e.tenantId,t,e.visitorId,e.profileVersion??null,null,JSON.stringify(e.consentJson),"created",n);let r=await this.getConsent(e.visitorId);if(!r)throw new Error("Failed to create consent");return r}async updateConsent(e,t){let n=await this.getConsent(e);if(!n)throw new Error(`Consent for visitor ${e} not found`);let s=new Date().toISOString(),r=["updated_at = ?","consent_json = ?"],o=[s,JSON.stringify(t.consentJson)];t.profileVersion!=null&&(r.push("profile_version = ?"),o.push(t.profileVersion)),t.locale!=null&&(r.push("locale = ?"),o.push(t.locale)),t.gpcDetected!=null&&(r.push("gpc_detected = ?"),o.push(t.gpcDetected?1:0)),t.signature!=null&&(r.push("signature = ?"),o.push(t.signature)),o.push(e),this.stmt(`UPDATE consent_records SET ${r.join(", ")} WHERE visitor_id = ?`).run(...o);let a=A();this.stmt("INSERT INTO consent_history (id,tenant_id,consent_record_id,visitor_id,profile_version,old_json,new_json,action,created_at) VALUES (?,?,?,?,?,?,?,?,?)").run(a,n.tenantId,n.id,e,t.profileVersion??n.profileVersion??null,JSON.stringify(n.consentJson),JSON.stringify(t.consentJson),"updated",s);let c=await this.getConsent(e);if(!c)throw new Error("Failed to update consent");return c}async deleteConsent(e){this.stmt("DELETE FROM consent_history WHERE visitor_id = ?").run(e),this.stmt("DELETE FROM consent_records WHERE visitor_id = ?").run(e)}async getConsent(e){let t=this.stmt("SELECT * FROM consent_records WHERE visitor_id = ?").get(e);return t?this.mapConsent(t):null}async getConsents(e){let t=["tenant_id = ?"],n=[e.tenantId];if(e.profileId&&(t.push("profile_id = ?"),n.push(e.profileId)),e.from&&(t.push("created_at >= ?"),n.push(e.from)),e.to&&(t.push("created_at <= ?"),n.push(e.to)),e.q){t.push("(visitor_id LIKE ? OR profile_id LIKE ? OR locale LIKE ? OR source LIKE ?) ESCAPE '\\'");let d=x(e.q);n.push(d,d,d,d)}let s=t.join(" AND "),r=e.page??1,o=Math.min(e.limit??50,En),a=(r-1)*o,c=this.stmt(`SELECT ${i.CONSENT_SUMMARY_COLS} FROM consent_records WHERE ${s} ORDER BY created_at DESC LIMIT ? OFFSET ?`).all(...n,o,a),l=this.stmt(`SELECT COUNT(*) AS total FROM consent_records WHERE ${s}`).get(...n);return{items:c.map(d=>this.mapConsentSummary(d)),total:l.total,page:r,limit:o}}async*streamConsents(e){let n=0;for(;;){let s=this.stmt("SELECT * FROM consent_records WHERE tenant_id = ? LIMIT ? OFFSET ?").all(e.tenantId,500,n);if(s.length===0)break;for(let r of s)yield this.mapConsent(r);n+=500}}async getConsentHistory(e){return this.stmt("SELECT * FROM consent_history WHERE visitor_id = ? ORDER BY created_at ASC").all(e).map(n=>({id:n.id,tenantId:n.tenant_id,consentRecordId:n.consent_record_id,visitorId:n.visitor_id,...n.profile_version!=null?{profileVersion:n.profile_version}:{},oldJson:n.old_json!=null?JSON.parse(n.old_json):null,newJson:JSON.parse(n.new_json),action:n.action,createdAt:n.created_at}))}async createNoticeShown(e){let t=A(),n=new Date().toISOString();return this.stmt("INSERT INTO notice_shown (id,tenant_id,visitor_id,profile_id,locale,created_at) VALUES (?,?,?,?,?,?)").run(t,e.tenantId,e.visitorId,e.profileId,e.locale,n),{id:t,tenantId:e.tenantId,visitorId:e.visitorId,profileId:e.profileId,locale:e.locale,createdAt:n}}async getNoticeShownForVisitor(e){return this.stmt("SELECT * FROM notice_shown WHERE visitor_id = ? ORDER BY created_at DESC").all(e).map(n=>({id:n.id,tenantId:n.tenant_id,visitorId:n.visitor_id,profileId:n.profile_id,locale:n.locale,createdAt:n.created_at}))}mapVisitor(e){return{id:e.id,tenantId:e.tenant_id,visitorId:e.visitor_id,firstSeen:e.first_seen,lastSeen:e.last_seen,...e.country!=null?{country:e.country}:{},...e.region!=null?{region:e.region}:{},...e.city!=null?{city:e.city}:{},...e.ip_hash!=null?{ipHash:e.ip_hash}:{},...e.user_agent_hash!=null?{userAgentHash:e.user_agent_hash}:{}}}async createVisitor(e){let t=H(),n=new Date().toISOString();this.stmt("INSERT INTO visitors (id,tenant_id,visitor_id,country,region,city,ip_hash,user_agent_hash,first_seen,last_seen) VALUES (?,?,?,?,?,?,?,?,?,?)").run(t,e.tenantId,e.visitorId,e.country??null,e.region??null,e.city??null,e.ipHash??null,e.userAgentHash??null,n,n);let s=await this.getVisitor(e.visitorId);if(!s)throw new Error("Failed to create visitor");return s}async updateVisitor(e,t){let n=new Date().toISOString(),s=["last_seen = ?"],r=[t.lastSeen??n];t.country!=null&&(s.push("country = ?"),r.push(t.country)),t.region!=null&&(s.push("region = ?"),r.push(t.region)),t.city!=null&&(s.push("city = ?"),r.push(t.city)),r.push(e),this.stmt(`UPDATE visitors SET ${s.join(", ")} WHERE visitor_id = ?`).run(...r);let o=await this.getVisitor(e);if(!o)throw new Error(`Visitor ${e} not found`);return o}async deleteVisitor(e){this.stmt("DELETE FROM visitors WHERE visitor_id = ?").run(e)}async getVisitor(e){let t=this.stmt("SELECT * FROM visitors WHERE visitor_id = ?").get(e);return t?this.mapVisitor(t):null}async getVisitors(e){let t=["tenant_id = ?"],n=[e.tenantId];if(e.from&&(t.push("first_seen >= ?"),n.push(e.from)),e.to&&(t.push("first_seen <= ?"),n.push(e.to)),e.q){t.push("(visitor_id LIKE ? OR country LIKE ?) ESCAPE '\\'");let d=x(e.q);n.push(d,d)}let s=t.join(" AND "),r=e.page??1,o=Math.min(e.limit??50,En),a=(r-1)*o,c=this.stmt(`SELECT * FROM visitors WHERE ${s} ORDER BY last_seen DESC LIMIT ? OFFSET ?`).all(...n,o,a),l=this.stmt(`SELECT COUNT(*) AS total FROM visitors WHERE ${s}`).get(...n);return{items:c.map(d=>this.mapVisitor(d)),total:l.total,page:r,limit:o}}mapUser(e){let t=e.allowed_tenants?JSON.parse(e.allowed_tenants):[];return{id:e.id,tenantId:e.tenant_id,name:e.name,email:e.email,passwordHash:e.password_hash,isActive:e.is_active===1,totpEnabled:e.totp_enabled===1,...e.totp_secret!=null?{totpSecret:e.totp_secret}:{},allowedTenants:t,createdAt:e.created_at,updatedAt:e.updated_at}}async createUser(e){let t=A(),n=new Date().toISOString(),s=e.allowedTenants?.length?JSON.stringify(e.allowedTenants):null;this.stmt("INSERT INTO users (id,tenant_id,name,email,password_hash,allowed_tenants,created_at,updated_at) VALUES (?,?,?,?,?,?,?,?)").run(t,e.tenantId,e.name,e.email,e.passwordHash,s,n,n);let r=await this.getUserByEmail(e.email);if(!r)throw new Error("Failed to create user");return r}async updateUser(e,t){let n=new Date().toISOString(),s=["updated_at = ?"],r=[n];t.name!=null&&(s.push("name = ?"),r.push(t.name)),t.email!=null&&(s.push("email = ?"),r.push(t.email)),t.passwordHash!=null&&(s.push("password_hash = ?"),r.push(t.passwordHash)),t.isActive!=null&&(s.push("is_active = ?"),r.push(t.isActive?1:0)),"totpSecret"in t&&(s.push("totp_secret = ?"),r.push(t.totpSecret??null)),t.totpEnabled!=null&&(s.push("totp_enabled = ?"),r.push(t.totpEnabled?1:0)),"allowedTenants"in t&&(s.push("allowed_tenants = ?"),r.push(t.allowedTenants?.length?JSON.stringify(t.allowedTenants):null)),r.push(e),this.stmt(`UPDATE users SET ${s.join(", ")} WHERE id = ?`).run(...r);let o=this.stmt("SELECT * FROM users WHERE id = ?").get(e);if(!o)throw new Error(`User ${e} not found`);return this.mapUser(o)}async deleteUser(e){this.stmt("DELETE FROM user_roles WHERE user_id = ?").run(e),this.stmt("DELETE FROM users WHERE id = ?").run(e)}async getUserById(e){let t=this.stmt("SELECT * FROM users WHERE id = ?").get(e);return t?this.mapUser(t):null}async getUserByEmail(e){let t=this.stmt("SELECT * FROM users WHERE email = ?").get(e);return t?this.mapUser(t):null}async getUsers(e){return this.stmt("SELECT * FROM users WHERE tenant_id = ? ORDER BY created_at ASC").all(e).map(n=>this.mapUser(n))}async countUsers(e){return(e?this.stmt("SELECT COUNT(*) as n FROM users WHERE tenant_id = ?").get(e):this.stmt("SELECT COUNT(*) as n FROM users").get())?.n??0}async getRoles(e){return this.stmt("SELECT * FROM roles WHERE tenant_id = ?").all(e).map(n=>({id:n.id,tenantId:n.tenant_id,name:n.name,...n.description!=null?{description:n.description}:{}}))}async createRole(e){let t=A();this.stmt("INSERT INTO roles (id,tenant_id,name,description) VALUES (?,?,?,?)").run(t,e.tenantId,e.name,e.description??null);let n=this.stmt("SELECT * FROM roles WHERE id = ?").get(t);if(!n)throw new Error("Failed to create role");return{id:n.id,tenantId:n.tenant_id,name:n.name,...n.description!=null?{description:n.description}:{}}}async updateRole(e,t){let n=[],s=[];t.name!=null&&(n.push("name = ?"),s.push(t.name)),t.description!=null&&(n.push("description = ?"),s.push(t.description)),n.length>0&&(s.push(e),this.stmt(`UPDATE roles SET ${n.join(", ")} WHERE id = ?`).run(...s));let r=this.stmt("SELECT * FROM roles WHERE id = ?").get(e);if(!r)throw new Error(`Role ${e} not found`);return{id:r.id,tenantId:r.tenant_id,name:r.name,...r.description!=null?{description:r.description}:{}}}async deleteRole(e){this.stmt("DELETE FROM role_permissions WHERE role_id = ?").run(e),this.stmt("DELETE FROM user_roles WHERE role_id = ?").run(e),this.stmt("DELETE FROM roles WHERE id = ?").run(e)}async getAllPermissions(){return this.stmt("SELECT * FROM permissions ORDER BY name ASC").all().map(t=>({id:t.id,name:t.name,...t.description!=null?{description:t.description}:{}}))}async assignPermissionToRole(e,t){this.stmt("INSERT OR IGNORE INTO role_permissions (role_id,permission_id) VALUES (?,?)").run(e,t)}async revokePermissionFromRole(e,t){this.stmt("DELETE FROM role_permissions WHERE role_id = ? AND permission_id = ?").run(e,t)}async getPermissionsForRole(e){return this.stmt("SELECT p.* FROM permissions p JOIN role_permissions rp ON rp.permission_id = p.id WHERE rp.role_id = ?").all(e).map(n=>({id:n.id,name:n.name,...n.description!=null?{description:n.description}:{}}))}async getUserRoles(e,t){return(t?this.stmt("SELECT r.* FROM roles r JOIN user_roles ur ON ur.role_id = r.id WHERE ur.user_id = ? AND ur.tenant_id = ?").all(e,t):this.stmt("SELECT r.* FROM roles r JOIN user_roles ur ON ur.role_id = r.id WHERE ur.user_id = ?").all(e)).map(s=>({id:s.id,tenantId:s.tenant_id,name:s.name,...s.description!=null?{description:s.description}:{}}))}async assignRole(e,t,n="default"){this.stmt("INSERT OR IGNORE INTO user_roles (user_id, role_id, tenant_id) VALUES (?,?,?)").run(e,t,n)}async revokeRole(e,t,n){n?this.stmt("DELETE FROM user_roles WHERE user_id = ? AND role_id = ? AND tenant_id = ?").run(e,t,n):this.stmt("DELETE FROM user_roles WHERE user_id = ? AND role_id = ?").run(e,t)}async createLog(e){let t=A(),n=new Date().toISOString();this.stmt("INSERT INTO audit_logs (id,tenant_id,user_id,action,resource_type,resource_id,old_data,new_data,created_at) VALUES (?,?,?,?,?,?,?,?,?)").run(t,e.tenantId,e.userId??null,e.action,e.resourceType,e.resourceId??null,e.oldData!=null?JSON.stringify(e.oldData):null,e.newData!=null?JSON.stringify(e.newData):null,n)}async getLogs(e){let t=["tenant_id = ?"],n=[e.tenantId];if(e.action&&(t.push("action = ?"),n.push(e.action)),e.resourceType&&(t.push("resource_type = ?"),n.push(e.resourceType)),e.from&&(t.push("created_at >= ?"),n.push(e.from)),e.to&&(t.push("created_at <= ?"),n.push(e.to)),e.q){t.push("(action LIKE ? OR resource_type LIKE ? OR resource_id LIKE ? OR user_id LIKE ?) ESCAPE '\\'");let d=x(e.q);n.push(d,d,d,d)}let s=t.join(" AND "),r=e.page??1,o=Math.min(e.limit??50,En),a=(r-1)*o,c=this.stmt(`SELECT ${i.AUDIT_SUMMARY_COLS} FROM audit_logs WHERE ${s} ORDER BY created_at DESC LIMIT ? OFFSET ?`).all(...n,o,a),l=this.stmt(`SELECT COUNT(*) AS total FROM audit_logs WHERE ${s}`).get(...n);return{items:c.map(d=>this.mapAuditSummary(d)),total:l.total,page:r,limit:o}}async getAuditLogById(e){let t=this.stmt("SELECT * FROM audit_logs WHERE id = ?").get(e);return t?this.mapAuditLog(t):null}static AUDIT_SUMMARY_COLS="id, tenant_id, user_id, action, resource_type, resource_id, created_at";mapAuditSummary(e){return{id:e.id,tenantId:e.tenant_id,action:e.action,resourceType:e.resource_type,...e.user_id!=null?{userId:e.user_id}:{},...e.resource_id!=null?{resourceId:e.resource_id}:{},createdAt:e.created_at}}mapAuditLog(e){return{id:e.id,tenantId:e.tenant_id,action:e.action,resourceType:e.resource_type,createdAt:e.created_at,...e.user_id!=null?{userId:e.user_id}:{},...e.resource_id!=null?{resourceId:e.resource_id}:{},...e.old_data!=null?{oldData:JSON.parse(e.old_data)}:{},...e.new_data!=null?{newData:JSON.parse(e.new_data)}:{}}}async*streamAuditLogs(e){let t=["tenant_id = ?"],n=[e.tenantId];e.action&&(t.push("action = ?"),n.push(e.action)),e.from&&(t.push("created_at >= ?"),n.push(e.from)),e.to&&(t.push("created_at <= ?"),n.push(e.to));let s=500,r=0;for(;;){let o=this.stmt(`SELECT * FROM audit_logs WHERE ${t.join(" AND ")} ORDER BY created_at DESC LIMIT ? OFFSET ?`).all(...n,s,r);if(o.length===0)break;for(let a of o)yield this.mapAuditLog(a);r+=s}}async getOverviewStats(e){let t=this.stmt("SELECT COUNT(*) as n FROM consent_records WHERE tenant_id = ?").get(e),n=this.stmt("SELECT COUNT(*) as n FROM consent_records WHERE tenant_id = ? AND gpc_detected = 1").get(e),s=this.stmt("SELECT COUNT(*) as n FROM visitors WHERE tenant_id = ?").get(e),r=t?.n??0,o=this.stmt("SELECT consent_json FROM consent_records WHERE tenant_id = ? ORDER BY created_at DESC LIMIT 500").all(e),a=0,c=0;for(let d of o){let u=Object.values(JSON.parse(d.consent_json)),p=u.length>0?u.filter(g=>g==="granted").length/u.length:0;p>=.8?a++:p<=.2&&c++}let l=o.length||1;return{totalConsents:r,gpcDetectedCount:n?.n??0,totalVisitors:s?.n??0,acceptedPct:Math.round(a/l*100),rejectedPct:Math.round(c/l*100)}}async getCategoryStats(e){let t=this.stmt("SELECT consent_json FROM consent_records WHERE tenant_id = ? LIMIT 5000").all(e),n={};for(let s of t){let r=JSON.parse(s.consent_json);for(let[o,a]of Object.entries(r))n[o]||(n[o]={granted:0,denied:0,objected:0}),a==="granted"?n[o].granted++:a==="denied"?n[o].denied++:a==="objected"&&n[o].objected++}return n}async getTimeline(e,t=30){return this.stmt("SELECT date(created_at) as date, COUNT(*) as count FROM consent_records WHERE tenant_id = ? AND created_at >= date('now', ?) GROUP BY date(created_at) ORDER BY date ASC").all(e,`-${t} days`).map(s=>({date:s.date,count:s.count}))}async getCountries(e){return this.stmt("SELECT v.country, COUNT(*) as count FROM consent_records cr JOIN visitors v ON v.visitor_id = cr.visitor_id WHERE cr.tenant_id = ? AND v.country IS NOT NULL GROUP BY v.country ORDER BY count DESC LIMIT 50").all(e).map(n=>({country:n.country,count:n.count}))}async getGpcStats(e){let t=this.stmt("SELECT COUNT(*) as n FROM consent_records WHERE tenant_id = ?").get(e),n=this.stmt("SELECT COUNT(*) as n FROM consent_records WHERE tenant_id = ? AND gpc_detected = 1").get(e),s=t?.n??0,r=n?.n??0;return{detected:r,total:s,rate:s>0?Math.round(r/s*100):0}}async getTenants(){return this.stmt("SELECT * FROM tenants ORDER BY name ASC").all().map(t=>({id:t.id,name:t.name,slug:t.slug,createdAt:t.created_at,updatedAt:t.updated_at}))}async createTenant(e){let t=A(),n=new Date().toISOString();this.stmt("INSERT INTO tenants (id,name,slug,created_at,updated_at) VALUES (?,?,?,?,?)").run(t,e.name,e.slug,n,n);let s=this.stmt("SELECT * FROM tenants WHERE id = ?").get(t);if(!s)throw new Error("Failed to create tenant");return{id:s.id,name:s.name,slug:s.slug,createdAt:s.created_at,updatedAt:s.updated_at}}async updateTenant(e,t){let n=new Date().toISOString(),s=["updated_at = ?"],r=[n];t.name!=null&&(s.push("name = ?"),r.push(t.name)),t.slug!=null&&(s.push("slug = ?"),r.push(t.slug)),r.push(e),this.stmt(`UPDATE tenants SET ${s.join(", ")} WHERE id = ?`).run(...r);let o=this.stmt("SELECT * FROM tenants WHERE id = ?").get(e);if(!o)throw new Error(`Tenant ${e} not found`);return{id:o.id,name:o.name,slug:o.slug,createdAt:o.created_at,updatedAt:o.updated_at}}async deleteTenant(e){this.stmt("DELETE FROM tenants WHERE id = ?").run(e)}async getSettings(e){let t=this.stmt("SELECT allowed_origins_json, admin_allowed_origins_json, setup_completed, profiles_seeded, tcf_confirmation_json, gpp_confirmation_json FROM tenant_settings WHERE tenant_id = ?").get(e);return t?{allowedOrigins:JSON.parse(t.allowed_origins_json),adminAllowedOrigins:JSON.parse(t.admin_allowed_origins_json),setupCompleted:!!t.setup_completed,profilesSeeded:!!t.profiles_seeded,tcfConfirmation:t.tcf_confirmation_json?JSON.parse(t.tcf_confirmation_json):void 0,gppConfirmation:t.gpp_confirmation_json?JSON.parse(t.gpp_confirmation_json):void 0}:{}}async updateSettings(e,t){let s={...await this.getSettings(e),...t};return this.stmt(`INSERT INTO tenant_settings (tenant_id, allowed_origins_json, admin_allowed_origins_json, setup_completed, profiles_seeded, tcf_confirmation_json, gpp_confirmation_json, updated_at) VALUES (?,?,?,?,?,?,?,?)
42
+ ON CONFLICT(tenant_id) DO UPDATE SET allowed_origins_json = excluded.allowed_origins_json, admin_allowed_origins_json = excluded.admin_allowed_origins_json, setup_completed = excluded.setup_completed, profiles_seeded = excluded.profiles_seeded, tcf_confirmation_json = excluded.tcf_confirmation_json, gpp_confirmation_json = excluded.gpp_confirmation_json, updated_at = excluded.updated_at`).run(e,JSON.stringify(s.allowedOrigins??[]),JSON.stringify(s.adminAllowedOrigins??[]),s.setupCompleted?1:0,s.profilesSeeded?1:0,s.tcfConfirmation?JSON.stringify(s.tcfConfirmation):null,s.gppConfirmation?JSON.stringify(s.gppConfirmation):null,new Date().toISOString()),this.getSettings(e)}async purgeExpiredConsents(e){let t=new Date(Date.now()-e*864e5).toISOString(),n=this.stmt("SELECT visitor_id FROM consent_records WHERE created_at < ?").all(t);for(let{visitor_id:s}of n)this.stmt("DELETE FROM consent_history WHERE visitor_id = ?").run(s),this.stmt("DELETE FROM consent_records WHERE visitor_id = ?").run(s);return n.length}mapApiKey(e){return{id:e.id,tenantId:e.tenant_id,keyHash:e.key_hash,name:e.name,isActive:e.is_active===1,...e.created_by!==null?{createdBy:e.created_by}:{},...e.expire_by!==null?{expireBy:e.expire_by}:{},createdAt:e.created_at,...e.updated_at!==null?{updatedAt:e.updated_at}:{}}}async createApiKey(e){let t=A(),n=new Date().toISOString();this.stmt("INSERT INTO api_keys (id,tenant_id,key_hash,name,created_by,expire_by,created_at,updated_at) VALUES (?,?,?,?,?,?,?,?)").run(t,e.tenantId,e.keyHash,e.name,e.createdBy??null,e.expireBy??null,n,n);let s=this.stmt("SELECT * FROM api_keys WHERE id = ?").get(t);if(!s)throw new Error("Failed to create API key");return this.mapApiKey(s)}async getApiKeyByHash(e){let t=this.stmt("SELECT * FROM api_keys WHERE key_hash = ? AND is_active = 1").get(e);return t?this.mapApiKey(t):null}async revokeApiKey(e){this.stmt("UPDATE api_keys SET is_active = 0, updated_at = ? WHERE id = ?").run(new Date().toISOString(),e)}async reactivateApiKey(e){let t=new Date().toISOString();this.stmt("UPDATE api_keys SET is_active = 1, updated_at = ?, expire_by = CASE WHEN expire_by <= ? THEN NULL ELSE expire_by END WHERE id = ?").run(t,t,e)}async deleteApiKey(e){this.stmt("DELETE FROM api_keys WHERE id = ?").run(e)}async getApiKeys(e){return this.stmt("SELECT * FROM api_keys WHERE tenant_id = ? ORDER BY created_at DESC").all(e).map(n=>this.mapApiKey(n))}mapConsentTemplate(e){return{id:e.id,tenantId:e.tenant_id,name:e.name,cookies:JSON.parse(e.cookies_json),categories:JSON.parse(e.categories_json),createdAt:e.created_at,updatedAt:e.updated_at}}async createConsentTemplate(e){let t=G(),n=new Date().toISOString();return this.stmt("INSERT INTO consent_templates (id,tenant_id,name,cookies_json,categories_json,created_at,updated_at) VALUES (?,?,?,?,?,?,?)").run(t,e.tenantId,e.name,JSON.stringify(e.cookies),JSON.stringify(e.categories),n,n),this.mapConsentTemplate(this.stmt("SELECT * FROM consent_templates WHERE id = ?").get(t))}async updateConsentTemplate(e,t){let n=new Date().toISOString();t.name!==void 0&&this.stmt("UPDATE consent_templates SET name = ?, updated_at = ? WHERE id = ?").run(t.name,n,e),t.cookies!==void 0&&this.stmt("UPDATE consent_templates SET cookies_json = ?, updated_at = ? WHERE id = ?").run(JSON.stringify(t.cookies),n,e),t.categories!==void 0&&this.stmt("UPDATE consent_templates SET categories_json = ?, updated_at = ? WHERE id = ?").run(JSON.stringify(t.categories),n,e);let s=this.stmt("SELECT * FROM consent_templates WHERE id = ?").get(e);if(!s)throw new Error("Consent template not found");return this.mapConsentTemplate(s)}async deleteConsentTemplate(e){this.stmt("DELETE FROM consent_templates WHERE id = ?").run(e)}async getConsentTemplate(e){let t=this.stmt("SELECT * FROM consent_templates WHERE id = ?").get(e);return t?this.mapConsentTemplate(t):null}async getConsentTemplates(e){return this.stmt("SELECT * FROM consent_templates WHERE tenant_id = ? ORDER BY name ASC").all(e).map(n=>this.mapConsentTemplate(n))}async copyConsentTemplate(e,t){let n=await this.getConsentTemplate(e);if(!n)throw new Error("Consent template not found");return this.createConsentTemplate({tenantId:n.tenantId,name:t,cookies:n.cookies,categories:n.categories})}mapUITemplate(e){let t=JSON.parse(e.settings_json);return{id:e.id,tenantId:e.tenant_id,name:e.name,...t,createdAt:e.created_at,updatedAt:e.updated_at}}async createUITemplate(e){let t=q(),n=new Date().toISOString(),{tenantId:s,name:r,...o}=e;return this.stmt("INSERT INTO ui_templates (id,tenant_id,name,settings_json,created_at,updated_at) VALUES (?,?,?,?,?,?)").run(t,s,r,JSON.stringify(o),n,n),this.mapUITemplate(this.stmt("SELECT * FROM ui_templates WHERE id = ?").get(t))}async updateUITemplate(e,t){let n=new Date().toISOString(),s=this.stmt("SELECT * FROM ui_templates WHERE id = ?").get(e);if(!s)throw new Error("UI template not found");t.name!==void 0&&this.stmt("UPDATE ui_templates SET name = ?, updated_at = ? WHERE id = ?").run(t.name,n,e);let{name:r,...o}=t;if(Object.keys(o).length>0){let c={...JSON.parse(s.settings_json),...o};this.stmt("UPDATE ui_templates SET settings_json = ?, updated_at = ? WHERE id = ?").run(JSON.stringify(c),n,e)}return this.mapUITemplate(this.stmt("SELECT * FROM ui_templates WHERE id = ?").get(e))}async deleteUITemplate(e){this.stmt("DELETE FROM ui_templates WHERE id = ?").run(e)}async getUITemplate(e){let t=this.stmt("SELECT * FROM ui_templates WHERE id = ?").get(e);return t?this.mapUITemplate(t):null}async getUITemplates(e){return this.stmt("SELECT * FROM ui_templates WHERE tenant_id = ? ORDER BY name ASC").all(e).map(n=>this.mapUITemplate(n))}async copyUITemplate(e,t){let n=await this.getUITemplate(e);if(!n)throw new Error("UI template not found");let{tenantId:s,mainBanner:r,gpcBanner:o,preferenceModal:a}=n;return this.createUITemplate({tenantId:s,name:t,mainBanner:r,gpcBanner:o,preferenceModal:a})}mapProfileSummary(e){return{id:e.id,name:e.name,defaultLocale:e.default_locale,complianceGroup:e.compliance_group??null,customComplianceGroup:e.custom_compliance_group??null,isActive:e.is_active===1,consentTemplateName:e.consent_template_name,uiTemplateName:e.ui_template_name,createdAt:e.created_at,updatedAt:e.updated_at}}PROFILE_SUMMARY_SQL=`
46
43
  SELECT
47
44
  p.id, p.name, p.default_locale, p.created_at, p.updated_at,
48
45
  json_extract(p.profile_json, '$.complianceGroup') AS compliance_group,
@@ -55,11 +52,11 @@ ${l.map(d=>` (${Q(c)},${Q(d)})`).join(`,
55
52
  FROM profiles p
56
53
  LEFT JOIN consent_templates ct ON ct.id = json_extract(p.profile_json, '$.consentTemplateId')
57
54
  LEFT JOIN ui_templates ut ON ut.id = json_extract(p.profile_json, '$.uiTemplateId')
58
- `;async listProfilesSummary(e){return this.stmt(`${this.PROFILE_SUMMARY_SQL} WHERE p.tenant_id = ? ORDER BY p.created_at ASC`).all(e).map(n=>this.mapProfileSummary(n))}async findProfilesUsingConsentTemplate(e){return this.stmt(`${this.PROFILE_SUMMARY_SQL} WHERE json_extract(p.profile_json, '$.consentTemplateId') = ? ORDER BY p.name ASC`).all(e).map(n=>this.mapProfileSummary(n))}async findProfilesUsingUITemplate(e){return this.stmt(`${this.PROFILE_SUMMARY_SQL} WHERE json_extract(p.profile_json, '$.uiTemplateId') = ? ORDER BY p.name ASC`).all(e).map(n=>this.mapProfileSummary(n))}async getOptInStats(e,t){let n=["cr.tenant_id = ?"],s=[e];t.profileId&&(n.push("cr.profile_id = ?"),s.push(t.profileId)),t.from&&(n.push("cr.created_at >= ?"),s.push(t.from)),t.to&&(n.push("cr.created_at <= ?"),s.push(t.to)),t.locale&&(n.push("cr.locale = ?"),s.push(t.locale));let r=n.join(" AND "),o=this.stmt(`SELECT cr.locale, cr.consent_json FROM consent_records cr WHERE ${r}`).all(...s),a=0,c=0,l=0,d=0,u={};for(let _ of o){let h=Object.values(JSON.parse(_.consent_json)),w=h.length>0?h.filter(T=>T==="granted").length/h.length:0,R=h.length>0?h.filter(T=>T==="denied").length/h.length:0;a++;let I=_.locale;u[I]||(u[I]={total:0,granted:0,denied:0,managed:0}),u[I].total++,w>=.9?(c++,u[I].granted++):R>=.9?(l++,u[I].denied++):(d++,u[I].managed++)}let p=this.stmt(`SELECT date(cr.created_at) as d, cr.consent_json FROM consent_records cr WHERE ${r} ORDER BY d ASC`).all(...s),g=new Map;for(let _ of p){let h=g.get(_.d)??{total:0,granted:0,denied:0,managed:0},w=Object.values(JSON.parse(_.consent_json)),R=w.length>0?w.filter(T=>T==="granted").length/w.length:0,I=w.length>0?w.filter(T=>T==="denied").length/w.length:0;h.total++,R>=.9?h.granted++:I>=.9?h.denied++:h.managed++,g.set(_.d,h)}let E=Array.from(g.entries()).map(([_,h])=>({date:_,...h}));return{total:a,granted:c,denied:l,managed:d,grantedPct:a>0?Math.round(c/a*1e3)/10:0,deniedPct:a>0?Math.round(l/a*1e3)/10:0,managedPct:a>0?Math.round(d/a*1e3)/10:0,byLocale:u,byDate:E}}};var dn=500,rt=class i{constructor(e){this.config=e}config;db;cache=new Map;stmt(e){let t=this.cache.get(e);return t||(t=this.db.prepare(e),this.cache.set(e,t)),t}async connect(){let e=await import("node-sqlite3-wasm").catch(()=>{throw new Error("driver 'node-sqlite3-wasm' requires: npm install node-sqlite3-wasm")}),{dbPath:t}=ne(this.config.path??"./consenti-data","node-sqlite3-wasm");this.db=new e.default.Database(t),this.db.exec("PRAGMA journal_mode=WAL"),this.db.exec("PRAGMA foreign_keys=ON"),this.db.exec("PRAGMA synchronous=NORMAL"),await this.migrate()}async disconnect(){for(let e of this.cache.values())e.finalize();this.cache.clear(),this.db.close()}async migrate(){(this.stmt("PRAGMA user_version").get()?.user_version??0)<1&&(this.db.exec(ye),this.db.exec(we),this.db.exec("PRAGMA user_version = 1")),this.db.exec(`
55
+ `;async listProfilesSummary(e){return this.stmt(`${this.PROFILE_SUMMARY_SQL} WHERE p.tenant_id = ? ORDER BY p.created_at ASC`).all(e).map(n=>this.mapProfileSummary(n))}async findProfilesUsingConsentTemplate(e){return this.stmt(`${this.PROFILE_SUMMARY_SQL} WHERE json_extract(p.profile_json, '$.consentTemplateId') = ? ORDER BY p.name ASC`).all(e).map(n=>this.mapProfileSummary(n))}async findProfilesUsingUITemplate(e){return this.stmt(`${this.PROFILE_SUMMARY_SQL} WHERE json_extract(p.profile_json, '$.uiTemplateId') = ? ORDER BY p.name ASC`).all(e).map(n=>this.mapProfileSummary(n))}async getOptInStats(e,t){let n=["cr.tenant_id = ?"],s=[e];t.profileId&&(n.push("cr.profile_id = ?"),s.push(t.profileId)),t.from&&(n.push("cr.created_at >= ?"),s.push(t.from)),t.to&&(n.push("cr.created_at <= ?"),s.push(t.to)),t.locale&&(n.push("cr.locale = ?"),s.push(t.locale));let r=n.join(" AND "),o=this.stmt(`SELECT cr.locale, cr.consent_json FROM consent_records cr WHERE ${r}`).all(...s),a=0,c=0,l=0,d=0,u={};for(let f of o){let y=Object.values(JSON.parse(f.consent_json)),w=y.length>0?y.filter(T=>T==="granted").length/y.length:0,R=y.length>0?y.filter(T=>T==="denied").length/y.length:0;a++;let I=f.locale;u[I]||(u[I]={total:0,granted:0,denied:0,managed:0}),u[I].total++,w>=.9?(c++,u[I].granted++):R>=.9?(l++,u[I].denied++):(d++,u[I].managed++)}let p=this.stmt(`SELECT date(cr.created_at) as d, cr.consent_json FROM consent_records cr WHERE ${r} ORDER BY d ASC`).all(...s),g=new Map;for(let f of p){let y=g.get(f.d)??{total:0,granted:0,denied:0,managed:0},w=Object.values(JSON.parse(f.consent_json)),R=w.length>0?w.filter(T=>T==="granted").length/w.length:0,I=w.length>0?w.filter(T=>T==="denied").length/w.length:0;y.total++,R>=.9?y.granted++:I>=.9?y.denied++:y.managed++,g.set(f.d,y)}let E=Array.from(g.entries()).map(([f,y])=>({date:f,...y}));return{total:a,granted:c,denied:l,managed:d,grantedPct:a>0?Math.round(c/a*1e3)/10:0,deniedPct:a>0?Math.round(l/a*1e3)/10:0,managedPct:a>0?Math.round(d/a*1e3)/10:0,byLocale:u,byDate:E}}};var Rn=500,ut=class i{constructor(e){this.config=e}config;db;cache=new Map;dbPath="";stmt(e){let t=this.cache.get(e);return t||(t=this.db.prepare(e),this.cache.set(e,t)),t}async connect(){let e=await import("node-sqlite3-wasm").catch(()=>{throw new Error("driver 'node-sqlite3-wasm' requires: npm install node-sqlite3-wasm")}),{dbPath:t}=ie(this.config.path??"./consenti-data","node-sqlite3-wasm");this.dbPath=t,this.db=new e.default.Database(t),this.db.exec("PRAGMA journal_mode=WAL"),this.db.exec("PRAGMA foreign_keys=ON"),this.db.exec("PRAGMA synchronous=NORMAL"),await this.migrate()}async disconnect(){for(let e of this.cache.values())e.finalize();this.cache.clear(),this.db.close()}async migrate(){let e=this.db.prepare("PRAGMA user_version"),t=0;try{t=e.get()?.user_version??0}finally{e.finalize()}t<1?(this.db.exec(Se),this.db.exec(Ce),this.db.exec(`PRAGMA user_version = ${ee}`)):t<2&&this.migrateToV2(),this.db.exec(`
59
56
  CREATE INDEX IF NOT EXISTS idx_profiles_tenant_compliance_active
60
57
  ON profiles (tenant_id, json_extract(profile_json,'$.complianceGroup'), json_extract(profile_json,'$.isActive'));
61
- `)}mapProfile(e){return{id:e.id,tenantId:e.tenant_id,name:e.name,defaultLocale:e.default_locale,version:e.version,profileJson:JSON.parse(e.profile_json),createdAt:e.created_at,updatedAt:e.updated_at}}async createProfile(e){let t=V(),n=new Date().toISOString();this.stmt("INSERT INTO profiles (id,tenant_id,name,default_locale,version,profile_json,created_at,updated_at) VALUES (?,?,?,?,?,?,?,?)").run([t,e.tenantId,e.name,e.defaultLocale,1,JSON.stringify(e.profileJson),n,n]);let s=await this.getProfile(t);if(!s)throw new Error("Failed to create profile");return s}async updateProfile(e,t){if(!await this.getProfile(e))throw new Error(`Profile ${e} not found`);let s=new Date().toISOString(),r=["updated_at = ?"],o=[s];t.name!=null&&(r.push("name = ?"),o.push(t.name)),t.defaultLocale!=null&&(r.push("default_locale = ?"),o.push(t.defaultLocale)),t.profileJson!=null&&(r.push("profile_json = ?"),o.push(JSON.stringify(t.profileJson))),t.version!=null&&(r.push("version = ?"),o.push(t.version)),o.push(e),this.stmt(`UPDATE profiles SET ${r.join(", ")} WHERE id = ?`).run(o);let a=await this.getProfile(e);if(!a)throw new Error("Failed to update profile");return a}async deleteProfile(e){this.stmt("DELETE FROM profiles WHERE id = ?").run(e)}async getProfile(e){let t=this.stmt("SELECT * FROM profiles WHERE id = ?").get(e);return t?this.mapProfile(t):null}async getProfiles(e){return this.stmt("SELECT * FROM profiles WHERE tenant_id = ? ORDER BY created_at ASC").all(e).map(n=>this.mapProfile(n))}async findActiveProfileByComplianceGroup(e,t){return(await this.getProfiles(e)).find(s=>(s.profileJson.complianceGroup===t||s.profileJson.customComplianceGroup===t)&&s.profileJson.isActive)??null}mapConsent(e){return{id:e.id,tenantId:e.tenant_id,visitorId:e.visitor_id,profileId:e.profile_id,locale:e.locale,consentJson:JSON.parse(e.consent_json),gpcDetected:e.gpc_detected===1,source:e.source,...e.age_verified!=null&&e.age_verified!==0?{ageVerified:!0}:{},...e.parental_consent_token!=null?{parentalConsentToken:e.parental_consent_token}:{},...e.tcf_string!=null?{tcfString:e.tcf_string}:{},...e.gpp_string!=null?{gppString:e.gpp_string}:{},...e.signature!=null?{signature:e.signature}:{},createdAt:e.created_at,updatedAt:e.updated_at}}static CONSENT_SUMMARY_COLS="id, tenant_id, visitor_id, profile_id, locale, gpc_detected, source, age_verified, created_at, updated_at";mapConsentSummary(e){return{id:e.id,tenantId:e.tenant_id,visitorId:e.visitor_id,profileId:e.profile_id,locale:e.locale,gpcDetected:e.gpc_detected===1,source:e.source,...e.age_verified!=null&&e.age_verified!==0?{ageVerified:!0}:{},createdAt:e.created_at,updatedAt:e.updated_at}}async createConsent(e){let t=W(),n=new Date().toISOString();this.stmt("INSERT INTO consent_records (id,tenant_id,visitor_id,profile_id,locale,consent_json,gpc_detected,source,age_verified,parental_consent_token,tcf_string,gpp_string,signature,created_at,updated_at) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)").run([t,e.tenantId,e.visitorId,e.profileId,e.locale,JSON.stringify(e.consentJson),e.gpcDetected?1:0,e.source,e.ageVerified?1:0,e.parentalConsentToken??null,e.tcfString??null,e.gppString??null,e.signature??null,n,n]);let s=A();this.stmt("INSERT INTO consent_history (id,tenant_id,consent_record_id,visitor_id,old_json,new_json,action,created_at) VALUES (?,?,?,?,?,?,?,?)").run([s,e.tenantId,t,e.visitorId,null,JSON.stringify(e.consentJson),"created",n]);let r=await this.getConsent(e.visitorId);if(!r)throw new Error("Failed to create consent");return r}async updateConsent(e,t){let n=await this.getConsent(e);if(!n)throw new Error(`Consent for visitor ${e} not found`);let s=new Date().toISOString(),r=["updated_at = ?","consent_json = ?"],o=[s,JSON.stringify(t.consentJson)];t.locale!=null&&(r.push("locale = ?"),o.push(t.locale)),t.gpcDetected!=null&&(r.push("gpc_detected = ?"),o.push(t.gpcDetected?1:0)),t.signature!=null&&(r.push("signature = ?"),o.push(t.signature)),o.push(e),this.stmt(`UPDATE consent_records SET ${r.join(", ")} WHERE visitor_id = ?`).run(o);let a=A();this.stmt("INSERT INTO consent_history (id,tenant_id,consent_record_id,visitor_id,old_json,new_json,action,created_at) VALUES (?,?,?,?,?,?,?,?)").run([a,n.tenantId,n.id,e,JSON.stringify(n.consentJson),JSON.stringify(t.consentJson),"updated",s]);let c=await this.getConsent(e);if(!c)throw new Error("Failed to update consent");return c}async deleteConsent(e){this.stmt("DELETE FROM consent_history WHERE visitor_id = ?").run(e),this.stmt("DELETE FROM consent_records WHERE visitor_id = ?").run(e)}async getConsent(e){let t=this.stmt("SELECT * FROM consent_records WHERE visitor_id = ?").get(e);return t?this.mapConsent(t):null}async getConsents(e){let t=["tenant_id = ?"],n=[e.tenantId];if(e.profileId&&(t.push("profile_id = ?"),n.push(e.profileId)),e.from&&(t.push("created_at >= ?"),n.push(e.from)),e.to&&(t.push("created_at <= ?"),n.push(e.to)),e.q){t.push("(visitor_id LIKE ? OR profile_id LIKE ? OR locale LIKE ? OR source LIKE ?) ESCAPE '\\'");let d=j(e.q);n.push(d,d,d,d)}let s=t.join(" AND "),r=e.page??1,o=Math.min(e.limit??50,dn),a=(r-1)*o,c=this.stmt(`SELECT ${i.CONSENT_SUMMARY_COLS} FROM consent_records WHERE ${s} ORDER BY created_at DESC LIMIT ? OFFSET ?`).all([...n,o,a]),l=this.stmt(`SELECT COUNT(*) AS total FROM consent_records WHERE ${s}`).get(n);return{items:c.map(d=>this.mapConsentSummary(d)),total:l?.total??0,page:r,limit:o}}async*streamConsents(e){let n=0;for(;;){let s=this.stmt("SELECT * FROM consent_records WHERE tenant_id = ? LIMIT ? OFFSET ?").all([e.tenantId,500,n]);if(s.length===0)break;for(let r of s)yield this.mapConsent(r);n+=500}}async getConsentHistory(e){return this.stmt("SELECT * FROM consent_history WHERE visitor_id = ? ORDER BY created_at ASC").all(e).map(n=>({id:n.id,tenantId:n.tenant_id,consentRecordId:n.consent_record_id,visitorId:n.visitor_id,oldJson:n.old_json!=null?JSON.parse(n.old_json):null,newJson:JSON.parse(n.new_json),action:n.action,createdAt:n.created_at}))}async createNoticeShown(e){let t=A(),n=new Date().toISOString();return this.stmt("INSERT INTO notice_shown (id,tenant_id,visitor_id,profile_id,locale,created_at) VALUES (?,?,?,?,?,?)").run([t,e.tenantId,e.visitorId,e.profileId,e.locale,n]),{id:t,tenantId:e.tenantId,visitorId:e.visitorId,profileId:e.profileId,locale:e.locale,createdAt:n}}async getNoticeShownForVisitor(e){return this.stmt("SELECT * FROM notice_shown WHERE visitor_id = ? ORDER BY created_at DESC").all(e).map(n=>({id:n.id,tenantId:n.tenant_id,visitorId:n.visitor_id,profileId:n.profile_id,locale:n.locale,createdAt:n.created_at}))}mapVisitor(e){return{id:e.id,tenantId:e.tenant_id,visitorId:e.visitor_id,firstSeen:e.first_seen,lastSeen:e.last_seen,...e.country!=null?{country:e.country}:{},...e.region!=null?{region:e.region}:{},...e.city!=null?{city:e.city}:{},...e.ip_hash!=null?{ipHash:e.ip_hash}:{},...e.user_agent_hash!=null?{userAgentHash:e.user_agent_hash}:{}}}async createVisitor(e){let t=H(),n=new Date().toISOString();this.stmt("INSERT INTO visitors (id,tenant_id,visitor_id,country,region,city,ip_hash,user_agent_hash,first_seen,last_seen) VALUES (?,?,?,?,?,?,?,?,?,?)").run([t,e.tenantId,e.visitorId,e.country??null,e.region??null,e.city??null,e.ipHash??null,e.userAgentHash??null,n,n]);let s=await this.getVisitor(e.visitorId);if(!s)throw new Error("Failed to create visitor");return s}async updateVisitor(e,t){let n=new Date().toISOString(),s=["last_seen = ?"],r=[t.lastSeen??n];t.country!=null&&(s.push("country = ?"),r.push(t.country)),t.region!=null&&(s.push("region = ?"),r.push(t.region)),t.city!=null&&(s.push("city = ?"),r.push(t.city)),r.push(e),this.stmt(`UPDATE visitors SET ${s.join(", ")} WHERE visitor_id = ?`).run(r);let o=await this.getVisitor(e);if(!o)throw new Error(`Visitor ${e} not found`);return o}async deleteVisitor(e){this.stmt("DELETE FROM visitors WHERE visitor_id = ?").run(e)}async getVisitor(e){let t=this.stmt("SELECT * FROM visitors WHERE visitor_id = ?").get(e);return t?this.mapVisitor(t):null}async getVisitors(e){let t=["tenant_id = ?"],n=[e.tenantId];if(e.from&&(t.push("first_seen >= ?"),n.push(e.from)),e.to&&(t.push("first_seen <= ?"),n.push(e.to)),e.q){t.push("(visitor_id LIKE ? OR country LIKE ?) ESCAPE '\\'");let d=j(e.q);n.push(d,d)}let s=t.join(" AND "),r=e.page??1,o=Math.min(e.limit??50,dn),a=(r-1)*o,c=this.stmt(`SELECT * FROM visitors WHERE ${s} ORDER BY last_seen DESC LIMIT ? OFFSET ?`).all([...n,o,a]),l=this.stmt(`SELECT COUNT(*) AS total FROM visitors WHERE ${s}`).get(n);return{items:c.map(d=>this.mapVisitor(d)),total:l?.total??0,page:r,limit:o}}mapUser(e){let t=e.allowed_tenants?JSON.parse(e.allowed_tenants):[];return{id:e.id,tenantId:e.tenant_id,name:e.name,email:e.email,passwordHash:e.password_hash,isActive:e.is_active===1,totpEnabled:e.totp_enabled===1,...e.totp_secret!=null?{totpSecret:e.totp_secret}:{},allowedTenants:t,createdAt:e.created_at,updatedAt:e.updated_at}}async createUser(e){let t=A(),n=new Date().toISOString(),s=e.allowedTenants?.length?JSON.stringify(e.allowedTenants):null;this.stmt("INSERT INTO users (id,tenant_id,name,email,password_hash,allowed_tenants,created_at,updated_at) VALUES (?,?,?,?,?,?,?,?)").run([t,e.tenantId,e.name,e.email,e.passwordHash,s,n,n]);let r=await this.getUserByEmail(e.email);if(!r)throw new Error("Failed to create user");return r}async updateUser(e,t){let n=new Date().toISOString(),s=["updated_at = ?"],r=[n];t.name!=null&&(s.push("name = ?"),r.push(t.name)),t.email!=null&&(s.push("email = ?"),r.push(t.email)),t.passwordHash!=null&&(s.push("password_hash = ?"),r.push(t.passwordHash)),t.isActive!=null&&(s.push("is_active = ?"),r.push(t.isActive?1:0)),"totpSecret"in t&&(s.push("totp_secret = ?"),r.push(t.totpSecret??null)),t.totpEnabled!=null&&(s.push("totp_enabled = ?"),r.push(t.totpEnabled?1:0)),"allowedTenants"in t&&(s.push("allowed_tenants = ?"),r.push(t.allowedTenants?.length?JSON.stringify(t.allowedTenants):null)),r.push(e),this.stmt(`UPDATE users SET ${s.join(", ")} WHERE id = ?`).run(r);let o=this.stmt("SELECT * FROM users WHERE id = ?").get(e);if(!o)throw new Error(`User ${e} not found`);return this.mapUser(o)}async deleteUser(e){this.stmt("DELETE FROM user_roles WHERE user_id = ?").run(e),this.stmt("DELETE FROM users WHERE id = ?").run(e)}async getUserById(e){let t=this.stmt("SELECT * FROM users WHERE id = ?").get(e);return t?this.mapUser(t):null}async getUserByEmail(e){let t=this.stmt("SELECT * FROM users WHERE email = ?").get(e);return t?this.mapUser(t):null}async getUsers(e){return this.stmt("SELECT * FROM users WHERE tenant_id = ? ORDER BY created_at ASC").all(e).map(n=>this.mapUser(n))}async countUsers(e){return(e?this.stmt("SELECT COUNT(*) as n FROM users WHERE tenant_id = ?").get(e):this.stmt("SELECT COUNT(*) as n FROM users").get())?.n??0}async getRoles(e){return this.stmt("SELECT * FROM roles WHERE tenant_id = ?").all(e).map(n=>({id:n.id,tenantId:n.tenant_id,name:n.name,...n.description!=null?{description:n.description}:{}}))}async createRole(e){let t=A();this.stmt("INSERT INTO roles (id,tenant_id,name,description) VALUES (?,?,?,?)").run([t,e.tenantId,e.name,e.description??null]);let n=this.stmt("SELECT * FROM roles WHERE id = ?").get(t);if(!n)throw new Error("Failed to create role");return{id:n.id,tenantId:n.tenant_id,name:n.name,...n.description!=null?{description:n.description}:{}}}async updateRole(e,t){let n=[],s=[];t.name!=null&&(n.push("name = ?"),s.push(t.name)),t.description!=null&&(n.push("description = ?"),s.push(t.description)),n.length>0&&(s.push(e),this.stmt(`UPDATE roles SET ${n.join(", ")} WHERE id = ?`).run(s));let r=this.stmt("SELECT * FROM roles WHERE id = ?").get(e);if(!r)throw new Error(`Role ${e} not found`);return{id:r.id,tenantId:r.tenant_id,name:r.name,...r.description!=null?{description:r.description}:{}}}async deleteRole(e){this.stmt("DELETE FROM role_permissions WHERE role_id = ?").run(e),this.stmt("DELETE FROM user_roles WHERE role_id = ?").run(e),this.stmt("DELETE FROM roles WHERE id = ?").run(e)}async getAllPermissions(){return this.stmt("SELECT * FROM permissions ORDER BY name ASC").all().map(t=>({id:t.id,name:t.name,...t.description!=null?{description:t.description}:{}}))}async assignPermissionToRole(e,t){this.stmt("INSERT OR IGNORE INTO role_permissions (role_id,permission_id) VALUES (?,?)").run([e,t])}async revokePermissionFromRole(e,t){this.stmt("DELETE FROM role_permissions WHERE role_id = ? AND permission_id = ?").run([e,t])}async getPermissionsForRole(e){return this.stmt("SELECT p.* FROM permissions p JOIN role_permissions rp ON rp.permission_id = p.id WHERE rp.role_id = ?").all(e).map(n=>({id:n.id,name:n.name,...n.description!=null?{description:n.description}:{}}))}async getUserRoles(e,t){return(t?this.stmt("SELECT r.* FROM roles r JOIN user_roles ur ON ur.role_id = r.id WHERE ur.user_id = ? AND ur.tenant_id = ?").all([e,t]):this.stmt("SELECT r.* FROM roles r JOIN user_roles ur ON ur.role_id = r.id WHERE ur.user_id = ?").all(e)).map(s=>({id:s.id,tenantId:s.tenant_id,name:s.name,...s.description!=null?{description:s.description}:{}}))}async assignRole(e,t,n="default"){this.stmt("INSERT OR IGNORE INTO user_roles (user_id, role_id, tenant_id) VALUES (?,?,?)").run([e,t,n])}async revokeRole(e,t,n){n?this.stmt("DELETE FROM user_roles WHERE user_id = ? AND role_id = ? AND tenant_id = ?").run([e,t,n]):this.stmt("DELETE FROM user_roles WHERE user_id = ? AND role_id = ?").run([e,t])}async createLog(e){let t=A(),n=new Date().toISOString();this.stmt("INSERT INTO audit_logs (id,tenant_id,user_id,action,resource_type,resource_id,old_data,new_data,created_at) VALUES (?,?,?,?,?,?,?,?,?)").run([t,e.tenantId,e.userId??null,e.action,e.resourceType,e.resourceId??null,e.oldData!=null?JSON.stringify(e.oldData):null,e.newData!=null?JSON.stringify(e.newData):null,n])}async getLogs(e){let t=["tenant_id = ?"],n=[e.tenantId];if(e.action&&(t.push("action = ?"),n.push(e.action)),e.resourceType&&(t.push("resource_type = ?"),n.push(e.resourceType)),e.from&&(t.push("created_at >= ?"),n.push(e.from)),e.to&&(t.push("created_at <= ?"),n.push(e.to)),e.q){t.push("(action LIKE ? OR resource_type LIKE ? OR resource_id LIKE ? OR user_id LIKE ?) ESCAPE '\\'");let d=j(e.q);n.push(d,d,d,d)}let s=t.join(" AND "),r=e.page??1,o=Math.min(e.limit??50,dn),a=(r-1)*o,c=this.stmt(`SELECT ${i.AUDIT_SUMMARY_COLS} FROM audit_logs WHERE ${s} ORDER BY created_at DESC LIMIT ? OFFSET ?`).all([...n,o,a]),l=this.stmt(`SELECT COUNT(*) AS total FROM audit_logs WHERE ${s}`).get(n);return{items:c.map(d=>this.mapAuditSummary(d)),total:l?.total??0,page:r,limit:o}}async getAuditLogById(e){let t=this.stmt("SELECT * FROM audit_logs WHERE id = ?").get(e);return t?this.mapAuditLog(t):null}static AUDIT_SUMMARY_COLS="id, tenant_id, user_id, action, resource_type, resource_id, created_at";mapAuditSummary(e){return{id:e.id,tenantId:e.tenant_id,action:e.action,resourceType:e.resource_type,...e.user_id!=null?{userId:e.user_id}:{},...e.resource_id!=null?{resourceId:e.resource_id}:{},createdAt:e.created_at}}mapAuditLog(e){return{id:e.id,tenantId:e.tenant_id,action:e.action,resourceType:e.resource_type,createdAt:e.created_at,...e.user_id!=null?{userId:e.user_id}:{},...e.resource_id!=null?{resourceId:e.resource_id}:{},...e.old_data!=null?{oldData:JSON.parse(e.old_data)}:{},...e.new_data!=null?{newData:JSON.parse(e.new_data)}:{}}}async*streamAuditLogs(e){let t=["tenant_id = ?"],n=[e.tenantId];e.action&&(t.push("action = ?"),n.push(e.action)),e.from&&(t.push("created_at >= ?"),n.push(e.from)),e.to&&(t.push("created_at <= ?"),n.push(e.to));let s=500,r=0;for(;;){let o=this.stmt(`SELECT * FROM audit_logs WHERE ${t.join(" AND ")} ORDER BY created_at DESC LIMIT ? OFFSET ?`).all([...n,s,r]);if(o.length===0)break;for(let a of o)yield this.mapAuditLog(a);r+=s}}async getOverviewStats(e){let t=this.stmt("SELECT COUNT(*) as n FROM consent_records WHERE tenant_id = ?").get(e),n=this.stmt("SELECT COUNT(*) as n FROM consent_records WHERE tenant_id = ? AND gpc_detected = 1").get(e),s=this.stmt("SELECT COUNT(*) as n FROM visitors WHERE tenant_id = ?").get(e),r=t?.n??0,o=this.stmt("SELECT consent_json FROM consent_records WHERE tenant_id = ? ORDER BY created_at DESC LIMIT 500").all(e),a=0,c=0;for(let d of o){let u=Object.values(JSON.parse(d.consent_json)),p=u.length>0?u.filter(g=>g==="granted").length/u.length:0;p>=.8?a++:p<=.2&&c++}let l=o.length||1;return{totalConsents:r,gpcDetectedCount:n?.n??0,totalVisitors:s?.n??0,acceptedPct:Math.round(a/l*100),rejectedPct:Math.round(c/l*100)}}async getCategoryStats(e){let t=this.stmt("SELECT consent_json FROM consent_records WHERE tenant_id = ? LIMIT 5000").all(e),n={};for(let s of t){let r=JSON.parse(s.consent_json);for(let[o,a]of Object.entries(r))n[o]||(n[o]={granted:0,denied:0,objected:0}),a==="granted"?n[o].granted++:a==="denied"?n[o].denied++:a==="objected"&&n[o].objected++}return n}async getTimeline(e,t=30){return this.stmt("SELECT date(created_at) as date, COUNT(*) as count FROM consent_records WHERE tenant_id = ? AND created_at >= date('now', ?) GROUP BY date(created_at) ORDER BY date ASC").all([e,`-${t} days`]).map(s=>({date:s.date,count:s.count}))}async getCountries(e){return this.stmt("SELECT v.country, COUNT(*) as count FROM consent_records cr JOIN visitors v ON v.visitor_id = cr.visitor_id WHERE cr.tenant_id = ? AND v.country IS NOT NULL GROUP BY v.country ORDER BY count DESC LIMIT 50").all(e).map(n=>({country:n.country,count:n.count}))}async getGpcStats(e){let t=this.stmt("SELECT COUNT(*) as n FROM consent_records WHERE tenant_id = ?").get(e),n=this.stmt("SELECT COUNT(*) as n FROM consent_records WHERE tenant_id = ? AND gpc_detected = 1").get(e),s=t?.n??0,r=n?.n??0;return{detected:r,total:s,rate:s>0?Math.round(r/s*100):0}}async getTenants(){return this.stmt("SELECT * FROM tenants ORDER BY name ASC").all().map(t=>({id:t.id,name:t.name,slug:t.slug,createdAt:t.created_at,updatedAt:t.updated_at}))}async createTenant(e){let t=A(),n=new Date().toISOString();this.stmt("INSERT INTO tenants (id,name,slug,created_at,updated_at) VALUES (?,?,?,?,?)").run([t,e.name,e.slug,n,n]);let s=this.stmt("SELECT * FROM tenants WHERE id = ?").get(t);if(!s)throw new Error("Failed to create tenant");return{id:s.id,name:s.name,slug:s.slug,createdAt:s.created_at,updatedAt:s.updated_at}}async updateTenant(e,t){let n=new Date().toISOString(),s=["updated_at = ?"],r=[n];t.name!=null&&(s.push("name = ?"),r.push(t.name)),t.slug!=null&&(s.push("slug = ?"),r.push(t.slug)),r.push(e),this.stmt(`UPDATE tenants SET ${s.join(", ")} WHERE id = ?`).run(r);let o=this.stmt("SELECT * FROM tenants WHERE id = ?").get(e);if(!o)throw new Error(`Tenant ${e} not found`);return{id:o.id,name:o.name,slug:o.slug,createdAt:o.created_at,updatedAt:o.updated_at}}async deleteTenant(e){this.stmt("DELETE FROM tenants WHERE id = ?").run(e)}async getSettings(e){let t=this.stmt("SELECT allowed_origins_json, admin_allowed_origins_json, setup_completed, profiles_seeded, tcf_confirmation_json, gpp_confirmation_json FROM tenant_settings WHERE tenant_id = ?").get(e);return t?{allowedOrigins:JSON.parse(t.allowed_origins_json),adminAllowedOrigins:JSON.parse(t.admin_allowed_origins_json),setupCompleted:!!t.setup_completed,profilesSeeded:!!t.profiles_seeded,tcfConfirmation:t.tcf_confirmation_json?JSON.parse(t.tcf_confirmation_json):void 0,gppConfirmation:t.gpp_confirmation_json?JSON.parse(t.gpp_confirmation_json):void 0}:{}}async updateSettings(e,t){let s={...await this.getSettings(e),...t};return this.stmt(`INSERT INTO tenant_settings (tenant_id, allowed_origins_json, admin_allowed_origins_json, setup_completed, profiles_seeded, tcf_confirmation_json, gpp_confirmation_json, updated_at) VALUES (?,?,?,?,?,?,?,?)
62
- ON CONFLICT(tenant_id) DO UPDATE SET allowed_origins_json = excluded.allowed_origins_json, admin_allowed_origins_json = excluded.admin_allowed_origins_json, setup_completed = excluded.setup_completed, profiles_seeded = excluded.profiles_seeded, tcf_confirmation_json = excluded.tcf_confirmation_json, gpp_confirmation_json = excluded.gpp_confirmation_json, updated_at = excluded.updated_at`).run([e,JSON.stringify(s.allowedOrigins??[]),JSON.stringify(s.adminAllowedOrigins??[]),s.setupCompleted?1:0,s.profilesSeeded?1:0,s.tcfConfirmation?JSON.stringify(s.tcfConfirmation):null,s.gppConfirmation?JSON.stringify(s.gppConfirmation):null,new Date().toISOString()]),this.getSettings(e)}async purgeExpiredConsents(e){let t=new Date(Date.now()-e*864e5).toISOString(),n=this.stmt("SELECT visitor_id FROM consent_records WHERE created_at < ?").all(t);for(let{visitor_id:s}of n)this.stmt("DELETE FROM consent_history WHERE visitor_id = ?").run(s),this.stmt("DELETE FROM consent_records WHERE visitor_id = ?").run(s);return n.length}mapApiKey(e){return{id:e.id,tenantId:e.tenant_id,keyHash:e.key_hash,name:e.name,isActive:e.is_active===1,...e.created_by!==null?{createdBy:e.created_by}:{},...e.expire_by!==null?{expireBy:e.expire_by}:{},createdAt:e.created_at,...e.updated_at!==null?{updatedAt:e.updated_at}:{}}}async createApiKey(e){let t=A(),n=new Date().toISOString();this.stmt("INSERT INTO api_keys (id,tenant_id,key_hash,name,created_by,expire_by,created_at,updated_at) VALUES (?,?,?,?,?,?,?,?)").run([t,e.tenantId,e.keyHash,e.name,e.createdBy??null,e.expireBy??null,n,n]);let s=this.stmt("SELECT * FROM api_keys WHERE id = ?").get(t);if(!s)throw new Error("Failed to create API key");return this.mapApiKey(s)}async getApiKeyByHash(e){let t=this.stmt("SELECT * FROM api_keys WHERE key_hash = ? AND is_active = 1").get(e);return t?this.mapApiKey(t):null}async revokeApiKey(e){this.stmt("UPDATE api_keys SET is_active = 0, updated_at = ? WHERE id = ?").run([new Date().toISOString(),e])}async reactivateApiKey(e){let t=new Date().toISOString();this.stmt("UPDATE api_keys SET is_active = 1, updated_at = ?, expire_by = CASE WHEN expire_by <= ? THEN NULL ELSE expire_by END WHERE id = ?").run([t,t,e])}async deleteApiKey(e){this.stmt("DELETE FROM api_keys WHERE id = ?").run([e])}async getApiKeys(e){return this.stmt("SELECT * FROM api_keys WHERE tenant_id = ? ORDER BY created_at DESC").all(e).map(n=>this.mapApiKey(n))}mapConsentTemplate(e){return{id:e.id,tenantId:e.tenant_id,name:e.name,cookies:JSON.parse(e.cookies_json),categories:JSON.parse(e.categories_json),createdAt:e.created_at,updatedAt:e.updated_at}}async createConsentTemplate(e){let t=J(),n=new Date().toISOString();return this.stmt("INSERT INTO consent_templates (id,tenant_id,name,cookies_json,categories_json,created_at,updated_at) VALUES (?,?,?,?,?,?,?)").run([t,e.tenantId,e.name,JSON.stringify(e.cookies),JSON.stringify(e.categories),n,n]),this.mapConsentTemplate(this.stmt("SELECT * FROM consent_templates WHERE id = ?").get(t))}async updateConsentTemplate(e,t){let n=new Date().toISOString();t.name!==void 0&&this.stmt("UPDATE consent_templates SET name = ?, updated_at = ? WHERE id = ?").run([t.name,n,e]),t.cookies!==void 0&&this.stmt("UPDATE consent_templates SET cookies_json = ?, updated_at = ? WHERE id = ?").run([JSON.stringify(t.cookies),n,e]),t.categories!==void 0&&this.stmt("UPDATE consent_templates SET categories_json = ?, updated_at = ? WHERE id = ?").run([JSON.stringify(t.categories),n,e]);let s=this.stmt("SELECT * FROM consent_templates WHERE id = ?").get(e);if(!s)throw new Error("Consent template not found");return this.mapConsentTemplate(s)}async deleteConsentTemplate(e){this.stmt("DELETE FROM consent_templates WHERE id = ?").run(e)}async getConsentTemplate(e){let t=this.stmt("SELECT * FROM consent_templates WHERE id = ?").get(e);return t?this.mapConsentTemplate(t):null}async getConsentTemplates(e){return this.stmt("SELECT * FROM consent_templates WHERE tenant_id = ? ORDER BY name ASC").all(e).map(n=>this.mapConsentTemplate(n))}async copyConsentTemplate(e,t){let n=await this.getConsentTemplate(e);if(!n)throw new Error("Consent template not found");return this.createConsentTemplate({tenantId:n.tenantId,name:t,cookies:n.cookies,categories:n.categories})}mapUITemplate(e){let t=JSON.parse(e.settings_json);return{id:e.id,tenantId:e.tenant_id,name:e.name,...t,createdAt:e.created_at,updatedAt:e.updated_at}}async createUITemplate(e){let t=q(),n=new Date().toISOString(),{tenantId:s,name:r,...o}=e;return this.stmt("INSERT INTO ui_templates (id,tenant_id,name,settings_json,created_at,updated_at) VALUES (?,?,?,?,?,?)").run([t,s,r,JSON.stringify(o),n,n]),this.mapUITemplate(this.stmt("SELECT * FROM ui_templates WHERE id = ?").get(t))}async updateUITemplate(e,t){let n=new Date().toISOString(),s=this.stmt("SELECT * FROM ui_templates WHERE id = ?").get(e);if(!s)throw new Error("UI template not found");t.name!==void 0&&this.stmt("UPDATE ui_templates SET name = ?, updated_at = ? WHERE id = ?").run([t.name,n,e]);let{name:r,...o}=t;if(Object.keys(o).length>0){let c={...JSON.parse(s.settings_json),...o};this.stmt("UPDATE ui_templates SET settings_json = ?, updated_at = ? WHERE id = ?").run([JSON.stringify(c),n,e])}return this.mapUITemplate(this.stmt("SELECT * FROM ui_templates WHERE id = ?").get(e))}async deleteUITemplate(e){this.stmt("DELETE FROM ui_templates WHERE id = ?").run(e)}async getUITemplate(e){let t=this.stmt("SELECT * FROM ui_templates WHERE id = ?").get(e);return t?this.mapUITemplate(t):null}async getUITemplates(e){return this.stmt("SELECT * FROM ui_templates WHERE tenant_id = ? ORDER BY name ASC").all(e).map(n=>this.mapUITemplate(n))}async copyUITemplate(e,t){let n=await this.getUITemplate(e);if(!n)throw new Error("UI template not found");let{tenantId:s,mainBanner:r,gpcBanner:o,preferenceModal:a}=n;return this.createUITemplate({tenantId:s,name:t,mainBanner:r,gpcBanner:o,preferenceModal:a})}mapProfileSummary(e){return{id:e.id,name:e.name,defaultLocale:e.default_locale,complianceGroup:e.compliance_group??null,customComplianceGroup:e.custom_compliance_group??null,isActive:e.is_active===1,consentTemplateName:e.consent_template_name,uiTemplateName:e.ui_template_name,createdAt:e.created_at,updatedAt:e.updated_at}}PROFILE_SUMMARY_SQL=`
58
+ `)}migrateToV2(){Ie({exec:e=>this.db.exec(e),all:e=>{let t=this.db.prepare(e);try{return t.all()}finally{t.finalize()}}},this.dbPath)}mapProfile(e){return{id:e.id,tenantId:e.tenant_id,name:e.name,defaultLocale:e.default_locale,version:e.version,profileJson:JSON.parse(e.profile_json),createdAt:e.created_at,updatedAt:e.updated_at}}async createProfile(e){let t=W(),n=new Date().toISOString();this.stmt("INSERT INTO profiles (id,tenant_id,name,default_locale,version,profile_json,created_at,updated_at) VALUES (?,?,?,?,?,?,?,?)").run([t,e.tenantId,e.name,e.defaultLocale,1,JSON.stringify(e.profileJson),n,n]);let s=await this.getProfile(t);if(!s)throw new Error("Failed to create profile");return s}async updateProfile(e,t){if(!await this.getProfile(e))throw new Error(`Profile ${e} not found`);let s=new Date().toISOString(),r=["updated_at = ?"],o=[s];t.name!=null&&(r.push("name = ?"),o.push(t.name)),t.defaultLocale!=null&&(r.push("default_locale = ?"),o.push(t.defaultLocale)),t.profileJson!=null&&(r.push("profile_json = ?"),o.push(JSON.stringify(t.profileJson))),t.version!=null&&(r.push("version = ?"),o.push(t.version)),o.push(e),this.stmt(`UPDATE profiles SET ${r.join(", ")} WHERE id = ?`).run(o);let a=await this.getProfile(e);if(!a)throw new Error("Failed to update profile");return a}async deleteProfile(e){this.stmt("DELETE FROM profiles WHERE id = ?").run(e)}async getProfile(e){let t=this.stmt("SELECT * FROM profiles WHERE id = ?").get(e);return t?this.mapProfile(t):null}async getProfiles(e){return this.stmt("SELECT * FROM profiles WHERE tenant_id = ? ORDER BY created_at ASC").all(e).map(n=>this.mapProfile(n))}async findActiveProfileByComplianceGroup(e,t){return(await this.getProfiles(e)).find(s=>(s.profileJson.complianceGroup===t||s.profileJson.customComplianceGroup===t)&&s.profileJson.isActive)??null}mapConsent(e){return{id:e.id,tenantId:e.tenant_id,visitorId:e.visitor_id,profileId:e.profile_id,...e.profile_version!=null?{profileVersion:e.profile_version}:{},locale:e.locale,consentJson:JSON.parse(e.consent_json),gpcDetected:e.gpc_detected===1,source:e.source,...e.age_verified!=null&&e.age_verified!==0?{ageVerified:!0}:{},...e.parental_consent_token!=null?{parentalConsentToken:e.parental_consent_token}:{},...e.tcf_string!=null?{tcfString:e.tcf_string}:{},...e.gpp_string!=null?{gppString:e.gpp_string}:{},...e.signature!=null?{signature:e.signature}:{},createdAt:e.created_at,updatedAt:e.updated_at}}static CONSENT_SUMMARY_COLS="id, tenant_id, visitor_id, profile_id, locale, gpc_detected, source, age_verified, created_at, updated_at";mapConsentSummary(e){return{id:e.id,tenantId:e.tenant_id,visitorId:e.visitor_id,profileId:e.profile_id,locale:e.locale,gpcDetected:e.gpc_detected===1,source:e.source,...e.age_verified!=null&&e.age_verified!==0?{ageVerified:!0}:{},createdAt:e.created_at,updatedAt:e.updated_at}}async createConsent(e){let t=J(),n=new Date().toISOString();this.stmt("INSERT INTO consent_records (id,tenant_id,visitor_id,profile_id,profile_version,locale,consent_json,gpc_detected,source,age_verified,parental_consent_token,tcf_string,gpp_string,signature,created_at,updated_at) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)").run([t,e.tenantId,e.visitorId,e.profileId,e.profileVersion??null,e.locale,JSON.stringify(e.consentJson),e.gpcDetected?1:0,e.source,e.ageVerified?1:0,e.parentalConsentToken??null,e.tcfString??null,e.gppString??null,e.signature??null,n,n]);let s=A();this.stmt("INSERT INTO consent_history (id,tenant_id,consent_record_id,visitor_id,profile_version,old_json,new_json,action,created_at) VALUES (?,?,?,?,?,?,?,?,?)").run([s,e.tenantId,t,e.visitorId,e.profileVersion??null,null,JSON.stringify(e.consentJson),"created",n]);let r=await this.getConsent(e.visitorId);if(!r)throw new Error("Failed to create consent");return r}async updateConsent(e,t){let n=await this.getConsent(e);if(!n)throw new Error(`Consent for visitor ${e} not found`);let s=new Date().toISOString(),r=["updated_at = ?","consent_json = ?"],o=[s,JSON.stringify(t.consentJson)];t.profileVersion!=null&&(r.push("profile_version = ?"),o.push(t.profileVersion)),t.locale!=null&&(r.push("locale = ?"),o.push(t.locale)),t.gpcDetected!=null&&(r.push("gpc_detected = ?"),o.push(t.gpcDetected?1:0)),t.signature!=null&&(r.push("signature = ?"),o.push(t.signature)),o.push(e),this.stmt(`UPDATE consent_records SET ${r.join(", ")} WHERE visitor_id = ?`).run(o);let a=A();this.stmt("INSERT INTO consent_history (id,tenant_id,consent_record_id,visitor_id,profile_version,old_json,new_json,action,created_at) VALUES (?,?,?,?,?,?,?,?,?)").run([a,n.tenantId,n.id,e,t.profileVersion??n.profileVersion??null,JSON.stringify(n.consentJson),JSON.stringify(t.consentJson),"updated",s]);let c=await this.getConsent(e);if(!c)throw new Error("Failed to update consent");return c}async deleteConsent(e){this.stmt("DELETE FROM consent_history WHERE visitor_id = ?").run(e),this.stmt("DELETE FROM consent_records WHERE visitor_id = ?").run(e)}async getConsent(e){let t=this.stmt("SELECT * FROM consent_records WHERE visitor_id = ?").get(e);return t?this.mapConsent(t):null}async getConsents(e){let t=["tenant_id = ?"],n=[e.tenantId];if(e.profileId&&(t.push("profile_id = ?"),n.push(e.profileId)),e.from&&(t.push("created_at >= ?"),n.push(e.from)),e.to&&(t.push("created_at <= ?"),n.push(e.to)),e.q){t.push("(visitor_id LIKE ? OR profile_id LIKE ? OR locale LIKE ? OR source LIKE ?) ESCAPE '\\'");let d=x(e.q);n.push(d,d,d,d)}let s=t.join(" AND "),r=e.page??1,o=Math.min(e.limit??50,Rn),a=(r-1)*o,c=this.stmt(`SELECT ${i.CONSENT_SUMMARY_COLS} FROM consent_records WHERE ${s} ORDER BY created_at DESC LIMIT ? OFFSET ?`).all([...n,o,a]),l=this.stmt(`SELECT COUNT(*) AS total FROM consent_records WHERE ${s}`).get(n);return{items:c.map(d=>this.mapConsentSummary(d)),total:l?.total??0,page:r,limit:o}}async*streamConsents(e){let n=0;for(;;){let s=this.stmt("SELECT * FROM consent_records WHERE tenant_id = ? LIMIT ? OFFSET ?").all([e.tenantId,500,n]);if(s.length===0)break;for(let r of s)yield this.mapConsent(r);n+=500}}async getConsentHistory(e){return this.stmt("SELECT * FROM consent_history WHERE visitor_id = ? ORDER BY created_at ASC").all(e).map(n=>({id:n.id,tenantId:n.tenant_id,consentRecordId:n.consent_record_id,visitorId:n.visitor_id,...n.profile_version!=null?{profileVersion:n.profile_version}:{},oldJson:n.old_json!=null?JSON.parse(n.old_json):null,newJson:JSON.parse(n.new_json),action:n.action,createdAt:n.created_at}))}async createNoticeShown(e){let t=A(),n=new Date().toISOString();return this.stmt("INSERT INTO notice_shown (id,tenant_id,visitor_id,profile_id,locale,created_at) VALUES (?,?,?,?,?,?)").run([t,e.tenantId,e.visitorId,e.profileId,e.locale,n]),{id:t,tenantId:e.tenantId,visitorId:e.visitorId,profileId:e.profileId,locale:e.locale,createdAt:n}}async getNoticeShownForVisitor(e){return this.stmt("SELECT * FROM notice_shown WHERE visitor_id = ? ORDER BY created_at DESC").all(e).map(n=>({id:n.id,tenantId:n.tenant_id,visitorId:n.visitor_id,profileId:n.profile_id,locale:n.locale,createdAt:n.created_at}))}mapVisitor(e){return{id:e.id,tenantId:e.tenant_id,visitorId:e.visitor_id,firstSeen:e.first_seen,lastSeen:e.last_seen,...e.country!=null?{country:e.country}:{},...e.region!=null?{region:e.region}:{},...e.city!=null?{city:e.city}:{},...e.ip_hash!=null?{ipHash:e.ip_hash}:{},...e.user_agent_hash!=null?{userAgentHash:e.user_agent_hash}:{}}}async createVisitor(e){let t=H(),n=new Date().toISOString();this.stmt("INSERT INTO visitors (id,tenant_id,visitor_id,country,region,city,ip_hash,user_agent_hash,first_seen,last_seen) VALUES (?,?,?,?,?,?,?,?,?,?)").run([t,e.tenantId,e.visitorId,e.country??null,e.region??null,e.city??null,e.ipHash??null,e.userAgentHash??null,n,n]);let s=await this.getVisitor(e.visitorId);if(!s)throw new Error("Failed to create visitor");return s}async updateVisitor(e,t){let n=new Date().toISOString(),s=["last_seen = ?"],r=[t.lastSeen??n];t.country!=null&&(s.push("country = ?"),r.push(t.country)),t.region!=null&&(s.push("region = ?"),r.push(t.region)),t.city!=null&&(s.push("city = ?"),r.push(t.city)),r.push(e),this.stmt(`UPDATE visitors SET ${s.join(", ")} WHERE visitor_id = ?`).run(r);let o=await this.getVisitor(e);if(!o)throw new Error(`Visitor ${e} not found`);return o}async deleteVisitor(e){this.stmt("DELETE FROM visitors WHERE visitor_id = ?").run(e)}async getVisitor(e){let t=this.stmt("SELECT * FROM visitors WHERE visitor_id = ?").get(e);return t?this.mapVisitor(t):null}async getVisitors(e){let t=["tenant_id = ?"],n=[e.tenantId];if(e.from&&(t.push("first_seen >= ?"),n.push(e.from)),e.to&&(t.push("first_seen <= ?"),n.push(e.to)),e.q){t.push("(visitor_id LIKE ? OR country LIKE ?) ESCAPE '\\'");let d=x(e.q);n.push(d,d)}let s=t.join(" AND "),r=e.page??1,o=Math.min(e.limit??50,Rn),a=(r-1)*o,c=this.stmt(`SELECT * FROM visitors WHERE ${s} ORDER BY last_seen DESC LIMIT ? OFFSET ?`).all([...n,o,a]),l=this.stmt(`SELECT COUNT(*) AS total FROM visitors WHERE ${s}`).get(n);return{items:c.map(d=>this.mapVisitor(d)),total:l?.total??0,page:r,limit:o}}mapUser(e){let t=e.allowed_tenants?JSON.parse(e.allowed_tenants):[];return{id:e.id,tenantId:e.tenant_id,name:e.name,email:e.email,passwordHash:e.password_hash,isActive:e.is_active===1,totpEnabled:e.totp_enabled===1,...e.totp_secret!=null?{totpSecret:e.totp_secret}:{},allowedTenants:t,createdAt:e.created_at,updatedAt:e.updated_at}}async createUser(e){let t=A(),n=new Date().toISOString(),s=e.allowedTenants?.length?JSON.stringify(e.allowedTenants):null;this.stmt("INSERT INTO users (id,tenant_id,name,email,password_hash,allowed_tenants,created_at,updated_at) VALUES (?,?,?,?,?,?,?,?)").run([t,e.tenantId,e.name,e.email,e.passwordHash,s,n,n]);let r=await this.getUserByEmail(e.email);if(!r)throw new Error("Failed to create user");return r}async updateUser(e,t){let n=new Date().toISOString(),s=["updated_at = ?"],r=[n];t.name!=null&&(s.push("name = ?"),r.push(t.name)),t.email!=null&&(s.push("email = ?"),r.push(t.email)),t.passwordHash!=null&&(s.push("password_hash = ?"),r.push(t.passwordHash)),t.isActive!=null&&(s.push("is_active = ?"),r.push(t.isActive?1:0)),"totpSecret"in t&&(s.push("totp_secret = ?"),r.push(t.totpSecret??null)),t.totpEnabled!=null&&(s.push("totp_enabled = ?"),r.push(t.totpEnabled?1:0)),"allowedTenants"in t&&(s.push("allowed_tenants = ?"),r.push(t.allowedTenants?.length?JSON.stringify(t.allowedTenants):null)),r.push(e),this.stmt(`UPDATE users SET ${s.join(", ")} WHERE id = ?`).run(r);let o=this.stmt("SELECT * FROM users WHERE id = ?").get(e);if(!o)throw new Error(`User ${e} not found`);return this.mapUser(o)}async deleteUser(e){this.stmt("DELETE FROM user_roles WHERE user_id = ?").run(e),this.stmt("DELETE FROM users WHERE id = ?").run(e)}async getUserById(e){let t=this.stmt("SELECT * FROM users WHERE id = ?").get(e);return t?this.mapUser(t):null}async getUserByEmail(e){let t=this.stmt("SELECT * FROM users WHERE email = ?").get(e);return t?this.mapUser(t):null}async getUsers(e){return this.stmt("SELECT * FROM users WHERE tenant_id = ? ORDER BY created_at ASC").all(e).map(n=>this.mapUser(n))}async countUsers(e){return(e?this.stmt("SELECT COUNT(*) as n FROM users WHERE tenant_id = ?").get(e):this.stmt("SELECT COUNT(*) as n FROM users").get())?.n??0}async getRoles(e){return this.stmt("SELECT * FROM roles WHERE tenant_id = ?").all(e).map(n=>({id:n.id,tenantId:n.tenant_id,name:n.name,...n.description!=null?{description:n.description}:{}}))}async createRole(e){let t=A();this.stmt("INSERT INTO roles (id,tenant_id,name,description) VALUES (?,?,?,?)").run([t,e.tenantId,e.name,e.description??null]);let n=this.stmt("SELECT * FROM roles WHERE id = ?").get(t);if(!n)throw new Error("Failed to create role");return{id:n.id,tenantId:n.tenant_id,name:n.name,...n.description!=null?{description:n.description}:{}}}async updateRole(e,t){let n=[],s=[];t.name!=null&&(n.push("name = ?"),s.push(t.name)),t.description!=null&&(n.push("description = ?"),s.push(t.description)),n.length>0&&(s.push(e),this.stmt(`UPDATE roles SET ${n.join(", ")} WHERE id = ?`).run(s));let r=this.stmt("SELECT * FROM roles WHERE id = ?").get(e);if(!r)throw new Error(`Role ${e} not found`);return{id:r.id,tenantId:r.tenant_id,name:r.name,...r.description!=null?{description:r.description}:{}}}async deleteRole(e){this.stmt("DELETE FROM role_permissions WHERE role_id = ?").run(e),this.stmt("DELETE FROM user_roles WHERE role_id = ?").run(e),this.stmt("DELETE FROM roles WHERE id = ?").run(e)}async getAllPermissions(){return this.stmt("SELECT * FROM permissions ORDER BY name ASC").all().map(t=>({id:t.id,name:t.name,...t.description!=null?{description:t.description}:{}}))}async assignPermissionToRole(e,t){this.stmt("INSERT OR IGNORE INTO role_permissions (role_id,permission_id) VALUES (?,?)").run([e,t])}async revokePermissionFromRole(e,t){this.stmt("DELETE FROM role_permissions WHERE role_id = ? AND permission_id = ?").run([e,t])}async getPermissionsForRole(e){return this.stmt("SELECT p.* FROM permissions p JOIN role_permissions rp ON rp.permission_id = p.id WHERE rp.role_id = ?").all(e).map(n=>({id:n.id,name:n.name,...n.description!=null?{description:n.description}:{}}))}async getUserRoles(e,t){return(t?this.stmt("SELECT r.* FROM roles r JOIN user_roles ur ON ur.role_id = r.id WHERE ur.user_id = ? AND ur.tenant_id = ?").all([e,t]):this.stmt("SELECT r.* FROM roles r JOIN user_roles ur ON ur.role_id = r.id WHERE ur.user_id = ?").all(e)).map(s=>({id:s.id,tenantId:s.tenant_id,name:s.name,...s.description!=null?{description:s.description}:{}}))}async assignRole(e,t,n="default"){this.stmt("INSERT OR IGNORE INTO user_roles (user_id, role_id, tenant_id) VALUES (?,?,?)").run([e,t,n])}async revokeRole(e,t,n){n?this.stmt("DELETE FROM user_roles WHERE user_id = ? AND role_id = ? AND tenant_id = ?").run([e,t,n]):this.stmt("DELETE FROM user_roles WHERE user_id = ? AND role_id = ?").run([e,t])}async createLog(e){let t=A(),n=new Date().toISOString();this.stmt("INSERT INTO audit_logs (id,tenant_id,user_id,action,resource_type,resource_id,old_data,new_data,created_at) VALUES (?,?,?,?,?,?,?,?,?)").run([t,e.tenantId,e.userId??null,e.action,e.resourceType,e.resourceId??null,e.oldData!=null?JSON.stringify(e.oldData):null,e.newData!=null?JSON.stringify(e.newData):null,n])}async getLogs(e){let t=["tenant_id = ?"],n=[e.tenantId];if(e.action&&(t.push("action = ?"),n.push(e.action)),e.resourceType&&(t.push("resource_type = ?"),n.push(e.resourceType)),e.from&&(t.push("created_at >= ?"),n.push(e.from)),e.to&&(t.push("created_at <= ?"),n.push(e.to)),e.q){t.push("(action LIKE ? OR resource_type LIKE ? OR resource_id LIKE ? OR user_id LIKE ?) ESCAPE '\\'");let d=x(e.q);n.push(d,d,d,d)}let s=t.join(" AND "),r=e.page??1,o=Math.min(e.limit??50,Rn),a=(r-1)*o,c=this.stmt(`SELECT ${i.AUDIT_SUMMARY_COLS} FROM audit_logs WHERE ${s} ORDER BY created_at DESC LIMIT ? OFFSET ?`).all([...n,o,a]),l=this.stmt(`SELECT COUNT(*) AS total FROM audit_logs WHERE ${s}`).get(n);return{items:c.map(d=>this.mapAuditSummary(d)),total:l?.total??0,page:r,limit:o}}async getAuditLogById(e){let t=this.stmt("SELECT * FROM audit_logs WHERE id = ?").get(e);return t?this.mapAuditLog(t):null}static AUDIT_SUMMARY_COLS="id, tenant_id, user_id, action, resource_type, resource_id, created_at";mapAuditSummary(e){return{id:e.id,tenantId:e.tenant_id,action:e.action,resourceType:e.resource_type,...e.user_id!=null?{userId:e.user_id}:{},...e.resource_id!=null?{resourceId:e.resource_id}:{},createdAt:e.created_at}}mapAuditLog(e){return{id:e.id,tenantId:e.tenant_id,action:e.action,resourceType:e.resource_type,createdAt:e.created_at,...e.user_id!=null?{userId:e.user_id}:{},...e.resource_id!=null?{resourceId:e.resource_id}:{},...e.old_data!=null?{oldData:JSON.parse(e.old_data)}:{},...e.new_data!=null?{newData:JSON.parse(e.new_data)}:{}}}async*streamAuditLogs(e){let t=["tenant_id = ?"],n=[e.tenantId];e.action&&(t.push("action = ?"),n.push(e.action)),e.from&&(t.push("created_at >= ?"),n.push(e.from)),e.to&&(t.push("created_at <= ?"),n.push(e.to));let s=500,r=0;for(;;){let o=this.stmt(`SELECT * FROM audit_logs WHERE ${t.join(" AND ")} ORDER BY created_at DESC LIMIT ? OFFSET ?`).all([...n,s,r]);if(o.length===0)break;for(let a of o)yield this.mapAuditLog(a);r+=s}}async getOverviewStats(e){let t=this.stmt("SELECT COUNT(*) as n FROM consent_records WHERE tenant_id = ?").get(e),n=this.stmt("SELECT COUNT(*) as n FROM consent_records WHERE tenant_id = ? AND gpc_detected = 1").get(e),s=this.stmt("SELECT COUNT(*) as n FROM visitors WHERE tenant_id = ?").get(e),r=t?.n??0,o=this.stmt("SELECT consent_json FROM consent_records WHERE tenant_id = ? ORDER BY created_at DESC LIMIT 500").all(e),a=0,c=0;for(let d of o){let u=Object.values(JSON.parse(d.consent_json)),p=u.length>0?u.filter(g=>g==="granted").length/u.length:0;p>=.8?a++:p<=.2&&c++}let l=o.length||1;return{totalConsents:r,gpcDetectedCount:n?.n??0,totalVisitors:s?.n??0,acceptedPct:Math.round(a/l*100),rejectedPct:Math.round(c/l*100)}}async getCategoryStats(e){let t=this.stmt("SELECT consent_json FROM consent_records WHERE tenant_id = ? LIMIT 5000").all(e),n={};for(let s of t){let r=JSON.parse(s.consent_json);for(let[o,a]of Object.entries(r))n[o]||(n[o]={granted:0,denied:0,objected:0}),a==="granted"?n[o].granted++:a==="denied"?n[o].denied++:a==="objected"&&n[o].objected++}return n}async getTimeline(e,t=30){return this.stmt("SELECT date(created_at) as date, COUNT(*) as count FROM consent_records WHERE tenant_id = ? AND created_at >= date('now', ?) GROUP BY date(created_at) ORDER BY date ASC").all([e,`-${t} days`]).map(s=>({date:s.date,count:s.count}))}async getCountries(e){return this.stmt("SELECT v.country, COUNT(*) as count FROM consent_records cr JOIN visitors v ON v.visitor_id = cr.visitor_id WHERE cr.tenant_id = ? AND v.country IS NOT NULL GROUP BY v.country ORDER BY count DESC LIMIT 50").all(e).map(n=>({country:n.country,count:n.count}))}async getGpcStats(e){let t=this.stmt("SELECT COUNT(*) as n FROM consent_records WHERE tenant_id = ?").get(e),n=this.stmt("SELECT COUNT(*) as n FROM consent_records WHERE tenant_id = ? AND gpc_detected = 1").get(e),s=t?.n??0,r=n?.n??0;return{detected:r,total:s,rate:s>0?Math.round(r/s*100):0}}async getTenants(){return this.stmt("SELECT * FROM tenants ORDER BY name ASC").all().map(t=>({id:t.id,name:t.name,slug:t.slug,createdAt:t.created_at,updatedAt:t.updated_at}))}async createTenant(e){let t=A(),n=new Date().toISOString();this.stmt("INSERT INTO tenants (id,name,slug,created_at,updated_at) VALUES (?,?,?,?,?)").run([t,e.name,e.slug,n,n]);let s=this.stmt("SELECT * FROM tenants WHERE id = ?").get(t);if(!s)throw new Error("Failed to create tenant");return{id:s.id,name:s.name,slug:s.slug,createdAt:s.created_at,updatedAt:s.updated_at}}async updateTenant(e,t){let n=new Date().toISOString(),s=["updated_at = ?"],r=[n];t.name!=null&&(s.push("name = ?"),r.push(t.name)),t.slug!=null&&(s.push("slug = ?"),r.push(t.slug)),r.push(e),this.stmt(`UPDATE tenants SET ${s.join(", ")} WHERE id = ?`).run(r);let o=this.stmt("SELECT * FROM tenants WHERE id = ?").get(e);if(!o)throw new Error(`Tenant ${e} not found`);return{id:o.id,name:o.name,slug:o.slug,createdAt:o.created_at,updatedAt:o.updated_at}}async deleteTenant(e){this.stmt("DELETE FROM tenants WHERE id = ?").run(e)}async getSettings(e){let t=this.stmt("SELECT allowed_origins_json, admin_allowed_origins_json, setup_completed, profiles_seeded, tcf_confirmation_json, gpp_confirmation_json FROM tenant_settings WHERE tenant_id = ?").get(e);return t?{allowedOrigins:JSON.parse(t.allowed_origins_json),adminAllowedOrigins:JSON.parse(t.admin_allowed_origins_json),setupCompleted:!!t.setup_completed,profilesSeeded:!!t.profiles_seeded,tcfConfirmation:t.tcf_confirmation_json?JSON.parse(t.tcf_confirmation_json):void 0,gppConfirmation:t.gpp_confirmation_json?JSON.parse(t.gpp_confirmation_json):void 0}:{}}async updateSettings(e,t){let s={...await this.getSettings(e),...t};return this.stmt(`INSERT INTO tenant_settings (tenant_id, allowed_origins_json, admin_allowed_origins_json, setup_completed, profiles_seeded, tcf_confirmation_json, gpp_confirmation_json, updated_at) VALUES (?,?,?,?,?,?,?,?)
59
+ ON CONFLICT(tenant_id) DO UPDATE SET allowed_origins_json = excluded.allowed_origins_json, admin_allowed_origins_json = excluded.admin_allowed_origins_json, setup_completed = excluded.setup_completed, profiles_seeded = excluded.profiles_seeded, tcf_confirmation_json = excluded.tcf_confirmation_json, gpp_confirmation_json = excluded.gpp_confirmation_json, updated_at = excluded.updated_at`).run([e,JSON.stringify(s.allowedOrigins??[]),JSON.stringify(s.adminAllowedOrigins??[]),s.setupCompleted?1:0,s.profilesSeeded?1:0,s.tcfConfirmation?JSON.stringify(s.tcfConfirmation):null,s.gppConfirmation?JSON.stringify(s.gppConfirmation):null,new Date().toISOString()]),this.getSettings(e)}async purgeExpiredConsents(e){let t=new Date(Date.now()-e*864e5).toISOString(),n=this.stmt("SELECT visitor_id FROM consent_records WHERE created_at < ?").all(t);for(let{visitor_id:s}of n)this.stmt("DELETE FROM consent_history WHERE visitor_id = ?").run(s),this.stmt("DELETE FROM consent_records WHERE visitor_id = ?").run(s);return n.length}mapApiKey(e){return{id:e.id,tenantId:e.tenant_id,keyHash:e.key_hash,name:e.name,isActive:e.is_active===1,...e.created_by!==null?{createdBy:e.created_by}:{},...e.expire_by!==null?{expireBy:e.expire_by}:{},createdAt:e.created_at,...e.updated_at!==null?{updatedAt:e.updated_at}:{}}}async createApiKey(e){let t=A(),n=new Date().toISOString();this.stmt("INSERT INTO api_keys (id,tenant_id,key_hash,name,created_by,expire_by,created_at,updated_at) VALUES (?,?,?,?,?,?,?,?)").run([t,e.tenantId,e.keyHash,e.name,e.createdBy??null,e.expireBy??null,n,n]);let s=this.stmt("SELECT * FROM api_keys WHERE id = ?").get(t);if(!s)throw new Error("Failed to create API key");return this.mapApiKey(s)}async getApiKeyByHash(e){let t=this.stmt("SELECT * FROM api_keys WHERE key_hash = ? AND is_active = 1").get(e);return t?this.mapApiKey(t):null}async revokeApiKey(e){this.stmt("UPDATE api_keys SET is_active = 0, updated_at = ? WHERE id = ?").run([new Date().toISOString(),e])}async reactivateApiKey(e){let t=new Date().toISOString();this.stmt("UPDATE api_keys SET is_active = 1, updated_at = ?, expire_by = CASE WHEN expire_by <= ? THEN NULL ELSE expire_by END WHERE id = ?").run([t,t,e])}async deleteApiKey(e){this.stmt("DELETE FROM api_keys WHERE id = ?").run([e])}async getApiKeys(e){return this.stmt("SELECT * FROM api_keys WHERE tenant_id = ? ORDER BY created_at DESC").all(e).map(n=>this.mapApiKey(n))}mapConsentTemplate(e){return{id:e.id,tenantId:e.tenant_id,name:e.name,cookies:JSON.parse(e.cookies_json),categories:JSON.parse(e.categories_json),createdAt:e.created_at,updatedAt:e.updated_at}}async createConsentTemplate(e){let t=G(),n=new Date().toISOString();return this.stmt("INSERT INTO consent_templates (id,tenant_id,name,cookies_json,categories_json,created_at,updated_at) VALUES (?,?,?,?,?,?,?)").run([t,e.tenantId,e.name,JSON.stringify(e.cookies),JSON.stringify(e.categories),n,n]),this.mapConsentTemplate(this.stmt("SELECT * FROM consent_templates WHERE id = ?").get(t))}async updateConsentTemplate(e,t){let n=new Date().toISOString();t.name!==void 0&&this.stmt("UPDATE consent_templates SET name = ?, updated_at = ? WHERE id = ?").run([t.name,n,e]),t.cookies!==void 0&&this.stmt("UPDATE consent_templates SET cookies_json = ?, updated_at = ? WHERE id = ?").run([JSON.stringify(t.cookies),n,e]),t.categories!==void 0&&this.stmt("UPDATE consent_templates SET categories_json = ?, updated_at = ? WHERE id = ?").run([JSON.stringify(t.categories),n,e]);let s=this.stmt("SELECT * FROM consent_templates WHERE id = ?").get(e);if(!s)throw new Error("Consent template not found");return this.mapConsentTemplate(s)}async deleteConsentTemplate(e){this.stmt("DELETE FROM consent_templates WHERE id = ?").run(e)}async getConsentTemplate(e){let t=this.stmt("SELECT * FROM consent_templates WHERE id = ?").get(e);return t?this.mapConsentTemplate(t):null}async getConsentTemplates(e){return this.stmt("SELECT * FROM consent_templates WHERE tenant_id = ? ORDER BY name ASC").all(e).map(n=>this.mapConsentTemplate(n))}async copyConsentTemplate(e,t){let n=await this.getConsentTemplate(e);if(!n)throw new Error("Consent template not found");return this.createConsentTemplate({tenantId:n.tenantId,name:t,cookies:n.cookies,categories:n.categories})}mapUITemplate(e){let t=JSON.parse(e.settings_json);return{id:e.id,tenantId:e.tenant_id,name:e.name,...t,createdAt:e.created_at,updatedAt:e.updated_at}}async createUITemplate(e){let t=q(),n=new Date().toISOString(),{tenantId:s,name:r,...o}=e;return this.stmt("INSERT INTO ui_templates (id,tenant_id,name,settings_json,created_at,updated_at) VALUES (?,?,?,?,?,?)").run([t,s,r,JSON.stringify(o),n,n]),this.mapUITemplate(this.stmt("SELECT * FROM ui_templates WHERE id = ?").get(t))}async updateUITemplate(e,t){let n=new Date().toISOString(),s=this.stmt("SELECT * FROM ui_templates WHERE id = ?").get(e);if(!s)throw new Error("UI template not found");t.name!==void 0&&this.stmt("UPDATE ui_templates SET name = ?, updated_at = ? WHERE id = ?").run([t.name,n,e]);let{name:r,...o}=t;if(Object.keys(o).length>0){let c={...JSON.parse(s.settings_json),...o};this.stmt("UPDATE ui_templates SET settings_json = ?, updated_at = ? WHERE id = ?").run([JSON.stringify(c),n,e])}return this.mapUITemplate(this.stmt("SELECT * FROM ui_templates WHERE id = ?").get(e))}async deleteUITemplate(e){this.stmt("DELETE FROM ui_templates WHERE id = ?").run(e)}async getUITemplate(e){let t=this.stmt("SELECT * FROM ui_templates WHERE id = ?").get(e);return t?this.mapUITemplate(t):null}async getUITemplates(e){return this.stmt("SELECT * FROM ui_templates WHERE tenant_id = ? ORDER BY name ASC").all(e).map(n=>this.mapUITemplate(n))}async copyUITemplate(e,t){let n=await this.getUITemplate(e);if(!n)throw new Error("UI template not found");let{tenantId:s,mainBanner:r,gpcBanner:o,preferenceModal:a}=n;return this.createUITemplate({tenantId:s,name:t,mainBanner:r,gpcBanner:o,preferenceModal:a})}mapProfileSummary(e){return{id:e.id,name:e.name,defaultLocale:e.default_locale,complianceGroup:e.compliance_group??null,customComplianceGroup:e.custom_compliance_group??null,isActive:e.is_active===1,consentTemplateName:e.consent_template_name,uiTemplateName:e.ui_template_name,createdAt:e.created_at,updatedAt:e.updated_at}}PROFILE_SUMMARY_SQL=`
63
60
  SELECT
64
61
  p.id, p.name, p.default_locale, p.created_at, p.updated_at,
65
62
  json_extract(p.profile_json, '$.complianceGroup') AS compliance_group,
@@ -72,13 +69,13 @@ ${l.map(d=>` (${Q(c)},${Q(d)})`).join(`,
72
69
  FROM profiles p
73
70
  LEFT JOIN consent_templates ct ON ct.id = json_extract(p.profile_json, '$.consentTemplateId')
74
71
  LEFT JOIN ui_templates ut ON ut.id = json_extract(p.profile_json, '$.uiTemplateId')
75
- `;async listProfilesSummary(e){return this.stmt(`${this.PROFILE_SUMMARY_SQL} WHERE p.tenant_id = ? ORDER BY p.created_at ASC`).all(e).map(n=>this.mapProfileSummary(n))}async findProfilesUsingConsentTemplate(e){return this.stmt(`${this.PROFILE_SUMMARY_SQL} WHERE json_extract(p.profile_json, '$.consentTemplateId') = ? ORDER BY p.name ASC`).all(e).map(n=>this.mapProfileSummary(n))}async findProfilesUsingUITemplate(e){return this.stmt(`${this.PROFILE_SUMMARY_SQL} WHERE json_extract(p.profile_json, '$.uiTemplateId') = ? ORDER BY p.name ASC`).all(e).map(n=>this.mapProfileSummary(n))}async getOptInStats(e,t){let n=["cr.tenant_id = ?"],s=[e];t.profileId&&(n.push("cr.profile_id = ?"),s.push(t.profileId)),t.from&&(n.push("cr.created_at >= ?"),s.push(t.from)),t.to&&(n.push("cr.created_at <= ?"),s.push(t.to)),t.locale&&(n.push("cr.locale = ?"),s.push(t.locale));let r=n.join(" AND "),o=this.stmt(`SELECT cr.locale, cr.consent_json FROM consent_records cr WHERE ${r}`).all(s),a=0,c=0,l=0,d=0,u={};for(let _ of o){let h=Object.values(JSON.parse(_.consent_json)),w=h.length>0?h.filter(T=>T==="granted").length/h.length:0,R=h.length>0?h.filter(T=>T==="denied").length/h.length:0;a++;let I=_.locale;u[I]||(u[I]={total:0,granted:0,denied:0,managed:0}),u[I].total++,w>=.9?(c++,u[I].granted++):R>=.9?(l++,u[I].denied++):(d++,u[I].managed++)}let p=this.stmt(`SELECT date(cr.created_at) as d, cr.consent_json FROM consent_records cr WHERE ${r} ORDER BY d ASC`).all(s),g=new Map;for(let _ of p){let h=g.get(_.d)??{total:0,granted:0,denied:0,managed:0},w=Object.values(JSON.parse(_.consent_json)),R=w.length>0?w.filter(T=>T==="granted").length/w.length:0,I=w.length>0?w.filter(T=>T==="denied").length/w.length:0;h.total++,R>=.9?h.granted++:I>=.9?h.denied++:h.managed++,g.set(_.d,h)}let E=Array.from(g.entries()).map(([_,h])=>({date:_,...h}));return{total:a,granted:c,denied:l,managed:d,grantedPct:a>0?Math.round(c/a*1e3)/10:0,deniedPct:a>0?Math.round(l/a*1e3)/10:0,managedPct:a>0?Math.round(d/a*1e3)/10:0,byLocale:u,byDate:E}}};var un=500,ot=class i{constructor(e){this.config=e}config;db;cache=new Map;stmt(e){let t=this.cache.get(e);return t||(t=this.db.prepare(e),this.cache.set(e,t)),t}async connect(){let{DatabaseSync:e}=await import("node:sqlite").catch(()=>{throw new Error(`driver 'node:sqlite' requires Node.js >= 22.5.0 (current: ${process.version})`)}),{dbPath:t}=ne(this.config.path??"./consenti-data","node:sqlite");this.db=new e(t),this.db.exec("PRAGMA journal_mode=WAL"),this.db.exec("PRAGMA foreign_keys=ON"),this.db.exec("PRAGMA synchronous=NORMAL"),await this.migrate()}async disconnect(){this.cache.clear(),this.db.close()}async migrate(){this.db.prepare("PRAGMA user_version").get().user_version<1&&(this.db.exec(ye),this.db.exec(we),this.db.exec("PRAGMA user_version = 1")),this.db.exec(`
72
+ `;async listProfilesSummary(e){return this.stmt(`${this.PROFILE_SUMMARY_SQL} WHERE p.tenant_id = ? ORDER BY p.created_at ASC`).all(e).map(n=>this.mapProfileSummary(n))}async findProfilesUsingConsentTemplate(e){return this.stmt(`${this.PROFILE_SUMMARY_SQL} WHERE json_extract(p.profile_json, '$.consentTemplateId') = ? ORDER BY p.name ASC`).all(e).map(n=>this.mapProfileSummary(n))}async findProfilesUsingUITemplate(e){return this.stmt(`${this.PROFILE_SUMMARY_SQL} WHERE json_extract(p.profile_json, '$.uiTemplateId') = ? ORDER BY p.name ASC`).all(e).map(n=>this.mapProfileSummary(n))}async getOptInStats(e,t){let n=["cr.tenant_id = ?"],s=[e];t.profileId&&(n.push("cr.profile_id = ?"),s.push(t.profileId)),t.from&&(n.push("cr.created_at >= ?"),s.push(t.from)),t.to&&(n.push("cr.created_at <= ?"),s.push(t.to)),t.locale&&(n.push("cr.locale = ?"),s.push(t.locale));let r=n.join(" AND "),o=this.stmt(`SELECT cr.locale, cr.consent_json FROM consent_records cr WHERE ${r}`).all(s),a=0,c=0,l=0,d=0,u={};for(let f of o){let y=Object.values(JSON.parse(f.consent_json)),w=y.length>0?y.filter(T=>T==="granted").length/y.length:0,R=y.length>0?y.filter(T=>T==="denied").length/y.length:0;a++;let I=f.locale;u[I]||(u[I]={total:0,granted:0,denied:0,managed:0}),u[I].total++,w>=.9?(c++,u[I].granted++):R>=.9?(l++,u[I].denied++):(d++,u[I].managed++)}let p=this.stmt(`SELECT date(cr.created_at) as d, cr.consent_json FROM consent_records cr WHERE ${r} ORDER BY d ASC`).all(s),g=new Map;for(let f of p){let y=g.get(f.d)??{total:0,granted:0,denied:0,managed:0},w=Object.values(JSON.parse(f.consent_json)),R=w.length>0?w.filter(T=>T==="granted").length/w.length:0,I=w.length>0?w.filter(T=>T==="denied").length/w.length:0;y.total++,R>=.9?y.granted++:I>=.9?y.denied++:y.managed++,g.set(f.d,y)}let E=Array.from(g.entries()).map(([f,y])=>({date:f,...y}));return{total:a,granted:c,denied:l,managed:d,grantedPct:a>0?Math.round(c/a*1e3)/10:0,deniedPct:a>0?Math.round(l/a*1e3)/10:0,managedPct:a>0?Math.round(d/a*1e3)/10:0,byLocale:u,byDate:E}}};var Sn=500,pt=class i{constructor(e){this.config=e}config;db;cache=new Map;dbPath="";stmt(e){let t=this.cache.get(e);return t||(t=this.db.prepare(e),this.cache.set(e,t)),t}async connect(){let{DatabaseSync:e}=await import("node:sqlite").catch(()=>{throw new Error(`driver 'node:sqlite' requires Node.js >= 22.5.0 (current: ${process.version})`)}),{dbPath:t}=ie(this.config.path??"./consenti-data","node:sqlite");this.dbPath=t,this.db=new e(t),this.db.exec("PRAGMA journal_mode=WAL"),this.db.exec("PRAGMA foreign_keys=ON"),this.db.exec("PRAGMA synchronous=NORMAL"),await this.migrate()}async disconnect(){this.cache.clear(),this.db.close()}async migrate(){let e=this.db.prepare("PRAGMA user_version").get();e.user_version<1?(this.db.exec(Se),this.db.exec(Ce),this.db.exec(`PRAGMA user_version = ${ee}`)):e.user_version<2&&this.migrateToV2(),this.db.exec(`
76
73
  CREATE INDEX IF NOT EXISTS idx_profiles_tenant_compliance_active
77
74
  ON profiles (tenant_id, json_extract(profile_json,'$.complianceGroup'), json_extract(profile_json,'$.isActive'));
78
- `)}mapProfile(e){return{id:e.id,tenantId:e.tenant_id,name:e.name,defaultLocale:e.default_locale,version:e.version,profileJson:JSON.parse(e.profile_json),createdAt:e.created_at,updatedAt:e.updated_at}}async createProfile(e){let t=V(),n=new Date().toISOString();this.stmt("INSERT INTO profiles (id,tenant_id,name,default_locale,version,profile_json,created_at,updated_at) VALUES (?,?,?,?,?,?,?,?)").run(t,e.tenantId,e.name,e.defaultLocale,1,JSON.stringify(e.profileJson),n,n);let s=await this.getProfile(t);if(!s)throw new Error("Failed to create profile");return s}async updateProfile(e,t){if(!await this.getProfile(e))throw new Error(`Profile ${e} not found`);let s=new Date().toISOString(),r=["updated_at = ?"],o=[s];t.name!=null&&(r.push("name = ?"),o.push(t.name)),t.defaultLocale!=null&&(r.push("default_locale = ?"),o.push(t.defaultLocale)),t.profileJson!=null&&(r.push("profile_json = ?"),o.push(JSON.stringify(t.profileJson))),t.version!=null&&(r.push("version = ?"),o.push(t.version)),o.push(e),this.stmt(`UPDATE profiles SET ${r.join(", ")} WHERE id = ?`).run(...o);let a=await this.getProfile(e);if(!a)throw new Error("Failed to update profile");return a}async deleteProfile(e){this.stmt("DELETE FROM profiles WHERE id = ?").run(e)}async getProfile(e){let t=this.stmt("SELECT * FROM profiles WHERE id = ?").get(e);return t?this.mapProfile(t):null}async getProfiles(e){return this.stmt("SELECT * FROM profiles WHERE tenant_id = ? ORDER BY created_at ASC").all(e).map(n=>this.mapProfile(n))}async findActiveProfileByComplianceGroup(e,t){let n=this.stmt(`SELECT * FROM profiles WHERE tenant_id = ?
75
+ `)}migrateToV2(){Ie({exec:e=>this.db.exec(e),all:e=>this.db.prepare(e).all()},this.dbPath)}mapProfile(e){return{id:e.id,tenantId:e.tenant_id,name:e.name,defaultLocale:e.default_locale,version:e.version,profileJson:JSON.parse(e.profile_json),createdAt:e.created_at,updatedAt:e.updated_at}}async createProfile(e){let t=W(),n=new Date().toISOString();this.stmt("INSERT INTO profiles (id,tenant_id,name,default_locale,version,profile_json,created_at,updated_at) VALUES (?,?,?,?,?,?,?,?)").run(t,e.tenantId,e.name,e.defaultLocale,1,JSON.stringify(e.profileJson),n,n);let s=await this.getProfile(t);if(!s)throw new Error("Failed to create profile");return s}async updateProfile(e,t){if(!await this.getProfile(e))throw new Error(`Profile ${e} not found`);let s=new Date().toISOString(),r=["updated_at = ?"],o=[s];t.name!=null&&(r.push("name = ?"),o.push(t.name)),t.defaultLocale!=null&&(r.push("default_locale = ?"),o.push(t.defaultLocale)),t.profileJson!=null&&(r.push("profile_json = ?"),o.push(JSON.stringify(t.profileJson))),t.version!=null&&(r.push("version = ?"),o.push(t.version)),o.push(e),this.stmt(`UPDATE profiles SET ${r.join(", ")} WHERE id = ?`).run(...o);let a=await this.getProfile(e);if(!a)throw new Error("Failed to update profile");return a}async deleteProfile(e){this.stmt("DELETE FROM profiles WHERE id = ?").run(e)}async getProfile(e){let t=this.stmt("SELECT * FROM profiles WHERE id = ?").get(e);return t?this.mapProfile(t):null}async getProfiles(e){return this.stmt("SELECT * FROM profiles WHERE tenant_id = ? ORDER BY created_at ASC").all(e).map(n=>this.mapProfile(n))}async findActiveProfileByComplianceGroup(e,t){let n=this.stmt(`SELECT * FROM profiles WHERE tenant_id = ?
79
76
  AND (json_extract(profile_json, '$.complianceGroup') = ? OR json_extract(profile_json, '$.customComplianceGroup') = ?)
80
- AND json_extract(profile_json, '$.isActive') = 1 LIMIT 1`).get(e,t,t);return n?this.mapProfile(n):null}mapConsent(e){return{id:e.id,tenantId:e.tenant_id,visitorId:e.visitor_id,profileId:e.profile_id,locale:e.locale,consentJson:JSON.parse(e.consent_json),gpcDetected:e.gpc_detected===1,source:e.source,...e.age_verified!=null&&e.age_verified!==0?{ageVerified:!0}:{},...e.parental_consent_token!=null?{parentalConsentToken:e.parental_consent_token}:{},...e.tcf_string!=null?{tcfString:e.tcf_string}:{},...e.gpp_string!=null?{gppString:e.gpp_string}:{},...e.signature!=null?{signature:e.signature}:{},createdAt:e.created_at,updatedAt:e.updated_at}}static CONSENT_SUMMARY_COLS="id, tenant_id, visitor_id, profile_id, locale, gpc_detected, source, age_verified, created_at, updated_at";mapConsentSummary(e){return{id:e.id,tenantId:e.tenant_id,visitorId:e.visitor_id,profileId:e.profile_id,locale:e.locale,gpcDetected:e.gpc_detected===1,source:e.source,...e.age_verified!=null&&e.age_verified!==0?{ageVerified:!0}:{},createdAt:e.created_at,updatedAt:e.updated_at}}async createConsent(e){let t=W(),n=new Date().toISOString();this.stmt("INSERT INTO consent_records (id,tenant_id,visitor_id,profile_id,locale,consent_json,gpc_detected,source,age_verified,parental_consent_token,tcf_string,gpp_string,signature,created_at,updated_at) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)").run(t,e.tenantId,e.visitorId,e.profileId,e.locale,JSON.stringify(e.consentJson),e.gpcDetected?1:0,e.source,e.ageVerified?1:0,e.parentalConsentToken??null,e.tcfString??null,e.gppString??null,e.signature??null,n,n);let s=A();this.stmt("INSERT INTO consent_history (id,tenant_id,consent_record_id,visitor_id,old_json,new_json,action,created_at) VALUES (?,?,?,?,?,?,?,?)").run(s,e.tenantId,t,e.visitorId,null,JSON.stringify(e.consentJson),"created",n);let r=await this.getConsent(e.visitorId);if(!r)throw new Error("Failed to create consent");return r}async updateConsent(e,t){let n=await this.getConsent(e);if(!n)throw new Error(`Consent for visitor ${e} not found`);let s=new Date().toISOString(),r=["updated_at = ?","consent_json = ?"],o=[s,JSON.stringify(t.consentJson)];t.locale!=null&&(r.push("locale = ?"),o.push(t.locale)),t.gpcDetected!=null&&(r.push("gpc_detected = ?"),o.push(t.gpcDetected?1:0)),t.signature!=null&&(r.push("signature = ?"),o.push(t.signature)),o.push(e),this.stmt(`UPDATE consent_records SET ${r.join(", ")} WHERE visitor_id = ?`).run(...o);let a=A();this.stmt("INSERT INTO consent_history (id,tenant_id,consent_record_id,visitor_id,old_json,new_json,action,created_at) VALUES (?,?,?,?,?,?,?,?)").run(a,n.tenantId,n.id,e,JSON.stringify(n.consentJson),JSON.stringify(t.consentJson),"updated",s);let c=await this.getConsent(e);if(!c)throw new Error("Failed to update consent");return c}async deleteConsent(e){this.stmt("DELETE FROM consent_history WHERE visitor_id = ?").run(e),this.stmt("DELETE FROM consent_records WHERE visitor_id = ?").run(e)}async getConsent(e){let t=this.stmt("SELECT * FROM consent_records WHERE visitor_id = ?").get(e);return t?this.mapConsent(t):null}async getConsents(e){let t=["tenant_id = ?"],n=[e.tenantId];if(e.profileId&&(t.push("profile_id = ?"),n.push(e.profileId)),e.from&&(t.push("created_at >= ?"),n.push(e.from)),e.to&&(t.push("created_at <= ?"),n.push(e.to)),e.q){t.push("(visitor_id LIKE ? OR profile_id LIKE ? OR locale LIKE ? OR source LIKE ?) ESCAPE '\\'");let d=j(e.q);n.push(d,d,d,d)}let s=t.join(" AND "),r=e.page??1,o=Math.min(e.limit??50,un),a=(r-1)*o,c=this.stmt(`SELECT ${i.CONSENT_SUMMARY_COLS} FROM consent_records WHERE ${s} ORDER BY created_at DESC LIMIT ? OFFSET ?`).all(...n,o,a),l=this.stmt(`SELECT COUNT(*) AS total FROM consent_records WHERE ${s}`).get(...n);return{items:c.map(d=>this.mapConsentSummary(d)),total:l.total,page:r,limit:o}}async*streamConsents(e){let n=0;for(;;){let s=this.stmt("SELECT * FROM consent_records WHERE tenant_id = ? LIMIT ? OFFSET ?").all(e.tenantId,500,n);if(s.length===0)break;for(let r of s)yield this.mapConsent(r);n+=500}}async getConsentHistory(e){return this.stmt("SELECT * FROM consent_history WHERE visitor_id = ? ORDER BY created_at ASC").all(e).map(n=>({id:n.id,tenantId:n.tenant_id,consentRecordId:n.consent_record_id,visitorId:n.visitor_id,oldJson:n.old_json!=null?JSON.parse(n.old_json):null,newJson:JSON.parse(n.new_json),action:n.action,createdAt:n.created_at}))}async createNoticeShown(e){let t=A(),n=new Date().toISOString();return this.stmt("INSERT INTO notice_shown (id,tenant_id,visitor_id,profile_id,locale,created_at) VALUES (?,?,?,?,?,?)").run(t,e.tenantId,e.visitorId,e.profileId,e.locale,n),{id:t,tenantId:e.tenantId,visitorId:e.visitorId,profileId:e.profileId,locale:e.locale,createdAt:n}}async getNoticeShownForVisitor(e){return this.stmt("SELECT * FROM notice_shown WHERE visitor_id = ? ORDER BY created_at DESC").all(e).map(n=>({id:n.id,tenantId:n.tenant_id,visitorId:n.visitor_id,profileId:n.profile_id,locale:n.locale,createdAt:n.created_at}))}mapVisitor(e){return{id:e.id,tenantId:e.tenant_id,visitorId:e.visitor_id,firstSeen:e.first_seen,lastSeen:e.last_seen,...e.country!=null?{country:e.country}:{},...e.region!=null?{region:e.region}:{},...e.city!=null?{city:e.city}:{},...e.ip_hash!=null?{ipHash:e.ip_hash}:{},...e.user_agent_hash!=null?{userAgentHash:e.user_agent_hash}:{}}}async createVisitor(e){let t=H(),n=new Date().toISOString();this.stmt("INSERT INTO visitors (id,tenant_id,visitor_id,country,region,city,ip_hash,user_agent_hash,first_seen,last_seen) VALUES (?,?,?,?,?,?,?,?,?,?)").run(t,e.tenantId,e.visitorId,e.country??null,e.region??null,e.city??null,e.ipHash??null,e.userAgentHash??null,n,n);let s=await this.getVisitor(e.visitorId);if(!s)throw new Error("Failed to create visitor");return s}async updateVisitor(e,t){let n=new Date().toISOString(),s=["last_seen = ?"],r=[t.lastSeen??n];t.country!=null&&(s.push("country = ?"),r.push(t.country)),t.region!=null&&(s.push("region = ?"),r.push(t.region)),t.city!=null&&(s.push("city = ?"),r.push(t.city)),r.push(e),this.stmt(`UPDATE visitors SET ${s.join(", ")} WHERE visitor_id = ?`).run(...r);let o=await this.getVisitor(e);if(!o)throw new Error(`Visitor ${e} not found`);return o}async deleteVisitor(e){this.stmt("DELETE FROM visitors WHERE visitor_id = ?").run(e)}async getVisitor(e){let t=this.stmt("SELECT * FROM visitors WHERE visitor_id = ?").get(e);return t?this.mapVisitor(t):null}async getVisitors(e){let t=["tenant_id = ?"],n=[e.tenantId];if(e.from&&(t.push("first_seen >= ?"),n.push(e.from)),e.to&&(t.push("first_seen <= ?"),n.push(e.to)),e.q){t.push("(visitor_id LIKE ? OR country LIKE ?) ESCAPE '\\'");let d=j(e.q);n.push(d,d)}let s=t.join(" AND "),r=e.page??1,o=Math.min(e.limit??50,un),a=(r-1)*o,c=this.stmt(`SELECT * FROM visitors WHERE ${s} ORDER BY last_seen DESC LIMIT ? OFFSET ?`).all(...n,o,a),l=this.stmt(`SELECT COUNT(*) AS total FROM visitors WHERE ${s}`).get(...n);return{items:c.map(d=>this.mapVisitor(d)),total:l.total,page:r,limit:o}}mapUser(e){let t=e.allowed_tenants?JSON.parse(e.allowed_tenants):[];return{id:e.id,tenantId:e.tenant_id,name:e.name,email:e.email,passwordHash:e.password_hash,isActive:e.is_active===1,totpEnabled:e.totp_enabled===1,...e.totp_secret!=null?{totpSecret:e.totp_secret}:{},allowedTenants:t,createdAt:e.created_at,updatedAt:e.updated_at}}async createUser(e){let t=A(),n=new Date().toISOString(),s=e.allowedTenants?.length?JSON.stringify(e.allowedTenants):null;this.stmt("INSERT INTO users (id,tenant_id,name,email,password_hash,allowed_tenants,created_at,updated_at) VALUES (?,?,?,?,?,?,?,?)").run(t,e.tenantId,e.name,e.email,e.passwordHash,s,n,n);let r=await this.getUserByEmail(e.email);if(!r)throw new Error("Failed to create user");return r}async updateUser(e,t){let n=new Date().toISOString(),s=["updated_at = ?"],r=[n];t.name!=null&&(s.push("name = ?"),r.push(t.name)),t.email!=null&&(s.push("email = ?"),r.push(t.email)),t.passwordHash!=null&&(s.push("password_hash = ?"),r.push(t.passwordHash)),t.isActive!=null&&(s.push("is_active = ?"),r.push(t.isActive?1:0)),"totpSecret"in t&&(s.push("totp_secret = ?"),r.push(t.totpSecret??null)),t.totpEnabled!=null&&(s.push("totp_enabled = ?"),r.push(t.totpEnabled?1:0)),"allowedTenants"in t&&(s.push("allowed_tenants = ?"),r.push(t.allowedTenants?.length?JSON.stringify(t.allowedTenants):null)),r.push(e),this.stmt(`UPDATE users SET ${s.join(", ")} WHERE id = ?`).run(...r);let o=this.stmt("SELECT * FROM users WHERE id = ?").get(e);if(!o)throw new Error(`User ${e} not found`);return this.mapUser(o)}async deleteUser(e){this.stmt("DELETE FROM user_roles WHERE user_id = ?").run(e),this.stmt("DELETE FROM users WHERE id = ?").run(e)}async getUserById(e){let t=this.stmt("SELECT * FROM users WHERE id = ?").get(e);return t?this.mapUser(t):null}async getUserByEmail(e){let t=this.stmt("SELECT * FROM users WHERE email = ?").get(e);return t?this.mapUser(t):null}async getUsers(e){return this.stmt("SELECT * FROM users WHERE tenant_id = ? ORDER BY created_at ASC").all(e).map(n=>this.mapUser(n))}async countUsers(e){return(e?this.stmt("SELECT COUNT(*) as n FROM users WHERE tenant_id = ?").get(e):this.stmt("SELECT COUNT(*) as n FROM users").get())?.n??0}async getRoles(e){return this.stmt("SELECT * FROM roles WHERE tenant_id = ?").all(e).map(n=>({id:n.id,tenantId:n.tenant_id,name:n.name,...n.description!=null?{description:n.description}:{}}))}async createRole(e){let t=A();this.stmt("INSERT INTO roles (id,tenant_id,name,description) VALUES (?,?,?,?)").run(t,e.tenantId,e.name,e.description??null);let n=this.stmt("SELECT * FROM roles WHERE id = ?").get(t);if(!n)throw new Error("Failed to create role");return{id:n.id,tenantId:n.tenant_id,name:n.name,...n.description!=null?{description:n.description}:{}}}async updateRole(e,t){let n=[],s=[];t.name!=null&&(n.push("name = ?"),s.push(t.name)),t.description!=null&&(n.push("description = ?"),s.push(t.description)),n.length>0&&(s.push(e),this.stmt(`UPDATE roles SET ${n.join(", ")} WHERE id = ?`).run(...s));let r=this.stmt("SELECT * FROM roles WHERE id = ?").get(e);if(!r)throw new Error(`Role ${e} not found`);return{id:r.id,tenantId:r.tenant_id,name:r.name,...r.description!=null?{description:r.description}:{}}}async deleteRole(e){this.stmt("DELETE FROM role_permissions WHERE role_id = ?").run(e),this.stmt("DELETE FROM user_roles WHERE role_id = ?").run(e),this.stmt("DELETE FROM roles WHERE id = ?").run(e)}async getAllPermissions(){return this.stmt("SELECT * FROM permissions ORDER BY name ASC").all().map(t=>({id:t.id,name:t.name,...t.description!=null?{description:t.description}:{}}))}async assignPermissionToRole(e,t){this.stmt("INSERT OR IGNORE INTO role_permissions (role_id,permission_id) VALUES (?,?)").run(e,t)}async revokePermissionFromRole(e,t){this.stmt("DELETE FROM role_permissions WHERE role_id = ? AND permission_id = ?").run(e,t)}async getPermissionsForRole(e){return this.stmt("SELECT p.* FROM permissions p JOIN role_permissions rp ON rp.permission_id = p.id WHERE rp.role_id = ?").all(e).map(n=>({id:n.id,name:n.name,...n.description!=null?{description:n.description}:{}}))}async getUserRoles(e,t){return(t?this.stmt("SELECT r.* FROM roles r JOIN user_roles ur ON ur.role_id = r.id WHERE ur.user_id = ? AND ur.tenant_id = ?").all(e,t):this.stmt("SELECT r.* FROM roles r JOIN user_roles ur ON ur.role_id = r.id WHERE ur.user_id = ?").all(e)).map(s=>({id:s.id,tenantId:s.tenant_id,name:s.name,...s.description!=null?{description:s.description}:{}}))}async assignRole(e,t,n="default"){this.stmt("INSERT OR IGNORE INTO user_roles (user_id, role_id, tenant_id) VALUES (?,?,?)").run(e,t,n)}async revokeRole(e,t,n){n?this.stmt("DELETE FROM user_roles WHERE user_id = ? AND role_id = ? AND tenant_id = ?").run(e,t,n):this.stmt("DELETE FROM user_roles WHERE user_id = ? AND role_id = ?").run(e,t)}async createLog(e){let t=A(),n=new Date().toISOString();this.stmt("INSERT INTO audit_logs (id,tenant_id,user_id,action,resource_type,resource_id,old_data,new_data,created_at) VALUES (?,?,?,?,?,?,?,?,?)").run(t,e.tenantId,e.userId??null,e.action,e.resourceType,e.resourceId??null,e.oldData!=null?JSON.stringify(e.oldData):null,e.newData!=null?JSON.stringify(e.newData):null,n)}async getLogs(e){let t=["tenant_id = ?"],n=[e.tenantId];if(e.action&&(t.push("action = ?"),n.push(e.action)),e.resourceType&&(t.push("resource_type = ?"),n.push(e.resourceType)),e.from&&(t.push("created_at >= ?"),n.push(e.from)),e.to&&(t.push("created_at <= ?"),n.push(e.to)),e.q){t.push("(action LIKE ? OR resource_type LIKE ? OR resource_id LIKE ? OR user_id LIKE ?) ESCAPE '\\'");let d=j(e.q);n.push(d,d,d,d)}let s=t.join(" AND "),r=e.page??1,o=Math.min(e.limit??50,un),a=(r-1)*o,c=this.stmt(`SELECT ${i.AUDIT_SUMMARY_COLS} FROM audit_logs WHERE ${s} ORDER BY created_at DESC LIMIT ? OFFSET ?`).all(...n,o,a),l=this.stmt(`SELECT COUNT(*) AS total FROM audit_logs WHERE ${s}`).get(...n);return{items:c.map(d=>this.mapAuditSummary(d)),total:l.total,page:r,limit:o}}async getAuditLogById(e){let t=this.stmt("SELECT * FROM audit_logs WHERE id = ?").get(e);return t?this.mapAuditLog(t):null}static AUDIT_SUMMARY_COLS="id, tenant_id, user_id, action, resource_type, resource_id, created_at";mapAuditSummary(e){return{id:e.id,tenantId:e.tenant_id,action:e.action,resourceType:e.resource_type,...e.user_id!=null?{userId:e.user_id}:{},...e.resource_id!=null?{resourceId:e.resource_id}:{},createdAt:e.created_at}}mapAuditLog(e){return{id:e.id,tenantId:e.tenant_id,action:e.action,resourceType:e.resource_type,createdAt:e.created_at,...e.user_id!=null?{userId:e.user_id}:{},...e.resource_id!=null?{resourceId:e.resource_id}:{},...e.old_data!=null?{oldData:JSON.parse(e.old_data)}:{},...e.new_data!=null?{newData:JSON.parse(e.new_data)}:{}}}async*streamAuditLogs(e){let t=["tenant_id = ?"],n=[e.tenantId];e.action&&(t.push("action = ?"),n.push(e.action)),e.from&&(t.push("created_at >= ?"),n.push(e.from)),e.to&&(t.push("created_at <= ?"),n.push(e.to));let s=500,r=0;for(;;){let o=this.stmt(`SELECT * FROM audit_logs WHERE ${t.join(" AND ")} ORDER BY created_at DESC LIMIT ? OFFSET ?`).all(...n,s,r);if(o.length===0)break;for(let a of o)yield this.mapAuditLog(a);r+=s}}async getOverviewStats(e){let t=this.stmt("SELECT COUNT(*) as n FROM consent_records WHERE tenant_id = ?").get(e),n=this.stmt("SELECT COUNT(*) as n FROM consent_records WHERE tenant_id = ? AND gpc_detected = 1").get(e),s=this.stmt("SELECT COUNT(*) as n FROM visitors WHERE tenant_id = ?").get(e),r=t?.n??0,o=this.stmt("SELECT consent_json FROM consent_records WHERE tenant_id = ? ORDER BY created_at DESC LIMIT 500").all(e),a=0,c=0;for(let d of o){let u=Object.values(JSON.parse(d.consent_json)),p=u.length>0?u.filter(g=>g==="granted").length/u.length:0;p>=.8?a++:p<=.2&&c++}let l=o.length||1;return{totalConsents:r,gpcDetectedCount:n?.n??0,totalVisitors:s?.n??0,acceptedPct:Math.round(a/l*100),rejectedPct:Math.round(c/l*100)}}async getCategoryStats(e){let t=this.stmt("SELECT consent_json FROM consent_records WHERE tenant_id = ? LIMIT 5000").all(e),n={};for(let s of t){let r=JSON.parse(s.consent_json);for(let[o,a]of Object.entries(r))n[o]||(n[o]={granted:0,denied:0,objected:0}),a==="granted"?n[o].granted++:a==="denied"?n[o].denied++:a==="objected"&&n[o].objected++}return n}async getTimeline(e,t=30){return this.stmt("SELECT date(created_at) as date, COUNT(*) as count FROM consent_records WHERE tenant_id = ? AND created_at >= date('now', ?) GROUP BY date(created_at) ORDER BY date ASC").all(e,`-${t} days`).map(s=>({date:s.date,count:s.count}))}async getCountries(e){return this.stmt("SELECT v.country, COUNT(*) as count FROM consent_records cr JOIN visitors v ON v.visitor_id = cr.visitor_id WHERE cr.tenant_id = ? AND v.country IS NOT NULL GROUP BY v.country ORDER BY count DESC LIMIT 50").all(e).map(n=>({country:n.country,count:n.count}))}async getGpcStats(e){let t=this.stmt("SELECT COUNT(*) as n FROM consent_records WHERE tenant_id = ?").get(e),n=this.stmt("SELECT COUNT(*) as n FROM consent_records WHERE tenant_id = ? AND gpc_detected = 1").get(e),s=t?.n??0,r=n?.n??0;return{detected:r,total:s,rate:s>0?Math.round(r/s*100):0}}async getTenants(){return this.stmt("SELECT * FROM tenants ORDER BY name ASC").all().map(t=>({id:t.id,name:t.name,slug:t.slug,createdAt:t.created_at,updatedAt:t.updated_at}))}async createTenant(e){let t=A(),n=new Date().toISOString();this.stmt("INSERT INTO tenants (id,name,slug,created_at,updated_at) VALUES (?,?,?,?,?)").run(t,e.name,e.slug,n,n);let s=this.stmt("SELECT * FROM tenants WHERE id = ?").get(t);if(!s)throw new Error("Failed to create tenant");return{id:s.id,name:s.name,slug:s.slug,createdAt:s.created_at,updatedAt:s.updated_at}}async updateTenant(e,t){let n=new Date().toISOString(),s=["updated_at = ?"],r=[n];t.name!=null&&(s.push("name = ?"),r.push(t.name)),t.slug!=null&&(s.push("slug = ?"),r.push(t.slug)),r.push(e),this.stmt(`UPDATE tenants SET ${s.join(", ")} WHERE id = ?`).run(...r);let o=this.stmt("SELECT * FROM tenants WHERE id = ?").get(e);if(!o)throw new Error(`Tenant ${e} not found`);return{id:o.id,name:o.name,slug:o.slug,createdAt:o.created_at,updatedAt:o.updated_at}}async deleteTenant(e){this.stmt("DELETE FROM tenants WHERE id = ?").run(e)}async getSettings(e){let t=this.stmt("SELECT allowed_origins_json, admin_allowed_origins_json, setup_completed, profiles_seeded, tcf_confirmation_json, gpp_confirmation_json FROM tenant_settings WHERE tenant_id = ?").get(e);return t?{allowedOrigins:JSON.parse(t.allowed_origins_json),adminAllowedOrigins:JSON.parse(t.admin_allowed_origins_json),setupCompleted:!!t.setup_completed,profilesSeeded:!!t.profiles_seeded,tcfConfirmation:t.tcf_confirmation_json?JSON.parse(t.tcf_confirmation_json):void 0,gppConfirmation:t.gpp_confirmation_json?JSON.parse(t.gpp_confirmation_json):void 0}:{}}async updateSettings(e,t){let s={...await this.getSettings(e),...t};return this.stmt(`INSERT INTO tenant_settings (tenant_id, allowed_origins_json, admin_allowed_origins_json, setup_completed, profiles_seeded, tcf_confirmation_json, gpp_confirmation_json, updated_at) VALUES (?,?,?,?,?,?,?,?)
81
- ON CONFLICT(tenant_id) DO UPDATE SET allowed_origins_json = excluded.allowed_origins_json, admin_allowed_origins_json = excluded.admin_allowed_origins_json, setup_completed = excluded.setup_completed, profiles_seeded = excluded.profiles_seeded, tcf_confirmation_json = excluded.tcf_confirmation_json, gpp_confirmation_json = excluded.gpp_confirmation_json, updated_at = excluded.updated_at`).run(e,JSON.stringify(s.allowedOrigins??[]),JSON.stringify(s.adminAllowedOrigins??[]),s.setupCompleted?1:0,s.profilesSeeded?1:0,s.tcfConfirmation?JSON.stringify(s.tcfConfirmation):null,s.gppConfirmation?JSON.stringify(s.gppConfirmation):null,new Date().toISOString()),this.getSettings(e)}async purgeExpiredConsents(e){let t=new Date(Date.now()-e*864e5).toISOString(),n=this.stmt("SELECT visitor_id FROM consent_records WHERE created_at < ?").all(t);for(let{visitor_id:s}of n)this.stmt("DELETE FROM consent_history WHERE visitor_id = ?").run(s),this.stmt("DELETE FROM consent_records WHERE visitor_id = ?").run(s);return n.length}mapApiKey(e){return{id:e.id,tenantId:e.tenant_id,keyHash:e.key_hash,name:e.name,isActive:e.is_active===1,...e.created_by!==null?{createdBy:e.created_by}:{},...e.expire_by!==null?{expireBy:e.expire_by}:{},createdAt:e.created_at,...e.updated_at!==null?{updatedAt:e.updated_at}:{}}}async createApiKey(e){let t=A(),n=new Date().toISOString();this.stmt("INSERT INTO api_keys (id,tenant_id,key_hash,name,created_by,expire_by,created_at,updated_at) VALUES (?,?,?,?,?,?,?,?)").run(t,e.tenantId,e.keyHash,e.name,e.createdBy??null,e.expireBy??null,n,n);let s=this.stmt("SELECT * FROM api_keys WHERE id = ?").get(t);if(!s)throw new Error("Failed to create API key");return this.mapApiKey(s)}async getApiKeyByHash(e){let t=this.stmt("SELECT * FROM api_keys WHERE key_hash = ? AND is_active = 1").get(e);return t?this.mapApiKey(t):null}async revokeApiKey(e){this.stmt("UPDATE api_keys SET is_active = 0, updated_at = ? WHERE id = ?").run(new Date().toISOString(),e)}async reactivateApiKey(e){let t=new Date().toISOString();this.stmt("UPDATE api_keys SET is_active = 1, updated_at = ?, expire_by = CASE WHEN expire_by <= ? THEN NULL ELSE expire_by END WHERE id = ?").run(t,t,e)}async deleteApiKey(e){this.stmt("DELETE FROM api_keys WHERE id = ?").run(e)}async getApiKeys(e){return this.stmt("SELECT * FROM api_keys WHERE tenant_id = ? ORDER BY created_at DESC").all(e).map(n=>this.mapApiKey(n))}mapConsentTemplate(e){return{id:e.id,tenantId:e.tenant_id,name:e.name,cookies:JSON.parse(e.cookies_json),categories:JSON.parse(e.categories_json),createdAt:e.created_at,updatedAt:e.updated_at}}async createConsentTemplate(e){let t=J(),n=new Date().toISOString();return this.stmt("INSERT INTO consent_templates (id,tenant_id,name,cookies_json,categories_json,created_at,updated_at) VALUES (?,?,?,?,?,?,?)").run(t,e.tenantId,e.name,JSON.stringify(e.cookies),JSON.stringify(e.categories),n,n),this.mapConsentTemplate(this.stmt("SELECT * FROM consent_templates WHERE id = ?").get(t))}async updateConsentTemplate(e,t){let n=new Date().toISOString();t.name!==void 0&&this.stmt("UPDATE consent_templates SET name = ?, updated_at = ? WHERE id = ?").run(t.name,n,e),t.cookies!==void 0&&this.stmt("UPDATE consent_templates SET cookies_json = ?, updated_at = ? WHERE id = ?").run(JSON.stringify(t.cookies),n,e),t.categories!==void 0&&this.stmt("UPDATE consent_templates SET categories_json = ?, updated_at = ? WHERE id = ?").run(JSON.stringify(t.categories),n,e);let s=this.stmt("SELECT * FROM consent_templates WHERE id = ?").get(e);if(!s)throw new Error("Consent template not found");return this.mapConsentTemplate(s)}async deleteConsentTemplate(e){this.stmt("DELETE FROM consent_templates WHERE id = ?").run(e)}async getConsentTemplate(e){let t=this.stmt("SELECT * FROM consent_templates WHERE id = ?").get(e);return t?this.mapConsentTemplate(t):null}async getConsentTemplates(e){return this.stmt("SELECT * FROM consent_templates WHERE tenant_id = ? ORDER BY name ASC").all(e).map(n=>this.mapConsentTemplate(n))}async copyConsentTemplate(e,t){let n=await this.getConsentTemplate(e);if(!n)throw new Error("Consent template not found");return this.createConsentTemplate({tenantId:n.tenantId,name:t,cookies:n.cookies,categories:n.categories})}mapUITemplate(e){let t=JSON.parse(e.settings_json);return{id:e.id,tenantId:e.tenant_id,name:e.name,...t,createdAt:e.created_at,updatedAt:e.updated_at}}async createUITemplate(e){let t=q(),n=new Date().toISOString(),{tenantId:s,name:r,...o}=e;return this.stmt("INSERT INTO ui_templates (id,tenant_id,name,settings_json,created_at,updated_at) VALUES (?,?,?,?,?,?)").run(t,s,r,JSON.stringify(o),n,n),this.mapUITemplate(this.stmt("SELECT * FROM ui_templates WHERE id = ?").get(t))}async updateUITemplate(e,t){let n=new Date().toISOString(),s=this.stmt("SELECT * FROM ui_templates WHERE id = ?").get(e);if(!s)throw new Error("UI template not found");t.name!==void 0&&this.stmt("UPDATE ui_templates SET name = ?, updated_at = ? WHERE id = ?").run(t.name,n,e);let{name:r,...o}=t;if(Object.keys(o).length>0){let c={...JSON.parse(s.settings_json),...o};this.stmt("UPDATE ui_templates SET settings_json = ?, updated_at = ? WHERE id = ?").run(JSON.stringify(c),n,e)}return this.mapUITemplate(this.stmt("SELECT * FROM ui_templates WHERE id = ?").get(e))}async deleteUITemplate(e){this.stmt("DELETE FROM ui_templates WHERE id = ?").run(e)}async getUITemplate(e){let t=this.stmt("SELECT * FROM ui_templates WHERE id = ?").get(e);return t?this.mapUITemplate(t):null}async getUITemplates(e){return this.stmt("SELECT * FROM ui_templates WHERE tenant_id = ? ORDER BY name ASC").all(e).map(n=>this.mapUITemplate(n))}async copyUITemplate(e,t){let n=await this.getUITemplate(e);if(!n)throw new Error("UI template not found");let{tenantId:s,mainBanner:r,gpcBanner:o,preferenceModal:a}=n;return this.createUITemplate({tenantId:s,name:t,mainBanner:r,gpcBanner:o,preferenceModal:a})}mapProfileSummary(e){return{id:e.id,name:e.name,defaultLocale:e.default_locale,complianceGroup:e.compliance_group??null,customComplianceGroup:e.custom_compliance_group??null,isActive:e.is_active===1,consentTemplateName:e.consent_template_name,uiTemplateName:e.ui_template_name,createdAt:e.created_at,updatedAt:e.updated_at}}PROFILE_SUMMARY_SQL=`
77
+ AND json_extract(profile_json, '$.isActive') = 1 LIMIT 1`).get(e,t,t);return n?this.mapProfile(n):null}mapConsent(e){return{id:e.id,tenantId:e.tenant_id,visitorId:e.visitor_id,profileId:e.profile_id,...e.profile_version!=null?{profileVersion:e.profile_version}:{},locale:e.locale,consentJson:JSON.parse(e.consent_json),gpcDetected:e.gpc_detected===1,source:e.source,...e.age_verified!=null&&e.age_verified!==0?{ageVerified:!0}:{},...e.parental_consent_token!=null?{parentalConsentToken:e.parental_consent_token}:{},...e.tcf_string!=null?{tcfString:e.tcf_string}:{},...e.gpp_string!=null?{gppString:e.gpp_string}:{},...e.signature!=null?{signature:e.signature}:{},createdAt:e.created_at,updatedAt:e.updated_at}}static CONSENT_SUMMARY_COLS="id, tenant_id, visitor_id, profile_id, locale, gpc_detected, source, age_verified, created_at, updated_at";mapConsentSummary(e){return{id:e.id,tenantId:e.tenant_id,visitorId:e.visitor_id,profileId:e.profile_id,locale:e.locale,gpcDetected:e.gpc_detected===1,source:e.source,...e.age_verified!=null&&e.age_verified!==0?{ageVerified:!0}:{},createdAt:e.created_at,updatedAt:e.updated_at}}async createConsent(e){let t=J(),n=new Date().toISOString();this.stmt("INSERT INTO consent_records (id,tenant_id,visitor_id,profile_id,profile_version,locale,consent_json,gpc_detected,source,age_verified,parental_consent_token,tcf_string,gpp_string,signature,created_at,updated_at) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)").run(t,e.tenantId,e.visitorId,e.profileId,e.profileVersion??null,e.locale,JSON.stringify(e.consentJson),e.gpcDetected?1:0,e.source,e.ageVerified?1:0,e.parentalConsentToken??null,e.tcfString??null,e.gppString??null,e.signature??null,n,n);let s=A();this.stmt("INSERT INTO consent_history (id,tenant_id,consent_record_id,visitor_id,profile_version,old_json,new_json,action,created_at) VALUES (?,?,?,?,?,?,?,?,?)").run(s,e.tenantId,t,e.visitorId,e.profileVersion??null,null,JSON.stringify(e.consentJson),"created",n);let r=await this.getConsent(e.visitorId);if(!r)throw new Error("Failed to create consent");return r}async updateConsent(e,t){let n=await this.getConsent(e);if(!n)throw new Error(`Consent for visitor ${e} not found`);let s=new Date().toISOString(),r=["updated_at = ?","consent_json = ?"],o=[s,JSON.stringify(t.consentJson)];t.profileVersion!=null&&(r.push("profile_version = ?"),o.push(t.profileVersion)),t.locale!=null&&(r.push("locale = ?"),o.push(t.locale)),t.gpcDetected!=null&&(r.push("gpc_detected = ?"),o.push(t.gpcDetected?1:0)),t.signature!=null&&(r.push("signature = ?"),o.push(t.signature)),o.push(e),this.stmt(`UPDATE consent_records SET ${r.join(", ")} WHERE visitor_id = ?`).run(...o);let a=A();this.stmt("INSERT INTO consent_history (id,tenant_id,consent_record_id,visitor_id,profile_version,old_json,new_json,action,created_at) VALUES (?,?,?,?,?,?,?,?,?)").run(a,n.tenantId,n.id,e,t.profileVersion??n.profileVersion??null,JSON.stringify(n.consentJson),JSON.stringify(t.consentJson),"updated",s);let c=await this.getConsent(e);if(!c)throw new Error("Failed to update consent");return c}async deleteConsent(e){this.stmt("DELETE FROM consent_history WHERE visitor_id = ?").run(e),this.stmt("DELETE FROM consent_records WHERE visitor_id = ?").run(e)}async getConsent(e){let t=this.stmt("SELECT * FROM consent_records WHERE visitor_id = ?").get(e);return t?this.mapConsent(t):null}async getConsents(e){let t=["tenant_id = ?"],n=[e.tenantId];if(e.profileId&&(t.push("profile_id = ?"),n.push(e.profileId)),e.from&&(t.push("created_at >= ?"),n.push(e.from)),e.to&&(t.push("created_at <= ?"),n.push(e.to)),e.q){t.push("(visitor_id LIKE ? OR profile_id LIKE ? OR locale LIKE ? OR source LIKE ?) ESCAPE '\\'");let d=x(e.q);n.push(d,d,d,d)}let s=t.join(" AND "),r=e.page??1,o=Math.min(e.limit??50,Sn),a=(r-1)*o,c=this.stmt(`SELECT ${i.CONSENT_SUMMARY_COLS} FROM consent_records WHERE ${s} ORDER BY created_at DESC LIMIT ? OFFSET ?`).all(...n,o,a),l=this.stmt(`SELECT COUNT(*) AS total FROM consent_records WHERE ${s}`).get(...n);return{items:c.map(d=>this.mapConsentSummary(d)),total:l.total,page:r,limit:o}}async*streamConsents(e){let n=0;for(;;){let s=this.stmt("SELECT * FROM consent_records WHERE tenant_id = ? LIMIT ? OFFSET ?").all(e.tenantId,500,n);if(s.length===0)break;for(let r of s)yield this.mapConsent(r);n+=500}}async getConsentHistory(e){return this.stmt("SELECT * FROM consent_history WHERE visitor_id = ? ORDER BY created_at ASC").all(e).map(n=>({id:n.id,tenantId:n.tenant_id,consentRecordId:n.consent_record_id,visitorId:n.visitor_id,...n.profile_version!=null?{profileVersion:n.profile_version}:{},oldJson:n.old_json!=null?JSON.parse(n.old_json):null,newJson:JSON.parse(n.new_json),action:n.action,createdAt:n.created_at}))}async createNoticeShown(e){let t=A(),n=new Date().toISOString();return this.stmt("INSERT INTO notice_shown (id,tenant_id,visitor_id,profile_id,locale,created_at) VALUES (?,?,?,?,?,?)").run(t,e.tenantId,e.visitorId,e.profileId,e.locale,n),{id:t,tenantId:e.tenantId,visitorId:e.visitorId,profileId:e.profileId,locale:e.locale,createdAt:n}}async getNoticeShownForVisitor(e){return this.stmt("SELECT * FROM notice_shown WHERE visitor_id = ? ORDER BY created_at DESC").all(e).map(n=>({id:n.id,tenantId:n.tenant_id,visitorId:n.visitor_id,profileId:n.profile_id,locale:n.locale,createdAt:n.created_at}))}mapVisitor(e){return{id:e.id,tenantId:e.tenant_id,visitorId:e.visitor_id,firstSeen:e.first_seen,lastSeen:e.last_seen,...e.country!=null?{country:e.country}:{},...e.region!=null?{region:e.region}:{},...e.city!=null?{city:e.city}:{},...e.ip_hash!=null?{ipHash:e.ip_hash}:{},...e.user_agent_hash!=null?{userAgentHash:e.user_agent_hash}:{}}}async createVisitor(e){let t=H(),n=new Date().toISOString();this.stmt("INSERT INTO visitors (id,tenant_id,visitor_id,country,region,city,ip_hash,user_agent_hash,first_seen,last_seen) VALUES (?,?,?,?,?,?,?,?,?,?)").run(t,e.tenantId,e.visitorId,e.country??null,e.region??null,e.city??null,e.ipHash??null,e.userAgentHash??null,n,n);let s=await this.getVisitor(e.visitorId);if(!s)throw new Error("Failed to create visitor");return s}async updateVisitor(e,t){let n=new Date().toISOString(),s=["last_seen = ?"],r=[t.lastSeen??n];t.country!=null&&(s.push("country = ?"),r.push(t.country)),t.region!=null&&(s.push("region = ?"),r.push(t.region)),t.city!=null&&(s.push("city = ?"),r.push(t.city)),r.push(e),this.stmt(`UPDATE visitors SET ${s.join(", ")} WHERE visitor_id = ?`).run(...r);let o=await this.getVisitor(e);if(!o)throw new Error(`Visitor ${e} not found`);return o}async deleteVisitor(e){this.stmt("DELETE FROM visitors WHERE visitor_id = ?").run(e)}async getVisitor(e){let t=this.stmt("SELECT * FROM visitors WHERE visitor_id = ?").get(e);return t?this.mapVisitor(t):null}async getVisitors(e){let t=["tenant_id = ?"],n=[e.tenantId];if(e.from&&(t.push("first_seen >= ?"),n.push(e.from)),e.to&&(t.push("first_seen <= ?"),n.push(e.to)),e.q){t.push("(visitor_id LIKE ? OR country LIKE ?) ESCAPE '\\'");let d=x(e.q);n.push(d,d)}let s=t.join(" AND "),r=e.page??1,o=Math.min(e.limit??50,Sn),a=(r-1)*o,c=this.stmt(`SELECT * FROM visitors WHERE ${s} ORDER BY last_seen DESC LIMIT ? OFFSET ?`).all(...n,o,a),l=this.stmt(`SELECT COUNT(*) AS total FROM visitors WHERE ${s}`).get(...n);return{items:c.map(d=>this.mapVisitor(d)),total:l.total,page:r,limit:o}}mapUser(e){let t=e.allowed_tenants?JSON.parse(e.allowed_tenants):[];return{id:e.id,tenantId:e.tenant_id,name:e.name,email:e.email,passwordHash:e.password_hash,isActive:e.is_active===1,totpEnabled:e.totp_enabled===1,...e.totp_secret!=null?{totpSecret:e.totp_secret}:{},allowedTenants:t,createdAt:e.created_at,updatedAt:e.updated_at}}async createUser(e){let t=A(),n=new Date().toISOString(),s=e.allowedTenants?.length?JSON.stringify(e.allowedTenants):null;this.stmt("INSERT INTO users (id,tenant_id,name,email,password_hash,allowed_tenants,created_at,updated_at) VALUES (?,?,?,?,?,?,?,?)").run(t,e.tenantId,e.name,e.email,e.passwordHash,s,n,n);let r=await this.getUserByEmail(e.email);if(!r)throw new Error("Failed to create user");return r}async updateUser(e,t){let n=new Date().toISOString(),s=["updated_at = ?"],r=[n];t.name!=null&&(s.push("name = ?"),r.push(t.name)),t.email!=null&&(s.push("email = ?"),r.push(t.email)),t.passwordHash!=null&&(s.push("password_hash = ?"),r.push(t.passwordHash)),t.isActive!=null&&(s.push("is_active = ?"),r.push(t.isActive?1:0)),"totpSecret"in t&&(s.push("totp_secret = ?"),r.push(t.totpSecret??null)),t.totpEnabled!=null&&(s.push("totp_enabled = ?"),r.push(t.totpEnabled?1:0)),"allowedTenants"in t&&(s.push("allowed_tenants = ?"),r.push(t.allowedTenants?.length?JSON.stringify(t.allowedTenants):null)),r.push(e),this.stmt(`UPDATE users SET ${s.join(", ")} WHERE id = ?`).run(...r);let o=this.stmt("SELECT * FROM users WHERE id = ?").get(e);if(!o)throw new Error(`User ${e} not found`);return this.mapUser(o)}async deleteUser(e){this.stmt("DELETE FROM user_roles WHERE user_id = ?").run(e),this.stmt("DELETE FROM users WHERE id = ?").run(e)}async getUserById(e){let t=this.stmt("SELECT * FROM users WHERE id = ?").get(e);return t?this.mapUser(t):null}async getUserByEmail(e){let t=this.stmt("SELECT * FROM users WHERE email = ?").get(e);return t?this.mapUser(t):null}async getUsers(e){return this.stmt("SELECT * FROM users WHERE tenant_id = ? ORDER BY created_at ASC").all(e).map(n=>this.mapUser(n))}async countUsers(e){return(e?this.stmt("SELECT COUNT(*) as n FROM users WHERE tenant_id = ?").get(e):this.stmt("SELECT COUNT(*) as n FROM users").get())?.n??0}async getRoles(e){return this.stmt("SELECT * FROM roles WHERE tenant_id = ?").all(e).map(n=>({id:n.id,tenantId:n.tenant_id,name:n.name,...n.description!=null?{description:n.description}:{}}))}async createRole(e){let t=A();this.stmt("INSERT INTO roles (id,tenant_id,name,description) VALUES (?,?,?,?)").run(t,e.tenantId,e.name,e.description??null);let n=this.stmt("SELECT * FROM roles WHERE id = ?").get(t);if(!n)throw new Error("Failed to create role");return{id:n.id,tenantId:n.tenant_id,name:n.name,...n.description!=null?{description:n.description}:{}}}async updateRole(e,t){let n=[],s=[];t.name!=null&&(n.push("name = ?"),s.push(t.name)),t.description!=null&&(n.push("description = ?"),s.push(t.description)),n.length>0&&(s.push(e),this.stmt(`UPDATE roles SET ${n.join(", ")} WHERE id = ?`).run(...s));let r=this.stmt("SELECT * FROM roles WHERE id = ?").get(e);if(!r)throw new Error(`Role ${e} not found`);return{id:r.id,tenantId:r.tenant_id,name:r.name,...r.description!=null?{description:r.description}:{}}}async deleteRole(e){this.stmt("DELETE FROM role_permissions WHERE role_id = ?").run(e),this.stmt("DELETE FROM user_roles WHERE role_id = ?").run(e),this.stmt("DELETE FROM roles WHERE id = ?").run(e)}async getAllPermissions(){return this.stmt("SELECT * FROM permissions ORDER BY name ASC").all().map(t=>({id:t.id,name:t.name,...t.description!=null?{description:t.description}:{}}))}async assignPermissionToRole(e,t){this.stmt("INSERT OR IGNORE INTO role_permissions (role_id,permission_id) VALUES (?,?)").run(e,t)}async revokePermissionFromRole(e,t){this.stmt("DELETE FROM role_permissions WHERE role_id = ? AND permission_id = ?").run(e,t)}async getPermissionsForRole(e){return this.stmt("SELECT p.* FROM permissions p JOIN role_permissions rp ON rp.permission_id = p.id WHERE rp.role_id = ?").all(e).map(n=>({id:n.id,name:n.name,...n.description!=null?{description:n.description}:{}}))}async getUserRoles(e,t){return(t?this.stmt("SELECT r.* FROM roles r JOIN user_roles ur ON ur.role_id = r.id WHERE ur.user_id = ? AND ur.tenant_id = ?").all(e,t):this.stmt("SELECT r.* FROM roles r JOIN user_roles ur ON ur.role_id = r.id WHERE ur.user_id = ?").all(e)).map(s=>({id:s.id,tenantId:s.tenant_id,name:s.name,...s.description!=null?{description:s.description}:{}}))}async assignRole(e,t,n="default"){this.stmt("INSERT OR IGNORE INTO user_roles (user_id, role_id, tenant_id) VALUES (?,?,?)").run(e,t,n)}async revokeRole(e,t,n){n?this.stmt("DELETE FROM user_roles WHERE user_id = ? AND role_id = ? AND tenant_id = ?").run(e,t,n):this.stmt("DELETE FROM user_roles WHERE user_id = ? AND role_id = ?").run(e,t)}async createLog(e){let t=A(),n=new Date().toISOString();this.stmt("INSERT INTO audit_logs (id,tenant_id,user_id,action,resource_type,resource_id,old_data,new_data,created_at) VALUES (?,?,?,?,?,?,?,?,?)").run(t,e.tenantId,e.userId??null,e.action,e.resourceType,e.resourceId??null,e.oldData!=null?JSON.stringify(e.oldData):null,e.newData!=null?JSON.stringify(e.newData):null,n)}async getLogs(e){let t=["tenant_id = ?"],n=[e.tenantId];if(e.action&&(t.push("action = ?"),n.push(e.action)),e.resourceType&&(t.push("resource_type = ?"),n.push(e.resourceType)),e.from&&(t.push("created_at >= ?"),n.push(e.from)),e.to&&(t.push("created_at <= ?"),n.push(e.to)),e.q){t.push("(action LIKE ? OR resource_type LIKE ? OR resource_id LIKE ? OR user_id LIKE ?) ESCAPE '\\'");let d=x(e.q);n.push(d,d,d,d)}let s=t.join(" AND "),r=e.page??1,o=Math.min(e.limit??50,Sn),a=(r-1)*o,c=this.stmt(`SELECT ${i.AUDIT_SUMMARY_COLS} FROM audit_logs WHERE ${s} ORDER BY created_at DESC LIMIT ? OFFSET ?`).all(...n,o,a),l=this.stmt(`SELECT COUNT(*) AS total FROM audit_logs WHERE ${s}`).get(...n);return{items:c.map(d=>this.mapAuditSummary(d)),total:l.total,page:r,limit:o}}async getAuditLogById(e){let t=this.stmt("SELECT * FROM audit_logs WHERE id = ?").get(e);return t?this.mapAuditLog(t):null}static AUDIT_SUMMARY_COLS="id, tenant_id, user_id, action, resource_type, resource_id, created_at";mapAuditSummary(e){return{id:e.id,tenantId:e.tenant_id,action:e.action,resourceType:e.resource_type,...e.user_id!=null?{userId:e.user_id}:{},...e.resource_id!=null?{resourceId:e.resource_id}:{},createdAt:e.created_at}}mapAuditLog(e){return{id:e.id,tenantId:e.tenant_id,action:e.action,resourceType:e.resource_type,createdAt:e.created_at,...e.user_id!=null?{userId:e.user_id}:{},...e.resource_id!=null?{resourceId:e.resource_id}:{},...e.old_data!=null?{oldData:JSON.parse(e.old_data)}:{},...e.new_data!=null?{newData:JSON.parse(e.new_data)}:{}}}async*streamAuditLogs(e){let t=["tenant_id = ?"],n=[e.tenantId];e.action&&(t.push("action = ?"),n.push(e.action)),e.from&&(t.push("created_at >= ?"),n.push(e.from)),e.to&&(t.push("created_at <= ?"),n.push(e.to));let s=500,r=0;for(;;){let o=this.stmt(`SELECT * FROM audit_logs WHERE ${t.join(" AND ")} ORDER BY created_at DESC LIMIT ? OFFSET ?`).all(...n,s,r);if(o.length===0)break;for(let a of o)yield this.mapAuditLog(a);r+=s}}async getOverviewStats(e){let t=this.stmt("SELECT COUNT(*) as n FROM consent_records WHERE tenant_id = ?").get(e),n=this.stmt("SELECT COUNT(*) as n FROM consent_records WHERE tenant_id = ? AND gpc_detected = 1").get(e),s=this.stmt("SELECT COUNT(*) as n FROM visitors WHERE tenant_id = ?").get(e),r=t?.n??0,o=this.stmt("SELECT consent_json FROM consent_records WHERE tenant_id = ? ORDER BY created_at DESC LIMIT 500").all(e),a=0,c=0;for(let d of o){let u=Object.values(JSON.parse(d.consent_json)),p=u.length>0?u.filter(g=>g==="granted").length/u.length:0;p>=.8?a++:p<=.2&&c++}let l=o.length||1;return{totalConsents:r,gpcDetectedCount:n?.n??0,totalVisitors:s?.n??0,acceptedPct:Math.round(a/l*100),rejectedPct:Math.round(c/l*100)}}async getCategoryStats(e){let t=this.stmt("SELECT consent_json FROM consent_records WHERE tenant_id = ? LIMIT 5000").all(e),n={};for(let s of t){let r=JSON.parse(s.consent_json);for(let[o,a]of Object.entries(r))n[o]||(n[o]={granted:0,denied:0,objected:0}),a==="granted"?n[o].granted++:a==="denied"?n[o].denied++:a==="objected"&&n[o].objected++}return n}async getTimeline(e,t=30){return this.stmt("SELECT date(created_at) as date, COUNT(*) as count FROM consent_records WHERE tenant_id = ? AND created_at >= date('now', ?) GROUP BY date(created_at) ORDER BY date ASC").all(e,`-${t} days`).map(s=>({date:s.date,count:s.count}))}async getCountries(e){return this.stmt("SELECT v.country, COUNT(*) as count FROM consent_records cr JOIN visitors v ON v.visitor_id = cr.visitor_id WHERE cr.tenant_id = ? AND v.country IS NOT NULL GROUP BY v.country ORDER BY count DESC LIMIT 50").all(e).map(n=>({country:n.country,count:n.count}))}async getGpcStats(e){let t=this.stmt("SELECT COUNT(*) as n FROM consent_records WHERE tenant_id = ?").get(e),n=this.stmt("SELECT COUNT(*) as n FROM consent_records WHERE tenant_id = ? AND gpc_detected = 1").get(e),s=t?.n??0,r=n?.n??0;return{detected:r,total:s,rate:s>0?Math.round(r/s*100):0}}async getTenants(){return this.stmt("SELECT * FROM tenants ORDER BY name ASC").all().map(t=>({id:t.id,name:t.name,slug:t.slug,createdAt:t.created_at,updatedAt:t.updated_at}))}async createTenant(e){let t=A(),n=new Date().toISOString();this.stmt("INSERT INTO tenants (id,name,slug,created_at,updated_at) VALUES (?,?,?,?,?)").run(t,e.name,e.slug,n,n);let s=this.stmt("SELECT * FROM tenants WHERE id = ?").get(t);if(!s)throw new Error("Failed to create tenant");return{id:s.id,name:s.name,slug:s.slug,createdAt:s.created_at,updatedAt:s.updated_at}}async updateTenant(e,t){let n=new Date().toISOString(),s=["updated_at = ?"],r=[n];t.name!=null&&(s.push("name = ?"),r.push(t.name)),t.slug!=null&&(s.push("slug = ?"),r.push(t.slug)),r.push(e),this.stmt(`UPDATE tenants SET ${s.join(", ")} WHERE id = ?`).run(...r);let o=this.stmt("SELECT * FROM tenants WHERE id = ?").get(e);if(!o)throw new Error(`Tenant ${e} not found`);return{id:o.id,name:o.name,slug:o.slug,createdAt:o.created_at,updatedAt:o.updated_at}}async deleteTenant(e){this.stmt("DELETE FROM tenants WHERE id = ?").run(e)}async getSettings(e){let t=this.stmt("SELECT allowed_origins_json, admin_allowed_origins_json, setup_completed, profiles_seeded, tcf_confirmation_json, gpp_confirmation_json FROM tenant_settings WHERE tenant_id = ?").get(e);return t?{allowedOrigins:JSON.parse(t.allowed_origins_json),adminAllowedOrigins:JSON.parse(t.admin_allowed_origins_json),setupCompleted:!!t.setup_completed,profilesSeeded:!!t.profiles_seeded,tcfConfirmation:t.tcf_confirmation_json?JSON.parse(t.tcf_confirmation_json):void 0,gppConfirmation:t.gpp_confirmation_json?JSON.parse(t.gpp_confirmation_json):void 0}:{}}async updateSettings(e,t){let s={...await this.getSettings(e),...t};return this.stmt(`INSERT INTO tenant_settings (tenant_id, allowed_origins_json, admin_allowed_origins_json, setup_completed, profiles_seeded, tcf_confirmation_json, gpp_confirmation_json, updated_at) VALUES (?,?,?,?,?,?,?,?)
78
+ ON CONFLICT(tenant_id) DO UPDATE SET allowed_origins_json = excluded.allowed_origins_json, admin_allowed_origins_json = excluded.admin_allowed_origins_json, setup_completed = excluded.setup_completed, profiles_seeded = excluded.profiles_seeded, tcf_confirmation_json = excluded.tcf_confirmation_json, gpp_confirmation_json = excluded.gpp_confirmation_json, updated_at = excluded.updated_at`).run(e,JSON.stringify(s.allowedOrigins??[]),JSON.stringify(s.adminAllowedOrigins??[]),s.setupCompleted?1:0,s.profilesSeeded?1:0,s.tcfConfirmation?JSON.stringify(s.tcfConfirmation):null,s.gppConfirmation?JSON.stringify(s.gppConfirmation):null,new Date().toISOString()),this.getSettings(e)}async purgeExpiredConsents(e){let t=new Date(Date.now()-e*864e5).toISOString(),n=this.stmt("SELECT visitor_id FROM consent_records WHERE created_at < ?").all(t);for(let{visitor_id:s}of n)this.stmt("DELETE FROM consent_history WHERE visitor_id = ?").run(s),this.stmt("DELETE FROM consent_records WHERE visitor_id = ?").run(s);return n.length}mapApiKey(e){return{id:e.id,tenantId:e.tenant_id,keyHash:e.key_hash,name:e.name,isActive:e.is_active===1,...e.created_by!==null?{createdBy:e.created_by}:{},...e.expire_by!==null?{expireBy:e.expire_by}:{},createdAt:e.created_at,...e.updated_at!==null?{updatedAt:e.updated_at}:{}}}async createApiKey(e){let t=A(),n=new Date().toISOString();this.stmt("INSERT INTO api_keys (id,tenant_id,key_hash,name,created_by,expire_by,created_at,updated_at) VALUES (?,?,?,?,?,?,?,?)").run(t,e.tenantId,e.keyHash,e.name,e.createdBy??null,e.expireBy??null,n,n);let s=this.stmt("SELECT * FROM api_keys WHERE id = ?").get(t);if(!s)throw new Error("Failed to create API key");return this.mapApiKey(s)}async getApiKeyByHash(e){let t=this.stmt("SELECT * FROM api_keys WHERE key_hash = ? AND is_active = 1").get(e);return t?this.mapApiKey(t):null}async revokeApiKey(e){this.stmt("UPDATE api_keys SET is_active = 0, updated_at = ? WHERE id = ?").run(new Date().toISOString(),e)}async reactivateApiKey(e){let t=new Date().toISOString();this.stmt("UPDATE api_keys SET is_active = 1, updated_at = ?, expire_by = CASE WHEN expire_by <= ? THEN NULL ELSE expire_by END WHERE id = ?").run(t,t,e)}async deleteApiKey(e){this.stmt("DELETE FROM api_keys WHERE id = ?").run(e)}async getApiKeys(e){return this.stmt("SELECT * FROM api_keys WHERE tenant_id = ? ORDER BY created_at DESC").all(e).map(n=>this.mapApiKey(n))}mapConsentTemplate(e){return{id:e.id,tenantId:e.tenant_id,name:e.name,cookies:JSON.parse(e.cookies_json),categories:JSON.parse(e.categories_json),createdAt:e.created_at,updatedAt:e.updated_at}}async createConsentTemplate(e){let t=G(),n=new Date().toISOString();return this.stmt("INSERT INTO consent_templates (id,tenant_id,name,cookies_json,categories_json,created_at,updated_at) VALUES (?,?,?,?,?,?,?)").run(t,e.tenantId,e.name,JSON.stringify(e.cookies),JSON.stringify(e.categories),n,n),this.mapConsentTemplate(this.stmt("SELECT * FROM consent_templates WHERE id = ?").get(t))}async updateConsentTemplate(e,t){let n=new Date().toISOString();t.name!==void 0&&this.stmt("UPDATE consent_templates SET name = ?, updated_at = ? WHERE id = ?").run(t.name,n,e),t.cookies!==void 0&&this.stmt("UPDATE consent_templates SET cookies_json = ?, updated_at = ? WHERE id = ?").run(JSON.stringify(t.cookies),n,e),t.categories!==void 0&&this.stmt("UPDATE consent_templates SET categories_json = ?, updated_at = ? WHERE id = ?").run(JSON.stringify(t.categories),n,e);let s=this.stmt("SELECT * FROM consent_templates WHERE id = ?").get(e);if(!s)throw new Error("Consent template not found");return this.mapConsentTemplate(s)}async deleteConsentTemplate(e){this.stmt("DELETE FROM consent_templates WHERE id = ?").run(e)}async getConsentTemplate(e){let t=this.stmt("SELECT * FROM consent_templates WHERE id = ?").get(e);return t?this.mapConsentTemplate(t):null}async getConsentTemplates(e){return this.stmt("SELECT * FROM consent_templates WHERE tenant_id = ? ORDER BY name ASC").all(e).map(n=>this.mapConsentTemplate(n))}async copyConsentTemplate(e,t){let n=await this.getConsentTemplate(e);if(!n)throw new Error("Consent template not found");return this.createConsentTemplate({tenantId:n.tenantId,name:t,cookies:n.cookies,categories:n.categories})}mapUITemplate(e){let t=JSON.parse(e.settings_json);return{id:e.id,tenantId:e.tenant_id,name:e.name,...t,createdAt:e.created_at,updatedAt:e.updated_at}}async createUITemplate(e){let t=q(),n=new Date().toISOString(),{tenantId:s,name:r,...o}=e;return this.stmt("INSERT INTO ui_templates (id,tenant_id,name,settings_json,created_at,updated_at) VALUES (?,?,?,?,?,?)").run(t,s,r,JSON.stringify(o),n,n),this.mapUITemplate(this.stmt("SELECT * FROM ui_templates WHERE id = ?").get(t))}async updateUITemplate(e,t){let n=new Date().toISOString(),s=this.stmt("SELECT * FROM ui_templates WHERE id = ?").get(e);if(!s)throw new Error("UI template not found");t.name!==void 0&&this.stmt("UPDATE ui_templates SET name = ?, updated_at = ? WHERE id = ?").run(t.name,n,e);let{name:r,...o}=t;if(Object.keys(o).length>0){let c={...JSON.parse(s.settings_json),...o};this.stmt("UPDATE ui_templates SET settings_json = ?, updated_at = ? WHERE id = ?").run(JSON.stringify(c),n,e)}return this.mapUITemplate(this.stmt("SELECT * FROM ui_templates WHERE id = ?").get(e))}async deleteUITemplate(e){this.stmt("DELETE FROM ui_templates WHERE id = ?").run(e)}async getUITemplate(e){let t=this.stmt("SELECT * FROM ui_templates WHERE id = ?").get(e);return t?this.mapUITemplate(t):null}async getUITemplates(e){return this.stmt("SELECT * FROM ui_templates WHERE tenant_id = ? ORDER BY name ASC").all(e).map(n=>this.mapUITemplate(n))}async copyUITemplate(e,t){let n=await this.getUITemplate(e);if(!n)throw new Error("UI template not found");let{tenantId:s,mainBanner:r,gpcBanner:o,preferenceModal:a}=n;return this.createUITemplate({tenantId:s,name:t,mainBanner:r,gpcBanner:o,preferenceModal:a})}mapProfileSummary(e){return{id:e.id,name:e.name,defaultLocale:e.default_locale,complianceGroup:e.compliance_group??null,customComplianceGroup:e.custom_compliance_group??null,isActive:e.is_active===1,consentTemplateName:e.consent_template_name,uiTemplateName:e.ui_template_name,createdAt:e.created_at,updatedAt:e.updated_at}}PROFILE_SUMMARY_SQL=`
82
79
  SELECT
83
80
  p.id, p.name, p.default_locale, p.created_at, p.updated_at,
84
81
  json_extract(p.profile_json, '$.complianceGroup') AS compliance_group,
@@ -91,50 +88,55 @@ ${l.map(d=>` (${Q(c)},${Q(d)})`).join(`,
91
88
  FROM profiles p
92
89
  LEFT JOIN consent_templates ct ON ct.id = json_extract(p.profile_json, '$.consentTemplateId')
93
90
  LEFT JOIN ui_templates ut ON ut.id = json_extract(p.profile_json, '$.uiTemplateId')
94
- `;async listProfilesSummary(e){return this.stmt(`${this.PROFILE_SUMMARY_SQL} WHERE p.tenant_id = ? ORDER BY p.created_at ASC`).all(e).map(n=>this.mapProfileSummary(n))}async findProfilesUsingConsentTemplate(e){return this.stmt(`${this.PROFILE_SUMMARY_SQL} WHERE json_extract(p.profile_json, '$.consentTemplateId') = ? ORDER BY p.name ASC`).all(e).map(n=>this.mapProfileSummary(n))}async findProfilesUsingUITemplate(e){return this.stmt(`${this.PROFILE_SUMMARY_SQL} WHERE json_extract(p.profile_json, '$.uiTemplateId') = ? ORDER BY p.name ASC`).all(e).map(n=>this.mapProfileSummary(n))}async getOptInStats(e,t){let n=["cr.tenant_id = ?"],s=[e];t.profileId&&(n.push("cr.profile_id = ?"),s.push(t.profileId)),t.from&&(n.push("cr.created_at >= ?"),s.push(t.from)),t.to&&(n.push("cr.created_at <= ?"),s.push(t.to)),t.locale&&(n.push("cr.locale = ?"),s.push(t.locale));let r=n.join(" AND "),o=this.stmt(`SELECT cr.locale, cr.consent_json FROM consent_records cr WHERE ${r}`).all(...s),a=0,c=0,l=0,d=0,u={};for(let _ of o){let h=JSON.parse(_.consent_json),w=Object.values(h),R=w.filter(b=>b==="granted").length,I=w.filter(b=>b==="denied").length,T=w.length>0?R/w.length:0;a++;let O=_.locale;u[O]||(u[O]={total:0,granted:0,denied:0,managed:0}),u[O].total++,T>=.9?(c++,u[O].granted++):I/(w.length||1)>=.9?(l++,u[O].denied++):(d++,u[O].managed++)}let p=this.stmt(`SELECT date(cr.created_at) as d, cr.consent_json FROM consent_records cr WHERE ${r} ORDER BY d ASC`).all(...s),g=new Map;for(let _ of p){let h=g.get(_.d)??{total:0,granted:0,denied:0,managed:0},w=JSON.parse(_.consent_json),R=Object.values(w),I=R.length>0?R.filter(O=>O==="granted").length/R.length:0,T=R.length>0?R.filter(O=>O==="denied").length/R.length:0;h.total++,I>=.9?h.granted++:T>=.9?h.denied++:h.managed++,g.set(_.d,h)}let E=Array.from(g.entries()).map(([_,h])=>({date:_,...h}));return{total:a,granted:c,denied:l,managed:d,grantedPct:a>0?Math.round(c/a*1e3)/10:0,deniedPct:a>0?Math.round(l/a*1e3)/10:0,managedPct:a>0?Math.round(d/a*1e3)/10:0,byLocale:u,byDate:E}}};function Re(i,e,t){return i.map(n=>{let s=n.profileJson;return{id:n.id,name:n.name,defaultLocale:n.defaultLocale,complianceGroup:s.complianceGroup??null,customComplianceGroup:s.customComplianceGroup??null,isActive:s.isActive===!0,consentTemplateName:s.consentTemplateId?e.get(s.consentTemplateId)??null:null,uiTemplateName:s.uiTemplateId?t.get(s.uiTemplateId)??null:null,createdAt:n.createdAt,updatedAt:n.updatedAt}})}function Se(i){let e=()=>({total:0,granted:0,denied:0,managed:0}),t=e(),n={},s=new Map;for(let o of i){let a=Object.values(o.consentJson),c=d=>a.length>0?a.filter(u=>u===d).length/a.length:0,l=c("granted")>=.9?"granted":c("denied")>=.9?"denied":"managed";s.has(o.day)||s.set(o.day,e()),n[o.locale]??=e();for(let d of[t,n[o.locale],s.get(o.day)])d.total++,d[l]++}let r=o=>t.total>0?Math.round(o/t.total*1e3)/10:0;return{...t,grantedPct:r(t.granted),deniedPct:r(t.denied),managedPct:r(t.managed),byLocale:n,byDate:Array.from(s,([o,a])=>({date:o,...a})).sort((o,a)=>o.date.localeCompare(a.date))}}function pn(i){return i.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}function fe(i){return{id:i._id,tenantId:i.tenant_id,name:i.name,defaultLocale:i.default_locale,version:i.version,profileJson:i.profile_json,createdAt:i.created_at,updatedAt:i.updated_at}}function at(i){return{id:i._id,tenantId:i.tenant_id,visitorId:i.visitor_id,profileId:i.profile_id,locale:i.locale,consentJson:i.consent_json,gpcDetected:i.gpc_detected,source:i.source,createdAt:i.created_at,updatedAt:i.updated_at,...i.age_verified!=null?{ageVerified:i.age_verified}:{},...i.parental_consent_token!=null?{parentalConsentToken:i.parental_consent_token}:{},...i.tcf_string!=null?{tcfString:i.tcf_string}:{},...i.gpp_string!=null?{gppString:i.gpp_string}:{},...i.signature!=null?{signature:i.signature}:{}}}var No={tenant_id:1,visitor_id:1,profile_id:1,locale:1,gpc_detected:1,source:1,age_verified:1,created_at:1,updated_at:1};function ko(i){return{id:i._id,tenantId:i.tenant_id,visitorId:i.visitor_id,profileId:i.profile_id,locale:i.locale,gpcDetected:i.gpc_detected,source:i.source,createdAt:i.created_at,updatedAt:i.updated_at,...i.age_verified!=null?{ageVerified:i.age_verified}:{}}}function Mo(i){return{id:i._id,tenantId:i.tenant_id,consentRecordId:i.consent_record_id,visitorId:i.visitor_id,oldJson:i.old_json,newJson:i.new_json,action:i.action,createdAt:i.created_at}}function ct(i){return{id:i._id,tenantId:i.tenant_id,visitorId:i.visitor_id,...i.country!=null?{country:i.country}:{},...i.region!=null?{region:i.region}:{},...i.city!=null?{city:i.city}:{},...i.ip_hash!=null?{ipHash:i.ip_hash}:{},...i.user_agent_hash!=null?{userAgentHash:i.user_agent_hash}:{},firstSeen:i.first_seen,lastSeen:i.last_seen}}function Le(i){return{id:i._id,tenantId:i.tenant_id,name:i.name,email:i.email,passwordHash:i.password_hash,isActive:i.is_active,createdAt:i.created_at,updatedAt:i.updated_at}}function lt(i){return{id:i._id,tenantId:i.tenant_id,name:i.name,...i.description!=null?{description:i.description}:{}}}function ks(i){return{id:i._id,name:i.name,...i.description!=null?{description:i.description}:{}}}function Ms(i){return{id:i._id,tenantId:i.tenant_id,action:i.action,resourceType:i.resource_type,...i.user_id!=null?{userId:i.user_id}:{},...i.resource_id!=null?{resourceId:i.resource_id}:{},...i.old_data!=null?{oldData:i.old_data}:{},...i.new_data!=null?{newData:i.new_data}:{},createdAt:i.created_at}}var jo={tenant_id:1,user_id:1,action:1,resource_type:1,resource_id:1,created_at:1};function xo(i){return{id:i._id,tenantId:i.tenant_id,action:i.action,resourceType:i.resource_type,...i.user_id!=null?{userId:i.user_id}:{},...i.resource_id!=null?{resourceId:i.resource_id}:{},createdAt:i.created_at}}var Ne=class{constructor(e){this.config=e}config;client;db;col(e){return this.db.collection(e)}async connect(){let{MongoClient:e}=await import("mongodb"),t=this.config.uri??"mongodb://localhost:27017";this.client=new e(t),await this.client.connect(),this.db=this.client.db(this.config.database??"consenti"),await this.migrate()}async disconnect(){await this.client.close()}async migrate(){await this.createIndexes(),await this.seed()}async createIndexes(){for(let{table:e,cols:t,unique:n}of Us){let s=Object.fromEntries(t.map(r=>[r,1]));await this.col(e).createIndex(s,n?{unique:!0}:void 0)}}async seed(){let{id:e,name:t,slug:n}=K;await this.col("tenants").findOne({_id:e})||await this.col("tenants").insertOne({_id:e,name:t,slug:n});for(let r of G){let o={_id:r.id,name:r.name,description:r.description};await this.col("permissions").updateOne({_id:r.id},{$setOnInsert:o},{upsert:!0})}for(let r of se){let o={_id:r.id,tenant_id:r.tenantId,name:r.name,description:r.description};await this.col("roles").updateOne({_id:r.id},{$setOnInsert:o},{upsert:!0})}let s=this.col("role_permissions");for(let{roleId:r,permissionId:o}of ie)await s.updateOne({role_id:r,permission_id:o},{$setOnInsert:{role_id:r,permission_id:o}},{upsert:!0})}async createProfile(e){let t=new Date().toISOString(),s={_id:V(),tenant_id:e.tenantId,name:e.name,default_locale:e.defaultLocale,version:1,profile_json:e.profileJson,created_at:t,updated_at:t};return await this.col("profiles").insertOne(s),fe(s)}async updateProfile(e,t){let s={updated_at:new Date().toISOString()};t.name!=null&&(s.name=t.name),t.defaultLocale!=null&&(s.default_locale=t.defaultLocale),t.profileJson!=null&&(s.profile_json=t.profileJson),t.version!=null&&(s.version=t.version);let r=await this.col("profiles").findOneAndUpdate({_id:e},{$set:s},{returnDocument:"after"});if(!r)throw new Error(`Profile ${e} not found`);return fe(r)}async deleteProfile(e){await this.col("profiles").deleteOne({_id:e})}async getProfile(e){let t=await this.col("profiles").findOne({_id:e});return t?fe(t):null}async getProfiles(e){return(await this.col("profiles").find({tenant_id:e}).toArray()).map(fe)}async findActiveProfileByComplianceGroup(e,t){return(await this.getProfiles(e)).find(s=>(s.profileJson.complianceGroup===t||s.profileJson.customComplianceGroup===t)&&s.profileJson.isActive)??null}async createConsent(e){let t=new Date().toISOString(),n={_id:W(),tenant_id:e.tenantId,visitor_id:e.visitorId,profile_id:e.profileId,locale:e.locale,consent_json:e.consentJson,gpc_detected:e.gpcDetected,source:e.source,created_at:t,updated_at:t,...e.ageVerified!=null?{age_verified:e.ageVerified}:{},...e.parentalConsentToken!=null?{parental_consent_token:e.parentalConsentToken}:{},...e.tcfString!=null?{tcf_string:e.tcfString}:{},...e.gppString!=null?{gpp_string:e.gppString}:{},...e.signature!=null?{signature:e.signature}:{}};return await this.col("consent_records").insertOne(n),await this.writeHistory(n._id,e.tenantId,e.visitorId,null,e.consentJson,"created"),at(n)}async updateConsent(e,t){let n=await this.getConsent(e);if(!n)throw new Error(`Consent for visitor ${e} not found`);let r={updated_at:new Date().toISOString(),consent_json:t.consentJson};t.locale!=null&&(r.locale=t.locale),t.gpcDetected!=null&&(r.gpc_detected=t.gpcDetected),t.signature!=null&&(r.signature=t.signature);let o=await this.col("consent_records").findOneAndUpdate({visitor_id:e},{$set:r},{returnDocument:"after"});if(!o)throw new Error(`Consent for visitor ${e} not found`);return await this.writeHistory(o._id,o.tenant_id,e,n.consentJson,t.consentJson,"updated"),at(o)}async deleteConsent(e){await this.col("consent_history").deleteMany({visitor_id:e}),await this.col("consent_records").deleteOne({visitor_id:e})}async getConsent(e){let t=await this.col("consent_records").findOne({visitor_id:e});return t?at(t):null}async getConsents(e){let t=this.buildConsentFilter(e),n=e.page??1,s=e.limit??50,[r,o]=await Promise.all([this.col("consent_records").find(t,{projection:No}).sort({created_at:-1}).skip((n-1)*s).limit(s).toArray(),this.col("consent_records").countDocuments(t)]);return{items:r.map(ko),total:o,page:n,limit:s}}async*streamConsents(e){let t=this.buildConsentFilter(e),n=this.col("consent_records").find(t).sort({created_at:1});for await(let s of n)yield at(s)}buildConsentFilter(e){let t={tenant_id:e.tenantId};if(e.profileId!=null&&(t.profile_id=e.profileId),e.from!=null||e.to!=null){let n={};e.from!=null&&(n.$gte=e.from),e.to!=null&&(n.$lte=e.to),t.created_at=n}if(e.q){let n={$regex:"^"+pn(e.q),$options:"i"};t.$or=[{visitor_id:n},{profile_id:n},{locale:n},{source:n}]}return t}async writeHistory(e,t,n,s,r,o){let a={_id:A(),tenant_id:t,consent_record_id:e,visitor_id:n,old_json:s,new_json:r,action:o,created_at:new Date().toISOString()};await this.col("consent_history").insertOne(a)}async getConsentHistory(e){return(await this.col("consent_history").find({visitor_id:e}).sort({created_at:1}).toArray()).map(Mo)}async createNoticeShown(e){let t={_id:A(),tenant_id:e.tenantId,visitor_id:e.visitorId,profile_id:e.profileId,locale:e.locale,created_at:new Date().toISOString()};return await this.col("notice_shown").insertOne(t),{id:t._id,tenantId:t.tenant_id,visitorId:t.visitor_id,profileId:t.profile_id,locale:t.locale,createdAt:t.created_at}}async getNoticeShownForVisitor(e){return(await this.col("notice_shown").find({visitor_id:e}).sort({created_at:-1}).toArray()).map(n=>({id:n._id,tenantId:n.tenant_id,visitorId:n.visitor_id,profileId:n.profile_id,locale:n.locale,createdAt:n.created_at}))}async createVisitor(e){let t=new Date().toISOString(),n={_id:H(),tenant_id:e.tenantId,visitor_id:e.visitorId,first_seen:t,last_seen:t,...e.country!=null?{country:e.country}:{},...e.region!=null?{region:e.region}:{},...e.city!=null?{city:e.city}:{},...e.ipHash!=null?{ip_hash:e.ipHash}:{},...e.userAgentHash!=null?{user_agent_hash:e.userAgentHash}:{}};return await this.col("visitors").insertOne(n),ct(n)}async updateVisitor(e,t){let n={last_seen:t.lastSeen??new Date().toISOString()};t.country!=null&&(n.country=t.country),t.region!=null&&(n.region=t.region),t.city!=null&&(n.city=t.city);let s=await this.col("visitors").findOneAndUpdate({visitor_id:e},{$set:n},{returnDocument:"after"});if(!s)throw new Error(`Visitor ${e} not found`);return ct(s)}async deleteVisitor(e){await this.col("visitors").deleteOne({visitor_id:e})}async getVisitor(e){let t=await this.col("visitors").findOne({visitor_id:e});return t?ct(t):null}async getVisitors(e){let t={tenant_id:e.tenantId};if(e.from!=null||e.to!=null){let a={};e.from!=null&&(a.$gte=e.from),e.to!=null&&(a.$lte=e.to),t.first_seen=a}if(e.q){let a={$regex:"^"+pn(e.q),$options:"i"};t.$or=[{visitor_id:a},{country:a}]}let n=e.page??1,s=e.limit??50,[r,o]=await Promise.all([this.col("visitors").find(t).sort({first_seen:-1}).skip((n-1)*s).limit(s).toArray(),this.col("visitors").countDocuments(t)]);return{items:r.map(ct),total:o,page:n,limit:s}}async getOverviewStats(e){let[t,n,s]=await Promise.all([this.col("consent_records").countDocuments({tenant_id:e}),this.col("consent_records").countDocuments({tenant_id:e,gpc_detected:!0}),this.col("visitors").countDocuments({tenant_id:e})]),r=[{$match:{tenant_id:e}},{$addFields:{values:{$objectToArray:"$consent_json"}}},{$unwind:"$values"},{$group:{_id:"$values.v",count:{$sum:1}}}],o=await this.col("consent_records").aggregate(r).toArray(),a=0,c=0,l=0;for(let d of o)l+=d.count,d._id==="granted"&&(a=d.count),d._id==="denied"&&(c=d.count);return{totalConsents:t,acceptedPct:l>0?Math.round(a/l*100):0,rejectedPct:l>0?Math.round(c/l*100):0,totalVisitors:s,gpcDetectedCount:n}}async getCategoryStats(e){let t=[{$match:{tenant_id:e}},{$addFields:{pairs:{$objectToArray:"$consent_json"}}},{$unwind:"$pairs"},{$group:{_id:{cookie:"$pairs.k",status:"$pairs.v"},count:{$sum:1}}}],n=await this.col("consent_records").aggregate(t).toArray(),s={};for(let r of n){let o=r._id.cookie;s[o]||(s[o]={granted:0,denied:0,objected:0});let a=s[o];a&&(r._id.status==="granted"&&(a.granted+=r.count),r._id.status==="denied"&&(a.denied+=r.count),r._id.status==="objected"&&(a.objected+=r.count))}return s}async getTimeline(e,t=30){let n=new Date(Date.now()-t*864e5).toISOString(),s=[{$match:{tenant_id:e,created_at:{$gte:n}}},{$group:{_id:{$substr:["$created_at",0,10]},count:{$sum:1}}},{$sort:{_id:1}}];return(await this.col("consent_records").aggregate(s).toArray()).map(o=>({date:o._id,count:o.count}))}async createUser(e){let t=new Date().toISOString(),n={_id:A(),tenant_id:e.tenantId,name:e.name,email:e.email,password_hash:e.passwordHash,is_active:!0,created_at:t,updated_at:t};return await this.col("users").insertOne(n),Le(n)}async updateUser(e,t){let n={updated_at:new Date().toISOString()};t.name!=null&&(n.name=t.name),t.email!=null&&(n.email=t.email),t.passwordHash!=null&&(n.password_hash=t.passwordHash),t.isActive!=null&&(n.is_active=t.isActive);let s=await this.col("users").findOneAndUpdate({_id:e},{$set:n},{returnDocument:"after"});if(!s)throw new Error(`User ${e} not found`);return Le(s)}async deleteUser(e){await this.col("user_roles").deleteMany({user_id:e}),await this.col("users").deleteOne({_id:e})}async getUserById(e){let t=await this.col("users").findOne({_id:e});return t?Le(t):null}async getUserByEmail(e){let t=await this.col("users").findOne({email:e});return t?Le(t):null}async getUsers(e){return(await this.col("users").find({tenant_id:e}).toArray()).map(Le)}async countUsers(e){return this.col("users").countDocuments(e?{tenant_id:e}:{})}async getRoles(e){return(await this.col("roles").find({tenant_id:e}).toArray()).map(lt)}async createRole(e){let t={_id:A(),tenant_id:e.tenantId,name:e.name,...e.description!=null?{description:e.description}:{}};return await this.col("roles").insertOne(t),lt(t)}async updateRole(e,t){let n={};t.name!=null&&(n.name=t.name),t.description!=null&&(n.description=t.description);let s=await this.col("roles").findOneAndUpdate({_id:e},{$set:n},{returnDocument:"after"});if(!s)throw new Error(`Role ${e} not found`);return lt(s)}async deleteRole(e){await this.col("role_permissions").deleteMany({role_id:e}),await this.col("user_roles").deleteMany({role_id:e}),await this.col("roles").deleteOne({_id:e})}async getAllPermissions(){return(await this.col("permissions").find({}).toArray()).map(ks)}async getPermissionsForRole(e){let n=(await this.col("role_permissions").find({role_id:e}).toArray()).map(r=>r.permission_id);return n.length===0?[]:(await this.col("permissions").find({_id:{$in:n}}).toArray()).map(ks)}async getUserRoles(e,t){let n=t?{user_id:e,tenant_id:t}:{user_id:e},r=(await this.col("user_roles").find(n).toArray()).map(a=>a.role_id);return r.length===0?[]:(await this.col("roles").find({_id:{$in:r}}).toArray()).map(lt)}async assignRole(e,t,n="default"){await this.col("user_roles").updateOne({user_id:e,role_id:t,tenant_id:n},{$setOnInsert:{user_id:e,role_id:t,tenant_id:n}},{upsert:!0})}async revokeRole(e,t,n){n?await this.col("user_roles").deleteOne({user_id:e,role_id:t,tenant_id:n}):await this.col("user_roles").deleteMany({user_id:e,role_id:t})}async assignPermissionToRole(e,t){await this.col("role_permissions").updateOne({role_id:e,permission_id:t},{$setOnInsert:{role_id:e,permission_id:t}},{upsert:!0})}async revokePermissionFromRole(e,t){await this.col("role_permissions").deleteOne({role_id:e,permission_id:t})}async createLog(e){let t={_id:A(),tenant_id:e.tenantId,action:e.action,resource_type:e.resourceType,...e.userId!=null?{user_id:e.userId}:{},...e.resourceId!=null?{resource_id:e.resourceId}:{},...e.oldData!=null?{old_data:e.oldData}:{},...e.newData!=null?{new_data:e.newData}:{},created_at:new Date().toISOString()};await this.col("audit_logs").insertOne(t)}async getLogs(e){let t=this.buildAuditFilter(e),n=e.page??1,s=e.limit??50,[r,o]=await Promise.all([this.col("audit_logs").find(t,{projection:jo}).sort({created_at:-1}).skip((n-1)*s).limit(s).toArray(),this.col("audit_logs").countDocuments(t)]);return{items:r.map(xo),total:o,page:n,limit:s}}async getAuditLogById(e){let t=await this.col("audit_logs").findOne({_id:e});return t?Ms(t):null}async*streamAuditLogs(e){let t=this.buildAuditFilter(e),n=this.col("audit_logs").find(t).sort({created_at:1});for await(let s of n)yield Ms(s)}buildAuditFilter(e){let t={tenant_id:e.tenantId};if(e.action!=null&&(t.action=e.action),e.resourceType!=null&&(t.resource_type=e.resourceType),e.from!=null||e.to!=null){let n={};e.from!=null&&(n.$gte=e.from),e.to!=null&&(n.$lte=e.to),t.created_at=n}if(e.q){let n={$regex:"^"+pn(e.q),$options:"i"};t.$or=[{action:n},{resource_type:n},{resource_id:n},{user_id:n}]}return t}async getCountries(e){let t=[{$match:{tenant_id:e}},{$lookup:{from:"visitors",localField:"visitor_id",foreignField:"visitor_id",as:"v"}},{$unwind:"$v"},{$match:{"v.country":{$ne:null}}},{$group:{_id:"$v.country",count:{$sum:1}}},{$sort:{count:-1}},{$limit:50}];return(await this.col("consent_records").aggregate(t).toArray()).map(s=>({country:s._id,count:s.count}))}async getGpcStats(e){let t=await this.col("consent_records").countDocuments({tenant_id:e}),n=await this.col("consent_records").countDocuments({tenant_id:e,gpc_detected:!0});return{detected:n,total:t,rate:t>0?Math.round(n/t*100):0}}async getTenants(){return(await this.col("tenants").find({}).toArray()).map(t=>({id:t._id,name:t.name,slug:t.slug,createdAt:t.created_at,updatedAt:t.updated_at}))}mapApiKey(e){return{id:e._id,tenantId:e.tenant_id,keyHash:e.key_hash,name:e.name,isActive:e.is_active,...e.created_by!==void 0?{createdBy:e.created_by}:{},...e.expire_by!==void 0?{expireBy:e.expire_by}:{},createdAt:e.created_at,...e.updated_at!==void 0?{updatedAt:e.updated_at}:{}}}async createApiKey(e){let t=new Date().toISOString(),n={_id:A(),tenant_id:e.tenantId,key_hash:e.keyHash,name:e.name,is_active:!0,...e.createdBy!==void 0?{created_by:e.createdBy}:{},...e.expireBy!==void 0?{expire_by:e.expireBy}:{},created_at:t,updated_at:t};return await this.col("api_keys").insertOne(n),this.mapApiKey(n)}async getApiKeyByHash(e){let t=await this.col("api_keys").findOne({key_hash:e,is_active:!0});return t?this.mapApiKey(t):null}async revokeApiKey(e){await this.col("api_keys").updateOne({_id:e},{$set:{is_active:!1,updated_at:new Date().toISOString()}})}async reactivateApiKey(e){let t=await this.col("api_keys").findOne({_id:e});if(!t)return;let n=t.expire_by&&new Date(t.expire_by)<=new Date;await this.col("api_keys").updateOne({_id:e},{$set:{is_active:!0,updated_at:new Date().toISOString()},...n?{$unset:{expire_by:""}}:{}})}async deleteApiKey(e){await this.col("api_keys").deleteOne({_id:e})}async getApiKeys(e){return(await this.col("api_keys").find({tenant_id:e}).sort({created_at:-1}).toArray()).map(n=>this.mapApiKey(n))}async createTenant(e){let t=A(),n=new Date().toISOString(),s={_id:t,name:e.name,slug:e.slug,created_at:n,updated_at:n};return await this.col("tenants").insertOne(s),{id:t,name:e.name,slug:e.slug,createdAt:n,updatedAt:n}}async updateTenant(e,t){let s={updated_at:new Date().toISOString()};t.name!=null&&(s.name=t.name),t.slug!=null&&(s.slug=t.slug),await this.col("tenants").updateOne({_id:e},{$set:s});let r=await this.col("tenants").findOne({_id:e});if(!r)throw new Error(`Tenant ${e} not found`);return{id:r._id,name:r.name,slug:r.slug,createdAt:r.created_at,updatedAt:r.updated_at}}async deleteTenant(e){await this.col("tenants").deleteOne({_id:e})}async getSettings(e){let t=await this.col("tenant_settings").findOne({_id:e});return{...t?.allowed_origins!==void 0?{allowedOrigins:t.allowed_origins}:{},...t?.admin_allowed_origins!==void 0?{adminAllowedOrigins:t.admin_allowed_origins}:{},...t?.setup_completed!==void 0?{setupCompleted:t.setup_completed}:{},...t?.profiles_seeded!==void 0?{profilesSeeded:t.profiles_seeded}:{},...t?.tcf_confirmation!==void 0?{tcfConfirmation:t.tcf_confirmation}:{},...t?.gpp_confirmation!==void 0?{gppConfirmation:t.gpp_confirmation}:{}}}async updateSettings(e,t){let n={updated_at:new Date().toISOString()};return t.allowedOrigins!==void 0&&(n.allowed_origins=t.allowedOrigins),t.adminAllowedOrigins!==void 0&&(n.admin_allowed_origins=t.adminAllowedOrigins),t.setupCompleted!==void 0&&(n.setup_completed=t.setupCompleted),t.profilesSeeded!==void 0&&(n.profiles_seeded=t.profilesSeeded),t.tcfConfirmation!==void 0&&(n.tcf_confirmation=t.tcfConfirmation),t.gppConfirmation!==void 0&&(n.gpp_confirmation=t.gppConfirmation),await this.col("tenant_settings").updateOne({_id:e},{$set:n},{upsert:!0}),this.getSettings(e)}async purgeExpiredConsents(e){let t=new Date(Date.now()-e*864e5).toISOString(),n=await this.col("consent_records").find({created_at:{$lt:t}}).toArray();if(n.length===0)return 0;let s=n.map(r=>r.visitor_id);return await this.col("consent_history").deleteMany({visitor_id:{$in:s}}),await this.col("consent_records").deleteMany({visitor_id:{$in:s}}),n.length}mapConsentTemplate(e){return{id:e._id,tenantId:e.tenant_id,name:e.name,cookies:e.cookies_json,categories:e.categories_json,createdAt:e.created_at,updatedAt:e.updated_at}}async createConsentTemplate(e){let t=new Date().toISOString(),n={_id:J(),tenant_id:e.tenantId,name:e.name,cookies_json:e.cookies,categories_json:e.categories,created_at:t,updated_at:t};return await this.col("consent_templates").insertOne(n),this.mapConsentTemplate(n)}async updateConsentTemplate(e,t){let n={updated_at:new Date().toISOString()};t.name!==void 0&&(n.name=t.name),t.cookies!==void 0&&(n.cookies_json=t.cookies),t.categories!==void 0&&(n.categories_json=t.categories);let s=await this.col("consent_templates").findOneAndUpdate({_id:e},{$set:n},{returnDocument:"after"});if(!s)throw new Error("Consent template not found");return this.mapConsentTemplate(s)}async deleteConsentTemplate(e){await this.col("consent_templates").deleteOne({_id:e})}async getConsentTemplate(e){let t=await this.col("consent_templates").findOne({_id:e});return t?this.mapConsentTemplate(t):null}async getConsentTemplates(e){return(await this.col("consent_templates").find({tenant_id:e}).sort({name:1}).toArray()).map(n=>this.mapConsentTemplate(n))}async copyConsentTemplate(e,t){let n=await this.getConsentTemplate(e);if(!n)throw new Error("Consent template not found");return this.createConsentTemplate({tenantId:n.tenantId,name:t,cookies:n.cookies,categories:n.categories})}mapUITemplate(e){return{id:e._id,tenantId:e.tenant_id,name:e.name,...e.settings_json,createdAt:e.created_at,updatedAt:e.updated_at}}async createUITemplate(e){let t=new Date().toISOString(),{tenantId:n,name:s,...r}=e,o={_id:q(),tenant_id:n,name:s,settings_json:r,created_at:t,updated_at:t};return await this.col("ui_templates").insertOne(o),this.mapUITemplate(o)}async updateUITemplate(e,t){let n=await this.col("ui_templates").findOne({_id:e});if(!n)throw new Error("UI template not found");let{name:s,...r}=t,o={updated_at:new Date().toISOString()};s!==void 0&&(o.name=s),Object.keys(r).length>0&&(o.settings_json={...n.settings_json,...r});let a=await this.col("ui_templates").findOneAndUpdate({_id:e},{$set:o},{returnDocument:"after"});if(!a)throw new Error("UI template not found");return this.mapUITemplate(a)}async deleteUITemplate(e){await this.col("ui_templates").deleteOne({_id:e})}async getUITemplate(e){let t=await this.col("ui_templates").findOne({_id:e});return t?this.mapUITemplate(t):null}async getUITemplates(e){return(await this.col("ui_templates").find({tenant_id:e}).sort({name:1}).toArray()).map(n=>this.mapUITemplate(n))}async copyUITemplate(e,t){let n=await this.getUITemplate(e);if(!n)throw new Error("UI template not found");let{tenantId:s,mainBanner:r,gpcBanner:o,preferenceModal:a}=n;return this.createUITemplate({tenantId:s,name:t,mainBanner:r,gpcBanner:o,preferenceModal:a})}async toProfileSummaries(e){if(e.length===0)return[];let t=[...new Set(e.map(r=>r.tenantId))],[n,s]=await Promise.all([Promise.all(t.map(r=>this.getConsentTemplates(r))),Promise.all(t.map(r=>this.getUITemplates(r)))]);return Re(e,new Map(n.flat().map(r=>[r.id,r.name])),new Map(s.flat().map(r=>[r.id,r.name])))}async listProfilesSummary(e){let t=await this.col("profiles").find({tenant_id:e}).sort({created_at:1}).toArray();return this.toProfileSummaries(t.map(fe))}async findProfilesUsingConsentTemplate(e){let t=await this.col("profiles").find({"profile_json.consentTemplateId":e}).sort({name:1}).toArray();return this.toProfileSummaries(t.map(fe))}async findProfilesUsingUITemplate(e){let t=await this.col("profiles").find({"profile_json.uiTemplateId":e}).sort({name:1}).toArray();return this.toProfileSummaries(t.map(fe))}async getOptInStats(e,t){let n={tenant_id:e};t.profileId&&(n.profile_id=t.profileId),t.locale&&(n.locale=t.locale),(t.from||t.to)&&(n.created_at={...t.from?{$gte:t.from}:{},...t.to?{$lte:t.to}:{}});let s=await this.col("consent_records").find(n,{projection:{locale:1,consent_json:1,created_at:1}}).sort({created_at:1}).toArray();return Se(s.map(r=>({locale:r.locale,day:r.created_at.slice(0,10),consentJson:r.consent_json})))}};function _e(i){return{id:i.id,tenantId:i.tenant_id,name:i.name,defaultLocale:i.default_locale,version:i.version,profileJson:JSON.parse(i.profile_json),createdAt:i.created_at,updatedAt:i.updated_at}}function dt(i){return{id:i.id,tenantId:i.tenant_id,visitorId:i.visitor_id,profileId:i.profile_id,locale:i.locale,consentJson:JSON.parse(i.consent_json),gpcDetected:i.gpc_detected===1,source:i.source,createdAt:i.created_at,updatedAt:i.updated_at,...i.age_verified!=null?{ageVerified:i.age_verified===1}:{},...i.parental_consent_token!=null?{parentalConsentToken:i.parental_consent_token}:{},...i.tcf_string!=null?{tcfString:i.tcf_string}:{},...i.gpp_string!=null?{gppString:i.gpp_string}:{},...i.signature!=null?{signature:i.signature}:{}}}var $o="id, tenant_id, visitor_id, profile_id, locale, gpc_detected, source, age_verified, created_at, updated_at";function Fo(i){return{id:i.id,tenantId:i.tenant_id,visitorId:i.visitor_id,profileId:i.profile_id,locale:i.locale,gpcDetected:i.gpc_detected===1,source:i.source,createdAt:i.created_at,updatedAt:i.updated_at,...i.age_verified!=null?{ageVerified:i.age_verified===1}:{}}}function Ho(i){return{id:i.id,tenantId:i.tenant_id,consentRecordId:i.consent_record_id,visitorId:i.visitor_id,oldJson:i.old_json?JSON.parse(i.old_json):null,newJson:JSON.parse(i.new_json),action:i.action,createdAt:i.created_at}}function ut(i){return{id:i.id,tenantId:i.tenant_id,visitorId:i.visitor_id,...i.country!=null?{country:i.country}:{},...i.region!=null?{region:i.region}:{},...i.city!=null?{city:i.city}:{},...i.ip_hash!=null?{ipHash:i.ip_hash}:{},...i.user_agent_hash!=null?{userAgentHash:i.user_agent_hash}:{},firstSeen:i.first_seen,lastSeen:i.last_seen}}function ke(i){return{id:i.id,tenantId:i.tenant_id,name:i.name,email:i.email,passwordHash:i.password_hash,isActive:i.is_active===1,createdAt:i.created_at,updatedAt:i.updated_at,totpEnabled:i.totp_enabled===1,...i.totp_secret!=null?{totpSecret:i.totp_secret}:{}}}function pt(i){return{id:i.id,tenantId:i.tenant_id,name:i.name,...i.description!=null?{description:i.description}:{}}}function js(i){return{id:i.id,name:i.name,...i.description!=null?{description:i.description}:{}}}function xs(i){return{id:i.id,tenantId:i.tenant_id,action:i.action,resourceType:i.resource_type,...i.user_id!=null?{userId:i.user_id}:{},...i.resource_id!=null?{resourceId:i.resource_id}:{},...i.old_data!=null?{oldData:JSON.parse(i.old_data)}:{},...i.new_data!=null?{newData:JSON.parse(i.new_data)}:{},createdAt:i.created_at}}var Bo="id, tenant_id, user_id, action, resource_type, resource_id, created_at";function Vo(i){return{id:i.id,tenantId:i.tenant_id,action:i.action,resourceType:i.resource_type,...i.user_id!=null?{userId:i.user_id}:{},...i.resource_id!=null?{resourceId:i.resource_id}:{},createdAt:i.created_at}}var Me=class{constructor(e){this.config=e}config;pool;async q(e,t=[]){let n=this.config.statementTimeoutMs,[s]=n?await this.pool.query({sql:e,timeout:n},t):await this.pool.query(e,t);return s}async exec(e,t=[]){let n=this.config.statementTimeoutMs,[s]=n?await this.pool.query({sql:e,timeout:n},t):await this.pool.query(e,t);return s}async connect(){let e=await import("mysql2/promise"),t=this.config;this.pool=e.createPool({...t.uri?{uri:t.uri}:{host:t.host??"localhost",port:t.port??3306,user:t.user??"root",password:t.password??"",database:t.database??"consenti"},connectionLimit:t.poolMax??10}),await this.migrate()}async disconnect(){await this.pool.end()}async migrate(){let e=await this.pool.getConnection();try{let[t]=await e.query("SHOW TABLES LIKE ?",["schema_version"]);if(t.length===0){for(let n of Ns.split(";").map(s=>s.trim()).filter(Boolean))await e.query(n);await this.seed(e),await e.query("INSERT IGNORE INTO schema_version (version) VALUES (1)")}}finally{e.release()}}async seed(e){await e.query("INSERT IGNORE INTO tenants (id, name, slug) VALUES (?,?,?)",[K.id,K.name,K.slug]);for(let t of G)await e.query("INSERT IGNORE INTO permissions (id, name, description) VALUES (?,?,?)",[t.id,t.name,t.description]);for(let t of se)await e.query("INSERT IGNORE INTO roles (id, tenant_id, name, description) VALUES (?,?,?,?)",[t.id,t.tenantId,t.name,t.description]);for(let{roleId:t,permissionId:n}of ie)await e.query("INSERT IGNORE INTO role_permissions (role_id, permission_id) VALUES (?,?)",[t,n])}async createProfile(e){let t=V();await this.exec("INSERT INTO profiles (id, tenant_id, name, default_locale, version, profile_json) VALUES (?,?,?,?,?,?)",[t,e.tenantId,e.name,e.defaultLocale,1,JSON.stringify(e.profileJson)]);let s=(await this.q("SELECT * FROM profiles WHERE id=?",[t]))[0];if(!s)throw new Error("Profile creation failed");return _e(s)}async updateProfile(e,t){let n=[],s=[];t.name!=null&&(n.push("name=?"),s.push(t.name)),t.defaultLocale!=null&&(n.push("default_locale=?"),s.push(t.defaultLocale)),t.profileJson!=null&&(n.push("profile_json=?"),s.push(JSON.stringify(t.profileJson))),t.version!=null&&(n.push("version=?"),s.push(t.version)),n.length>0&&(s.push(e),await this.exec(`UPDATE profiles SET ${n.join(",")} WHERE id=?`,s));let o=(await this.q("SELECT * FROM profiles WHERE id=?",[e]))[0];if(!o)throw new Error(`Profile ${e} not found`);return _e(o)}async deleteProfile(e){await this.exec("DELETE FROM profiles WHERE id=?",[e])}async getProfile(e){let t=await this.q("SELECT * FROM profiles WHERE id=?",[e]);return t[0]?_e(t[0]):null}async getProfiles(e){return(await this.q("SELECT * FROM profiles WHERE tenant_id=? ORDER BY created_at DESC",[e])).map(_e)}async findActiveProfileByComplianceGroup(e,t){return(await this.getProfiles(e)).find(s=>(s.profileJson.complianceGroup===t||s.profileJson.customComplianceGroup===t)&&s.profileJson.isActive)??null}async createConsent(e){let t=W();await this.exec("INSERT INTO consent_records (id, tenant_id, visitor_id, profile_id, locale, consent_json, gpc_detected, source, age_verified, parental_consent_token, tcf_string, gpp_string, signature) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)",[t,e.tenantId,e.visitorId,e.profileId,e.locale,JSON.stringify(e.consentJson),e.gpcDetected?1:0,e.source,e.ageVerified?1:0,e.parentalConsentToken??null,e.tcfString??null,e.gppString??null,e.signature??null]);let s=(await this.q("SELECT * FROM consent_records WHERE id=?",[t]))[0];if(!s)throw new Error("Consent creation failed");return await this.writeHistory(t,e.tenantId,e.visitorId,null,e.consentJson,"created"),dt(s)}async updateConsent(e,t){let n=await this.getConsent(e);if(!n)throw new Error(`Consent for visitor ${e} not found`);let s=["consent_json=?"],r=[JSON.stringify(t.consentJson)];t.locale!=null&&(s.push("locale=?"),r.push(t.locale)),t.gpcDetected!=null&&(s.push("gpc_detected=?"),r.push(t.gpcDetected?1:0)),t.signature!=null&&(s.push("signature=?"),r.push(t.signature)),r.push(e),await this.exec(`UPDATE consent_records SET ${s.join(",")} WHERE visitor_id=?`,r);let a=(await this.q("SELECT * FROM consent_records WHERE visitor_id=?",[e]))[0];if(!a)throw new Error(`Consent for visitor ${e} not found`);return await this.writeHistory(a.id,a.tenant_id,e,n.consentJson,t.consentJson,"updated"),dt(a)}async deleteConsent(e){await this.exec("DELETE FROM consent_history WHERE visitor_id=?",[e]),await this.exec("DELETE FROM consent_records WHERE visitor_id=?",[e])}async getConsent(e){let t=await this.q("SELECT * FROM consent_records WHERE visitor_id=?",[e]);return t[0]?dt(t[0]):null}async getConsents(e){let{where:t,params:n}=this.buildConsentWhere(e),s=e.page??1,r=e.limit??50,[o,a]=await Promise.all([this.q(`SELECT ${$o} ${t} ORDER BY created_at DESC LIMIT ? OFFSET ?`,[...n,r,(s-1)*r]),this.q(`SELECT COUNT(*) AS total ${t}`,n)]);return{items:o.map(Fo),total:a[0]?.total??0,page:s,limit:r}}async*streamConsents(e){let{where:t,params:n}=this.buildConsentWhere(e),s=await this.q(`SELECT * ${t} ORDER BY created_at ASC`,n);for(let r of s)yield dt(r)}buildConsentWhere(e){let t="FROM consent_records WHERE tenant_id=?",n=[e.tenantId];if(e.profileId!=null&&(t+=" AND profile_id=?",n.push(e.profileId)),e.from!=null&&(t+=" AND created_at>=?",n.push(e.from)),e.to!=null&&(t+=" AND created_at<=?",n.push(e.to)),e.q){t+=" AND (visitor_id LIKE ? OR profile_id LIKE ? OR locale LIKE ? OR source LIKE ?) ESCAPE '\\\\'";let s=j(e.q);n.push(s,s,s,s)}return{where:t,params:n}}async writeHistory(e,t,n,s,r,o){await this.exec("INSERT INTO consent_history (id, tenant_id, consent_record_id, visitor_id, old_json, new_json, action) VALUES (?,?,?,?,?,?,?)",[A(),t,e,n,s?JSON.stringify(s):null,JSON.stringify(r),o])}async getConsentHistory(e){return(await this.q("SELECT * FROM consent_history WHERE visitor_id=? ORDER BY created_at ASC",[e])).map(Ho)}async createNoticeShown(e){let t=A();await this.exec("INSERT INTO notice_shown (id, tenant_id, visitor_id, profile_id, locale) VALUES (?,?,?,?,?)",[t,e.tenantId,e.visitorId,e.profileId,e.locale]);let s=(await this.q("SELECT * FROM notice_shown WHERE id=?",[t]))[0];return{id:s.id,tenantId:s.tenant_id,visitorId:s.visitor_id,profileId:s.profile_id,locale:s.locale,createdAt:s.created_at}}async getNoticeShownForVisitor(e){return(await this.q("SELECT * FROM notice_shown WHERE visitor_id=? ORDER BY created_at DESC",[e])).map(n=>({id:n.id,tenantId:n.tenant_id,visitorId:n.visitor_id,profileId:n.profile_id,locale:n.locale,createdAt:n.created_at}))}async createVisitor(e){let t=H();await this.exec("INSERT INTO visitors (id, tenant_id, visitor_id, country, region, city, ip_hash, user_agent_hash) VALUES (?,?,?,?,?,?,?,?)",[t,e.tenantId,e.visitorId,e.country??null,e.region??null,e.city??null,e.ipHash??null,e.userAgentHash??null]);let s=(await this.q("SELECT * FROM visitors WHERE id=?",[t]))[0];if(!s)throw new Error("Visitor creation failed");return ut(s)}async updateVisitor(e,t){let n=["last_seen=NOW()"],s=[];t.country!=null&&(n.push("country=?"),s.push(t.country)),t.region!=null&&(n.push("region=?"),s.push(t.region)),t.city!=null&&(n.push("city=?"),s.push(t.city)),s.push(e),await this.exec(`UPDATE visitors SET ${n.join(",")} WHERE visitor_id=?`,s);let o=(await this.q("SELECT * FROM visitors WHERE visitor_id=?",[e]))[0];if(!o)throw new Error(`Visitor ${e} not found`);return ut(o)}async deleteVisitor(e){await this.exec("DELETE FROM visitors WHERE visitor_id=?",[e])}async getVisitor(e){let t=await this.q("SELECT * FROM visitors WHERE visitor_id=?",[e]);return t[0]?ut(t[0]):null}async getVisitors(e){let t="FROM visitors WHERE tenant_id=?",n=[e.tenantId];if(e.from!=null&&(t+=" AND first_seen>=?",n.push(e.from)),e.to!=null&&(t+=" AND first_seen<=?",n.push(e.to)),e.q){t+=" AND (visitor_id LIKE ? OR country LIKE ?) ESCAPE '\\\\'";let c=j(e.q);n.push(c,c)}let s=e.page??1,r=e.limit??50,[o,a]=await Promise.all([this.q(`SELECT * ${t} ORDER BY first_seen DESC LIMIT ? OFFSET ?`,[...n,r,(s-1)*r]),this.q(`SELECT COUNT(*) AS total ${t}`,n)]);return{items:o.map(ut),total:a[0]?.total??0,page:s,limit:r}}async getOverviewStats(e){let[[t],[n],[s]]=await Promise.all([this.q("SELECT COUNT(*) AS total FROM consent_records WHERE tenant_id=?",[e]),this.q("SELECT COUNT(*) AS total FROM consent_records WHERE tenant_id=? AND gpc_detected=1",[e]),this.q("SELECT COUNT(*) AS total FROM visitors WHERE tenant_id=?",[e])]),r=t?.total??0,o=n?.total??0,a=s?.total??0,c=await this.q(`SELECT jt.status, COUNT(*) AS count
95
- FROM consent_records cr
91
+ `;async listProfilesSummary(e){return this.stmt(`${this.PROFILE_SUMMARY_SQL} WHERE p.tenant_id = ? ORDER BY p.created_at ASC`).all(e).map(n=>this.mapProfileSummary(n))}async findProfilesUsingConsentTemplate(e){return this.stmt(`${this.PROFILE_SUMMARY_SQL} WHERE json_extract(p.profile_json, '$.consentTemplateId') = ? ORDER BY p.name ASC`).all(e).map(n=>this.mapProfileSummary(n))}async findProfilesUsingUITemplate(e){return this.stmt(`${this.PROFILE_SUMMARY_SQL} WHERE json_extract(p.profile_json, '$.uiTemplateId') = ? ORDER BY p.name ASC`).all(e).map(n=>this.mapProfileSummary(n))}async getOptInStats(e,t){let n=["cr.tenant_id = ?"],s=[e];t.profileId&&(n.push("cr.profile_id = ?"),s.push(t.profileId)),t.from&&(n.push("cr.created_at >= ?"),s.push(t.from)),t.to&&(n.push("cr.created_at <= ?"),s.push(t.to)),t.locale&&(n.push("cr.locale = ?"),s.push(t.locale));let r=n.join(" AND "),o=this.stmt(`SELECT cr.locale, cr.consent_json FROM consent_records cr WHERE ${r}`).all(...s),a=0,c=0,l=0,d=0,u={};for(let f of o){let y=JSON.parse(f.consent_json),w=Object.values(y),R=w.filter(N=>N==="granted").length,I=w.filter(N=>N==="denied").length,T=w.length>0?R/w.length:0;a++;let v=f.locale;u[v]||(u[v]={total:0,granted:0,denied:0,managed:0}),u[v].total++,T>=.9?(c++,u[v].granted++):I/(w.length||1)>=.9?(l++,u[v].denied++):(d++,u[v].managed++)}let p=this.stmt(`SELECT date(cr.created_at) as d, cr.consent_json FROM consent_records cr WHERE ${r} ORDER BY d ASC`).all(...s),g=new Map;for(let f of p){let y=g.get(f.d)??{total:0,granted:0,denied:0,managed:0},w=JSON.parse(f.consent_json),R=Object.values(w),I=R.length>0?R.filter(v=>v==="granted").length/R.length:0,T=R.length>0?R.filter(v=>v==="denied").length/R.length:0;y.total++,I>=.9?y.granted++:T>=.9?y.denied++:y.managed++,g.set(f.d,y)}let E=Array.from(g.entries()).map(([f,y])=>({date:f,...y}));return{total:a,granted:c,denied:l,managed:d,grantedPct:a>0?Math.round(c/a*1e3)/10:0,deniedPct:a>0?Math.round(l/a*1e3)/10:0,managedPct:a>0?Math.round(d/a*1e3)/10:0,byLocale:u,byDate:E}}};function Ae(i,e,t){return i.map(n=>{let s=n.profileJson;return{id:n.id,name:n.name,defaultLocale:n.defaultLocale,complianceGroup:s.complianceGroup??null,customComplianceGroup:s.customComplianceGroup??null,isActive:s.isActive===!0,consentTemplateName:s.consentTemplateId?e.get(s.consentTemplateId)??null:null,uiTemplateName:s.uiTemplateId?t.get(s.uiTemplateId)??null:null,createdAt:n.createdAt,updatedAt:n.updatedAt}})}function ve(i){let e=()=>({total:0,granted:0,denied:0,managed:0}),t=e(),n={},s=new Map;for(let o of i){let a=Object.values(o.consentJson),c=d=>a.length>0?a.filter(u=>u===d).length/a.length:0,l=c("granted")>=.9?"granted":c("denied")>=.9?"denied":"managed";s.has(o.day)||s.set(o.day,e()),n[o.locale]??=e();for(let d of[t,n[o.locale],s.get(o.day)])d.total++,d[l]++}let r=o=>t.total>0?Math.round(o/t.total*1e3)/10:0;return{...t,grantedPct:r(t.granted),deniedPct:r(t.denied),managedPct:r(t.managed),byLocale:n,byDate:Array.from(s,([o,a])=>({date:o,...a})).sort((o,a)=>o.date.localeCompare(a.date))}}function Cn(i){return i.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}function ye(i){return{id:i._id,tenantId:i.tenant_id,name:i.name,defaultLocale:i.default_locale,version:i.version,profileJson:i.profile_json,createdAt:i.created_at,updatedAt:i.updated_at}}function mt(i){return{id:i._id,tenantId:i.tenant_id,visitorId:i.visitor_id,profileId:i.profile_id,...i.profile_version!=null?{profileVersion:i.profile_version}:{},locale:i.locale,consentJson:i.consent_json,gpcDetected:i.gpc_detected,source:i.source,createdAt:i.created_at,updatedAt:i.updated_at,...i.age_verified!=null?{ageVerified:i.age_verified}:{},...i.parental_consent_token!=null?{parentalConsentToken:i.parental_consent_token}:{},...i.tcf_string!=null?{tcfString:i.tcf_string}:{},...i.gpp_string!=null?{gppString:i.gpp_string}:{},...i.signature!=null?{signature:i.signature}:{}}}var oa={tenant_id:1,visitor_id:1,profile_id:1,locale:1,gpc_detected:1,source:1,age_verified:1,created_at:1,updated_at:1};function aa(i){return{id:i._id,tenantId:i.tenant_id,visitorId:i.visitor_id,profileId:i.profile_id,locale:i.locale,gpcDetected:i.gpc_detected,source:i.source,createdAt:i.created_at,updatedAt:i.updated_at,...i.age_verified!=null?{ageVerified:i.age_verified}:{}}}function ca(i){return{id:i._id,tenantId:i.tenant_id,consentRecordId:i.consent_record_id,visitorId:i.visitor_id,oldJson:i.old_json,newJson:i.new_json,...i.profile_version!=null?{profileVersion:i.profile_version}:{},action:i.action,createdAt:i.created_at}}function gt(i){return{id:i._id,tenantId:i.tenant_id,visitorId:i.visitor_id,...i.country!=null?{country:i.country}:{},...i.region!=null?{region:i.region}:{},...i.city!=null?{city:i.city}:{},...i.ip_hash!=null?{ipHash:i.ip_hash}:{},...i.user_agent_hash!=null?{userAgentHash:i.user_agent_hash}:{},firstSeen:i.first_seen,lastSeen:i.last_seen}}function $e(i){return{id:i._id,tenantId:i.tenant_id,name:i.name,email:i.email,passwordHash:i.password_hash,isActive:i.is_active,createdAt:i.created_at,updatedAt:i.updated_at}}function ft(i){return{id:i._id,tenantId:i.tenant_id,name:i.name,...i.description!=null?{description:i.description}:{}}}function ei(i){return{id:i._id,name:i.name,...i.description!=null?{description:i.description}:{}}}function ti(i){return{id:i._id,tenantId:i.tenant_id,action:i.action,resourceType:i.resource_type,...i.user_id!=null?{userId:i.user_id}:{},...i.resource_id!=null?{resourceId:i.resource_id}:{},...i.old_data!=null?{oldData:i.old_data}:{},...i.new_data!=null?{newData:i.new_data}:{},createdAt:i.created_at}}var la={tenant_id:1,user_id:1,action:1,resource_type:1,resource_id:1,created_at:1};function da(i){return{id:i._id,tenantId:i.tenant_id,action:i.action,resourceType:i.resource_type,...i.user_id!=null?{userId:i.user_id}:{},...i.resource_id!=null?{resourceId:i.resource_id}:{},createdAt:i.created_at}}var Fe=class{constructor(e){this.config=e}config;client;db;col(e){return this.db.collection(Z(e))}async connect(){let{MongoClient:e}=await import("mongodb"),t=this.config.uri??"mongodb://localhost:27017";this.client=new e(t,{maxPoolSize:this.config.poolMax??5}),await this.client.connect(),this.db=this.client.db(this.config.database??"consenti"),await this.migrate()}async disconnect(){await this.client.close()}async migrate(){await this.refuseLegacyCollections(),await this.createIndexes(),await this.seed()}async refuseLegacyCollections(){let e;try{e=new Set((await this.db.listCollections({},{nameOnly:!0}).toArray()).map(n=>n.name))}catch{return}let t=ue.filter(n=>n!=="schema_version");if(!e.has(Z("tenants"))&&t.every(n=>e.has(n)))throw Re("collections")}async createIndexes(){for(let{table:e,cols:t,unique:n}of zs){let s=Object.fromEntries(t.map(r=>[r,1]));await this.col(e).createIndex(s,n?{unique:!0}:void 0)}}async seed(){let e=async(r,o,a)=>{try{await this.col(r).updateOne(o,{$setOnInsert:a},{upsert:!0})}catch(c){if(c.code!==11e3)throw c}},{id:t,name:n,slug:s}=Y;await e("tenants",{_id:t},{_id:t,name:n,slug:s});for(let r of K)await e("permissions",{_id:r.id},{_id:r.id,name:r.name,description:r.description});for(let r of re)await e("roles",{_id:r.id},{_id:r.id,tenant_id:r.tenantId,name:r.name,description:r.description});for(let{roleId:r,permissionId:o}of oe)await e("role_permissions",{role_id:r,permission_id:o},{role_id:r,permission_id:o})}async createProfile(e){let t=new Date().toISOString(),s={_id:W(),tenant_id:e.tenantId,name:e.name,default_locale:e.defaultLocale,version:1,profile_json:e.profileJson,created_at:t,updated_at:t};return await this.col("profiles").insertOne(s),ye(s)}async updateProfile(e,t){let s={updated_at:new Date().toISOString()};t.name!=null&&(s.name=t.name),t.defaultLocale!=null&&(s.default_locale=t.defaultLocale),t.profileJson!=null&&(s.profile_json=t.profileJson),t.version!=null&&(s.version=t.version);let r=await this.col("profiles").findOneAndUpdate({_id:e},{$set:s},{returnDocument:"after"});if(!r)throw new Error(`Profile ${e} not found`);return ye(r)}async deleteProfile(e){await this.col("profiles").deleteOne({_id:e})}async getProfile(e){let t=await this.col("profiles").findOne({_id:e});return t?ye(t):null}async getProfiles(e){return(await this.col("profiles").find({tenant_id:e}).toArray()).map(ye)}async findActiveProfileByComplianceGroup(e,t){return(await this.getProfiles(e)).find(s=>(s.profileJson.complianceGroup===t||s.profileJson.customComplianceGroup===t)&&s.profileJson.isActive)??null}async createConsent(e){let t=new Date().toISOString(),n={_id:J(),tenant_id:e.tenantId,visitor_id:e.visitorId,profile_id:e.profileId,...e.profileVersion!=null?{profile_version:e.profileVersion}:{},locale:e.locale,consent_json:e.consentJson,gpc_detected:e.gpcDetected,source:e.source,created_at:t,updated_at:t,...e.ageVerified!=null?{age_verified:e.ageVerified}:{},...e.parentalConsentToken!=null?{parental_consent_token:e.parentalConsentToken}:{},...e.tcfString!=null?{tcf_string:e.tcfString}:{},...e.gppString!=null?{gpp_string:e.gppString}:{},...e.signature!=null?{signature:e.signature}:{}};return await this.col("consent_records").insertOne(n),await this.writeHistory(n._id,e.tenantId,e.visitorId,null,e.consentJson,"created",e.profileVersion),mt(n)}async updateConsent(e,t){let n=await this.getConsent(e);if(!n)throw new Error(`Consent for visitor ${e} not found`);let r={updated_at:new Date().toISOString(),consent_json:t.consentJson};t.profileVersion!=null&&(r.profile_version=t.profileVersion),t.locale!=null&&(r.locale=t.locale),t.gpcDetected!=null&&(r.gpc_detected=t.gpcDetected),t.signature!=null&&(r.signature=t.signature);let o=await this.col("consent_records").findOneAndUpdate({visitor_id:e},{$set:r},{returnDocument:"after"});if(!o)throw new Error(`Consent for visitor ${e} not found`);return await this.writeHistory(o._id,o.tenant_id,e,n.consentJson,t.consentJson,"updated",t.profileVersion??n.profileVersion),mt(o)}async deleteConsent(e){await this.col("consent_history").deleteMany({visitor_id:e}),await this.col("consent_records").deleteOne({visitor_id:e})}async getConsent(e){let t=await this.col("consent_records").findOne({visitor_id:e});return t?mt(t):null}async getConsents(e){let t=this.buildConsentFilter(e),n=e.page??1,s=e.limit??50,[r,o]=await Promise.all([this.col("consent_records").find(t,{projection:oa}).sort({created_at:-1}).skip((n-1)*s).limit(s).toArray(),this.col("consent_records").countDocuments(t)]);return{items:r.map(aa),total:o,page:n,limit:s}}async*streamConsents(e){let t=this.buildConsentFilter(e),n=this.col("consent_records").find(t).sort({created_at:1});for await(let s of n)yield mt(s)}buildConsentFilter(e){let t={tenant_id:e.tenantId};if(e.profileId!=null&&(t.profile_id=e.profileId),e.from!=null||e.to!=null){let n={};e.from!=null&&(n.$gte=e.from),e.to!=null&&(n.$lte=e.to),t.created_at=n}if(e.q){let n={$regex:"^"+Cn(e.q),$options:"i"};t.$or=[{visitor_id:n},{profile_id:n},{locale:n},{source:n}]}return t}async writeHistory(e,t,n,s,r,o,a){let c={_id:A(),tenant_id:t,consent_record_id:e,visitor_id:n,old_json:s,new_json:r,...a!=null?{profile_version:a}:{},action:o,created_at:new Date().toISOString()};await this.col("consent_history").insertOne(c)}async getConsentHistory(e){return(await this.col("consent_history").find({visitor_id:e}).sort({created_at:1}).toArray()).map(ca)}async createNoticeShown(e){let t={_id:A(),tenant_id:e.tenantId,visitor_id:e.visitorId,profile_id:e.profileId,locale:e.locale,created_at:new Date().toISOString()};return await this.col("notice_shown").insertOne(t),{id:t._id,tenantId:t.tenant_id,visitorId:t.visitor_id,profileId:t.profile_id,locale:t.locale,createdAt:t.created_at}}async getNoticeShownForVisitor(e){return(await this.col("notice_shown").find({visitor_id:e}).sort({created_at:-1}).toArray()).map(n=>({id:n._id,tenantId:n.tenant_id,visitorId:n.visitor_id,profileId:n.profile_id,locale:n.locale,createdAt:n.created_at}))}async createVisitor(e){let t=new Date().toISOString(),n={_id:H(),tenant_id:e.tenantId,visitor_id:e.visitorId,first_seen:t,last_seen:t,...e.country!=null?{country:e.country}:{},...e.region!=null?{region:e.region}:{},...e.city!=null?{city:e.city}:{},...e.ipHash!=null?{ip_hash:e.ipHash}:{},...e.userAgentHash!=null?{user_agent_hash:e.userAgentHash}:{}};return await this.col("visitors").insertOne(n),gt(n)}async updateVisitor(e,t){let n={last_seen:t.lastSeen??new Date().toISOString()};t.country!=null&&(n.country=t.country),t.region!=null&&(n.region=t.region),t.city!=null&&(n.city=t.city);let s=await this.col("visitors").findOneAndUpdate({visitor_id:e},{$set:n},{returnDocument:"after"});if(!s)throw new Error(`Visitor ${e} not found`);return gt(s)}async deleteVisitor(e){await this.col("visitors").deleteOne({visitor_id:e})}async getVisitor(e){let t=await this.col("visitors").findOne({visitor_id:e});return t?gt(t):null}async getVisitors(e){let t={tenant_id:e.tenantId};if(e.from!=null||e.to!=null){let a={};e.from!=null&&(a.$gte=e.from),e.to!=null&&(a.$lte=e.to),t.first_seen=a}if(e.q){let a={$regex:"^"+Cn(e.q),$options:"i"};t.$or=[{visitor_id:a},{country:a}]}let n=e.page??1,s=e.limit??50,[r,o]=await Promise.all([this.col("visitors").find(t).sort({first_seen:-1}).skip((n-1)*s).limit(s).toArray(),this.col("visitors").countDocuments(t)]);return{items:r.map(gt),total:o,page:n,limit:s}}async getOverviewStats(e){let[t,n,s]=await Promise.all([this.col("consent_records").countDocuments({tenant_id:e}),this.col("consent_records").countDocuments({tenant_id:e,gpc_detected:!0}),this.col("visitors").countDocuments({tenant_id:e})]),r=[{$match:{tenant_id:e}},{$addFields:{values:{$objectToArray:"$consent_json"}}},{$unwind:"$values"},{$group:{_id:"$values.v",count:{$sum:1}}}],o=await this.col("consent_records").aggregate(r).toArray(),a=0,c=0,l=0;for(let d of o)l+=d.count,d._id==="granted"&&(a=d.count),d._id==="denied"&&(c=d.count);return{totalConsents:t,acceptedPct:l>0?Math.round(a/l*100):0,rejectedPct:l>0?Math.round(c/l*100):0,totalVisitors:s,gpcDetectedCount:n}}async getCategoryStats(e){let t=[{$match:{tenant_id:e}},{$addFields:{pairs:{$objectToArray:"$consent_json"}}},{$unwind:"$pairs"},{$group:{_id:{cookie:"$pairs.k",status:"$pairs.v"},count:{$sum:1}}}],n=await this.col("consent_records").aggregate(t).toArray(),s={};for(let r of n){let o=r._id.cookie;s[o]||(s[o]={granted:0,denied:0,objected:0});let a=s[o];a&&(r._id.status==="granted"&&(a.granted+=r.count),r._id.status==="denied"&&(a.denied+=r.count),r._id.status==="objected"&&(a.objected+=r.count))}return s}async getTimeline(e,t=30){let n=new Date(Date.now()-t*864e5).toISOString(),s=[{$match:{tenant_id:e,created_at:{$gte:n}}},{$group:{_id:{$substr:["$created_at",0,10]},count:{$sum:1}}},{$sort:{_id:1}}];return(await this.col("consent_records").aggregate(s).toArray()).map(o=>({date:o._id,count:o.count}))}async createUser(e){let t=new Date().toISOString(),n={_id:A(),tenant_id:e.tenantId,name:e.name,email:e.email,password_hash:e.passwordHash,is_active:!0,created_at:t,updated_at:t};return await this.col("users").insertOne(n),$e(n)}async updateUser(e,t){let n={updated_at:new Date().toISOString()};t.name!=null&&(n.name=t.name),t.email!=null&&(n.email=t.email),t.passwordHash!=null&&(n.password_hash=t.passwordHash),t.isActive!=null&&(n.is_active=t.isActive);let s=await this.col("users").findOneAndUpdate({_id:e},{$set:n},{returnDocument:"after"});if(!s)throw new Error(`User ${e} not found`);return $e(s)}async deleteUser(e){await this.col("user_roles").deleteMany({user_id:e}),await this.col("users").deleteOne({_id:e})}async getUserById(e){let t=await this.col("users").findOne({_id:e});return t?$e(t):null}async getUserByEmail(e){let t=await this.col("users").findOne({email:e});return t?$e(t):null}async getUsers(e){return(await this.col("users").find({tenant_id:e}).toArray()).map($e)}async countUsers(e){return this.col("users").countDocuments(e?{tenant_id:e}:{})}async getRoles(e){return(await this.col("roles").find({tenant_id:e}).toArray()).map(ft)}async createRole(e){let t={_id:A(),tenant_id:e.tenantId,name:e.name,...e.description!=null?{description:e.description}:{}};return await this.col("roles").insertOne(t),ft(t)}async updateRole(e,t){let n={};t.name!=null&&(n.name=t.name),t.description!=null&&(n.description=t.description);let s=await this.col("roles").findOneAndUpdate({_id:e},{$set:n},{returnDocument:"after"});if(!s)throw new Error(`Role ${e} not found`);return ft(s)}async deleteRole(e){await this.col("role_permissions").deleteMany({role_id:e}),await this.col("user_roles").deleteMany({role_id:e}),await this.col("roles").deleteOne({_id:e})}async getAllPermissions(){return(await this.col("permissions").find({}).toArray()).map(ei)}async getPermissionsForRole(e){let n=(await this.col("role_permissions").find({role_id:e}).toArray()).map(r=>r.permission_id);return n.length===0?[]:(await this.col("permissions").find({_id:{$in:n}}).toArray()).map(ei)}async getUserRoles(e,t){let n=t?{user_id:e,tenant_id:t}:{user_id:e},r=(await this.col("user_roles").find(n).toArray()).map(a=>a.role_id);return r.length===0?[]:(await this.col("roles").find({_id:{$in:r}}).toArray()).map(ft)}async assignRole(e,t,n="default"){await this.col("user_roles").updateOne({user_id:e,role_id:t,tenant_id:n},{$setOnInsert:{user_id:e,role_id:t,tenant_id:n}},{upsert:!0})}async revokeRole(e,t,n){n?await this.col("user_roles").deleteOne({user_id:e,role_id:t,tenant_id:n}):await this.col("user_roles").deleteMany({user_id:e,role_id:t})}async assignPermissionToRole(e,t){await this.col("role_permissions").updateOne({role_id:e,permission_id:t},{$setOnInsert:{role_id:e,permission_id:t}},{upsert:!0})}async revokePermissionFromRole(e,t){await this.col("role_permissions").deleteOne({role_id:e,permission_id:t})}async createLog(e){let t={_id:A(),tenant_id:e.tenantId,action:e.action,resource_type:e.resourceType,...e.userId!=null?{user_id:e.userId}:{},...e.resourceId!=null?{resource_id:e.resourceId}:{},...e.oldData!=null?{old_data:e.oldData}:{},...e.newData!=null?{new_data:e.newData}:{},created_at:new Date().toISOString()};await this.col("audit_logs").insertOne(t)}async getLogs(e){let t=this.buildAuditFilter(e),n=e.page??1,s=e.limit??50,[r,o]=await Promise.all([this.col("audit_logs").find(t,{projection:la}).sort({created_at:-1}).skip((n-1)*s).limit(s).toArray(),this.col("audit_logs").countDocuments(t)]);return{items:r.map(da),total:o,page:n,limit:s}}async getAuditLogById(e){let t=await this.col("audit_logs").findOne({_id:e});return t?ti(t):null}async*streamAuditLogs(e){let t=this.buildAuditFilter(e),n=this.col("audit_logs").find(t).sort({created_at:1});for await(let s of n)yield ti(s)}buildAuditFilter(e){let t={tenant_id:e.tenantId};if(e.action!=null&&(t.action=e.action),e.resourceType!=null&&(t.resource_type=e.resourceType),e.from!=null||e.to!=null){let n={};e.from!=null&&(n.$gte=e.from),e.to!=null&&(n.$lte=e.to),t.created_at=n}if(e.q){let n={$regex:"^"+Cn(e.q),$options:"i"};t.$or=[{action:n},{resource_type:n},{resource_id:n},{user_id:n}]}return t}async getCountries(e){let t=[{$match:{tenant_id:e}},{$lookup:{from:Z("visitors"),localField:"visitor_id",foreignField:"visitor_id",as:"v"}},{$unwind:"$v"},{$match:{"v.country":{$ne:null}}},{$group:{_id:"$v.country",count:{$sum:1}}},{$sort:{count:-1}},{$limit:50}];return(await this.col("consent_records").aggregate(t).toArray()).map(s=>({country:s._id,count:s.count}))}async getGpcStats(e){let t=await this.col("consent_records").countDocuments({tenant_id:e}),n=await this.col("consent_records").countDocuments({tenant_id:e,gpc_detected:!0});return{detected:n,total:t,rate:t>0?Math.round(n/t*100):0}}async getTenants(){return(await this.col("tenants").find({}).toArray()).map(t=>({id:t._id,name:t.name,slug:t.slug,createdAt:t.created_at,updatedAt:t.updated_at}))}mapApiKey(e){return{id:e._id,tenantId:e.tenant_id,keyHash:e.key_hash,name:e.name,isActive:e.is_active,...e.created_by!==void 0?{createdBy:e.created_by}:{},...e.expire_by!==void 0?{expireBy:e.expire_by}:{},createdAt:e.created_at,...e.updated_at!==void 0?{updatedAt:e.updated_at}:{}}}async createApiKey(e){let t=new Date().toISOString(),n={_id:A(),tenant_id:e.tenantId,key_hash:e.keyHash,name:e.name,is_active:!0,...e.createdBy!==void 0?{created_by:e.createdBy}:{},...e.expireBy!==void 0?{expire_by:e.expireBy}:{},created_at:t,updated_at:t};return await this.col("api_keys").insertOne(n),this.mapApiKey(n)}async getApiKeyByHash(e){let t=await this.col("api_keys").findOne({key_hash:e,is_active:!0});return t?this.mapApiKey(t):null}async revokeApiKey(e){await this.col("api_keys").updateOne({_id:e},{$set:{is_active:!1,updated_at:new Date().toISOString()}})}async reactivateApiKey(e){let t=await this.col("api_keys").findOne({_id:e});if(!t)return;let n=t.expire_by&&new Date(t.expire_by)<=new Date;await this.col("api_keys").updateOne({_id:e},{$set:{is_active:!0,updated_at:new Date().toISOString()},...n?{$unset:{expire_by:""}}:{}})}async deleteApiKey(e){await this.col("api_keys").deleteOne({_id:e})}async getApiKeys(e){return(await this.col("api_keys").find({tenant_id:e}).sort({created_at:-1}).toArray()).map(n=>this.mapApiKey(n))}async createTenant(e){let t=A(),n=new Date().toISOString(),s={_id:t,name:e.name,slug:e.slug,created_at:n,updated_at:n};return await this.col("tenants").insertOne(s),{id:t,name:e.name,slug:e.slug,createdAt:n,updatedAt:n}}async updateTenant(e,t){let s={updated_at:new Date().toISOString()};t.name!=null&&(s.name=t.name),t.slug!=null&&(s.slug=t.slug),await this.col("tenants").updateOne({_id:e},{$set:s});let r=await this.col("tenants").findOne({_id:e});if(!r)throw new Error(`Tenant ${e} not found`);return{id:r._id,name:r.name,slug:r.slug,createdAt:r.created_at,updatedAt:r.updated_at}}async deleteTenant(e){await this.col("tenants").deleteOne({_id:e})}async getSettings(e){let t=await this.col("tenant_settings").findOne({_id:e});return{...t?.allowed_origins!==void 0?{allowedOrigins:t.allowed_origins}:{},...t?.admin_allowed_origins!==void 0?{adminAllowedOrigins:t.admin_allowed_origins}:{},...t?.setup_completed!==void 0?{setupCompleted:t.setup_completed}:{},...t?.profiles_seeded!==void 0?{profilesSeeded:t.profiles_seeded}:{},...t?.tcf_confirmation!==void 0?{tcfConfirmation:t.tcf_confirmation}:{},...t?.gpp_confirmation!==void 0?{gppConfirmation:t.gpp_confirmation}:{}}}async updateSettings(e,t){let n={updated_at:new Date().toISOString()};return t.allowedOrigins!==void 0&&(n.allowed_origins=t.allowedOrigins),t.adminAllowedOrigins!==void 0&&(n.admin_allowed_origins=t.adminAllowedOrigins),t.setupCompleted!==void 0&&(n.setup_completed=t.setupCompleted),t.profilesSeeded!==void 0&&(n.profiles_seeded=t.profilesSeeded),t.tcfConfirmation!==void 0&&(n.tcf_confirmation=t.tcfConfirmation),t.gppConfirmation!==void 0&&(n.gpp_confirmation=t.gppConfirmation),await this.col("tenant_settings").updateOne({_id:e},{$set:n},{upsert:!0}),this.getSettings(e)}async purgeExpiredConsents(e){let t=new Date(Date.now()-e*864e5).toISOString(),n=await this.col("consent_records").find({created_at:{$lt:t}}).toArray();if(n.length===0)return 0;let s=n.map(r=>r.visitor_id);return await this.col("consent_history").deleteMany({visitor_id:{$in:s}}),await this.col("consent_records").deleteMany({visitor_id:{$in:s}}),n.length}mapConsentTemplate(e){return{id:e._id,tenantId:e.tenant_id,name:e.name,cookies:e.cookies_json,categories:e.categories_json,createdAt:e.created_at,updatedAt:e.updated_at}}async createConsentTemplate(e){let t=new Date().toISOString(),n={_id:G(),tenant_id:e.tenantId,name:e.name,cookies_json:e.cookies,categories_json:e.categories,created_at:t,updated_at:t};return await this.col("consent_templates").insertOne(n),this.mapConsentTemplate(n)}async updateConsentTemplate(e,t){let n={updated_at:new Date().toISOString()};t.name!==void 0&&(n.name=t.name),t.cookies!==void 0&&(n.cookies_json=t.cookies),t.categories!==void 0&&(n.categories_json=t.categories);let s=await this.col("consent_templates").findOneAndUpdate({_id:e},{$set:n},{returnDocument:"after"});if(!s)throw new Error("Consent template not found");return this.mapConsentTemplate(s)}async deleteConsentTemplate(e){await this.col("consent_templates").deleteOne({_id:e})}async getConsentTemplate(e){let t=await this.col("consent_templates").findOne({_id:e});return t?this.mapConsentTemplate(t):null}async getConsentTemplates(e){return(await this.col("consent_templates").find({tenant_id:e}).sort({name:1}).toArray()).map(n=>this.mapConsentTemplate(n))}async copyConsentTemplate(e,t){let n=await this.getConsentTemplate(e);if(!n)throw new Error("Consent template not found");return this.createConsentTemplate({tenantId:n.tenantId,name:t,cookies:n.cookies,categories:n.categories})}mapUITemplate(e){return{id:e._id,tenantId:e.tenant_id,name:e.name,...e.settings_json,createdAt:e.created_at,updatedAt:e.updated_at}}async createUITemplate(e){let t=new Date().toISOString(),{tenantId:n,name:s,...r}=e,o={_id:q(),tenant_id:n,name:s,settings_json:r,created_at:t,updated_at:t};return await this.col("ui_templates").insertOne(o),this.mapUITemplate(o)}async updateUITemplate(e,t){let n=await this.col("ui_templates").findOne({_id:e});if(!n)throw new Error("UI template not found");let{name:s,...r}=t,o={updated_at:new Date().toISOString()};s!==void 0&&(o.name=s),Object.keys(r).length>0&&(o.settings_json={...n.settings_json,...r});let a=await this.col("ui_templates").findOneAndUpdate({_id:e},{$set:o},{returnDocument:"after"});if(!a)throw new Error("UI template not found");return this.mapUITemplate(a)}async deleteUITemplate(e){await this.col("ui_templates").deleteOne({_id:e})}async getUITemplate(e){let t=await this.col("ui_templates").findOne({_id:e});return t?this.mapUITemplate(t):null}async getUITemplates(e){return(await this.col("ui_templates").find({tenant_id:e}).sort({name:1}).toArray()).map(n=>this.mapUITemplate(n))}async copyUITemplate(e,t){let n=await this.getUITemplate(e);if(!n)throw new Error("UI template not found");let{tenantId:s,mainBanner:r,gpcBanner:o,preferenceModal:a}=n;return this.createUITemplate({tenantId:s,name:t,mainBanner:r,gpcBanner:o,preferenceModal:a})}async toProfileSummaries(e){if(e.length===0)return[];let t=[...new Set(e.map(r=>r.tenantId))],[n,s]=await Promise.all([Promise.all(t.map(r=>this.getConsentTemplates(r))),Promise.all(t.map(r=>this.getUITemplates(r)))]);return Ae(e,new Map(n.flat().map(r=>[r.id,r.name])),new Map(s.flat().map(r=>[r.id,r.name])))}async listProfilesSummary(e){let t=await this.col("profiles").find({tenant_id:e}).sort({created_at:1}).toArray();return this.toProfileSummaries(t.map(ye))}async findProfilesUsingConsentTemplate(e){let t=await this.col("profiles").find({"profile_json.consentTemplateId":e}).sort({name:1}).toArray();return this.toProfileSummaries(t.map(ye))}async findProfilesUsingUITemplate(e){let t=await this.col("profiles").find({"profile_json.uiTemplateId":e}).sort({name:1}).toArray();return this.toProfileSummaries(t.map(ye))}async getOptInStats(e,t){let n={tenant_id:e};t.profileId&&(n.profile_id=t.profileId),t.locale&&(n.locale=t.locale),(t.from||t.to)&&(n.created_at={...t.from?{$gte:t.from}:{},...t.to?{$lte:t.to}:{}});let s=await this.col("consent_records").find(n,{projection:{locale:1,consent_json:1,created_at:1}}).sort({created_at:1}).toArray();return ve(s.map(r=>({locale:r.locale,day:r.created_at.slice(0,10),consentJson:r.consent_json})))}};function we(i){return{id:i.id,tenantId:i.tenant_id,name:i.name,defaultLocale:i.default_locale,version:i.version,profileJson:JSON.parse(i.profile_json),createdAt:i.created_at,updatedAt:i.updated_at}}function _t(i){return{id:i.id,tenantId:i.tenant_id,visitorId:i.visitor_id,profileId:i.profile_id,...i.profile_version!=null?{profileVersion:i.profile_version}:{},locale:i.locale,consentJson:JSON.parse(i.consent_json),gpcDetected:i.gpc_detected===1,source:i.source,createdAt:i.created_at,updatedAt:i.updated_at,...i.age_verified!=null?{ageVerified:i.age_verified===1}:{},...i.parental_consent_token!=null?{parentalConsentToken:i.parental_consent_token}:{},...i.tcf_string!=null?{tcfString:i.tcf_string}:{},...i.gpp_string!=null?{gppString:i.gpp_string}:{},...i.signature!=null?{signature:i.signature}:{}}}var ua="id, tenant_id, visitor_id, profile_id, locale, gpc_detected, source, age_verified, created_at, updated_at";function pa(i){return{id:i.id,tenantId:i.tenant_id,visitorId:i.visitor_id,profileId:i.profile_id,locale:i.locale,gpcDetected:i.gpc_detected===1,source:i.source,createdAt:i.created_at,updatedAt:i.updated_at,...i.age_verified!=null?{ageVerified:i.age_verified===1}:{}}}function ma(i){return{id:i.id,tenantId:i.tenant_id,consentRecordId:i.consent_record_id,visitorId:i.visitor_id,...i.profile_version!=null?{profileVersion:i.profile_version}:{},oldJson:i.old_json?JSON.parse(i.old_json):null,newJson:JSON.parse(i.new_json),action:i.action,createdAt:i.created_at}}function ht(i){return{id:i.id,tenantId:i.tenant_id,visitorId:i.visitor_id,...i.country!=null?{country:i.country}:{},...i.region!=null?{region:i.region}:{},...i.city!=null?{city:i.city}:{},...i.ip_hash!=null?{ipHash:i.ip_hash}:{},...i.user_agent_hash!=null?{userAgentHash:i.user_agent_hash}:{},firstSeen:i.first_seen,lastSeen:i.last_seen}}function He(i){return{id:i.id,tenantId:i.tenant_id,name:i.name,email:i.email,passwordHash:i.password_hash,isActive:i.is_active===1,createdAt:i.created_at,updatedAt:i.updated_at,totpEnabled:i.totp_enabled===1,...i.totp_secret!=null?{totpSecret:i.totp_secret}:{}}}function yt(i){return{id:i.id,tenantId:i.tenant_id,name:i.name,...i.description!=null?{description:i.description}:{}}}function ni(i){return{id:i.id,name:i.name,...i.description!=null?{description:i.description}:{}}}function si(i){return{id:i.id,tenantId:i.tenant_id,action:i.action,resourceType:i.resource_type,...i.user_id!=null?{userId:i.user_id}:{},...i.resource_id!=null?{resourceId:i.resource_id}:{},...i.old_data!=null?{oldData:JSON.parse(i.old_data)}:{},...i.new_data!=null?{newData:JSON.parse(i.new_data)}:{},createdAt:i.created_at}}var ga="id, tenant_id, user_id, action, resource_type, resource_id, created_at";function fa(i){return{id:i.id,tenantId:i.tenant_id,action:i.action,resourceType:i.resource_type,...i.user_id!=null?{userId:i.user_id}:{},...i.resource_id!=null?{resourceId:i.resource_id}:{},createdAt:i.created_at}}function In(i=new Date){return i.toISOString().slice(0,19).replace("T"," ")}var Be=class{constructor(e){this.config=e}config;pool;async q(e,t=[]){let n=this.config.statementTimeoutMs,[s]=n?await this.pool.query({sql:e,timeout:n},t):await this.pool.query(e,t);return s}async exec(e,t=[]){let n=this.config.statementTimeoutMs,[s]=n?await this.pool.query({sql:e,timeout:n},t):await this.pool.query(e,t);return s}async connect(){let e=await import("mysql2/promise"),t=this.config;this.pool=e.createPool({...t.uri?{uri:t.uri}:{host:t.host??"localhost",port:t.port??3306,user:t.user??"root",password:t.password??"",database:t.database??"consenti"},connectionLimit:t.poolMax??5}),await this.migrate()}async disconnect(){await this.pool.end()}async migrate(){let e=await this.pool.getConnection(),t="CONCAT('consenti_schema_', IFNULL(DATABASE(), ''))";try{let[[n]]=await e.query(`SELECT GET_LOCK(${t}, 60) AS got`);if(n?.got!==1)throw new Error("[consenti] Timed out waiting for another Consenti instance to finish setting up the database schema.");let[s]=await e.query("SELECT TABLE_NAME AS name FROM information_schema.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME IN (?)",[["consenti_schema_version",...ue]]),r=new Set(s.map(o=>String(o.name)));if(!r.has("consenti_schema_version")&&ue.every(o=>r.has(o)))throw Re("tables");if(r.has("consenti_schema_version")){let[o]=await e.query("SELECT MAX(version) AS version FROM consenti_schema_version");if((o[0]?.version??0)<2)try{await this.migrateToV2(e)}catch(a){throw new Error(`Upgrading the database schema to v2 failed: ${a instanceof Error?a.message:String(a)}. The database user needs the ALTER privilege. This step only changes table structure, never rows, and is safe to re-run: fix the cause and start again.`,{cause:a})}}else{try{for(let o of Xs.split(";").map(a=>a.trim()).filter(Boolean))await e.query(o);await this.seed(e)}catch(o){throw new Error(`Creating the Consenti tables failed: ${o instanceof Error?o.message:String(o)}. The database user needs SELECT, INSERT, UPDATE, DELETE, CREATE, ALTER, INDEX and REFERENCES on this database (it never creates databases or users). Safe to re-run: fix the cause and start again.`,{cause:o})}await e.query("INSERT IGNORE INTO consenti_schema_version (version) VALUES (?)",[ee])}}finally{try{await e.query(`SELECT RELEASE_LOCK(${t})`)}catch{}e.release()}}async migrateToV2(e){let[t]=await e.query(`SELECT TABLE_NAME AS tbl, CONSTRAINT_NAME AS name FROM information_schema.KEY_COLUMN_USAGE
92
+ WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME IN ('consenti_consent_records','consenti_notice_shown')
93
+ AND COLUMN_NAME = 'profile_id' AND REFERENCED_TABLE_NAME = 'consenti_profiles'`);for(let n of t)await e.query(`ALTER TABLE \`${String(n.tbl)}\` DROP FOREIGN KEY \`${String(n.name).replace(/`/g,"``")}\``);for(let n of["consenti_consent_records","consenti_consent_history"]){let[s]=await e.query(`SELECT 1 FROM information_schema.COLUMNS
94
+ WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ? AND COLUMN_NAME = 'profile_version'`,[n]);s.length===0&&await e.query(`ALTER TABLE ${n} ADD COLUMN profile_version INT NULL`)}await e.query("INSERT IGNORE INTO consenti_schema_version (version) VALUES (?)",[ee])}async seed(e){await e.query("INSERT IGNORE INTO consenti_tenants (id, name, slug) VALUES (?,?,?)",[Y.id,Y.name,Y.slug]);for(let t of K)await e.query("INSERT IGNORE INTO consenti_permissions (id, name, description) VALUES (?,?,?)",[t.id,t.name,t.description]);for(let t of re)await e.query("INSERT IGNORE INTO consenti_roles (id, tenant_id, name, description) VALUES (?,?,?,?)",[t.id,t.tenantId,t.name,t.description]);for(let{roleId:t,permissionId:n}of oe)await e.query("INSERT IGNORE INTO consenti_role_permissions (role_id, permission_id) VALUES (?,?)",[t,n])}async createProfile(e){let t=W();await this.exec("INSERT INTO consenti_profiles (id, tenant_id, name, default_locale, version, profile_json) VALUES (?,?,?,?,?,?)",[t,e.tenantId,e.name,e.defaultLocale,1,JSON.stringify(e.profileJson)]);let s=(await this.q("SELECT * FROM consenti_profiles WHERE id=?",[t]))[0];if(!s)throw new Error("Profile creation failed");return we(s)}async updateProfile(e,t){let n=[],s=[];t.name!=null&&(n.push("name=?"),s.push(t.name)),t.defaultLocale!=null&&(n.push("default_locale=?"),s.push(t.defaultLocale)),t.profileJson!=null&&(n.push("profile_json=?"),s.push(JSON.stringify(t.profileJson))),t.version!=null&&(n.push("version=?"),s.push(t.version)),n.length>0&&(s.push(e),await this.exec(`UPDATE consenti_profiles SET ${n.join(",")} WHERE id=?`,s));let o=(await this.q("SELECT * FROM consenti_profiles WHERE id=?",[e]))[0];if(!o)throw new Error(`Profile ${e} not found`);return we(o)}async deleteProfile(e){await this.exec("DELETE FROM consenti_profiles WHERE id=?",[e])}async getProfile(e){let t=await this.q("SELECT * FROM consenti_profiles WHERE id=?",[e]);return t[0]?we(t[0]):null}async getProfiles(e){return(await this.q("SELECT * FROM consenti_profiles WHERE tenant_id=? ORDER BY created_at DESC",[e])).map(we)}async findActiveProfileByComplianceGroup(e,t){return(await this.getProfiles(e)).find(s=>(s.profileJson.complianceGroup===t||s.profileJson.customComplianceGroup===t)&&s.profileJson.isActive)??null}async createConsent(e){let t=J();await this.exec("INSERT INTO consenti_consent_records (id, tenant_id, visitor_id, profile_id, profile_version, locale, consent_json, gpc_detected, source, age_verified, parental_consent_token, tcf_string, gpp_string, signature) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?)",[t,e.tenantId,e.visitorId,e.profileId,e.profileVersion??null,e.locale,JSON.stringify(e.consentJson),e.gpcDetected?1:0,e.source,e.ageVerified?1:0,e.parentalConsentToken??null,e.tcfString??null,e.gppString??null,e.signature??null]);let s=(await this.q("SELECT * FROM consenti_consent_records WHERE id=?",[t]))[0];if(!s)throw new Error("Consent creation failed");return await this.writeHistory(t,e.tenantId,e.visitorId,null,e.consentJson,"created",e.profileVersion??null),_t(s)}async updateConsent(e,t){let n=await this.getConsent(e);if(!n)throw new Error(`Consent for visitor ${e} not found`);let s=["consent_json=?"],r=[JSON.stringify(t.consentJson)];t.profileVersion!=null&&(s.push("profile_version=?"),r.push(t.profileVersion)),t.locale!=null&&(s.push("locale=?"),r.push(t.locale)),t.gpcDetected!=null&&(s.push("gpc_detected=?"),r.push(t.gpcDetected?1:0)),t.signature!=null&&(s.push("signature=?"),r.push(t.signature)),r.push(e),await this.exec(`UPDATE consenti_consent_records SET ${s.join(",")} WHERE visitor_id=?`,r);let a=(await this.q("SELECT * FROM consenti_consent_records WHERE visitor_id=?",[e]))[0];if(!a)throw new Error(`Consent for visitor ${e} not found`);return await this.writeHistory(a.id,a.tenant_id,e,n.consentJson,t.consentJson,"updated",t.profileVersion??n.profileVersion??null),_t(a)}async deleteConsent(e){await this.exec("DELETE FROM consenti_consent_history WHERE visitor_id=?",[e]),await this.exec("DELETE FROM consenti_consent_records WHERE visitor_id=?",[e])}async getConsent(e){let t=await this.q("SELECT * FROM consenti_consent_records WHERE visitor_id=?",[e]);return t[0]?_t(t[0]):null}async getConsents(e){let{where:t,params:n}=this.buildConsentWhere(e),s=e.page??1,r=e.limit??50,[o,a]=await Promise.all([this.q(`SELECT ${ua} ${t} ORDER BY created_at DESC LIMIT ? OFFSET ?`,[...n,r,(s-1)*r]),this.q(`SELECT COUNT(*) AS total ${t}`,n)]);return{items:o.map(pa),total:a[0]?.total??0,page:s,limit:r}}async*streamConsents(e){let{where:t,params:n}=this.buildConsentWhere(e),s=await this.q(`SELECT * ${t} ORDER BY created_at ASC`,n);for(let r of s)yield _t(r)}buildConsentWhere(e){let t="FROM consenti_consent_records WHERE tenant_id=?",n=[e.tenantId];if(e.profileId!=null&&(t+=" AND profile_id=?",n.push(e.profileId)),e.from!=null&&(t+=" AND created_at>=?",n.push(e.from)),e.to!=null&&(t+=" AND created_at<=?",n.push(e.to)),e.q){t+=" AND (visitor_id LIKE ? OR profile_id LIKE ? OR locale LIKE ? OR source LIKE ?)";let s=x(e.q);n.push(s,s,s,s)}return{where:t,params:n}}async writeHistory(e,t,n,s,r,o,a){await this.exec("INSERT INTO consenti_consent_history (id, tenant_id, consent_record_id, visitor_id, profile_version, old_json, new_json, action) VALUES (?,?,?,?,?,?,?,?)",[A(),t,e,n,a,s?JSON.stringify(s):null,JSON.stringify(r),o])}async getConsentHistory(e){return(await this.q("SELECT * FROM consenti_consent_history WHERE visitor_id=? ORDER BY created_at ASC",[e])).map(ma)}async createNoticeShown(e){let t=A();await this.exec("INSERT INTO consenti_notice_shown (id, tenant_id, visitor_id, profile_id, locale) VALUES (?,?,?,?,?)",[t,e.tenantId,e.visitorId,e.profileId,e.locale]);let s=(await this.q("SELECT * FROM consenti_notice_shown WHERE id=?",[t]))[0];return{id:s.id,tenantId:s.tenant_id,visitorId:s.visitor_id,profileId:s.profile_id,locale:s.locale,createdAt:s.created_at}}async getNoticeShownForVisitor(e){return(await this.q("SELECT * FROM consenti_notice_shown WHERE visitor_id=? ORDER BY created_at DESC",[e])).map(n=>({id:n.id,tenantId:n.tenant_id,visitorId:n.visitor_id,profileId:n.profile_id,locale:n.locale,createdAt:n.created_at}))}async createVisitor(e){let t=H();await this.exec("INSERT INTO consenti_visitors (id, tenant_id, visitor_id, country, region, city, ip_hash, user_agent_hash) VALUES (?,?,?,?,?,?,?,?)",[t,e.tenantId,e.visitorId,e.country??null,e.region??null,e.city??null,e.ipHash??null,e.userAgentHash??null]);let s=(await this.q("SELECT * FROM consenti_visitors WHERE id=?",[t]))[0];if(!s)throw new Error("Visitor creation failed");return ht(s)}async updateVisitor(e,t){let n=["last_seen=NOW()"],s=[];t.country!=null&&(n.push("country=?"),s.push(t.country)),t.region!=null&&(n.push("region=?"),s.push(t.region)),t.city!=null&&(n.push("city=?"),s.push(t.city)),s.push(e),await this.exec(`UPDATE consenti_visitors SET ${n.join(",")} WHERE visitor_id=?`,s);let o=(await this.q("SELECT * FROM consenti_visitors WHERE visitor_id=?",[e]))[0];if(!o)throw new Error(`Visitor ${e} not found`);return ht(o)}async deleteVisitor(e){await this.exec("DELETE FROM consenti_visitors WHERE visitor_id=?",[e])}async getVisitor(e){let t=await this.q("SELECT * FROM consenti_visitors WHERE visitor_id=?",[e]);return t[0]?ht(t[0]):null}async getVisitors(e){let t="FROM consenti_visitors WHERE tenant_id=?",n=[e.tenantId];if(e.from!=null&&(t+=" AND first_seen>=?",n.push(e.from)),e.to!=null&&(t+=" AND first_seen<=?",n.push(e.to)),e.q){t+=" AND (visitor_id LIKE ? OR country LIKE ?)";let c=x(e.q);n.push(c,c)}let s=e.page??1,r=e.limit??50,[o,a]=await Promise.all([this.q(`SELECT * ${t} ORDER BY first_seen DESC LIMIT ? OFFSET ?`,[...n,r,(s-1)*r]),this.q(`SELECT COUNT(*) AS total ${t}`,n)]);return{items:o.map(ht),total:a[0]?.total??0,page:s,limit:r}}async getOverviewStats(e){let[[t],[n],[s]]=await Promise.all([this.q("SELECT COUNT(*) AS total FROM consenti_consent_records WHERE tenant_id=?",[e]),this.q("SELECT COUNT(*) AS total FROM consenti_consent_records WHERE tenant_id=? AND gpc_detected=1",[e]),this.q("SELECT COUNT(*) AS total FROM consenti_visitors WHERE tenant_id=?",[e])]),r=t?.total??0,o=n?.total??0,a=s?.total??0,c=await this.q(`SELECT jt.status, COUNT(*) AS count
95
+ FROM consenti_consent_records cr
96
96
  JOIN JSON_TABLE(cr.consent_json, '$.*' COLUMNS (status VARCHAR(20) PATH '$')) AS jt
97
97
  WHERE cr.tenant_id=?
98
98
  GROUP BY jt.status`,[e]),l=0,d=0,u=0;for(let p of c)u+=p.count,p.status==="granted"&&(l=p.count),p.status==="denied"&&(d=p.count);return{totalConsents:r,acceptedPct:u>0?Math.round(l/u*100):0,rejectedPct:u>0?Math.round(d/u*100):0,totalVisitors:a,gpcDetectedCount:o}}async getCategoryStats(e){let t=await this.q(`SELECT jt.cookie_id, jt.status, COUNT(*) AS count
99
- FROM consent_records cr
99
+ FROM consenti_consent_records cr
100
100
  JOIN JSON_TABLE(cr.consent_json, '$.*' COLUMNS (NESTED PATH '$' COLUMNS (
101
101
  cookie_id VARCHAR(100) PATH '$.k',
102
102
  status VARCHAR(20) PATH '$.v'
103
103
  ))) AS jt
104
104
  WHERE cr.tenant_id=?
105
105
  GROUP BY jt.cookie_id, jt.status`,[e]),n={};for(let s of t){n[s.cookie_id]||(n[s.cookie_id]={granted:0,denied:0,objected:0});let r=n[s.cookie_id];r&&(s.status==="granted"&&(r.granted+=s.count),s.status==="denied"&&(r.denied+=s.count),s.status==="objected"&&(r.objected+=s.count))}return n}async getTimeline(e,t=30){let n=new Date(Date.now()-t*864e5).toISOString().slice(0,10);return(await this.q(`SELECT DATE_FORMAT(created_at, '%Y-%m-%d') AS date, COUNT(*) AS count
106
- FROM consent_records WHERE tenant_id=? AND created_at>=?
107
- GROUP BY DATE_FORMAT(created_at, '%Y-%m-%d') ORDER BY date ASC`,[e,n])).map(r=>({date:r.date,count:r.count}))}async createUser(e){let t=A();await this.exec("INSERT INTO users (id, tenant_id, name, email, password_hash) VALUES (?,?,?,?,?)",[t,e.tenantId,e.name,e.email,e.passwordHash]);let s=(await this.q("SELECT * FROM users WHERE id=?",[t]))[0];if(!s)throw new Error("User creation failed");return ke(s)}async updateUser(e,t){let n=[],s=[];t.name!=null&&(n.push("name=?"),s.push(t.name)),t.email!=null&&(n.push("email=?"),s.push(t.email)),t.passwordHash!=null&&(n.push("password_hash=?"),s.push(t.passwordHash)),t.isActive!=null&&(n.push("is_active=?"),s.push(t.isActive?1:0)),n.length>0&&(s.push(e),await this.exec(`UPDATE users SET ${n.join(",")} WHERE id=?`,s));let o=(await this.q("SELECT * FROM users WHERE id=?",[e]))[0];if(!o)throw new Error(`User ${e} not found`);return ke(o)}async deleteUser(e){await this.exec("DELETE FROM user_roles WHERE user_id=?",[e]),await this.exec("DELETE FROM users WHERE id=?",[e])}async getUserById(e){let t=await this.q("SELECT * FROM users WHERE id=?",[e]);return t[0]?ke(t[0]):null}async getUserByEmail(e){let t=await this.q("SELECT * FROM users WHERE email=?",[e]);return t[0]?ke(t[0]):null}async getUsers(e){return(await this.q("SELECT * FROM users WHERE tenant_id=? ORDER BY created_at DESC",[e])).map(ke)}async countUsers(e){return(e?await this.q("SELECT COUNT(*) AS total FROM users WHERE tenant_id=?",[e]):await this.q("SELECT COUNT(*) AS total FROM users"))[0]?.total??0}async getRoles(e){return(await this.q("SELECT * FROM roles WHERE tenant_id=?",[e])).map(pt)}async createRole(e){let t=A();await this.exec("INSERT INTO roles (id, tenant_id, name, description) VALUES (?,?,?,?)",[t,e.tenantId,e.name,e.description??null]);let s=(await this.q("SELECT * FROM roles WHERE id=?",[t]))[0];if(!s)throw new Error("Role creation failed");return pt(s)}async updateRole(e,t){let n=[],s=[];t.name!=null&&(n.push("name=?"),s.push(t.name)),t.description!=null&&(n.push("description=?"),s.push(t.description)),n.length>0&&(s.push(e),await this.exec(`UPDATE roles SET ${n.join(",")} WHERE id=?`,s));let o=(await this.q("SELECT * FROM roles WHERE id=?",[e]))[0];if(!o)throw new Error(`Role ${e} not found`);return pt(o)}async deleteRole(e){await this.exec("DELETE FROM role_permissions WHERE role_id=?",[e]),await this.exec("DELETE FROM user_roles WHERE role_id=?",[e]),await this.exec("DELETE FROM roles WHERE id=?",[e])}async getAllPermissions(){return(await this.q("SELECT * FROM permissions ORDER BY name")).map(js)}async getPermissionsForRole(e){return(await this.q("SELECT p.* FROM permissions p JOIN role_permissions rp ON p.id=rp.permission_id WHERE rp.role_id=?",[e])).map(js)}async getUserRoles(e,t){return(t?await this.q("SELECT r.* FROM roles r JOIN user_roles ur ON r.id=ur.role_id WHERE ur.user_id=? AND ur.tenant_id=?",[e,t]):await this.q("SELECT r.* FROM roles r JOIN user_roles ur ON r.id=ur.role_id WHERE ur.user_id=?",[e])).map(pt)}async assignRole(e,t,n="default"){await this.exec("INSERT IGNORE INTO user_roles (user_id, role_id, tenant_id) VALUES (?,?,?)",[e,t,n])}async revokeRole(e,t,n){n?await this.exec("DELETE FROM user_roles WHERE user_id=? AND role_id=? AND tenant_id=?",[e,t,n]):await this.exec("DELETE FROM user_roles WHERE user_id=? AND role_id=?",[e,t])}async assignPermissionToRole(e,t){await this.exec("INSERT IGNORE INTO role_permissions (role_id, permission_id) VALUES (?,?)",[e,t])}async revokePermissionFromRole(e,t){await this.exec("DELETE FROM role_permissions WHERE role_id=? AND permission_id=?",[e,t])}async createLog(e){await this.exec("INSERT INTO audit_logs (id, tenant_id, user_id, action, resource_type, resource_id, old_data, new_data) VALUES (?,?,?,?,?,?,?,?)",[A(),e.tenantId,e.userId??null,e.action,e.resourceType,e.resourceId??null,e.oldData!=null?JSON.stringify(e.oldData):null,e.newData!=null?JSON.stringify(e.newData):null])}async getLogs(e){let{where:t,params:n}=this.buildAuditWhere(e),s=e.page??1,r=e.limit??50,[o,a]=await Promise.all([this.q(`SELECT ${Bo} ${t} ORDER BY created_at DESC LIMIT ? OFFSET ?`,[...n,r,(s-1)*r]),this.q(`SELECT COUNT(*) AS total ${t}`,n)]);return{items:o.map(Vo),total:a[0]?.total??0,page:s,limit:r}}async getAuditLogById(e){let t=await this.q("SELECT * FROM audit_logs WHERE id=?",[e]);return t[0]?xs(t[0]):null}async*streamAuditLogs(e){let{where:t,params:n}=this.buildAuditWhere(e),s=await this.q(`SELECT * ${t} ORDER BY created_at ASC`,n);for(let r of s)yield xs(r)}buildAuditWhere(e){let t="FROM audit_logs WHERE tenant_id=?",n=[e.tenantId];if(e.action!=null&&(t+=" AND action=?",n.push(e.action)),e.resourceType!=null&&(t+=" AND resource_type=?",n.push(e.resourceType)),e.from!=null&&(t+=" AND created_at>=?",n.push(e.from)),e.to!=null&&(t+=" AND created_at<=?",n.push(e.to)),e.q){t+=" AND (action LIKE ? OR resource_type LIKE ? OR resource_id LIKE ? OR user_id LIKE ?) ESCAPE '\\\\'";let s=j(e.q);n.push(s,s,s,s)}return{where:t,params:n}}async getCountries(e){return(await this.q(`SELECT v.country, COUNT(*) AS count FROM consent_records cr
108
- JOIN visitors v ON v.visitor_id=cr.visitor_id
106
+ FROM consenti_consent_records WHERE tenant_id=? AND created_at>=?
107
+ GROUP BY DATE_FORMAT(created_at, '%Y-%m-%d') ORDER BY date ASC`,[e,n])).map(r=>({date:r.date,count:r.count}))}async createUser(e){let t=A();await this.exec("INSERT INTO consenti_users (id, tenant_id, name, email, password_hash) VALUES (?,?,?,?,?)",[t,e.tenantId,e.name,e.email,e.passwordHash]);let s=(await this.q("SELECT * FROM consenti_users WHERE id=?",[t]))[0];if(!s)throw new Error("User creation failed");return He(s)}async updateUser(e,t){let n=[],s=[];t.name!=null&&(n.push("name=?"),s.push(t.name)),t.email!=null&&(n.push("email=?"),s.push(t.email)),t.passwordHash!=null&&(n.push("password_hash=?"),s.push(t.passwordHash)),t.isActive!=null&&(n.push("is_active=?"),s.push(t.isActive?1:0)),n.length>0&&(s.push(e),await this.exec(`UPDATE consenti_users SET ${n.join(",")} WHERE id=?`,s));let o=(await this.q("SELECT * FROM consenti_users WHERE id=?",[e]))[0];if(!o)throw new Error(`User ${e} not found`);return He(o)}async deleteUser(e){await this.exec("DELETE FROM consenti_user_roles WHERE user_id=?",[e]),await this.exec("DELETE FROM consenti_users WHERE id=?",[e])}async getUserById(e){let t=await this.q("SELECT * FROM consenti_users WHERE id=?",[e]);return t[0]?He(t[0]):null}async getUserByEmail(e){let t=await this.q("SELECT * FROM consenti_users WHERE email=?",[e]);return t[0]?He(t[0]):null}async getUsers(e){return(await this.q("SELECT * FROM consenti_users WHERE tenant_id=? ORDER BY created_at DESC",[e])).map(He)}async countUsers(e){return(e?await this.q("SELECT COUNT(*) AS total FROM consenti_users WHERE tenant_id=?",[e]):await this.q("SELECT COUNT(*) AS total FROM consenti_users"))[0]?.total??0}async getRoles(e){return(await this.q("SELECT * FROM consenti_roles WHERE tenant_id=?",[e])).map(yt)}async createRole(e){let t=A();await this.exec("INSERT INTO consenti_roles (id, tenant_id, name, description) VALUES (?,?,?,?)",[t,e.tenantId,e.name,e.description??null]);let s=(await this.q("SELECT * FROM consenti_roles WHERE id=?",[t]))[0];if(!s)throw new Error("Role creation failed");return yt(s)}async updateRole(e,t){let n=[],s=[];t.name!=null&&(n.push("name=?"),s.push(t.name)),t.description!=null&&(n.push("description=?"),s.push(t.description)),n.length>0&&(s.push(e),await this.exec(`UPDATE consenti_roles SET ${n.join(",")} WHERE id=?`,s));let o=(await this.q("SELECT * FROM consenti_roles WHERE id=?",[e]))[0];if(!o)throw new Error(`Role ${e} not found`);return yt(o)}async deleteRole(e){await this.exec("DELETE FROM consenti_role_permissions WHERE role_id=?",[e]),await this.exec("DELETE FROM consenti_user_roles WHERE role_id=?",[e]),await this.exec("DELETE FROM consenti_roles WHERE id=?",[e])}async getAllPermissions(){return(await this.q("SELECT * FROM consenti_permissions ORDER BY name")).map(ni)}async getPermissionsForRole(e){return(await this.q("SELECT p.* FROM consenti_permissions p JOIN consenti_role_permissions rp ON p.id=rp.permission_id WHERE rp.role_id=?",[e])).map(ni)}async getUserRoles(e,t){return(t?await this.q("SELECT r.* FROM consenti_roles r JOIN consenti_user_roles ur ON r.id=ur.role_id WHERE ur.user_id=? AND ur.tenant_id=?",[e,t]):await this.q("SELECT r.* FROM consenti_roles r JOIN consenti_user_roles ur ON r.id=ur.role_id WHERE ur.user_id=?",[e])).map(yt)}async assignRole(e,t,n="default"){await this.exec("INSERT IGNORE INTO consenti_user_roles (user_id, role_id, tenant_id) VALUES (?,?,?)",[e,t,n])}async revokeRole(e,t,n){n?await this.exec("DELETE FROM consenti_user_roles WHERE user_id=? AND role_id=? AND tenant_id=?",[e,t,n]):await this.exec("DELETE FROM consenti_user_roles WHERE user_id=? AND role_id=?",[e,t])}async assignPermissionToRole(e,t){await this.exec("INSERT IGNORE INTO consenti_role_permissions (role_id, permission_id) VALUES (?,?)",[e,t])}async revokePermissionFromRole(e,t){await this.exec("DELETE FROM consenti_role_permissions WHERE role_id=? AND permission_id=?",[e,t])}async createLog(e){await this.exec("INSERT INTO consenti_audit_logs (id, tenant_id, user_id, action, resource_type, resource_id, old_data, new_data) VALUES (?,?,?,?,?,?,?,?)",[A(),e.tenantId,e.userId??null,e.action,e.resourceType,e.resourceId??null,e.oldData!=null?JSON.stringify(e.oldData):null,e.newData!=null?JSON.stringify(e.newData):null])}async getLogs(e){let{where:t,params:n}=this.buildAuditWhere(e),s=e.page??1,r=e.limit??50,[o,a]=await Promise.all([this.q(`SELECT ${ga} ${t} ORDER BY created_at DESC LIMIT ? OFFSET ?`,[...n,r,(s-1)*r]),this.q(`SELECT COUNT(*) AS total ${t}`,n)]);return{items:o.map(fa),total:a[0]?.total??0,page:s,limit:r}}async getAuditLogById(e){let t=await this.q("SELECT * FROM consenti_audit_logs WHERE id=?",[e]);return t[0]?si(t[0]):null}async*streamAuditLogs(e){let{where:t,params:n}=this.buildAuditWhere(e),s=await this.q(`SELECT * ${t} ORDER BY created_at ASC`,n);for(let r of s)yield si(r)}buildAuditWhere(e){let t="FROM consenti_audit_logs WHERE tenant_id=?",n=[e.tenantId];if(e.action!=null&&(t+=" AND action=?",n.push(e.action)),e.resourceType!=null&&(t+=" AND resource_type=?",n.push(e.resourceType)),e.from!=null&&(t+=" AND created_at>=?",n.push(e.from)),e.to!=null&&(t+=" AND created_at<=?",n.push(e.to)),e.q){t+=" AND (action LIKE ? OR resource_type LIKE ? OR resource_id LIKE ? OR user_id LIKE ?)";let s=x(e.q);n.push(s,s,s,s)}return{where:t,params:n}}async getCountries(e){return(await this.q(`SELECT v.country, COUNT(*) AS count FROM consenti_consent_records cr
108
+ JOIN consenti_visitors v ON v.visitor_id=cr.visitor_id
109
109
  WHERE cr.tenant_id=? AND v.country IS NOT NULL
110
- GROUP BY v.country ORDER BY count DESC LIMIT 50`,[e])).map(n=>({country:n.country,count:n.count}))}async getGpcStats(e){let[t]=await this.q("SELECT COUNT(*) AS total FROM consent_records WHERE tenant_id=?",[e]),[n]=await this.q("SELECT COUNT(*) AS total FROM consent_records WHERE tenant_id=? AND gpc_detected=1",[e]),s=t?.total??0,r=n?.total??0;return{detected:r,total:s,rate:s>0?Math.round(r/s*100):0}}async getTenants(){return(await this.q("SELECT * FROM tenants ORDER BY name ASC",[])).map(t=>({id:t.id,name:t.name,slug:t.slug,createdAt:t.created_at,updatedAt:t.updated_at}))}mapApiKey(e){return{id:e.id,tenantId:e.tenant_id,keyHash:e.key_hash,name:e.name,isActive:e.is_active===1,...e.created_by!==null?{createdBy:e.created_by}:{},...e.expire_by!==null?{expireBy:e.expire_by}:{},createdAt:e.created_at,...e.updated_at!==null?{updatedAt:e.updated_at}:{}}}async createApiKey(e){let t=A();await this.exec("INSERT INTO api_keys (id, tenant_id, key_hash, name, created_by, expire_by) VALUES (?,?,?,?,?,?)",[t,e.tenantId,e.keyHash,e.name,e.createdBy??null,e.expireBy??null]);let s=(await this.q("SELECT * FROM api_keys WHERE id=?",[t]))[0];if(!s)throw new Error("Failed to create API key");return this.mapApiKey(s)}async getApiKeyByHash(e){let n=(await this.q("SELECT * FROM api_keys WHERE key_hash=? AND is_active=1",[e]))[0];return n?this.mapApiKey(n):null}async revokeApiKey(e){await this.exec("UPDATE api_keys SET is_active=0, updated_at=NOW() WHERE id=?",[e])}async reactivateApiKey(e){await this.exec("UPDATE api_keys SET is_active=1, updated_at=NOW(), expire_by=CASE WHEN expire_by <= NOW() THEN NULL ELSE expire_by END WHERE id=?",[e])}async deleteApiKey(e){await this.exec("DELETE FROM api_keys WHERE id=?",[e])}async getApiKeys(e){return(await this.q("SELECT * FROM api_keys WHERE tenant_id=? ORDER BY created_at DESC",[e])).map(n=>this.mapApiKey(n))}async createTenant(e){let t=A(),n=new Date().toISOString();await this.exec("INSERT INTO tenants (id, name, slug, created_at, updated_at) VALUES (?,?,?,?,?)",[t,e.name,e.slug,n,n]);let r=(await this.q("SELECT * FROM tenants WHERE id=?",[t]))[0];if(!r)throw new Error("Failed to create tenant");return{id:r.id,name:r.name,slug:r.slug,createdAt:r.created_at,updatedAt:r.updated_at}}async updateTenant(e,t){let n=new Date().toISOString(),s=["updated_at=?"],r=[n];t.name!=null&&(s.push("name=?"),r.push(t.name)),t.slug!=null&&(s.push("slug=?"),r.push(t.slug)),r.push(e),await this.exec(`UPDATE tenants SET ${s.join(",")} WHERE id=?`,r);let a=(await this.q("SELECT * FROM tenants WHERE id=?",[e]))[0];if(!a)throw new Error(`Tenant ${e} not found`);return{id:a.id,name:a.name,slug:a.slug,createdAt:a.created_at,updatedAt:a.updated_at}}async getSettings(e){let n=(await this.q("SELECT allowed_origins_json, admin_allowed_origins_json, setup_completed, profiles_seeded, tcf_confirmation_json, gpp_confirmation_json FROM tenant_settings WHERE tenant_id=?",[e]))[0];return n?{allowedOrigins:JSON.parse(n.allowed_origins_json),adminAllowedOrigins:JSON.parse(n.admin_allowed_origins_json),setupCompleted:!!n.setup_completed,profilesSeeded:!!n.profiles_seeded,tcfConfirmation:n.tcf_confirmation_json?JSON.parse(n.tcf_confirmation_json):void 0,gppConfirmation:n.gpp_confirmation_json?JSON.parse(n.gpp_confirmation_json):void 0}:{}}async updateSettings(e,t){let s={...await this.getSettings(e),...t};return await this.exec(`INSERT INTO tenant_settings (tenant_id, allowed_origins_json, admin_allowed_origins_json, setup_completed, profiles_seeded, tcf_confirmation_json, gpp_confirmation_json, updated_at) VALUES (?,?,?,?,?,?,?,NOW())
111
- ON DUPLICATE KEY UPDATE allowed_origins_json=VALUES(allowed_origins_json), admin_allowed_origins_json=VALUES(admin_allowed_origins_json), setup_completed=VALUES(setup_completed), profiles_seeded=VALUES(profiles_seeded), tcf_confirmation_json=VALUES(tcf_confirmation_json), gpp_confirmation_json=VALUES(gpp_confirmation_json), updated_at=NOW()`,[e,JSON.stringify(s.allowedOrigins??[]),JSON.stringify(s.adminAllowedOrigins??[]),s.setupCompleted?1:0,s.profilesSeeded?1:0,s.tcfConfirmation?JSON.stringify(s.tcfConfirmation):null,s.gppConfirmation?JSON.stringify(s.gppConfirmation):null]),this.getSettings(e)}async deleteTenant(e){await this.exec("DELETE FROM tenants WHERE id=?",[e])}async purgeExpiredConsents(e){let t=new Date(Date.now()-e*864e5).toISOString(),n=await this.q("SELECT visitor_id FROM consent_records WHERE created_at < ?",[t]);if(n.length===0)return 0;let s=n.map(o=>o.visitor_id),r=s.map(()=>"?").join(",");return await this.exec(`DELETE FROM consent_history WHERE visitor_id IN (${r})`,s),await this.exec(`DELETE FROM consent_records WHERE visitor_id IN (${r})`,s),s.length}mapConsentTemplate(e){return{id:e.id,tenantId:e.tenant_id,name:e.name,cookies:JSON.parse(e.cookies_json),categories:JSON.parse(e.categories_json),createdAt:e.created_at,updatedAt:e.updated_at}}async createConsentTemplate(e){let t=J();await this.exec("INSERT INTO consent_templates (id, tenant_id, name, cookies_json, categories_json) VALUES (?,?,?,?,?)",[t,e.tenantId,e.name,JSON.stringify(e.cookies),JSON.stringify(e.categories)]);let n=await this.getConsentTemplate(t);if(!n)throw new Error("Consent template creation failed");return n}async updateConsentTemplate(e,t){let n=[],s=[];t.name!==void 0&&(n.push("name=?"),s.push(t.name)),t.cookies!==void 0&&(n.push("cookies_json=?"),s.push(JSON.stringify(t.cookies))),t.categories!==void 0&&(n.push("categories_json=?"),s.push(JSON.stringify(t.categories))),n.length>0&&await this.exec(`UPDATE consent_templates SET ${n.join(",")} WHERE id=?`,[...s,e]);let r=await this.getConsentTemplate(e);if(!r)throw new Error("Consent template not found");return r}async deleteConsentTemplate(e){await this.exec("DELETE FROM consent_templates WHERE id=?",[e])}async getConsentTemplate(e){let t=await this.q("SELECT * FROM consent_templates WHERE id=?",[e]);return t[0]?this.mapConsentTemplate(t[0]):null}async getConsentTemplates(e){return(await this.q("SELECT * FROM consent_templates WHERE tenant_id=? ORDER BY name ASC",[e])).map(n=>this.mapConsentTemplate(n))}async copyConsentTemplate(e,t){let n=await this.getConsentTemplate(e);if(!n)throw new Error("Consent template not found");return this.createConsentTemplate({tenantId:n.tenantId,name:t,cookies:n.cookies,categories:n.categories})}mapUITemplate(e){let t=JSON.parse(e.settings_json);return{id:e.id,tenantId:e.tenant_id,name:e.name,...t,createdAt:e.created_at,updatedAt:e.updated_at}}async createUITemplate(e){let t=q(),{tenantId:n,name:s,...r}=e;await this.exec("INSERT INTO ui_templates (id, tenant_id, name, settings_json) VALUES (?,?,?,?)",[t,n,s,JSON.stringify(r)]);let o=await this.getUITemplate(t);if(!o)throw new Error("UI template creation failed");return o}async updateUITemplate(e,t){let s=(await this.q("SELECT * FROM ui_templates WHERE id=?",[e]))[0];if(!s)throw new Error("UI template not found");let{name:r,...o}=t,a=[],c=[];if(r!==void 0&&(a.push("name=?"),c.push(r)),Object.keys(o).length>0){let d={...JSON.parse(s.settings_json),...o};a.push("settings_json=?"),c.push(JSON.stringify(d))}a.length>0&&await this.exec(`UPDATE ui_templates SET ${a.join(",")} WHERE id=?`,[...c,e]);let l=await this.getUITemplate(e);if(!l)throw new Error("UI template not found");return l}async deleteUITemplate(e){await this.exec("DELETE FROM ui_templates WHERE id=?",[e])}async getUITemplate(e){let t=await this.q("SELECT * FROM ui_templates WHERE id=?",[e]);return t[0]?this.mapUITemplate(t[0]):null}async getUITemplates(e){return(await this.q("SELECT * FROM ui_templates WHERE tenant_id=? ORDER BY name ASC",[e])).map(n=>this.mapUITemplate(n))}async copyUITemplate(e,t){let n=await this.getUITemplate(e);if(!n)throw new Error("UI template not found");let{tenantId:s,mainBanner:r,gpcBanner:o,preferenceModal:a}=n;return this.createUITemplate({tenantId:s,name:t,mainBanner:r,gpcBanner:o,preferenceModal:a})}async toProfileSummaries(e){if(e.length===0)return[];let t=[...new Set(e.map(r=>r.tenantId))],[n,s]=await Promise.all([Promise.all(t.map(r=>this.getConsentTemplates(r))),Promise.all(t.map(r=>this.getUITemplates(r)))]);return Re(e,new Map(n.flat().map(r=>[r.id,r.name])),new Map(s.flat().map(r=>[r.id,r.name])))}async listProfilesSummary(e){let t=await this.q("SELECT * FROM profiles WHERE tenant_id=? ORDER BY created_at ASC",[e]);return this.toProfileSummaries(t.map(_e))}async findProfilesUsingConsentTemplate(e){let t=await this.q("SELECT * FROM profiles WHERE JSON_UNQUOTE(JSON_EXTRACT(profile_json, '$.consentTemplateId'))=? ORDER BY name ASC",[e]);return this.toProfileSummaries(t.map(_e))}async findProfilesUsingUITemplate(e){let t=await this.q("SELECT * FROM profiles WHERE JSON_UNQUOTE(JSON_EXTRACT(profile_json, '$.uiTemplateId'))=? ORDER BY name ASC",[e]);return this.toProfileSummaries(t.map(_e))}async getOptInStats(e,t){let n="tenant_id=?",s=[e];t.profileId&&(n+=" AND profile_id=?",s.push(t.profileId)),t.from&&(n+=" AND created_at>=?",s.push(t.from)),t.to&&(n+=" AND created_at<=?",s.push(t.to)),t.locale&&(n+=" AND locale=?",s.push(t.locale));let r=await this.q(`SELECT locale, DATE_FORMAT(created_at, '%Y-%m-%d') AS day, consent_json FROM consent_records WHERE ${n} ORDER BY created_at ASC`,s);return Se(r.map(o=>({locale:o.locale,day:o.day,consentJson:JSON.parse(o.consent_json)})))}};function he(i){return{id:i.id,tenantId:i.tenant_id,name:i.name,defaultLocale:i.default_locale,version:i.version,profileJson:i.profile_json,createdAt:i.created_at,updatedAt:i.updated_at}}function mt(i){return{id:i.id,tenantId:i.tenant_id,visitorId:i.visitor_id,profileId:i.profile_id,locale:i.locale,consentJson:i.consent_json,gpcDetected:i.gpc_detected,source:i.source,createdAt:i.created_at,updatedAt:i.updated_at,...i.age_verified!=null?{ageVerified:i.age_verified}:{},...i.parental_consent_token!=null?{parentalConsentToken:i.parental_consent_token}:{},...i.tcf_string!=null?{tcfString:i.tcf_string}:{},...i.gpp_string!=null?{gppString:i.gpp_string}:{},...i.signature!=null?{signature:i.signature}:{}}}var Wo="id, tenant_id, visitor_id, profile_id, locale, gpc_detected, source, age_verified, created_at, updated_at";function Jo(i){return{id:i.id,tenantId:i.tenant_id,visitorId:i.visitor_id,profileId:i.profile_id,locale:i.locale,gpcDetected:i.gpc_detected,source:i.source,createdAt:i.created_at,updatedAt:i.updated_at,...i.age_verified!=null?{ageVerified:i.age_verified}:{}}}function qo(i){return{id:i.id,tenantId:i.tenant_id,consentRecordId:i.consent_record_id,visitorId:i.visitor_id,oldJson:i.old_json,newJson:i.new_json,action:i.action,createdAt:i.created_at}}function gt(i){return{id:i.id,tenantId:i.tenant_id,visitorId:i.visitor_id,...i.country!=null?{country:i.country}:{},...i.region!=null?{region:i.region}:{},...i.city!=null?{city:i.city}:{},...i.ip_hash!=null?{ipHash:i.ip_hash}:{},...i.user_agent_hash!=null?{userAgentHash:i.user_agent_hash}:{},firstSeen:i.first_seen,lastSeen:i.last_seen}}function je(i){return{id:i.id,tenantId:i.tenant_id,name:i.name,email:i.email,passwordHash:i.password_hash,isActive:i.is_active,createdAt:i.created_at,updatedAt:i.updated_at,totpEnabled:i.totp_enabled??!1,...i.totp_secret!=null?{totpSecret:i.totp_secret}:{}}}function xe(i){return{id:i.id,tenantId:i.tenant_id,name:i.name,...i.description!=null?{description:i.description}:{}}}function $s(i){return{id:i.id,name:i.name,...i.description!=null?{description:i.description}:{}}}function Fs(i){return{id:i.id,tenantId:i.tenant_id,action:i.action,resourceType:i.resource_type,...i.user_id!=null?{userId:i.user_id}:{},...i.resource_id!=null?{resourceId:i.resource_id}:{},...i.old_data!=null?{oldData:i.old_data}:{},...i.new_data!=null?{newData:i.new_data}:{},createdAt:i.created_at}}var Go="id, tenant_id, user_id, action, resource_type, resource_id, created_at";function Ko(i){return{id:i.id,tenantId:i.tenant_id,action:i.action,resourceType:i.resource_type,...i.user_id!=null?{userId:i.user_id}:{},...i.resource_id!=null?{resourceId:i.resource_id}:{},createdAt:i.created_at}}var $e=class{constructor(e){this.config=e}config;pool;async q(e,t=[]){return(await this.pool.query(e,t)).rows}async connect(){let t=(await import("pg")).default.Pool,n=this.config,s=n.uri?{connectionString:n.uri}:{host:n.host??"localhost",port:n.port??5432,user:n.user??"postgres",password:n.password??"",database:n.database??"consenti"};s.max=n.poolMax??10,n.statementTimeoutMs!=null&&(s.statement_timeout=n.statementTimeoutMs),n.idleInTransactionTimeoutMs!=null&&(s.idle_in_transaction_session_timeout=n.idleInTransactionTimeoutMs),this.pool=new t(s),await this.migrate()}async disconnect(){await this.pool.end()}async migrate(){let e=await this.pool.connect();try{let t=0;try{t=(await e.query("SELECT version FROM schema_version ORDER BY version DESC LIMIT 1")).rows[0]?.version??0}catch{}if(t===0){await e.query("BEGIN");try{for(let n of Ls.split(";").map(s=>s.trim()).filter(Boolean))await e.query(n);await this.seedTx(e),await e.query("INSERT INTO schema_version (version) VALUES (1) ON CONFLICT DO NOTHING"),await e.query("COMMIT")}catch(n){throw await e.query("ROLLBACK"),n}}}finally{e.release()}}async seedTx(e){await e.query("INSERT INTO tenants (id, name, slug) VALUES ($1,$2,$3) ON CONFLICT DO NOTHING",[K.id,K.name,K.slug]);for(let t of G)await e.query("INSERT INTO permissions (id, name, description) VALUES ($1,$2,$3) ON CONFLICT DO NOTHING",[t.id,t.name,t.description]);for(let t of se)await e.query("INSERT INTO roles (id, tenant_id, name, description) VALUES ($1,$2,$3,$4) ON CONFLICT DO NOTHING",[t.id,t.tenantId,t.name,t.description]);for(let{roleId:t,permissionId:n}of ie)await e.query("INSERT INTO role_permissions VALUES ($1,$2) ON CONFLICT DO NOTHING",[t,n])}async createProfile(e){let t=V(),s=(await this.q(`INSERT INTO profiles (id, tenant_id, name, default_locale, version, profile_json)
112
- VALUES ($1,$2,$3,$4,$5,$6::json) RETURNING *`,[t,e.tenantId,e.name,e.defaultLocale,1,JSON.stringify(e.profileJson)]))[0];if(!s)throw new Error("Profile creation failed");return he(s)}async updateProfile(e,t){let n=["updated_at = NOW()"],s=[],r=1;t.name!=null&&(n.push(`name=$${r++}`),s.push(t.name)),t.defaultLocale!=null&&(n.push(`default_locale=$${r++}`),s.push(t.defaultLocale)),t.profileJson!=null&&(n.push(`profile_json=$${r++}::json`),s.push(JSON.stringify(t.profileJson))),t.version!=null&&(n.push(`version=$${r++}`),s.push(t.version)),s.push(e);let a=(await this.q(`UPDATE profiles SET ${n.join(",")} WHERE id=$${r} RETURNING *`,s))[0];if(!a)throw new Error(`Profile ${e} not found`);return he(a)}async deleteProfile(e){await this.q("DELETE FROM profiles WHERE id=$1",[e])}async getProfile(e){let t=await this.q("SELECT * FROM profiles WHERE id=$1",[e]);return t[0]?he(t[0]):null}async getProfiles(e){return(await this.q("SELECT * FROM profiles WHERE tenant_id=$1 ORDER BY created_at DESC",[e])).map(he)}async findActiveProfileByComplianceGroup(e,t){return(await this.getProfiles(e)).find(s=>(s.profileJson.complianceGroup===t||s.profileJson.customComplianceGroup===t)&&s.profileJson.isActive)??null}async createConsent(e){let t=W(),s=(await this.q(`INSERT INTO consent_records (id, tenant_id, visitor_id, profile_id, locale, consent_json, gpc_detected, source, age_verified, parental_consent_token, tcf_string, gpp_string, signature)
113
- VALUES ($1,$2,$3,$4,$5,$6::jsonb,$7,$8,$9,$10,$11,$12,$13) RETURNING *`,[t,e.tenantId,e.visitorId,e.profileId,e.locale,JSON.stringify(e.consentJson),e.gpcDetected,e.source,e.ageVerified??!1,e.parentalConsentToken??null,e.tcfString??null,e.gppString??null,e.signature??null]))[0];if(!s)throw new Error("Consent creation failed");return await this.writeHistory(t,e.tenantId,e.visitorId,null,e.consentJson,"created"),mt(s)}async updateConsent(e,t){let n=await this.getConsent(e);if(!n)throw new Error(`Consent for visitor ${e} not found`);let s=["consent_json=$1::jsonb","updated_at=NOW()"],r=[JSON.stringify(t.consentJson)],o=2;t.locale!=null&&(s.push(`locale=$${o++}`),r.push(t.locale)),t.gpcDetected!=null&&(s.push(`gpc_detected=$${o++}`),r.push(t.gpcDetected)),t.signature!=null&&(s.push(`signature=$${o++}`),r.push(t.signature)),r.push(e);let c=(await this.q(`UPDATE consent_records SET ${s.join(",")} WHERE visitor_id=$${o} RETURNING *`,r))[0];if(!c)throw new Error(`Consent for visitor ${e} not found`);return await this.writeHistory(c.id,c.tenant_id,e,n.consentJson,t.consentJson,"updated"),mt(c)}async deleteConsent(e){await this.q("DELETE FROM consent_history WHERE visitor_id=$1",[e]),await this.q("DELETE FROM consent_records WHERE visitor_id=$1",[e])}async getConsent(e){let t=await this.q("SELECT * FROM consent_records WHERE visitor_id=$1",[e]);return t[0]?mt(t[0]):null}async getConsents(e){let{where:t,params:n,nextIdx:s}=this.buildConsentWhere(e),r=e.page??1,o=e.limit??50,[a,c]=await Promise.all([this.q(`SELECT ${Wo} ${t} ORDER BY created_at DESC LIMIT $${s} OFFSET $${s+1}`,[...n,o,(r-1)*o]),this.q(`SELECT COUNT(*)::int AS total ${t}`,n)]);return{items:a.map(Jo),total:Number(c[0]?.total??0),page:r,limit:o}}async*streamConsents(e){let{where:t,params:n}=this.buildConsentWhere(e),s=await this.q(`SELECT * ${t} ORDER BY created_at ASC`,n);for(let r of s)yield mt(r)}buildConsentWhere(e){let t="FROM consent_records WHERE tenant_id=$1",n=[e.tenantId],s=2;return e.profileId!=null&&(t+=` AND profile_id=$${s++}`,n.push(e.profileId)),e.from!=null&&(t+=` AND created_at>=$${s++}`,n.push(e.from)),e.to!=null&&(t+=` AND created_at<=$${s++}`,n.push(e.to)),e.q&&(t+=` AND (visitor_id ILIKE $${s} OR profile_id ILIKE $${s} OR locale ILIKE $${s} OR source ILIKE $${s}) ESCAPE '\\'`,n.push(j(e.q)),s++),{where:t,params:n,nextIdx:s}}async writeHistory(e,t,n,s,r,o){await this.q(`INSERT INTO consent_history (id, tenant_id, consent_record_id, visitor_id, old_json, new_json, action)
114
- VALUES ($1,$2,$3,$4,$5::jsonb,$6::jsonb,$7)`,[A(),t,e,n,s?JSON.stringify(s):null,JSON.stringify(r),o])}async getConsentHistory(e){return(await this.q("SELECT * FROM consent_history WHERE visitor_id=$1 ORDER BY created_at ASC",[e])).map(qo)}async createNoticeShown(e){let t=A(),s=(await this.q(`INSERT INTO notice_shown (id, tenant_id, visitor_id, profile_id, locale)
115
- VALUES ($1,$2,$3,$4,$5) RETURNING *`,[t,e.tenantId,e.visitorId,e.profileId,e.locale]))[0];return{id:s.id,tenantId:s.tenant_id,visitorId:s.visitor_id,profileId:s.profile_id,locale:s.locale,createdAt:s.created_at}}async getNoticeShownForVisitor(e){return(await this.q("SELECT * FROM notice_shown WHERE visitor_id=$1 ORDER BY created_at DESC",[e])).map(n=>({id:n.id,tenantId:n.tenant_id,visitorId:n.visitor_id,profileId:n.profile_id,locale:n.locale,createdAt:n.created_at}))}async createVisitor(e){let t=H(),s=(await this.q(`INSERT INTO visitors (id, tenant_id, visitor_id, country, region, city, ip_hash, user_agent_hash)
116
- VALUES ($1,$2,$3,$4,$5,$6,$7,$8) RETURNING *`,[t,e.tenantId,e.visitorId,e.country??null,e.region??null,e.city??null,e.ipHash??null,e.userAgentHash??null]))[0];if(!s)throw new Error("Visitor creation failed");return gt(s)}async updateVisitor(e,t){let n=["last_seen=NOW()"],s=[],r=1;t.country!=null&&(n.push(`country=$${r++}`),s.push(t.country)),t.region!=null&&(n.push(`region=$${r++}`),s.push(t.region)),t.city!=null&&(n.push(`city=$${r++}`),s.push(t.city)),s.push(e);let a=(await this.q(`UPDATE visitors SET ${n.join(",")} WHERE visitor_id=$${r} RETURNING *`,s))[0];if(!a)throw new Error(`Visitor ${e} not found`);return gt(a)}async deleteVisitor(e){await this.q("DELETE FROM visitors WHERE visitor_id=$1",[e])}async getVisitor(e){let t=await this.q("SELECT * FROM visitors WHERE visitor_id=$1",[e]);return t[0]?gt(t[0]):null}async getVisitors(e){let t="FROM visitors WHERE tenant_id=$1",n=[e.tenantId],s=2;e.from!=null&&(t+=` AND first_seen>=$${s++}`,n.push(e.from)),e.to!=null&&(t+=` AND first_seen<=$${s++}`,n.push(e.to)),e.q&&(t+=` AND (visitor_id ILIKE $${s} OR country ILIKE $${s}) ESCAPE '\\'`,n.push(j(e.q)),s++);let r=e.page??1,o=e.limit??50,[a,c]=await Promise.all([this.q(`SELECT * ${t} ORDER BY first_seen DESC LIMIT $${s} OFFSET $${s+1}`,[...n,o,(r-1)*o]),this.q(`SELECT COUNT(*)::int AS total ${t}`,n)]);return{items:a.map(gt),total:Number(c[0]?.total??0),page:r,limit:o}}async getOverviewStats(e){let[[t],[n],[s]]=await Promise.all([this.q("SELECT COUNT(*)::int AS total FROM consent_records WHERE tenant_id=$1",[e]),this.q("SELECT COUNT(*)::int AS total FROM consent_records WHERE tenant_id=$1 AND gpc_detected=true",[e]),this.q("SELECT COUNT(*)::int AS total FROM visitors WHERE tenant_id=$1",[e])]),r=Number(t?.total??0),o=Number(n?.total??0),a=Number(s?.total??0),c=await this.q(`SELECT val AS status, COUNT(*)::int AS count
117
- FROM consent_records cr,
110
+ GROUP BY v.country ORDER BY count DESC LIMIT 50`,[e])).map(n=>({country:n.country,count:n.count}))}async getGpcStats(e){let[t]=await this.q("SELECT COUNT(*) AS total FROM consenti_consent_records WHERE tenant_id=?",[e]),[n]=await this.q("SELECT COUNT(*) AS total FROM consenti_consent_records WHERE tenant_id=? AND gpc_detected=1",[e]),s=t?.total??0,r=n?.total??0;return{detected:r,total:s,rate:s>0?Math.round(r/s*100):0}}async getTenants(){return(await this.q("SELECT * FROM consenti_tenants ORDER BY name ASC",[])).map(t=>({id:t.id,name:t.name,slug:t.slug,createdAt:t.created_at,updatedAt:t.updated_at}))}mapApiKey(e){return{id:e.id,tenantId:e.tenant_id,keyHash:e.key_hash,name:e.name,isActive:e.is_active===1,...e.created_by!==null?{createdBy:e.created_by}:{},...e.expire_by!==null?{expireBy:e.expire_by}:{},createdAt:e.created_at,...e.updated_at!==null?{updatedAt:e.updated_at}:{}}}async createApiKey(e){let t=A();await this.exec("INSERT INTO consenti_api_keys (id, tenant_id, key_hash, name, created_by, expire_by) VALUES (?,?,?,?,?,?)",[t,e.tenantId,e.keyHash,e.name,e.createdBy??null,e.expireBy??null]);let s=(await this.q("SELECT * FROM consenti_api_keys WHERE id=?",[t]))[0];if(!s)throw new Error("Failed to create API key");return this.mapApiKey(s)}async getApiKeyByHash(e){let n=(await this.q("SELECT * FROM consenti_api_keys WHERE key_hash=? AND is_active=1",[e]))[0];return n?this.mapApiKey(n):null}async revokeApiKey(e){await this.exec("UPDATE consenti_api_keys SET is_active=0, updated_at=NOW() WHERE id=?",[e])}async reactivateApiKey(e){await this.exec("UPDATE consenti_api_keys SET is_active=1, updated_at=NOW(), expire_by=CASE WHEN expire_by <= NOW() THEN NULL ELSE expire_by END WHERE id=?",[e])}async deleteApiKey(e){await this.exec("DELETE FROM consenti_api_keys WHERE id=?",[e])}async getApiKeys(e){return(await this.q("SELECT * FROM consenti_api_keys WHERE tenant_id=? ORDER BY created_at DESC",[e])).map(n=>this.mapApiKey(n))}async createTenant(e){let t=A(),n=In();await this.exec("INSERT INTO consenti_tenants (id, name, slug, created_at, updated_at) VALUES (?,?,?,?,?)",[t,e.name,e.slug,n,n]);let r=(await this.q("SELECT * FROM consenti_tenants WHERE id=?",[t]))[0];if(!r)throw new Error("Failed to create tenant");return{id:r.id,name:r.name,slug:r.slug,createdAt:r.created_at,updatedAt:r.updated_at}}async updateTenant(e,t){let n=In(),s=["updated_at=?"],r=[n];t.name!=null&&(s.push("name=?"),r.push(t.name)),t.slug!=null&&(s.push("slug=?"),r.push(t.slug)),r.push(e),await this.exec(`UPDATE consenti_tenants SET ${s.join(",")} WHERE id=?`,r);let a=(await this.q("SELECT * FROM consenti_tenants WHERE id=?",[e]))[0];if(!a)throw new Error(`Tenant ${e} not found`);return{id:a.id,name:a.name,slug:a.slug,createdAt:a.created_at,updatedAt:a.updated_at}}async getSettings(e){let n=(await this.q("SELECT allowed_origins_json, admin_allowed_origins_json, setup_completed, profiles_seeded, tcf_confirmation_json, gpp_confirmation_json FROM consenti_tenant_settings WHERE tenant_id=?",[e]))[0];return n?{allowedOrigins:JSON.parse(n.allowed_origins_json),adminAllowedOrigins:JSON.parse(n.admin_allowed_origins_json),setupCompleted:!!n.setup_completed,profilesSeeded:!!n.profiles_seeded,tcfConfirmation:n.tcf_confirmation_json?JSON.parse(n.tcf_confirmation_json):void 0,gppConfirmation:n.gpp_confirmation_json?JSON.parse(n.gpp_confirmation_json):void 0}:{}}async updateSettings(e,t){let s={...await this.getSettings(e),...t};return await this.exec(`INSERT INTO consenti_tenant_settings (tenant_id, allowed_origins_json, admin_allowed_origins_json, setup_completed, profiles_seeded, tcf_confirmation_json, gpp_confirmation_json, updated_at) VALUES (?,?,?,?,?,?,?,NOW())
111
+ ON DUPLICATE KEY UPDATE allowed_origins_json=VALUES(allowed_origins_json), admin_allowed_origins_json=VALUES(admin_allowed_origins_json), setup_completed=VALUES(setup_completed), profiles_seeded=VALUES(profiles_seeded), tcf_confirmation_json=VALUES(tcf_confirmation_json), gpp_confirmation_json=VALUES(gpp_confirmation_json), updated_at=NOW()`,[e,JSON.stringify(s.allowedOrigins??[]),JSON.stringify(s.adminAllowedOrigins??[]),s.setupCompleted?1:0,s.profilesSeeded?1:0,s.tcfConfirmation?JSON.stringify(s.tcfConfirmation):null,s.gppConfirmation?JSON.stringify(s.gppConfirmation):null]),this.getSettings(e)}async deleteTenant(e){await this.exec("DELETE FROM consenti_tenants WHERE id=?",[e])}async purgeExpiredConsents(e){let t=In(new Date(Date.now()-e*864e5)),n=await this.q("SELECT visitor_id FROM consenti_consent_records WHERE created_at < ?",[t]);if(n.length===0)return 0;let s=n.map(o=>o.visitor_id),r=s.map(()=>"?").join(",");return await this.exec(`DELETE FROM consenti_consent_history WHERE visitor_id IN (${r})`,s),await this.exec(`DELETE FROM consenti_consent_records WHERE visitor_id IN (${r})`,s),s.length}mapConsentTemplate(e){return{id:e.id,tenantId:e.tenant_id,name:e.name,cookies:JSON.parse(e.cookies_json),categories:JSON.parse(e.categories_json),createdAt:e.created_at,updatedAt:e.updated_at}}async createConsentTemplate(e){let t=G();await this.exec("INSERT INTO consenti_consent_templates (id, tenant_id, name, cookies_json, categories_json) VALUES (?,?,?,?,?)",[t,e.tenantId,e.name,JSON.stringify(e.cookies),JSON.stringify(e.categories)]);let n=await this.getConsentTemplate(t);if(!n)throw new Error("Consent template creation failed");return n}async updateConsentTemplate(e,t){let n=[],s=[];t.name!==void 0&&(n.push("name=?"),s.push(t.name)),t.cookies!==void 0&&(n.push("cookies_json=?"),s.push(JSON.stringify(t.cookies))),t.categories!==void 0&&(n.push("categories_json=?"),s.push(JSON.stringify(t.categories))),n.length>0&&await this.exec(`UPDATE consenti_consent_templates SET ${n.join(",")} WHERE id=?`,[...s,e]);let r=await this.getConsentTemplate(e);if(!r)throw new Error("Consent template not found");return r}async deleteConsentTemplate(e){await this.exec("DELETE FROM consenti_consent_templates WHERE id=?",[e])}async getConsentTemplate(e){let t=await this.q("SELECT * FROM consenti_consent_templates WHERE id=?",[e]);return t[0]?this.mapConsentTemplate(t[0]):null}async getConsentTemplates(e){return(await this.q("SELECT * FROM consenti_consent_templates WHERE tenant_id=? ORDER BY name ASC",[e])).map(n=>this.mapConsentTemplate(n))}async copyConsentTemplate(e,t){let n=await this.getConsentTemplate(e);if(!n)throw new Error("Consent template not found");return this.createConsentTemplate({tenantId:n.tenantId,name:t,cookies:n.cookies,categories:n.categories})}mapUITemplate(e){let t=JSON.parse(e.settings_json);return{id:e.id,tenantId:e.tenant_id,name:e.name,...t,createdAt:e.created_at,updatedAt:e.updated_at}}async createUITemplate(e){let t=q(),{tenantId:n,name:s,...r}=e;await this.exec("INSERT INTO consenti_ui_templates (id, tenant_id, name, settings_json) VALUES (?,?,?,?)",[t,n,s,JSON.stringify(r)]);let o=await this.getUITemplate(t);if(!o)throw new Error("UI template creation failed");return o}async updateUITemplate(e,t){let s=(await this.q("SELECT * FROM consenti_ui_templates WHERE id=?",[e]))[0];if(!s)throw new Error("UI template not found");let{name:r,...o}=t,a=[],c=[];if(r!==void 0&&(a.push("name=?"),c.push(r)),Object.keys(o).length>0){let d={...JSON.parse(s.settings_json),...o};a.push("settings_json=?"),c.push(JSON.stringify(d))}a.length>0&&await this.exec(`UPDATE consenti_ui_templates SET ${a.join(",")} WHERE id=?`,[...c,e]);let l=await this.getUITemplate(e);if(!l)throw new Error("UI template not found");return l}async deleteUITemplate(e){await this.exec("DELETE FROM consenti_ui_templates WHERE id=?",[e])}async getUITemplate(e){let t=await this.q("SELECT * FROM consenti_ui_templates WHERE id=?",[e]);return t[0]?this.mapUITemplate(t[0]):null}async getUITemplates(e){return(await this.q("SELECT * FROM consenti_ui_templates WHERE tenant_id=? ORDER BY name ASC",[e])).map(n=>this.mapUITemplate(n))}async copyUITemplate(e,t){let n=await this.getUITemplate(e);if(!n)throw new Error("UI template not found");let{tenantId:s,mainBanner:r,gpcBanner:o,preferenceModal:a}=n;return this.createUITemplate({tenantId:s,name:t,mainBanner:r,gpcBanner:o,preferenceModal:a})}async toProfileSummaries(e){if(e.length===0)return[];let t=[...new Set(e.map(r=>r.tenantId))],[n,s]=await Promise.all([Promise.all(t.map(r=>this.getConsentTemplates(r))),Promise.all(t.map(r=>this.getUITemplates(r)))]);return Ae(e,new Map(n.flat().map(r=>[r.id,r.name])),new Map(s.flat().map(r=>[r.id,r.name])))}async listProfilesSummary(e){let t=await this.q("SELECT * FROM consenti_profiles WHERE tenant_id=? ORDER BY created_at ASC",[e]);return this.toProfileSummaries(t.map(we))}async findProfilesUsingConsentTemplate(e){let t=await this.q("SELECT * FROM consenti_profiles WHERE JSON_UNQUOTE(JSON_EXTRACT(profile_json, '$.consentTemplateId'))=? ORDER BY name ASC",[e]);return this.toProfileSummaries(t.map(we))}async findProfilesUsingUITemplate(e){let t=await this.q("SELECT * FROM consenti_profiles WHERE JSON_UNQUOTE(JSON_EXTRACT(profile_json, '$.uiTemplateId'))=? ORDER BY name ASC",[e]);return this.toProfileSummaries(t.map(we))}async getOptInStats(e,t){let n="tenant_id=?",s=[e];t.profileId&&(n+=" AND profile_id=?",s.push(t.profileId)),t.from&&(n+=" AND created_at>=?",s.push(t.from)),t.to&&(n+=" AND created_at<=?",s.push(t.to)),t.locale&&(n+=" AND locale=?",s.push(t.locale));let r=await this.q(`SELECT locale, DATE_FORMAT(created_at, '%Y-%m-%d') AS day, consent_json FROM consenti_consent_records WHERE ${n} ORDER BY created_at ASC`,s);return ve(r.map(o=>({locale:o.locale,day:o.day,consentJson:JSON.parse(o.consent_json)})))}};function Ee(i){return{id:i.id,tenantId:i.tenant_id,name:i.name,defaultLocale:i.default_locale,version:i.version,profileJson:i.profile_json,createdAt:i.created_at,updatedAt:i.updated_at}}function wt(i){return{id:i.id,tenantId:i.tenant_id,visitorId:i.visitor_id,profileId:i.profile_id,...i.profile_version!=null?{profileVersion:i.profile_version}:{},locale:i.locale,consentJson:i.consent_json,gpcDetected:i.gpc_detected,source:i.source,createdAt:i.created_at,updatedAt:i.updated_at,...i.age_verified!=null?{ageVerified:i.age_verified}:{},...i.parental_consent_token!=null?{parentalConsentToken:i.parental_consent_token}:{},...i.tcf_string!=null?{tcfString:i.tcf_string}:{},...i.gpp_string!=null?{gppString:i.gpp_string}:{},...i.signature!=null?{signature:i.signature}:{}}}var _a="id, tenant_id, visitor_id, profile_id, locale, gpc_detected, source, age_verified, created_at, updated_at";function ha(i){return{id:i.id,tenantId:i.tenant_id,visitorId:i.visitor_id,profileId:i.profile_id,locale:i.locale,gpcDetected:i.gpc_detected,source:i.source,createdAt:i.created_at,updatedAt:i.updated_at,...i.age_verified!=null?{ageVerified:i.age_verified}:{}}}function ya(i){return{id:i.id,tenantId:i.tenant_id,consentRecordId:i.consent_record_id,visitorId:i.visitor_id,...i.profile_version!=null?{profileVersion:i.profile_version}:{},oldJson:i.old_json,newJson:i.new_json,action:i.action,createdAt:i.created_at}}function Et(i){return{id:i.id,tenantId:i.tenant_id,visitorId:i.visitor_id,...i.country!=null?{country:i.country}:{},...i.region!=null?{region:i.region}:{},...i.city!=null?{city:i.city}:{},...i.ip_hash!=null?{ipHash:i.ip_hash}:{},...i.user_agent_hash!=null?{userAgentHash:i.user_agent_hash}:{},firstSeen:i.first_seen,lastSeen:i.last_seen}}function Ve(i){return{id:i.id,tenantId:i.tenant_id,name:i.name,email:i.email,passwordHash:i.password_hash,isActive:i.is_active,createdAt:i.created_at,updatedAt:i.updated_at,totpEnabled:i.totp_enabled??!1,...i.totp_secret!=null?{totpSecret:i.totp_secret}:{}}}function We(i){return{id:i.id,tenantId:i.tenant_id,name:i.name,...i.description!=null?{description:i.description}:{}}}function ii(i){return{id:i.id,name:i.name,...i.description!=null?{description:i.description}:{}}}function ri(i){return{id:i.id,tenantId:i.tenant_id,action:i.action,resourceType:i.resource_type,...i.user_id!=null?{userId:i.user_id}:{},...i.resource_id!=null?{resourceId:i.resource_id}:{},...i.old_data!=null?{oldData:i.old_data}:{},...i.new_data!=null?{newData:i.new_data}:{},createdAt:i.created_at}}var wa="id, tenant_id, user_id, action, resource_type, resource_id, created_at";function Ea(i){return{id:i.id,tenantId:i.tenant_id,action:i.action,resourceType:i.resource_type,...i.user_id!=null?{userId:i.user_id}:{},...i.resource_id!=null?{resourceId:i.resource_id}:{},createdAt:i.created_at}}var Je=class{constructor(e){this.config=e}config;pool;async q(e,t=[]){return(await this.pool.query(e,t)).rows}async connect(){let t=(await import("pg")).default.Pool,n=this.config,s=n.uri?{connectionString:n.uri}:{host:n.host??"localhost",port:n.port??5432,user:n.user??"postgres",password:n.password??"",database:n.database??"consenti"};s.max=n.poolMax??5,n.statementTimeoutMs!=null&&(s.statement_timeout=n.statementTimeoutMs),n.idleInTransactionTimeoutMs!=null&&(s.idle_in_transaction_session_timeout=n.idleInTransactionTimeoutMs),this.pool=new t(s),await this.migrate()}async disconnect(){await this.pool.end()}async migrate(){let e=await this.pool.connect();await e.query("SELECT pg_advisory_lock(hashtext('consenti_schema'))");try{let t=await e.query("SELECT n AS name, to_regclass(n) IS NOT NULL AS found FROM unnest($1::text[]) AS n",[["consenti_schema_version",...ue]]),n=new Set(t.rows.filter(r=>r.found).map(r=>r.name));if(!n.has("consenti_schema_version")&&ue.every(r=>n.has(r)))throw Re("tables");let s=0;if(n.has("consenti_schema_version")&&(s=(await e.query("SELECT version FROM consenti_schema_version ORDER BY version DESC LIMIT 1")).rows[0]?.version??0),s===0){await e.query("BEGIN");try{for(let r of Qs.split(";").map(o=>o.trim()).filter(Boolean))await e.query(r);await this.seedTx(e),await e.query("INSERT INTO consenti_schema_version (version) VALUES ($1) ON CONFLICT DO NOTHING",[ee]),await e.query("COMMIT")}catch(r){throw await e.query("ROLLBACK"),new Error(`Creating the Consenti tables failed: ${r instanceof Error?r.message:String(r)}. The database user needs CREATE on the schema it connects to (normally "public" \u2014 the database owner has it) plus SELECT, INSERT, UPDATE and DELETE; it never creates databases, schemas or extensions. Nothing was changed (one transaction, rolled back): fix the cause and start again.`,{cause:r})}}else if(s<2)try{await this.migrateToV2(e)}catch(r){throw new Error(`Upgrading the database schema to v2 failed: ${r instanceof Error?r.message:String(r)}. The database user must own the tables (or be a superuser). Nothing was changed \u2014 the upgrade runs in one transaction and was rolled back; fix the cause and start again.`,{cause:r})}}finally{try{await e.query("SELECT pg_advisory_unlock(hashtext('consenti_schema'))")}catch{}e.release()}}async migrateToV2(e){await e.query("BEGIN");try{let t=await e.query(`SELECT c.conrelid::regclass::text AS tbl, c.conname AS name FROM pg_constraint c
112
+ WHERE c.contype = 'f' AND c.confrelid = to_regclass('consenti_profiles')
113
+ AND c.conrelid IN (to_regclass('consenti_consent_records'), to_regclass('consenti_notice_shown'))`);for(let n of t.rows)await e.query(`ALTER TABLE ${n.tbl} DROP CONSTRAINT "${n.name.replace(/"/g,'""')}"`);await e.query("ALTER TABLE consenti_consent_records ADD COLUMN IF NOT EXISTS profile_version INTEGER"),await e.query("ALTER TABLE consenti_consent_history ADD COLUMN IF NOT EXISTS profile_version INTEGER"),await e.query("INSERT INTO consenti_schema_version (version) VALUES ($1) ON CONFLICT DO NOTHING",[ee]),await e.query("COMMIT")}catch(t){throw await e.query("ROLLBACK"),t}}async seedTx(e){await e.query("INSERT INTO consenti_tenants (id, name, slug) VALUES ($1,$2,$3) ON CONFLICT DO NOTHING",[Y.id,Y.name,Y.slug]);for(let t of K)await e.query("INSERT INTO consenti_permissions (id, name, description) VALUES ($1,$2,$3) ON CONFLICT DO NOTHING",[t.id,t.name,t.description]);for(let t of re)await e.query("INSERT INTO consenti_roles (id, tenant_id, name, description) VALUES ($1,$2,$3,$4) ON CONFLICT DO NOTHING",[t.id,t.tenantId,t.name,t.description]);for(let{roleId:t,permissionId:n}of oe)await e.query("INSERT INTO consenti_role_permissions VALUES ($1,$2) ON CONFLICT DO NOTHING",[t,n])}async createProfile(e){let t=W(),s=(await this.q(`INSERT INTO consenti_profiles (id, tenant_id, name, default_locale, version, profile_json)
114
+ VALUES ($1,$2,$3,$4,$5,$6::json) RETURNING *`,[t,e.tenantId,e.name,e.defaultLocale,1,JSON.stringify(e.profileJson)]))[0];if(!s)throw new Error("Profile creation failed");return Ee(s)}async updateProfile(e,t){let n=["updated_at = NOW()"],s=[],r=1;t.name!=null&&(n.push(`name=$${r++}`),s.push(t.name)),t.defaultLocale!=null&&(n.push(`default_locale=$${r++}`),s.push(t.defaultLocale)),t.profileJson!=null&&(n.push(`profile_json=$${r++}::json`),s.push(JSON.stringify(t.profileJson))),t.version!=null&&(n.push(`version=$${r++}`),s.push(t.version)),s.push(e);let a=(await this.q(`UPDATE consenti_profiles SET ${n.join(",")} WHERE id=$${r} RETURNING *`,s))[0];if(!a)throw new Error(`Profile ${e} not found`);return Ee(a)}async deleteProfile(e){await this.q("DELETE FROM consenti_profiles WHERE id=$1",[e])}async getProfile(e){let t=await this.q("SELECT * FROM consenti_profiles WHERE id=$1",[e]);return t[0]?Ee(t[0]):null}async getProfiles(e){return(await this.q("SELECT * FROM consenti_profiles WHERE tenant_id=$1 ORDER BY created_at DESC",[e])).map(Ee)}async findActiveProfileByComplianceGroup(e,t){return(await this.getProfiles(e)).find(s=>(s.profileJson.complianceGroup===t||s.profileJson.customComplianceGroup===t)&&s.profileJson.isActive)??null}async createConsent(e){let t=J(),s=(await this.q(`INSERT INTO consenti_consent_records (id, tenant_id, visitor_id, profile_id, profile_version, locale, consent_json, gpc_detected, source, age_verified, parental_consent_token, tcf_string, gpp_string, signature)
115
+ VALUES ($1,$2,$3,$4,$5,$6,$7::jsonb,$8,$9,$10,$11,$12,$13,$14) RETURNING *`,[t,e.tenantId,e.visitorId,e.profileId,e.profileVersion??null,e.locale,JSON.stringify(e.consentJson),e.gpcDetected,e.source,e.ageVerified??!1,e.parentalConsentToken??null,e.tcfString??null,e.gppString??null,e.signature??null]))[0];if(!s)throw new Error("Consent creation failed");return await this.writeHistory(t,e.tenantId,e.visitorId,null,e.consentJson,"created",e.profileVersion??null),wt(s)}async updateConsent(e,t){let n=await this.getConsent(e);if(!n)throw new Error(`Consent for visitor ${e} not found`);let s=["consent_json=$1::jsonb","updated_at=NOW()"],r=[JSON.stringify(t.consentJson)],o=2;t.profileVersion!=null&&(s.push(`profile_version=$${o++}`),r.push(t.profileVersion)),t.locale!=null&&(s.push(`locale=$${o++}`),r.push(t.locale)),t.gpcDetected!=null&&(s.push(`gpc_detected=$${o++}`),r.push(t.gpcDetected)),t.signature!=null&&(s.push(`signature=$${o++}`),r.push(t.signature)),r.push(e);let c=(await this.q(`UPDATE consenti_consent_records SET ${s.join(",")} WHERE visitor_id=$${o} RETURNING *`,r))[0];if(!c)throw new Error(`Consent for visitor ${e} not found`);return await this.writeHistory(c.id,c.tenant_id,e,n.consentJson,t.consentJson,"updated",t.profileVersion??n.profileVersion??null),wt(c)}async deleteConsent(e){await this.q("DELETE FROM consenti_consent_history WHERE visitor_id=$1",[e]),await this.q("DELETE FROM consenti_consent_records WHERE visitor_id=$1",[e])}async getConsent(e){let t=await this.q("SELECT * FROM consenti_consent_records WHERE visitor_id=$1",[e]);return t[0]?wt(t[0]):null}async getConsents(e){let{where:t,params:n,nextIdx:s}=this.buildConsentWhere(e),r=e.page??1,o=e.limit??50,[a,c]=await Promise.all([this.q(`SELECT ${_a} ${t} ORDER BY created_at DESC LIMIT $${s} OFFSET $${s+1}`,[...n,o,(r-1)*o]),this.q(`SELECT COUNT(*)::int AS total ${t}`,n)]);return{items:a.map(ha),total:Number(c[0]?.total??0),page:r,limit:o}}async*streamConsents(e){let{where:t,params:n}=this.buildConsentWhere(e),s=await this.q(`SELECT * ${t} ORDER BY created_at ASC`,n);for(let r of s)yield wt(r)}buildConsentWhere(e){let t="FROM consenti_consent_records WHERE tenant_id=$1",n=[e.tenantId],s=2;return e.profileId!=null&&(t+=` AND profile_id=$${s++}`,n.push(e.profileId)),e.from!=null&&(t+=` AND created_at>=$${s++}`,n.push(e.from)),e.to!=null&&(t+=` AND created_at<=$${s++}`,n.push(e.to)),e.q&&(t+=` AND (visitor_id ILIKE $${s} OR profile_id ILIKE $${s} OR locale ILIKE $${s} OR source ILIKE $${s})`,n.push(x(e.q)),s++),{where:t,params:n,nextIdx:s}}async writeHistory(e,t,n,s,r,o,a){await this.q(`INSERT INTO consenti_consent_history (id, tenant_id, consent_record_id, visitor_id, profile_version, old_json, new_json, action)
116
+ VALUES ($1,$2,$3,$4,$5,$6::jsonb,$7::jsonb,$8)`,[A(),t,e,n,a,s?JSON.stringify(s):null,JSON.stringify(r),o])}async getConsentHistory(e){return(await this.q("SELECT * FROM consenti_consent_history WHERE visitor_id=$1 ORDER BY created_at ASC",[e])).map(ya)}async createNoticeShown(e){let t=A(),s=(await this.q(`INSERT INTO consenti_notice_shown (id, tenant_id, visitor_id, profile_id, locale)
117
+ VALUES ($1,$2,$3,$4,$5) RETURNING *`,[t,e.tenantId,e.visitorId,e.profileId,e.locale]))[0];return{id:s.id,tenantId:s.tenant_id,visitorId:s.visitor_id,profileId:s.profile_id,locale:s.locale,createdAt:s.created_at}}async getNoticeShownForVisitor(e){return(await this.q("SELECT * FROM consenti_notice_shown WHERE visitor_id=$1 ORDER BY created_at DESC",[e])).map(n=>({id:n.id,tenantId:n.tenant_id,visitorId:n.visitor_id,profileId:n.profile_id,locale:n.locale,createdAt:n.created_at}))}async createVisitor(e){let t=H(),s=(await this.q(`INSERT INTO consenti_visitors (id, tenant_id, visitor_id, country, region, city, ip_hash, user_agent_hash)
118
+ VALUES ($1,$2,$3,$4,$5,$6,$7,$8) RETURNING *`,[t,e.tenantId,e.visitorId,e.country??null,e.region??null,e.city??null,e.ipHash??null,e.userAgentHash??null]))[0];if(!s)throw new Error("Visitor creation failed");return Et(s)}async updateVisitor(e,t){let n=["last_seen=NOW()"],s=[],r=1;t.country!=null&&(n.push(`country=$${r++}`),s.push(t.country)),t.region!=null&&(n.push(`region=$${r++}`),s.push(t.region)),t.city!=null&&(n.push(`city=$${r++}`),s.push(t.city)),s.push(e);let a=(await this.q(`UPDATE consenti_visitors SET ${n.join(",")} WHERE visitor_id=$${r} RETURNING *`,s))[0];if(!a)throw new Error(`Visitor ${e} not found`);return Et(a)}async deleteVisitor(e){await this.q("DELETE FROM consenti_visitors WHERE visitor_id=$1",[e])}async getVisitor(e){let t=await this.q("SELECT * FROM consenti_visitors WHERE visitor_id=$1",[e]);return t[0]?Et(t[0]):null}async getVisitors(e){let t="FROM consenti_visitors WHERE tenant_id=$1",n=[e.tenantId],s=2;e.from!=null&&(t+=` AND first_seen>=$${s++}`,n.push(e.from)),e.to!=null&&(t+=` AND first_seen<=$${s++}`,n.push(e.to)),e.q&&(t+=` AND (visitor_id ILIKE $${s} OR country ILIKE $${s})`,n.push(x(e.q)),s++);let r=e.page??1,o=e.limit??50,[a,c]=await Promise.all([this.q(`SELECT * ${t} ORDER BY first_seen DESC LIMIT $${s} OFFSET $${s+1}`,[...n,o,(r-1)*o]),this.q(`SELECT COUNT(*)::int AS total ${t}`,n)]);return{items:a.map(Et),total:Number(c[0]?.total??0),page:r,limit:o}}async getOverviewStats(e){let[[t],[n],[s]]=await Promise.all([this.q("SELECT COUNT(*)::int AS total FROM consenti_consent_records WHERE tenant_id=$1",[e]),this.q("SELECT COUNT(*)::int AS total FROM consenti_consent_records WHERE tenant_id=$1 AND gpc_detected=true",[e]),this.q("SELECT COUNT(*)::int AS total FROM consenti_visitors WHERE tenant_id=$1",[e])]),r=Number(t?.total??0),o=Number(n?.total??0),a=Number(s?.total??0),c=await this.q(`SELECT val AS status, COUNT(*)::int AS count
119
+ FROM consenti_consent_records cr,
118
120
  jsonb_each_text(cr.consent_json) AS kv(key, val)
119
121
  WHERE cr.tenant_id=$1
120
122
  GROUP BY val`,[e]),l=0,d=0,u=0;for(let p of c)u+=Number(p.count),p.status==="granted"&&(l=Number(p.count)),p.status==="denied"&&(d=Number(p.count));return{totalConsents:r,acceptedPct:u>0?Math.round(l/u*100):0,rejectedPct:u>0?Math.round(d/u*100):0,totalVisitors:a,gpcDetectedCount:o}}async getCategoryStats(e){let t=await this.q(`SELECT kv.key AS cookie_id, kv.val AS status, COUNT(*)::int AS count
121
- FROM consent_records cr,
123
+ FROM consenti_consent_records cr,
122
124
  jsonb_each_text(cr.consent_json) AS kv(key, val)
123
125
  WHERE cr.tenant_id=$1
124
126
  GROUP BY kv.key, kv.val`,[e]),n={};for(let s of t){n[s.cookie_id]||(n[s.cookie_id]={granted:0,denied:0,objected:0});let r=n[s.cookie_id];r&&(s.status==="granted"&&(r.granted+=Number(s.count)),s.status==="denied"&&(r.denied+=Number(s.count)),s.status==="objected"&&(r.objected+=Number(s.count)))}return n}async getTimeline(e,t=30){let n=new Date(Date.now()-t*864e5).toISOString();return(await this.q(`SELECT to_char(created_at, 'YYYY-MM-DD') AS date, COUNT(*)::int AS count
125
- FROM consent_records
127
+ FROM consenti_consent_records
126
128
  WHERE tenant_id=$1 AND created_at>=$2
127
129
  GROUP BY to_char(created_at, 'YYYY-MM-DD')
128
- ORDER BY date ASC`,[e,n])).map(r=>({date:r.date,count:Number(r.count)}))}async createUser(e){let t=A(),s=(await this.q("INSERT INTO users (id, tenant_id, name, email, password_hash) VALUES ($1,$2,$3,$4,$5) RETURNING *",[t,e.tenantId,e.name,e.email,e.passwordHash]))[0];if(!s)throw new Error("User creation failed");return je(s)}async updateUser(e,t){let n=["updated_at=NOW()"],s=[],r=1;t.name!=null&&(n.push(`name=$${r++}`),s.push(t.name)),t.email!=null&&(n.push(`email=$${r++}`),s.push(t.email)),t.passwordHash!=null&&(n.push(`password_hash=$${r++}`),s.push(t.passwordHash)),t.isActive!=null&&(n.push(`is_active=$${r++}`),s.push(t.isActive)),s.push(e);let a=(await this.q(`UPDATE users SET ${n.join(",")} WHERE id=$${r} RETURNING *`,s))[0];if(!a)throw new Error(`User ${e} not found`);return je(a)}async deleteUser(e){await this.q("DELETE FROM user_roles WHERE user_id=$1",[e]),await this.q("DELETE FROM users WHERE id=$1",[e])}async getUserById(e){let t=await this.q("SELECT * FROM users WHERE id=$1",[e]);return t[0]?je(t[0]):null}async getUserByEmail(e){let t=await this.q("SELECT * FROM users WHERE email=$1",[e]);return t[0]?je(t[0]):null}async getUsers(e){return(await this.q("SELECT * FROM users WHERE tenant_id=$1 ORDER BY created_at DESC",[e])).map(je)}async countUsers(e){let t=e?await this.q("SELECT COUNT(*)::int AS total FROM users WHERE tenant_id=$1",[e]):await this.q("SELECT COUNT(*)::int AS total FROM users");return Number(t[0]?.total??0)}async getRoles(e){return(await this.q("SELECT * FROM roles WHERE tenant_id=$1",[e])).map(xe)}async createRole(e){let t=A(),s=(await this.q("INSERT INTO roles (id, tenant_id, name, description) VALUES ($1,$2,$3,$4) RETURNING *",[t,e.tenantId,e.name,e.description??null]))[0];if(!s)throw new Error("Role creation failed");return xe(s)}async updateRole(e,t){let n=[],s=[],r=1;if(t.name!=null&&(n.push(`name=$${r++}`),s.push(t.name)),t.description!=null&&(n.push(`description=$${r++}`),s.push(t.description)),n.length===0){let l=(await this.q("SELECT * FROM roles WHERE id=$1",[e]))[0];if(!l)throw new Error(`Role ${e} not found`);return xe(l)}s.push(e);let a=(await this.q(`UPDATE roles SET ${n.join(",")} WHERE id=$${r} RETURNING *`,s))[0];if(!a)throw new Error(`Role ${e} not found`);return xe(a)}async deleteRole(e){await this.q("DELETE FROM role_permissions WHERE role_id=$1",[e]),await this.q("DELETE FROM user_roles WHERE role_id=$1",[e]),await this.q("DELETE FROM roles WHERE id=$1",[e])}async getAllPermissions(){return(await this.q("SELECT * FROM permissions ORDER BY name")).map($s)}async getPermissionsForRole(e){return(await this.q("SELECT p.* FROM permissions p JOIN role_permissions rp ON p.id=rp.permission_id WHERE rp.role_id=$1",[e])).map($s)}async getUserRoles(e,t){return(t?await this.q("SELECT r.* FROM roles r JOIN user_roles ur ON r.id=ur.role_id WHERE ur.user_id=$1 AND ur.tenant_id=$2",[e,t]):await this.q("SELECT r.* FROM roles r JOIN user_roles ur ON r.id=ur.role_id WHERE ur.user_id=$1",[e])).map(xe)}async assignRole(e,t,n="default"){await this.q("INSERT INTO user_roles (user_id, role_id, tenant_id) VALUES ($1,$2,$3) ON CONFLICT DO NOTHING",[e,t,n])}async revokeRole(e,t,n){n?await this.q("DELETE FROM user_roles WHERE user_id=$1 AND role_id=$2 AND tenant_id=$3",[e,t,n]):await this.q("DELETE FROM user_roles WHERE user_id=$1 AND role_id=$2",[e,t])}async assignPermissionToRole(e,t){await this.q("INSERT INTO role_permissions (role_id, permission_id) VALUES ($1,$2) ON CONFLICT DO NOTHING",[e,t])}async revokePermissionFromRole(e,t){await this.q("DELETE FROM role_permissions WHERE role_id=$1 AND permission_id=$2",[e,t])}async createLog(e){await this.q(`INSERT INTO audit_logs (id, tenant_id, user_id, action, resource_type, resource_id, old_data, new_data)
129
- VALUES ($1,$2,$3,$4,$5,$6,$7::jsonb,$8::jsonb)`,[A(),e.tenantId,e.userId??null,e.action,e.resourceType,e.resourceId??null,e.oldData!=null?JSON.stringify(e.oldData):null,e.newData!=null?JSON.stringify(e.newData):null])}async getLogs(e){let{where:t,params:n,nextIdx:s}=this.buildAuditWhere(e),r=e.page??1,o=e.limit??50,[a,c]=await Promise.all([this.q(`SELECT ${Go} ${t} ORDER BY created_at DESC LIMIT $${s} OFFSET $${s+1}`,[...n,o,(r-1)*o]),this.q(`SELECT COUNT(*)::int AS total ${t}`,n)]);return{items:a.map(Ko),total:Number(c[0]?.total??0),page:r,limit:o}}async getAuditLogById(e){let t=await this.q("SELECT * FROM audit_logs WHERE id=$1",[e]);return t[0]?Fs(t[0]):null}async*streamAuditLogs(e){let{where:t,params:n}=this.buildAuditWhere(e),s=await this.q(`SELECT * ${t} ORDER BY created_at ASC`,n);for(let r of s)yield Fs(r)}buildAuditWhere(e){let t="FROM audit_logs WHERE tenant_id=$1",n=[e.tenantId],s=2;return e.action!=null&&(t+=` AND action=$${s++}`,n.push(e.action)),e.resourceType!=null&&(t+=` AND resource_type=$${s++}`,n.push(e.resourceType)),e.from!=null&&(t+=` AND created_at>=$${s++}`,n.push(e.from)),e.to!=null&&(t+=` AND created_at<=$${s++}`,n.push(e.to)),e.q&&(t+=` AND (action ILIKE $${s} OR resource_type ILIKE $${s} OR resource_id ILIKE $${s} OR user_id ILIKE $${s}) ESCAPE '\\'`,n.push(j(e.q)),s++),{where:t,params:n,nextIdx:s}}async getCountries(e){return(await this.q(`SELECT v.country, COUNT(*)::int AS count FROM consent_records cr
130
- JOIN visitors v ON v.visitor_id=cr.visitor_id
130
+ ORDER BY date ASC`,[e,n])).map(r=>({date:r.date,count:Number(r.count)}))}async createUser(e){let t=A(),s=(await this.q("INSERT INTO consenti_users (id, tenant_id, name, email, password_hash) VALUES ($1,$2,$3,$4,$5) RETURNING *",[t,e.tenantId,e.name,e.email,e.passwordHash]))[0];if(!s)throw new Error("User creation failed");return Ve(s)}async updateUser(e,t){let n=["updated_at=NOW()"],s=[],r=1;t.name!=null&&(n.push(`name=$${r++}`),s.push(t.name)),t.email!=null&&(n.push(`email=$${r++}`),s.push(t.email)),t.passwordHash!=null&&(n.push(`password_hash=$${r++}`),s.push(t.passwordHash)),t.isActive!=null&&(n.push(`is_active=$${r++}`),s.push(t.isActive)),s.push(e);let a=(await this.q(`UPDATE consenti_users SET ${n.join(",")} WHERE id=$${r} RETURNING *`,s))[0];if(!a)throw new Error(`User ${e} not found`);return Ve(a)}async deleteUser(e){await this.q("DELETE FROM consenti_user_roles WHERE user_id=$1",[e]),await this.q("DELETE FROM consenti_users WHERE id=$1",[e])}async getUserById(e){let t=await this.q("SELECT * FROM consenti_users WHERE id=$1",[e]);return t[0]?Ve(t[0]):null}async getUserByEmail(e){let t=await this.q("SELECT * FROM consenti_users WHERE email=$1",[e]);return t[0]?Ve(t[0]):null}async getUsers(e){return(await this.q("SELECT * FROM consenti_users WHERE tenant_id=$1 ORDER BY created_at DESC",[e])).map(Ve)}async countUsers(e){let t=e?await this.q("SELECT COUNT(*)::int AS total FROM consenti_users WHERE tenant_id=$1",[e]):await this.q("SELECT COUNT(*)::int AS total FROM consenti_users");return Number(t[0]?.total??0)}async getRoles(e){return(await this.q("SELECT * FROM consenti_roles WHERE tenant_id=$1",[e])).map(We)}async createRole(e){let t=A(),s=(await this.q("INSERT INTO consenti_roles (id, tenant_id, name, description) VALUES ($1,$2,$3,$4) RETURNING *",[t,e.tenantId,e.name,e.description??null]))[0];if(!s)throw new Error("Role creation failed");return We(s)}async updateRole(e,t){let n=[],s=[],r=1;if(t.name!=null&&(n.push(`name=$${r++}`),s.push(t.name)),t.description!=null&&(n.push(`description=$${r++}`),s.push(t.description)),n.length===0){let l=(await this.q("SELECT * FROM consenti_roles WHERE id=$1",[e]))[0];if(!l)throw new Error(`Role ${e} not found`);return We(l)}s.push(e);let a=(await this.q(`UPDATE consenti_roles SET ${n.join(",")} WHERE id=$${r} RETURNING *`,s))[0];if(!a)throw new Error(`Role ${e} not found`);return We(a)}async deleteRole(e){await this.q("DELETE FROM consenti_role_permissions WHERE role_id=$1",[e]),await this.q("DELETE FROM consenti_user_roles WHERE role_id=$1",[e]),await this.q("DELETE FROM consenti_roles WHERE id=$1",[e])}async getAllPermissions(){return(await this.q("SELECT * FROM consenti_permissions ORDER BY name")).map(ii)}async getPermissionsForRole(e){return(await this.q("SELECT p.* FROM consenti_permissions p JOIN consenti_role_permissions rp ON p.id=rp.permission_id WHERE rp.role_id=$1",[e])).map(ii)}async getUserRoles(e,t){return(t?await this.q("SELECT r.* FROM consenti_roles r JOIN consenti_user_roles ur ON r.id=ur.role_id WHERE ur.user_id=$1 AND ur.tenant_id=$2",[e,t]):await this.q("SELECT r.* FROM consenti_roles r JOIN consenti_user_roles ur ON r.id=ur.role_id WHERE ur.user_id=$1",[e])).map(We)}async assignRole(e,t,n="default"){await this.q("INSERT INTO consenti_user_roles (user_id, role_id, tenant_id) VALUES ($1,$2,$3) ON CONFLICT DO NOTHING",[e,t,n])}async revokeRole(e,t,n){n?await this.q("DELETE FROM consenti_user_roles WHERE user_id=$1 AND role_id=$2 AND tenant_id=$3",[e,t,n]):await this.q("DELETE FROM consenti_user_roles WHERE user_id=$1 AND role_id=$2",[e,t])}async assignPermissionToRole(e,t){await this.q("INSERT INTO consenti_role_permissions (role_id, permission_id) VALUES ($1,$2) ON CONFLICT DO NOTHING",[e,t])}async revokePermissionFromRole(e,t){await this.q("DELETE FROM consenti_role_permissions WHERE role_id=$1 AND permission_id=$2",[e,t])}async createLog(e){await this.q(`INSERT INTO consenti_audit_logs (id, tenant_id, user_id, action, resource_type, resource_id, old_data, new_data)
131
+ VALUES ($1,$2,$3,$4,$5,$6,$7::jsonb,$8::jsonb)`,[A(),e.tenantId,e.userId??null,e.action,e.resourceType,e.resourceId??null,e.oldData!=null?JSON.stringify(e.oldData):null,e.newData!=null?JSON.stringify(e.newData):null])}async getLogs(e){let{where:t,params:n,nextIdx:s}=this.buildAuditWhere(e),r=e.page??1,o=e.limit??50,[a,c]=await Promise.all([this.q(`SELECT ${wa} ${t} ORDER BY created_at DESC LIMIT $${s} OFFSET $${s+1}`,[...n,o,(r-1)*o]),this.q(`SELECT COUNT(*)::int AS total ${t}`,n)]);return{items:a.map(Ea),total:Number(c[0]?.total??0),page:r,limit:o}}async getAuditLogById(e){let t=await this.q("SELECT * FROM consenti_audit_logs WHERE id=$1",[e]);return t[0]?ri(t[0]):null}async*streamAuditLogs(e){let{where:t,params:n}=this.buildAuditWhere(e),s=await this.q(`SELECT * ${t} ORDER BY created_at ASC`,n);for(let r of s)yield ri(r)}buildAuditWhere(e){let t="FROM consenti_audit_logs WHERE tenant_id=$1",n=[e.tenantId],s=2;return e.action!=null&&(t+=` AND action=$${s++}`,n.push(e.action)),e.resourceType!=null&&(t+=` AND resource_type=$${s++}`,n.push(e.resourceType)),e.from!=null&&(t+=` AND created_at>=$${s++}`,n.push(e.from)),e.to!=null&&(t+=` AND created_at<=$${s++}`,n.push(e.to)),e.q&&(t+=` AND (action ILIKE $${s} OR resource_type ILIKE $${s} OR resource_id ILIKE $${s} OR user_id ILIKE $${s})`,n.push(x(e.q)),s++),{where:t,params:n,nextIdx:s}}async getCountries(e){return(await this.q(`SELECT v.country, COUNT(*)::int AS count FROM consenti_consent_records cr
132
+ JOIN consenti_visitors v ON v.visitor_id=cr.visitor_id
131
133
  WHERE cr.tenant_id=$1 AND v.country IS NOT NULL
132
- GROUP BY v.country ORDER BY count DESC LIMIT 50`,[e])).map(n=>({country:n.country,count:Number(n.count)}))}async getGpcStats(e){let[t]=await this.q("SELECT COUNT(*)::int AS total FROM consent_records WHERE tenant_id=$1",[e]),[n]=await this.q("SELECT COUNT(*)::int AS total FROM consent_records WHERE tenant_id=$1 AND gpc_detected=TRUE",[e]),s=Number(t?.total??0),r=Number(n?.total??0);return{detected:r,total:s,rate:s>0?Math.round(r/s*100):0}}async getTenants(){return(await this.q("SELECT * FROM tenants ORDER BY name ASC",[])).map(t=>({id:t.id,name:t.name,slug:t.slug,createdAt:t.created_at,updatedAt:t.updated_at}))}mapApiKey(e){return{id:e.id,tenantId:e.tenant_id,keyHash:e.key_hash,name:e.name,isActive:e.is_active,...e.created_by!==null?{createdBy:e.created_by}:{},...e.expire_by!==null?{expireBy:e.expire_by}:{},createdAt:e.created_at,...e.updated_at!==null?{updatedAt:e.updated_at}:{}}}async createApiKey(e){let t=A(),s=(await this.q("INSERT INTO api_keys (id, tenant_id, key_hash, name, created_by, expire_by) VALUES ($1,$2,$3,$4,$5,$6) RETURNING *",[t,e.tenantId,e.keyHash,e.name,e.createdBy??null,e.expireBy??null]))[0];if(!s)throw new Error("Failed to create API key");return this.mapApiKey(s)}async getApiKeyByHash(e){let n=(await this.q("SELECT * FROM api_keys WHERE key_hash=$1 AND is_active=TRUE",[e]))[0];return n?this.mapApiKey(n):null}async revokeApiKey(e){await this.q("UPDATE api_keys SET is_active=FALSE, updated_at=NOW() WHERE id=$1",[e])}async reactivateApiKey(e){await this.q("UPDATE api_keys SET is_active=TRUE, updated_at=NOW(), expire_by=CASE WHEN expire_by <= NOW() THEN NULL ELSE expire_by END WHERE id=$1",[e])}async deleteApiKey(e){await this.q("DELETE FROM api_keys WHERE id=$1",[e])}async getApiKeys(e){return(await this.q("SELECT * FROM api_keys WHERE tenant_id=$1 ORDER BY created_at DESC",[e])).map(n=>this.mapApiKey(n))}async createTenant(e){let t=A(),n=new Date().toISOString(),r=(await this.q("INSERT INTO tenants (id, name, slug, created_at, updated_at) VALUES ($1,$2,$3,$4,$5) RETURNING *",[t,e.name,e.slug,n,n]))[0];if(!r)throw new Error("Failed to create tenant");return{id:r.id,name:r.name,slug:r.slug,createdAt:r.created_at,updatedAt:r.updated_at}}async updateTenant(e,t){let n=new Date().toISOString(),s=["updated_at=$1"],r=[n],o=2;t.name!=null&&(s.push(`name=$${o++}`),r.push(t.name)),t.slug!=null&&(s.push(`slug=$${o++}`),r.push(t.slug)),r.push(e);let c=(await this.q(`UPDATE tenants SET ${s.join(",")} WHERE id=$${o} RETURNING *`,r))[0];if(!c)throw new Error(`Tenant ${e} not found`);return{id:c.id,name:c.name,slug:c.slug,createdAt:c.created_at,updatedAt:c.updated_at}}async deleteTenant(e){await this.q("DELETE FROM tenants WHERE id=$1",[e])}async getSettings(e){let n=(await this.q("SELECT allowed_origins_json, admin_allowed_origins_json, setup_completed, profiles_seeded, tcf_confirmation_json, gpp_confirmation_json FROM tenant_settings WHERE tenant_id=$1",[e]))[0];return n?{allowedOrigins:n.allowed_origins_json,adminAllowedOrigins:n.admin_allowed_origins_json,setupCompleted:n.setup_completed,profilesSeeded:n.profiles_seeded,...n.tcf_confirmation_json?{tcfConfirmation:n.tcf_confirmation_json}:{},...n.gpp_confirmation_json?{gppConfirmation:n.gpp_confirmation_json}:{}}:{}}async updateSettings(e,t){let s={...await this.getSettings(e),...t};return await this.q(`INSERT INTO tenant_settings (tenant_id, allowed_origins_json, admin_allowed_origins_json, setup_completed, profiles_seeded, tcf_confirmation_json, gpp_confirmation_json, updated_at) VALUES ($1,$2::jsonb,$3::jsonb,$4,$5,$6::jsonb,$7::jsonb,NOW())
133
- ON CONFLICT (tenant_id) DO UPDATE SET allowed_origins_json=$2::jsonb, admin_allowed_origins_json=$3::jsonb, setup_completed=$4, profiles_seeded=$5, tcf_confirmation_json=$6::jsonb, gpp_confirmation_json=$7::jsonb, updated_at=NOW()`,[e,JSON.stringify(s.allowedOrigins??[]),JSON.stringify(s.adminAllowedOrigins??[]),s.setupCompleted??!1,s.profilesSeeded??!1,s.tcfConfirmation?JSON.stringify(s.tcfConfirmation):null,s.gppConfirmation?JSON.stringify(s.gppConfirmation):null]),this.getSettings(e)}async purgeExpiredConsents(e){let t=new Date(Date.now()-e*864e5).toISOString(),n=await this.q("SELECT visitor_id FROM consent_records WHERE created_at < $1",[t]);if(n.length===0)return 0;let s=n.map(o=>o.visitor_id),r=s.map((o,a)=>`$${a+1}`).join(",");return await this.q(`DELETE FROM consent_history WHERE visitor_id IN (${r})`,s),await this.q(`DELETE FROM consent_records WHERE visitor_id IN (${r})`,s),s.length}mapConsentTemplate(e){return{id:e.id,tenantId:e.tenant_id,name:e.name,cookies:e.cookies_json,categories:e.categories_json,createdAt:e.created_at,updatedAt:e.updated_at}}async createConsentTemplate(e){let n=(await this.q(`INSERT INTO consent_templates (id, tenant_id, name, cookies_json, categories_json)
134
- VALUES ($1,$2,$3,$4::json,$5::json) RETURNING *`,[J(),e.tenantId,e.name,JSON.stringify(e.cookies),JSON.stringify(e.categories)]))[0];if(!n)throw new Error("Consent template creation failed");return this.mapConsentTemplate(n)}async updateConsentTemplate(e,t){let n=["updated_at = NOW()"],s=[],r=1;t.name!==void 0&&(n.push(`name=$${r++}`),s.push(t.name)),t.cookies!==void 0&&(n.push(`cookies_json=$${r++}::json`),s.push(JSON.stringify(t.cookies))),t.categories!==void 0&&(n.push(`categories_json=$${r++}::json`),s.push(JSON.stringify(t.categories)));let a=(await this.q(`UPDATE consent_templates SET ${n.join(",")} WHERE id=$${r} RETURNING *`,[...s,e]))[0];if(!a)throw new Error("Consent template not found");return this.mapConsentTemplate(a)}async deleteConsentTemplate(e){await this.q("DELETE FROM consent_templates WHERE id=$1",[e])}async getConsentTemplate(e){let t=await this.q("SELECT * FROM consent_templates WHERE id=$1",[e]);return t[0]?this.mapConsentTemplate(t[0]):null}async getConsentTemplates(e){return(await this.q("SELECT * FROM consent_templates WHERE tenant_id=$1 ORDER BY name ASC",[e])).map(n=>this.mapConsentTemplate(n))}async copyConsentTemplate(e,t){let n=await this.getConsentTemplate(e);if(!n)throw new Error("Consent template not found");return this.createConsentTemplate({tenantId:n.tenantId,name:t,cookies:n.cookies,categories:n.categories})}mapUITemplate(e){return{id:e.id,tenantId:e.tenant_id,name:e.name,...e.settings_json,createdAt:e.created_at,updatedAt:e.updated_at}}async createUITemplate(e){let{tenantId:t,name:n,...s}=e,o=(await this.q("INSERT INTO ui_templates (id, tenant_id, name, settings_json) VALUES ($1,$2,$3,$4::json) RETURNING *",[q(),t,n,JSON.stringify(s)]))[0];if(!o)throw new Error("UI template creation failed");return this.mapUITemplate(o)}async updateUITemplate(e,t){let n=await this.q("SELECT * FROM ui_templates WHERE id=$1",[e]);if(!n[0])throw new Error("UI template not found");let{name:s,...r}=t,o=["updated_at = NOW()"],a=[],c=1;s!==void 0&&(o.push(`name=$${c++}`),a.push(s)),Object.keys(r).length>0&&(o.push(`settings_json=$${c++}::json`),a.push(JSON.stringify({...n[0].settings_json,...r})));let d=(await this.q(`UPDATE ui_templates SET ${o.join(",")} WHERE id=$${c} RETURNING *`,[...a,e]))[0];if(!d)throw new Error("UI template not found");return this.mapUITemplate(d)}async deleteUITemplate(e){await this.q("DELETE FROM ui_templates WHERE id=$1",[e])}async getUITemplate(e){let t=await this.q("SELECT * FROM ui_templates WHERE id=$1",[e]);return t[0]?this.mapUITemplate(t[0]):null}async getUITemplates(e){return(await this.q("SELECT * FROM ui_templates WHERE tenant_id=$1 ORDER BY name ASC",[e])).map(n=>this.mapUITemplate(n))}async copyUITemplate(e,t){let n=await this.getUITemplate(e);if(!n)throw new Error("UI template not found");let{tenantId:s,mainBanner:r,gpcBanner:o,preferenceModal:a}=n;return this.createUITemplate({tenantId:s,name:t,mainBanner:r,gpcBanner:o,preferenceModal:a})}async toProfileSummaries(e){if(e.length===0)return[];let t=[...new Set(e.map(r=>r.tenantId))],[n,s]=await Promise.all([Promise.all(t.map(r=>this.getConsentTemplates(r))),Promise.all(t.map(r=>this.getUITemplates(r)))]);return Re(e,new Map(n.flat().map(r=>[r.id,r.name])),new Map(s.flat().map(r=>[r.id,r.name])))}async listProfilesSummary(e){let t=await this.q("SELECT * FROM profiles WHERE tenant_id=$1 ORDER BY created_at ASC",[e]);return this.toProfileSummaries(t.map(he))}async findProfilesUsingConsentTemplate(e){let t=await this.q("SELECT * FROM profiles WHERE profile_json->>'consentTemplateId'=$1 ORDER BY name ASC",[e]);return this.toProfileSummaries(t.map(he))}async findProfilesUsingUITemplate(e){let t=await this.q("SELECT * FROM profiles WHERE profile_json->>'uiTemplateId'=$1 ORDER BY name ASC",[e]);return this.toProfileSummaries(t.map(he))}async getOptInStats(e,t){let n="tenant_id=$1",s=[e];t.profileId&&(s.push(t.profileId),n+=` AND profile_id=$${s.length}`),t.from&&(s.push(t.from),n+=` AND created_at>=$${s.length}`),t.to&&(s.push(t.to),n+=` AND created_at<=$${s.length}`),t.locale&&(s.push(t.locale),n+=` AND locale=$${s.length}`);let r=await this.q(`SELECT locale, to_char(created_at, 'YYYY-MM-DD') AS day, consent_json FROM consent_records WHERE ${n} ORDER BY created_at ASC`,s);return Se(r.map(o=>({locale:o.locale,day:o.day,consentJson:o.consent_json})))}};import{readFile as Yo,writeFile as zo,rename as Qo,mkdir as Xo}from"fs/promises";import{randomUUID as le}from"crypto";var Zo=500;function mn(){return{_version:1,tenants:[],profiles:[],consents:[],consent_history:[],visitors:[],users:[],roles:[],permissions:[],user_roles:[],role_permissions:[],audit_logs:[],consent_templates:[],ui_templates:[],api_keys:[],settings:{},notice_shown:[]}}function gn(i,e,t){let n=Math.min(t,Zo),s=(e-1)*n;return i.slice(s,s+n)}function ea(i){let{id:e,tenantId:t,visitorId:n,profileId:s,locale:r,gpcDetected:o,source:a,ageVerified:c,createdAt:l,updatedAt:d}=i;return{id:e,tenantId:t,visitorId:n,profileId:s,locale:r,gpcDetected:o,source:a,...c!=null?{ageVerified:c}:{},createdAt:l,updatedAt:d}}function ta(i){let{id:e,tenantId:t,userId:n,action:s,resourceType:r,resourceId:o,createdAt:a}=i;return{id:e,tenantId:t,action:s,resourceType:r,createdAt:a,...n!=null?{userId:n}:{},...o!=null?{resourceId:o}:{}}}var ft=class{constructor(e){this.config=e}config;db=mn();filePath="";writeQueue=Promise.resolve();scheduleWrite(){this.writeQueue=this.writeQueue.then(async()=>{await Xo(this.filePath.replace(/\/[^/]+$/,""),{recursive:!0});let e=`${this.filePath}.tmp`;await zo(e,JSON.stringify(this.db),"utf8"),await Qo(e,this.filePath)}).catch(e=>{console.warn("[Consenti] JSON store write failed:",e)})}async connect(){let{dbPath:e}=ne(this.config.path??"./consenti-data","json");this.filePath=e;try{let t=await Yo(this.filePath,"utf8");this.db={...mn(),...JSON.parse(t)}}catch{this.db=mn()}await this.migrate()}async disconnect(){await this.writeQueue}async migrate(){this.seed()}seed(){let e=new Date().toISOString(),{id:t,name:n,slug:s}=K;this.db.tenants.find(r=>r.id===t)||this.db.tenants.push({id:t,name:n,slug:s,createdAt:e,updatedAt:e});for(let r of G)this.db.permissions.find(o=>o.id===r.id)||this.db.permissions.push({id:r.id,name:r.name,description:r.description});for(let r of se)this.db.roles.find(o=>o.id===r.id)||this.db.roles.push({id:r.id,tenantId:r.tenantId,name:r.name,description:r.description});for(let r of ie)this.db.role_permissions.find(o=>o.roleId===r.roleId&&o.permissionId===r.permissionId)||this.db.role_permissions.push(r);this.scheduleWrite()}async createProfile(e){let t=new Date().toISOString(),s={id:V(),tenantId:e.tenantId,name:e.name,defaultLocale:e.defaultLocale,version:1,profileJson:e.profileJson,createdAt:t,updatedAt:t};return this.db.profiles.push(s),this.scheduleWrite(),s}async updateProfile(e,t){let n=this.db.profiles.findIndex(o=>o.id===e);if(n===-1)throw new Error(`Profile ${e} not found`);let r={...this.db.profiles[n],...t.name!=null&&{name:t.name},...t.defaultLocale!=null&&{defaultLocale:t.defaultLocale},...t.profileJson!=null&&{profileJson:t.profileJson},...t.version!=null&&{version:t.version},updatedAt:new Date().toISOString()};return this.db.profiles[n]=r,this.scheduleWrite(),r}async deleteProfile(e){this.db.profiles=this.db.profiles.filter(t=>t.id!==e),this.scheduleWrite()}async getProfile(e){return this.db.profiles.find(t=>t.id===e)??null}async getProfiles(e){return this.db.profiles.filter(t=>t.tenantId===e).sort((t,n)=>t.createdAt.localeCompare(n.createdAt))}async findActiveProfileByComplianceGroup(e,t){return this.db.profiles.find(n=>n.tenantId===e&&(n.profileJson.complianceGroup===t||n.profileJson.customComplianceGroup===t)&&n.profileJson.isActive)??null}async createConsent(e){let t=new Date().toISOString(),n=W(),s={id:n,tenantId:e.tenantId,visitorId:e.visitorId,profileId:e.profileId,locale:e.locale,consentJson:e.consentJson,gpcDetected:e.gpcDetected??!1,source:e.source,...e.ageVerified!=null&&{ageVerified:e.ageVerified},...e.parentalConsentToken!=null&&{parentalConsentToken:e.parentalConsentToken},...e.tcfString!=null&&{tcfString:e.tcfString},...e.gppString!=null&&{gppString:e.gppString},...e.signature!=null&&{signature:e.signature},createdAt:t,updatedAt:t};return this.db.consents.push(s),this.db.consent_history.push({id:le(),tenantId:e.tenantId,consentRecordId:n,visitorId:e.visitorId,oldJson:null,newJson:e.consentJson,action:"created",createdAt:t}),this.scheduleWrite(),s}async updateConsent(e,t){let n=this.db.consents.findIndex(a=>a.visitorId===e);if(n===-1)throw new Error(`Consent for visitor ${e} not found`);let s=this.db.consents[n],r=new Date().toISOString(),o={...s,consentJson:t.consentJson,...t.locale!=null&&{locale:t.locale},...t.gpcDetected!=null&&{gpcDetected:t.gpcDetected},...t.signature!=null&&{signature:t.signature},updatedAt:r};return this.db.consents[n]=o,this.db.consent_history.push({id:le(),tenantId:s.tenantId,consentRecordId:s.id,visitorId:e,oldJson:s.consentJson,newJson:t.consentJson,action:"updated",createdAt:r}),this.scheduleWrite(),o}async deleteConsent(e){this.db.consent_history=this.db.consent_history.filter(t=>t.visitorId!==e),this.db.consents=this.db.consents.filter(t=>t.visitorId!==e),this.scheduleWrite()}async getConsent(e){return this.db.consents.find(t=>t.visitorId===e)??null}async getConsents(e){let t=this.db.consents.filter(r=>r.tenantId===e.tenantId);if(e.profileId&&(t=t.filter(r=>r.profileId===e.profileId)),e.from&&(t=t.filter(r=>r.createdAt>=e.from)),e.to&&(t=t.filter(r=>r.createdAt<=e.to)),e.q){let r=e.q.toLowerCase();t=t.filter(o=>o.visitorId.toLowerCase().startsWith(r)||o.profileId.toLowerCase().startsWith(r)||o.locale.toLowerCase().startsWith(r)||o.source.toLowerCase().startsWith(r))}t.sort((r,o)=>o.createdAt.localeCompare(r.createdAt));let n=e.page??1,s=e.limit??50;return{items:gn(t,n,s).map(ea),total:t.length,page:n,limit:s}}async*streamConsents(e){for(let t of this.db.consents.filter(n=>n.tenantId===e.tenantId))yield t}async getConsentHistory(e){return this.db.consent_history.filter(t=>t.visitorId===e).sort((t,n)=>t.createdAt.localeCompare(n.createdAt))}async createNoticeShown(e){let t={id:le(),tenantId:e.tenantId,visitorId:e.visitorId,profileId:e.profileId,locale:e.locale,createdAt:new Date().toISOString()};return this.db.notice_shown.push(t),this.scheduleWrite(),t}async getNoticeShownForVisitor(e){return this.db.notice_shown.filter(t=>t.visitorId===e).sort((t,n)=>n.createdAt.localeCompare(t.createdAt))}async createVisitor(e){let t=new Date().toISOString(),n={id:H(),tenantId:e.tenantId,visitorId:e.visitorId,firstSeen:t,lastSeen:t,...e.country!=null&&{country:e.country},...e.region!=null&&{region:e.region},...e.city!=null&&{city:e.city},...e.ipHash!=null&&{ipHash:e.ipHash},...e.userAgentHash!=null&&{userAgentHash:e.userAgentHash}};return this.db.visitors.push(n),this.scheduleWrite(),n}async updateVisitor(e,t){let n=this.db.visitors.findIndex(o=>o.visitorId===e);if(n===-1)throw new Error(`Visitor ${e} not found`);let r={...this.db.visitors[n],lastSeen:t.lastSeen??new Date().toISOString(),...t.country!=null&&{country:t.country},...t.region!=null&&{region:t.region},...t.city!=null&&{city:t.city}};return this.db.visitors[n]=r,this.scheduleWrite(),r}async deleteVisitor(e){this.db.visitors=this.db.visitors.filter(t=>t.visitorId!==e),this.scheduleWrite()}async getVisitor(e){return this.db.visitors.find(t=>t.visitorId===e)??null}async getVisitors(e){let t=this.db.visitors.filter(r=>r.tenantId===e.tenantId);if(e.from&&(t=t.filter(r=>r.firstSeen>=e.from)),e.to&&(t=t.filter(r=>r.firstSeen<=e.to)),e.q){let r=e.q.toLowerCase();t=t.filter(o=>o.visitorId.toLowerCase().startsWith(r)||(o.country?.toLowerCase().startsWith(r)??!1))}t.sort((r,o)=>o.lastSeen.localeCompare(r.lastSeen));let n=e.page??1,s=e.limit??50;return{items:gn(t,n,s),total:t.length,page:n,limit:s}}async createUser(e){let t=new Date().toISOString(),n={id:le(),tenantId:e.tenantId,name:e.name,email:e.email,passwordHash:e.passwordHash,isActive:!0,allowedTenants:e.allowedTenants??[],createdAt:t,updatedAt:t};return this.db.users.push(n),this.scheduleWrite(),n}async updateUser(e,t){let n=this.db.users.findIndex(o=>o.id===e);if(n===-1)throw new Error(`User ${e} not found`);let r={...this.db.users[n],...t.name!=null&&{name:t.name},...t.email!=null&&{email:t.email},...t.passwordHash!=null&&{passwordHash:t.passwordHash},...t.isActive!=null&&{isActive:t.isActive},..."totpSecret"in t&&t.totpSecret!=null&&{totpSecret:t.totpSecret},...t.totpEnabled!=null&&{totpEnabled:t.totpEnabled},..."allowedTenants"in t&&{allowedTenants:t.allowedTenants??[]},updatedAt:new Date().toISOString()};return this.db.users[n]=r,this.scheduleWrite(),r}async deleteUser(e){this.db.user_roles=this.db.user_roles.filter(t=>t.userId!==e),this.db.users=this.db.users.filter(t=>t.id!==e),this.scheduleWrite()}async getUserById(e){return this.db.users.find(t=>t.id===e)??null}async getUserByEmail(e){return this.db.users.find(t=>t.email===e)??null}async getUsers(e){return this.db.users.filter(t=>t.tenantId===e).sort((t,n)=>t.createdAt.localeCompare(n.createdAt))}async countUsers(e){return e?this.db.users.filter(t=>t.tenantId===e).length:this.db.users.length}async getRoles(e){return this.db.roles.filter(t=>t.tenantId===e)}async createRole(e){let t={id:le(),tenantId:e.tenantId,name:e.name,...e.description!=null&&{description:e.description}};return this.db.roles.push(t),this.scheduleWrite(),t}async updateRole(e,t){let n=this.db.roles.findIndex(r=>r.id===e);if(n===-1)throw new Error(`Role ${e} not found`);let s={...this.db.roles[n],...t.name!=null&&{name:t.name},...t.description!=null&&{description:t.description}};return this.db.roles[n]=s,this.scheduleWrite(),s}async deleteRole(e){this.db.role_permissions=this.db.role_permissions.filter(t=>t.roleId!==e),this.db.user_roles=this.db.user_roles.filter(t=>t.roleId!==e),this.db.roles=this.db.roles.filter(t=>t.id!==e),this.scheduleWrite()}async getAllPermissions(){return[...this.db.permissions].sort((e,t)=>e.name.localeCompare(t.name))}async getPermissionsForRole(e){let t=new Set(this.db.role_permissions.filter(n=>n.roleId===e).map(n=>n.permissionId));return this.db.permissions.filter(n=>t.has(n.id))}async getUserRoles(e,t){let n=this.db.user_roles.filter(r=>r.userId===e);t&&(n=n.filter(r=>r.tenantId===t));let s=new Set(n.map(r=>r.roleId));return this.db.roles.filter(r=>s.has(r.id))}async assignRole(e,t,n="default"){this.db.user_roles.find(s=>s.userId===e&&s.roleId===t&&s.tenantId===n)||(this.db.user_roles.push({userId:e,roleId:t,tenantId:n}),this.scheduleWrite())}async revokeRole(e,t,n){this.db.user_roles=this.db.user_roles.filter(s=>!(s.userId===e&&s.roleId===t&&(n==null||s.tenantId===n))),this.scheduleWrite()}async assignPermissionToRole(e,t){this.db.role_permissions.find(n=>n.roleId===e&&n.permissionId===t)||(this.db.role_permissions.push({roleId:e,permissionId:t}),this.scheduleWrite())}async revokePermissionFromRole(e,t){this.db.role_permissions=this.db.role_permissions.filter(n=>!(n.roleId===e&&n.permissionId===t)),this.scheduleWrite()}async createLog(e){let t={id:le(),tenantId:e.tenantId,action:e.action,resourceType:e.resourceType,createdAt:new Date().toISOString(),...e.userId!=null&&{userId:e.userId},...e.resourceId!=null&&{resourceId:e.resourceId},...e.oldData!=null&&{oldData:e.oldData},...e.newData!=null&&{newData:e.newData}};this.db.audit_logs.push(t),this.scheduleWrite()}async getLogs(e){let t=this.db.audit_logs.filter(r=>r.tenantId===e.tenantId);if(e.action&&(t=t.filter(r=>r.action===e.action)),e.resourceType&&(t=t.filter(r=>r.resourceType===e.resourceType)),e.from&&(t=t.filter(r=>r.createdAt>=e.from)),e.to&&(t=t.filter(r=>r.createdAt<=e.to)),e.q){let r=e.q.toLowerCase();t=t.filter(o=>o.action.toLowerCase().startsWith(r)||o.resourceType.toLowerCase().startsWith(r)||(o.resourceId?.toLowerCase().startsWith(r)??!1)||(o.userId?.toLowerCase().startsWith(r)??!1))}t.sort((r,o)=>o.createdAt.localeCompare(r.createdAt));let n=e.page??1,s=e.limit??50;return{items:gn(t,n,s).map(ta),total:t.length,page:n,limit:s}}async getAuditLogById(e){return this.db.audit_logs.find(t=>t.id===e)??null}async*streamAuditLogs(e){let t=this.db.audit_logs.filter(n=>n.tenantId===e.tenantId);e.action&&(t=t.filter(n=>n.action===e.action)),e.from&&(t=t.filter(n=>n.createdAt>=e.from)),e.to&&(t=t.filter(n=>n.createdAt<=e.to)),t.sort((n,s)=>s.createdAt.localeCompare(n.createdAt));for(let n of t)yield n}async getOverviewStats(e){let t=this.db.consents.filter(a=>a.tenantId===e),n=t.slice(-500),s=0,r=0;for(let a of n){let c=Object.values(a.consentJson),l=c.length>0?c.filter(d=>d==="granted").length/c.length:0;l>=.8?s++:l<=.2&&r++}let o=n.length||1;return{totalConsents:t.length,gpcDetectedCount:t.filter(a=>a.gpcDetected).length,totalVisitors:this.db.visitors.filter(a=>a.tenantId===e).length,acceptedPct:Math.round(s/o*100),rejectedPct:Math.round(r/o*100)}}async getCategoryStats(e){let t={};for(let n of this.db.consents.filter(s=>s.tenantId===e).slice(0,5e3))for(let[s,r]of Object.entries(n.consentJson))t[s]||(t[s]={granted:0,denied:0,objected:0}),r==="granted"?t[s].granted++:r==="denied"?t[s].denied++:r==="objected"&&t[s].objected++;return t}async getTimeline(e,t=30){let n=new Date(Date.now()-t*864e5).toISOString().slice(0,10),s=new Map;for(let r of this.db.consents.filter(o=>o.tenantId===e&&o.createdAt.slice(0,10)>=n)){let o=r.createdAt.slice(0,10);s.set(o,(s.get(o)??0)+1)}return[...s.entries()].sort(([r],[o])=>r.localeCompare(o)).map(([r,o])=>({date:r,count:o}))}async getCountries(e){let t=new Set(this.db.consents.filter(s=>s.tenantId===e).map(s=>s.visitorId)),n=new Map;for(let s of this.db.visitors.filter(r=>t.has(r.visitorId)&&r.country)){let r=s.country;n.set(r,(n.get(r)??0)+1)}return[...n.entries()].sort(([,s],[,r])=>r-s).slice(0,50).map(([s,r])=>({country:s,count:r}))}async getGpcStats(e){let t=this.db.consents.filter(r=>r.tenantId===e),n=t.filter(r=>r.gpcDetected).length,s=t.length;return{detected:n,total:s,rate:s>0?Math.round(n/s*100):0}}async getTenants(){return[...this.db.tenants].sort((e,t)=>e.name.localeCompare(t.name))}async createTenant(e){let t=new Date().toISOString(),n={id:le(),name:e.name,slug:e.slug,createdAt:t,updatedAt:t};return this.db.tenants.push(n),this.scheduleWrite(),n}async updateTenant(e,t){let n=this.db.tenants.findIndex(o=>o.id===e);if(n===-1)throw new Error(`Tenant ${e} not found`);let s=new Date().toISOString(),r={...this.db.tenants[n],...t.name!=null&&{name:t.name},...t.slug!=null&&{slug:t.slug},updatedAt:s};return this.db.tenants[n]=r,this.scheduleWrite(),r}async deleteTenant(e){this.db.tenants=this.db.tenants.filter(t=>t.id!==e),this.scheduleWrite()}async getSettings(e){return this.db.settings[e]??{}}async updateSettings(e,t){let n={...this.db.settings[e]??{},...t};return this.db.settings[e]=n,this.scheduleWrite(),n}async purgeExpiredConsents(e){let t=new Date(Date.now()-e*864e5).toISOString(),n=new Set(this.db.consents.filter(s=>s.createdAt<t).map(s=>s.visitorId));return n.size===0?0:(this.db.consent_history=this.db.consent_history.filter(s=>!n.has(s.visitorId)),this.db.consents=this.db.consents.filter(s=>!n.has(s.visitorId)),this.scheduleWrite(),n.size)}async createApiKey(e){let t=new Date().toISOString(),n={id:le(),tenantId:e.tenantId,keyHash:e.keyHash,name:e.name,isActive:!0,...e.createdBy!==void 0?{createdBy:e.createdBy}:{},...e.expireBy!==void 0?{expireBy:e.expireBy}:{},createdAt:t,updatedAt:t};return this.db.api_keys.push(n),this.scheduleWrite(),n}async getApiKeyByHash(e){return this.db.api_keys.find(t=>t.keyHash===e&&t.isActive)??null}async revokeApiKey(e){let t=this.db.api_keys.find(n=>n.id===e);t&&(t.isActive=!1,t.updatedAt=new Date().toISOString(),this.scheduleWrite())}async reactivateApiKey(e){let t=this.db.api_keys.find(n=>n.id===e);t&&(t.isActive=!0,t.expireBy&&new Date(t.expireBy)<=new Date&&delete t.expireBy,t.updatedAt=new Date().toISOString(),this.scheduleWrite())}async deleteApiKey(e){let t=this.db.api_keys.findIndex(n=>n.id===e);t!==-1&&(this.db.api_keys.splice(t,1),this.scheduleWrite())}async getApiKeys(e){return this.db.api_keys.filter(t=>t.tenantId===e).sort((t,n)=>n.createdAt.localeCompare(t.createdAt))}async createConsentTemplate(e){let t=new Date().toISOString(),n={id:J(),tenantId:e.tenantId,name:e.name,cookies:e.cookies,categories:e.categories,createdAt:t,updatedAt:t};return this.db.consent_templates.push(n),this.scheduleWrite(),n}async updateConsentTemplate(e,t){let n=this.db.consent_templates.findIndex(o=>o.id===e);if(n===-1)throw new Error("Consent template not found");let s=new Date().toISOString(),r={...this.db.consent_templates[n],...t.name!==void 0&&{name:t.name},...t.cookies!==void 0&&{cookies:t.cookies},...t.categories!==void 0&&{categories:t.categories},updatedAt:s};return this.db.consent_templates[n]=r,this.scheduleWrite(),r}async deleteConsentTemplate(e){this.db.consent_templates=this.db.consent_templates.filter(t=>t.id!==e),this.scheduleWrite()}async getConsentTemplate(e){return this.db.consent_templates.find(t=>t.id===e)??null}async getConsentTemplates(e){return this.db.consent_templates.filter(t=>t.tenantId===e).sort((t,n)=>t.name.localeCompare(n.name))}async copyConsentTemplate(e,t){let n=await this.getConsentTemplate(e);if(!n)throw new Error("Consent template not found");return this.createConsentTemplate({tenantId:n.tenantId,name:t,cookies:n.cookies,categories:n.categories})}async createUITemplate(e){let t=new Date().toISOString(),{tenantId:n,name:s,...r}=e,o={id:q(),tenantId:n,name:s,...r,createdAt:t,updatedAt:t};return this.db.ui_templates.push(o),this.scheduleWrite(),o}async updateUITemplate(e,t){let n=this.db.ui_templates.findIndex(c=>c.id===e);if(n===-1)throw new Error("UI template not found");let{name:s,...r}=t,o=new Date().toISOString(),a={...this.db.ui_templates[n],...t.name!==void 0&&{name:t.name},...r,updatedAt:o};return this.db.ui_templates[n]=a,this.scheduleWrite(),a}async deleteUITemplate(e){this.db.ui_templates=this.db.ui_templates.filter(t=>t.id!==e),this.scheduleWrite()}async getUITemplate(e){return this.db.ui_templates.find(t=>t.id===e)??null}async getUITemplates(e){return this.db.ui_templates.filter(t=>t.tenantId===e).sort((t,n)=>t.name.localeCompare(n.name))}async copyUITemplate(e,t){let n=await this.getUITemplate(e);if(!n)throw new Error("UI template not found");let{tenantId:s,mainBanner:r,gpcBanner:o,preferenceModal:a}=n;return this.createUITemplate({tenantId:s,name:t,...r!==void 0&&{mainBanner:r},...o!==void 0&&{gpcBanner:o},...a!==void 0&&{preferenceModal:a}})}profileToSummary(e){let t=e.profileJson,n=t.consentTemplateId?this.db.consent_templates.find(r=>r.id===t.consentTemplateId):null,s=t.uiTemplateId?this.db.ui_templates.find(r=>r.id===t.uiTemplateId):null;return{id:e.id,name:e.name,defaultLocale:e.defaultLocale,complianceGroup:t.complianceGroup??null,customComplianceGroup:t.customComplianceGroup??null,isActive:t.isActive??!1,consentTemplateName:n?.name??null,uiTemplateName:s?.name??null,createdAt:e.createdAt,updatedAt:e.updatedAt}}async listProfilesSummary(e){return this.db.profiles.filter(t=>t.tenantId===e).sort((t,n)=>t.createdAt.localeCompare(n.createdAt)).map(t=>this.profileToSummary(t))}async findProfilesUsingConsentTemplate(e){return this.db.profiles.filter(t=>t.profileJson.consentTemplateId===e).sort((t,n)=>t.name.localeCompare(n.name)).map(t=>this.profileToSummary(t))}async findProfilesUsingUITemplate(e){return this.db.profiles.filter(t=>t.profileJson.uiTemplateId===e).sort((t,n)=>t.name.localeCompare(n.name)).map(t=>this.profileToSummary(t))}async getOptInStats(e,t){let n=this.db.consents.filter(u=>u.tenantId===e);t.profileId&&(n=n.filter(u=>u.profileId===t.profileId)),t.from&&(n=n.filter(u=>u.createdAt>=t.from)),t.to&&(n=n.filter(u=>u.createdAt<=t.to)),t.locale&&(n=n.filter(u=>u.locale===t.locale));let s=0,r=0,o=0,a=0,c={},l=new Map;for(let u of n){let p=Object.values(u.consentJson),g=p.length>0?p.filter(R=>R==="granted").length/p.length:0,E=p.length>0?p.filter(R=>R==="denied").length/p.length:0;s++;let _=u.locale;c[_]||(c[_]={total:0,granted:0,denied:0,managed:0}),c[_].total++;let h=u.createdAt.slice(0,10),w=l.get(h)??{total:0,granted:0,denied:0,managed:0};w.total++,g>=.9?(r++,c[_].granted++,w.granted++):E>=.9?(o++,c[_].denied++,w.denied++):(a++,c[_].managed++,w.managed++),l.set(h,w)}let d=Array.from(l.entries()).sort(([u],[p])=>u.localeCompare(p)).map(([u,p])=>({date:u,...p}));return{total:s,granted:r,denied:o,managed:a,grantedPct:s>0?Math.round(r/s*1e3)/10:0,deniedPct:s>0?Math.round(o/s*1e3)/10:0,managedPct:s>0?Math.round(a/s*1e3)/10:0,byLocale:c,byDate:d}}};var _t=class{constructor(e){this.adapter=e}adapter;create(e){return this.adapter.createProfile(e)}update(e,t){return this.adapter.updateProfile(e,t)}delete(e){return this.adapter.deleteProfile(e)}get(e){return this.adapter.getProfile(e)}list(e){return this.adapter.getProfiles(e)}findActiveByComplianceGroup(e,t){return this.adapter.findActiveProfileByComplianceGroup(e,t)}};var ht=class{constructor(e){this.adapter=e}adapter;create(e){return this.adapter.createConsent(e)}update(e,t){return this.adapter.updateConsent(e,t)}delete(e){return this.adapter.deleteConsent(e)}get(e){return this.adapter.getConsent(e)}list(e){return this.adapter.getConsents(e)}};var yt=class{constructor(e){this.adapter=e}adapter;create(e){return this.adapter.createVisitor(e)}update(e,t){return this.adapter.updateVisitor(e,t)}delete(e){return this.adapter.deleteVisitor(e)}get(e){return this.adapter.getVisitor(e)}};var wt=class{constructor(e){this.adapter=e}adapter;create(e){return this.adapter.createNoticeShown(e)}getForVisitor(e){return this.adapter.getNoticeShownForVisitor(e)}};var Et=class{constructor(e){this.adapter=e}adapter;log(e){return this.adapter.createLog(e)}list(e){return this.adapter.getLogs(e)}get(e){return this.adapter.getAuditLogById(e)}};var Rt=class{constructor(e,t=500,n=6e4){this.ttlMs=e;this.maxEntries=t;this.sweepTimer=setInterval(()=>this.sweep(),n),this.sweepTimer.unref?.()}ttlMs;maxEntries;store=new Map;sweepTimer=null;get(e){let t=this.store.get(e);if(t){if(Date.now()>t.expiresAt){this.store.delete(e);return}return t.value}}set(e,t){if(this.store.size>=this.maxEntries&&!this.store.has(e)){let n=this.store.keys().next().value;n!==void 0&&this.store.delete(n)}this.store.set(e,{value:t,expiresAt:Date.now()+this.ttlMs})}sweep(){let e=Date.now();for(let[t,n]of this.store)e>n.expiresAt&&this.store.delete(t)}dispose(){this.sweepTimer&&clearInterval(this.sweepTimer),this.sweepTimer=null}};var na=3e4,St=class{constructor(e){this.storage=e}storage;cache=new Rt(na);async getOverview(e){return this.cached(`overview:${e}`,()=>this.storage.getOverviewStats(e))}async getCategories(e){return this.cached(`categories:${e}`,()=>this.storage.getCategoryStats(e))}async getTimeline(e,t){return this.cached(`timeline:${e}:${t}`,()=>this.storage.getTimeline(e,t))}async getCountries(e){return this.cached(`countries:${e}`,()=>this.storage.getCountries(e))}async getGpc(e){return this.cached(`gpc:${e}`,()=>this.storage.getGpcStats(e))}async getTenants(){return this.storage.getTenants()}dispose(){this.cache.dispose()}async cached(e,t){let n=this.cache.get(e);if(n!==void 0)return n;let s=await t();return this.cache.set(e,s),s}};var Ct=class{constructor(e){this.adapter=e}adapter;getById(e){return this.adapter.getUserById(e)}getByEmail(e){return this.adapter.getUserByEmail(e)}list(e){return this.adapter.getUsers(e)}create(e){return this.adapter.createUser(e)}update(e,t){return this.adapter.updateUser(e,t)}delete(e){return this.adapter.deleteUser(e)}getRoles(e,t){return this.adapter.getUserRoles(e,t)}assignRole(e,t,n){return this.adapter.assignRole(e,t,n)}revokeRole(e,t,n){return this.adapter.revokeRole(e,t,n)}};import{readdirSync as vt,readFileSync as ei,existsSync as Pt,statSync as ti,lstatSync as Ta}from"fs";import{join as Y}from"path";import{Worker as ya}from"worker_threads";import{fileURLToPath as wa}from"url";import{join as Ea,dirname as Ra}from"path";import{existsSync as Sa}from"fs";import{writeFileSync as oa,renameSync as Gs,mkdirSync as Ks,copyFileSync as aa,rmSync as Ys,existsSync as Fe,readFileSync as ca,lstatSync as zs,symlinkSync as la,unlinkSync as da}from"fs";import{join as re,resolve as ua}from"path";import{randomUUID as pa}from"crypto";import{createHmac as Hs,createHash as sa}from"crypto";function fn(i){return sa("sha256").update(i).digest("hex")}function It(i,e){return Hs("sha256",i).update(e).digest()}function ia(i){let e=i.toISOString().replace(/[-:]/g,"").slice(0,15)+"Z";return{amzDate:e,dateStamp:e.slice(0,8)}}function ra(i,e,t,n){let s=It(`AWS4${i}`,e),r=It(s,t),o=It(r,n);return It(o,"aws4_request")}function Bs(i){let{method:e,key:t,body:n,contentType:s,config:r}=i,o=new Date,{amzDate:a,dateStamp:c}=ia(o),l=`${r.bucketName}.s3.${r.region}.amazonaws.com`,d=fn(n?Buffer.isBuffer(n)?n:Buffer.from(n,"utf8"):""),u={host:l,"x-amz-date":a,"x-amz-content-sha256":d};r.sessionToken&&(u["x-amz-security-token"]=r.sessionToken),s&&e==="PUT"&&(u["content-type"]=s);let p=Object.keys(u).sort(),g=p.map(O=>`${O}:${u[O]}
135
- `).join(""),E=p.join(";"),_=[e,`/${encodeURIComponent(t).replace(/%2F/g,"/")}`,"",g,E,d].join(`
136
- `),h=`${c}/${r.region}/s3/aws4_request`,w=["AWS4-HMAC-SHA256",a,h,fn(_)].join(`
137
- `),R=ra(r.secretAccessKey,c,r.region,"s3"),I=Hs("sha256",R).update(w).digest("hex"),T=[`AWS4-HMAC-SHA256 Credential=${r.accessKeyId}/${h}`,`SignedHeaders=${E}`,`Signature=${I}`].join(", ");return{...u,authorization:T}}function Vs(i,e){let t=encodeURIComponent(i).replace(/%2F/g,"/");return`https://${e.bucketName}.s3.${e.region}.amazonaws.com/${t}`}async function Tt(i,e,t,n="application/json"){let s=Bs({method:"PUT",key:i,body:e,contentType:n,config:t}),r=typeof e=="string"?e:e.toString("utf8"),o=await fetch(Vs(i,t),{method:"PUT",headers:{...s,"content-length":String(Buffer.byteLength(r,"utf8"))},body:r});if(!o.ok){let a=await o.text().catch(()=>"");throw new Error(`S3 PUT ${i} failed: ${o.status} ${a}`)}}async function Ws(i,e){let t=Bs({method:"DELETE",key:i,config:e}),n=await fetch(Vs(i,e),{method:"DELETE",headers:t});if(!n.ok&&n.status!==404){let s=await n.text().catch(()=>"");throw new Error(`S3 DELETE ${i} failed: ${n.status} ${s}`)}}function hn(i,e){return re(i,"profiles",e)}function _n(i,e,t,n){return re(hn(i,e),t,String(n))}function Qs(i,e,t){return re(hn(i,e),t)}function At(i,e,t){return`profiles/${i}/${e}/${t}.json`}function Js(i,e){let t=i+".tmp";oa(t,e,"utf8"),JSON.parse(e),Gs(t,i)}var ma=["gpcMode","complianceGroup","darkMode","allowReceipt","enhanceAccessibility","showFooterMetadata","allowedOrigins","complianceConfig","dpdpa","ageGate","hidePoweredBy"];function ga(i){let e={};for(let t of ma){let n=i[t];n!==void 0&&(e[t]=n)}return e}async function qs(i){let{storagePath:e,tenantId:t,profileId:n,version:s,profile:r,localeContent:o,previousVersion:a,s3Config:c}=i,l=_n(e,t,n,s);Ks(l,{recursive:!0});let{locales:d,defaultLocale:u,cookies:p,expiryDays:g}=r;for(let E of d){let _,h=o[E];if(h){let R={id:r.id,version:s,defaultLocale:u,currentLocale:E,locales:d,cookies:p,...g!==void 0?{expiryDays:g}:{},...ga(r),mainBanner:h.mainBanner,...h.gpcBanner?{gpcBanner:h.gpcBanner}:{},preferenceModal:h.preferenceModal,...h.ageGateModal?{ageGateModal:h.ageGateModal}:{}};_=JSON.stringify(R,null,2)}else if(a!==void 0){let R=re(_n(e,t,n,a),`${E}.json`);if(!Fe(R))continue;_=ca(R,"utf8")}else continue;let w=re(l,`${E}.json`);Js(w,_),E===u&&Js(re(l,"default.json"),_),c?.enabled&&(await Tt(At(t,`${n}/${s}`,E),_,c),E===u&&await Tt(At(t,`${n}/${s}`,"default"),_,c))}if(!Fe(re(l,"default.json"))){let E=d.includes(u)?u:d[0]??"";if(E){let _=re(l,`${E}.json`);Fe(_)&&aa(_,re(l,"default.json"))}}}function fa(i){Fe(i)&&(zs(i).isSymbolicLink()||Ys(i,{recursive:!0,force:!0}))}async function _a(i){let{storagePath:e,tenantId:t,profileId:n,version:s,complianceGroup:r,s3Config:o}=i,a=ua(_n(e,t,n,s)),c=Qs(e,t,r);Ks(hn(e,t),{recursive:!0}),fa(c);let l=`${c}.new-${pa().slice(0,8)}`;if(la(a,l,"junction"),Gs(l,c),o?.enabled){let d=JSON.stringify({profileId:n,version:s});await Tt(At(t,r,"pointer"),d,o)}}async function ha(i){let{storagePath:e,tenantId:t,complianceGroup:n,s3Config:s}=i,r=Qs(e,t,n);Fe(r)&&(zs(r).isSymbolicLink()?da(r):Ys(r,{recursive:!0,force:!0})),s?.enabled&&await Ws(At(t,n,"pointer"),s)}async function Xs(i){i.action==="write"?await qs(i):i.action==="activate"?(await qs(i),await _a(i)):i.action==="deactivate"&&await ha(i)}var Ca=Ra(wa(import.meta.url)),Zs=Ea(Ca,"profile-json.worker.js"),Ia=!Sa(Zs);async function yn(i){if(Ia){await Xs(i);return}return new Promise((e,t)=>{let n=new ya(Zs);n.postMessage(i),n.once("message",s=>{n.terminate(),s.ok?e():t(new Error(`[profile-worker] ${s.action} failed: ${s.error}`))}),n.once("error",s=>{n.terminate(),t(s)})})}function wn(i,e){if(!e)return i;let t={...i};for(let[n,s]of Object.entries(e))t[n]&&(t[n]={...t[n],...s});return t}function ni(i){return{...i.gpcMode!==void 0?{gpcMode:i.gpcMode}:{},...i.complianceGroup!==void 0?{complianceGroup:i.complianceGroup}:{},...i.darkMode!==void 0?{darkMode:i.darkMode}:{},...i.allowReceipt!==void 0?{allowReceipt:i.allowReceipt}:{},...i.enhanceAccessibility!==void 0?{enhanceAccessibility:i.enhanceAccessibility}:{},...i.showFooterMetadata!==void 0?{showFooterMetadata:i.showFooterMetadata}:{},...i.allowedOrigins!==void 0?{allowedOrigins:i.allowedOrigins}:{},...i.complianceConfig!==void 0?{complianceConfig:i.complianceConfig}:{},...i.dpdpa!==void 0?{dpdpa:i.dpdpa}:{},...i.ageGate!==void 0?{ageGate:i.ageGate}:{},...i.hidePoweredBy!==void 0?{hidePoweredBy:i.hidePoweredBy}:{}}}var Ot=class{constructor(e,t,n="default",s,r,o,a,c,l){this.profiles=e;this.audit=t;this.tenantId=n;this.storage=s;this.eventBus=r;this.localeCache=o;this.profilesDir=a;this.handleCache=c;this.s3Config=l}profiles;audit;tenantId;storage;eventBus;localeCache;profilesDir;handleCache;s3Config;versionsRootDir(e){return Y(this.profilesDir??"",this.tenantId,e)}complianceGroupDir(e){return Y(this.profilesDir??"",this.tenantId,e)}groupKey(e){return e.complianceGroup||e.customComplianceGroup||""}profileFilePaths(e,t){let n=this.complianceGroupDir(e);return t.map(s=>Y(n,`${s}.json`)).concat(Y(n,"default.json"))}async resolveCookies(e){let t=e.cookies??{};if(e.consentTemplateId&&this.storage){let n=await this.storage.getConsentTemplate(e.consentTemplateId);n&&(t=n.cookies)}return wn(t,e.cookiesOverride)}async writeLocaleFiles(e,t,n,s){if(!this.profilesDir)return;let r=e.profileJson,o=await this.resolveCookies(r),a=r.locales?.length?r.locales:[e.defaultLocale],c={[e.defaultLocale]:{mainBanner:r.mainBanner,...r.gpcBanner?{gpcBanner:r.gpcBanner}:{},preferenceModal:r.preferenceModal,...r.ageGateModal?{ageGateModal:r.ageGateModal}:{}},...t??{}},l=r.isActive??!1,u={action:l?"activate":"write",storagePath:Y(this.profilesDir,".."),tenantId:this.tenantId,profileId:e.id,version:e.version,complianceGroup:n,profile:{id:e.id,defaultLocale:e.defaultLocale,locales:a,cookies:o,...r.expiryDays!==void 0?{expiryDays:r.expiryDays}:{},...ni(r)},localeContent:c,...s!==void 0?{previousVersion:s}:{},...this.s3Config?.enabled?{s3Config:this.s3Config}:{}};if(await yn(u),l){let p=this.profileFilePaths(n,a);this.handleCache?.(p,e.id,!0),this.eventBus?.emit("cache:purge",{paths:p,profileId:e.id})}}async create(e){let{localeContent:t,...n}=e,s=await this.profiles.create({...n,tenantId:this.tenantId});if(await this.audit.log({tenantId:this.tenantId,action:"profile:created",resourceType:"profile",resourceId:s.id,newData:{profileId:s.id,version:s.version,complianceGroup:this.groupKey(s.profileJson)}}),this.eventBus?.emit("profile:created",s),this.profilesDir){let r=this.groupKey(s.profileJson);await this.writeLocaleFiles(s,t,r,void 0);let o=s.profileJson.locales?.length?s.profileJson.locales:[s.defaultLocale],a=this.profileFilePaths(r,o);this.handleCache?.(a,s.id,!1),this.eventBus?.emit("cache:warm",{paths:a,profileId:s.id})}return s}async copy(e,t){let n=await this.profiles.get(e);if(!n)throw new Error(`Profile ${e} not found`);let s=n.profileJson.locales?.length?n.profileJson.locales:[n.defaultLocale],r={};for(let o of s){if(o===n.defaultLocale)continue;let a=await this.getVersionFile(e,String(n.version),o);if(a)try{let c=JSON.parse(a);r[o]={mainBanner:c.mainBanner,...c.gpcBanner?{gpcBanner:c.gpcBanner}:{},preferenceModal:c.preferenceModal}}catch{}}return this.create({name:t??`Copy of ${n.name}`,defaultLocale:n.defaultLocale,profileJson:{...n.profileJson,isActive:!1},...Object.keys(r).length>0?{localeContent:r}:{}})}async update(e,t){let n=await this.profiles.get(e);if(!n)throw new Error(`Profile ${e} not found`);let{localeContent:s,...r}=t,o=r.profileJson?.isActive??n.profileJson.isActive,a=r.profileJson?{...r.profileJson,...o!==void 0?{isActive:o}:{}}:n.profileJson,c=await this.profiles.update(e,{name:r.name??n.name,defaultLocale:r.defaultLocale??n.defaultLocale,profileJson:a,version:n.version+1});if(this.localeCache?.invalidate(e),await this.audit.log({tenantId:this.tenantId,action:"profile:updated",resourceType:"profile",resourceId:c.id,oldData:{profileId:n.id,version:n.version},newData:{profileId:c.id,version:c.version,complianceGroup:this.groupKey(c.profileJson)}}),this.eventBus?.emit("profile:updated",{previous:n,current:c}),this.profilesDir){let l=this.groupKey(c.profileJson);await this.writeLocaleFiles(c,s,l,n.version)}return c}async delete(e){let t=await this.profiles.get(e);if(await this.profiles.delete(e),this.localeCache?.invalidate(e),await this.audit.log({tenantId:this.tenantId,action:"profile:deleted",resourceType:"profile",resourceId:e,...t!=null?{oldData:{profileId:t.id,version:t.version}}:{}}),this.eventBus?.emit("profile:deleted",{id:e,previous:t}),this.profilesDir&&t){let n=this.groupKey(t.profileJson);if((t.profileJson.isActive??!1)&&n){let r=t.profileJson.locales?.length?t.profileJson.locales:[t.defaultLocale];await this.runDeactivate(n);let o=this.profileFilePaths(n,r);this.handleCache?.(o,e,!0),this.eventBus?.emit("cache:purge",{paths:o,profileId:e})}}}async activate(e){let t=await this.profiles.get(e);if(!t)throw new Error(`Profile ${e} not found`);let n=this.groupKey(t.profileJson),s=await this.profiles.update(e,{profileJson:{...t.profileJson,isActive:!0}});return this.localeCache?.invalidate(e),this.profilesDir&&n&&await this.writeLocaleFiles(s,void 0,n,t.version),await this.audit.log({tenantId:this.tenantId,action:"profile.activated",resourceType:"profile",resourceId:e}),this.eventBus?.emit("profile.activated",s),s}async deactivate(e){let t=await this.profiles.get(e);if(!t)throw new Error(`Profile ${e} not found`);let n=this.groupKey(t.profileJson),s=await this.profiles.update(e,{profileJson:{...t.profileJson,isActive:!1}});if(this.localeCache?.invalidate(e),this.profilesDir&&n){let r=t.profileJson.locales?.length?t.profileJson.locales:[t.defaultLocale];await this.runDeactivate(n);let o=this.profileFilePaths(n,r);this.handleCache?.(o,e,!0),this.eventBus?.emit("cache:purge",{paths:o,profileId:e})}return await this.audit.log({tenantId:this.tenantId,action:"profile.deactivated",resourceType:"profile",resourceId:e}),this.eventBus?.emit("profile.deactivated",s),s}async runDeactivate(e){if(!this.profilesDir)return;let t={action:"deactivate",storagePath:Y(this.profilesDir,".."),tenantId:this.tenantId,profileId:"",version:0,complianceGroup:e,profile:{id:"",defaultLocale:"",locales:[],cookies:{}},localeContent:{},...this.s3Config?.enabled?{s3Config:this.s3Config}:{}};await yn(t)}async get(e){return this.profiles.get(e)}async getResolved(e,t){let n=await this.profiles.get(e);if(!n)return null;let s=t??n.defaultLocale;if(this.localeCache){let u=this.localeCache.read(e,s);if(u)return u}let r=n.profileJson,o=await this.resolveCookies(r),a=r.locales?.length?r.locales:[n.defaultLocale],l={mainBanner:r.mainBanner,...r.gpcBanner?{gpcBanner:r.gpcBanner}:{},preferenceModal:r.preferenceModal,...r.ageGateModal?{ageGateModal:r.ageGateModal}:{}};if(s!==n.defaultLocale){let u=await this.getVersionFile(e,String(n.version),s);if(u)try{let p=JSON.parse(u);l={mainBanner:p.mainBanner,...p.gpcBanner?{gpcBanner:p.gpcBanner}:{},preferenceModal:p.preferenceModal,...p.ageGateModal?{ageGateModal:p.ageGateModal}:{}}}catch{}}let d={id:n.id,version:n.version,defaultLocale:n.defaultLocale,currentLocale:s,locales:a,cookies:o,...r.expiryDays!==void 0?{expiryDays:r.expiryDays}:{},...ni(r),...l};if(this.localeCache)try{this.localeCache.write(e,s,d)}catch(u){console.warn(`[Consenti] locale cache write failed for ${e}/${s}:`,u)}return d}findActiveByComplianceGroup(e){return this.profiles.findActiveByComplianceGroup(this.tenantId,e)}list(){return this.profiles.list(this.tenantId)}listSummary(){return this.storage?this.storage.listProfilesSummary(this.tenantId):Promise.resolve([])}async listVersions(e){if(!this.profilesDir)return[];let t=this.versionsRootDir(e);if(!Pt(t))return[];let n=[];try{let s=vt(t,{withFileTypes:!0});for(let r of s){if(!r.isDirectory())continue;let o=Number(r.name);if(!Number.isInteger(o))continue;let a=Y(t,r.name),c=vt(a).filter(d=>d.endsWith(".json")&&d!=="default.json").map(d=>d.replace(".json","")),l;try{l=ti(a).mtime.toISOString()}catch{l=new Date().toISOString()}n.push({version:o,createdAt:l,locales:c})}}catch{return[]}return n.sort((s,r)=>r.version-s.version)}async getVersionFile(e,t,n){if(!this.profilesDir)return null;let s=Y(this.versionsRootDir(e),t),r=Y(s,`${n}.json`);if(!Pt(r)){let o=Y(s,"default.json");return Pt(o)?ei(o,"utf8"):null}return ei(r,"utf8")}async listArchivedProfiles(){if(!this.profilesDir)return[];let e=Y(this.profilesDir,this.tenantId);if(!Pt(e))return[];let t=new Set((await this.profiles.list(this.tenantId)).map(r=>r.id)),n=[],s;try{s=vt(e,{withFileTypes:!0})}catch{return[]}for(let r of s){if(!r.isDirectory()||t.has(r.name))continue;let o=Y(e,r.name);if(Ta(o).isSymbolicLink())continue;let a;try{a=vt(o,{withFileTypes:!0})}catch{continue}let c=a.filter(d=>d.isDirectory()&&Number.isInteger(Number(d.name)));if(c.length===0)continue;let l=new Date(0).toISOString();for(let d of c)try{let u=ti(Y(o,d.name)).mtime.toISOString();u>l&&(l=u)}catch{}n.push({id:r.name,versionCount:c.length,lastModified:l})}return n.sort((r,o)=>o.lastModified.localeCompare(r.lastModified))}toTemplateButtonMap(e){let t={};for(let[n,s]of Object.entries(e))t[n]={type:s.style,action:s.action,...s.cookies!==void 0?{cookies:s.cookies}:{}};return t}buildSeedUiTemplatePayload(e){let t={position:e.mainBanner.position,buttons:this.toTemplateButtonMap(e.mainBanner.buttons),...e.mainBanner.showClose!==void 0?{showClose:e.mainBanner.showClose}:{},...e.mainBanner.showLocaleSwitcher!==void 0?{showLocaleSwitcher:e.mainBanner.showLocaleSwitcher}:{}},n=e.gpcBanner?{position:e.gpcBanner.position,buttons:this.toTemplateButtonMap(e.gpcBanner.buttons),...e.gpcBanner.showClose!==void 0?{showClose:e.gpcBanner.showClose}:{},...e.gpcBanner.showLocaleSwitcher!==void 0?{showLocaleSwitcher:e.gpcBanner.showLocaleSwitcher}:{}}:t,s={buttons:this.toTemplateButtonMap(e.preferenceModal.buttons),...e.preferenceModal.position!==void 0?{position:e.preferenceModal.position}:{},...e.preferenceModal.persistent!==void 0?{persistent:e.preferenceModal.persistent}:{}};return{mainBanner:t,gpcBanner:n,preferenceModal:s}}async getOrCreateSeedTemplates(e,t,n){if(!this.storage)throw new Error("Storage adapter required to seed templates");let s=`Default \u2014 ${e}`,o=(await this.storage.getConsentTemplates(this.tenantId)).find(l=>l.name===s)??await this.storage.createConsentTemplate({tenantId:this.tenantId,name:s,cookies:t.cookies,categories:n.preferenceModal.categories}),c=(await this.storage.getUITemplates(this.tenantId)).find(l=>l.name===s)??await this.storage.createUITemplate({tenantId:this.tenantId,name:s,...this.buildSeedUiTemplatePayload(n)});return{consentTemplateId:o.id,uiTemplateId:c.id}}async seedDefaultProfile(e){if(await this.profiles.findActiveByComplianceGroup(this.tenantId,e))return;let{DEFAULT_PROFILES:n,resolveLocaleTranslation:s}=await import("./profiles-FCX454HS.js"),r=n[e];if(!r)return;let o=r.translations.en;if(!o)return;let a=["de","es","fr","ja"],c=["en",...a.filter(p=>s(e,p)!==void 0)],l={};for(let p of a){let g=s(e,p);g&&(l[p]={mainBanner:g.mainBanner,...g.gpcBanner?{gpcBanner:g.gpcBanner}:{},preferenceModal:g.preferenceModal})}let{consentTemplateId:d,uiTemplateId:u}=await this.getOrCreateSeedTemplates(e,{cookies:r.cookies},o);await this.create({name:`Default \u2014 ${e}`,defaultLocale:r.defaultLocale,profileJson:{id:A(),defaultLocale:r.defaultLocale,complianceGroup:r.complianceGroup,gpcMode:r.gpcMode,cookies:r.cookies,consentTemplateId:d,uiTemplateId:u,...r.expiryDays!==void 0?{expiryDays:r.expiryDays}:{},locales:c,mainBanner:o.mainBanner,...o.gpcBanner?{gpcBanner:o.gpcBanner}:{},preferenceModal:o.preferenceModal,isActive:!0},...Object.keys(l).length>0?{localeContent:l}:{}})}async seedAllDefaults(){let{COMPLIANCE_GROUP_IDS:e}=await import("./dist-QJYW5KUK.js");await Promise.all(e.map(t=>this.seedDefaultProfile(t)))}};var si={"Europe/Amsterdam":"NL","Europe/Andorra":"AD","Europe/Athens":"GR","Europe/Belgrade":"RS","Europe/Berlin":"DE","Europe/Bratislava":"SK","Europe/Brussels":"BE","Europe/Bucharest":"RO","Europe/Budapest":"HU","Europe/Busingen":"DE","Europe/Chisinau":"MD","Europe/Copenhagen":"DK","Europe/Dublin":"IE","Europe/Gibraltar":"GI","Europe/Guernsey":"GG","Europe/Helsinki":"FI","Europe/Isle_of_Man":"IM","Europe/Istanbul":"TR","Europe/Jersey":"JE","Europe/Kaliningrad":"RU","Europe/Kiev":"UA","Europe/Kyiv":"UA","Europe/Lisbon":"PT","Europe/Ljubljana":"SI","Europe/London":"GB","Europe/Luxembourg":"LU","Europe/Madrid":"ES","Europe/Malta":"MT","Europe/Mariehamn":"FI","Europe/Minsk":"BY","Europe/Monaco":"MC","Europe/Moscow":"RU","Europe/Nicosia":"CY","Europe/Oslo":"NO","Europe/Paris":"FR","Europe/Podgorica":"ME","Europe/Prague":"CZ","Europe/Riga":"LV","Europe/Rome":"IT","Europe/Samara":"RU","Europe/San_Marino":"SM","Europe/Sarajevo":"BA","Europe/Saratov":"RU","Europe/Simferopol":"UA","Europe/Skopje":"MK","Europe/Sofia":"BG","Europe/Stockholm":"SE","Europe/Tallinn":"EE","Europe/Tirane":"AL","Europe/Ulyanovsk":"RU","Europe/Uzhgorod":"UA","Europe/Vaduz":"LI","Europe/Vatican":"VA","Europe/Vienna":"AT","Europe/Vilnius":"LT","Europe/Volgograd":"RU","Europe/Warsaw":"PL","Europe/Zagreb":"HR","Europe/Zaporozhye":"UA","Europe/Zurich":"CH","Atlantic/Reykjavik":"IS","Atlantic/Faroe":"FO","America/New_York":"US","America/Chicago":"US","America/Denver":"US","America/Los_Angeles":"US","America/Anchorage":"US","America/Adak":"US","America/Honolulu":"US","America/Phoenix":"US","America/Detroit":"US","America/Indiana/Indianapolis":"US","America/Indiana/Knox":"US","America/Indiana/Marengo":"US","America/Indiana/Petersburg":"US","America/Indiana/Tell_City":"US","America/Indiana/Vevay":"US","America/Indiana/Vincennes":"US","America/Indiana/Winamac":"US","America/Kentucky/Louisville":"US","America/Kentucky/Monticello":"US","America/North_Dakota/Beulah":"US","America/North_Dakota/Center":"US","America/North_Dakota/New_Salem":"US","America/Boise":"US","America/Menominee":"US","America/Nome":"US","America/Sitka":"US","America/Yakutat":"US","America/Juneau":"US","America/Metlakatla":"US","America/Toronto":"CA","America/Vancouver":"CA","America/Winnipeg":"CA","America/Halifax":"CA","America/St_Johns":"CA","America/Edmonton":"CA","America/Regina":"CA","America/Whitehorse":"CA","America/Yellowknife":"CA","America/Iqaluit":"CA","America/Moncton":"CA","America/Glace_Bay":"CA","America/Goose_Bay":"CA","America/Swift_Current":"CA","America/Dawson":"CA","America/Cambridge_Bay":"CA","America/Dawson_Creek":"CA","America/Fort_Nelson":"CA","America/Creston":"CA","America/Rankin_Inlet":"CA","America/Resolute":"CA","America/Pangnirtung":"CA","America/Nipigon":"CA","America/Mexico_City":"MX","America/Cancun":"MX","America/Monterrey":"MX","America/Merida":"MX","America/Chihuahua":"MX","America/Hermosillo":"MX","America/Mazatlan":"MX","America/Ojinaga":"MX","America/Bahia_Banderas":"MX","America/Matamoros":"MX","America/Tijuana":"MX","America/Sao_Paulo":"BR","America/Manaus":"BR","America/Belem":"BR","America/Fortaleza":"BR","America/Recife":"BR","America/Maceio":"BR","America/Bahia":"BR","America/Porto_Velho":"BR","America/Boa_Vista":"BR","America/Campo_Grande":"BR","America/Cuiaba":"BR","America/Santarem":"BR","America/Rio_Branco":"BR","America/Eirunepe":"BR","America/Noronha":"BR","America/Argentina/Buenos_Aires":"AR","America/Argentina/Cordoba":"AR","America/Argentina/Salta":"AR","America/Argentina/Jujuy":"AR","America/Argentina/Tucuman":"AR","America/Argentina/Catamarca":"AR","America/Argentina/La_Rioja":"AR","America/Argentina/San_Juan":"AR","America/Argentina/Mendoza":"AR","America/Argentina/San_Luis":"AR","America/Argentina/Rio_Gallegos":"AR","America/Argentina/Ushuaia":"AR","America/Bogota":"CO","America/Lima":"PE","America/Santiago":"CL","America/Caracas":"VE","America/La_Paz":"BO","America/Asuncion":"PY","America/Montevideo":"UY","America/Guayaquil":"EC","America/Guyana":"GY","America/Paramaribo":"SR","America/Cayenne":"GF","America/Panama":"PA","America/Costa_Rica":"CR","America/Guatemala":"GT","America/El_Salvador":"SV","America/Tegucigalpa":"HN","America/Managua":"NI","America/Belize":"BZ","America/Havana":"CU","America/Jamaica":"JM","America/Nassau":"BS","America/Port_of_Spain":"TT","America/Santo_Domingo":"DO","America/Port-au-Prince":"HT","America/Barbados":"BB","America/Martinique":"MQ","America/Guadeloupe":"GP","America/Puerto_Rico":"PR","America/St_Kitts":"KN","Asia/Kolkata":"IN","Asia/Calcutta":"IN","Asia/Karachi":"PK","Asia/Dhaka":"BD","Asia/Colombo":"LK","Asia/Kathmandu":"NP","Asia/Kabul":"AF","Asia/Tokyo":"JP","Asia/Seoul":"KR","Asia/Shanghai":"CN","Asia/Hong_Kong":"HK","Asia/Macau":"MO","Asia/Taipei":"TW","Asia/Ulaanbaatar":"MN","Asia/Choibalsan":"MN","Asia/Hovd":"MN","Asia/Singapore":"SG","Asia/Bangkok":"TH","Asia/Jakarta":"ID","Asia/Kuala_Lumpur":"MY","Asia/Manila":"PH","Asia/Ho_Chi_Minh":"VN","Asia/Saigon":"VN","Asia/Phnom_Penh":"KH","Asia/Vientiane":"LA","Asia/Rangoon":"MM","Asia/Yangon":"MM","Asia/Pontianak":"ID","Asia/Makassar":"ID","Asia/Jayapura":"ID","Asia/Kuching":"MY","Asia/Brunei":"BN","Asia/Dili":"TL","Asia/Tashkent":"UZ","Asia/Almaty":"KZ","Asia/Qyzylorda":"KZ","Asia/Aqtau":"KZ","Asia/Aqtobe":"KZ","Asia/Atyrau":"KZ","Asia/Oral":"KZ","Asia/Ashgabat":"TM","Asia/Dushanbe":"TJ","Asia/Bishkek":"KG","Asia/Yekaterinburg":"RU","Asia/Tehran":"IR","Asia/Baghdad":"IQ","Asia/Riyadh":"SA","Asia/Dubai":"AE","Asia/Kuwait":"KW","Asia/Qatar":"QA","Asia/Bahrain":"BH","Asia/Muscat":"OM","Asia/Beirut":"LB","Asia/Jerusalem":"IL","Asia/Tel_Aviv":"IL","Asia/Amman":"JO","Asia/Damascus":"SY","Asia/Nicosia":"CY","Asia/Famagusta":"CY","Asia/Baku":"AZ","Asia/Yerevan":"AM","Asia/Tbilisi":"GE","Asia/Samarkand":"UZ","Africa/Cairo":"EG","Africa/Casablanca":"MA","Africa/Lagos":"NG","Africa/Nairobi":"KE","Africa/Johannesburg":"ZA","Africa/Addis_Ababa":"ET","Africa/Algiers":"DZ","Africa/Tunis":"TN","Africa/Tripoli":"LY","Africa/Khartoum":"SD","Africa/Accra":"GH","Africa/Dar_es_Salaam":"TZ","Africa/Abidjan":"CI","Africa/Bamako":"ML","Africa/Banjul":"GM","Africa/Conakry":"GN","Africa/Dakar":"SN","Africa/Freetown":"SL","Africa/Lome":"TG","Africa/Monrovia":"LR","Africa/Nouakchott":"MR","Africa/Ouagadougou":"BF","Africa/Sao_Tome":"ST","Africa/Bissau":"GW","Africa/Bangui":"CF","Africa/Brazzaville":"CG","Africa/Douala":"CM","Africa/Kinshasa":"CD","Africa/Libreville":"GA","Africa/Luanda":"AO","Africa/Malabo":"GQ","Africa/Ndjamena":"TD","Africa/Niamey":"NE","Africa/Porto-Novo":"BJ","Africa/Blantyre":"MW","Africa/Bujumbura":"BI","Africa/Gaborone":"BW","Africa/Harare":"ZW","Africa/Kampala":"UG","Africa/Kigali":"RW","Africa/Lubumbashi":"CD","Africa/Lusaka":"ZM","Africa/Maputo":"MZ","Africa/Maseru":"LS","Africa/Mbabane":"SZ","Africa/Mogadishu":"SO","Africa/Windhoek":"NA","Africa/Asmara":"ER","Africa/Djibouti":"DJ","Africa/El_Aaiun":"EH","Africa/Juba":"SS","Africa/Ceuta":"ES","Australia/Sydney":"AU","Australia/Melbourne":"AU","Australia/Brisbane":"AU","Australia/Perth":"AU","Australia/Adelaide":"AU","Australia/Darwin":"AU","Australia/Hobart":"AU","Australia/Canberra":"AU","Australia/Broken_Hill":"AU","Australia/Lord_Howe":"AU","Australia/Lindeman":"AU","Australia/Eucla":"AU","Pacific/Auckland":"NZ","Pacific/Chatham":"NZ","Pacific/Fiji":"FJ","Pacific/Port_Moresby":"PG","Pacific/Bougainville":"PG","Pacific/Guam":"GU","Pacific/Saipan":"MP","Pacific/Pago_Pago":"AS","Pacific/Apia":"WS","Pacific/Tongatapu":"TO","Pacific/Fakaofo":"TK","Pacific/Niue":"NU","Pacific/Rarotonga":"CK","Pacific/Tahiti":"PF","Pacific/Marquesas":"PF","Pacific/Gambier":"PF","Pacific/Honolulu":"US","Pacific/Johnston":"US","Pacific/Midway":"UM","Pacific/Wake":"UM","Pacific/Tarawa":"KI","Pacific/Enderbury":"KI","Pacific/Kiritimati":"KI","Pacific/Efate":"VU","Pacific/Guadalcanal":"SB","Pacific/Noumea":"NC","Pacific/Nauru":"NR","Pacific/Palau":"PW","Pacific/Chuuk":"FM","Pacific/Pohnpei":"FM","Pacific/Kosrae":"FM","Pacific/Majuro":"MH","Pacific/Kwajalein":"MH","Pacific/Funafuti":"TV","Pacific/Wallis":"WF","Asia/Novosibirsk":"RU","Asia/Omsk":"RU","Asia/Krasnoyarsk":"RU","Asia/Irkutsk":"RU","Asia/Chita":"RU","Asia/Yakutsk":"RU","Asia/Khandyga":"RU","Asia/Vladivostok":"RU","Asia/Ust-Nera":"RU","Asia/Magadan":"RU","Asia/Sakhalin":"RU","Asia/Srednekolymsk":"RU","Asia/Kamchatka":"RU","Asia/Anadyr":"RU"};var Aa={ar:"SA",cs:"CZ",da:"DK",de:"DE",el:"GR",fi:"FI",fr:"FR",he:"IL",hi:"IN",hu:"HU",id:"ID",it:"IT",ja:"JP",ko:"KR",nl:"NL",no:"NO",pl:"PL",pt:"BR",ro:"RO",ru:"RU",sv:"SE",th:"TH",tr:"TR",uk:"UA",vi:"VN",zh:"CN"},bt=class{constructor(e,t){this.map=t;this.provider=e??"default",this.provider==="default"&&console.warn("[Consenti] geoDataProvider is unset (using the timezone/language heuristic) \u2014 this cannot resolve US state-level regions (California, Colorado, etc.), so US visitors always fall back to the country-level default (the strictest group, opt-out-strict) rather than their actual state law. Configure a geoip-capable provider (geoip, hosted-geoip-lite, or maxmind) for accurate US state routing in production.")}map;provider;setMap(e){this.map=e}async resolve(e){let t="",n,s,r=null,o;if(typeof this.provider=="function"){let l=await this.provider(e);t=l.country??"",n=l.region??void 0,r=l.locale,o=l.complianceGroup??void 0}else switch(this.provider){case"default":({country:t}=Pa(e.timezone)),t||({country:t,region:n}=va(e.language));break;case"geoip":({country:t,region:n}=await Oa(e.ip));break;case"hosted-geoip-lite":({country:t,region:n}=await Da(e.ip));break;case"maxmind":({country:t,region:n}=await ba(e.ip));break}let a={country:t,complianceGroup:o??this.resolveGroup(t,n)};return n!==void 0&&(a.region=n),s!==void 0&&(a.regulation=s),r!==null&&(a.locale=r),this.resolveRequiresSensitiveOptIn(t,n)&&(a.requiresSensitiveOptIn=!0),a}countryEntry(e){return this.map.countries[e]}resolveGroup(e,t){if(!e)return"";let n=this.countryEntry(e);return n?t!==void 0?n.overriddenRegions?.[t]?.complianceGroup??n.complianceGroup:n.default??n.complianceGroup:""}resolveRequiresSensitiveOptIn(e,t){return!e||t===void 0?!1:this.countryEntry(e)?.overriddenRegions?.[t]?.requiresSensitiveOptIn===!0}};function va(i){let t=(i.split(",")[0]?.trim().split(";")[0]?.trim()??"").split("-");if(t.length>=2){let s=(t[t.length-1]??"").toUpperCase();if(/^[A-Z]{2}$/.test(s))return{country:s}}return{country:Aa[(t[0]??"").toLowerCase()]??""}}function Pa(i){return{country:si[i]??""}}async function Oa(i){if(!i)return{country:""};try{let t=(await import("geoip-lite")).lookup(i),n={country:t?.country??""},s=t?.region;return s&&(n.region=s),n}catch{return console.warn("[Consenti] geoip-lite not installed \u2014 country detection skipped. Run: npm install geoip-lite"),{country:""}}}async function ba(i){if(!i)return{country:""};try{let e=await import("maxmind"),t=process.env.MAXMIND_DB_PATH??"./GeoLite2-City.mmdb",s=(await e.open(t)).get(i),r=s?.country?.iso_code??"",o=s?.subdivisions?.[0]?.iso_code,a={country:r};return o&&(a.region=o),a}catch{return console.warn("[Consenti] maxmind not available \u2014 country detection skipped. See HOW_TO_MAXMIND.md"),{country:""}}}async function Da(i){return i?new Promise(e=>{import("node:https").then(({default:t})=>{let n=`https://ipinfo.io/${encodeURIComponent(i)}/json`,s=t.get(n,{timeout:3e3},r=>{let o=[];r.on("data",a=>o.push(a)),r.on("end",()=>{try{let a=JSON.parse(Buffer.concat(o).toString()),c={country:a.country??""};a.region&&(c.region=a.region),e(c)}catch{e({country:""})}})});s.on("error",()=>e({country:""})),s.on("timeout",()=>{s.destroy(),e({country:""})})}).catch(()=>e({country:""}))}):{country:""}}var Ua=1440*60*1e3;function La(i){let e=i?.headers.get("cache-control")?.match(/max-age=(\d+)/)?.[1];if(e){let n=parseInt(e,10)*1e3;if(!isNaN(n)&&n>0)return n}let t=i?.headers.get("expires");if(t){let n=Date.parse(t)-Date.now();if(!isNaN(n)&&n>0)return n}return Ua}function ii(i,e){let t,n=async()=>{let{map:s,response:r}=await Gn(i,o=>console.error("[Consenti]",o));e.setMap(s),typeof i=="string"&&(t=setTimeout(()=>{n()},La(r)),t.unref())};return n(),{stop:()=>{t&&clearTimeout(t)}}}import{existsSync as Fa,readFileSync as Ha,readdirSync as Ba,unlinkSync as Va}from"fs";import{join as ai}from"path";import{copyFileSync as ri,writeFileSync as Na,renameSync as ka,unlinkSync as En,readFileSync as Ma,existsSync as ja,mkdirSync as xa}from"fs";import{dirname as $a}from"path";function oi(i,e,t){let n=i+".bak",s=i+".tmp";xa($a(i),{recursive:!0});let r=ja(i);r&&ri(i,n);try{Na(s,JSON.stringify(e,null,2),"utf8");let o=JSON.parse(Ma(s,"utf8"));if(!t(o))throw new Error("Schema validation failed for "+i);if(ka(s,i),r)try{En(n)}catch{}}catch(o){if(r)try{ri(n,i),En(n)}catch{}try{En(s)}catch{}throw o}}function ci(i){return typeof i=="object"&&i!==null&&typeof i.id=="string"&&typeof i.version=="number"}var Dt=class{constructor(e){this.dir=e}dir;read(e,t){let n=this.path(e,t);if(!Fa(n))return null;try{let s=JSON.parse(Ha(n,"utf8"));return ci(s)?s:null}catch{return null}}write(e,t,n){oi(this.path(e,t),n,ci)}rebuild(e,t){for(let[n,s]of t)try{this.write(e,n,s)}catch(r){console.warn(`[Consenti] locale cache rebuild failed for ${e}/${n}:`,r)}}invalidate(e){try{let t=`${e}_`;for(let n of Ba(this.dir))if(n.startsWith(t)&&n.endsWith(".json"))try{Va(ai(this.dir,n))}catch{}}catch{}}path(e,t){return ai(this.dir,`${e}_${t}.json`)}};function li(i,e,t){let n=[];for(let[s,r]of Object.entries(i.consentJson)){if(!e[s])continue;let o=Ze(s,t);o==="mandatory"&&r!=="granted"&&n.push(`Cookie "${s}" is mandatory and cannot be "${r}"`),r==="objected"&&o!=="legitimate_interest"&&n.push(`Cookie "${s}" has legal basis "${o}" \u2014 'objected' is only valid for 'legitimate_interest'`)}return{valid:n.length===0,errors:n}}function Rn(i,e,t,n,s){let r={};for(let[o,a]of Object.entries(i)){if(Wn(o,e)){r[o]="granted";continue}if(s==="cpra"){if(a.cpraCategory==="sensitive"){let l=t[o];r[o]=l==="granted"||l==="denied"||l==="objected"?l:"denied";continue}if(n&&(a.cpraCategory==="sale"||a.cpraCategory==="sharing")){r[o]="denied";continue}}if(n&&a.listenGpc&&s!=="dpdpa"){r[o]="denied";continue}let c=t[o];c==="granted"||c==="denied"||c==="objected"?r[o]=c:r[o]="denied"}return r}function Sn(i){let e=Object.keys(i.consentJson).sort().map(t=>`${t}=${i.consentJson[t]}`).join(",");return`${i.tenantId}|${i.visitorId}|${i.profileId}|${i.locale}|${e}`}function di(i,e,t,n){let s=[];return(!t||t.id!==e.id)&&s.push("profile_changed"),Wa(i,e)&&s.push("consent_expired"),n&&i.signature&&Z(Sn(i),n)!==i.signature&&s.push("hmac_invalid"),{valid:s.length===0,reasons:s,...t?{currentProfileId:t.id}:{},consentProfileId:e.id}}function ui(i,e){let t=new Set,n=new Set;for(let[s,r]of Object.entries(i))if(e[s]==="granted"){r.tcfVendorId&&t.add(r.tcfVendorId);for(let o of r.tcfPurposes??[])n.add(o)}return{vendorConsents:[...t].sort((s,r)=>s-r),purposeConsents:[...n].sort((s,r)=>s-r)}}function Wa(i,e){let t=e.profileJson.expiryDays;if(!t)return!1;let s=new Date(i.createdAt).getTime()+t*24*60*60*1e3;return Date.now()>s}var pi=!1,Ut=null,mi=!1;async function Ja(){if(pi)return Ut;pi=!0;try{Ut=await import("@iabtechlabtcf/core")}catch{Ut=null}return Ut}function qa(i,e){let t=new Set,n=new Set;for(let s of i){let r=e.vendors[String(s)];if(r?.legIntPurposes?.length){t.add(s);for(let o of r.legIntPurposes)n.add(o)}}return{vendorLegitimateInterests:[...t].sort((s,r)=>s-r),purposeLegitimateInterests:[...n].sort((s,r)=>s-r)}}async function gi(i){let e=await Ja();if(!e)return mi||(mi=!0,console.warn('[consenti] TCF is enabled but the optional "@iabtechlabtcf/core" dependency is not installed. Falling back to a simplified, non-spec-compliant TC string. Run `npm install @iabtechlabtcf/core` to produce real IAB-spec binary TC strings.')),null;let{TCModel:t,TCString:n,GVL:s}=e,r=new s(i.gvl.raw),o=new t(r);o.cmpId=i.cmpId,o.cmpVersion=i.cmpVersion,o.consentScreen=i.consentScreen,o.consentLanguage=i.consentLanguage,o.publisherCountryCode=i.publisherCC,o.isServiceSpecific=i.isServiceSpecific,o.purposeConsents.set(i.purposeConsents),o.vendorConsents.set(i.vendorConsents);let{vendorLegitimateInterests:a,purposeLegitimateInterests:c}=qa(i.vendorConsents,i.gvl);return o.purposeLegitimateInterests.set(c),o.vendorLegitimateInterests.set(a),o.vendorsDisclosed.set(i.vendorConsents),n.encode(o)}import{createHash as Ga}from"crypto";function He(i){let e=Object.keys(i).sort().map(t=>`${t}=${i[t]??""}`).join("&");return Ga("sha256").update(e).digest("hex")}function ee(i,e,t){return i?t?t.configHash===He(e):!1:!0}var fi=!1,Lt=null,_i=!1;async function Ka(){if(fi)return Lt;fi=!0;try{Lt=await import("@iabgpp/cmpapi")}catch{Lt=null}return Lt}var hi=0,yi=1,Ya=2;function Nt(i){return i?yi:hi}function Cn(i,e){return i?e?yi:Ya:hi}async function za(i){let e=await Ka();if(!e)return null;let{GppModel:t}=e,n=new t;return n.setFieldValue("usnat","SharingNotice",Nt(i.sharingApplicable)),n.setFieldValue("usnat","SaleOptOutNotice",Nt(i.saleApplicable)),n.setFieldValue("usnat","SharingOptOutNotice",Nt(i.sharingApplicable)),n.setFieldValue("usnat","TargetedAdvertisingOptOutNotice",Nt(i.sharingApplicable)),n.setFieldValue("usnat","SaleOptOut",Cn(i.saleApplicable,i.saleOptOut)),n.setFieldValue("usnat","SharingOptOut",Cn(i.sharingApplicable,i.sharingOptOut)),n.setFieldValue("usnat","TargetedAdvertisingOptOut",Cn(i.sharingApplicable,i.sharingOptOut)),n.setFieldValue("usnat","MspaCoveredTransaction",i.mspaCoveredTransaction?1:2),n.setFieldValue("usnat","MspaOptOutOptionMode",i.mspaOptOutOptionMode),n.setFieldValue("usnat","MspaServiceProviderMode",i.mspaServiceProviderMode),i.gpcDetected!==void 0&&(n.setFieldValue("usnat","GpcSegmentIncluded",!0),n.setFieldValue("usnat","Gpc",i.gpcDetected)),n.encode()}async function wi(i){let e=await za(i);return e||(_i||(_i=!0,console.warn('[consenti] GPP is enabled but the optional "@iabgpp/cmpapi" dependency is not installed. Falling back to a simplified, non-spec-compliant GPP string. Run `npm install @iabgpp/cmpapi` to produce real IAB-spec binary GPP strings.')),Yn(i))}var kt=class{constructor(e,t,n,s,r="default",o,a,c,l,d,u,p){this.consents=e;this.visitors=t;this.profiles=n;this.audit=s;this.tenantId=r;this.compliance=o;this.pluginEngine=a;this.eventBus=c;this.tcfConfig=l;this.storage=d;this.dataSigningHash=u;this.gppConfig=p}consents;visitors;profiles;audit;tenantId;compliance;pluginEngine;eventBus;tcfConfig;storage;dataSigningHash;gppConfig;sign(e){if(this.dataSigningHash)return Z(Sn(e),this.dataSigningHash)}async isTcfConfirmed(){if(!this.tcfConfig?.enabled)return!0;if(!this.storage)return!1;let e=await this.storage.getSettings(this.tenantId);return ee(!0,{cmpId:this.tcfConfig.cmpId,cmpVersion:this.tcfConfig.cmpVersion,publisherCC:this.tcfConfig.publisherCC},e.tcfConfirmation)}async isGppConfirmed(){if(!this.gppConfig?.enabled)return!0;if(!this.storage)return!1;let e=await this.storage.getSettings(this.tenantId);return ee(!0,{cmpId:this.gppConfig.cmpId,cmpVersion:this.gppConfig.cmpVersion},e.gppConfirmation)}async resolveTemplateData(e){let t=!e.cookies,n=!e.preferenceModal?.categories,s=(t||n)&&e.consentTemplateId&&this.storage?await this.storage.getConsentTemplate(e.consentTemplateId):null;return{cookies:e.cookies??(s?wn(s.cookies,e.cookiesOverride):{}),categories:e.preferenceModal?.categories??s?.categories??{}}}async create(e){let t=await this.profiles.get(e.profileId);if(!t)throw new Error(`Profile ${e.profileId} not found`);let n=t.profileJson.regulation,{cookies:s,categories:r}=await this.resolveTemplateData(t.profileJson),o=Ae(r),a=e.consentJson;if(e.gpcDetected&&this.compliance?.gpc!==!1&&n!=="dpdpa"){a={...a};for(let[L,X]of Object.entries(s))n==="cpra"?(X.cpraCategory==="sale"||X.cpraCategory==="sharing")&&(a[L]="denied"):X.listenGpc&&(a[L]="denied")}let c=li({...e,consentJson:a},s,o);if(!c.valid)throw new Error(c.errors.join("; "));let l=Rn(s,o,a,e.gpcDetected,n),d,u=this.tcfConfig,p=u?.enabled&&Object.values(s).some(L=>L.tcfVendorId),g=p?await this.isTcfConfirmed():!0;if(u&&p&&!g)console.error(`[Consenti] TCF is enabled (cmpId=${u.cmpId}, cmpVersion=${u.cmpVersion}) but this registration is unconfirmed \u2014 treating TCF as disabled for this consent write (no tcfString generated). Confirm the current cmpId/cmpVersion in the dashboard's TCF Registration panel to re-enable.`);else if(u&&p){let L=await me(),{vendorConsents:X,purposeConsents:z}=ui(s,l),Te=e.locale?.slice(0,2)??"en";d=u.publisherCC&&L?await gi({cmpId:u.cmpId,cmpVersion:u.cmpVersion,consentScreen:1,consentLanguage:Te,publisherCC:u.publisherCC,isServiceSpecific:!0,purposeConsents:z,vendorConsents:X,gvl:L})??void 0:void 0,d||(d=Kn({cmpId:u.cmpId,cmpVersion:u.cmpVersion,consentScreen:1,consentLanguage:Te,vendorListVersion:L?.vendorListVersion??0,purposeConsents:z,vendorConsents:X}))}let E,_=this.gppConfig,h=Object.values(s).some(L=>L.cpraCategory==="sale"),w=Object.values(s).some(L=>L.cpraCategory==="sharing"),R=_?.enabled&&(h||w),I=R?await this.isGppConfirmed():!0;if(_&&R&&!I)console.error(`[Consenti] GPP is enabled (cmpId=${_.cmpId}, cmpVersion=${_.cmpVersion}) but this registration is unconfirmed \u2014 treating GPP as disabled for this consent write (no gppString generated). Confirm the current cmpId/cmpVersion in the dashboard's GPP Registration panel to re-enable.`);else if(_&&R){let L=!h||Object.entries(s).filter(([,z])=>z.cpraCategory==="sale").every(([z])=>l[z]!=="granted"),X=!w||Object.entries(s).filter(([,z])=>z.cpraCategory==="sharing").every(([z])=>l[z]!=="granted");E=await wi({cmpId:_.cmpId,cmpVersion:_.cmpVersion,mspaCoveredTransaction:_.mspaCoveredTransaction,mspaOptOutOptionMode:_.mspaOptOutOptionMode,mspaServiceProviderMode:_.mspaServiceProviderMode,saleOptOut:L,saleApplicable:h,sharingOptOut:X,sharingApplicable:w,gpcDetected:e.gpcDetected})}let T=this.sign({tenantId:this.tenantId,visitorId:e.visitorId,profileId:e.profileId,locale:e.locale,consentJson:l}),O={...e,tenantId:this.tenantId,consentJson:l,...d?{tcfString:d}:{},...E?{gppString:E}:{},...T?{signature:T}:{}};this.pluginEngine&&(O=await this.pluginEngine.runBeforeConsentSave(O));let b=await this.consents.get(e.visitorId),M;if(b){let L=await(this.pluginEngine?this.pluginEngine.runBeforeConsentUpdate({consentJson:l,locale:e.locale,gpcDetected:e.gpcDetected,...T?{signature:T}:{}}):Promise.resolve({consentJson:l,locale:e.locale,gpcDetected:e.gpcDetected,...T?{signature:T}:{}}));M=await this.consents.update(e.visitorId,L)}else M=await this.consents.create(O);return await this.audit.log({tenantId:this.tenantId,action:b?"consent:updated":"consent:created",resourceType:"consent",resourceId:M.id,...b!=null?{oldData:b}:{},newData:M}),b?(this.eventBus?.emit("consent:updated",{previous:b,current:M}),await this.pluginEngine?.runAfterConsentUpdate(M)):(this.eventBus?.emit("consent:created",M),await this.pluginEngine?.runAfterConsentSave(M)),M}async update(e,t){let n=await this.consents.get(e);if(!n)throw new Error(`Consent for visitor ${e} not found`);let s=await this.profiles.get(n.profileId);if(!s)throw new Error(`Profile ${n.profileId} not found`);let r=s.profileJson.regulation,{cookies:o,categories:a}=await this.resolveTemplateData(s.profileJson),c=Ae(a),l=t.gpcDetected??n.gpcDetected,d=t.consentJson;if(l&&this.compliance?.gpc!==!1&&r!=="dpdpa"){d={...d};for(let[h,w]of Object.entries(o))r==="cpra"?(w.cpraCategory==="sale"||w.cpraCategory==="sharing")&&(d[h]="denied"):w.listenGpc&&(d[h]="denied")}let u=Rn(o,c,d,l,r),p=this.sign({tenantId:this.tenantId,visitorId:e,profileId:n.profileId,locale:t.locale??n.locale,consentJson:u}),g={consentJson:u,...t.locale!==void 0?{locale:t.locale}:{},...t.gpcDetected!==void 0?{gpcDetected:t.gpcDetected}:{},...p?{signature:p}:{}},E=await(this.pluginEngine?this.pluginEngine.runBeforeConsentUpdate(g):Promise.resolve(g)),_=await this.consents.update(e,E);return await this.audit.log({tenantId:this.tenantId,action:"consent:updated",resourceType:"consent",resourceId:_.id,oldData:n,newData:_}),this.eventBus?.emit("consent:updated",{previous:n,current:_}),await this.pluginEngine?.runAfterConsentUpdate(_),_}get(e){return this.consents.get(e)}async verify(e){let t=await this.consents.get(e);if(!t)return{valid:!1,reasons:["profile_changed"]};let n=await this.profiles.get(t.profileId);if(!n)return{valid:!1,reasons:["profile_changed"]};let s=n.profileJson.complianceGroup,r=s?await this.profiles.findActiveByComplianceGroup(this.tenantId,s):null;return di(t,n,r,this.dataSigningHash)}async erase(e){let t=await this.consents.get(e);await this.consents.delete(e),await this.visitors.delete(e),await this.audit.log({tenantId:this.tenantId,action:"consent:erased",resourceType:"consent",resourceId:e,...t!=null?{oldData:t}:{}}),this.eventBus?.emit("consent:erased",{visitorId:e})}};var Mt=class{constructor(e,t="default",n,s=""){this.visitors=e;this.tenantId=t;this.eventBus=n;this.dataSigningHash=s}visitors;tenantId;eventBus;dataSigningHash;async upsert(e){if(await this.visitors.get(e.visitorId))return this.visitors.update(e.visitorId,{...e.country!=null?{country:e.country}:{},...e.region!=null?{region:e.region}:{},...e.city!=null?{city:e.city}:{}});let n={tenantId:this.tenantId,visitorId:e.visitorId,...e.country!=null?{country:e.country}:{},...e.region!=null?{region:e.region}:{},...e.city!=null?{city:e.city}:{},...e.ip?{ipHash:ls(e.ip,this.dataSigningHash)}:{},...e.userAgent?{userAgentHash:ds(e.userAgent)}:{}},s=await this.visitors.create(n);return this.eventBus?.emit("visitor:created",s),s}};var jt=class{constructor(e,t="default"){this.notices=e;this.tenantId=t}notices;tenantId;recordShown(e){return this.notices.create({tenantId:this.tenantId,...e})}getForVisitor(e){return this.notices.getForVisitor(e)}};var xt=class{constructor(e,t,n="default"){this.users=e;this.audit=t;this.tenantId=n}users;audit;tenantId;safe(e){let{passwordHash:t,...n}=e;return n}async create(e,t){let n=await this.users.create({tenantId:this.tenantId,name:e.name,email:e.email,passwordHash:Ue(e.password),...e.allowedTenants?{allowedTenants:e.allowedTenants}:{}});return e.roleId&&await this.users.assignRole(n.id,e.roleId),await this.audit.log({tenantId:this.tenantId,...t!=null?{userId:t}:{},action:"user.created",resourceType:"user",resourceId:n.id,newData:this.safe(n)}),this.safe(n)}async update(e,t,n){let s=await this.users.getById(e);if(!s)throw new Error(`User ${e} not found`);let r={};t.name!=null&&(r.name=t.name),t.email!=null&&(r.email=t.email),t.isActive!=null&&(r.isActive=t.isActive),t.password&&(r.passwordHash=Ue(t.password)),t.allowedTenants!=null&&(r.allowedTenants=t.allowedTenants);let o=await this.users.update(e,r);return await this.audit.log({tenantId:this.tenantId,...n!=null?{userId:n}:{},action:"user.updated",resourceType:"user",resourceId:e,oldData:this.safe(s),newData:this.safe(o)}),this.safe(o)}async delete(e,t){let n=await this.users.getById(e);await this.users.delete(e),await this.audit.log({tenantId:this.tenantId,...t!=null?{userId:t}:{},action:"user.deleted",resourceType:"user",resourceId:e,...n!=null?{oldData:this.safe(n)}:{}})}async get(e){let t=await this.users.getById(e);return t?this.safe(t):null}async list(){return(await this.users.list(this.tenantId)).map(t=>this.safe(t))}getRoles(e,t){return this.users.getRoles(e,t)}assignRole(e,t,n){return this.users.assignRole(e,t,n)}revokeRole(e,t,n){return this.users.revokeRole(e,t,n)}};var Ei=15*6e4,Qa=5,Ri=1800,$t=class{constructor(e,t){this.storage=e;this.secret=t;setInterval(()=>{let n=Date.now();for(let[s,r]of this.loginAttempts)n>=r.resetAt&&this.loginAttempts.delete(s)},Ei).unref()}storage;secret;loginAttempts=new Map;async bootstrap(e,t){if(await this.storage.countUsers("default")>0)return null;if(t.length<12)throw new Error("[Consenti] bootstrap: admin password must be at least 12 characters");t.length<16&&console.warn("[Consenti] bootstrap: admin password is shorter than 16 characters \u2014 consider using a longer password");let s=Ue(t),r=await this.storage.createUser({tenantId:"default",name:"Admin",email:e,passwordHash:s});return await this.storage.assignRole(r.id,"role_super_admin"),r}async login(e,t){let n=Date.now(),s=this.loginAttempts.get(e);if(s&&n<s.resetAt&&s.count>=Qa)return null;let r=await this.storage.getUserByEmail(e);if(!r||!r.isActive||!us(t,r.passwordHash)){let l=this.loginAttempts.get(e);return!l||n>=l.resetAt?this.loginAttempts.set(e,{count:1,resetAt:n+Ei}):l.count+=1,null}this.loginAttempts.delete(e);let o=await this.storage.getUserRoles(r.id),a=await Promise.all(o.map(l=>this.storage.getPermissionsForRole(l.id))),c=[...new Set(a.flat().map(l=>l.name))];return rn({sub:r.id,email:r.email,roles:o.map(l=>l.name),permissions:c,allowedTenants:r.allowedTenants??[]},this.secret,Ri)}signToken(e){return rn(e,this.secret,Ri)}verify(e){return nt(e,this.secret)}async getUser(e){let t=this.verify(e);if(!t)return null;let n=t.sub;return typeof n!="string"?null:this.storage.getUserById(n)}};var Be=class extends Error{constructor(){super("Payload Too Large")}};function Xa(i,e){let t=i.socket?.remoteAddress??"";if(!e?.length||!e.includes(t))return t;let n=i.headers["x-forwarded-for"];return typeof n=="string"?n.split(",")[0]?.trim()??t:Array.isArray(n)?n[0]?.trim()??t:t}function f(i,e){return new Response(JSON.stringify(e),{status:i,headers:{"Content-Type":"application/json"}})}async function v(i){if(!(i.headers.get("content-type")??"").includes("application/json"))return null;try{return await i.json()}catch{return null}}function k(i,e){return i.searchParams.get(e)??void 0}function Ft(i,e,t){let n=i.searchParams.get(e);if(!n)return t;let s=parseInt(n,10);return isNaN(s)?t:s}var Si=500;function Ce(i,e=50){let t=Ft(i,"limit",e);return t<1||t>Si?{error:`limit must be between 1 and ${Si}`}:{page:Math.max(1,Ft(i,"page",1)),limit:t}}async function Ci(i,e=1048576,t){let s=i.headers["x-forwarded-proto"]==="https"?"https":"http",r=i.headers.host??"localhost",o=new URL(i.url??"/",`${s}://${r}`),a=new Headers;for(let[d,u]of Object.entries(i.headers))if(u)if(Array.isArray(u))for(let p of u)a.append(d,p);else a.set(d,u);a.set("x-real-ip",Xa(i,t));let c=null,l=(i.method??"GET").toUpperCase();if(l!=="GET"&&l!=="HEAD"){let d=[],u=0;for await(let p of i){if(u+=p.length,u>e)throw new Be;d.push(p)}d.length>0&&(c=Buffer.concat(d))}return new Request(o.toString(),{method:l,headers:a,body:c})}async function Ii(i,e){e.statusCode=i.status;let t=typeof i.headers.getSetCookie=="function"?i.headers.getSetCookie():i.headers.get("set-cookie")?[i.headers.get("set-cookie")]:[];i.headers.forEach((n,s)=>{s.toLowerCase()!=="set-cookie"&&e.setHeader(s,n)});for(let n of t)e.appendHeader("set-cookie",n);if(i.body){let n=i.body.getReader();try{for(;;){let{done:s,value:r}=await n.read();if(s)break;e.write(Buffer.from(r))}}finally{n.releaseLock()}e.end()}else e.end()}function m(i,e,t){let n={error:e};return t!=null&&(n.details=t),new Response(JSON.stringify(n),{status:i,headers:{"Content-Type":"application/json"}})}async function y(i){try{return await i()}catch(e){let t=e instanceof Error?e.message:"Internal Server Error";return m(500,"Internal Server Error",process.env.NODE_ENV!=="production"?t:void 0)}}import{existsSync as Ht,readFileSync as Za}from"fs";import{join as Bt}from"path";var ec="public, max-age=3600, stale-while-revalidate=60",Ti={"Content-Type":"application/json","Cache-Control":ec};function tc(i){let e=Za(i,"utf8");return new Response(e,{headers:Ti})}function nc(i,e,t){return new Response(null,{status:303,headers:{Location:`${i}/profiles/${e}/${t}/default`}})}function Ai(i,e,t,n=""){return{"GET /profiles/:tenantId/:complianceGroupOrProfileId/:locale":async(s,r)=>y(async()=>{let{tenantId:o,complianceGroupOrProfileId:a,locale:c}=r;if(!o||!a||!c)return m(400,"Missing path parameters");if(t){let d=Bt(t,o,a,`${c}.json`);if(Ht(d))return tc(d);let u=Bt(t,o,a,"default.json");if(Ht(u))return nc(n,o,a)}let l=await i.getResolved(a,c);return l?f(200,l):m(404,"Profile not found")}),"GET /profiles/:tenantId/:profileId/:entryId/:locale":async(s,r)=>y(async()=>{let{profileId:o,entryId:a,locale:c}=r;if(!o||!a||!c)return m(400,"Missing path parameters");let l=await i.getVersionFile(o,a,c);return l?new Response(l,{headers:Ti}):m(404,"Version not found")}),"GET /resolve-profile":async(s,r)=>y(async()=>{if(!e)return m(501,"Geo compliance routing not configured");let o=new URL(s.url),a=s.headers.get("x-forwarded-for")?.split(",")[0]?.trim()??s.headers.get("x-real-ip")??"",c=o.searchParams.get("tz")??"",l=o.searchParams.get("lang")??s.headers.get("accept-language")??"",d=o.searchParams.get("locale")??"",u=o.searchParams.get("data");if(u)try{let b=JSON.parse(Buffer.from(u,"base64").toString("utf8"));b.timezone&&(c=b.timezone),b.language&&(l=b.language),b.locale&&(d=b.locale)}catch{}let p=await e.resolve({ip:a,language:l,timezone:c}),g=p.requiresSensitiveOptIn?{requiresSensitiveOptIn:!0}:{};if(!p.complianceGroup)return f(200,{path:null,complianceGroup:"opt-in",locale:d||"en",found:!1,...g});let E=await i.findActiveByComplianceGroup(p.complianceGroup);if(!E)return f(200,{path:null,complianceGroup:p.complianceGroup,locale:d||"en",found:!1,...g});let _=E.profileJson.locales??[],h=E.defaultLocale;if(d&&_.includes(d))h=d;else if(_.length>0){let b=(d||l).split("-")[0]??"",M=_.find(L=>L===b||L.startsWith(`${b}-`));M&&(h=M)}let w=p.complianceGroup,R=E.tenantId,I=!1,T=t?Bt(t,R,w,`${h}.json`):"";if(t&&Ht(T))I=!0;else if(t){let b=Bt(t,R,w,`${E.defaultLocale}.json`);Ht(b)&&(h=E.defaultLocale,T=b,I=!0)}let O=I?`${n}/profiles/${R}/${w}/${h}`:null;return f(200,{path:O,complianceGroup:w,locale:h,found:I,...g})}),"GET /profiles/:id":async(s,r)=>y(async()=>{let o=r.id;if(!o)return m(400,"Missing profile id");let a=await i.getResolved(o);return a?f(200,a):m(404,"Profile not found")}),"GET /profiles/:id/:locale":async(s,r)=>y(async()=>{let{id:o,locale:a}=r;if(!o)return m(400,"Missing profile id");let c=await i.getResolved(o,a);return c?f(200,c):m(404,"Profile not found")}),"GET /profiles/auto/:locale":async(s,r)=>y(async()=>{if(!e)return m(501,"Geo compliance routing not configured");let o=r.locale??"en",a=new URL(s.url),c=s.headers.get("x-forwarded-for")?.split(",")[0]?.trim()??s.headers.get("x-real-ip")??"",l=a.searchParams.get("tz")??"",d=s.headers.get("accept-language")??a.searchParams.get("lang")??"",u=await e.resolve({ip:c,language:d,timezone:l});if(!u.complianceGroup)return m(404,"Could not resolve compliance group for this location");let p=await i.findActiveByComplianceGroup(u.complianceGroup);if(!p)return m(404,`No active profile found for compliance group: ${u.complianceGroup}`);let g=await i.getResolved(p.id,o);return g?f(200,{...g,resolvedComplianceGroup:u.complianceGroup,...u.requiresSensitiveOptIn?{requiresSensitiveOptIn:!0}:{}}):m(404,"Profile not found")})}}function vi(){return process.env.NODE_ENV==="production"?"SameSite=None; Secure":"SameSite=Lax"}function sc(i,e,t){let n=Z(`${i}.${e}`,t);return`${e}.${n}`}function In(i,e,t){let n=`consenti_${i}`,s=sc(i,e,t);return`${n}=${s}; ${vi()}; Path=/; Max-Age=31536000`}function Pi(i){return`${`consenti_${i}`}=; ${vi()}; Path=/; Max-Age=0`}function Oi(i,e,t){let n=i.split(".");if(n.length!==2)return!1;let[s,r]=n;return!s||!r?!1:tt(`${e}.${s}`,r,t)}var Vt="pcon_";function ic(i){return Buffer.from(JSON.stringify(i),"utf8").toString("base64url")}function rc(i){try{let e=JSON.parse(Buffer.from(i,"base64url").toString("utf8"));return typeof e.visitorId!="string"||typeof e.profileId!="string"||typeof e.issuedAt!="number"?null:{visitorId:e.visitorId,profileId:e.profileId,issuedAt:e.issuedAt}}catch{return null}}function bi(i,e,t){let n={visitorId:i,profileId:e,issuedAt:Math.round(Date.now()/1e3)},s=ic(n);if(!t)return`${Vt}${s}`;let r=Z(s,t);return`${Vt}${s}.${r}`}function Di(i,e,t){if(!i.startsWith(Vt))return{valid:!1,reason:"malformed"};let n=i.slice(Vt.length),s=n.indexOf("."),r=s===-1?n:n.slice(0,s),o=s===-1?void 0:n.slice(s+1);if(e&&(!o||!tt(r,o,e)))return{valid:!1,reason:"invalid_signature"};let a=rc(r);return a?t>0&&Math.round(Date.now()/1e3)-a.issuedAt>t*24*60*60?{valid:!1,reason:"expired"}:{valid:!0,visitorId:a.visitorId,profileId:a.profileId}:{valid:!1,reason:"malformed"}}function oc(i){if(!i.includes("://"))return i;try{return new URL(i).hostname}catch{return i}}function Ui(i,e){let t;try{t=new URL(i).hostname}catch{return!1}return e.some(n=>{if(n.startsWith("*.")){let s=n.slice(1);return t===n.slice(2)||t.endsWith(s)}return t===oc(n)})}function Li(i,e){let t=i??"*";return e&&e.length>0&&i&&(t=Ui(i,e)?i:"null"),{"Access-Control-Allow-Origin":t,"Access-Control-Allow-Methods":"GET, POST, PUT, DELETE, OPTIONS","Access-Control-Allow-Headers":"Content-Type, Authorization","Access-Control-Max-Age":"86400","Access-Control-Allow-Credentials":"true"}}function Ni(i){if(i.method==="OPTIONS"){let e=i.headers.get("origin");return new Response(null,{status:204,headers:Li(e)})}return null}function Ve(i,e){let t=e.headers.get("origin"),n=Li(t),s=new Headers(i.headers);for(let[r,o]of Object.entries(n))s.set(r,o);return new Response(i.body,{status:i.status,statusText:i.statusText,headers:s})}function Wt(i,e){if(e.length===0)return!0;let t=i.headers.get("origin");return t?Ui(t,e):!0}var ac=["default-src 'self'","script-src 'self' https://unpkg.com","style-src 'self' https://unpkg.com 'unsafe-inline'","img-src 'self' data:","font-src 'self'","connect-src 'self'","frame-ancestors 'none'","base-uri 'self'","form-action 'self'"].join("; ");function Tn(i){let e=new Headers(i.headers);return e.set("X-Content-Type-Options","nosniff"),e.set("X-Frame-Options","DENY"),e.set("Referrer-Policy","no-referrer"),e.set("Permissions-Policy","interest-cohort=()"),e.set("Content-Security-Policy",ac),e.set("Strict-Transport-Security","max-age=63072000; includeSubDomains; preload"),new Response(i.body,{status:i.status,statusText:i.statusText,headers:e})}var cc=new Set(["granted","denied","objected"]);function ki(i){let e=Object.entries(i);for(let[t,n]of e){if(!t)return{valid:!1,error:"consentJson keys must be non-empty strings"};if(!cc.has(n))return{valid:!1,error:`consentJson value "${String(n)}" is invalid; must be granted, denied, or objected`}}return{valid:!0}}function Mi(i){if(!i||typeof i!="object")return{valid:!1,error:"Request body must be a JSON object"};let e=i;return typeof e.profileId!="string"||!e.profileId?{valid:!1,error:"profileId is required"}:!e.consentJson||typeof e.consentJson!="object"||Array.isArray(e.consentJson)?{valid:!1,error:"consentJson must be an object"}:ki(e.consentJson)}function ji(i){if(!i||typeof i!="object")return{valid:!1,error:"Request body must be a JSON object"};let e=i;return!e.consentJson||typeof e.consentJson!="object"||Array.isArray(e.consentJson)?{valid:!1,error:"consentJson must be an object"}:ki(e.consentJson)}function xi(i,e,t){return{tenantId:t,visitorId:e,profileId:i.profileId,locale:typeof i.locale=="string"?i.locale:"en",consentJson:i.consentJson,gpcDetected:!!i.gpcDetected,source:i.source??"banner",...typeof i.ageVerified=="boolean"?{ageVerified:i.ageVerified}:{},...typeof i.parentalConsentToken=="string"?{parentalConsentToken:i.parentalConsentToken}:{}}}function $i(i){let e={consentJson:i.consentJson};return typeof i.locale=="string"&&(e.locale=i.locale),i.gpcDetected!=null&&(e.gpcDetected=!!i.gpcDetected),e}function We(i,e,t){let n=i.headers.get("cookie")??"",s=`consenti_${e}`;return n.split(";").some(o=>{let a=o.trim(),c=a.indexOf("=");return c===-1||a.slice(0,c)!==s?!1:Oi(a.slice(c+1),e,t)})?null:m(403,"Forbidden")}function Fi(i,e,t,n=async()=>"default",s,r="",o,a=7){return{"POST /consent":async(c,l)=>y(async()=>{let d=await v(c),u=Mi(d);if(!u.valid)return m(400,u.error??"Invalid request");let p=d,g=typeof p.profileId=="string"?p.profileId:null;if(!g)return m(400,"profileId is required");let E=await t.get(g);if(!E)return m(404,"Profile not found");let _=await n(c),w=E.profileJson.allowedOrigins??[];if(w.length===0&&s&&(w=(await s.getSettings(_)).allowedOrigins??[]),!Wt(c,w))return m(403,"Origin not allowed for this profile");let R=typeof p.visitorId=="string"?p.visitorId:H(),I=c.headers.get("x-real-ip")??"",T=c.headers.get("user-agent")??"";await e.upsert({visitorId:R,ip:I,userAgent:T});let O=xi(p,R,_),b=await i.create(O),M=In(R,b.id,r);return new Response(JSON.stringify(b),{status:201,headers:{"Content-Type":"application/json","Set-Cookie":M}})}),"PUT /consent/:visitorId":async(c,l)=>y(async()=>{let d=l.visitorId;if(!d)return m(400,"Missing visitorId");let u=We(c,d,r);if(u)return u;let p=await v(c),g=ji(p);if(!g.valid)return m(400,g.error??"Invalid request");let E=$i(p),_=await i.update(d,E),h=In(d,_.id,r);return new Response(JSON.stringify(_),{status:200,headers:{"Content-Type":"application/json","Set-Cookie":h}})}),"GET /consent/:visitorId":async(c,l)=>y(async()=>{let d=l.visitorId;if(!d)return m(400,"Missing visitorId");let u=We(c,d,r);if(u)return u;let p=await i.get(d);return p?new Response(JSON.stringify(p),{status:200,headers:{"Content-Type":"application/json"}}):m(404,"Consent not found")}),"GET /consent/:visitorId/verify":async(c,l)=>y(async()=>{let d=l.visitorId;if(!d)return m(400,"Missing visitorId");let u=We(c,d,r);if(u)return u;let p=await i.verify(d);return new Response(JSON.stringify(p),{status:200,headers:{"Content-Type":"application/json"}})}),"DELETE /consent/:visitorId":async(c,l)=>y(async()=>{let d=l.visitorId;if(!d)return m(400,"Missing visitorId");let u=We(c,d,r);if(u)return u;let p=await i.get(d);await i.erase(d);let g={"Content-Type":"application/json"};return p&&(g["Set-Cookie"]=Pi(d)),new Response(JSON.stringify({success:!0}),{status:200,headers:g})}),"POST /consent/:visitorId/parental-consent-request":async(c,l)=>y(async()=>{let d=l.visitorId;if(!d)return m(400,"Missing visitorId");let u=We(c,d,r);if(u)return u;let p=await v(c),g=typeof p.profileId=="string"?p.profileId:null;if(!g)return m(400,"profileId is required");if(!await t.get(g))return m(404,"Profile not found");let E=bi(d,g,r),_=Math.round(Date.now()/1e3);return o?.emit("consent.parentalConsentRequired",{visitorId:d,profileId:g,token:E,issuedAt:_}),f(200,{token:E})}),"POST /consent/parental-consent-resolve":async c=>y(async()=>{let l=await v(c),d=typeof l.token=="string"?l.token:null;if(!d)return m(400,"token is required");let u=Di(d,r,a);return u.valid?(o?.emit("consent.parentalConsentGranted",{visitorId:u.visitorId,profileId:u.profileId}),f(200,{visitorId:u.visitorId,profileId:u.profileId})):m(403,`Invalid or expired token (${u.reason})`)})}}var lc=!1;function Hi(i){return{"POST /notice-shown":async(e,t)=>y(async()=>{if(!lc)return m(404,"Not found");let n=await v(e);if(!n||typeof n!="object")return m(400,"Request body must be a JSON object");let s=n,r=typeof s.visitorId=="string"?s.visitorId:"",o=typeof s.profileId=="string"?s.profileId:"",a=typeof s.locale=="string"?s.locale:"";if(!r)return m(400,"visitorId is required");if(!o)return m(400,"profileId is required");if(!a)return m(400,"locale is required");let c=await i.recordShown({visitorId:r,profileId:o,locale:a});return new Response(JSON.stringify({success:!0,id:c.id}),{status:201,headers:{"Content-Type":"application/json"}})})}}function Bi(i,e){return{"GET /tcf/status":async(t,n)=>y(async()=>{if(!e?.enabled)return f(200,{blocked:!1});let s=await i.getSettings("default"),r=ee(!0,{cmpId:e.cmpId,cmpVersion:e.cmpVersion,publisherCC:e.publisherCC},s.tcfConfirmation);return f(200,{blocked:!r})})}}function Vi(i,e){return{"GET /gpp/status":async(t,n)=>y(async()=>{if(!e?.enabled)return f(200,{blocked:!1});let s=await i.getSettings("default"),r=ee(!0,{cmpId:e.cmpId,cmpVersion:e.cmpVersion},s.gppConfirmation);return f(200,{blocked:!r})})}}import{randomBytes as rr}from"crypto";import{createHash as dc}from"crypto";async function P(i,e,t,n){let s=i.headers.get("authorization"),r=s?.startsWith("Bearer ")?s.slice(7):null;if(!r)return null;if(r.startsWith("ck_")){let o=dc("sha256").update(r).digest("hex"),a=await e.getApiKeyByHash(o);if(!a||!a.isActive)return null;if(a.expireBy&&new Date(a.expireBy)<=new Date)return await e.revokeApiKey(a.id).catch(()=>{}),null;let c=(await e.getAllPermissions()).map(l=>l.name);return{sub:`apikey:${a.id}`,email:a.name,roles:["api_key"],permissions:c,allowedTenants:[a.tenantId]}}if(t.mode==="local"||t.mode==="jwt"){let o=nt(r,n);if(!o)return null;let a=o.sub,c=o.email,l=o.roles,d=o.permissions,u=o.allowedTenants;return typeof a!="string"||typeof c!="string"?null:{sub:a,email:c,roles:Array.isArray(l)?l:[],permissions:Array.isArray(d)?d:[],allowedTenants:Array.isArray(u)?u:[]}}if(t.mode==="custom"&&t.validateUser){let o=await t.validateUser(i);if(!o)return null;let a=await e.getUserRoles(o.id),c=await Promise.all(a.map(d=>e.getPermissionsForRole(d.id))),l=[...new Set(c.flat().map(d=>d.name))];return{sub:o.id,email:o.email,roles:a.map(d=>d.name),permissions:l,allowedTenants:[]}}return null}function D(i,e){return i?e&&!i.permissions.includes(e)?m(403,"Forbidden"):null:m(401,"Unauthorized")}import{randomBytes as uc,createHash as pc}from"crypto";function Wi(i){return i.toString("base64").replace(/\+/g,"-").replace(/\//g,"_").replace(/=/g,"")}function Ji(i){return Buffer.from(i.replace(/-/g,"+").replace(/_/g,"/"),"base64").toString("utf8")}function qi(){let i=Wi(uc(32)),e=Wi(pc("sha256").update(i).digest());return{verifier:i,challenge:e}}async function An(i){let e=await fetch(`${i}/.well-known/openid-configuration`,{signal:AbortSignal.timeout(1e4)});if(!e.ok)throw new Error(`OIDC discovery failed for ${i}: ${e.status}`);return e.json()}function Gi(i,e,t,n,s){let r=new URLSearchParams({response_type:"code",client_id:i.clientId,redirect_uri:i.redirectUri,scope:"openid email profile",state:t,nonce:n,code_challenge:s,code_challenge_method:"S256"});return`${e}?${r.toString()}`}async function Ki(i,e,t,n){let s=new URLSearchParams({grant_type:"authorization_code",client_id:i.clientId,client_secret:i.clientSecret,redirect_uri:i.redirectUri,code:t,code_verifier:n}),r=await fetch(e,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:s.toString(),signal:AbortSignal.timeout(1e4)});if(!r.ok){let o=await r.text();throw new Error(`OIDC token exchange failed: ${o}`)}return r.json()}async function Yi(i,e){let t=i.split(".");if(t.length!==3)throw new Error("Invalid ID token format");let n=JSON.parse(Ji(t[0])),s=n.alg??"RS256",r=await fetch(e,{signal:AbortSignal.timeout(1e4)});if(!r.ok)throw new Error(`Failed to fetch JWKS: ${r.status}`);let o=await r.json(),a=n.kid?o.keys.find(w=>w.kid===n.kid):o.keys.find(w=>w.use==="sig")??o.keys[0];if(!a)throw new Error("No matching key in JWKS");let c;if(s==="RS256"||s==="RS384"||s==="RS512"){let w=s==="RS256"?"SHA-256":s==="RS384"?"SHA-384":"SHA-512";c=await globalThis.crypto.subtle.importKey("jwk",a,{name:"RSASSA-PKCS1-v1_5",hash:{name:w}},!1,["verify"])}else if(s==="ES256"||s==="ES384"||s==="ES512"){let w=s==="ES256"?"P-256":s==="ES384"?"P-384":"P-521";c=await globalThis.crypto.subtle.importKey("jwk",a,{name:"ECDSA",namedCurve:w},!1,["verify"])}else throw new Error(`Unsupported OIDC token algorithm: ${s}`);let l=new TextEncoder().encode(`${t[0]}.${t[1]}`),d=(4-t[2].length%4)%4,u=t[2].replace(/-/g,"+").replace(/_/g,"/")+"=".repeat(d),p=Uint8Array.from(atob(u),w=>w.charCodeAt(0)),g=s.startsWith("ES")?{name:"ECDSA",hash:{name:`SHA-${s.slice(2)}`}}:{name:"RSASSA-PKCS1-v1_5"};if(!await globalThis.crypto.subtle.verify(g,c,p,l))throw new Error("ID token signature verification failed");let _=JSON.parse(Ji(t[1])),h=Math.floor(Date.now()/1e3);if(typeof _.exp=="number"&&_.exp<h)throw new Error("ID token has expired");return _}function zi(i,e){let t=e?.email??"email",n=i[t];if(typeof n!="string")throw new Error("OIDC token missing email claim");return n}function Qi(i,e){let t=e?.roles??"consenti_roles",n=i[t];return Array.isArray(n)?n.filter(s=>typeof s=="string"):[]}var Je=new Map;setInterval(()=>{let i=Date.now();for(let[e,t]of Je)t.expiresAt<i&&Je.delete(e)},5*6e4).unref();function Xi(i,e,t){Je.set(i,{verifier:e,nonce:t,expiresAt:Date.now()+10*6e4})}function Zi(i){let e=Je.get(i);return Je.delete(i),!e||e.expiresAt<Date.now()?null:{verifier:e.verifier,nonce:e.nonce}}function mc(i){return i.replace(/&/g,"&amp;").replace(/"/g,"&quot;").replace(/</g,"&lt;").replace(/>/g,"&gt;")}function er(i){return`<?xml version="1.0" encoding="UTF-8"?>
134
+ GROUP BY v.country ORDER BY count DESC LIMIT 50`,[e])).map(n=>({country:n.country,count:Number(n.count)}))}async getGpcStats(e){let[t]=await this.q("SELECT COUNT(*)::int AS total FROM consenti_consent_records WHERE tenant_id=$1",[e]),[n]=await this.q("SELECT COUNT(*)::int AS total FROM consenti_consent_records WHERE tenant_id=$1 AND gpc_detected=TRUE",[e]),s=Number(t?.total??0),r=Number(n?.total??0);return{detected:r,total:s,rate:s>0?Math.round(r/s*100):0}}async getTenants(){return(await this.q("SELECT * FROM consenti_tenants ORDER BY name ASC",[])).map(t=>({id:t.id,name:t.name,slug:t.slug,createdAt:t.created_at,updatedAt:t.updated_at}))}mapApiKey(e){return{id:e.id,tenantId:e.tenant_id,keyHash:e.key_hash,name:e.name,isActive:e.is_active,...e.created_by!==null?{createdBy:e.created_by}:{},...e.expire_by!==null?{expireBy:e.expire_by}:{},createdAt:e.created_at,...e.updated_at!==null?{updatedAt:e.updated_at}:{}}}async createApiKey(e){let t=A(),s=(await this.q("INSERT INTO consenti_api_keys (id, tenant_id, key_hash, name, created_by, expire_by) VALUES ($1,$2,$3,$4,$5,$6) RETURNING *",[t,e.tenantId,e.keyHash,e.name,e.createdBy??null,e.expireBy??null]))[0];if(!s)throw new Error("Failed to create API key");return this.mapApiKey(s)}async getApiKeyByHash(e){let n=(await this.q("SELECT * FROM consenti_api_keys WHERE key_hash=$1 AND is_active=TRUE",[e]))[0];return n?this.mapApiKey(n):null}async revokeApiKey(e){await this.q("UPDATE consenti_api_keys SET is_active=FALSE, updated_at=NOW() WHERE id=$1",[e])}async reactivateApiKey(e){await this.q("UPDATE consenti_api_keys SET is_active=TRUE, updated_at=NOW(), expire_by=CASE WHEN expire_by <= NOW() THEN NULL ELSE expire_by END WHERE id=$1",[e])}async deleteApiKey(e){await this.q("DELETE FROM consenti_api_keys WHERE id=$1",[e])}async getApiKeys(e){return(await this.q("SELECT * FROM consenti_api_keys WHERE tenant_id=$1 ORDER BY created_at DESC",[e])).map(n=>this.mapApiKey(n))}async createTenant(e){let t=A(),n=new Date().toISOString(),r=(await this.q("INSERT INTO consenti_tenants (id, name, slug, created_at, updated_at) VALUES ($1,$2,$3,$4,$5) RETURNING *",[t,e.name,e.slug,n,n]))[0];if(!r)throw new Error("Failed to create tenant");return{id:r.id,name:r.name,slug:r.slug,createdAt:r.created_at,updatedAt:r.updated_at}}async updateTenant(e,t){let n=new Date().toISOString(),s=["updated_at=$1"],r=[n],o=2;t.name!=null&&(s.push(`name=$${o++}`),r.push(t.name)),t.slug!=null&&(s.push(`slug=$${o++}`),r.push(t.slug)),r.push(e);let c=(await this.q(`UPDATE consenti_tenants SET ${s.join(",")} WHERE id=$${o} RETURNING *`,r))[0];if(!c)throw new Error(`Tenant ${e} not found`);return{id:c.id,name:c.name,slug:c.slug,createdAt:c.created_at,updatedAt:c.updated_at}}async deleteTenant(e){await this.q("DELETE FROM consenti_tenants WHERE id=$1",[e])}async getSettings(e){let n=(await this.q("SELECT allowed_origins_json, admin_allowed_origins_json, setup_completed, profiles_seeded, tcf_confirmation_json, gpp_confirmation_json FROM consenti_tenant_settings WHERE tenant_id=$1",[e]))[0];return n?{allowedOrigins:n.allowed_origins_json,adminAllowedOrigins:n.admin_allowed_origins_json,setupCompleted:n.setup_completed,profilesSeeded:n.profiles_seeded,...n.tcf_confirmation_json?{tcfConfirmation:n.tcf_confirmation_json}:{},...n.gpp_confirmation_json?{gppConfirmation:n.gpp_confirmation_json}:{}}:{}}async updateSettings(e,t){let s={...await this.getSettings(e),...t};return await this.q(`INSERT INTO consenti_tenant_settings (tenant_id, allowed_origins_json, admin_allowed_origins_json, setup_completed, profiles_seeded, tcf_confirmation_json, gpp_confirmation_json, updated_at) VALUES ($1,$2::jsonb,$3::jsonb,$4,$5,$6::jsonb,$7::jsonb,NOW())
135
+ ON CONFLICT (tenant_id) DO UPDATE SET allowed_origins_json=$2::jsonb, admin_allowed_origins_json=$3::jsonb, setup_completed=$4, profiles_seeded=$5, tcf_confirmation_json=$6::jsonb, gpp_confirmation_json=$7::jsonb, updated_at=NOW()`,[e,JSON.stringify(s.allowedOrigins??[]),JSON.stringify(s.adminAllowedOrigins??[]),s.setupCompleted??!1,s.profilesSeeded??!1,s.tcfConfirmation?JSON.stringify(s.tcfConfirmation):null,s.gppConfirmation?JSON.stringify(s.gppConfirmation):null]),this.getSettings(e)}async purgeExpiredConsents(e){let t=new Date(Date.now()-e*864e5).toISOString(),n=await this.q("SELECT visitor_id FROM consenti_consent_records WHERE created_at < $1",[t]);if(n.length===0)return 0;let s=n.map(o=>o.visitor_id),r=s.map((o,a)=>`$${a+1}`).join(",");return await this.q(`DELETE FROM consenti_consent_history WHERE visitor_id IN (${r})`,s),await this.q(`DELETE FROM consenti_consent_records WHERE visitor_id IN (${r})`,s),s.length}mapConsentTemplate(e){return{id:e.id,tenantId:e.tenant_id,name:e.name,cookies:e.cookies_json,categories:e.categories_json,createdAt:e.created_at,updatedAt:e.updated_at}}async createConsentTemplate(e){let n=(await this.q(`INSERT INTO consenti_consent_templates (id, tenant_id, name, cookies_json, categories_json)
136
+ VALUES ($1,$2,$3,$4::json,$5::json) RETURNING *`,[G(),e.tenantId,e.name,JSON.stringify(e.cookies),JSON.stringify(e.categories)]))[0];if(!n)throw new Error("Consent template creation failed");return this.mapConsentTemplate(n)}async updateConsentTemplate(e,t){let n=["updated_at = NOW()"],s=[],r=1;t.name!==void 0&&(n.push(`name=$${r++}`),s.push(t.name)),t.cookies!==void 0&&(n.push(`cookies_json=$${r++}::json`),s.push(JSON.stringify(t.cookies))),t.categories!==void 0&&(n.push(`categories_json=$${r++}::json`),s.push(JSON.stringify(t.categories)));let a=(await this.q(`UPDATE consenti_consent_templates SET ${n.join(",")} WHERE id=$${r} RETURNING *`,[...s,e]))[0];if(!a)throw new Error("Consent template not found");return this.mapConsentTemplate(a)}async deleteConsentTemplate(e){await this.q("DELETE FROM consenti_consent_templates WHERE id=$1",[e])}async getConsentTemplate(e){let t=await this.q("SELECT * FROM consenti_consent_templates WHERE id=$1",[e]);return t[0]?this.mapConsentTemplate(t[0]):null}async getConsentTemplates(e){return(await this.q("SELECT * FROM consenti_consent_templates WHERE tenant_id=$1 ORDER BY name ASC",[e])).map(n=>this.mapConsentTemplate(n))}async copyConsentTemplate(e,t){let n=await this.getConsentTemplate(e);if(!n)throw new Error("Consent template not found");return this.createConsentTemplate({tenantId:n.tenantId,name:t,cookies:n.cookies,categories:n.categories})}mapUITemplate(e){return{id:e.id,tenantId:e.tenant_id,name:e.name,...e.settings_json,createdAt:e.created_at,updatedAt:e.updated_at}}async createUITemplate(e){let{tenantId:t,name:n,...s}=e,o=(await this.q("INSERT INTO consenti_ui_templates (id, tenant_id, name, settings_json) VALUES ($1,$2,$3,$4::json) RETURNING *",[q(),t,n,JSON.stringify(s)]))[0];if(!o)throw new Error("UI template creation failed");return this.mapUITemplate(o)}async updateUITemplate(e,t){let n=await this.q("SELECT * FROM consenti_ui_templates WHERE id=$1",[e]);if(!n[0])throw new Error("UI template not found");let{name:s,...r}=t,o=["updated_at = NOW()"],a=[],c=1;s!==void 0&&(o.push(`name=$${c++}`),a.push(s)),Object.keys(r).length>0&&(o.push(`settings_json=$${c++}::json`),a.push(JSON.stringify({...n[0].settings_json,...r})));let d=(await this.q(`UPDATE consenti_ui_templates SET ${o.join(",")} WHERE id=$${c} RETURNING *`,[...a,e]))[0];if(!d)throw new Error("UI template not found");return this.mapUITemplate(d)}async deleteUITemplate(e){await this.q("DELETE FROM consenti_ui_templates WHERE id=$1",[e])}async getUITemplate(e){let t=await this.q("SELECT * FROM consenti_ui_templates WHERE id=$1",[e]);return t[0]?this.mapUITemplate(t[0]):null}async getUITemplates(e){return(await this.q("SELECT * FROM consenti_ui_templates WHERE tenant_id=$1 ORDER BY name ASC",[e])).map(n=>this.mapUITemplate(n))}async copyUITemplate(e,t){let n=await this.getUITemplate(e);if(!n)throw new Error("UI template not found");let{tenantId:s,mainBanner:r,gpcBanner:o,preferenceModal:a}=n;return this.createUITemplate({tenantId:s,name:t,mainBanner:r,gpcBanner:o,preferenceModal:a})}async toProfileSummaries(e){if(e.length===0)return[];let t=[...new Set(e.map(r=>r.tenantId))],[n,s]=await Promise.all([Promise.all(t.map(r=>this.getConsentTemplates(r))),Promise.all(t.map(r=>this.getUITemplates(r)))]);return Ae(e,new Map(n.flat().map(r=>[r.id,r.name])),new Map(s.flat().map(r=>[r.id,r.name])))}async listProfilesSummary(e){let t=await this.q("SELECT * FROM consenti_profiles WHERE tenant_id=$1 ORDER BY created_at ASC",[e]);return this.toProfileSummaries(t.map(Ee))}async findProfilesUsingConsentTemplate(e){let t=await this.q("SELECT * FROM consenti_profiles WHERE profile_json->>'consentTemplateId'=$1 ORDER BY name ASC",[e]);return this.toProfileSummaries(t.map(Ee))}async findProfilesUsingUITemplate(e){let t=await this.q("SELECT * FROM consenti_profiles WHERE profile_json->>'uiTemplateId'=$1 ORDER BY name ASC",[e]);return this.toProfileSummaries(t.map(Ee))}async getOptInStats(e,t){let n="tenant_id=$1",s=[e];t.profileId&&(s.push(t.profileId),n+=` AND profile_id=$${s.length}`),t.from&&(s.push(t.from),n+=` AND created_at>=$${s.length}`),t.to&&(s.push(t.to),n+=` AND created_at<=$${s.length}`),t.locale&&(s.push(t.locale),n+=` AND locale=$${s.length}`);let r=await this.q(`SELECT locale, to_char(created_at, 'YYYY-MM-DD') AS day, consent_json FROM consenti_consent_records WHERE ${n} ORDER BY created_at ASC`,s);return ve(r.map(o=>({locale:o.locale,day:o.day,consentJson:o.consent_json})))}};import{readFile as Ra,writeFile as Sa,rename as Ca,mkdir as Ia}from"fs/promises";import{randomUUID as pe}from"crypto";import{dirname as Ta}from"path";var Aa=500;function Tn(){return{_version:1,tenants:[],profiles:[],consents:[],consent_history:[],visitors:[],users:[],roles:[],permissions:[],user_roles:[],role_permissions:[],audit_logs:[],consent_templates:[],ui_templates:[],api_keys:[],settings:{},notice_shown:[]}}function An(i,e,t){let n=Math.min(t,Aa),s=(e-1)*n;return i.slice(s,s+n)}function va(i){let{id:e,tenantId:t,visitorId:n,profileId:s,locale:r,gpcDetected:o,source:a,ageVerified:c,createdAt:l,updatedAt:d}=i;return{id:e,tenantId:t,visitorId:n,profileId:s,locale:r,gpcDetected:o,source:a,...c!=null?{ageVerified:c}:{},createdAt:l,updatedAt:d}}function Pa(i){let{id:e,tenantId:t,userId:n,action:s,resourceType:r,resourceId:o,createdAt:a}=i;return{id:e,tenantId:t,action:s,resourceType:r,createdAt:a,...n!=null?{userId:n}:{},...o!=null?{resourceId:o}:{}}}var Rt=class{constructor(e){this.config=e}config;db=Tn();filePath="";writeQueue=Promise.resolve();scheduleWrite(){this.writeQueue=this.writeQueue.then(async()=>{await Ia(Ta(this.filePath),{recursive:!0});let e=`${this.filePath}.tmp`;await Sa(e,JSON.stringify(this.db),"utf8"),await Ca(e,this.filePath)}).catch(e=>{console.warn("[Consenti] JSON store write failed:",e)})}async connect(){let{dbPath:e}=ie(this.config.path??"./consenti-data","json");this.filePath=e;try{let t=await Ra(this.filePath,"utf8");this.db={...Tn(),...JSON.parse(t)}}catch{this.db=Tn()}await this.migrate()}async disconnect(){await this.writeQueue}async migrate(){this.seed()}seed(){let e=new Date().toISOString(),{id:t,name:n,slug:s}=Y;this.db.tenants.find(r=>r.id===t)||this.db.tenants.push({id:t,name:n,slug:s,createdAt:e,updatedAt:e});for(let r of K)this.db.permissions.find(o=>o.id===r.id)||this.db.permissions.push({id:r.id,name:r.name,description:r.description});for(let r of re)this.db.roles.find(o=>o.id===r.id)||this.db.roles.push({id:r.id,tenantId:r.tenantId,name:r.name,description:r.description});for(let r of oe)this.db.role_permissions.find(o=>o.roleId===r.roleId&&o.permissionId===r.permissionId)||this.db.role_permissions.push(r);this.scheduleWrite()}async createProfile(e){let t=new Date().toISOString(),s={id:W(),tenantId:e.tenantId,name:e.name,defaultLocale:e.defaultLocale,version:1,profileJson:e.profileJson,createdAt:t,updatedAt:t};return this.db.profiles.push(s),this.scheduleWrite(),s}async updateProfile(e,t){let n=this.db.profiles.findIndex(o=>o.id===e);if(n===-1)throw new Error(`Profile ${e} not found`);let r={...this.db.profiles[n],...t.name!=null&&{name:t.name},...t.defaultLocale!=null&&{defaultLocale:t.defaultLocale},...t.profileJson!=null&&{profileJson:t.profileJson},...t.version!=null&&{version:t.version},updatedAt:new Date().toISOString()};return this.db.profiles[n]=r,this.scheduleWrite(),r}async deleteProfile(e){this.db.profiles=this.db.profiles.filter(t=>t.id!==e),this.scheduleWrite()}async getProfile(e){return this.db.profiles.find(t=>t.id===e)??null}async getProfiles(e){return this.db.profiles.filter(t=>t.tenantId===e).sort((t,n)=>t.createdAt.localeCompare(n.createdAt))}async findActiveProfileByComplianceGroup(e,t){return this.db.profiles.find(n=>n.tenantId===e&&(n.profileJson.complianceGroup===t||n.profileJson.customComplianceGroup===t)&&n.profileJson.isActive)??null}async createConsent(e){let t=new Date().toISOString(),n=J(),s={id:n,tenantId:e.tenantId,visitorId:e.visitorId,profileId:e.profileId,...e.profileVersion!=null&&{profileVersion:e.profileVersion},locale:e.locale,consentJson:e.consentJson,gpcDetected:e.gpcDetected??!1,source:e.source,...e.ageVerified!=null&&{ageVerified:e.ageVerified},...e.parentalConsentToken!=null&&{parentalConsentToken:e.parentalConsentToken},...e.tcfString!=null&&{tcfString:e.tcfString},...e.gppString!=null&&{gppString:e.gppString},...e.signature!=null&&{signature:e.signature},createdAt:t,updatedAt:t};return this.db.consents.push(s),this.db.consent_history.push({id:pe(),tenantId:e.tenantId,consentRecordId:n,visitorId:e.visitorId,...e.profileVersion!=null&&{profileVersion:e.profileVersion},oldJson:null,newJson:e.consentJson,action:"created",createdAt:t}),this.scheduleWrite(),s}async updateConsent(e,t){let n=this.db.consents.findIndex(a=>a.visitorId===e);if(n===-1)throw new Error(`Consent for visitor ${e} not found`);let s=this.db.consents[n],r=new Date().toISOString(),o={...s,consentJson:t.consentJson,...t.profileVersion!=null&&{profileVersion:t.profileVersion},...t.locale!=null&&{locale:t.locale},...t.gpcDetected!=null&&{gpcDetected:t.gpcDetected},...t.signature!=null&&{signature:t.signature},updatedAt:r};return this.db.consents[n]=o,this.db.consent_history.push({id:pe(),tenantId:s.tenantId,consentRecordId:s.id,visitorId:e,...(t.profileVersion??s.profileVersion)!=null&&{profileVersion:t.profileVersion??s.profileVersion},oldJson:s.consentJson,newJson:t.consentJson,action:"updated",createdAt:r}),this.scheduleWrite(),o}async deleteConsent(e){this.db.consent_history=this.db.consent_history.filter(t=>t.visitorId!==e),this.db.consents=this.db.consents.filter(t=>t.visitorId!==e),this.scheduleWrite()}async getConsent(e){return this.db.consents.find(t=>t.visitorId===e)??null}async getConsents(e){let t=this.db.consents.filter(r=>r.tenantId===e.tenantId);if(e.profileId&&(t=t.filter(r=>r.profileId===e.profileId)),e.from&&(t=t.filter(r=>r.createdAt>=e.from)),e.to&&(t=t.filter(r=>r.createdAt<=e.to)),e.q){let r=e.q.toLowerCase();t=t.filter(o=>o.visitorId.toLowerCase().startsWith(r)||o.profileId.toLowerCase().startsWith(r)||o.locale.toLowerCase().startsWith(r)||o.source.toLowerCase().startsWith(r))}t.sort((r,o)=>o.createdAt.localeCompare(r.createdAt));let n=e.page??1,s=e.limit??50;return{items:An(t,n,s).map(va),total:t.length,page:n,limit:s}}async*streamConsents(e){for(let t of this.db.consents.filter(n=>n.tenantId===e.tenantId))yield t}async getConsentHistory(e){return this.db.consent_history.filter(t=>t.visitorId===e).sort((t,n)=>t.createdAt.localeCompare(n.createdAt))}async createNoticeShown(e){let t={id:pe(),tenantId:e.tenantId,visitorId:e.visitorId,profileId:e.profileId,locale:e.locale,createdAt:new Date().toISOString()};return this.db.notice_shown.push(t),this.scheduleWrite(),t}async getNoticeShownForVisitor(e){return this.db.notice_shown.filter(t=>t.visitorId===e).sort((t,n)=>n.createdAt.localeCompare(t.createdAt))}async createVisitor(e){let t=new Date().toISOString(),n={id:H(),tenantId:e.tenantId,visitorId:e.visitorId,firstSeen:t,lastSeen:t,...e.country!=null&&{country:e.country},...e.region!=null&&{region:e.region},...e.city!=null&&{city:e.city},...e.ipHash!=null&&{ipHash:e.ipHash},...e.userAgentHash!=null&&{userAgentHash:e.userAgentHash}};return this.db.visitors.push(n),this.scheduleWrite(),n}async updateVisitor(e,t){let n=this.db.visitors.findIndex(o=>o.visitorId===e);if(n===-1)throw new Error(`Visitor ${e} not found`);let r={...this.db.visitors[n],lastSeen:t.lastSeen??new Date().toISOString(),...t.country!=null&&{country:t.country},...t.region!=null&&{region:t.region},...t.city!=null&&{city:t.city}};return this.db.visitors[n]=r,this.scheduleWrite(),r}async deleteVisitor(e){this.db.visitors=this.db.visitors.filter(t=>t.visitorId!==e),this.scheduleWrite()}async getVisitor(e){return this.db.visitors.find(t=>t.visitorId===e)??null}async getVisitors(e){let t=this.db.visitors.filter(r=>r.tenantId===e.tenantId);if(e.from&&(t=t.filter(r=>r.firstSeen>=e.from)),e.to&&(t=t.filter(r=>r.firstSeen<=e.to)),e.q){let r=e.q.toLowerCase();t=t.filter(o=>o.visitorId.toLowerCase().startsWith(r)||(o.country?.toLowerCase().startsWith(r)??!1))}t.sort((r,o)=>o.lastSeen.localeCompare(r.lastSeen));let n=e.page??1,s=e.limit??50;return{items:An(t,n,s),total:t.length,page:n,limit:s}}async createUser(e){let t=new Date().toISOString(),n={id:pe(),tenantId:e.tenantId,name:e.name,email:e.email,passwordHash:e.passwordHash,isActive:!0,allowedTenants:e.allowedTenants??[],createdAt:t,updatedAt:t};return this.db.users.push(n),this.scheduleWrite(),n}async updateUser(e,t){let n=this.db.users.findIndex(o=>o.id===e);if(n===-1)throw new Error(`User ${e} not found`);let r={...this.db.users[n],...t.name!=null&&{name:t.name},...t.email!=null&&{email:t.email},...t.passwordHash!=null&&{passwordHash:t.passwordHash},...t.isActive!=null&&{isActive:t.isActive},..."totpSecret"in t&&t.totpSecret!=null&&{totpSecret:t.totpSecret},...t.totpEnabled!=null&&{totpEnabled:t.totpEnabled},..."allowedTenants"in t&&{allowedTenants:t.allowedTenants??[]},updatedAt:new Date().toISOString()};return this.db.users[n]=r,this.scheduleWrite(),r}async deleteUser(e){this.db.user_roles=this.db.user_roles.filter(t=>t.userId!==e),this.db.users=this.db.users.filter(t=>t.id!==e),this.scheduleWrite()}async getUserById(e){return this.db.users.find(t=>t.id===e)??null}async getUserByEmail(e){return this.db.users.find(t=>t.email===e)??null}async getUsers(e){return this.db.users.filter(t=>t.tenantId===e).sort((t,n)=>t.createdAt.localeCompare(n.createdAt))}async countUsers(e){return e?this.db.users.filter(t=>t.tenantId===e).length:this.db.users.length}async getRoles(e){return this.db.roles.filter(t=>t.tenantId===e)}async createRole(e){let t={id:pe(),tenantId:e.tenantId,name:e.name,...e.description!=null&&{description:e.description}};return this.db.roles.push(t),this.scheduleWrite(),t}async updateRole(e,t){let n=this.db.roles.findIndex(r=>r.id===e);if(n===-1)throw new Error(`Role ${e} not found`);let s={...this.db.roles[n],...t.name!=null&&{name:t.name},...t.description!=null&&{description:t.description}};return this.db.roles[n]=s,this.scheduleWrite(),s}async deleteRole(e){this.db.role_permissions=this.db.role_permissions.filter(t=>t.roleId!==e),this.db.user_roles=this.db.user_roles.filter(t=>t.roleId!==e),this.db.roles=this.db.roles.filter(t=>t.id!==e),this.scheduleWrite()}async getAllPermissions(){return[...this.db.permissions].sort((e,t)=>e.name.localeCompare(t.name))}async getPermissionsForRole(e){let t=new Set(this.db.role_permissions.filter(n=>n.roleId===e).map(n=>n.permissionId));return this.db.permissions.filter(n=>t.has(n.id))}async getUserRoles(e,t){let n=this.db.user_roles.filter(r=>r.userId===e);t&&(n=n.filter(r=>r.tenantId===t));let s=new Set(n.map(r=>r.roleId));return this.db.roles.filter(r=>s.has(r.id))}async assignRole(e,t,n="default"){this.db.user_roles.find(s=>s.userId===e&&s.roleId===t&&s.tenantId===n)||(this.db.user_roles.push({userId:e,roleId:t,tenantId:n}),this.scheduleWrite())}async revokeRole(e,t,n){this.db.user_roles=this.db.user_roles.filter(s=>!(s.userId===e&&s.roleId===t&&(n==null||s.tenantId===n))),this.scheduleWrite()}async assignPermissionToRole(e,t){this.db.role_permissions.find(n=>n.roleId===e&&n.permissionId===t)||(this.db.role_permissions.push({roleId:e,permissionId:t}),this.scheduleWrite())}async revokePermissionFromRole(e,t){this.db.role_permissions=this.db.role_permissions.filter(n=>!(n.roleId===e&&n.permissionId===t)),this.scheduleWrite()}async createLog(e){let t={id:pe(),tenantId:e.tenantId,action:e.action,resourceType:e.resourceType,createdAt:new Date().toISOString(),...e.userId!=null&&{userId:e.userId},...e.resourceId!=null&&{resourceId:e.resourceId},...e.oldData!=null&&{oldData:e.oldData},...e.newData!=null&&{newData:e.newData}};this.db.audit_logs.push(t),this.scheduleWrite()}async getLogs(e){let t=this.db.audit_logs.filter(r=>r.tenantId===e.tenantId);if(e.action&&(t=t.filter(r=>r.action===e.action)),e.resourceType&&(t=t.filter(r=>r.resourceType===e.resourceType)),e.from&&(t=t.filter(r=>r.createdAt>=e.from)),e.to&&(t=t.filter(r=>r.createdAt<=e.to)),e.q){let r=e.q.toLowerCase();t=t.filter(o=>o.action.toLowerCase().startsWith(r)||o.resourceType.toLowerCase().startsWith(r)||(o.resourceId?.toLowerCase().startsWith(r)??!1)||(o.userId?.toLowerCase().startsWith(r)??!1))}t.sort((r,o)=>o.createdAt.localeCompare(r.createdAt));let n=e.page??1,s=e.limit??50;return{items:An(t,n,s).map(Pa),total:t.length,page:n,limit:s}}async getAuditLogById(e){return this.db.audit_logs.find(t=>t.id===e)??null}async*streamAuditLogs(e){let t=this.db.audit_logs.filter(n=>n.tenantId===e.tenantId);e.action&&(t=t.filter(n=>n.action===e.action)),e.from&&(t=t.filter(n=>n.createdAt>=e.from)),e.to&&(t=t.filter(n=>n.createdAt<=e.to)),t.sort((n,s)=>s.createdAt.localeCompare(n.createdAt));for(let n of t)yield n}async getOverviewStats(e){let t=this.db.consents.filter(a=>a.tenantId===e),n=t.slice(-500),s=0,r=0;for(let a of n){let c=Object.values(a.consentJson),l=c.length>0?c.filter(d=>d==="granted").length/c.length:0;l>=.8?s++:l<=.2&&r++}let o=n.length||1;return{totalConsents:t.length,gpcDetectedCount:t.filter(a=>a.gpcDetected).length,totalVisitors:this.db.visitors.filter(a=>a.tenantId===e).length,acceptedPct:Math.round(s/o*100),rejectedPct:Math.round(r/o*100)}}async getCategoryStats(e){let t={};for(let n of this.db.consents.filter(s=>s.tenantId===e).slice(0,5e3))for(let[s,r]of Object.entries(n.consentJson))t[s]||(t[s]={granted:0,denied:0,objected:0}),r==="granted"?t[s].granted++:r==="denied"?t[s].denied++:r==="objected"&&t[s].objected++;return t}async getTimeline(e,t=30){let n=new Date(Date.now()-t*864e5).toISOString().slice(0,10),s=new Map;for(let r of this.db.consents.filter(o=>o.tenantId===e&&o.createdAt.slice(0,10)>=n)){let o=r.createdAt.slice(0,10);s.set(o,(s.get(o)??0)+1)}return[...s.entries()].sort(([r],[o])=>r.localeCompare(o)).map(([r,o])=>({date:r,count:o}))}async getCountries(e){let t=new Set(this.db.consents.filter(s=>s.tenantId===e).map(s=>s.visitorId)),n=new Map;for(let s of this.db.visitors.filter(r=>t.has(r.visitorId)&&r.country)){let r=s.country;n.set(r,(n.get(r)??0)+1)}return[...n.entries()].sort(([,s],[,r])=>r-s).slice(0,50).map(([s,r])=>({country:s,count:r}))}async getGpcStats(e){let t=this.db.consents.filter(r=>r.tenantId===e),n=t.filter(r=>r.gpcDetected).length,s=t.length;return{detected:n,total:s,rate:s>0?Math.round(n/s*100):0}}async getTenants(){return[...this.db.tenants].sort((e,t)=>e.name.localeCompare(t.name))}async createTenant(e){let t=new Date().toISOString(),n={id:pe(),name:e.name,slug:e.slug,createdAt:t,updatedAt:t};return this.db.tenants.push(n),this.scheduleWrite(),n}async updateTenant(e,t){let n=this.db.tenants.findIndex(o=>o.id===e);if(n===-1)throw new Error(`Tenant ${e} not found`);let s=new Date().toISOString(),r={...this.db.tenants[n],...t.name!=null&&{name:t.name},...t.slug!=null&&{slug:t.slug},updatedAt:s};return this.db.tenants[n]=r,this.scheduleWrite(),r}async deleteTenant(e){this.db.tenants=this.db.tenants.filter(t=>t.id!==e),this.scheduleWrite()}async getSettings(e){return this.db.settings[e]??{}}async updateSettings(e,t){let n={...this.db.settings[e]??{},...t};return this.db.settings[e]=n,this.scheduleWrite(),n}async purgeExpiredConsents(e){let t=new Date(Date.now()-e*864e5).toISOString(),n=new Set(this.db.consents.filter(s=>s.createdAt<t).map(s=>s.visitorId));return n.size===0?0:(this.db.consent_history=this.db.consent_history.filter(s=>!n.has(s.visitorId)),this.db.consents=this.db.consents.filter(s=>!n.has(s.visitorId)),this.scheduleWrite(),n.size)}async createApiKey(e){let t=new Date().toISOString(),n={id:pe(),tenantId:e.tenantId,keyHash:e.keyHash,name:e.name,isActive:!0,...e.createdBy!==void 0?{createdBy:e.createdBy}:{},...e.expireBy!==void 0?{expireBy:e.expireBy}:{},createdAt:t,updatedAt:t};return this.db.api_keys.push(n),this.scheduleWrite(),n}async getApiKeyByHash(e){return this.db.api_keys.find(t=>t.keyHash===e&&t.isActive)??null}async revokeApiKey(e){let t=this.db.api_keys.find(n=>n.id===e);t&&(t.isActive=!1,t.updatedAt=new Date().toISOString(),this.scheduleWrite())}async reactivateApiKey(e){let t=this.db.api_keys.find(n=>n.id===e);t&&(t.isActive=!0,t.expireBy&&new Date(t.expireBy)<=new Date&&delete t.expireBy,t.updatedAt=new Date().toISOString(),this.scheduleWrite())}async deleteApiKey(e){let t=this.db.api_keys.findIndex(n=>n.id===e);t!==-1&&(this.db.api_keys.splice(t,1),this.scheduleWrite())}async getApiKeys(e){return this.db.api_keys.filter(t=>t.tenantId===e).sort((t,n)=>n.createdAt.localeCompare(t.createdAt))}async createConsentTemplate(e){let t=new Date().toISOString(),n={id:G(),tenantId:e.tenantId,name:e.name,cookies:e.cookies,categories:e.categories,createdAt:t,updatedAt:t};return this.db.consent_templates.push(n),this.scheduleWrite(),n}async updateConsentTemplate(e,t){let n=this.db.consent_templates.findIndex(o=>o.id===e);if(n===-1)throw new Error("Consent template not found");let s=new Date().toISOString(),r={...this.db.consent_templates[n],...t.name!==void 0&&{name:t.name},...t.cookies!==void 0&&{cookies:t.cookies},...t.categories!==void 0&&{categories:t.categories},updatedAt:s};return this.db.consent_templates[n]=r,this.scheduleWrite(),r}async deleteConsentTemplate(e){this.db.consent_templates=this.db.consent_templates.filter(t=>t.id!==e),this.scheduleWrite()}async getConsentTemplate(e){return this.db.consent_templates.find(t=>t.id===e)??null}async getConsentTemplates(e){return this.db.consent_templates.filter(t=>t.tenantId===e).sort((t,n)=>t.name.localeCompare(n.name))}async copyConsentTemplate(e,t){let n=await this.getConsentTemplate(e);if(!n)throw new Error("Consent template not found");return this.createConsentTemplate({tenantId:n.tenantId,name:t,cookies:n.cookies,categories:n.categories})}async createUITemplate(e){let t=new Date().toISOString(),{tenantId:n,name:s,...r}=e,o={id:q(),tenantId:n,name:s,...r,createdAt:t,updatedAt:t};return this.db.ui_templates.push(o),this.scheduleWrite(),o}async updateUITemplate(e,t){let n=this.db.ui_templates.findIndex(c=>c.id===e);if(n===-1)throw new Error("UI template not found");let{name:s,...r}=t,o=new Date().toISOString(),a={...this.db.ui_templates[n],...t.name!==void 0&&{name:t.name},...r,updatedAt:o};return this.db.ui_templates[n]=a,this.scheduleWrite(),a}async deleteUITemplate(e){this.db.ui_templates=this.db.ui_templates.filter(t=>t.id!==e),this.scheduleWrite()}async getUITemplate(e){return this.db.ui_templates.find(t=>t.id===e)??null}async getUITemplates(e){return this.db.ui_templates.filter(t=>t.tenantId===e).sort((t,n)=>t.name.localeCompare(n.name))}async copyUITemplate(e,t){let n=await this.getUITemplate(e);if(!n)throw new Error("UI template not found");let{tenantId:s,mainBanner:r,gpcBanner:o,preferenceModal:a}=n;return this.createUITemplate({tenantId:s,name:t,...r!==void 0&&{mainBanner:r},...o!==void 0&&{gpcBanner:o},...a!==void 0&&{preferenceModal:a}})}profileToSummary(e){let t=e.profileJson,n=t.consentTemplateId?this.db.consent_templates.find(r=>r.id===t.consentTemplateId):null,s=t.uiTemplateId?this.db.ui_templates.find(r=>r.id===t.uiTemplateId):null;return{id:e.id,name:e.name,defaultLocale:e.defaultLocale,complianceGroup:t.complianceGroup??null,customComplianceGroup:t.customComplianceGroup??null,isActive:t.isActive??!1,consentTemplateName:n?.name??null,uiTemplateName:s?.name??null,createdAt:e.createdAt,updatedAt:e.updatedAt}}async listProfilesSummary(e){return this.db.profiles.filter(t=>t.tenantId===e).sort((t,n)=>t.createdAt.localeCompare(n.createdAt)).map(t=>this.profileToSummary(t))}async findProfilesUsingConsentTemplate(e){return this.db.profiles.filter(t=>t.profileJson.consentTemplateId===e).sort((t,n)=>t.name.localeCompare(n.name)).map(t=>this.profileToSummary(t))}async findProfilesUsingUITemplate(e){return this.db.profiles.filter(t=>t.profileJson.uiTemplateId===e).sort((t,n)=>t.name.localeCompare(n.name)).map(t=>this.profileToSummary(t))}async getOptInStats(e,t){let n=this.db.consents.filter(u=>u.tenantId===e);t.profileId&&(n=n.filter(u=>u.profileId===t.profileId)),t.from&&(n=n.filter(u=>u.createdAt>=t.from)),t.to&&(n=n.filter(u=>u.createdAt<=t.to)),t.locale&&(n=n.filter(u=>u.locale===t.locale));let s=0,r=0,o=0,a=0,c={},l=new Map;for(let u of n){let p=Object.values(u.consentJson),g=p.length>0?p.filter(R=>R==="granted").length/p.length:0,E=p.length>0?p.filter(R=>R==="denied").length/p.length:0;s++;let f=u.locale;c[f]||(c[f]={total:0,granted:0,denied:0,managed:0}),c[f].total++;let y=u.createdAt.slice(0,10),w=l.get(y)??{total:0,granted:0,denied:0,managed:0};w.total++,g>=.9?(r++,c[f].granted++,w.granted++):E>=.9?(o++,c[f].denied++,w.denied++):(a++,c[f].managed++,w.managed++),l.set(y,w)}let d=Array.from(l.entries()).sort(([u],[p])=>u.localeCompare(p)).map(([u,p])=>({date:u,...p}));return{total:s,granted:r,denied:o,managed:a,grantedPct:s>0?Math.round(r/s*1e3)/10:0,deniedPct:s>0?Math.round(o/s*1e3)/10:0,managedPct:s>0?Math.round(a/s*1e3)/10:0,byLocale:c,byDate:d}}};var St=class{constructor(e){this.adapter=e}adapter;create(e){return this.adapter.createProfile(e)}update(e,t){return this.adapter.updateProfile(e,t)}delete(e){return this.adapter.deleteProfile(e)}get(e){return this.adapter.getProfile(e)}list(e){return this.adapter.getProfiles(e)}findActiveByComplianceGroup(e,t){return this.adapter.findActiveProfileByComplianceGroup(e,t)}};var Ct=class{constructor(e){this.adapter=e}adapter;create(e){return this.adapter.createConsent(e)}update(e,t){return this.adapter.updateConsent(e,t)}delete(e){return this.adapter.deleteConsent(e)}get(e){return this.adapter.getConsent(e)}list(e){return this.adapter.getConsents(e)}};var It=class{constructor(e){this.adapter=e}adapter;create(e){return this.adapter.createVisitor(e)}update(e,t){return this.adapter.updateVisitor(e,t)}delete(e){return this.adapter.deleteVisitor(e)}get(e){return this.adapter.getVisitor(e)}};var Tt=class{constructor(e){this.adapter=e}adapter;create(e){return this.adapter.createNoticeShown(e)}getForVisitor(e){return this.adapter.getNoticeShownForVisitor(e)}};var At=class{constructor(e){this.adapter=e}adapter;log(e){return this.adapter.createLog(e)}list(e){return this.adapter.getLogs(e)}get(e){return this.adapter.getAuditLogById(e)}};var vt=class{constructor(e,t=500,n=6e4){this.ttlMs=e;this.maxEntries=t;this.sweepTimer=setInterval(()=>this.sweep(),n),this.sweepTimer.unref?.()}ttlMs;maxEntries;store=new Map;sweepTimer=null;get(e){let t=this.store.get(e);if(t){if(Date.now()>t.expiresAt){this.store.delete(e);return}return t.value}}set(e,t){if(this.store.size>=this.maxEntries&&!this.store.has(e)){let n=this.store.keys().next().value;n!==void 0&&this.store.delete(n)}this.store.set(e,{value:t,expiresAt:Date.now()+this.ttlMs})}sweep(){let e=Date.now();for(let[t,n]of this.store)e>n.expiresAt&&this.store.delete(t)}dispose(){this.sweepTimer&&clearInterval(this.sweepTimer),this.sweepTimer=null}};var Oa=3e4,Pt=class{constructor(e){this.storage=e}storage;cache=new vt(Oa);async getOverview(e){return this.cached(`overview:${e}`,()=>this.storage.getOverviewStats(e))}async getCategories(e){return this.cached(`categories:${e}`,()=>this.storage.getCategoryStats(e))}async getTimeline(e,t){return this.cached(`timeline:${e}:${t}`,()=>this.storage.getTimeline(e,t))}async getCountries(e){return this.cached(`countries:${e}`,()=>this.storage.getCountries(e))}async getGpc(e){return this.cached(`gpc:${e}`,()=>this.storage.getGpcStats(e))}async getTenants(){return this.storage.getTenants()}dispose(){this.cache.dispose()}async cached(e,t){let n=this.cache.get(e);if(n!==void 0)return n;let s=await t();return this.cache.set(e,s),s}};var Ot=class{constructor(e){this.adapter=e}adapter;getById(e){return this.adapter.getUserById(e)}getByEmail(e){return this.adapter.getUserByEmail(e)}list(e){return this.adapter.getUsers(e)}create(e){return this.adapter.createUser(e)}update(e,t){return this.adapter.updateUser(e,t)}delete(e){return this.adapter.deleteUser(e)}getRoles(e,t){return this.adapter.getUserRoles(e,t)}assignRole(e,t,n){return this.adapter.assignRole(e,t,n)}revokeRole(e,t,n){return this.adapter.revokeRole(e,t,n)}};import{readdirSync as Nt,readFileSync as Ci,existsSync as kt,statSync as Ii,lstatSync as nc}from"fs";import{join as z}from"path";import{Worker as Ya}from"worker_threads";import{fileURLToPath as za}from"url";import{join as Qa,dirname as Xa}from"path";import{existsSync as Za}from"fs";import{writeFileSync as xa,renameSync as fi,mkdirSync as _i,copyFileSync as ja,rmSync as hi,existsSync as Ge,readFileSync as $a,lstatSync as yi,symlinkSync as Fa,unlinkSync as On}from"fs";import{join as ae,resolve as Ha}from"path";import{randomUUID as Ba}from"crypto";import{createHmac as oi,createHash as ba}from"crypto";function vn(i){return ba("sha256").update(i).digest("hex")}function bt(i,e){return oi("sha256",i).update(e).digest()}function Da(i){let e=i.toISOString().replace(/[-:]/g,"").slice(0,15)+"Z";return{amzDate:e,dateStamp:e.slice(0,8)}}function La(i,e,t,n){let s=bt(`AWS4${i}`,e),r=bt(s,t),o=bt(r,n);return bt(o,"aws4_request")}function ai(i){let{method:e,key:t,body:n,contentType:s,config:r}=i,o=new Date,{amzDate:a,dateStamp:c}=Da(o),l=`${r.bucketName}.s3.${r.region}.amazonaws.com`,d=vn(n?Buffer.isBuffer(n)?n:Buffer.from(n,"utf8"):""),u={host:l,"x-amz-date":a,"x-amz-content-sha256":d};r.sessionToken&&(u["x-amz-security-token"]=r.sessionToken),s&&e==="PUT"&&(u["content-type"]=s);let p=Object.keys(u).sort(),g=p.map(v=>`${v}:${u[v]}
137
+ `).join(""),E=p.join(";"),f=[e,`/${encodeURIComponent(t).replace(/%2F/g,"/")}`,"",g,E,d].join(`
138
+ `),y=`${c}/${r.region}/s3/aws4_request`,w=["AWS4-HMAC-SHA256",a,y,vn(f)].join(`
139
+ `),R=La(r.secretAccessKey,c,r.region,"s3"),I=oi("sha256",R).update(w).digest("hex"),T=[`AWS4-HMAC-SHA256 Credential=${r.accessKeyId}/${y}`,`SignedHeaders=${E}`,`Signature=${I}`].join(", ");return{...u,authorization:T}}function ci(i,e){let t=encodeURIComponent(i).replace(/%2F/g,"/");return`https://${e.bucketName}.s3.${e.region}.amazonaws.com/${t}`}async function Dt(i,e,t,n="application/json"){let s=ai({method:"PUT",key:i,body:e,contentType:n,config:t}),r=typeof e=="string"?e:e.toString("utf8"),o=await fetch(ci(i,t),{method:"PUT",headers:{...s,"content-length":String(Buffer.byteLength(r,"utf8"))},body:r});if(!o.ok){let a=await o.text().catch(()=>"");throw new Error(`S3 PUT ${i} failed: ${o.status} ${a}`)}}async function li(i,e){let t=ai({method:"DELETE",key:i,config:e}),n=await fetch(ci(i,e),{method:"DELETE",headers:t});if(!n.ok&&n.status!==404){let s=await n.text().catch(()=>"");throw new Error(`S3 DELETE ${i} failed: ${n.status} ${s}`)}}var Ua=/^[a-z0-9][a-z0-9_-]{0,63}$/,Na=/^[A-Za-z]{2,3}(?:-[A-Za-z0-9]{2,8}){0,3}$/,pi=/^(?:con|prn|aux|nul|com[1-9]|lpt[1-9])$/i,ka="prof_";function Pn(i){return typeof i=="string"&&Ua.test(i)&&!pi.test(i)&&!i.startsWith(ka)}function di(i){return typeof i=="string"&&Na.test(i)&&!pi.test(i)}var Ma='must be 1-64 lowercase letters, digits, "-" or "_", start with a letter or digit, and not be a reserved name or start with "prof_"',ui='must be a language tag such as "en" or "pt-BR"';function Lt(i){let{defaultLocale:e,complianceGroup:t,customComplianceGroup:n,locales:s,localeContentKeys:r}=i;if(e!==void 0&&!di(e))return`defaultLocale ${ui}`;for(let[l,d]of[["complianceGroup",t],["customComplianceGroup",n]])if(d!=null&&d!==""&&!Pn(d))return`${l} ${Ma}`;let o=[...Array.isArray(s)?s:[],...r??[]];if(s!==void 0&&!Array.isArray(s))return"locales must be an array";let a=o.find(l=>!di(l));if(a!==void 0)return`locale ${JSON.stringify(a)} ${ui}`;let c=new Set;for(let l of Array.isArray(s)?s:[]){let d=l.toLowerCase();if(c.has(d))return`locale "${l}" is listed twice (locale codes are compared case-insensitively)`;c.add(d)}return null}function Dn(i,e){return ae(i,"profiles",e)}function bn(i,e,t,n){return ae(Dn(i,e),t,String(n))}function wi(i,e,t){return ae(Dn(i,e),t)}function Ut(i,e,t){return`profiles/${i}/${e}/${t}.json`}function Va(i){let e=Lt({defaultLocale:i.profile.defaultLocale,locales:i.profile.locales,localeContentKeys:Object.keys(i.localeContent)});if(e)throw new Error(`Refusing to write profile files: ${e}`)}function Ei(i){if(!Pn(i))throw new Error(`Refusing to touch the live profile directory for compliance group ${JSON.stringify(i)}`)}function mi(i,e){let t=i+".tmp";xa(t,e,"utf8"),JSON.parse(e),fi(t,i)}var Wa=["gpcMode","complianceGroup","darkMode","allowReceipt","enhanceAccessibility","showFooterMetadata","allowedOrigins","complianceConfig","dpdpa","ageGate","hidePoweredBy"];function Ja(i){let e={};for(let t of Wa){let n=i[t];n!==void 0&&(e[t]=n)}return e}async function gi(i){let{storagePath:e,tenantId:t,profileId:n,version:s,profile:r,localeContent:o,previousVersion:a,s3Config:c}=i;Va(i);let l=bn(e,t,n,s);_i(l,{recursive:!0});let{locales:d,defaultLocale:u,cookies:p,expiryDays:g}=r;for(let E of d){let f,y=o[E];if(y){let R={id:r.id,version:s,defaultLocale:u,currentLocale:E,locales:d,cookies:p,...g!==void 0?{expiryDays:g}:{},...Ja(r),mainBanner:y.mainBanner,...y.gpcBanner?{gpcBanner:y.gpcBanner}:{},preferenceModal:y.preferenceModal,...y.ageGateModal?{ageGateModal:y.ageGateModal}:{}};f=JSON.stringify(R,null,2)}else if(a!==void 0){let R=ae(bn(e,t,n,a),`${E}.json`);if(!Ge(R))continue;f=$a(R,"utf8")}else continue;let w=ae(l,`${E}.json`);mi(w,f),E===u&&mi(ae(l,"default.json"),f),c?.enabled&&(await Dt(Ut(t,`${n}/${s}`,E),f,c),E===u&&await Dt(Ut(t,`${n}/${s}`,"default"),f,c))}if(!Ge(ae(l,"default.json"))){let E=d.includes(u)?u:d[0]??"";if(E){let f=ae(l,`${E}.json`);Ge(f)&&ja(f,ae(l,"default.json"))}}}function Ga(i){Ge(i)&&(yi(i).isSymbolicLink()||hi(i,{recursive:!0,force:!0}))}async function qa(i){let{storagePath:e,tenantId:t,profileId:n,version:s,complianceGroup:r,s3Config:o}=i;Ei(r);let a=Ha(bn(e,t,n,s)),c=wi(e,t,r);_i(Dn(e,t),{recursive:!0}),Ga(c);let l=`${c}.new-${Ba().slice(0,8)}`;Fa(a,l,"junction");try{if(process.platform==="win32")try{On(c)}catch(d){if(d.code!=="ENOENT")throw d}fi(l,c)}catch(d){try{On(l)}catch{}throw d}if(o?.enabled){let d=JSON.stringify({profileId:n,version:s});await Dt(Ut(t,r,"pointer"),d,o)}}async function Ka(i){let{storagePath:e,tenantId:t,complianceGroup:n,s3Config:s}=i;Ei(n);let r=wi(e,t,n);Ge(r)&&(yi(r).isSymbolicLink()?On(r):hi(r,{recursive:!0,force:!0})),s?.enabled&&await li(Ut(t,n,"pointer"),s)}async function Ri(i){i.action==="write"?await gi(i):i.action==="activate"?(await gi(i),await qa(i)):i.action==="deactivate"&&await Ka(i)}var ec=Xa(za(import.meta.url)),Si=Qa(ec,"profile-json.worker.js"),tc=!Za(Si);async function Ln(i){if(tc){await Ri(i);return}return new Promise((e,t)=>{let n=new Ya(Si);n.postMessage(i),n.once("message",s=>{n.terminate(),s.ok?e():t(new Error(`[profile-worker] ${s.action} failed: ${s.error}`))}),n.once("error",s=>{n.terminate(),t(s)})})}function Un(i,e){if(!e)return i;let t={...i};for(let[n,s]of Object.entries(e))t[n]&&(t[n]={...t[n],...s});return t}function Ti(i){return{...i.gpcMode!==void 0?{gpcMode:i.gpcMode}:{},...i.complianceGroup!==void 0?{complianceGroup:i.complianceGroup}:{},...i.darkMode!==void 0?{darkMode:i.darkMode}:{},...i.allowReceipt!==void 0?{allowReceipt:i.allowReceipt}:{},...i.enhanceAccessibility!==void 0?{enhanceAccessibility:i.enhanceAccessibility}:{},...i.showFooterMetadata!==void 0?{showFooterMetadata:i.showFooterMetadata}:{},...i.allowedOrigins!==void 0?{allowedOrigins:i.allowedOrigins}:{},...i.complianceConfig!==void 0?{complianceConfig:i.complianceConfig}:{},...i.dpdpa!==void 0?{dpdpa:i.dpdpa}:{},...i.ageGate!==void 0?{ageGate:i.ageGate}:{},...i.hidePoweredBy!==void 0?{hidePoweredBy:i.hidePoweredBy}:{}}}var Mt=class{constructor(e,t,n="default",s,r,o,a,c,l){this.profiles=e;this.audit=t;this.tenantId=n;this.storage=s;this.eventBus=r;this.localeCache=o;this.profilesDir=a;this.handleCache=c;this.s3Config=l}profiles;audit;tenantId;storage;eventBus;localeCache;profilesDir;handleCache;s3Config;versionsRootDir(e){return z(this.profilesDir??"",this.tenantId,e)}complianceGroupDir(e){return z(this.profilesDir??"",this.tenantId,e)}groupKey(e){return e.complianceGroup||e.customComplianceGroup||""}profileFilePaths(e,t){let n=this.complianceGroupDir(e);return t.map(s=>z(n,`${s}.json`)).concat(z(n,"default.json"))}async resolveCookies(e){let t=e.cookies??{};if(e.consentTemplateId&&this.storage){let n=await this.storage.getConsentTemplate(e.consentTemplateId);n&&(t=n.cookies)}return Un(t,e.cookiesOverride)}async writeLocaleFiles(e,t,n,s){if(!this.profilesDir)return;let r=e.profileJson,o=await this.resolveCookies(r),a=r.locales?.length?r.locales:[e.defaultLocale],c={[e.defaultLocale]:{mainBanner:r.mainBanner,...r.gpcBanner?{gpcBanner:r.gpcBanner}:{},preferenceModal:r.preferenceModal,...r.ageGateModal?{ageGateModal:r.ageGateModal}:{}},...t??{}},l=(r.isActive??!1)&&n!=="",u={action:l?"activate":"write",storagePath:z(this.profilesDir,".."),tenantId:this.tenantId,profileId:e.id,version:e.version,complianceGroup:n,profile:{id:e.id,defaultLocale:e.defaultLocale,locales:a,cookies:o,...r.expiryDays!==void 0?{expiryDays:r.expiryDays}:{},...Ti(r)},localeContent:c,...s!==void 0?{previousVersion:s}:{},...this.s3Config?.enabled?{s3Config:this.s3Config}:{}};if(await Ln(u),l){let p=this.profileFilePaths(n,a);this.handleCache?.(p,e.id,!0),this.eventBus?.emit("cache:purge",{paths:p,profileId:e.id})}}async create(e,t){let{localeContent:n,...s}=e,r=await this.profiles.create({...s,tenantId:this.tenantId});if(await this.audit.log({tenantId:this.tenantId,...t!=null?{userId:t}:{},action:"profile:created",resourceType:"profile",resourceId:r.id,newData:{profileId:r.id,version:r.version,complianceGroup:this.groupKey(r.profileJson)}}),this.eventBus?.emit("profile:created",r),this.profilesDir){let o=this.groupKey(r.profileJson);await this.writeLocaleFiles(r,n,o,void 0);let a=r.profileJson.locales?.length?r.profileJson.locales:[r.defaultLocale],c=this.profileFilePaths(o,a);this.handleCache?.(c,r.id,!1),this.eventBus?.emit("cache:warm",{paths:c,profileId:r.id})}return r}async copy(e,t,n){let s=await this.profiles.get(e);if(!s)throw new Error(`Profile ${e} not found`);let r=s.profileJson.locales?.length?s.profileJson.locales:[s.defaultLocale],o={};for(let a of r){if(a===s.defaultLocale)continue;let c=await this.getVersionFile(e,String(s.version),a);if(c)try{let l=JSON.parse(c);o[a]={mainBanner:l.mainBanner,...l.gpcBanner?{gpcBanner:l.gpcBanner}:{},preferenceModal:l.preferenceModal}}catch{}}return this.create({name:t??`Copy of ${s.name}`,defaultLocale:s.defaultLocale,profileJson:{...s.profileJson,isActive:!1},...Object.keys(o).length>0?{localeContent:o}:{}},n)}async update(e,t,n){let s=await this.profiles.get(e);if(!s)throw new Error(`Profile ${e} not found`);let{localeContent:r,...o}=t,a=o.profileJson?.isActive??s.profileJson.isActive,c=o.profileJson?{...o.profileJson,...a!==void 0?{isActive:a}:{}}:s.profileJson,l=await this.profiles.update(e,{name:o.name??s.name,defaultLocale:o.defaultLocale??s.defaultLocale,profileJson:c,version:s.version+1});if(this.localeCache?.invalidate(e),await this.audit.log({tenantId:this.tenantId,...n!=null?{userId:n}:{},action:"profile:updated",resourceType:"profile",resourceId:l.id,oldData:{profileId:s.id,version:s.version},newData:{profileId:l.id,version:l.version,complianceGroup:this.groupKey(l.profileJson)}}),this.eventBus?.emit("profile:updated",{previous:s,current:l}),this.profilesDir){let d=this.groupKey(l.profileJson);await this.writeLocaleFiles(l,r,d,s.version)}return l}async delete(e,t){let n=await this.profiles.get(e);if(await this.profiles.delete(e),this.localeCache?.invalidate(e),await this.audit.log({tenantId:this.tenantId,...t!=null?{userId:t}:{},action:"profile:deleted",resourceType:"profile",resourceId:e,...n!=null?{oldData:{profileId:n.id,version:n.version,name:n.name,complianceGroup:this.groupKey(n.profileJson),wasActive:n.profileJson.isActive??!1}}:{}}),this.eventBus?.emit("profile:deleted",{id:e,previous:n}),this.profilesDir&&n){let s=this.groupKey(n.profileJson);if((n.profileJson.isActive??!1)&&s){let o=n.profileJson.locales?.length?n.profileJson.locales:[n.defaultLocale];await this.runDeactivate(s);let a=this.profileFilePaths(s,o);this.handleCache?.(a,e,!0),this.eventBus?.emit("cache:purge",{paths:a,profileId:e})}}}async activate(e,t){let n=await this.profiles.get(e);if(!n)throw new Error(`Profile ${e} not found`);let s=this.groupKey(n.profileJson),r=await this.profiles.update(e,{profileJson:{...n.profileJson,isActive:!0}});return this.localeCache?.invalidate(e),this.profilesDir&&s&&await this.writeLocaleFiles(r,void 0,s,n.version),await this.audit.log({tenantId:this.tenantId,...t!=null?{userId:t}:{},action:"profile.activated",resourceType:"profile",resourceId:e}),this.eventBus?.emit("profile.activated",r),r}async deactivate(e,t){let n=await this.profiles.get(e);if(!n)throw new Error(`Profile ${e} not found`);let s=this.groupKey(n.profileJson),r=await this.profiles.update(e,{profileJson:{...n.profileJson,isActive:!1}});if(this.localeCache?.invalidate(e),this.profilesDir&&s){let o=n.profileJson.locales?.length?n.profileJson.locales:[n.defaultLocale];await this.runDeactivate(s);let a=this.profileFilePaths(s,o);this.handleCache?.(a,e,!0),this.eventBus?.emit("cache:purge",{paths:a,profileId:e})}return await this.audit.log({tenantId:this.tenantId,...t!=null?{userId:t}:{},action:"profile.deactivated",resourceType:"profile",resourceId:e}),this.eventBus?.emit("profile.deactivated",r),r}async runDeactivate(e){if(!this.profilesDir)return;let t={action:"deactivate",storagePath:z(this.profilesDir,".."),tenantId:this.tenantId,profileId:"",version:0,complianceGroup:e,profile:{id:"",defaultLocale:"",locales:[],cookies:{}},localeContent:{},...this.s3Config?.enabled?{s3Config:this.s3Config}:{}};await Ln(t)}async get(e){return this.profiles.get(e)}async getResolved(e,t){let n=await this.profiles.get(e);if(!n)return null;let s=t??n.defaultLocale;if(this.localeCache){let u=this.localeCache.read(e,s);if(u)return u}let r=n.profileJson,o=await this.resolveCookies(r),a=r.locales?.length?r.locales:[n.defaultLocale],l={mainBanner:r.mainBanner,...r.gpcBanner?{gpcBanner:r.gpcBanner}:{},preferenceModal:r.preferenceModal,...r.ageGateModal?{ageGateModal:r.ageGateModal}:{}};if(s!==n.defaultLocale){let u=await this.getVersionFile(e,String(n.version),s);if(u)try{let p=JSON.parse(u);l={mainBanner:p.mainBanner,...p.gpcBanner?{gpcBanner:p.gpcBanner}:{},preferenceModal:p.preferenceModal,...p.ageGateModal?{ageGateModal:p.ageGateModal}:{}}}catch{}}let d={id:n.id,version:n.version,defaultLocale:n.defaultLocale,currentLocale:s,locales:a,cookies:o,...r.expiryDays!==void 0?{expiryDays:r.expiryDays}:{},...Ti(r),...l};if(this.localeCache)try{this.localeCache.write(e,s,d)}catch(u){console.warn(`[Consenti] locale cache write failed for ${e}/${s}:`,u)}return d}findActiveByComplianceGroup(e){return this.profiles.findActiveByComplianceGroup(this.tenantId,e)}list(){return this.profiles.list(this.tenantId)}listSummary(){return this.storage?this.storage.listProfilesSummary(this.tenantId):Promise.resolve([])}async listVersions(e){if(!this.profilesDir)return[];let t=this.versionsRootDir(e);if(!kt(t))return[];let n=[];try{let s=Nt(t,{withFileTypes:!0});for(let r of s){if(!r.isDirectory())continue;let o=Number(r.name);if(!Number.isInteger(o))continue;let a=z(t,r.name),c=Nt(a).filter(d=>d.endsWith(".json")&&d!=="default.json").map(d=>d.replace(".json","")),l;try{l=Ii(a).mtime.toISOString()}catch{l=new Date().toISOString()}n.push({version:o,createdAt:l,locales:c})}}catch{return[]}return n.sort((s,r)=>r.version-s.version)}async getArchivedSnapshot(e,t){let n=await this.listVersions(e),s=n.find(o=>o.version===t)??n[0];if(!s)return null;let r=await this.getVersionFile(e,String(s.version),"default");if(!r)return null;try{return JSON.parse(r)}catch{return null}}async getVersionFile(e,t,n){if(!this.profilesDir)return null;let s=z(this.versionsRootDir(e),t),r=z(s,`${n}.json`);if(!kt(r)){let o=z(s,"default.json");return kt(o)?Ci(o,"utf8"):null}return Ci(r,"utf8")}async listArchivedProfiles(){if(!this.profilesDir)return[];let e=z(this.profilesDir,this.tenantId);if(!kt(e))return[];let t=new Set((await this.profiles.list(this.tenantId)).map(r=>r.id)),n=[],s;try{s=Nt(e,{withFileTypes:!0})}catch{return[]}for(let r of s){if(!r.isDirectory()||t.has(r.name))continue;let o=z(e,r.name);if(nc(o).isSymbolicLink())continue;let a;try{a=Nt(o,{withFileTypes:!0})}catch{continue}let c=a.filter(d=>d.isDirectory()&&Number.isInteger(Number(d.name)));if(c.length===0)continue;let l=new Date(0).toISOString();for(let d of c)try{let u=Ii(z(o,d.name)).mtime.toISOString();u>l&&(l=u)}catch{}n.push({id:r.name,versionCount:c.length,lastModified:l})}return n.sort((r,o)=>o.lastModified.localeCompare(r.lastModified))}toTemplateButtonMap(e){let t={};for(let[n,s]of Object.entries(e))t[n]={type:s.style,action:s.action,...s.cookies!==void 0?{cookies:s.cookies}:{}};return t}buildSeedUiTemplatePayload(e){let t={position:e.mainBanner.position,buttons:this.toTemplateButtonMap(e.mainBanner.buttons),...e.mainBanner.showClose!==void 0?{showClose:e.mainBanner.showClose}:{},...e.mainBanner.showLocaleSwitcher!==void 0?{showLocaleSwitcher:e.mainBanner.showLocaleSwitcher}:{}},n=e.gpcBanner?{position:e.gpcBanner.position,buttons:this.toTemplateButtonMap(e.gpcBanner.buttons),...e.gpcBanner.showClose!==void 0?{showClose:e.gpcBanner.showClose}:{},...e.gpcBanner.showLocaleSwitcher!==void 0?{showLocaleSwitcher:e.gpcBanner.showLocaleSwitcher}:{}}:t,s={buttons:this.toTemplateButtonMap(e.preferenceModal.buttons),...e.preferenceModal.position!==void 0?{position:e.preferenceModal.position}:{},...e.preferenceModal.persistent!==void 0?{persistent:e.preferenceModal.persistent}:{}};return{mainBanner:t,gpcBanner:n,preferenceModal:s}}async getOrCreateSeedTemplates(e,t,n){if(!this.storage)throw new Error("Storage adapter required to seed templates");let s=`Default \u2014 ${e}`,o=(await this.storage.getConsentTemplates(this.tenantId)).find(l=>l.name===s)??await this.storage.createConsentTemplate({tenantId:this.tenantId,name:s,cookies:t.cookies,categories:n.preferenceModal.categories}),c=(await this.storage.getUITemplates(this.tenantId)).find(l=>l.name===s)??await this.storage.createUITemplate({tenantId:this.tenantId,name:s,...this.buildSeedUiTemplatePayload(n)});return{consentTemplateId:o.id,uiTemplateId:c.id}}async seedDefaultProfile(e){if(await this.profiles.findActiveByComplianceGroup(this.tenantId,e))return;let{DEFAULT_PROFILES:n,resolveLocaleTranslation:s}=await import("./profiles-FCX454HS.js"),r=n[e];if(!r)return;let o=r.translations.en;if(!o)return;let a=["de","es","fr","ja"],c=["en",...a.filter(p=>s(e,p)!==void 0)],l={};for(let p of a){let g=s(e,p);g&&(l[p]={mainBanner:g.mainBanner,...g.gpcBanner?{gpcBanner:g.gpcBanner}:{},preferenceModal:g.preferenceModal})}let{consentTemplateId:d,uiTemplateId:u}=await this.getOrCreateSeedTemplates(e,{cookies:r.cookies},o);await this.create({name:`Default \u2014 ${e}`,defaultLocale:r.defaultLocale,profileJson:{id:A(),defaultLocale:r.defaultLocale,complianceGroup:r.complianceGroup,gpcMode:r.gpcMode,cookies:r.cookies,consentTemplateId:d,uiTemplateId:u,...r.expiryDays!==void 0?{expiryDays:r.expiryDays}:{},locales:c,mainBanner:o.mainBanner,...o.gpcBanner?{gpcBanner:o.gpcBanner}:{},preferenceModal:o.preferenceModal,isActive:!0},...Object.keys(l).length>0?{localeContent:l}:{}})}async seedAllDefaults(){let{COMPLIANCE_GROUP_IDS:e}=await import("./dist-MNR4WETI.js");await Promise.all(e.map(t=>this.seedDefaultProfile(t)))}};var Ai={"Europe/Amsterdam":"NL","Europe/Andorra":"AD","Europe/Athens":"GR","Europe/Belgrade":"RS","Europe/Berlin":"DE","Europe/Bratislava":"SK","Europe/Brussels":"BE","Europe/Bucharest":"RO","Europe/Budapest":"HU","Europe/Busingen":"DE","Europe/Chisinau":"MD","Europe/Copenhagen":"DK","Europe/Dublin":"IE","Europe/Gibraltar":"GI","Europe/Guernsey":"GG","Europe/Helsinki":"FI","Europe/Isle_of_Man":"IM","Europe/Istanbul":"TR","Europe/Jersey":"JE","Europe/Kaliningrad":"RU","Europe/Kiev":"UA","Europe/Kyiv":"UA","Europe/Lisbon":"PT","Europe/Ljubljana":"SI","Europe/London":"GB","Europe/Luxembourg":"LU","Europe/Madrid":"ES","Europe/Malta":"MT","Europe/Mariehamn":"FI","Europe/Minsk":"BY","Europe/Monaco":"MC","Europe/Moscow":"RU","Europe/Nicosia":"CY","Europe/Oslo":"NO","Europe/Paris":"FR","Europe/Podgorica":"ME","Europe/Prague":"CZ","Europe/Riga":"LV","Europe/Rome":"IT","Europe/Samara":"RU","Europe/San_Marino":"SM","Europe/Sarajevo":"BA","Europe/Saratov":"RU","Europe/Simferopol":"UA","Europe/Skopje":"MK","Europe/Sofia":"BG","Europe/Stockholm":"SE","Europe/Tallinn":"EE","Europe/Tirane":"AL","Europe/Ulyanovsk":"RU","Europe/Uzhgorod":"UA","Europe/Vaduz":"LI","Europe/Vatican":"VA","Europe/Vienna":"AT","Europe/Vilnius":"LT","Europe/Volgograd":"RU","Europe/Warsaw":"PL","Europe/Zagreb":"HR","Europe/Zaporozhye":"UA","Europe/Zurich":"CH","Atlantic/Reykjavik":"IS","Atlantic/Faroe":"FO","America/New_York":"US","America/Chicago":"US","America/Denver":"US","America/Los_Angeles":"US","America/Anchorage":"US","America/Adak":"US","America/Honolulu":"US","America/Phoenix":"US","America/Detroit":"US","America/Indiana/Indianapolis":"US","America/Indiana/Knox":"US","America/Indiana/Marengo":"US","America/Indiana/Petersburg":"US","America/Indiana/Tell_City":"US","America/Indiana/Vevay":"US","America/Indiana/Vincennes":"US","America/Indiana/Winamac":"US","America/Kentucky/Louisville":"US","America/Kentucky/Monticello":"US","America/North_Dakota/Beulah":"US","America/North_Dakota/Center":"US","America/North_Dakota/New_Salem":"US","America/Boise":"US","America/Menominee":"US","America/Nome":"US","America/Sitka":"US","America/Yakutat":"US","America/Juneau":"US","America/Metlakatla":"US","America/Toronto":"CA","America/Vancouver":"CA","America/Winnipeg":"CA","America/Halifax":"CA","America/St_Johns":"CA","America/Edmonton":"CA","America/Regina":"CA","America/Whitehorse":"CA","America/Yellowknife":"CA","America/Iqaluit":"CA","America/Moncton":"CA","America/Glace_Bay":"CA","America/Goose_Bay":"CA","America/Swift_Current":"CA","America/Dawson":"CA","America/Cambridge_Bay":"CA","America/Dawson_Creek":"CA","America/Fort_Nelson":"CA","America/Creston":"CA","America/Rankin_Inlet":"CA","America/Resolute":"CA","America/Pangnirtung":"CA","America/Nipigon":"CA","America/Mexico_City":"MX","America/Cancun":"MX","America/Monterrey":"MX","America/Merida":"MX","America/Chihuahua":"MX","America/Hermosillo":"MX","America/Mazatlan":"MX","America/Ojinaga":"MX","America/Bahia_Banderas":"MX","America/Matamoros":"MX","America/Tijuana":"MX","America/Sao_Paulo":"BR","America/Manaus":"BR","America/Belem":"BR","America/Fortaleza":"BR","America/Recife":"BR","America/Maceio":"BR","America/Bahia":"BR","America/Porto_Velho":"BR","America/Boa_Vista":"BR","America/Campo_Grande":"BR","America/Cuiaba":"BR","America/Santarem":"BR","America/Rio_Branco":"BR","America/Eirunepe":"BR","America/Noronha":"BR","America/Argentina/Buenos_Aires":"AR","America/Argentina/Cordoba":"AR","America/Argentina/Salta":"AR","America/Argentina/Jujuy":"AR","America/Argentina/Tucuman":"AR","America/Argentina/Catamarca":"AR","America/Argentina/La_Rioja":"AR","America/Argentina/San_Juan":"AR","America/Argentina/Mendoza":"AR","America/Argentina/San_Luis":"AR","America/Argentina/Rio_Gallegos":"AR","America/Argentina/Ushuaia":"AR","America/Bogota":"CO","America/Lima":"PE","America/Santiago":"CL","America/Caracas":"VE","America/La_Paz":"BO","America/Asuncion":"PY","America/Montevideo":"UY","America/Guayaquil":"EC","America/Guyana":"GY","America/Paramaribo":"SR","America/Cayenne":"GF","America/Panama":"PA","America/Costa_Rica":"CR","America/Guatemala":"GT","America/El_Salvador":"SV","America/Tegucigalpa":"HN","America/Managua":"NI","America/Belize":"BZ","America/Havana":"CU","America/Jamaica":"JM","America/Nassau":"BS","America/Port_of_Spain":"TT","America/Santo_Domingo":"DO","America/Port-au-Prince":"HT","America/Barbados":"BB","America/Martinique":"MQ","America/Guadeloupe":"GP","America/Puerto_Rico":"PR","America/St_Kitts":"KN","Asia/Kolkata":"IN","Asia/Calcutta":"IN","Asia/Karachi":"PK","Asia/Dhaka":"BD","Asia/Colombo":"LK","Asia/Kathmandu":"NP","Asia/Kabul":"AF","Asia/Tokyo":"JP","Asia/Seoul":"KR","Asia/Shanghai":"CN","Asia/Hong_Kong":"HK","Asia/Macau":"MO","Asia/Taipei":"TW","Asia/Ulaanbaatar":"MN","Asia/Choibalsan":"MN","Asia/Hovd":"MN","Asia/Singapore":"SG","Asia/Bangkok":"TH","Asia/Jakarta":"ID","Asia/Kuala_Lumpur":"MY","Asia/Manila":"PH","Asia/Ho_Chi_Minh":"VN","Asia/Saigon":"VN","Asia/Phnom_Penh":"KH","Asia/Vientiane":"LA","Asia/Rangoon":"MM","Asia/Yangon":"MM","Asia/Pontianak":"ID","Asia/Makassar":"ID","Asia/Jayapura":"ID","Asia/Kuching":"MY","Asia/Brunei":"BN","Asia/Dili":"TL","Asia/Tashkent":"UZ","Asia/Almaty":"KZ","Asia/Qyzylorda":"KZ","Asia/Aqtau":"KZ","Asia/Aqtobe":"KZ","Asia/Atyrau":"KZ","Asia/Oral":"KZ","Asia/Ashgabat":"TM","Asia/Dushanbe":"TJ","Asia/Bishkek":"KG","Asia/Yekaterinburg":"RU","Asia/Tehran":"IR","Asia/Baghdad":"IQ","Asia/Riyadh":"SA","Asia/Dubai":"AE","Asia/Kuwait":"KW","Asia/Qatar":"QA","Asia/Bahrain":"BH","Asia/Muscat":"OM","Asia/Beirut":"LB","Asia/Jerusalem":"IL","Asia/Tel_Aviv":"IL","Asia/Amman":"JO","Asia/Damascus":"SY","Asia/Nicosia":"CY","Asia/Famagusta":"CY","Asia/Baku":"AZ","Asia/Yerevan":"AM","Asia/Tbilisi":"GE","Asia/Samarkand":"UZ","Africa/Cairo":"EG","Africa/Casablanca":"MA","Africa/Lagos":"NG","Africa/Nairobi":"KE","Africa/Johannesburg":"ZA","Africa/Addis_Ababa":"ET","Africa/Algiers":"DZ","Africa/Tunis":"TN","Africa/Tripoli":"LY","Africa/Khartoum":"SD","Africa/Accra":"GH","Africa/Dar_es_Salaam":"TZ","Africa/Abidjan":"CI","Africa/Bamako":"ML","Africa/Banjul":"GM","Africa/Conakry":"GN","Africa/Dakar":"SN","Africa/Freetown":"SL","Africa/Lome":"TG","Africa/Monrovia":"LR","Africa/Nouakchott":"MR","Africa/Ouagadougou":"BF","Africa/Sao_Tome":"ST","Africa/Bissau":"GW","Africa/Bangui":"CF","Africa/Brazzaville":"CG","Africa/Douala":"CM","Africa/Kinshasa":"CD","Africa/Libreville":"GA","Africa/Luanda":"AO","Africa/Malabo":"GQ","Africa/Ndjamena":"TD","Africa/Niamey":"NE","Africa/Porto-Novo":"BJ","Africa/Blantyre":"MW","Africa/Bujumbura":"BI","Africa/Gaborone":"BW","Africa/Harare":"ZW","Africa/Kampala":"UG","Africa/Kigali":"RW","Africa/Lubumbashi":"CD","Africa/Lusaka":"ZM","Africa/Maputo":"MZ","Africa/Maseru":"LS","Africa/Mbabane":"SZ","Africa/Mogadishu":"SO","Africa/Windhoek":"NA","Africa/Asmara":"ER","Africa/Djibouti":"DJ","Africa/El_Aaiun":"EH","Africa/Juba":"SS","Africa/Ceuta":"ES","Australia/Sydney":"AU","Australia/Melbourne":"AU","Australia/Brisbane":"AU","Australia/Perth":"AU","Australia/Adelaide":"AU","Australia/Darwin":"AU","Australia/Hobart":"AU","Australia/Canberra":"AU","Australia/Broken_Hill":"AU","Australia/Lord_Howe":"AU","Australia/Lindeman":"AU","Australia/Eucla":"AU","Pacific/Auckland":"NZ","Pacific/Chatham":"NZ","Pacific/Fiji":"FJ","Pacific/Port_Moresby":"PG","Pacific/Bougainville":"PG","Pacific/Guam":"GU","Pacific/Saipan":"MP","Pacific/Pago_Pago":"AS","Pacific/Apia":"WS","Pacific/Tongatapu":"TO","Pacific/Fakaofo":"TK","Pacific/Niue":"NU","Pacific/Rarotonga":"CK","Pacific/Tahiti":"PF","Pacific/Marquesas":"PF","Pacific/Gambier":"PF","Pacific/Honolulu":"US","Pacific/Johnston":"US","Pacific/Midway":"UM","Pacific/Wake":"UM","Pacific/Tarawa":"KI","Pacific/Enderbury":"KI","Pacific/Kiritimati":"KI","Pacific/Efate":"VU","Pacific/Guadalcanal":"SB","Pacific/Noumea":"NC","Pacific/Nauru":"NR","Pacific/Palau":"PW","Pacific/Chuuk":"FM","Pacific/Pohnpei":"FM","Pacific/Kosrae":"FM","Pacific/Majuro":"MH","Pacific/Kwajalein":"MH","Pacific/Funafuti":"TV","Pacific/Wallis":"WF","Asia/Novosibirsk":"RU","Asia/Omsk":"RU","Asia/Krasnoyarsk":"RU","Asia/Irkutsk":"RU","Asia/Chita":"RU","Asia/Yakutsk":"RU","Asia/Khandyga":"RU","Asia/Vladivostok":"RU","Asia/Ust-Nera":"RU","Asia/Magadan":"RU","Asia/Sakhalin":"RU","Asia/Srednekolymsk":"RU","Asia/Kamchatka":"RU","Asia/Anadyr":"RU"};var sc={ar:"SA",cs:"CZ",da:"DK",de:"DE",el:"GR",fi:"FI",fr:"FR",he:"IL",hi:"IN",hu:"HU",id:"ID",it:"IT",ja:"JP",ko:"KR",nl:"NL",no:"NO",pl:"PL",pt:"BR",ro:"RO",ru:"RU",sv:"SE",th:"TH",tr:"TR",uk:"UA",vi:"VN",zh:"CN"},xt=class{constructor(e,t){this.map=t;this.provider=e??"default",this.provider==="default"&&console.warn("[Consenti] geoDataProvider is unset (using the timezone/language heuristic) \u2014 this cannot resolve US state-level regions (California, Colorado, etc.), so US visitors always fall back to the country-level default (the strictest group, opt-out-strict) rather than their actual state law. Configure a geoip-capable provider (geoip, hosted-geoip-lite, or maxmind) for accurate US state routing in production.")}map;provider;setMap(e){this.map=e}async resolve(e){let t="",n,s,r=null,o;if(typeof this.provider=="function"){let l=await this.provider(e);t=l.country??"",n=l.region??void 0,r=l.locale,o=l.complianceGroup??void 0}else switch(this.provider){case"default":({country:t}=rc(e.timezone)),t||({country:t,region:n}=ic(e.language));break;case"geoip":({country:t,region:n}=await oc(e.ip));break;case"hosted-geoip-lite":({country:t,region:n}=await cc(e.ip));break;case"maxmind":({country:t,region:n}=await ac(e.ip));break}let a={country:t,complianceGroup:o??this.resolveGroup(t,n)};return n!==void 0&&(a.region=n),s!==void 0&&(a.regulation=s),r!==null&&(a.locale=r),this.resolveRequiresSensitiveOptIn(t,n)&&(a.requiresSensitiveOptIn=!0),a}countryEntry(e){let t=this.map.countries;return ls(t,e)}resolveGroup(e,t){if(!e)return"";let n=this.countryEntry(e);return n?t!==void 0?n.overriddenRegions?.[t]?.complianceGroup??n.complianceGroup:n.default??n.complianceGroup:""}resolveRequiresSensitiveOptIn(e,t){return!e||t===void 0?!1:this.countryEntry(e)?.overriddenRegions?.[t]?.requiresSensitiveOptIn===!0}};function ic(i){let t=(i.split(",")[0]?.trim().split(";")[0]?.trim()??"").split("-");if(t.length>=2){let s=(t[t.length-1]??"").toUpperCase();if(/^[A-Z]{2}$/.test(s))return{country:s}}return{country:sc[(t[0]??"").toLowerCase()]??""}}function rc(i){return{country:Ai[i]??""}}async function oc(i){if(!i)return{country:""};try{let t=(await import("geoip-lite")).lookup(i),n={country:t?.country??""},s=t?.region;return s&&(n.region=s),n}catch{return console.warn("[Consenti] geoip-lite not installed \u2014 country detection skipped. Run: npm install geoip-lite"),{country:""}}}async function ac(i){if(!i)return{country:""};try{let e=await import("maxmind"),t=process.env.MAXMIND_DB_PATH??"./GeoLite2-City.mmdb",s=(await e.open(t)).get(i),r=s?.country?.iso_code??"",o=s?.subdivisions?.[0]?.iso_code,a={country:r};return o&&(a.region=o),a}catch{return console.warn("[Consenti] maxmind not available \u2014 country detection skipped. See HOW_TO_MAXMIND.md"),{country:""}}}async function cc(i){return i?new Promise(e=>{import("node:https").then(({default:t})=>{let n=`https://ipinfo.io/${encodeURIComponent(i)}/json`,s=t.get(n,{timeout:3e3},r=>{let o=[];r.on("data",a=>o.push(a)),r.on("end",()=>{try{let a=JSON.parse(Buffer.concat(o).toString()),c={country:a.country??""};a.region&&(c.region=a.region),e(c)}catch{e({country:""})}})});s.on("error",()=>e({country:""})),s.on("timeout",()=>{s.destroy(),e({country:""})})}).catch(()=>e({country:""}))}):{country:""}}var lc=1440*60*1e3;function dc(i){let e=i?.headers.get("cache-control")?.match(/max-age=(\d+)/)?.[1];if(e){let n=parseInt(e,10)*1e3;if(!isNaN(n)&&n>0)return n}let t=i?.headers.get("expires");if(t){let n=Date.parse(t)-Date.now();if(!isNaN(n)&&n>0)return n}return lc}function vi(i,e){let t,n=async()=>{let{map:s,response:r}=await ds(i,o=>console.error("[Consenti]",o));e.setMap(s),typeof i=="string"&&(t=setTimeout(()=>{n()},dc(r)),t.unref())};return n(),{stop:()=>{t&&clearTimeout(t)}}}import{existsSync as hc,readFileSync as yc,readdirSync as wc,unlinkSync as Ec}from"fs";import{join as bi}from"path";import{copyFileSync as Pi,writeFileSync as uc,renameSync as pc,unlinkSync as Nn,readFileSync as mc,existsSync as gc,mkdirSync as fc}from"fs";import{dirname as _c}from"path";function Oi(i,e,t){let n=i+".bak",s=i+".tmp";fc(_c(i),{recursive:!0});let r=gc(i);r&&Pi(i,n);try{uc(s,JSON.stringify(e,null,2),"utf8");let o=JSON.parse(mc(s,"utf8"));if(!t(o))throw new Error("Schema validation failed for "+i);if(pc(s,i),r)try{Nn(n)}catch{}}catch(o){if(r)try{Pi(n,i),Nn(n)}catch{}try{Nn(s)}catch{}throw o}}function Di(i){return typeof i=="object"&&i!==null&&typeof i.id=="string"&&typeof i.version=="number"}var jt=class{constructor(e){this.dir=e}dir;read(e,t){let n=this.path(e,t);if(!hc(n))return null;try{let s=JSON.parse(yc(n,"utf8"));return Di(s)?s:null}catch{return null}}write(e,t,n){Oi(this.path(e,t),n,Di)}rebuild(e,t){for(let[n,s]of t)try{this.write(e,n,s)}catch(r){console.warn(`[Consenti] locale cache rebuild failed for ${e}/${n}:`,r)}}invalidate(e){try{let t=`${e}_`;for(let n of wc(this.dir))if(n.startsWith(t)&&n.endsWith(".json"))try{Ec(bi(this.dir,n))}catch{}}catch{}}path(e,t){return bi(this.dir,`${e}_${t}.json`)}};function Li(i,e,t){let n=[];for(let[s,r]of Object.entries(i.consentJson)){if(!e[s])continue;let o=rt(s,t);o==="mandatory"&&r!=="granted"&&n.push(`Cookie "${s}" is mandatory and cannot be "${r}"`),r==="objected"&&o!=="legitimate_interest"&&n.push(`Cookie "${s}" has legal basis "${o}" \u2014 'objected' is only valid for 'legitimate_interest'`)}return{valid:n.length===0,errors:n}}function kn(i,e,t,n,s){let r={};for(let[o,a]of Object.entries(i)){if(os(o,e)){r[o]="granted";continue}if(s==="cpra"){if(a.cpraCategory==="sensitive"){let l=t[o];r[o]=l==="granted"||l==="denied"||l==="objected"?l:"denied";continue}if(n&&(a.cpraCategory==="sale"||a.cpraCategory==="sharing")){r[o]="denied";continue}}if(n&&a.listenGpc&&s!=="dpdpa"){r[o]="denied";continue}let c=t[o];c==="granted"||c==="denied"||c==="objected"?r[o]=c:r[o]="denied"}return r}function Mn(i){let e=Object.keys(i.consentJson).sort().map(t=>`${t}=${i.consentJson[t]}`).join(",");return`${i.tenantId}|${i.visitorId}|${i.profileId}|${i.locale}|${e}`}function Ui(i,e,t,n){let s=[];return(!t||t.id!==e.id)&&s.push("profile_changed"),Rc(i,e)&&s.push("consent_expired"),n&&i.signature&&te(Mn(i),n)!==i.signature&&s.push("hmac_invalid"),{valid:s.length===0,reasons:s,...t?{currentProfileId:t.id}:{},consentProfileId:e.id}}function Ni(i,e){let t=new Set,n=new Set;for(let[s,r]of Object.entries(i))if(e[s]==="granted"){r.tcfVendorId&&t.add(r.tcfVendorId);for(let o of r.tcfPurposes??[])n.add(o)}return{vendorConsents:[...t].sort((s,r)=>s-r),purposeConsents:[...n].sort((s,r)=>s-r)}}function Rc(i,e){let{expiryDays:t}=e;if(!t)return!1;let s=new Date(i.createdAt).getTime()+t*24*60*60*1e3;return Date.now()>s}var ki=!1,$t=null,Mi=!1;async function Sc(){if(ki)return $t;ki=!0;try{$t=await import("@iabtechlabtcf/core")}catch{$t=null}return $t}function Cc(i,e){let t=new Set,n=new Set;for(let s of i){let r=e.vendors[String(s)];if(r?.legIntPurposes?.length){t.add(s);for(let o of r.legIntPurposes)n.add(o)}}return{vendorLegitimateInterests:[...t].sort((s,r)=>s-r),purposeLegitimateInterests:[...n].sort((s,r)=>s-r)}}async function xi(i){let e=await Sc();if(!e)return Mi||(Mi=!0,console.warn('[consenti] TCF is enabled but the optional "@iabtechlabtcf/core" dependency is not installed. Falling back to a simplified, non-spec-compliant TC string. Run `npm install @iabtechlabtcf/core` to produce real IAB-spec binary TC strings.')),null;let{TCModel:t,TCString:n,GVL:s}=e,r=new s(i.gvl.raw),o=new t(r);o.cmpId=i.cmpId,o.cmpVersion=i.cmpVersion,o.consentScreen=i.consentScreen,o.consentLanguage=i.consentLanguage,o.publisherCountryCode=i.publisherCC,o.isServiceSpecific=i.isServiceSpecific,o.purposeConsents.set(i.purposeConsents),o.vendorConsents.set(i.vendorConsents);let{vendorLegitimateInterests:a,purposeLegitimateInterests:c}=Cc(i.vendorConsents,i.gvl);return o.purposeLegitimateInterests.set(c),o.vendorLegitimateInterests.set(a),o.vendorsDisclosed.set(i.vendorConsents),n.encode(o)}import{createHash as Ic}from"crypto";function qe(i){let e=Object.keys(i).sort().map(t=>`${t}=${i[t]??""}`).join("&");return Ic("sha256").update(e).digest("hex")}function ne(i,e,t){return i?t?t.configHash===qe(e):!1:!0}var ji=!1,Ft=null,$i=!1;async function Tc(){if(ji)return Ft;ji=!0;try{Ft=await import("@iabgpp/cmpapi")}catch{Ft=null}return Ft}var Fi=0,Hi=1,Ac=2;function Ht(i){return i?Hi:Fi}function xn(i,e){return i?e?Hi:Ac:Fi}async function vc(i){let e=await Tc();if(!e)return null;let{GppModel:t}=e,n=new t;return n.setFieldValue("usnat","SharingNotice",Ht(i.sharingApplicable)),n.setFieldValue("usnat","SaleOptOutNotice",Ht(i.saleApplicable)),n.setFieldValue("usnat","SharingOptOutNotice",Ht(i.sharingApplicable)),n.setFieldValue("usnat","TargetedAdvertisingOptOutNotice",Ht(i.sharingApplicable)),n.setFieldValue("usnat","SaleOptOut",xn(i.saleApplicable,i.saleOptOut)),n.setFieldValue("usnat","SharingOptOut",xn(i.sharingApplicable,i.sharingOptOut)),n.setFieldValue("usnat","TargetedAdvertisingOptOut",xn(i.sharingApplicable,i.sharingOptOut)),n.setFieldValue("usnat","MspaCoveredTransaction",i.mspaCoveredTransaction?1:2),n.setFieldValue("usnat","MspaOptOutOptionMode",i.mspaOptOutOptionMode),n.setFieldValue("usnat","MspaServiceProviderMode",i.mspaServiceProviderMode),i.gpcDetected!==void 0&&(n.setFieldValue("usnat","GpcSegmentIncluded",!0),n.setFieldValue("usnat","Gpc",i.gpcDetected)),n.encode()}async function Bi(i){let e=await vc(i);return e||($i||($i=!0,console.warn('[consenti] GPP is enabled but the optional "@iabgpp/cmpapi" dependency is not installed. Falling back to a simplified, non-spec-compliant GPP string. Run `npm install @iabgpp/cmpapi` to produce real IAB-spec binary GPP strings.')),ps(i))}var Bt=class{constructor(e,t,n,s,r="default",o,a,c,l,d,u,p,g){this.consents=e;this.visitors=t;this.profiles=n;this.audit=s;this.tenantId=r;this.compliance=o;this.pluginEngine=a;this.eventBus=c;this.tcfConfig=l;this.storage=d;this.dataSigningHash=u;this.gppConfig=p;this.archivedSnapshot=g}consents;visitors;profiles;audit;tenantId;compliance;pluginEngine;eventBus;tcfConfig;storage;dataSigningHash;gppConfig;archivedSnapshot;sign(e){if(this.dataSigningHash)return te(Mn(e),this.dataSigningHash)}async isTcfConfirmed(){if(!this.tcfConfig?.enabled)return!0;if(!this.storage)return!1;let e=await this.storage.getSettings(this.tenantId);return ne(!0,{cmpId:this.tcfConfig.cmpId,cmpVersion:this.tcfConfig.cmpVersion,publisherCC:this.tcfConfig.publisherCC},e.tcfConfirmation)}async isGppConfirmed(){if(!this.gppConfig?.enabled)return!0;if(!this.storage)return!1;let e=await this.storage.getSettings(this.tenantId);return ne(!0,{cmpId:this.gppConfig.cmpId,cmpVersion:this.gppConfig.cmpVersion},e.gppConfirmation)}async resolveTemplateData(e){let t=!e.cookies,n=!e.preferenceModal?.categories,s=(t||n)&&e.consentTemplateId&&this.storage?await this.storage.getConsentTemplate(e.consentTemplateId):null;return{cookies:e.cookies??(s?Un(s.cookies,e.cookiesOverride):{}),categories:e.preferenceModal?.categories??s?.categories??{}}}async create(e){let t=await this.profiles.get(e.profileId);if(!t)throw new Error(`Profile ${e.profileId} not found`);let n=t.profileJson.regulation,{cookies:s,categories:r}=await this.resolveTemplateData(t.profileJson),o=De(r),a=e.consentJson;if(e.gpcDetected&&this.compliance?.gpc!==!1&&n!=="dpdpa"){a={...a};for(let[U,V]of Object.entries(s))n==="cpra"?(V.cpraCategory==="sale"||V.cpraCategory==="sharing")&&(a[U]="denied"):V.listenGpc&&(a[U]="denied")}let c=Li({...e,consentJson:a},s,o);if(!c.valid)throw new Error(c.errors.join("; "));let l=kn(s,o,a,e.gpcDetected,n),d,u=this.tcfConfig,p=u?.enabled&&Object.values(s).some(U=>U.tcfVendorId),g=p?await this.isTcfConfirmed():!0;if(u&&p&&!g)console.error(`[Consenti] TCF is enabled (cmpId=${u.cmpId}, cmpVersion=${u.cmpVersion}) but this registration is unconfirmed \u2014 treating TCF as disabled for this consent write (no tcfString generated). Confirm the current cmpId/cmpVersion in the dashboard's TCF Registration panel to re-enable.`);else if(u&&p){let U=await _e(),{vendorConsents:V,purposeConsents:Q}=Ni(s,l),be=e.locale?.slice(0,2)??"en";d=u.publisherCC&&U?await xi({cmpId:u.cmpId,cmpVersion:u.cmpVersion,consentScreen:1,consentLanguage:be,publisherCC:u.publisherCC,isServiceSpecific:!0,purposeConsents:Q,vendorConsents:V,gvl:U})??void 0:void 0,d||(d=us({cmpId:u.cmpId,cmpVersion:u.cmpVersion,consentScreen:1,consentLanguage:be,vendorListVersion:U?.vendorListVersion??0,purposeConsents:Q,vendorConsents:V}))}let E,f=this.gppConfig,y=Object.values(s).some(U=>U.cpraCategory==="sale"),w=Object.values(s).some(U=>U.cpraCategory==="sharing"),R=f?.enabled&&(y||w),I=R?await this.isGppConfirmed():!0;if(f&&R&&!I)console.error(`[Consenti] GPP is enabled (cmpId=${f.cmpId}, cmpVersion=${f.cmpVersion}) but this registration is unconfirmed \u2014 treating GPP as disabled for this consent write (no gppString generated). Confirm the current cmpId/cmpVersion in the dashboard's GPP Registration panel to re-enable.`);else if(f&&R){let U=!y||Object.entries(s).filter(([,Q])=>Q.cpraCategory==="sale").every(([Q])=>l[Q]!=="granted"),V=!w||Object.entries(s).filter(([,Q])=>Q.cpraCategory==="sharing").every(([Q])=>l[Q]!=="granted");E=await Bi({cmpId:f.cmpId,cmpVersion:f.cmpVersion,mspaCoveredTransaction:f.mspaCoveredTransaction,mspaOptOutOptionMode:f.mspaOptOutOptionMode,mspaServiceProviderMode:f.mspaServiceProviderMode,saleOptOut:U,saleApplicable:y,sharingOptOut:V,sharingApplicable:w,gpcDetected:e.gpcDetected})}let T=this.sign({tenantId:this.tenantId,visitorId:e.visitorId,profileId:e.profileId,locale:e.locale,consentJson:l}),v={...e,tenantId:this.tenantId,consentJson:l,...d?{tcfString:d}:{},...E?{gppString:E}:{},...T?{signature:T}:{}};this.pluginEngine&&(v=await this.pluginEngine.runBeforeConsentSave(v)),v={...v,profileVersion:t.version};let N=await this.consents.get(e.visitorId),L;if(N){let U=await(this.pluginEngine?this.pluginEngine.runBeforeConsentUpdate({consentJson:l,locale:e.locale,gpcDetected:e.gpcDetected,...T?{signature:T}:{}}):Promise.resolve({consentJson:l,locale:e.locale,gpcDetected:e.gpcDetected,...T?{signature:T}:{}}));L=await this.consents.update(e.visitorId,{...U,profileVersion:t.version})}else L=await this.consents.create(v);return await this.audit.log({tenantId:this.tenantId,action:N?"consent:updated":"consent:created",resourceType:"consent",resourceId:L.id,...N!=null?{oldData:N}:{},newData:L}),N?(this.eventBus?.emit("consent:updated",{previous:N,current:L}),await this.pluginEngine?.runAfterConsentUpdate(L)):(this.eventBus?.emit("consent:created",L),await this.pluginEngine?.runAfterConsentSave(L)),L}async update(e,t){let n=await this.consents.get(e);if(!n)throw new Error(`Consent for visitor ${e} not found`);let s=await this.profiles.get(n.profileId);if(!s)throw new Error(`Profile ${n.profileId} not found`);let r=s.profileJson.regulation,{cookies:o,categories:a}=await this.resolveTemplateData(s.profileJson),c=De(a),l=t.gpcDetected??n.gpcDetected,d=t.consentJson;if(l&&this.compliance?.gpc!==!1&&r!=="dpdpa"){d={...d};for(let[y,w]of Object.entries(o))r==="cpra"?(w.cpraCategory==="sale"||w.cpraCategory==="sharing")&&(d[y]="denied"):w.listenGpc&&(d[y]="denied")}let u=kn(o,c,d,l,r),p=this.sign({tenantId:this.tenantId,visitorId:e,profileId:n.profileId,locale:t.locale??n.locale,consentJson:u}),g={consentJson:u,...t.locale!==void 0?{locale:t.locale}:{},...t.gpcDetected!==void 0?{gpcDetected:t.gpcDetected}:{},...p?{signature:p}:{}},E=await(this.pluginEngine?this.pluginEngine.runBeforeConsentUpdate(g):Promise.resolve(g)),f=await this.consents.update(e,{...E,profileVersion:s.version});return await this.audit.log({tenantId:this.tenantId,action:"consent:updated",resourceType:"consent",resourceId:f.id,oldData:n,newData:f}),this.eventBus?.emit("consent:updated",{previous:n,current:f}),await this.pluginEngine?.runAfterConsentUpdate(f),f}get(e){return this.consents.get(e)}async verify(e){let t=await this.consents.get(e);if(!t)return{valid:!1,reasons:["profile_changed"]};let n=await this.profiles.get(t.profileId),s=n?null:await this.archivedSnapshot?.(t.profileId,t.profileVersion)??null;if(!n&&!s)return{valid:!1,reasons:["profile_changed"]};let r=n?{id:n.id,expiryDays:n.profileJson.expiryDays}:{id:t.profileId,expiryDays:s?.expiryDays},o=n?n.profileJson.complianceGroup:s?.complianceGroup,a=o?await this.profiles.findActiveByComplianceGroup(this.tenantId,o):null;return Ui(t,r,a,this.dataSigningHash)}async erase(e){let t=await this.consents.get(e);await this.consents.delete(e),await this.visitors.delete(e),await this.audit.log({tenantId:this.tenantId,action:"consent:erased",resourceType:"consent",resourceId:e,...t!=null?{oldData:t}:{}}),this.eventBus?.emit("consent:erased",{visitorId:e})}};var Vt=class{constructor(e,t="default",n,s=""){this.visitors=e;this.tenantId=t;this.eventBus=n;this.dataSigningHash=s}visitors;tenantId;eventBus;dataSigningHash;async upsert(e){if(await this.visitors.get(e.visitorId))return this.visitors.update(e.visitorId,{...e.country!=null?{country:e.country}:{},...e.region!=null?{region:e.region}:{},...e.city!=null?{city:e.city}:{}});let n={tenantId:this.tenantId,visitorId:e.visitorId,...e.country!=null?{country:e.country}:{},...e.region!=null?{region:e.region}:{},...e.city!=null?{city:e.city}:{},...e.ip?{ipHash:As(e.ip,this.dataSigningHash)}:{},...e.userAgent?{userAgentHash:vs(e.userAgent)}:{}},s=await this.visitors.create(n);return this.eventBus?.emit("visitor:created",s),s}};var Wt=class{constructor(e,t="default"){this.notices=e;this.tenantId=t}notices;tenantId;recordShown(e){return this.notices.create({tenantId:this.tenantId,...e})}getForVisitor(e){return this.notices.getForVisitor(e)}};var Jt=class{constructor(e,t,n="default"){this.users=e;this.audit=t;this.tenantId=n}users;audit;tenantId;safe(e){let{passwordHash:t,...n}=e;return n}async create(e,t){let n=await this.users.create({tenantId:this.tenantId,name:e.name,email:e.email,passwordHash:xe(e.password),...e.allowedTenants?{allowedTenants:e.allowedTenants}:{}});return e.roleId&&await this.users.assignRole(n.id,e.roleId),await this.audit.log({tenantId:this.tenantId,...t!=null?{userId:t}:{},action:"user.created",resourceType:"user",resourceId:n.id,newData:this.safe(n)}),this.safe(n)}async update(e,t,n){let s=await this.users.getById(e);if(!s)throw new Error(`User ${e} not found`);let r={};t.name!=null&&(r.name=t.name),t.email!=null&&(r.email=t.email),t.isActive!=null&&(r.isActive=t.isActive),t.password&&(r.passwordHash=xe(t.password)),t.allowedTenants!=null&&(r.allowedTenants=t.allowedTenants);let o=await this.users.update(e,r);return await this.audit.log({tenantId:this.tenantId,...n!=null?{userId:n}:{},action:"user.updated",resourceType:"user",resourceId:e,oldData:this.safe(s),newData:this.safe(o)}),this.safe(o)}async delete(e,t){let n=await this.users.getById(e);await this.users.delete(e),await this.audit.log({tenantId:this.tenantId,...t!=null?{userId:t}:{},action:"user.deleted",resourceType:"user",resourceId:e,...n!=null?{oldData:this.safe(n)}:{}})}async get(e){let t=await this.users.getById(e);return t?this.safe(t):null}async list(){return(await this.users.list(this.tenantId)).map(t=>this.safe(t))}getRoles(e,t){return this.users.getRoles(e,t)}assignRole(e,t,n){return this.users.assignRole(e,t,n)}revokeRole(e,t,n){return this.users.revokeRole(e,t,n)}};var Vi=15*6e4,Pc=5,Wi=1800,Gt=class{constructor(e,t){this.storage=e;this.secret=t;setInterval(()=>{let n=Date.now();for(let[s,r]of this.loginAttempts)n>=r.resetAt&&this.loginAttempts.delete(s)},Vi).unref()}storage;secret;loginAttempts=new Map;async bootstrap(e,t){if(await this.storage.countUsers("default")>0)return null;if(t.length<12)throw new Error("[Consenti] bootstrap: admin password must be at least 12 characters");t.length<16&&console.warn("[Consenti] bootstrap: admin password is shorter than 16 characters \u2014 consider using a longer password");let s=xe(t),r=await this.storage.createUser({tenantId:"default",name:"Admin",email:e,passwordHash:s});return await this.storage.assignRole(r.id,"role_super_admin"),r}async login(e,t){let n=Date.now(),s=this.loginAttempts.get(e);if(s&&n<s.resetAt&&s.count>=Pc)return null;let r=await this.storage.getUserByEmail(e);if(!r||!r.isActive||!Ps(t,r.passwordHash)){let l=this.loginAttempts.get(e);return!l||n>=l.resetAt?this.loginAttempts.set(e,{count:1,resetAt:n+Vi}):l.count+=1,null}this.loginAttempts.delete(e);let o=await this.storage.getUserRoles(r.id),a=await Promise.all(o.map(l=>this.storage.getPermissionsForRole(l.id))),c=[...new Set(a.flat().map(l=>l.name))];return pn({sub:r.id,email:r.email,roles:o.map(l=>l.name),permissions:c,allowedTenants:r.allowedTenants??[]},this.secret,Wi)}signToken(e){return pn(e,this.secret,Wi)}verify(e){return ct(e,this.secret)}async getUser(e){let t=this.verify(e);if(!t)return null;let n=t.sub;return typeof n!="string"?null:this.storage.getUserById(n)}};var Ke=class extends Error{constructor(){super("Payload Too Large")}};function Oc(i,e){let t=i.socket?.remoteAddress??"";if(!e?.length||!e.includes(t))return t;let n=i.headers["x-forwarded-for"];return typeof n=="string"?n.split(",")[0]?.trim()??t:Array.isArray(n)?n[0]?.trim()??t:t}function _(i,e){return new Response(JSON.stringify(e),{status:i,headers:{"Content-Type":"application/json"}})}async function P(i){if(!(i.headers.get("content-type")??"").includes("application/json"))return null;try{return await i.json()}catch{return null}}function M(i,e){return i.searchParams.get(e)??void 0}function qt(i,e,t){let n=i.searchParams.get(e);if(!n)return t;let s=parseInt(n,10);return isNaN(s)?t:s}var Ji=500;function Pe(i,e=50){let t=qt(i,"limit",e);return t<1||t>Ji?{error:`limit must be between 1 and ${Ji}`}:{page:Math.max(1,qt(i,"page",1)),limit:t}}async function Gi(i,e=1048576,t){let s=i.headers["x-forwarded-proto"]==="https"?"https":"http",r=i.headers.host??"localhost",o=new URL(i.url??"/",`${s}://${r}`),a=new Headers;for(let[d,u]of Object.entries(i.headers))if(u)if(Array.isArray(u))for(let p of u)a.append(d,p);else a.set(d,u);a.set("x-real-ip",Oc(i,t));let c=null,l=(i.method??"GET").toUpperCase();if(l!=="GET"&&l!=="HEAD"){let d=[],u=0;for await(let p of i){if(u+=p.length,u>e)throw new Ke;d.push(p)}d.length>0&&(c=Buffer.concat(d))}return new Request(o.toString(),{method:l,headers:a,body:c})}async function qi(i,e){e.statusCode=i.status;let t=typeof i.headers.getSetCookie=="function"?i.headers.getSetCookie():i.headers.get("set-cookie")?[i.headers.get("set-cookie")]:[];i.headers.forEach((n,s)=>{s.toLowerCase()!=="set-cookie"&&e.setHeader(s,n)});for(let n of t)e.appendHeader("set-cookie",n);if(i.body){let n=i.body.getReader();try{for(;;){let{done:s,value:r}=await n.read();if(s)break;e.write(Buffer.from(r))}}finally{n.releaseLock()}e.end()}else e.end()}function Ki(i){if(typeof i!="string")return;let e=i.trim();return e.length>0&&e.length<=35?e:void 0}function jn(i){if(!Array.isArray(i))return[];let e=[];for(let t of i){let n=Ki(t);if(n&&e.push(n),e.length===20)break}return e}function $n(i){return jn(i.split(",").map(e=>e.split(";")[0]))}function Yi(i){return Ki(i)??""}function m(i,e,t){let n={error:e};return t!=null&&(n.details=t),new Response(JSON.stringify(n),{status:i,headers:{"Content-Type":"application/json"}})}async function h(i){try{return await i()}catch(e){let t=e instanceof Error?e.message:"Internal Server Error";return m(500,"Internal Server Error",process.env.NODE_ENV!=="production"?t:void 0)}}import{existsSync as Kt,readFileSync as bc}from"fs";import{join as Yt}from"path";var Dc="public, max-age=3600, stale-while-revalidate=60",zi={"Content-Type":"application/json","Cache-Control":Dc};function Lc(i){let e=bc(i,"utf8");return new Response(e,{headers:zi})}function Uc(i,e,t){return new Response(null,{status:303,headers:{Location:`${i}/profiles/${e}/${t}/default`}})}function Qi(i,e,t,n=""){return{"GET /profiles/:tenantId/:complianceGroupOrProfileId/:locale":async(s,r)=>h(async()=>{let{tenantId:o,complianceGroupOrProfileId:a,locale:c}=r;if(!o||!a||!c)return m(400,"Missing path parameters");if(t){let d=Yt(t,o,a,`${c}.json`);if(Kt(d))return Lc(d);let u=Yt(t,o,a,"default.json");if(Kt(u))return Uc(n,o,a)}let l=await i.getResolved(a,c);return l?_(200,l):m(404,"Profile not found")}),"GET /profiles/:tenantId/:profileId/:entryId/:locale":async(s,r)=>h(async()=>{let{profileId:o,entryId:a,locale:c}=r;if(!o||!a||!c)return m(400,"Missing path parameters");let l=await i.getVersionFile(o,a,c);return l?new Response(l,{headers:zi}):m(404,"Version not found")}),"GET /resolve-profile":async(s,r)=>h(async()=>{if(!e)return m(501,"Geo compliance routing not configured");let o=new URL(s.url),a=s.headers.get("x-forwarded-for")?.split(",")[0]?.trim()??s.headers.get("x-real-ip")??"",c=o.searchParams.get("tz")??"",l=o.searchParams.get("lang")??s.headers.get("accept-language")??"",d=o.searchParams.get("locale")??"",u=$n(o.searchParams.get("lang")??s.headers.get("accept-language")??""),p=o.searchParams.get("data");if(p)try{let L=JSON.parse(Buffer.from(p,"base64").toString("utf8"));L.timezone&&(c=L.timezone),L.language&&(l=L.language),L.locale&&(d=L.locale);let U=jn(L.languages);U.length>0&&(u=U)}catch{}let g=await e.resolve({ip:a,language:l,languages:u,timezone:c,locale:Yi(d)}),E=g.requiresSensitiveOptIn?{requiresSensitiveOptIn:!0}:{};if(!g.complianceGroup)return _(200,{path:null,complianceGroup:"opt-in",locale:d||"en",found:!1,...E});let f=await i.findActiveByComplianceGroup(g.complianceGroup);if(!f)return _(200,{path:null,complianceGroup:g.complianceGroup,locale:d||"en",found:!1,...E});let y=f.profileJson.locales??[],w=f.defaultLocale;if(d&&y.includes(d))w=d;else if(y.length>0){let L=(d||l).split("-")[0]??"",U=y.find(V=>V===L||V.startsWith(`${L}-`));U&&(w=U)}let R=g.complianceGroup,I=f.tenantId,T=!1,v=t?Yt(t,I,R,`${w}.json`):"";if(t&&Kt(v))T=!0;else if(t){let L=Yt(t,I,R,`${f.defaultLocale}.json`);Kt(L)&&(w=f.defaultLocale,v=L,T=!0)}let N=T?`${n}/profiles/${I}/${R}/${w}`:null;return _(200,{path:N,complianceGroup:R,locale:w,found:T,...E})}),"GET /profiles/:id":async(s,r)=>h(async()=>{let o=r.id;if(!o)return m(400,"Missing profile id");let a=await i.getResolved(o);return a?_(200,a):m(404,"Profile not found")}),"GET /profiles/:id/:locale":async(s,r)=>h(async()=>{let{id:o,locale:a}=r;if(!o)return m(400,"Missing profile id");let c=await i.getResolved(o,a);return c?_(200,c):m(404,"Profile not found")}),"GET /profiles/auto/:locale":async(s,r)=>h(async()=>{if(!e)return m(501,"Geo compliance routing not configured");let o=r.locale??"en",a=new URL(s.url),c=s.headers.get("x-forwarded-for")?.split(",")[0]?.trim()??s.headers.get("x-real-ip")??"",l=a.searchParams.get("tz")??"",d=s.headers.get("accept-language")??a.searchParams.get("lang")??"",u=await e.resolve({ip:c,language:d,languages:$n(d),timezone:l,locale:o});if(!u.complianceGroup)return m(404,"Could not resolve compliance group for this location");let p=await i.findActiveByComplianceGroup(u.complianceGroup);if(!p)return m(404,`No active profile found for compliance group: ${u.complianceGroup}`);let g=await i.getResolved(p.id,o);return g?_(200,{...g,resolvedComplianceGroup:u.complianceGroup,...u.requiresSensitiveOptIn?{requiresSensitiveOptIn:!0}:{}}):m(404,"Profile not found")})}}function Xi(){return process.env.NODE_ENV==="production"?"SameSite=None; Secure":"SameSite=Lax"}function Nc(i,e,t){let n=te(`${i}.${e}`,t);return`${e}.${n}`}function Fn(i,e,t){let n=`consenti_${i}`,s=Nc(i,e,t);return`${n}=${s}; ${Xi()}; Path=/; Max-Age=31536000`}function Zi(i){return`${`consenti_${i}`}=; ${Xi()}; Path=/; Max-Age=0`}function er(i,e,t){let n=i.split(".");if(n.length!==2)return!1;let[s,r]=n;return!s||!r?!1:at(`${e}.${s}`,r,t)}var zt="pcon_";function kc(i){return Buffer.from(JSON.stringify(i),"utf8").toString("base64url")}function Mc(i){try{let e=JSON.parse(Buffer.from(i,"base64url").toString("utf8"));return typeof e.visitorId!="string"||typeof e.profileId!="string"||typeof e.issuedAt!="number"?null:{visitorId:e.visitorId,profileId:e.profileId,issuedAt:e.issuedAt}}catch{return null}}function tr(i,e,t){let n={visitorId:i,profileId:e,issuedAt:Math.round(Date.now()/1e3)},s=kc(n);if(!t)return`${zt}${s}`;let r=te(s,t);return`${zt}${s}.${r}`}function nr(i,e,t){if(!i.startsWith(zt))return{valid:!1,reason:"malformed"};let n=i.slice(zt.length),s=n.indexOf("."),r=s===-1?n:n.slice(0,s),o=s===-1?void 0:n.slice(s+1);if(e&&(!o||!at(r,o,e)))return{valid:!1,reason:"invalid_signature"};let a=Mc(r);return a?t>0&&Math.round(Date.now()/1e3)-a.issuedAt>t*24*60*60?{valid:!1,reason:"expired"}:{valid:!0,visitorId:a.visitorId,profileId:a.profileId}:{valid:!1,reason:"malformed"}}function xc(i){if(!i.includes("://"))return i;try{return new URL(i).hostname}catch{return i}}function sr(i,e){let t;try{t=new URL(i).hostname}catch{return!1}return e.some(n=>{if(n.startsWith("*.")){let s=n.slice(1);return t===n.slice(2)||t.endsWith(s)}return t===xc(n)})}function ir(i,e){let t=i??"*";return e&&e.length>0&&i&&(t=sr(i,e)?i:"null"),{"Access-Control-Allow-Origin":t,"Access-Control-Allow-Methods":"GET, POST, PUT, DELETE, OPTIONS","Access-Control-Allow-Headers":"Content-Type, Authorization","Access-Control-Max-Age":"86400","Access-Control-Allow-Credentials":"true"}}function rr(i){if(i.method==="OPTIONS"){let e=i.headers.get("origin");return new Response(null,{status:204,headers:ir(e)})}return null}function Ye(i,e){let t=e.headers.get("origin"),n=ir(t),s=new Headers(i.headers);for(let[r,o]of Object.entries(n))s.set(r,o);return new Response(i.body,{status:i.status,statusText:i.statusText,headers:s})}function Qt(i,e){if(e.length===0)return!0;let t=i.headers.get("origin");return t?sr(t,e):!0}var jc=["default-src 'self'","script-src 'self' https://unpkg.com","style-src 'self' https://unpkg.com 'unsafe-inline'","img-src 'self' data:","font-src 'self'","connect-src 'self'","frame-ancestors 'none'","base-uri 'self'","form-action 'self'"].join("; ");function Hn(i){let e=new Headers(i.headers);return e.set("X-Content-Type-Options","nosniff"),e.set("X-Frame-Options","DENY"),e.set("Referrer-Policy","no-referrer"),e.set("Permissions-Policy","interest-cohort=()"),e.set("Content-Security-Policy",jc),e.set("Strict-Transport-Security","max-age=63072000; includeSubDomains; preload"),new Response(i.body,{status:i.status,statusText:i.statusText,headers:e})}var $c=new Set(["granted","denied","objected"]);function or(i){let e=Object.entries(i);for(let[t,n]of e){if(!t)return{valid:!1,error:"consentJson keys must be non-empty strings"};if(!$c.has(n))return{valid:!1,error:`consentJson value "${String(n)}" is invalid; must be granted, denied, or objected`}}return{valid:!0}}function ar(i){if(!i||typeof i!="object")return{valid:!1,error:"Request body must be a JSON object"};let e=i;return typeof e.profileId!="string"||!e.profileId?{valid:!1,error:"profileId is required"}:!e.consentJson||typeof e.consentJson!="object"||Array.isArray(e.consentJson)?{valid:!1,error:"consentJson must be an object"}:or(e.consentJson)}function cr(i){if(!i||typeof i!="object")return{valid:!1,error:"Request body must be a JSON object"};let e=i;return!e.consentJson||typeof e.consentJson!="object"||Array.isArray(e.consentJson)?{valid:!1,error:"consentJson must be an object"}:or(e.consentJson)}function lr(i,e,t){return{tenantId:t,visitorId:e,profileId:i.profileId,locale:typeof i.locale=="string"?i.locale:"en",consentJson:i.consentJson,gpcDetected:!!i.gpcDetected,source:i.source??"banner",...typeof i.ageVerified=="boolean"?{ageVerified:i.ageVerified}:{},...typeof i.parentalConsentToken=="string"?{parentalConsentToken:i.parentalConsentToken}:{}}}function dr(i){let e={consentJson:i.consentJson};return typeof i.locale=="string"&&(e.locale=i.locale),i.gpcDetected!=null&&(e.gpcDetected=!!i.gpcDetected),e}function ze(i,e,t){let n=i.headers.get("cookie")??"",s=`consenti_${e}`;return n.split(";").some(o=>{let a=o.trim(),c=a.indexOf("=");return c===-1||a.slice(0,c)!==s?!1:er(a.slice(c+1),e,t)})?null:m(403,"Forbidden")}function ur(i,e,t,n=async()=>"default",s,r="",o,a=7){return{"POST /consent":async(c,l)=>h(async()=>{let d=await P(c),u=ar(d);if(!u.valid)return m(400,u.error??"Invalid request");let p=d,g=typeof p.profileId=="string"?p.profileId:null;if(!g)return m(400,"profileId is required");let E=await t.get(g);if(!E)return m(404,"Profile not found");let f=await n(c),w=E.profileJson.allowedOrigins??[];if(w.length===0&&s&&(w=(await s.getSettings(f)).allowedOrigins??[]),!Qt(c,w))return m(403,"Origin not allowed for this profile");let R=typeof p.visitorId=="string"?p.visitorId:H(),I=c.headers.get("x-real-ip")??"",T=c.headers.get("user-agent")??"";await e.upsert({visitorId:R,ip:I,userAgent:T});let v=lr(p,R,f),N=await i.create(v),L=Fn(R,N.id,r);return new Response(JSON.stringify(N),{status:201,headers:{"Content-Type":"application/json","Set-Cookie":L}})}),"PUT /consent/:visitorId":async(c,l)=>h(async()=>{let d=l.visitorId;if(!d)return m(400,"Missing visitorId");let u=ze(c,d,r);if(u)return u;let p=await P(c),g=cr(p);if(!g.valid)return m(400,g.error??"Invalid request");let E=dr(p),f=await i.update(d,E),y=Fn(d,f.id,r);return new Response(JSON.stringify(f),{status:200,headers:{"Content-Type":"application/json","Set-Cookie":y}})}),"GET /consent/:visitorId":async(c,l)=>h(async()=>{let d=l.visitorId;if(!d)return m(400,"Missing visitorId");let u=ze(c,d,r);if(u)return u;let p=await i.get(d);return p?new Response(JSON.stringify(p),{status:200,headers:{"Content-Type":"application/json"}}):m(404,"Consent not found")}),"GET /consent/:visitorId/verify":async(c,l)=>h(async()=>{let d=l.visitorId;if(!d)return m(400,"Missing visitorId");let u=ze(c,d,r);if(u)return u;let p=await i.verify(d);return new Response(JSON.stringify(p),{status:200,headers:{"Content-Type":"application/json"}})}),"DELETE /consent/:visitorId":async(c,l)=>h(async()=>{let d=l.visitorId;if(!d)return m(400,"Missing visitorId");let u=ze(c,d,r);if(u)return u;let p=await i.get(d);await i.erase(d);let g={"Content-Type":"application/json"};return p&&(g["Set-Cookie"]=Zi(d)),new Response(JSON.stringify({success:!0}),{status:200,headers:g})}),"POST /consent/:visitorId/parental-consent-request":async(c,l)=>h(async()=>{let d=l.visitorId;if(!d)return m(400,"Missing visitorId");let u=ze(c,d,r);if(u)return u;let p=await P(c),g=typeof p.profileId=="string"?p.profileId:null;if(!g)return m(400,"profileId is required");if(!await t.get(g))return m(404,"Profile not found");let E=tr(d,g,r),f=Math.round(Date.now()/1e3);return o?.emit("consent.parentalConsentRequired",{visitorId:d,profileId:g,token:E,issuedAt:f}),_(200,{token:E})}),"POST /consent/parental-consent-resolve":async c=>h(async()=>{let l=await P(c),d=typeof l.token=="string"?l.token:null;if(!d)return m(400,"token is required");let u=nr(d,r,a);return u.valid?(o?.emit("consent.parentalConsentGranted",{visitorId:u.visitorId,profileId:u.profileId}),_(200,{visitorId:u.visitorId,profileId:u.profileId})):m(403,`Invalid or expired token (${u.reason})`)})}}var Fc=!1;function pr(i){return{"POST /notice-shown":async(e,t)=>h(async()=>{if(!Fc)return m(404,"Not found");let n=await P(e);if(!n||typeof n!="object")return m(400,"Request body must be a JSON object");let s=n,r=typeof s.visitorId=="string"?s.visitorId:"",o=typeof s.profileId=="string"?s.profileId:"",a=typeof s.locale=="string"?s.locale:"";if(!r)return m(400,"visitorId is required");if(!o)return m(400,"profileId is required");if(!a)return m(400,"locale is required");let c=await i.recordShown({visitorId:r,profileId:o,locale:a});return new Response(JSON.stringify({success:!0,id:c.id}),{status:201,headers:{"Content-Type":"application/json"}})})}}function mr(i,e){return{"GET /tcf/status":async(t,n)=>h(async()=>{if(!e?.enabled)return _(200,{blocked:!1});let s=await i.getSettings("default"),r=ne(!0,{cmpId:e.cmpId,cmpVersion:e.cmpVersion,publisherCC:e.publisherCC},s.tcfConfirmation);return _(200,{blocked:!r})})}}function gr(i,e){return{"GET /gpp/status":async(t,n)=>h(async()=>{if(!e?.enabled)return _(200,{blocked:!1});let s=await i.getSettings("default"),r=ne(!0,{cmpId:e.cmpId,cmpVersion:e.cmpVersion},s.gppConfirmation);return _(200,{blocked:!r})})}}import{randomBytes as br}from"crypto";import{createHash as Hc}from"crypto";async function O(i,e,t,n){let s=i.headers.get("authorization"),r=s?.startsWith("Bearer ")?s.slice(7):null;if(!r)return null;if(r.startsWith("ck_")){let o=Hc("sha256").update(r).digest("hex"),a=await e.getApiKeyByHash(o);if(!a||!a.isActive)return null;if(a.expireBy&&new Date(a.expireBy)<=new Date)return await e.revokeApiKey(a.id).catch(()=>{}),null;let c=(await e.getAllPermissions()).map(l=>l.name);return{sub:`apikey:${a.id}`,email:a.name,roles:["api_key"],permissions:c,allowedTenants:[a.tenantId]}}if(t.mode==="local"||t.mode==="jwt"){let o=ct(r,n);if(!o)return null;let a=o.sub,c=o.email,l=o.roles,d=o.permissions,u=o.allowedTenants;return typeof a!="string"||typeof c!="string"?null:{sub:a,email:c,roles:Array.isArray(l)?l:[],permissions:Array.isArray(d)?d:[],allowedTenants:Array.isArray(u)?u:[]}}if(t.mode==="custom"&&t.validateUser){let o=await t.validateUser(i);if(!o)return null;let a=await e.getUserRoles(o.id),c=await Promise.all(a.map(d=>e.getPermissionsForRole(d.id))),l=[...new Set(c.flat().map(d=>d.name))];return{sub:o.id,email:o.email,roles:a.map(d=>d.name),permissions:l,allowedTenants:[]}}return null}function D(i,e){return i?e&&!i.permissions.includes(e)?m(403,"Forbidden"):null:m(401,"Unauthorized")}import{randomBytes as Bc,createHash as Vc}from"crypto";function fr(i){return i.toString("base64").replace(/\+/g,"-").replace(/\//g,"_").replace(/=/g,"")}function _r(i){return Buffer.from(i.replace(/-/g,"+").replace(/_/g,"/"),"base64").toString("utf8")}function hr(){let i=fr(Bc(32)),e=fr(Vc("sha256").update(i).digest());return{verifier:i,challenge:e}}async function Bn(i){let e=await fetch(`${i}/.well-known/openid-configuration`,{signal:AbortSignal.timeout(1e4)});if(!e.ok)throw new Error(`OIDC discovery failed for ${i}: ${e.status}`);return e.json()}function yr(i,e,t,n,s){let r=new URLSearchParams({response_type:"code",client_id:i.clientId,redirect_uri:i.redirectUri,scope:"openid email profile",state:t,nonce:n,code_challenge:s,code_challenge_method:"S256"});return`${e}?${r.toString()}`}async function wr(i,e,t,n){let s=new URLSearchParams({grant_type:"authorization_code",client_id:i.clientId,client_secret:i.clientSecret,redirect_uri:i.redirectUri,code:t,code_verifier:n}),r=await fetch(e,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:s.toString(),signal:AbortSignal.timeout(1e4)});if(!r.ok){let o=await r.text();throw new Error(`OIDC token exchange failed: ${o}`)}return r.json()}async function Er(i,e){let t=i.split(".");if(t.length!==3)throw new Error("Invalid ID token format");let n=JSON.parse(_r(t[0])),s=n.alg??"RS256",r=await fetch(e,{signal:AbortSignal.timeout(1e4)});if(!r.ok)throw new Error(`Failed to fetch JWKS: ${r.status}`);let o=await r.json(),a=n.kid?o.keys.find(w=>w.kid===n.kid):o.keys.find(w=>w.use==="sig")??o.keys[0];if(!a)throw new Error("No matching key in JWKS");let c;if(s==="RS256"||s==="RS384"||s==="RS512"){let w=s==="RS256"?"SHA-256":s==="RS384"?"SHA-384":"SHA-512";c=await globalThis.crypto.subtle.importKey("jwk",a,{name:"RSASSA-PKCS1-v1_5",hash:{name:w}},!1,["verify"])}else if(s==="ES256"||s==="ES384"||s==="ES512"){let w=s==="ES256"?"P-256":s==="ES384"?"P-384":"P-521";c=await globalThis.crypto.subtle.importKey("jwk",a,{name:"ECDSA",namedCurve:w},!1,["verify"])}else throw new Error(`Unsupported OIDC token algorithm: ${s}`);let l=new TextEncoder().encode(`${t[0]}.${t[1]}`),d=(4-t[2].length%4)%4,u=t[2].replace(/-/g,"+").replace(/_/g,"/")+"=".repeat(d),p=Uint8Array.from(atob(u),w=>w.charCodeAt(0)),g=s.startsWith("ES")?{name:"ECDSA",hash:{name:`SHA-${s.slice(2)}`}}:{name:"RSASSA-PKCS1-v1_5"};if(!await globalThis.crypto.subtle.verify(g,c,p,l))throw new Error("ID token signature verification failed");let f=JSON.parse(_r(t[1])),y=Math.floor(Date.now()/1e3);if(typeof f.exp=="number"&&f.exp<y)throw new Error("ID token has expired");return f}function Rr(i,e){let t=e?.email??"email",n=i[t];if(typeof n!="string")throw new Error("OIDC token missing email claim");return n}function Sr(i,e){let t=e?.roles??"consenti_roles",n=i[t];return Array.isArray(n)?n.filter(s=>typeof s=="string"):[]}var Qe=new Map;setInterval(()=>{let i=Date.now();for(let[e,t]of Qe)t.expiresAt<i&&Qe.delete(e)},5*6e4).unref();function Cr(i,e,t){Qe.set(i,{verifier:e,nonce:t,expiresAt:Date.now()+10*6e4})}function Ir(i){let e=Qe.get(i);return Qe.delete(i),!e||e.expiresAt<Date.now()?null:{verifier:e.verifier,nonce:e.nonce}}function Wc(i){return i.replace(/&/g,"&amp;").replace(/"/g,"&quot;").replace(/</g,"&lt;").replace(/>/g,"&gt;")}function Tr(i){return`<?xml version="1.0" encoding="UTF-8"?>
138
140
  <EntityDescriptor xmlns="urn:oasis:names:tc:SAML:2.0:metadata"
139
141
  entityID="saml:sp:consenti">
140
142
  <SPSSODescriptor
@@ -144,19 +146,19 @@ ${l.map(d=>` (${Q(c)},${Q(d)})`).join(`,
144
146
  <NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress</NameIDFormat>
145
147
  <AssertionConsumerService
146
148
  Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
147
- Location="${mc(i.callbackUrl)}"
149
+ Location="${Wc(i.callbackUrl)}"
148
150
  index="0"
149
151
  isDefault="true"/>
150
152
  </SPSSODescriptor>
151
- </EntityDescriptor>`}async function tr(i,e){let t;try{t=await import("samlify")}catch{return console.warn("[Consenti] samlify is not installed. Install it to use SAML auth mode."),null}try{let n=t.ServiceProvider({entityID:"saml:sp:consenti",assertionConsumerService:[{Binding:"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST",Location:e.callbackUrl}]}),s=t.IdentityProvider({entityID:e.issuer,singleSignOnService:[{Binding:"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect",Location:e.entryPoint}],signingCert:e.cert}),r=await n.parseLoginResponse(s,"post",{body:{SAMLResponse:i}}),o=r.extract.nameID,a=r.extract.attributes.roles,c=Array.isArray(a)?a.filter(l=>typeof l=="string"):[];return{email:o,roles:c}}catch(n){return console.warn("[Consenti] SAML response validation failed:",n),null}}import{createHmac as gc,randomBytes as fc,timingSafeEqual as _c}from"crypto";var vn="ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";function hc(i){let e="",t=0,n=0;for(let s of i)for(n=n<<8|s,t+=8;t>=5;)e+=vn[n>>>t-5&31],t-=5;return t>0&&(e+=vn[n<<5-t&31]),e}function yc(i){let e=i.toUpperCase().replace(/[^A-Z2-7]/g,""),t=[],n=0,s=0;for(let r of e){let o=vn.indexOf(r);o<0||(s=s<<5|o,n+=5,n>=8&&(t.push(s>>>n-8&255),n-=8))}return Buffer.from(t)}function wc(i,e){let t=Buffer.alloc(8),n=e;for(let a=7;a>=0;a--)t[a]=n&255,n=Math.floor(n/256);let s=gc("sha1",i).update(t).digest(),r=s[s.length-1]&15,o=(s[r]&127)<<24|(s[r+1]&255)<<16|(s[r+2]&255)<<8|s[r+3]&255;return String(o%1e6).padStart(6,"0")}function nr(){return hc(fc(20))}var Jt=new Map;setInterval(()=>{let i=Date.now();for(let[e,t]of Jt)i>=t&&Jt.delete(e)},9e4).unref();function sr(i,e,t=1){if(Jt.has(e))return!1;let n=Math.floor(Date.now()/3e4),s=yc(i),r=Buffer.from(e);for(let o=-t;o<=t;o++){let a=Buffer.from(wc(s,n+o));if(a.length===r.length&&_c(a,r))return Jt.set(e,Date.now()+9e4),!0}return!1}function ir(i,e,t="Consenti"){let n=encodeURIComponent(`${t}:${e}`),s=new URLSearchParams({secret:i,issuer:t,algorithm:"SHA1",digits:"6",period:"30"});return`otpauth://totp/${n}?${s.toString()}`}function or(i){return i.toString("base64").replace(/\+/g,"-").replace(/\//g,"_").replace(/=/g,"")}function ar(i,e,t,n,s){function r(o){if(!s)return null;let a=o.headers.get("x-real-ip")??"";return s.checkRequest(a)?null:m(429,"Too Many Requests")}return{"POST /auth/login":async(o,a)=>y(async()=>{let c=r(o);if(c)return c;if(t.mode!=="local")return m(400,"Login not available in this auth mode");let l=await v(o);if(!l||typeof l!="object")return m(400,"Invalid body");let d=l;if(typeof d.email!="string"||typeof d.password!="string")return m(400,"email and password are required");let u=await i.login(d.email,d.password);return u?f(200,{token:u}):m(401,"Invalid credentials")}),"GET /auth/me":async(o,a)=>y(async()=>{let c=await P(o,e,t,n);if(!c)return m(401,"Unauthorized");let l=await e.getUserById(c.sub);if(!l)return m(401,"User not found");let d=await e.getUserRoles(l.id);return f(200,{id:l.id,email:l.email,name:l.name,isActive:l.isActive,totpEnabled:l.totpEnabled??!1,roles:d.map(u=>u.name),permissions:c.permissions})}),"POST /auth/logout":async(o,a)=>y(async()=>f(200,{success:!0})),"POST /auth/refresh":async(o,a)=>y(async()=>{let c=await P(o,e,t,n);if(!c)return m(401,"Unauthorized");let l=i.signToken({sub:c.sub,email:c.email,roles:c.roles,permissions:c.permissions,allowedTenants:c.allowedTenants});return f(200,{token:l})}),"GET /auth/oidc/authorize":async(o,a)=>y(async()=>{if(t.mode!=="oidc"||!t.oidc)return m(400,"OIDC not configured");let c=await An(t.oidc.issuer),l=or(rr(16)),d=or(rr(16)),{verifier:u,challenge:p}=qi();Xi(l,u,d);let g=Gi(t.oidc,c.authorization_endpoint,l,d,p);return new Response(null,{status:302,headers:{Location:g}})}),"GET /auth/oidc/callback":async(o,a)=>y(async()=>{if(t.mode!=="oidc"||!t.oidc)return m(400,"OIDC not configured");let c=new URL(o.url),l=c.searchParams.get("code"),d=c.searchParams.get("state");if(!l||!d)return m(400,"Missing code or state");let u=Zi(d);if(!u)return m(400,"Invalid or expired state");let p=await An(t.oidc.issuer),g=await Ki(t.oidc,p.token_endpoint,l,u.verifier),E=await Yi(g.id_token,p.jwks_uri),_=zi(E,t.oidc.claimsMapping),h=Qi(E,t.oidc.claimsMapping),w=await e.getUserByEmail(_);if(w||(w=await e.createUser({tenantId:"default",name:_,email:_,passwordHash:""})),h.length>0){let O=await e.getRoles("default");for(let b of h){let M=O.find(L=>L.name===b);M&&await e.assignRole(w.id,M.id).catch(()=>{})}}let R=await e.getUserRoles(w.id),I=[];for(let O of R){let b=await e.getPermissionsForRole(O.id);I.push(...b.map(M=>M.name))}let T=i.signToken({sub:w.id,email:w.email,roles:R.map(O=>O.name),permissions:[...new Set(I)]});return f(200,{token:T})}),"GET /auth/saml/metadata":async(o,a)=>y(async()=>{if(t.mode!=="saml"||!t.saml)return m(400,"SAML not configured");let c=er(t.saml);return new Response(c,{status:200,headers:{"Content-Type":"application/xml"}})}),"POST /auth/saml/acs":async(o,a)=>y(async()=>{let c=r(o);if(c)return c;if(t.mode!=="saml"||!t.saml)return m(400,"SAML not configured");let d=new URLSearchParams(await o.text()).get("SAMLResponse");if(typeof d!="string")return m(400,"Missing SAMLResponse");let u=await tr(d,t.saml);if(!u)return m(401,"SAML validation failed");let p=await e.getUserByEmail(u.email);p||(p=await e.createUser({tenantId:"default",name:u.email,email:u.email,passwordHash:""}));let g=await e.getUserRoles(p.id),E=[];for(let h of g){let w=await e.getPermissionsForRole(h.id);E.push(...w.map(R=>R.name))}let _=i.signToken({sub:p.id,email:p.email,roles:g.map(h=>h.name),permissions:[...new Set(E)]});return f(200,{token:_})}),"POST /auth/totp/setup":async(o,a)=>y(async()=>{let c=await P(o,e,t,n);if(!c)return m(401,"Unauthorized");let l=await e.getUserById(c.sub);if(!l)return m(401,"User not found");let d=nr(),u=ir(d,l.email);return await e.updateUser(l.id,{totpSecret:d,totpEnabled:!1}),f(200,{qrUrl:u,message:"Scan QR code, then verify with POST /auth/totp/verify"})}),"POST /auth/totp/verify":async(o,a)=>y(async()=>{let c=r(o);if(c)return c;let l=await P(o,e,t,n);if(!l)return m(401,"Unauthorized");let d=await e.getUserById(l.sub);if(!d)return m(401,"User not found");if(!d.totpSecret)return m(400,"TOTP not set up \u2014 call POST /auth/totp/setup first");let u=await v(o);if(!u||typeof u!="object")return m(400,"Invalid body");let p=u;return typeof p.token!="string"?m(400,"token is required"):sr(d.totpSecret,p.token)?(await e.updateUser(d.id,{totpEnabled:!0}),f(200,{success:!0,message:"TOTP enabled successfully"})):m(400,"Invalid TOTP token")}),"POST /auth/totp/disable":async(o,a)=>y(async()=>{let c=await P(o,e,t,n);if(!c)return m(401,"Unauthorized");let l=await e.getUserById(c.sub);return l?(await e.updateUser(l.id,{totpSecret:null,totpEnabled:!1}),f(200,{success:!0,message:"TOTP disabled"})):m(401,"User not found")})}}function cr(i){if(!i||typeof i!="object")return{valid:!1,error:"Request body must be a JSON object"};let e=i;if(typeof e.name!="string"||!e.name)return{valid:!1,error:"name is required"};if(typeof e.defaultLocale!="string"||!e.defaultLocale)return{valid:!1,error:"defaultLocale is required"};if(!e.profileJson||typeof e.profileJson!="object")return{valid:!1,error:"profileJson is required"};let t=e.profileJson;return typeof t.consentTemplateId!="string"||!t.consentTemplateId?{valid:!1,error:"profileJson.consentTemplateId is required"}:typeof t.uiTemplateId!="string"||!t.uiTemplateId?{valid:!1,error:"profileJson.uiTemplateId is required"}:{valid:!0}}function lr(i){return!i||typeof i!="object"?{valid:!1,error:"Request body must be a JSON object"}:{valid:!0}}function dr(i,e){return{tenantId:e,name:i.name,defaultLocale:i.defaultLocale,profileJson:i.profileJson,...i.localeContent!=null?{localeContent:i.localeContent}:{}}}function ur(i){let e={};return typeof i.name=="string"&&(e.name=i.name),typeof i.defaultLocale=="string"&&(e.defaultLocale=i.defaultLocale),i.profileJson!=null&&(e.profileJson=i.profileJson),i.localeContent!=null&&(e.localeContent=i.localeContent),e}function Ie(i,e,t){let n=Jn[t];if(!n)return{valid:!0,errors:[],warnings:[]};let s=[],r=[],o=Ae(e);for(let[a,c]of Object.entries(e)){let l=c;for(let d of n.categoryErrors)d.check(l,void 0)&&s.push({cookieId:a,field:d.field,message:d.message(a),rule:d.rule});for(let d of n.categoryWarnings)d.check(l,void 0)&&r.push({cookieId:a,field:d.field,message:d.message(a),suggestion:d.suggestion})}for(let[a,c]of Object.entries(i)){let l=c,d=Ze(a,o);for(let u of n.cookieErrors)u.check(l,d)&&s.push({cookieId:a,field:u.field,message:u.message(a),rule:u.rule});for(let u of n.cookieWarnings)u.check(l,d)&&r.push({cookieId:a,field:u.field,message:u.message(a),suggestion:u.suggestion})}return{valid:s.length===0,errors:s,warnings:r}}function pr(i,e,t){let n=[];en(t.htmlText)||n.push({locale:i,section:e,field:"htmlText",message:"Body text is required"});for(let[s,r]of Object.entries(t.buttons))r.text?.trim()||n.push({locale:i,section:e,field:`buttons.${s}`,message:`Label for button "${s}" is required`});return n}function Ec(i,e){let t=[];e.heading?.trim()||t.push({locale:i,section:"preferenceModal",field:"heading",message:"Heading is required"}),en(e.htmlText)||t.push({locale:i,section:"preferenceModal",field:"htmlText",message:"Intro text is required"});for(let[n,s]of Object.entries(e.buttons))s.text?.trim()||t.push({locale:i,section:"preferenceModal",field:`buttons.${n}`,message:`Label for button "${n}" is required`});for(let[n,s]of Object.entries(e.categories))s.heading?.trim()||t.push({locale:i,section:"preferenceModal",field:`categories.${n}.heading`,message:`Heading for category "${n}" is required`});return t}function Pn(i){let e=[];for(let[t,n]of Object.entries(i))e.push(...pr(t,"mainBanner",n.mainBanner)),n.gpcBanner&&e.push(...pr(t,"gpcBanner",n.gpcBanner)),e.push(...Ec(t,n.preferenceModal));return{valid:e.length===0,errors:e}}function mr(i,e){let t={...e};return i?.defaultLocale&&i.mainBanner&&i.preferenceModal&&(t[i.defaultLocale]={mainBanner:i.mainBanner,...i.gpcBanner?{gpcBanner:i.gpcBanner}:{},preferenceModal:i.preferenceModal}),t}function gr(i,e,t,n){async function s(o,a){let c=await P(o,e,t,n);return{user:c,denied:D(c,a)}}function r(o,a){return o?o.allowedTenants.length===0||o.allowedTenants.includes(a):!1}return{"GET /profiles":async(o,a)=>y(async()=>{let{user:c,denied:l}=await s(o,"profile:view");return l||(r(c,"default")?new URL(o.url).searchParams.get("summary")==="1"?f(200,await i.listSummary()):f(200,await i.list()):f(200,[]))}),"GET /profiles/archived":async(o,a)=>y(async()=>{let{denied:c}=await s(o,"profile:view");return c||f(200,await i.listArchivedProfiles())}),"GET /profiles/:id":async(o,a)=>y(async()=>{let{denied:c}=await s(o,"profile:view");if(c)return c;let l=await i.get(a.id??"");return l?f(200,l):m(404,"Profile not found")}),"POST /profiles/validate":async(o,a)=>y(async()=>{let{denied:c}=await s(o,"profile:view");if(c)return c;let l=await v(o);return!l.complianceGroup||!l.cookies||typeof l.cookies!="object"||!l.categories||typeof l.categories!="object"?m(400,"complianceGroup, cookies, and categories are required"):f(200,Ie(l.cookies,l.categories,l.complianceGroup))}),"GET /compliance-coverage":async(o,a)=>y(async()=>{let{denied:c}=await s(o,"profile:view");if(c)return c;let l={};for(let[d,u]of Object.entries(Xe)){let p=await i.findActiveByComplianceGroup(d);l[d]={label:u.label,compliances:u.compliances,activeProfile:p?{id:p.id,name:p.name}:null}}return f(200,{groups:l})}),"POST /profiles":async(o,a)=>y(async()=>{let{denied:c}=await s(o,"profile:create");if(c)return c;let l=await v(o),d=cr(l);if(!d.valid)return m(400,d.error??"Invalid body");let u=dr(l,"default"),p=Pn(mr(u.profileJson,u.localeContent));if(!p.valid)return m(422,"Profile content is missing required fields",{errors:p.errors});let g=u.profileJson.complianceGroup,E=u.profileJson.customComplianceGroup,_=typeof l.choice=="string"?l.choice:void 0;if(g){let w=u.profileJson.cookies??{},R=u.profileJson.preferenceModal?.categories??{},I=Ie(w,R,g);if(!I.valid)return m(422,"Profile does not meet compliance requirements",{errors:I.errors,warnings:I.warnings});let T=await i.findActiveByComplianceGroup(g);if(T){if(!_)return f(200,{conflict:{id:T.id,name:T.name},requiresChoice:!0});_==="deactivate"?(await i.deactivate(T.id),u.profileJson.isActive=!0):_==="inactive"&&(u.profileJson.isActive=!1)}let O=await i.create(u);return f(201,{profile:O,warnings:I.warnings})}if(E){let w=await i.findActiveByComplianceGroup(E);if(w){if(!_)return f(200,{conflict:{id:w.id,name:w.name},requiresChoice:!0});_==="deactivate"?(await i.deactivate(w.id),u.profileJson.isActive=!0):_==="inactive"&&(u.profileJson.isActive=!1)}}let h=await i.create(u);return f(201,h)}),"PUT /profiles/:id":async(o,a)=>y(async()=>{let{denied:c}=await s(o,"profile:update");if(c)return c;let l=a.id??"",d=await v(o),u=lr(d);if(!u.valid)return m(400,u.error??"Invalid body");let p=ur(d),g=mr(p.profileJson,p.localeContent);if(Object.keys(g).length>0){let R=Pn(g);if(!R.valid)return m(422,"Profile content is missing required fields",{errors:R.errors})}let E=p.profileJson?.complianceGroup,_=p.profileJson?.customComplianceGroup,h=typeof d.choice=="string"?d.choice:void 0;if(E){let R=p.profileJson?.cookies??{},I=p.profileJson?.preferenceModal?.categories??{},T=Ie(R,I,E);if(!T.valid)return m(422,"Profile does not meet compliance requirements",{errors:T.errors,warnings:T.warnings});let O=await i.findActiveByComplianceGroup(E);if(O&&O.id!==l){if(!h)return f(200,{conflict:{id:O.id,name:O.name},requiresChoice:!0});h==="deactivate"?(await i.deactivate(O.id),p.profileJson.isActive=!0):h==="inactive"&&(p.profileJson.isActive=!1)}let b=await i.update(l,p);return f(200,{profile:b,warnings:T.warnings})}if(_){let R=await i.findActiveByComplianceGroup(_);if(R&&R.id!==l){if(!h)return f(200,{conflict:{id:R.id,name:R.name},requiresChoice:!0});h==="deactivate"?(await i.deactivate(R.id),p.profileJson.isActive=!0):h==="inactive"&&(p.profileJson.isActive=!1)}}let w=await i.update(l,p);return f(200,w)}),"DELETE /profiles/:id":async(o,a)=>y(async()=>{let{denied:c}=await s(o,"profile:delete");return c||(await i.delete(a.id??""),f(200,{success:!0}))}),"POST /profiles/:id/copy":async(o,a)=>y(async()=>{let{denied:c}=await s(o,"profile:create");if(c)return c;let l=a.id??"";if(!await i.get(l))return m(404,"Profile not found");let u=await v(o),p=typeof u?.name=="string"?u.name:void 0,g=await i.copy(l,p);return f(201,g)}),"POST /profiles/:id/activate":async(o,a)=>y(async()=>{let{denied:c}=await s(o,"profile:update");if(c)return c;let l=a.id??"",d=await v(o),u=typeof d?.choice=="string"?d.choice:void 0,p=await i.get(l);if(!p)return m(404,"Profile not found");let g=p.profileJson,E=g.complianceGroup||g.customComplianceGroup;if(E){let h=await i.findActiveByComplianceGroup(E);if(h&&h.id!==l){if(!u)return f(200,{conflict:{id:h.id,name:h.name},requiresChoice:!0});u==="deactivate"&&await i.deactivate(h.id)}}let _=await i.activate(l);return f(200,_)}),"POST /profiles/:id/deactivate":async(o,a)=>y(async()=>{let{denied:c}=await s(o,"profile:update");if(c)return c;let l=await i.deactivate(a.id??"");return f(200,l)}),"GET /profiles/:id/versions":async(o,a)=>y(async()=>{let{denied:c}=await s(o,"profile:view");return c||f(200,await i.listVersions(a.id??""))}),"GET /profiles/:id/versions/:entryId":async(o,a)=>y(async()=>{let{denied:c}=await s(o,"profile:view");if(c)return c;let d=new URL(o.url).searchParams.get("locale")??"default",u=a.entryId??"";if(!u)return m(400,"Invalid version id");let p=await i.getVersionFile(a.id??"",u,d);return p?new Response(p,{headers:{"Content-Type":"application/json"}}):m(404,"Version file not found")})}}function fr(i,e,t){async function n(s,r){let o=await P(s,i,e,t);return{user:o,denied:D(o,r)}}return{"GET /consents":async(s,r)=>y(async()=>{let{user:o,denied:a}=await n(s,"consent:view");if(a)return a;if(o?.allowedTenants.length&&!o.allowedTenants.includes("default"))return f(200,{items:[],total:0,page:1,limit:50});let c=new URL(s.url),l=k(c,"profileId"),d=k(c,"from"),u=k(c,"to"),p=k(c,"q"),g=Ce(c);if("error"in g)return m(400,g.error);let{page:E,limit:_}=g,h={tenantId:"default",page:E,limit:_,...l!==void 0?{profileId:l}:{},...d!==void 0?{from:d}:{},...u!==void 0?{to:u}:{},...p!==void 0?{q:p}:{}},w=await i.getConsents(h);return f(200,w)}),"GET /consents/:visitorId":async(s,r)=>y(async()=>{let{user:o,denied:a}=await n(s,"consent:view");if(a)return a;if(o?.allowedTenants.length&&!o.allowedTenants.includes("default"))return m(403,"Forbidden");let c=await i.getConsent(r.visitorId??"");return c?f(200,c):m(404,"Consent not found")}),"GET /consents/:visitorId/history":async(s,r)=>y(async()=>{let{user:o,denied:a}=await n(s,"consent:view");if(a)return a;if(o?.allowedTenants.length&&!o.allowedTenants.includes("default"))return m(403,"Forbidden");let c=await i.getConsentHistory(r.visitorId??"");return f(200,c)})}}var Rc=!1;function _r(i,e,t){async function n(s,r){let o=await P(s,i,e,t);return{user:o,denied:D(o,r)}}return{"GET /visitors":async(s,r)=>y(async()=>{let{user:o,denied:a}=await n(s,"visitor:view");if(a)return a;if(o?.allowedTenants.length&&!o.allowedTenants.includes("default"))return f(200,{items:[],total:0,page:1,limit:50});let c=new URL(s.url),l=k(c,"from"),d=k(c,"to"),u=k(c,"q"),p=Ce(c);if("error"in p)return m(400,p.error);let{page:g,limit:E}=p,_={tenantId:"default",page:g,limit:E,...l!==void 0?{from:l}:{},...d!==void 0?{to:d}:{},...u!==void 0?{q:u}:{}},h=await i.getVisitors(_);return f(200,h)}),"GET /visitors/:visitorId/notice-shown":async(s,r)=>y(async()=>{if(!Rc)return m(404,"Not found");let{denied:o}=await n(s,"visitor:view");if(o)return o;let a=await i.getNoticeShownForVisitor(r.visitorId??"");return f(200,a)})}}var hr="role_super_admin";function yr(i,e,t,n){async function s(o,a){let c=await P(o,e,t,n);return{user:c,denied:D(c,a)}}async function r(o){return(await i.getRoles(o)).some(c=>c.id===hr)}return{"GET /users":async(o,a)=>y(async()=>{let{denied:c}=await s(o,"user:view");if(c)return c;let l=await i.list(),d=await Promise.all(l.map(async u=>({...u,roles:(await i.getRoles(u.id)).map(p=>({id:p.id,name:p.name}))})));return f(200,d)}),"GET /users/:id":async(o,a)=>y(async()=>{let{denied:c}=await s(o,"user:view");if(c)return c;let l=await i.get(a.id??"");if(!l)return m(404,"User not found");let d=await i.getRoles(l.id);return f(200,{...l,roles:d.map(u=>({id:u.id,name:u.name}))})}),"POST /users":async(o,a)=>y(async()=>{let{user:c,denied:l}=await s(o,"user:create");if(l)return l;let d=await v(o);if(!d||typeof d!="object")return m(400,"Invalid body");let u=d;if(typeof u.name!="string"||!u.name)return m(400,"name is required");if(typeof u.email!="string"||!u.email)return m(400,"email is required");if(typeof u.password!="string"||!u.password)return m(400,"password is required");if(u.password.length<12)return m(400,"password must be at least 12 characters");let p=await i.create({name:u.name,email:u.email,password:u.password,...typeof u.roleId=="string"?{roleId:u.roleId}:{},...Array.isArray(u.allowedTenants)?{allowedTenants:u.allowedTenants}:{}},c.sub);return f(201,p)}),"PUT /users/:id":async(o,a)=>y(async()=>{let{user:c,denied:l}=await s(o,"user:update");if(l)return l;let d=a.id??"";if(d!==c.sub&&await r(d))return m(403,"Super admin users cannot be modified");let u=await v(o);if(!u||typeof u!="object")return m(400,"Invalid body");let p=u;if(typeof p.password=="string"&&p.password.length<12)return m(400,"password must be at least 12 characters");let g=await i.update(d,{...typeof p.name=="string"?{name:p.name}:{},...typeof p.email=="string"?{email:p.email}:{},...p.isActive!=null?{isActive:!!p.isActive}:{},...typeof p.password=="string"?{password:p.password}:{},...Array.isArray(p.allowedTenants)?{allowedTenants:p.allowedTenants}:{}},c.sub);return f(200,g)}),"DELETE /users/:id":async(o,a)=>y(async()=>{let{user:c,denied:l}=await s(o,"user:delete");if(l)return l;let d=a.id??"";return d===c.sub?m(400,"Cannot delete your own account"):await r(d)?m(403,"Super admin users cannot be deleted"):(await i.delete(d,c.sub),f(200,{success:!0}))}),"POST /users/:id/roles":async(o,a)=>y(async()=>{let{user:c,denied:l}=await s(o,"role:update");if(l)return l;let d=await v(o);if(!d||typeof d!="object")return m(400,"Invalid body");let u=d;if(typeof u.roleId!="string")return m(400,"roleId is required");let p=typeof u.tenantId=="string"?u.tenantId:void 0;return await i.assignRole(a.id??"",u.roleId,p),await e.createLog({tenantId:"default",userId:c.sub,action:"user.role_assigned",resourceType:"user",...a.id!=null?{resourceId:a.id}:{},newData:{roleId:u.roleId}}),f(200,{success:!0})}),"DELETE /users/:id/roles/:roleId":async(o,a)=>y(async()=>{let{user:c,denied:l}=await s(o,"role:update");if(l)return l;if(a.roleId===hr)return m(403,"The super_admin role cannot be revoked");let u=new URL(o.url).searchParams.get("tenantId")??void 0;return await i.revokeRole(a.id??"",a.roleId??"",u),await e.createLog({tenantId:"default",userId:c.sub,action:"user.role_revoked",resourceType:"user",...a.id!=null?{resourceId:a.id}:{},oldData:{roleId:a.roleId}}),f(200,{success:!0})})}}var qt="role_super_admin";function wr(i,e,t){async function n(s,r){let o=await P(s,i,e,t);return{user:o,denied:D(o,r)}}return{"GET /roles":async(s,r)=>y(async()=>{let{denied:o}=await n(s,"role:view");if(o)return o;let a=await i.getRoles("default");return f(200,a)}),"GET /roles/:id/permissions":async(s,r)=>y(async()=>{let{denied:o}=await n(s,"role:view");if(o)return o;let a=await i.getPermissionsForRole(r.id??"");return f(200,a)}),"POST /roles":async(s,r)=>y(async()=>{let{user:o,denied:a}=await n(s,"role:create");if(a)return a;let c=await v(s);if(!c||typeof c!="object")return m(400,"Invalid body");let l=c;if(typeof l.name!="string"||!l.name)return m(400,"name is required");let d=await i.createRole({tenantId:"default",name:l.name,...typeof l.description=="string"?{description:l.description}:{}});return await i.createLog({tenantId:"default",userId:o.sub,action:"role.created",resourceType:"role",resourceId:d.id,newData:d}),f(201,d)}),"PUT /roles/:id":async(s,r)=>y(async()=>{let{user:o,denied:a}=await n(s,"role:update");if(a)return a;if(r.id===qt)return m(403,"The super_admin role cannot be modified");let c=await v(s);if(!c||typeof c!="object")return m(400,"Invalid body");let l=c,d=await i.updateRole(r.id??"",{...typeof l.name=="string"?{name:l.name}:{},...typeof l.description=="string"?{description:l.description}:{}});return await i.createLog({tenantId:"default",userId:o.sub,action:"role.updated",resourceType:"role",resourceId:d.id,newData:d}),f(200,d)}),"DELETE /roles/:id":async(s,r)=>y(async()=>{let{user:o,denied:a}=await n(s,"role:delete");return a||(r.id===qt?m(403,"The super_admin role cannot be deleted"):(await i.deleteRole(r.id??""),await i.createLog({tenantId:"default",userId:o.sub,action:"role.deleted",resourceType:"role",...r.id!=null?{resourceId:r.id}:{}}),f(200,{success:!0})))}),"GET /permissions":async(s,r)=>y(async()=>{let{denied:o}=await n(s,"role:view");if(o)return o;let a=await i.getAllPermissions();return f(200,a)}),"POST /roles/:id/permissions":async(s,r)=>y(async()=>{let{denied:o}=await n(s,"role:update");if(o)return o;if(r.id===qt)return m(403,"The super_admin role cannot be modified");let a=await v(s);if(!a||typeof a!="object")return m(400,"Invalid body");let c=a;return typeof c.permissionId!="string"?m(400,"permissionId is required"):(await i.assignPermissionToRole(r.id??"",c.permissionId),f(200,{success:!0}))}),"DELETE /roles/:id/permissions/:permissionId":async(s,r)=>y(async()=>{let{denied:o}=await n(s,"role:update");return o||(r.id===qt?m(403,"The super_admin role cannot be modified"):(await i.revokePermissionFromRole(r.id??"",r.permissionId??""),f(200,{success:!0})))})}}function Er(i,e,t){async function n(s,r){let o=await P(s,i,e,t);return{user:o,denied:D(o,r)}}return{"GET /audit":async(s,r)=>y(async()=>{let{user:o,denied:a}=await n(s,"audit:view");if(a)return a;if(o?.allowedTenants.length&&!o.allowedTenants.includes("default"))return f(200,{items:[],total:0,page:1,limit:50});let c=new URL(s.url),l=k(c,"action"),d=k(c,"resourceType"),u=k(c,"from"),p=k(c,"to"),g=k(c,"q"),E=Ce(c);if("error"in E)return m(400,E.error);let{page:_,limit:h}=E,w={tenantId:"default",page:_,limit:h,...l!==void 0?{action:l}:{},...d!==void 0?{resourceType:d}:{},...u!==void 0?{from:u}:{},...p!==void 0?{to:p}:{},...g!==void 0?{q:g}:{}},R=await i.getLogs(w);return f(200,R)}),"GET /audit/:id":async(s,r)=>y(async()=>{let{user:o,denied:a}=await n(s,"audit:view");if(a)return a;if(o?.allowedTenants.length&&!o.allowedTenants.includes("default"))return m(403,"Forbidden");let c=await i.getAuditLogById(r.id??"");return!c||c.tenantId!=="default"?m(404,"Audit log entry not found"):f(200,c)})}}function Rr(i,e,t,n){async function s(r){let o=await P(r,i,t,n);return{denied:D(o,"consent:view")}}return{"GET /stats/overview":async(r,o)=>y(async()=>{let{denied:a}=await s(r);if(a)return a;let c=await e.getOverview("default");return f(200,c)}),"GET /stats/categories":async(r,o)=>y(async()=>{let{denied:a}=await s(r);if(a)return a;let c=await e.getCategories("default");return f(200,c)}),"GET /stats/timeline":async(r,o)=>y(async()=>{let{denied:a}=await s(r);if(a)return a;let c=new URL(r.url),l=Ft(c,"days",30),d=await e.getTimeline("default",l);return f(200,d)}),"GET /stats/countries":async(r,o)=>y(async()=>{let{denied:a}=await s(r);if(a)return a;let c=await e.getCountries("default");return f(200,c)}),"GET /stats/gpc":async(r,o)=>y(async()=>{let{denied:a}=await s(r);if(a)return a;let c=await e.getGpc("default");return f(200,c)}),"GET /stats/aggregate":async(r,o)=>y(async()=>{let{denied:a}=await s(r);if(a)return a;let c=await e.getTenants(),l=await Promise.all(c.map(async u=>{let p=await e.getOverview(u.id);return{tenantId:u.id,tenantName:u.name,...p}})),d=l.reduce((u,p)=>({totalConsents:u.totalConsents+p.totalConsents,totalVisitors:u.totalVisitors+p.totalVisitors,gpcDetectedCount:u.gpcDetectedCount+p.gpcDetectedCount}),{totalConsents:0,totalVisitors:0,gpcDetectedCount:0});return f(200,{aggregate:d,perTenant:l})})}}function On(i){let e=i==null?"":String(i);return e.includes(",")||e.includes('"')||e.includes(`
152
- `)?`"${e.replace(/"/g,'""')}"`:e}function Sc(i){return[i.id,i.visitorId,i.profileId,i.locale,JSON.stringify(i.consentJson),i.gpcDetected?"1":"0",i.source,i.signature??"",i.createdAt,i.updatedAt].map(On).join(",")+`
153
- `}function Cc(i){return[i.id,i.userId??"",i.action,i.resourceType,i.resourceId??"",i.createdAt].map(On).join(",")+`
154
- `}function Sr(i,e,t,n){async function s(r){let o=await P(r,i,e,t);return{denied:D(o,"export:run")}}return{"GET /export/consents":async(r,o)=>y(async()=>{let{denied:a}=await s(r);if(a)return a;let c=new URL(r.url),l=k(c,"format")??"csv",d=k(c,"profileId"),u=k(c,"from"),p=k(c,"to"),g={tenantId:"default",...d!==void 0?{profileId:d}:{},...u!==void 0?{from:u}:{},...p!==void 0?{to:p}:{}};if(l==="json"){let w=[];for await(let R of i.streamConsents(g))w.push(R);return new Response(JSON.stringify(w),{status:200,headers:{"Content-Type":"application/json","Content-Disposition":'attachment; filename="consents.json"'}})}let E=new TextEncoder,_=`id,visitor_id,profile_id,locale,consent_json,gpc_detected,source,signature,created_at,updated_at
155
- `,h=new ReadableStream({async start(w){w.enqueue(E.encode(_));for await(let R of i.streamConsents(g))w.enqueue(E.encode(Sc(R)));w.close()}});return new Response(h,{status:200,headers:{"Content-Type":"text/csv; charset=utf-8","Content-Disposition":'attachment; filename="consents.csv"'}})}),"GET /export/consents/xlsx":async(r,o)=>y(async()=>{let{denied:a}=await s(r);if(a)return a;let c=null;try{c=await import("xlsx")}catch{return new Response(JSON.stringify({error:"xlsx peer dependency not installed. Run: npm install xlsx"}),{status:501,headers:{"Content-Type":"application/json"}})}if(!c)return new Response(null,{status:501});let l=new URL(r.url),d=l.searchParams.get("profileId")??void 0,u=l.searchParams.get("from")??void 0,p=l.searchParams.get("to")??void 0,g={tenantId:"default",...d!==void 0?{profileId:d}:{},...u!==void 0?{from:u}:{},...p!==void 0?{to:p}:{}},E=[];for await(let R of i.streamConsents(g))E.push({id:R.id,visitor_id:R.visitorId,profile_id:R.profileId,locale:R.locale,consent_json:JSON.stringify(R.consentJson),gpc_detected:R.gpcDetected?1:0,age_verified:R.ageVerified?1:0,tcf_string:R.tcfString??"",signature:R.signature??"",source:R.source,created_at:R.createdAt,updated_at:R.updatedAt});let _=c.utils.book_new(),h=c.utils.json_to_sheet(E);c.utils.book_append_sheet(_,h,"Consents");let w=c.write(_,{type:"buffer",bookType:"xlsx"});return new Response(w,{status:200,headers:{"Content-Type":"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet","Content-Disposition":'attachment; filename="consents.xlsx"'}})}),"GET /export/translations/:profileId":async(r,o)=>y(async()=>{let{denied:a}=await s(r);if(a)return a;let c=o.profileId??"",l=await i.getProfile(c);if(!l)return m(404,"Profile not found");let d=l.profileJson,u=d.locales?.length?d.locales:[l.defaultLocale],p={};for(let E of u){if(E===l.defaultLocale){p[E]={mainBanner:d.mainBanner,...d.gpcBanner?{gpcBanner:d.gpcBanner}:{},preferenceModal:d.preferenceModal};continue}let _=await n.getVersionFile(c,String(l.version),E);if(_)try{let h=JSON.parse(_);p[E]={mainBanner:h.mainBanner,...h.gpcBanner?{gpcBanner:h.gpcBanner}:{},preferenceModal:h.preferenceModal}}catch{}}if(Object.keys(p).length===0)return new Response(`locale,field,value
156
- `,{status:200,headers:{"Content-Type":"text/csv; charset=utf-8","Content-Disposition":`attachment; filename="${c}-translations.csv"`}});let g=["locale,section,field,value"];for(let[E,_]of Object.entries(p))for(let[h,w]of Object.entries(_))if(typeof w=="object"&&w!==null)for(let[R,I]of Object.entries(w))typeof I=="string"&&g.push([E,h,R,I].map(On).join(","));return new Response(g.join(`
153
+ </EntityDescriptor>`}async function Ar(i,e){let t;try{t=await import("samlify")}catch{return console.warn("[Consenti] samlify is not installed. Install it to use SAML auth mode."),null}try{let n=t.ServiceProvider({entityID:"saml:sp:consenti",assertionConsumerService:[{Binding:"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST",Location:e.callbackUrl}]}),s=t.IdentityProvider({entityID:e.issuer,singleSignOnService:[{Binding:"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect",Location:e.entryPoint}],signingCert:e.cert}),r=await n.parseLoginResponse(s,"post",{body:{SAMLResponse:i}}),o=r.extract.nameID,a=r.extract.attributes.roles,c=Array.isArray(a)?a.filter(l=>typeof l=="string"):[];return{email:o,roles:c}}catch(n){return console.warn("[Consenti] SAML response validation failed:",n),null}}import{createHmac as Jc,randomBytes as Gc,timingSafeEqual as qc}from"crypto";var Vn="ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";function Kc(i){let e="",t=0,n=0;for(let s of i)for(n=n<<8|s,t+=8;t>=5;)e+=Vn[n>>>t-5&31],t-=5;return t>0&&(e+=Vn[n<<5-t&31]),e}function Yc(i){let e=i.toUpperCase().replace(/[^A-Z2-7]/g,""),t=[],n=0,s=0;for(let r of e){let o=Vn.indexOf(r);o<0||(s=s<<5|o,n+=5,n>=8&&(t.push(s>>>n-8&255),n-=8))}return Buffer.from(t)}function zc(i,e){let t=Buffer.alloc(8),n=e;for(let a=7;a>=0;a--)t[a]=n&255,n=Math.floor(n/256);let s=Jc("sha1",i).update(t).digest(),r=s[s.length-1]&15,o=(s[r]&127)<<24|(s[r+1]&255)<<16|(s[r+2]&255)<<8|s[r+3]&255;return String(o%1e6).padStart(6,"0")}function vr(){return Kc(Gc(20))}var Xt=new Map;setInterval(()=>{let i=Date.now();for(let[e,t]of Xt)i>=t&&Xt.delete(e)},9e4).unref();function Pr(i,e,t=1){if(Xt.has(e))return!1;let n=Math.floor(Date.now()/3e4),s=Yc(i),r=Buffer.from(e);for(let o=-t;o<=t;o++){let a=Buffer.from(zc(s,n+o));if(a.length===r.length&&qc(a,r))return Xt.set(e,Date.now()+9e4),!0}return!1}function Or(i,e,t="Consenti"){let n=encodeURIComponent(`${t}:${e}`),s=new URLSearchParams({secret:i,issuer:t,algorithm:"SHA1",digits:"6",period:"30"});return`otpauth://totp/${n}?${s.toString()}`}function Dr(i){return i.toString("base64").replace(/\+/g,"-").replace(/\//g,"_").replace(/=/g,"")}function Lr(i,e,t,n,s){function r(o){if(!s)return null;let a=o.headers.get("x-real-ip")??"";return s.checkRequest(a)?null:m(429,"Too Many Requests")}return{"POST /auth/login":async(o,a)=>h(async()=>{let c=r(o);if(c)return c;if(t.mode!=="local")return m(400,"Login not available in this auth mode");let l=await P(o);if(!l||typeof l!="object")return m(400,"Invalid body");let d=l;if(typeof d.email!="string"||typeof d.password!="string")return m(400,"email and password are required");let u=await i.login(d.email,d.password);return u?_(200,{token:u}):m(401,"Invalid credentials")}),"GET /auth/me":async(o,a)=>h(async()=>{let c=await O(o,e,t,n);if(!c)return m(401,"Unauthorized");let l=await e.getUserById(c.sub);if(!l)return m(401,"User not found");let d=await e.getUserRoles(l.id);return _(200,{id:l.id,email:l.email,name:l.name,isActive:l.isActive,totpEnabled:l.totpEnabled??!1,roles:d.map(u=>u.name),permissions:c.permissions})}),"POST /auth/logout":async(o,a)=>h(async()=>_(200,{success:!0})),"POST /auth/refresh":async(o,a)=>h(async()=>{let c=await O(o,e,t,n);if(!c)return m(401,"Unauthorized");let l=i.signToken({sub:c.sub,email:c.email,roles:c.roles,permissions:c.permissions,allowedTenants:c.allowedTenants});return _(200,{token:l})}),"GET /auth/oidc/authorize":async(o,a)=>h(async()=>{if(t.mode!=="oidc"||!t.oidc)return m(400,"OIDC not configured");let c=await Bn(t.oidc.issuer),l=Dr(br(16)),d=Dr(br(16)),{verifier:u,challenge:p}=hr();Cr(l,u,d);let g=yr(t.oidc,c.authorization_endpoint,l,d,p);return new Response(null,{status:302,headers:{Location:g}})}),"GET /auth/oidc/callback":async(o,a)=>h(async()=>{if(t.mode!=="oidc"||!t.oidc)return m(400,"OIDC not configured");let c=new URL(o.url),l=c.searchParams.get("code"),d=c.searchParams.get("state");if(!l||!d)return m(400,"Missing code or state");let u=Ir(d);if(!u)return m(400,"Invalid or expired state");let p=await Bn(t.oidc.issuer),g=await wr(t.oidc,p.token_endpoint,l,u.verifier),E=await Er(g.id_token,p.jwks_uri),f=Rr(E,t.oidc.claimsMapping),y=Sr(E,t.oidc.claimsMapping),w=await e.getUserByEmail(f);if(w||(w=await e.createUser({tenantId:"default",name:f,email:f,passwordHash:""})),y.length>0){let v=await e.getRoles("default");for(let N of y){let L=v.find(U=>U.name===N);L&&await e.assignRole(w.id,L.id).catch(()=>{})}}let R=await e.getUserRoles(w.id),I=[];for(let v of R){let N=await e.getPermissionsForRole(v.id);I.push(...N.map(L=>L.name))}let T=i.signToken({sub:w.id,email:w.email,roles:R.map(v=>v.name),permissions:[...new Set(I)]});return _(200,{token:T})}),"GET /auth/saml/metadata":async(o,a)=>h(async()=>{if(t.mode!=="saml"||!t.saml)return m(400,"SAML not configured");let c=Tr(t.saml);return new Response(c,{status:200,headers:{"Content-Type":"application/xml"}})}),"POST /auth/saml/acs":async(o,a)=>h(async()=>{let c=r(o);if(c)return c;if(t.mode!=="saml"||!t.saml)return m(400,"SAML not configured");let d=new URLSearchParams(await o.text()).get("SAMLResponse");if(typeof d!="string")return m(400,"Missing SAMLResponse");let u=await Ar(d,t.saml);if(!u)return m(401,"SAML validation failed");let p=await e.getUserByEmail(u.email);p||(p=await e.createUser({tenantId:"default",name:u.email,email:u.email,passwordHash:""}));let g=await e.getUserRoles(p.id),E=[];for(let y of g){let w=await e.getPermissionsForRole(y.id);E.push(...w.map(R=>R.name))}let f=i.signToken({sub:p.id,email:p.email,roles:g.map(y=>y.name),permissions:[...new Set(E)]});return _(200,{token:f})}),"POST /auth/totp/setup":async(o,a)=>h(async()=>{let c=await O(o,e,t,n);if(!c)return m(401,"Unauthorized");let l=await e.getUserById(c.sub);if(!l)return m(401,"User not found");let d=vr(),u=Or(d,l.email);return await e.updateUser(l.id,{totpSecret:d,totpEnabled:!1}),_(200,{qrUrl:u,message:"Scan QR code, then verify with POST /auth/totp/verify"})}),"POST /auth/totp/verify":async(o,a)=>h(async()=>{let c=r(o);if(c)return c;let l=await O(o,e,t,n);if(!l)return m(401,"Unauthorized");let d=await e.getUserById(l.sub);if(!d)return m(401,"User not found");if(!d.totpSecret)return m(400,"TOTP not set up \u2014 call POST /auth/totp/setup first");let u=await P(o);if(!u||typeof u!="object")return m(400,"Invalid body");let p=u;return typeof p.token!="string"?m(400,"token is required"):Pr(d.totpSecret,p.token)?(await e.updateUser(d.id,{totpEnabled:!0}),_(200,{success:!0,message:"TOTP enabled successfully"})):m(400,"Invalid TOTP token")}),"POST /auth/totp/disable":async(o,a)=>h(async()=>{let c=await O(o,e,t,n);if(!c)return m(401,"Unauthorized");let l=await e.getUserById(c.sub);return l?(await e.updateUser(l.id,{totpSecret:null,totpEnabled:!1}),_(200,{success:!0,message:"TOTP disabled"})):m(401,"User not found")})}}function Ur(i){let e=i.profileJson&&typeof i.profileJson=="object"?i.profileJson:{},t=i.localeContent&&typeof i.localeContent=="object"?Object.keys(i.localeContent):[];return Lt({defaultLocale:i.defaultLocale,complianceGroup:e.complianceGroup,customComplianceGroup:e.customComplianceGroup,locales:e.locales,localeContentKeys:t})}function Nr(i){if(!i||typeof i!="object")return{valid:!1,error:"Request body must be a JSON object"};let e=i;if(typeof e.name!="string"||!e.name)return{valid:!1,error:"name is required"};if(typeof e.defaultLocale!="string"||!e.defaultLocale)return{valid:!1,error:"defaultLocale is required"};if(!e.profileJson||typeof e.profileJson!="object")return{valid:!1,error:"profileJson is required"};let t=e.profileJson;if(typeof t.consentTemplateId!="string"||!t.consentTemplateId)return{valid:!1,error:"profileJson.consentTemplateId is required"};if(typeof t.uiTemplateId!="string"||!t.uiTemplateId)return{valid:!1,error:"profileJson.uiTemplateId is required"};let n=Ur(e);return n?{valid:!1,error:n}:{valid:!0}}function kr(i){if(!i||typeof i!="object")return{valid:!1,error:"Request body must be a JSON object"};let e=Ur(i);return e?{valid:!1,error:e}:{valid:!0}}function Mr(i,e){return{tenantId:e,name:i.name,defaultLocale:i.defaultLocale,profileJson:i.profileJson,...i.localeContent!=null?{localeContent:i.localeContent}:{}}}function xr(i){let e={};return typeof i.name=="string"&&(e.name=i.name),typeof i.defaultLocale=="string"&&(e.defaultLocale=i.defaultLocale),i.profileJson!=null&&(e.profileJson=i.profileJson),i.localeContent!=null&&(e.localeContent=i.localeContent),e}function Oe(i,e,t){let n=as[t];if(!n)return{valid:!0,errors:[],warnings:[]};let s=[],r=[],o=De(e);for(let[a,c]of Object.entries(e)){let l=c;for(let d of n.categoryErrors)d.check(l,void 0)&&s.push({cookieId:a,field:d.field,message:d.message(a),rule:d.rule});for(let d of n.categoryWarnings)d.check(l,void 0)&&r.push({cookieId:a,field:d.field,message:d.message(a),suggestion:d.suggestion})}for(let[a,c]of Object.entries(i)){let l=c,d=rt(a,o);for(let u of n.cookieErrors)u.check(l,d)&&s.push({cookieId:a,field:u.field,message:u.message(a),rule:u.rule});for(let u of n.cookieWarnings)u.check(l,d)&&r.push({cookieId:a,field:u.field,message:u.message(a),suggestion:u.suggestion})}return{valid:s.length===0,errors:s,warnings:r}}function jr(i,e,t){let n=[];cn(t.htmlText)||n.push({locale:i,section:e,field:"htmlText",message:"Body text is required"});for(let[s,r]of Object.entries(t.buttons))r.text?.trim()||n.push({locale:i,section:e,field:`buttons.${s}`,message:`Label for button "${s}" is required`});return n}function Qc(i,e){let t=[];e.heading?.trim()||t.push({locale:i,section:"preferenceModal",field:"heading",message:"Heading is required"}),cn(e.htmlText)||t.push({locale:i,section:"preferenceModal",field:"htmlText",message:"Intro text is required"});for(let[n,s]of Object.entries(e.buttons))s.text?.trim()||t.push({locale:i,section:"preferenceModal",field:`buttons.${n}`,message:`Label for button "${n}" is required`});for(let[n,s]of Object.entries(e.categories))s.heading?.trim()||t.push({locale:i,section:"preferenceModal",field:`categories.${n}.heading`,message:`Heading for category "${n}" is required`});return t}function Wn(i){let e=[];for(let[t,n]of Object.entries(i))e.push(...jr(t,"mainBanner",n.mainBanner)),n.gpcBanner&&e.push(...jr(t,"gpcBanner",n.gpcBanner)),e.push(...Qc(t,n.preferenceModal));return{valid:e.length===0,errors:e}}function $r(i,e){let t={...e};return i?.defaultLocale&&i.mainBanner&&i.preferenceModal&&(t[i.defaultLocale]={mainBanner:i.mainBanner,...i.gpcBanner?{gpcBanner:i.gpcBanner}:{},preferenceModal:i.preferenceModal}),t}function Fr(i,e,t,n){async function s(o,a){let c=await O(o,e,t,n);return{user:c,denied:D(c,a)}}function r(o,a){return o?o.allowedTenants.length===0||o.allowedTenants.includes(a):!1}return{"GET /profiles":async(o,a)=>h(async()=>{let{user:c,denied:l}=await s(o,"profile:view");return l||(r(c,"default")?new URL(o.url).searchParams.get("summary")==="1"?_(200,await i.listSummary()):_(200,await i.list()):_(200,[]))}),"GET /profiles/archived":async(o,a)=>h(async()=>{let{denied:c}=await s(o,"profile:view");return c||_(200,await i.listArchivedProfiles())}),"GET /profiles/:id":async(o,a)=>h(async()=>{let{denied:c}=await s(o,"profile:view");if(c)return c;let l=await i.get(a.id??"");return l?_(200,l):m(404,"Profile not found")}),"POST /profiles/validate":async(o,a)=>h(async()=>{let{denied:c}=await s(o,"profile:view");if(c)return c;let l=await P(o);return!l.complianceGroup||!l.cookies||typeof l.cookies!="object"||!l.categories||typeof l.categories!="object"?m(400,"complianceGroup, cookies, and categories are required"):_(200,Oe(l.cookies,l.categories,l.complianceGroup))}),"GET /compliance-coverage":async(o,a)=>h(async()=>{let{denied:c}=await s(o,"profile:view");if(c)return c;let l={};for(let[d,u]of Object.entries(it)){let p=await i.findActiveByComplianceGroup(d);l[d]={label:u.label,compliances:u.compliances,activeProfile:p?{id:p.id,name:p.name}:null}}return _(200,{groups:l})}),"POST /profiles":async(o,a)=>h(async()=>{let{user:c,denied:l}=await s(o,"profile:create");if(l)return l;let d=await P(o),u=Nr(d);if(!u.valid)return m(400,u.error??"Invalid body");let p=Mr(d,"default"),g=Wn($r(p.profileJson,p.localeContent));if(!g.valid)return m(422,"Profile content is missing required fields",{errors:g.errors});let E=p.profileJson.complianceGroup,f=p.profileJson.customComplianceGroup,y=typeof d.choice=="string"?d.choice:void 0;if(E){let R=p.profileJson.cookies??{},I=p.profileJson.preferenceModal?.categories??{},T=Oe(R,I,E);if(!T.valid)return m(422,"Profile does not meet compliance requirements",{errors:T.errors,warnings:T.warnings});let v=await i.findActiveByComplianceGroup(E);if(v){if(!y)return _(200,{conflict:{id:v.id,name:v.name},requiresChoice:!0});y==="deactivate"?(await i.deactivate(v.id,c?.sub),p.profileJson.isActive=!0):y==="inactive"&&(p.profileJson.isActive=!1)}let N=await i.create(p,c?.sub);return _(201,{profile:N,warnings:T.warnings})}if(f){let R=await i.findActiveByComplianceGroup(f);if(R){if(!y)return _(200,{conflict:{id:R.id,name:R.name},requiresChoice:!0});y==="deactivate"?(await i.deactivate(R.id,c?.sub),p.profileJson.isActive=!0):y==="inactive"&&(p.profileJson.isActive=!1)}}let w=await i.create(p,c?.sub);return _(201,w)}),"PUT /profiles/:id":async(o,a)=>h(async()=>{let{user:c,denied:l}=await s(o,"profile:update");if(l)return l;let d=a.id??"",u=await P(o),p=kr(u);if(!p.valid)return m(400,p.error??"Invalid body");let g=xr(u),E=$r(g.profileJson,g.localeContent);if(Object.keys(E).length>0){let I=Wn(E);if(!I.valid)return m(422,"Profile content is missing required fields",{errors:I.errors})}let f=g.profileJson?.complianceGroup,y=g.profileJson?.customComplianceGroup,w=typeof u.choice=="string"?u.choice:void 0;if(f){let I=g.profileJson?.cookies??{},T=g.profileJson?.preferenceModal?.categories??{},v=Oe(I,T,f);if(!v.valid)return m(422,"Profile does not meet compliance requirements",{errors:v.errors,warnings:v.warnings});let N=await i.findActiveByComplianceGroup(f);if(N&&N.id!==d){if(!w)return _(200,{conflict:{id:N.id,name:N.name},requiresChoice:!0});w==="deactivate"?(await i.deactivate(N.id,c?.sub),g.profileJson.isActive=!0):w==="inactive"&&(g.profileJson.isActive=!1)}let L=await i.update(d,g,c?.sub);return _(200,{profile:L,warnings:v.warnings})}if(y){let I=await i.findActiveByComplianceGroup(y);if(I&&I.id!==d){if(!w)return _(200,{conflict:{id:I.id,name:I.name},requiresChoice:!0});w==="deactivate"?(await i.deactivate(I.id,c?.sub),g.profileJson.isActive=!0):w==="inactive"&&(g.profileJson.isActive=!1)}}let R=await i.update(d,g,c?.sub);return _(200,R)}),"DELETE /profiles/:id":async(o,a)=>h(async()=>{let{user:c,denied:l}=await s(o,"profile:delete");return l||(await i.delete(a.id??"",c?.sub),_(200,{success:!0}))}),"POST /profiles/:id/copy":async(o,a)=>h(async()=>{let{user:c,denied:l}=await s(o,"profile:create");if(l)return l;let d=a.id??"";if(!await i.get(d))return m(404,"Profile not found");let p=await P(o),g=typeof p?.name=="string"?p.name:void 0,E=await i.copy(d,g,c?.sub);return _(201,E)}),"POST /profiles/:id/activate":async(o,a)=>h(async()=>{let{user:c,denied:l}=await s(o,"profile:update");if(l)return l;let d=a.id??"",u=await P(o),p=typeof u?.choice=="string"?u.choice:void 0,g=await i.get(d);if(!g)return m(404,"Profile not found");let E=g.profileJson,f=E.complianceGroup||E.customComplianceGroup;if(f){let w=await i.findActiveByComplianceGroup(f);if(w&&w.id!==d){if(!p)return _(200,{conflict:{id:w.id,name:w.name},requiresChoice:!0});p==="deactivate"&&await i.deactivate(w.id,c?.sub)}}let y=await i.activate(d,c?.sub);return _(200,y)}),"POST /profiles/:id/deactivate":async(o,a)=>h(async()=>{let{user:c,denied:l}=await s(o,"profile:update");if(l)return l;let d=await i.deactivate(a.id??"",c?.sub);return _(200,d)}),"GET /profiles/:id/versions":async(o,a)=>h(async()=>{let{denied:c}=await s(o,"profile:view");return c||_(200,await i.listVersions(a.id??""))}),"GET /profiles/:id/versions/:entryId":async(o,a)=>h(async()=>{let{denied:c}=await s(o,"profile:view");if(c)return c;let d=new URL(o.url).searchParams.get("locale")??"default",u=a.entryId??"";if(!u)return m(400,"Invalid version id");let p=await i.getVersionFile(a.id??"",u,d);return p?new Response(p,{headers:{"Content-Type":"application/json"}}):m(404,"Version file not found")})}}function Hr(i,e,t){async function n(s,r){let o=await O(s,i,e,t);return{user:o,denied:D(o,r)}}return{"GET /consents":async(s,r)=>h(async()=>{let{user:o,denied:a}=await n(s,"consent:view");if(a)return a;if(o?.allowedTenants.length&&!o.allowedTenants.includes("default"))return _(200,{items:[],total:0,page:1,limit:50});let c=new URL(s.url),l=M(c,"profileId"),d=M(c,"from"),u=M(c,"to"),p=M(c,"q"),g=Pe(c);if("error"in g)return m(400,g.error);let{page:E,limit:f}=g,y={tenantId:"default",page:E,limit:f,...l!==void 0?{profileId:l}:{},...d!==void 0?{from:d}:{},...u!==void 0?{to:u}:{},...p!==void 0?{q:p}:{}},w=await i.getConsents(y);return _(200,w)}),"GET /consents/:visitorId":async(s,r)=>h(async()=>{let{user:o,denied:a}=await n(s,"consent:view");if(a)return a;if(o?.allowedTenants.length&&!o.allowedTenants.includes("default"))return m(403,"Forbidden");let c=await i.getConsent(r.visitorId??"");return c?_(200,c):m(404,"Consent not found")}),"GET /consents/:visitorId/history":async(s,r)=>h(async()=>{let{user:o,denied:a}=await n(s,"consent:view");if(a)return a;if(o?.allowedTenants.length&&!o.allowedTenants.includes("default"))return m(403,"Forbidden");let c=await i.getConsentHistory(r.visitorId??"");return _(200,c)})}}var Xc=!1;function Br(i,e,t){async function n(s,r){let o=await O(s,i,e,t);return{user:o,denied:D(o,r)}}return{"GET /visitors":async(s,r)=>h(async()=>{let{user:o,denied:a}=await n(s,"visitor:view");if(a)return a;if(o?.allowedTenants.length&&!o.allowedTenants.includes("default"))return _(200,{items:[],total:0,page:1,limit:50});let c=new URL(s.url),l=M(c,"from"),d=M(c,"to"),u=M(c,"q"),p=Pe(c);if("error"in p)return m(400,p.error);let{page:g,limit:E}=p,f={tenantId:"default",page:g,limit:E,...l!==void 0?{from:l}:{},...d!==void 0?{to:d}:{},...u!==void 0?{q:u}:{}},y=await i.getVisitors(f);return _(200,y)}),"GET /visitors/:visitorId/notice-shown":async(s,r)=>h(async()=>{if(!Xc)return m(404,"Not found");let{denied:o}=await n(s,"visitor:view");if(o)return o;let a=await i.getNoticeShownForVisitor(r.visitorId??"");return _(200,a)})}}var Vr="role_super_admin";function Wr(i,e,t,n){async function s(o,a){let c=await O(o,e,t,n);return{user:c,denied:D(c,a)}}async function r(o){return(await i.getRoles(o)).some(c=>c.id===Vr)}return{"GET /users":async(o,a)=>h(async()=>{let{denied:c}=await s(o,"user:view");if(c)return c;let l=await i.list(),d=await Promise.all(l.map(async u=>({...u,roles:(await i.getRoles(u.id)).map(p=>({id:p.id,name:p.name}))})));return _(200,d)}),"GET /users/:id":async(o,a)=>h(async()=>{let{denied:c}=await s(o,"user:view");if(c)return c;let l=await i.get(a.id??"");if(!l)return m(404,"User not found");let d=await i.getRoles(l.id);return _(200,{...l,roles:d.map(u=>({id:u.id,name:u.name}))})}),"POST /users":async(o,a)=>h(async()=>{let{user:c,denied:l}=await s(o,"user:create");if(l)return l;let d=await P(o);if(!d||typeof d!="object")return m(400,"Invalid body");let u=d;if(typeof u.name!="string"||!u.name)return m(400,"name is required");if(typeof u.email!="string"||!u.email)return m(400,"email is required");if(typeof u.password!="string"||!u.password)return m(400,"password is required");if(u.password.length<12)return m(400,"password must be at least 12 characters");let p=await i.create({name:u.name,email:u.email,password:u.password,...typeof u.roleId=="string"?{roleId:u.roleId}:{},...Array.isArray(u.allowedTenants)?{allowedTenants:u.allowedTenants}:{}},c.sub);return _(201,p)}),"PUT /users/:id":async(o,a)=>h(async()=>{let{user:c,denied:l}=await s(o,"user:update");if(l)return l;let d=a.id??"";if(d!==c.sub&&await r(d))return m(403,"Super admin users cannot be modified");let u=await P(o);if(!u||typeof u!="object")return m(400,"Invalid body");let p=u;if(typeof p.password=="string"&&p.password.length<12)return m(400,"password must be at least 12 characters");let g=await i.update(d,{...typeof p.name=="string"?{name:p.name}:{},...typeof p.email=="string"?{email:p.email}:{},...p.isActive!=null?{isActive:!!p.isActive}:{},...typeof p.password=="string"?{password:p.password}:{},...Array.isArray(p.allowedTenants)?{allowedTenants:p.allowedTenants}:{}},c.sub);return _(200,g)}),"DELETE /users/:id":async(o,a)=>h(async()=>{let{user:c,denied:l}=await s(o,"user:delete");if(l)return l;let d=a.id??"";return d===c.sub?m(400,"Cannot delete your own account"):await r(d)?m(403,"Super admin users cannot be deleted"):(await i.delete(d,c.sub),_(200,{success:!0}))}),"POST /users/:id/roles":async(o,a)=>h(async()=>{let{user:c,denied:l}=await s(o,"role:update");if(l)return l;let d=await P(o);if(!d||typeof d!="object")return m(400,"Invalid body");let u=d;if(typeof u.roleId!="string")return m(400,"roleId is required");let p=typeof u.tenantId=="string"?u.tenantId:void 0;return await i.assignRole(a.id??"",u.roleId,p),await e.createLog({tenantId:"default",userId:c.sub,action:"user.role_assigned",resourceType:"user",...a.id!=null?{resourceId:a.id}:{},newData:{roleId:u.roleId}}),_(200,{success:!0})}),"DELETE /users/:id/roles/:roleId":async(o,a)=>h(async()=>{let{user:c,denied:l}=await s(o,"role:update");if(l)return l;if(a.roleId===Vr)return m(403,"The super_admin role cannot be revoked");let u=new URL(o.url).searchParams.get("tenantId")??void 0;return await i.revokeRole(a.id??"",a.roleId??"",u),await e.createLog({tenantId:"default",userId:c.sub,action:"user.role_revoked",resourceType:"user",...a.id!=null?{resourceId:a.id}:{},oldData:{roleId:a.roleId}}),_(200,{success:!0})})}}var Zt="role_super_admin";function Jr(i,e,t){async function n(s,r){let o=await O(s,i,e,t);return{user:o,denied:D(o,r)}}return{"GET /roles":async(s,r)=>h(async()=>{let{denied:o}=await n(s,"role:view");if(o)return o;let a=await i.getRoles("default");return _(200,a)}),"GET /roles/:id/permissions":async(s,r)=>h(async()=>{let{denied:o}=await n(s,"role:view");if(o)return o;let a=await i.getPermissionsForRole(r.id??"");return _(200,a)}),"POST /roles":async(s,r)=>h(async()=>{let{user:o,denied:a}=await n(s,"role:create");if(a)return a;let c=await P(s);if(!c||typeof c!="object")return m(400,"Invalid body");let l=c;if(typeof l.name!="string"||!l.name)return m(400,"name is required");let d=await i.createRole({tenantId:"default",name:l.name,...typeof l.description=="string"?{description:l.description}:{}});return await i.createLog({tenantId:"default",userId:o.sub,action:"role.created",resourceType:"role",resourceId:d.id,newData:d}),_(201,d)}),"PUT /roles/:id":async(s,r)=>h(async()=>{let{user:o,denied:a}=await n(s,"role:update");if(a)return a;if(r.id===Zt)return m(403,"The super_admin role cannot be modified");let c=await P(s);if(!c||typeof c!="object")return m(400,"Invalid body");let l=c,d=await i.updateRole(r.id??"",{...typeof l.name=="string"?{name:l.name}:{},...typeof l.description=="string"?{description:l.description}:{}});return await i.createLog({tenantId:"default",userId:o.sub,action:"role.updated",resourceType:"role",resourceId:d.id,newData:d}),_(200,d)}),"DELETE /roles/:id":async(s,r)=>h(async()=>{let{user:o,denied:a}=await n(s,"role:delete");return a||(r.id===Zt?m(403,"The super_admin role cannot be deleted"):(await i.deleteRole(r.id??""),await i.createLog({tenantId:"default",userId:o.sub,action:"role.deleted",resourceType:"role",...r.id!=null?{resourceId:r.id}:{}}),_(200,{success:!0})))}),"GET /permissions":async(s,r)=>h(async()=>{let{denied:o}=await n(s,"role:view");if(o)return o;let a=await i.getAllPermissions();return _(200,a)}),"POST /roles/:id/permissions":async(s,r)=>h(async()=>{let{denied:o}=await n(s,"role:update");if(o)return o;if(r.id===Zt)return m(403,"The super_admin role cannot be modified");let a=await P(s);if(!a||typeof a!="object")return m(400,"Invalid body");let c=a;return typeof c.permissionId!="string"?m(400,"permissionId is required"):(await i.assignPermissionToRole(r.id??"",c.permissionId),_(200,{success:!0}))}),"DELETE /roles/:id/permissions/:permissionId":async(s,r)=>h(async()=>{let{denied:o}=await n(s,"role:update");return o||(r.id===Zt?m(403,"The super_admin role cannot be modified"):(await i.revokePermissionFromRole(r.id??"",r.permissionId??""),_(200,{success:!0})))})}}function Gr(i,e,t){async function n(s,r){let o=await O(s,i,e,t);return{user:o,denied:D(o,r)}}return{"GET /audit":async(s,r)=>h(async()=>{let{user:o,denied:a}=await n(s,"audit:view");if(a)return a;if(o?.allowedTenants.length&&!o.allowedTenants.includes("default"))return _(200,{items:[],total:0,page:1,limit:50});let c=new URL(s.url),l=M(c,"action"),d=M(c,"resourceType"),u=M(c,"from"),p=M(c,"to"),g=M(c,"q"),E=Pe(c);if("error"in E)return m(400,E.error);let{page:f,limit:y}=E,w={tenantId:"default",page:f,limit:y,...l!==void 0?{action:l}:{},...d!==void 0?{resourceType:d}:{},...u!==void 0?{from:u}:{},...p!==void 0?{to:p}:{},...g!==void 0?{q:g}:{}},R=await i.getLogs(w);return _(200,R)}),"GET /audit/:id":async(s,r)=>h(async()=>{let{user:o,denied:a}=await n(s,"audit:view");if(a)return a;if(o?.allowedTenants.length&&!o.allowedTenants.includes("default"))return m(403,"Forbidden");let c=await i.getAuditLogById(r.id??"");return!c||c.tenantId!=="default"?m(404,"Audit log entry not found"):_(200,c)})}}function qr(i,e,t,n){async function s(r){let o=await O(r,i,t,n);return{denied:D(o,"consent:view")}}return{"GET /stats/overview":async(r,o)=>h(async()=>{let{denied:a}=await s(r);if(a)return a;let c=await e.getOverview("default");return _(200,c)}),"GET /stats/categories":async(r,o)=>h(async()=>{let{denied:a}=await s(r);if(a)return a;let c=await e.getCategories("default");return _(200,c)}),"GET /stats/timeline":async(r,o)=>h(async()=>{let{denied:a}=await s(r);if(a)return a;let c=new URL(r.url),l=qt(c,"days",30),d=await e.getTimeline("default",l);return _(200,d)}),"GET /stats/countries":async(r,o)=>h(async()=>{let{denied:a}=await s(r);if(a)return a;let c=await e.getCountries("default");return _(200,c)}),"GET /stats/gpc":async(r,o)=>h(async()=>{let{denied:a}=await s(r);if(a)return a;let c=await e.getGpc("default");return _(200,c)}),"GET /stats/aggregate":async(r,o)=>h(async()=>{let{denied:a}=await s(r);if(a)return a;let c=await e.getTenants(),l=await Promise.all(c.map(async u=>{let p=await e.getOverview(u.id);return{tenantId:u.id,tenantName:u.name,...p}})),d=l.reduce((u,p)=>({totalConsents:u.totalConsents+p.totalConsents,totalVisitors:u.totalVisitors+p.totalVisitors,gpcDetectedCount:u.gpcDetectedCount+p.gpcDetectedCount}),{totalConsents:0,totalVisitors:0,gpcDetectedCount:0});return _(200,{aggregate:d,perTenant:l})})}}function Jn(i){let e=i==null?"":String(i);return e.includes(",")||e.includes('"')||e.includes(`
154
+ `)?`"${e.replace(/"/g,'""')}"`:e}function Zc(i){return[i.id,i.visitorId,i.profileId,i.locale,JSON.stringify(i.consentJson),i.gpcDetected?"1":"0",i.source,i.signature??"",i.createdAt,i.updatedAt,i.profileVersion??""].map(Jn).join(",")+`
155
+ `}function el(i){return[i.id,i.userId??"",i.action,i.resourceType,i.resourceId??"",i.createdAt].map(Jn).join(",")+`
156
+ `}function Kr(i,e,t,n){async function s(r){let o=await O(r,i,e,t);return{denied:D(o,"export:run")}}return{"GET /export/consents":async(r,o)=>h(async()=>{let{denied:a}=await s(r);if(a)return a;let c=new URL(r.url),l=M(c,"format")??"csv",d=M(c,"profileId"),u=M(c,"from"),p=M(c,"to"),g={tenantId:"default",...d!==void 0?{profileId:d}:{},...u!==void 0?{from:u}:{},...p!==void 0?{to:p}:{}};if(l==="json"){let w=[];for await(let R of i.streamConsents(g))w.push(R);return new Response(JSON.stringify(w),{status:200,headers:{"Content-Type":"application/json","Content-Disposition":'attachment; filename="consents.json"'}})}let E=new TextEncoder,f=`id,visitor_id,profile_id,locale,consent_json,gpc_detected,source,signature,created_at,updated_at,profile_version
157
+ `,y=new ReadableStream({async start(w){w.enqueue(E.encode(f));for await(let R of i.streamConsents(g))w.enqueue(E.encode(Zc(R)));w.close()}});return new Response(y,{status:200,headers:{"Content-Type":"text/csv; charset=utf-8","Content-Disposition":'attachment; filename="consents.csv"'}})}),"GET /export/consents/xlsx":async(r,o)=>h(async()=>{let{denied:a}=await s(r);if(a)return a;let c=null;try{c=await import("xlsx")}catch{return new Response(JSON.stringify({error:"xlsx peer dependency not installed. Run: npm install xlsx"}),{status:501,headers:{"Content-Type":"application/json"}})}if(!c)return new Response(null,{status:501});let l=new URL(r.url),d=l.searchParams.get("profileId")??void 0,u=l.searchParams.get("from")??void 0,p=l.searchParams.get("to")??void 0,g={tenantId:"default",...d!==void 0?{profileId:d}:{},...u!==void 0?{from:u}:{},...p!==void 0?{to:p}:{}},E=[];for await(let R of i.streamConsents(g))E.push({id:R.id,visitor_id:R.visitorId,profile_id:R.profileId,profile_version:R.profileVersion??"",locale:R.locale,consent_json:JSON.stringify(R.consentJson),gpc_detected:R.gpcDetected?1:0,age_verified:R.ageVerified?1:0,tcf_string:R.tcfString??"",signature:R.signature??"",source:R.source,created_at:R.createdAt,updated_at:R.updatedAt});let f=c.utils.book_new(),y=c.utils.json_to_sheet(E);c.utils.book_append_sheet(f,y,"Consents");let w=c.write(f,{type:"buffer",bookType:"xlsx"});return new Response(w,{status:200,headers:{"Content-Type":"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet","Content-Disposition":'attachment; filename="consents.xlsx"'}})}),"GET /export/translations/:profileId":async(r,o)=>h(async()=>{let{denied:a}=await s(r);if(a)return a;let c=o.profileId??"",l=await i.getProfile(c);if(!l)return m(404,"Profile not found");let d=l.profileJson,u=d.locales?.length?d.locales:[l.defaultLocale],p={};for(let E of u){if(E===l.defaultLocale){p[E]={mainBanner:d.mainBanner,...d.gpcBanner?{gpcBanner:d.gpcBanner}:{},preferenceModal:d.preferenceModal};continue}let f=await n.getVersionFile(c,String(l.version),E);if(f)try{let y=JSON.parse(f);p[E]={mainBanner:y.mainBanner,...y.gpcBanner?{gpcBanner:y.gpcBanner}:{},preferenceModal:y.preferenceModal}}catch{}}if(Object.keys(p).length===0)return new Response(`locale,field,value
158
+ `,{status:200,headers:{"Content-Type":"text/csv; charset=utf-8","Content-Disposition":`attachment; filename="${c}-translations.csv"`}});let g=["locale,section,field,value"];for(let[E,f]of Object.entries(p))for(let[y,w]of Object.entries(f))if(typeof w=="object"&&w!==null)for(let[R,I]of Object.entries(w))typeof I=="string"&&g.push([E,y,R,I].map(Jn).join(","));return new Response(g.join(`
157
159
  `)+`
158
- `,{status:200,headers:{"Content-Type":"text/csv; charset=utf-8","Content-Disposition":`attachment; filename="${c}-translations.csv"`}})}),"GET /export/audit":async(r,o)=>y(async()=>{let{denied:a}=await s(r);if(a)return a;let c=new URL(r.url),l=k(c,"format")??"csv",d=k(c,"from"),u=k(c,"to"),p={tenantId:"default",...d!==void 0?{from:d}:{},...u!==void 0?{to:u}:{}};if(l==="json"){let h=[];for await(let w of i.streamAuditLogs(p))h.push(w);return new Response(JSON.stringify(h),{status:200,headers:{"Content-Type":"application/json","Content-Disposition":'attachment; filename="audit.json"'}})}let g=new TextEncoder,E=`id,user_id,action,resource_type,resource_id,created_at
159
- `,_=new ReadableStream({async start(h){h.enqueue(g.encode(E));for await(let w of i.streamAuditLogs(p))h.enqueue(g.encode(Cc(w)));h.close()}});return new Response(_,{status:200,headers:{"Content-Type":"text/csv; charset=utf-8","Content-Disposition":'attachment; filename="audit.csv"'}})})}}import{randomBytes as Cr,createHash as Ic}from"crypto";function Tc(){let i=`ck_live_${Cr(12).toString("hex")}${Cr(12).toString("hex")}`,e=Ic("sha256").update(i).digest("hex");return{raw:i,hash:e}}function Ir(i,e,t){async function n(s){let r=await P(s,i,e,t);return{user:r,denied:D(r,"settings:update")}}return{"GET /apikeys":async(s,r)=>y(async()=>{let{denied:o}=await n(s);if(o)return o;let a=await i.getApiKeys("default"),c=new Date,l=await Promise.all(a.map(async d=>{let u=d.isActive&&!!d.expireBy&&new Date(d.expireBy)<=c;return u&&await i.revokeApiKey(d.id).catch(()=>{}),{id:d.id,name:d.name,isActive:u?!1:d.isActive,createdAt:d.createdAt,updatedAt:d.updatedAt,...d.createdBy!==void 0?{createdBy:d.createdBy}:{},...d.expireBy!==void 0?{expireBy:d.expireBy}:{}}}));return f(200,l)}),"POST /apikeys":async(s,r)=>y(async()=>{let{user:o,denied:a}=await n(s);if(a)return a;let c=await s.json();if(!c.name)return f(400,{error:"name is required"});if(c.expireBy!==void 0&&Number.isNaN(new Date(c.expireBy).getTime()))return f(400,{error:"expireBy must be a valid date"});let{raw:l,hash:d}=Tc(),u=await i.createApiKey({tenantId:c.tenantId??"default",name:c.name,keyHash:d,...o?{createdBy:o.sub}:{},...c.expireBy!==void 0?{expireBy:c.expireBy}:{}});return f(201,{id:u.id,name:u.name,key:l,createdAt:u.createdAt})}),"DELETE /apikeys/:id":async(s,r)=>y(async()=>{let{denied:o}=await n(s);return o||(await i.revokeApiKey(r.id??""),new Response(null,{status:204}))}),"POST /apikeys/:id/reactivate":async(s,r)=>y(async()=>{let{denied:o}=await n(s);return o||(await i.reactivateApiKey(r.id??""),new Response(null,{status:204}))}),"DELETE /apikeys/:id/permanent":async(s,r)=>y(async()=>{let{denied:o}=await n(s);return o||(await i.deleteApiKey(r.id??""),new Response(null,{status:204}))})}}function Tr(i,e,t){async function n(s,r){let o=await P(s,i,e,t);return{user:o,denied:D(o,r)}}return{"GET /tenants":async(s,r)=>y(async()=>{let{denied:o}=await n(s,"settings:update");if(o)return o;let a=await i.getTenants();return f(200,a)}),"POST /tenants":async(s,r)=>y(async()=>{let{denied:o}=await n(s,"settings:update");if(o)return o;let a=await v(s);if(!a||typeof a!="object")return m(400,"Invalid body");let c=a;if(typeof c.name!="string"||typeof c.slug!="string")return m(400,"name and slug are required");let l=await i.createTenant({name:c.name,slug:c.slug});return f(201,l)}),"PUT /tenants/:id":async(s,r)=>y(async()=>{let{denied:o}=await n(s,"settings:update");if(o)return o;let a=await v(s);if(!a||typeof a!="object")return m(400,"Invalid body");let c=a,l=await i.updateTenant(r.id??"",{...typeof c.name=="string"?{name:c.name}:{},...typeof c.slug=="string"?{slug:c.slug}:{}});return f(200,l)}),"DELETE /tenants/:id":async(s,r)=>y(async()=>{let{denied:o}=await n(s,"settings:update");if(o)return o;let a=r.id??"";return a==="default"?m(400,"Cannot delete the default tenant"):(await i.deleteTenant(a),f(200,{success:!0}))})}}function Ar(i,e,t){async function n(s){let r=await P(s,i,e,t);return{user:r,denied:D(r,"settings:update")}}return{"GET /settings":async(s,r)=>y(async()=>{let{denied:o}=await n(s);if(o)return o;let a=await i.getSettings("default");return f(200,a)}),"PATCH /settings":async(s,r)=>y(async()=>{let{denied:o}=await n(s);if(o)return o;let a=await v(s);if(!a||typeof a!="object")return m(400,"Invalid body");let c=a;if(c.allowedOrigins!==void 0&&!Array.isArray(c.allowedOrigins))return m(400,"allowedOrigins must be an array of strings");if(c.adminAllowedOrigins!==void 0&&!Array.isArray(c.adminAllowedOrigins))return m(400,"adminAllowedOrigins must be an array of strings");let l=await i.updateSettings("default",{...c.allowedOrigins!==void 0?{allowedOrigins:c.allowedOrigins}:{},...c.adminAllowedOrigins!==void 0?{adminAllowedOrigins:c.adminAllowedOrigins}:{}});return f(200,l)})}}function vr(i,e,t,n){async function s(o){let a=await P(o,i,e,t);return{user:a,denied:D(a,"consent:view")}}async function r(o){let a=await P(o,i,e,t);return{user:a,denied:D(a,"settings:update")}}return{"GET /tcf/vendors":async(o,a)=>y(async()=>{let{denied:c}=await s(o);if(c)return c;let l=await me();if(!l)return f(503,{error:"GVL not available. Enable tcf.enabled in config."});let d=new URL(o.url),u=d.searchParams.get("q")?.toLowerCase(),p=Math.max(1,parseInt(d.searchParams.get("page")??"1",10)),g=Math.min(200,Math.max(1,parseInt(d.searchParams.get("limit")??"100",10))),E=Object.values(l.vendors),_=u?E.filter(T=>T.name.toLowerCase().includes(u)):E,h=_.length,w=Math.max(1,Math.ceil(h/g)),R=Math.min(p,w),I=(R-1)*g;return f(200,{vendorListVersion:l.vendorListVersion,total:h,page:R,totalPages:w,vendors:_.slice(I,I+g)})}),"GET /tcf/purposes":async(o,a)=>y(async()=>{let{denied:c}=await s(o);if(c)return c;let l=await me();return l?f(200,{purposes:Object.values(l.purposes)}):f(503,{error:"GVL not available. Enable tcf.enabled in config."})}),"GET /tcf/registration-status":async(o,a)=>y(async()=>{let{denied:c}=await r(o);if(c)return c;if(!n)return f(200,{enabled:!1,confirmed:!0});let l=await i.getSettings("default"),d=ee(n.enabled,{cmpId:n.cmpId,cmpVersion:n.cmpVersion,publisherCC:n.publisherCC},l.tcfConfirmation),p=new URL(o.url).searchParams.get("refresh")==="true",g=n.enabled?await be(p):null,E=g?.cmps[String(n.cmpId)];return f(200,{enabled:n.enabled,cmpId:n.cmpId,cmpVersion:n.cmpVersion,...n.publisherCC?{publisherCC:n.publisherCC}:{},confirmed:d,...l.tcfConfirmation?{confirmedAt:l.tcfConfirmation.confirmedAt,confirmedBy:l.tcfConfirmation.confirmedBy}:{},...g?{cmpListVersion:g.cmpListVersion}:{},...n.enabled?{cmpListEntry:{found:!!E,deregistered:!!E?.deletedDate,...E?.deletedDate?{deletedDate:E.deletedDate}:{}}}:{}})}),"POST /tcf/confirm-registration":async(o,a)=>y(async()=>{let{user:c,denied:l}=await r(o);if(l)return l;if(!n?.enabled)return m(400,"TCF is not enabled in the server config \u2014 nothing to confirm.");if(!(await v(o))?.acknowledge)return m(400,"acknowledge: true is required to confirm TCF registration.");let p=(await be())?.cmps[String(n.cmpId)];if(p?.deletedDate)return m(409,`cmpId ${n.cmpId} was deregistered on ${p.deletedDate} per IAB's CMP List. Registration confirmation blocked.`);if(!p)return m(404,`cmpId ${n.cmpId} was not found in the cached CMP List. Registration can take time to propagate after signing up with IAB Europe \u2014 try "Refresh Status" and confirm again shortly.`);let g=He({cmpId:n.cmpId,cmpVersion:n.cmpVersion,publisherCC:n.publisherCC}),E=await i.updateSettings("default",{tcfConfirmation:{configHash:g,confirmedAt:new Date().toISOString(),confirmedBy:c.sub}});return f(200,{confirmed:!0,tcfConfirmation:E.tcfConfirmation})})}}function Pr(i,e,t,n){async function s(r){let o=await P(r,i,e,t);return{user:o,denied:D(o,"settings:update")}}return{"GET /gpp/registration-status":async(r,o)=>y(async()=>{let{denied:a}=await s(r);if(a)return a;if(!n)return f(200,{enabled:!1,confirmed:!0});let c=await i.getSettings("default"),l=ee(n.enabled,{cmpId:n.cmpId,cmpVersion:n.cmpVersion},c.gppConfirmation);return f(200,{enabled:n.enabled,cmpId:n.cmpId,cmpVersion:n.cmpVersion,confirmed:l,...c.gppConfirmation?{confirmedAt:c.gppConfirmation.confirmedAt,confirmedBy:c.gppConfirmation.confirmedBy}:{}})}),"POST /gpp/confirm-registration":async(r,o)=>y(async()=>{let{user:a,denied:c}=await s(r);if(c)return c;if(!n?.enabled)return m(400,"GPP is not enabled in the server config \u2014 nothing to confirm.");if(!(await v(r))?.acknowledge)return m(400,"acknowledge: true is required to confirm GPP registration.");let d=He({cmpId:n.cmpId,cmpVersion:n.cmpVersion}),u=await i.updateSettings("default",{gppConfirmation:{configHash:d,confirmedAt:new Date().toISOString(),confirmedBy:a.sub}});return f(200,{confirmed:!0,gppConfirmation:u.gppConfirmation})})}}var Or=["mandatory","consent","legitimate_interest"],br=["sale","sharing","sensitive"];function Ac(i){if(!i||typeof i!="object"||Array.isArray(i))return{valid:!1,error:"cookies must be an object keyed by parameter id"};for(let[e,t]of Object.entries(i)){if(!e.trim())return{valid:!1,error:"cookie id must be non-empty"};if(!t||typeof t!="object")return{valid:!1,error:`cookies["${e}"] must be an object`};let n=t;if(!Zt.includes(n.purpose))return{valid:!1,error:`cookies["${e}"].purpose must be one of: ${Zt.join(", ")}`};if(typeof n.listenGpc!="boolean")return{valid:!1,error:`cookies["${e}"].listenGpc must be a boolean`};if(n.preGrant!==void 0&&typeof n.preGrant!="boolean")return{valid:!1,error:`cookies["${e}"].preGrant must be a boolean`};if(n.cpraCategory!==void 0&&!br.includes(n.cpraCategory))return{valid:!1,error:`cookies["${e}"].cpraCategory must be one of: ${br.join(", ")}`}}return{valid:!0}}function vc(i){if(!i||typeof i!="object"||Array.isArray(i))return{valid:!1,error:"categories must be an object keyed by category id"};let e=Object.entries(i);if(e.length===0)return{valid:!1,error:"at least one category is required"};for(let[t,n]of e){if(!n||typeof n!="object")return{valid:!1,error:`categories["${t}"] must be an object`};let s=n;if(typeof s.heading!="string"||!s.heading.trim())return{valid:!1,error:`categories["${t}"].heading is required`};if(typeof s.htmlText!="string")return{valid:!1,error:`categories["${t}"].htmlText is required`};if(!Or.includes(s.legalBasis))return{valid:!1,error:`categories["${t}"].legalBasis must be one of: ${Or.join(", ")}`};if(!Array.isArray(s.cookies))return{valid:!1,error:`categories["${t}"].cookies must be an array of parameter ids`}}return{valid:!0}}function Pc(i,e){let t=new Map;for(let n of Object.keys(i))t.set(n,0);for(let n of Object.values(e))for(let s of n.cookies)t.set(s,(t.get(s)??0)+1);for(let[n,s]of t){if(s===0)return{valid:!1,error:`Parameter "${n}" is not assigned to any category`};if(s>1)return{valid:!1,error:`Parameter "${n}" is assigned to more than one category`}}return{valid:!0}}function Oc(i,e){let t=new Map;for(let[s,r]of Object.entries(e))for(let o of r.cookies)t.set(o,s);let n=[];for(let[s,r]of Object.entries(i)){let o=t.get(s);if(!o)continue;let a=e[o].legalBasis,c=r.purpose;if(a==="mandatory"&&c!=="necessary")return{valid:!1,error:`Parameter "${s}" has purpose "${c??"unknown"}" but is assigned to a Strictly Necessary category \u2014 this grants a tracking parameter with no consent gate.`};a!=="mandatory"&&c==="necessary"&&n.push(`Parameter "${s}" has purpose "necessary" but is assigned to a non-mandatory category \u2014 this may break site functionality since it can be denied.`)}return{valid:!0,...n.length>0?{warnings:n}:{}}}function bn(i,e){let t=Ac(i);if(!t.valid)return t;let n=vc(e);if(!n.valid)return n;let s=i,r=e,o=Pc(s,r);return o.valid?Oc(s,r):o}function Dr(i,e,t,n="default"){async function s(r,o){let a=await P(r,i,e,t);return{user:a,denied:D(a,o)}}return{"GET /consent-templates":async r=>y(async()=>{let{denied:o}=await s(r,"profile:view");return o||f(200,await i.getConsentTemplates(n))}),"GET /consent-templates/:id":async(r,o)=>y(async()=>{let{denied:a}=await s(r,"profile:view");if(a)return a;let c=await i.getConsentTemplate(o.id);return c?f(200,c):m(404,"Consent template not found")}),"POST /consent-templates":async r=>y(async()=>{let{denied:o}=await s(r,"profile:create");if(o)return o;let a=await v(r);if(!a?.name||typeof a.name!="string")return m(400,"name is required");let c=bn(a.cookies,a.categories);if(!c.valid)return m(400,c.error);let l=await i.createConsentTemplate({tenantId:n,name:a.name,cookies:a.cookies,categories:a.categories});return f(201,l)}),"PUT /consent-templates/:id":async(r,o)=>y(async()=>{let{denied:a}=await s(r,"profile:update");if(a)return a;let c=o.id,l=await v(r),d=await i.getConsentTemplate(c);if(!d)return m(404,"Consent template not found");let u={};l?.name!==void 0&&typeof l.name=="string"&&(u.name=l.name);let p=l?.cookies!==void 0,g=l?.categories!==void 0,E=p?l.cookies:d.cookies,_=g?l.categories:d.categories;if(p||g){let w=bn(E,_);if(!w.valid)return m(400,w.error);p&&(u.cookies=E),g&&(u.categories=_);let R=await i.findProfilesUsingConsentTemplate(c),I=[];for(let T of R){if(!T.complianceGroup)continue;Ie(E,_,T.complianceGroup).valid||I.push({id:T.id,name:T.name,complianceGroup:T.complianceGroup})}if(I.length>0)return m(422,"This change would break compliance for affected profiles",{blockingProfiles:I})}let h=await i.updateConsentTemplate(c,u);return f(200,h)}),"DELETE /consent-templates/:id":async(r,o)=>y(async()=>{let{denied:a}=await s(r,"profile:delete");if(a)return a;let c=o.id,l=await i.findProfilesUsingConsentTemplate(c);return l.length>0?m(422,"Cannot delete: template is used by profiles",{profiles:l.map(d=>({id:d.id,name:d.name,isActive:d.isActive}))}):(await i.deleteConsentTemplate(c),f(200,{ok:!0}))}),"POST /consent-templates/:id/copy":async(r,o)=>y(async()=>{let{denied:a}=await s(r,"profile:create");if(a)return a;let c=await v(r),l=await i.getConsentTemplate(o.id);if(!l)return m(404,"Consent template not found");let d=typeof c?.name=="string"&&c.name.trim()?c.name.trim():`${l.name} (Copy)`,u=await i.copyConsentTemplate(o.id,d);return f(201,u)}),"GET /consent-templates/:id/profile-usage":async(r,o)=>y(async()=>{let{denied:a}=await s(r,"profile:view");if(a)return a;let c=await i.findProfilesUsingConsentTemplate(o.id);return f(200,c)})}}function Ur(i,e,t,n="default"){async function s(r,o){let a=await P(r,i,e,t);return{user:a,denied:D(a,o)}}return{"GET /ui-templates":async r=>y(async()=>{let{denied:o}=await s(r,"profile:view");return o||f(200,await i.getUITemplates(n))}),"GET /ui-templates/:id":async(r,o)=>y(async()=>{let{denied:a}=await s(r,"profile:view");if(a)return a;let c=await i.getUITemplate(o.id);return c?f(200,c):m(404,"UI template not found")}),"POST /ui-templates":async r=>y(async()=>{let{denied:o}=await s(r,"profile:create");if(o)return o;let a=await v(r);if(!a?.name||typeof a.name!="string")return m(400,"name is required");if(!a.mainBanner||!a.gpcBanner||!a.preferenceModal)return m(400,"mainBanner, gpcBanner, and preferenceModal are required");let c=await i.createUITemplate({tenantId:n,name:a.name,mainBanner:a.mainBanner,gpcBanner:a.gpcBanner,preferenceModal:a.preferenceModal});return f(201,c)}),"PUT /ui-templates/:id":async(r,o)=>y(async()=>{let{denied:a}=await s(r,"profile:update");if(a)return a;let c=await v(r),l={};typeof c?.name=="string"&&(l.name=c.name),c?.mainBanner&&(l.mainBanner=c.mainBanner),c?.gpcBanner&&(l.gpcBanner=c.gpcBanner),c?.preferenceModal&&(l.preferenceModal=c.preferenceModal);let d=await i.updateUITemplate(o.id,l);return f(200,d)}),"DELETE /ui-templates/:id":async(r,o)=>y(async()=>{let{denied:a}=await s(r,"profile:delete");if(a)return a;let c=o.id,l=await i.findProfilesUsingUITemplate(c);return l.length>0?m(422,"Cannot delete: template is used by profiles",{profiles:l.map(d=>({id:d.id,name:d.name,isActive:d.isActive}))}):(await i.deleteUITemplate(c),f(200,{ok:!0}))}),"POST /ui-templates/:id/copy":async(r,o)=>y(async()=>{let{denied:a}=await s(r,"profile:create");if(a)return a;let c=await v(r),l=await i.getUITemplate(o.id);if(!l)return m(404,"UI template not found");let d=typeof c?.name=="string"&&c.name.trim()?c.name.trim():`${l.name} (Copy)`,u=await i.copyUITemplate(o.id,d);return f(201,u)}),"GET /ui-templates/:id/profile-usage":async(r,o)=>y(async()=>{let{denied:a}=await s(r,"profile:view");if(a)return a;let c=await i.findProfilesUsingUITemplate(o.id);return f(200,c)})}}function Lr(i,e,t,n="default"){async function s(r,o){let a=await P(r,i,e,t);return{user:a,denied:D(a,o)}}return{"GET /analytics/opt-in":async(r,o)=>y(async()=>{let{denied:a}=await s(r,"stats:view");if(a)return a;let c=new URL(r.url),l=c.searchParams.get("tenantId")??n,d={},u=c.searchParams.get("profileId"),p=c.searchParams.get("complianceGroup"),g=c.searchParams.get("from"),E=c.searchParams.get("to"),_=c.searchParams.get("locale");u&&(d.profileId=u),p&&(d.complianceGroup=p),g&&(d.from=g),E&&(d.to=E),_&&(d.locale=_);let h=await i.getOptInStats(l,d);return f(200,h)})}}var oe="\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022";function Nr(i){let e=i.storage,t=i.auth,n=i.compliance,s=i.dashboard;return{basePath:i.basePath,dashboard:typeof s=="object"?{enabled:s.enabled,path:s.path}:s,...e?{storage:{driver:e.driver,...e.path!==void 0?{path:e.path}:{},...e.uri!==void 0?{uri:oe}:{},...e.database!==void 0?{database:e.database}:{},...e.host!==void 0?{host:e.host}:{},...e.port!==void 0?{port:e.port}:{},...e.user!==void 0?{user:e.user}:{},...e.password!==void 0?{password:oe}:{},...e.poolMax!==void 0?{poolMax:e.poolMax}:{},...e.statementTimeoutMs!==void 0?{statementTimeoutMs:e.statementTimeoutMs}:{},...e.idleInTransactionTimeoutMs!==void 0?{idleInTransactionTimeoutMs:e.idleInTransactionTimeoutMs}:{}}}:{},...t?{auth:{mode:t.mode,...t.adminEmail!==void 0?{adminEmail:t.adminEmail}:{},...t.adminPassword!==void 0?{adminPassword:oe}:{},...t.masterSecret!==void 0?{masterSecret:oe}:{},...t.oidc?{oidc:{issuer:t.oidc.issuer,clientId:t.oidc.clientId,clientSecret:oe,redirectUri:t.oidc.redirectUri}}:{},...t.saml?{saml:{issuer:t.saml.issuer,entryPoint:t.saml.entryPoint,cert:oe,callbackUrl:t.saml.callbackUrl}}:{}}}:{},...i.rateLimit?{rateLimit:i.rateLimit}:{},...n?{compliance:{...n.type!==void 0?{type:n.type}:{},...n.gpc!==void 0?{gpc:n.gpc}:{},...n.geoDataProvider!==void 0?{geoDataProvider:typeof n.geoDataProvider=="function"?"<custom function>":n.geoDataProvider}:{},...n.complianceMap!==void 0?{complianceMap:typeof n.complianceMap=="object"?`<custom map, ${Object.keys(n.complianceMap.countries).length} countries>`:n.complianceMap}:{},...n.tcf?{tcf:n.tcf}:{},...n.gpp?{gpp:n.gpp}:{},...n.dataRetention?{dataRetention:n.dataRetention}:{},...n.parentalConsentTokenTtlDays!==void 0?{parentalConsentTokenTtlDays:n.parentalConsentTokenTtlDays}:{},...n.dataSigningHash!==void 0?{dataSigningHash:oe}:{}}}:{},...i.multiTenant?{multiTenant:i.multiTenant}:{},...i.plugins?{plugins:{count:i.plugins.length}}:{},...i.maxBodySize!==void 0?{maxBodySize:i.maxBodySize}:{},...i.trustedProxies?{trustedProxies:i.trustedProxies}:{},...i.branding?{branding:i.branding}:{},...i.s3Api?{s3Api:{enabled:i.s3Api.enabled,region:i.s3Api.region,bucketName:i.s3Api.bucketName,accessKeyId:oe,secretAccessKey:oe}}:{}}}function kr(i,e,t,n,s,r){async function o(c){let l=await P(c,e,t,n);return{denied:D(l,"settings:update")}}async function a(){return(await e.getSettings("default")).setupCompleted===!0?m(409,"Setup has already been completed"):null}return{"GET /setup/status":async(c,l)=>y(async()=>{let{denied:d}=await o(c);if(d)return d;let u=await e.getSettings("default");return f(200,{completed:u.setupCompleted===!0})}),"GET /setup/config":async(c,l)=>y(async()=>{let{denied:d}=await o(c);return d||f(200,{config:Nr(s),...r})}),"GET /setup/compliance-groups":async(c,l)=>y(async()=>{let{denied:d}=await o(c);if(d)return d;let u=Xt.map(p=>({id:p,...Xe[p]}));return f(200,u)}),"POST /setup/seed-profiles":async(c,l)=>y(async()=>{let{denied:d}=await o(c);if(d)return d;let u=await a();if(u)return u;let g=(await v(c))?.groups;if(!Array.isArray(g)||g.some(_=>typeof _!="string"))return m(400,"groups must be an array of compliance group ids");let E=g.filter(_=>!Xt.includes(_));return E.length>0?m(400,`Unknown compliance group id(s): ${E.join(", ")}`):(await Promise.all(g.map(_=>i.seedDefaultProfile(_))),await e.updateSettings("default",{profilesSeeded:!0}),f(200,{seeded:g}))}),"POST /setup/complete":async(c,l)=>y(async()=>{let{denied:d}=await o(c);if(d)return d;let u=await a();if(u)return u;if((await e.getSettings("default")).profilesSeeded!==!0)return m(409,"POST /setup/seed-profiles must be called at least once (an empty groups: [] call is a valid pass) before setup can be completed");let g=await e.updateSettings("default",{setupCompleted:!0});return f(200,g)})}}import{createHash as bc}from"crypto";async function Mr(i,e,t){let n=i.headers.get("authorization");if(!n?.startsWith("Bearer ck_"))return t;let s=n.slice(7),r=bc("sha256").update(s).digest("hex"),o=await e.getApiKeyByHash(r);return!o||!o.isActive?t:o.expireBy&&new Date(o.expireBy)<=new Date?(await e.revokeApiKey(o.id).catch(()=>{}),t):o.tenantId}var qe=class{constructor(e=6e4,t=60){this.windowMs=e;this.maxRequests=t;setInterval(()=>{let n=Date.now();for(let[s,r]of this.store)n>=r.resetAt&&this.store.delete(s)},this.windowMs).unref()}windowMs;maxRequests;store=new Map;check(e){let t=Date.now(),n=this.store.get(e);return!n||t>=n.resetAt?(this.store.set(e,{count:1,resetAt:t+this.windowMs}),!0):(n.count+=1,n.count<=this.maxRequests)}checkRequest(e,t){return this.check(`ip:${e}`)?t?this.check(`profile:${t}`):!0:!1}};var Dn=class{constructor(e){this.options=e;e.eventBus.on("consent:created",this.onCreated),e.eventBus.on("consent:updated",this.onUpdated)}options;onCreated=e=>this.fire(e,void 0);onUpdated=({previous:e,current:t})=>this.fire(t,e);fire(e,t){let n=e.consentJson[this.options.id]??"denied",s=t?.consentJson[this.options.id]??"denied";if(n===s)return;let r={visitorId:e.visitorId,cookieId:this.options.id,status:n,record:e};n==="granted"?this.options.onGrant?.(r):this.options.onDeny?.(r)}destroy(){this.options.eventBus.off("consent:created",this.onCreated),this.options.eventBus.off("consent:updated",this.onUpdated)}};function jr(i,e){return e.length>0&&e.every(t=>i[t]==="granted")}var Un=class{constructor(e){this.options=e;e.eventBus.on("consent:created",this.onCreated),e.eventBus.on("consent:updated",this.onUpdated)}options;onCreated=e=>{this.fire(e,void 0)};onUpdated=({previous:e,current:t})=>{this.fire(t,e)};async fire(e,t){let s=(await this.options.profiles.getResolved(e.profileId))?.preferenceModal.categories[this.options.categoryId]?.cookies??[];if(s.length===0)return;let r=jr(e.consentJson,s)?"granted":"denied",o=t&&jr(t.consentJson,s)?"granted":"denied";if(r===o)return;let a={visitorId:e.visitorId,categoryId:this.options.categoryId,status:r,record:e};r==="granted"?this.options.onGrant?.(a):this.options.onDeny?.(a)}destroy(){this.options.eventBus.off("consent:created",this.onCreated),this.options.eventBus.off("consent:updated",this.onUpdated)}};var Ln=Lc(Hr(import.meta.url)),O_=Hr(new URL("./dashboard",import.meta.url)),Br={".html":"text/html; charset=utf-8",".js":"application/javascript; charset=utf-8",".mjs":"application/javascript; charset=utf-8",".css":"text/css; charset=utf-8",".json":"application/json",".png":"image/png",".svg":"image/svg+xml",".ico":"image/x-icon",".woff2":"font/woff2",".woff":"font/woff",".ttf":"font/ttf"};async function Nc(i,e,t,n,s){let r=i.slice(e.length)||"/";if(r.startsWith("/api/")||r.startsWith("/admin/"))return!1;let o=r==="/"||r===""||!Gt(r)?ae(t,"index.html"):ae(t,r);if(!o.startsWith(t))return!1;try{let a=await Ge(o),c=Gt(o).toLowerCase(),l=c===".html"||c==="",d=Br[c]??"application/octet-stream";return n.writeHead(200,{"Content-Type":d}),n.end(l?ve(a,s):a),!0}catch{return!1}}async function xr(i,e){try{let t=await Ge(i);return new Response(t,{headers:{"Content-Type":e,"Cache-Control":"public, max-age=3600"}})}catch{return new Response("Not Found",{status:404})}}function kc(i){let e=[],t=i.split("/").map(n=>n.startsWith(":")?(e.push(n.slice(1)),"([^/]+)"):n.replace(/[.*+?^${}()|[\]\\]/g,"\\$&"));return{pattern:new RegExp(`^${t.join("\\/")}$`),paramNames:e}}function Mc(i){let t=`<!DOCTYPE html>
160
+ `,{status:200,headers:{"Content-Type":"text/csv; charset=utf-8","Content-Disposition":`attachment; filename="${c}-translations.csv"`}})}),"GET /export/audit":async(r,o)=>h(async()=>{let{denied:a}=await s(r);if(a)return a;let c=new URL(r.url),l=M(c,"format")??"csv",d=M(c,"from"),u=M(c,"to"),p={tenantId:"default",...d!==void 0?{from:d}:{},...u!==void 0?{to:u}:{}};if(l==="json"){let y=[];for await(let w of i.streamAuditLogs(p))y.push(w);return new Response(JSON.stringify(y),{status:200,headers:{"Content-Type":"application/json","Content-Disposition":'attachment; filename="audit.json"'}})}let g=new TextEncoder,E=`id,user_id,action,resource_type,resource_id,created_at
161
+ `,f=new ReadableStream({async start(y){y.enqueue(g.encode(E));for await(let w of i.streamAuditLogs(p))y.enqueue(g.encode(el(w)));y.close()}});return new Response(f,{status:200,headers:{"Content-Type":"text/csv; charset=utf-8","Content-Disposition":'attachment; filename="audit.csv"'}})})}}import{randomBytes as Yr,createHash as tl}from"crypto";function nl(){let i=`ck_live_${Yr(12).toString("hex")}${Yr(12).toString("hex")}`,e=tl("sha256").update(i).digest("hex");return{raw:i,hash:e}}function zr(i,e,t){async function n(s){let r=await O(s,i,e,t);return{user:r,denied:D(r,"settings:update")}}return{"GET /apikeys":async(s,r)=>h(async()=>{let{denied:o}=await n(s);if(o)return o;let a=await i.getApiKeys("default"),c=new Date,l=await Promise.all(a.map(async d=>{let u=d.isActive&&!!d.expireBy&&new Date(d.expireBy)<=c;return u&&await i.revokeApiKey(d.id).catch(()=>{}),{id:d.id,name:d.name,isActive:u?!1:d.isActive,createdAt:d.createdAt,updatedAt:d.updatedAt,...d.createdBy!==void 0?{createdBy:d.createdBy}:{},...d.expireBy!==void 0?{expireBy:d.expireBy}:{}}}));return _(200,l)}),"POST /apikeys":async(s,r)=>h(async()=>{let{user:o,denied:a}=await n(s);if(a)return a;let c=await s.json();if(!c.name)return _(400,{error:"name is required"});if(c.expireBy!==void 0&&Number.isNaN(new Date(c.expireBy).getTime()))return _(400,{error:"expireBy must be a valid date"});let{raw:l,hash:d}=nl(),u=await i.createApiKey({tenantId:c.tenantId??"default",name:c.name,keyHash:d,...o?{createdBy:o.sub}:{},...c.expireBy!==void 0?{expireBy:c.expireBy}:{}});return _(201,{id:u.id,name:u.name,key:l,createdAt:u.createdAt})}),"DELETE /apikeys/:id":async(s,r)=>h(async()=>{let{denied:o}=await n(s);return o||(await i.revokeApiKey(r.id??""),new Response(null,{status:204}))}),"POST /apikeys/:id/reactivate":async(s,r)=>h(async()=>{let{denied:o}=await n(s);return o||(await i.reactivateApiKey(r.id??""),new Response(null,{status:204}))}),"DELETE /apikeys/:id/permanent":async(s,r)=>h(async()=>{let{denied:o}=await n(s);return o||(await i.deleteApiKey(r.id??""),new Response(null,{status:204}))})}}function Qr(i,e,t){async function n(s,r){let o=await O(s,i,e,t);return{user:o,denied:D(o,r)}}return{"GET /tenants":async(s,r)=>h(async()=>{let{denied:o}=await n(s,"settings:update");if(o)return o;let a=await i.getTenants();return _(200,a)}),"POST /tenants":async(s,r)=>h(async()=>{let{denied:o}=await n(s,"settings:update");if(o)return o;let a=await P(s);if(!a||typeof a!="object")return m(400,"Invalid body");let c=a;if(typeof c.name!="string"||typeof c.slug!="string")return m(400,"name and slug are required");let l=await i.createTenant({name:c.name,slug:c.slug});return _(201,l)}),"PUT /tenants/:id":async(s,r)=>h(async()=>{let{denied:o}=await n(s,"settings:update");if(o)return o;let a=await P(s);if(!a||typeof a!="object")return m(400,"Invalid body");let c=a,l=await i.updateTenant(r.id??"",{...typeof c.name=="string"?{name:c.name}:{},...typeof c.slug=="string"?{slug:c.slug}:{}});return _(200,l)}),"DELETE /tenants/:id":async(s,r)=>h(async()=>{let{denied:o}=await n(s,"settings:update");if(o)return o;let a=r.id??"";return a==="default"?m(400,"Cannot delete the default tenant"):(await i.deleteTenant(a),_(200,{success:!0}))})}}function Xr(i,e,t){async function n(s){let r=await O(s,i,e,t);return{user:r,denied:D(r,"settings:update")}}return{"GET /settings":async(s,r)=>h(async()=>{let{denied:o}=await n(s);if(o)return o;let a=await i.getSettings("default");return _(200,a)}),"PATCH /settings":async(s,r)=>h(async()=>{let{denied:o}=await n(s);if(o)return o;let a=await P(s);if(!a||typeof a!="object")return m(400,"Invalid body");let c=a;if(c.allowedOrigins!==void 0&&!Array.isArray(c.allowedOrigins))return m(400,"allowedOrigins must be an array of strings");if(c.adminAllowedOrigins!==void 0&&!Array.isArray(c.adminAllowedOrigins))return m(400,"adminAllowedOrigins must be an array of strings");let l=await i.updateSettings("default",{...c.allowedOrigins!==void 0?{allowedOrigins:c.allowedOrigins}:{},...c.adminAllowedOrigins!==void 0?{adminAllowedOrigins:c.adminAllowedOrigins}:{}});return _(200,l)})}}function Zr(i,e,t,n){async function s(o){let a=await O(o,i,e,t);return{user:a,denied:D(a,"consent:view")}}async function r(o){let a=await O(o,i,e,t);return{user:a,denied:D(a,"settings:update")}}return{"GET /tcf/vendors":async(o,a)=>h(async()=>{let{denied:c}=await s(o);if(c)return c;let l=await _e();if(!l)return _(503,{error:"GVL not available. Enable tcf.enabled in config."});let d=new URL(o.url),u=d.searchParams.get("q")?.toLowerCase(),p=Math.max(1,parseInt(d.searchParams.get("page")??"1",10)),g=Math.min(200,Math.max(1,parseInt(d.searchParams.get("limit")??"100",10))),E=Object.values(l.vendors),f=u?E.filter(T=>T.name.toLowerCase().includes(u)):E,y=f.length,w=Math.max(1,Math.ceil(y/g)),R=Math.min(p,w),I=(R-1)*g;return _(200,{vendorListVersion:l.vendorListVersion,total:y,page:R,totalPages:w,vendors:f.slice(I,I+g)})}),"GET /tcf/purposes":async(o,a)=>h(async()=>{let{denied:c}=await s(o);if(c)return c;let l=await _e();return l?_(200,{purposes:Object.values(l.purposes)}):_(503,{error:"GVL not available. Enable tcf.enabled in config."})}),"GET /tcf/registration-status":async(o,a)=>h(async()=>{let{denied:c}=await r(o);if(c)return c;if(!n)return _(200,{enabled:!1,confirmed:!0});let l=await i.getSettings("default"),d=ne(n.enabled,{cmpId:n.cmpId,cmpVersion:n.cmpVersion,publisherCC:n.publisherCC},l.tcfConfirmation),p=new URL(o.url).searchParams.get("refresh")==="true",g=n.enabled?await ke(p):null,E=g?.cmps[String(n.cmpId)];return _(200,{enabled:n.enabled,cmpId:n.cmpId,cmpVersion:n.cmpVersion,...n.publisherCC?{publisherCC:n.publisherCC}:{},confirmed:d,...l.tcfConfirmation?{confirmedAt:l.tcfConfirmation.confirmedAt,confirmedBy:l.tcfConfirmation.confirmedBy}:{},...g?{cmpListVersion:g.cmpListVersion}:{},...n.enabled?{cmpListEntry:{found:!!E,deregistered:!!E?.deletedDate,...E?.deletedDate?{deletedDate:E.deletedDate}:{}}}:{}})}),"POST /tcf/confirm-registration":async(o,a)=>h(async()=>{let{user:c,denied:l}=await r(o);if(l)return l;if(!n?.enabled)return m(400,"TCF is not enabled in the server config \u2014 nothing to confirm.");if(!(await P(o))?.acknowledge)return m(400,"acknowledge: true is required to confirm TCF registration.");let p=(await ke())?.cmps[String(n.cmpId)];if(p?.deletedDate)return m(409,`cmpId ${n.cmpId} was deregistered on ${p.deletedDate} per IAB's CMP List. Registration confirmation blocked.`);if(!p)return m(404,`cmpId ${n.cmpId} was not found in the cached CMP List. Registration can take time to propagate after signing up with IAB Europe \u2014 try "Refresh Status" and confirm again shortly.`);let g=qe({cmpId:n.cmpId,cmpVersion:n.cmpVersion,publisherCC:n.publisherCC}),E=await i.updateSettings("default",{tcfConfirmation:{configHash:g,confirmedAt:new Date().toISOString(),confirmedBy:c.sub}});return _(200,{confirmed:!0,tcfConfirmation:E.tcfConfirmation})})}}function eo(i,e,t,n){async function s(r){let o=await O(r,i,e,t);return{user:o,denied:D(o,"settings:update")}}return{"GET /gpp/registration-status":async(r,o)=>h(async()=>{let{denied:a}=await s(r);if(a)return a;if(!n)return _(200,{enabled:!1,confirmed:!0});let c=await i.getSettings("default"),l=ne(n.enabled,{cmpId:n.cmpId,cmpVersion:n.cmpVersion},c.gppConfirmation);return _(200,{enabled:n.enabled,cmpId:n.cmpId,cmpVersion:n.cmpVersion,confirmed:l,...c.gppConfirmation?{confirmedAt:c.gppConfirmation.confirmedAt,confirmedBy:c.gppConfirmation.confirmedBy}:{}})}),"POST /gpp/confirm-registration":async(r,o)=>h(async()=>{let{user:a,denied:c}=await s(r);if(c)return c;if(!n?.enabled)return m(400,"GPP is not enabled in the server config \u2014 nothing to confirm.");if(!(await P(r))?.acknowledge)return m(400,"acknowledge: true is required to confirm GPP registration.");let d=qe({cmpId:n.cmpId,cmpVersion:n.cmpVersion}),u=await i.updateSettings("default",{gppConfirmation:{configHash:d,confirmedAt:new Date().toISOString(),confirmedBy:a.sub}});return _(200,{confirmed:!0,gppConfirmation:u.gppConfirmation})})}}var to=["mandatory","consent","legitimate_interest"],no=["sale","sharing","sensitive"];function sl(i){if(!i||typeof i!="object"||Array.isArray(i))return{valid:!1,error:"cookies must be an object keyed by parameter id"};for(let[e,t]of Object.entries(i)){if(!e.trim())return{valid:!1,error:"cookie id must be non-empty"};if(!t||typeof t!="object")return{valid:!1,error:`cookies["${e}"] must be an object`};let n=t;if(!an.includes(n.purpose))return{valid:!1,error:`cookies["${e}"].purpose must be one of: ${an.join(", ")}`};if(typeof n.listenGpc!="boolean")return{valid:!1,error:`cookies["${e}"].listenGpc must be a boolean`};if(n.preGrant!==void 0&&typeof n.preGrant!="boolean")return{valid:!1,error:`cookies["${e}"].preGrant must be a boolean`};if(n.cpraCategory!==void 0&&!no.includes(n.cpraCategory))return{valid:!1,error:`cookies["${e}"].cpraCategory must be one of: ${no.join(", ")}`}}return{valid:!0}}function il(i){if(!i||typeof i!="object"||Array.isArray(i))return{valid:!1,error:"categories must be an object keyed by category id"};let e=Object.entries(i);if(e.length===0)return{valid:!1,error:"at least one category is required"};for(let[t,n]of e){if(!n||typeof n!="object")return{valid:!1,error:`categories["${t}"] must be an object`};let s=n;if(typeof s.heading!="string"||!s.heading.trim())return{valid:!1,error:`categories["${t}"].heading is required`};if(typeof s.htmlText!="string")return{valid:!1,error:`categories["${t}"].htmlText is required`};if(!to.includes(s.legalBasis))return{valid:!1,error:`categories["${t}"].legalBasis must be one of: ${to.join(", ")}`};if(!Array.isArray(s.cookies))return{valid:!1,error:`categories["${t}"].cookies must be an array of parameter ids`}}return{valid:!0}}function rl(i,e){let t=new Map;for(let n of Object.keys(i))t.set(n,0);for(let n of Object.values(e))for(let s of n.cookies)t.set(s,(t.get(s)??0)+1);for(let[n,s]of t){if(s===0)return{valid:!1,error:`Parameter "${n}" is not assigned to any category`};if(s>1)return{valid:!1,error:`Parameter "${n}" is assigned to more than one category`}}return{valid:!0}}function ol(i,e){let t=new Map;for(let[s,r]of Object.entries(e))for(let o of r.cookies)t.set(o,s);let n=[];for(let[s,r]of Object.entries(i)){let o=t.get(s);if(!o)continue;let a=e[o].legalBasis,c=r.purpose;if(a==="mandatory"&&c!=="necessary")return{valid:!1,error:`Parameter "${s}" has purpose "${c??"unknown"}" but is assigned to a Strictly Necessary category \u2014 this grants a tracking parameter with no consent gate.`};a!=="mandatory"&&c==="necessary"&&n.push(`Parameter "${s}" has purpose "necessary" but is assigned to a non-mandatory category \u2014 this may break site functionality since it can be denied.`)}return{valid:!0,...n.length>0?{warnings:n}:{}}}function Gn(i,e){let t=sl(i);if(!t.valid)return t;let n=il(e);if(!n.valid)return n;let s=i,r=e,o=rl(s,r);return o.valid?ol(s,r):o}function so(i,e,t,n="default"){async function s(r,o){let a=await O(r,i,e,t);return{user:a,denied:D(a,o)}}return{"GET /consent-templates":async r=>h(async()=>{let{denied:o}=await s(r,"profile:view");return o||_(200,await i.getConsentTemplates(n))}),"GET /consent-templates/:id":async(r,o)=>h(async()=>{let{denied:a}=await s(r,"profile:view");if(a)return a;let c=await i.getConsentTemplate(o.id);return c?_(200,c):m(404,"Consent template not found")}),"POST /consent-templates":async r=>h(async()=>{let{denied:o}=await s(r,"profile:create");if(o)return o;let a=await P(r);if(!a?.name||typeof a.name!="string")return m(400,"name is required");let c=Gn(a.cookies,a.categories);if(!c.valid)return m(400,c.error);let l=await i.createConsentTemplate({tenantId:n,name:a.name,cookies:a.cookies,categories:a.categories});return _(201,l)}),"PUT /consent-templates/:id":async(r,o)=>h(async()=>{let{denied:a}=await s(r,"profile:update");if(a)return a;let c=o.id,l=await P(r),d=await i.getConsentTemplate(c);if(!d)return m(404,"Consent template not found");let u={};l?.name!==void 0&&typeof l.name=="string"&&(u.name=l.name);let p=l?.cookies!==void 0,g=l?.categories!==void 0,E=p?l.cookies:d.cookies,f=g?l.categories:d.categories;if(p||g){let w=Gn(E,f);if(!w.valid)return m(400,w.error);p&&(u.cookies=E),g&&(u.categories=f);let R=await i.findProfilesUsingConsentTemplate(c),I=[];for(let T of R){if(!T.complianceGroup)continue;Oe(E,f,T.complianceGroup).valid||I.push({id:T.id,name:T.name,complianceGroup:T.complianceGroup})}if(I.length>0)return m(422,"This change would break compliance for affected profiles",{blockingProfiles:I})}let y=await i.updateConsentTemplate(c,u);return _(200,y)}),"DELETE /consent-templates/:id":async(r,o)=>h(async()=>{let{denied:a}=await s(r,"profile:delete");if(a)return a;let c=o.id,l=await i.findProfilesUsingConsentTemplate(c);return l.length>0?m(422,"Cannot delete: template is used by profiles",{profiles:l.map(d=>({id:d.id,name:d.name,isActive:d.isActive}))}):(await i.deleteConsentTemplate(c),_(200,{ok:!0}))}),"POST /consent-templates/:id/copy":async(r,o)=>h(async()=>{let{denied:a}=await s(r,"profile:create");if(a)return a;let c=await P(r),l=await i.getConsentTemplate(o.id);if(!l)return m(404,"Consent template not found");let d=typeof c?.name=="string"&&c.name.trim()?c.name.trim():`${l.name} (Copy)`,u=await i.copyConsentTemplate(o.id,d);return _(201,u)}),"GET /consent-templates/:id/profile-usage":async(r,o)=>h(async()=>{let{denied:a}=await s(r,"profile:view");if(a)return a;let c=await i.findProfilesUsingConsentTemplate(o.id);return _(200,c)})}}function io(i,e,t,n="default"){async function s(r,o){let a=await O(r,i,e,t);return{user:a,denied:D(a,o)}}return{"GET /ui-templates":async r=>h(async()=>{let{denied:o}=await s(r,"profile:view");return o||_(200,await i.getUITemplates(n))}),"GET /ui-templates/:id":async(r,o)=>h(async()=>{let{denied:a}=await s(r,"profile:view");if(a)return a;let c=await i.getUITemplate(o.id);return c?_(200,c):m(404,"UI template not found")}),"POST /ui-templates":async r=>h(async()=>{let{denied:o}=await s(r,"profile:create");if(o)return o;let a=await P(r);if(!a?.name||typeof a.name!="string")return m(400,"name is required");if(!a.mainBanner||!a.gpcBanner||!a.preferenceModal)return m(400,"mainBanner, gpcBanner, and preferenceModal are required");let c=await i.createUITemplate({tenantId:n,name:a.name,mainBanner:a.mainBanner,gpcBanner:a.gpcBanner,preferenceModal:a.preferenceModal});return _(201,c)}),"PUT /ui-templates/:id":async(r,o)=>h(async()=>{let{denied:a}=await s(r,"profile:update");if(a)return a;let c=await P(r),l={};typeof c?.name=="string"&&(l.name=c.name),c?.mainBanner&&(l.mainBanner=c.mainBanner),c?.gpcBanner&&(l.gpcBanner=c.gpcBanner),c?.preferenceModal&&(l.preferenceModal=c.preferenceModal);let d=await i.updateUITemplate(o.id,l);return _(200,d)}),"DELETE /ui-templates/:id":async(r,o)=>h(async()=>{let{denied:a}=await s(r,"profile:delete");if(a)return a;let c=o.id,l=await i.findProfilesUsingUITemplate(c);return l.length>0?m(422,"Cannot delete: template is used by profiles",{profiles:l.map(d=>({id:d.id,name:d.name,isActive:d.isActive}))}):(await i.deleteUITemplate(c),_(200,{ok:!0}))}),"POST /ui-templates/:id/copy":async(r,o)=>h(async()=>{let{denied:a}=await s(r,"profile:create");if(a)return a;let c=await P(r),l=await i.getUITemplate(o.id);if(!l)return m(404,"UI template not found");let d=typeof c?.name=="string"&&c.name.trim()?c.name.trim():`${l.name} (Copy)`,u=await i.copyUITemplate(o.id,d);return _(201,u)}),"GET /ui-templates/:id/profile-usage":async(r,o)=>h(async()=>{let{denied:a}=await s(r,"profile:view");if(a)return a;let c=await i.findProfilesUsingUITemplate(o.id);return _(200,c)})}}function ro(i,e,t,n="default"){async function s(r,o){let a=await O(r,i,e,t);return{user:a,denied:D(a,o)}}return{"GET /analytics/opt-in":async(r,o)=>h(async()=>{let{denied:a}=await s(r,"stats:view");if(a)return a;let c=new URL(r.url),l=c.searchParams.get("tenantId")??n,d={},u=c.searchParams.get("profileId"),p=c.searchParams.get("complianceGroup"),g=c.searchParams.get("from"),E=c.searchParams.get("to"),f=c.searchParams.get("locale");u&&(d.profileId=u),p&&(d.complianceGroup=p),g&&(d.from=g),E&&(d.to=E),f&&(d.locale=f);let y=await i.getOptInStats(l,d);return _(200,y)})}}var ce="\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022";function oo(i){let e=i.storage,t=i.auth,n=i.compliance,s=i.dashboard;return{basePath:i.basePath,dashboard:typeof s=="object"?{enabled:s.enabled,path:s.path}:s,...e?{storage:{driver:e.driver,...e.path!==void 0?{path:e.path}:{},...e.uri!==void 0?{uri:ce}:{},...e.database!==void 0?{database:e.database}:{},...e.host!==void 0?{host:e.host}:{},...e.port!==void 0?{port:e.port}:{},...e.user!==void 0?{user:e.user}:{},...e.password!==void 0?{password:ce}:{},...e.poolMax!==void 0?{poolMax:e.poolMax}:{},...e.statementTimeoutMs!==void 0?{statementTimeoutMs:e.statementTimeoutMs}:{},...e.idleInTransactionTimeoutMs!==void 0?{idleInTransactionTimeoutMs:e.idleInTransactionTimeoutMs}:{}}}:{},...t?{auth:{mode:t.mode,...t.adminEmail!==void 0?{adminEmail:t.adminEmail}:{},...t.adminPassword!==void 0?{adminPassword:ce}:{},...t.masterSecret!==void 0?{masterSecret:ce}:{},...t.oidc?{oidc:{issuer:t.oidc.issuer,clientId:t.oidc.clientId,clientSecret:ce,redirectUri:t.oidc.redirectUri}}:{},...t.saml?{saml:{issuer:t.saml.issuer,entryPoint:t.saml.entryPoint,cert:ce,callbackUrl:t.saml.callbackUrl}}:{}}}:{},...i.rateLimit?{rateLimit:i.rateLimit}:{},...n?{compliance:{...n.type!==void 0?{type:n.type}:{},...n.gpc!==void 0?{gpc:n.gpc}:{},...n.geoDataProvider!==void 0?{geoDataProvider:typeof n.geoDataProvider=="function"?"<custom function>":n.geoDataProvider}:{},...n.complianceMap!==void 0?{complianceMap:typeof n.complianceMap=="object"?`<custom map, ${Object.keys(n.complianceMap.countries).length} countries, ${n.complianceMap.override===!1?"merged over":"replaces"} the embedded map>`:n.complianceMap}:{},...n.tcf?{tcf:n.tcf}:{},...n.gpp?{gpp:n.gpp}:{},...n.dataRetention?{dataRetention:n.dataRetention}:{},...n.parentalConsentTokenTtlDays!==void 0?{parentalConsentTokenTtlDays:n.parentalConsentTokenTtlDays}:{},...n.dataSigningHash!==void 0?{dataSigningHash:ce}:{}}}:{},...i.multiTenant?{multiTenant:i.multiTenant}:{},...i.plugins?{plugins:{count:i.plugins.length}}:{},...i.maxBodySize!==void 0?{maxBodySize:i.maxBodySize}:{},...i.trustedProxies?{trustedProxies:i.trustedProxies}:{},...i.branding?{branding:i.branding}:{},...i.s3Api?{s3Api:{enabled:i.s3Api.enabled,region:i.s3Api.region,bucketName:i.s3Api.bucketName,accessKeyId:ce,secretAccessKey:ce}}:{}}}function ao(i,e,t,n,s,r){async function o(c){let l=await O(c,e,t,n);return{denied:D(l,"settings:update")}}async function a(){return(await e.getSettings("default")).setupCompleted===!0?m(409,"Setup has already been completed"):null}return{"GET /setup/status":async(c,l)=>h(async()=>{let{denied:d}=await o(c);if(d)return d;let u=await e.getSettings("default");return _(200,{completed:u.setupCompleted===!0})}),"GET /setup/config":async(c,l)=>h(async()=>{let{denied:d}=await o(c);return d||_(200,{config:oo(s),...r})}),"GET /setup/compliance-groups":async(c,l)=>h(async()=>{let{denied:d}=await o(c);if(d)return d;let u=on.map(p=>({id:p,...it[p]}));return _(200,u)}),"POST /setup/seed-profiles":async(c,l)=>h(async()=>{let{denied:d}=await o(c);if(d)return d;let u=await a();if(u)return u;let g=(await P(c))?.groups;if(!Array.isArray(g)||g.some(f=>typeof f!="string"))return m(400,"groups must be an array of compliance group ids");let E=g.filter(f=>!on.includes(f));return E.length>0?m(400,`Unknown compliance group id(s): ${E.join(", ")}`):(await Promise.all(g.map(f=>i.seedDefaultProfile(f))),await e.updateSettings("default",{profilesSeeded:!0}),_(200,{seeded:g}))}),"POST /setup/complete":async(c,l)=>h(async()=>{let{denied:d}=await o(c);if(d)return d;let u=await a();if(u)return u;if((await e.getSettings("default")).profilesSeeded!==!0)return m(409,"POST /setup/seed-profiles must be called at least once (an empty groups: [] call is a valid pass) before setup can be completed");let g=await e.updateSettings("default",{setupCompleted:!0});return _(200,g)})}}import{createHash as al}from"crypto";async function co(i,e,t){let n=i.headers.get("authorization");if(!n?.startsWith("Bearer ck_"))return t;let s=n.slice(7),r=al("sha256").update(s).digest("hex"),o=await e.getApiKeyByHash(r);return!o||!o.isActive?t:o.expireBy&&new Date(o.expireBy)<=new Date?(await e.revokeApiKey(o.id).catch(()=>{}),t):o.tenantId}var Xe=class{constructor(e=6e4,t=60){this.windowMs=e;this.maxRequests=t;setInterval(()=>{let n=Date.now();for(let[s,r]of this.store)n>=r.resetAt&&this.store.delete(s)},this.windowMs).unref()}windowMs;maxRequests;store=new Map;check(e){let t=Date.now(),n=this.store.get(e);return!n||t>=n.resetAt?(this.store.set(e,{count:1,resetAt:t+this.windowMs}),!0):(n.count+=1,n.count<=this.maxRequests)}checkRequest(e,t){return this.check(`ip:${e}`)?t?this.check(`profile:${t}`):!0:!1}};var qn=class{constructor(e){this.options=e;e.eventBus.on("consent:created",this.onCreated),e.eventBus.on("consent:updated",this.onUpdated)}options;onCreated=e=>this.fire(e,void 0);onUpdated=({previous:e,current:t})=>this.fire(t,e);fire(e,t){let n=e.consentJson[this.options.id]??"denied",s=t?.consentJson[this.options.id]??"denied";if(n===s)return;let r={visitorId:e.visitorId,cookieId:this.options.id,status:n,record:e};n==="granted"?this.options.onGrant?.(r):this.options.onDeny?.(r)}destroy(){this.options.eventBus.off("consent:created",this.onCreated),this.options.eventBus.off("consent:updated",this.onUpdated)}};function lo(i,e){return e.length>0&&e.every(t=>i[t]==="granted")}var Kn=class{constructor(e){this.options=e;e.eventBus.on("consent:created",this.onCreated),e.eventBus.on("consent:updated",this.onUpdated)}options;onCreated=e=>{this.fire(e,void 0)};onUpdated=({previous:e,current:t})=>{this.fire(t,e)};async fire(e,t){let s=(await this.options.profiles.getResolved(e.profileId))?.preferenceModal.categories[this.options.categoryId]?.cookies??[];if(s.length===0)return;let r=lo(e.consentJson,s)?"granted":"denied",o=t&&lo(t.consentJson,s)?"granted":"denied";if(r===o)return;let a={visitorId:e.visitorId,categoryId:this.options.categoryId,status:r,record:e};r==="granted"?this.options.onGrant?.(a):this.options.onDeny?.(a)}destroy(){this.options.eventBus.off("consent:created",this.onCreated),this.options.eventBus.off("consent:updated",this.onUpdated)}};var Yn=dl(go(import.meta.url)),Ch=go(new URL("./dashboard",import.meta.url)),fo={".html":"text/html; charset=utf-8",".js":"application/javascript; charset=utf-8",".mjs":"application/javascript; charset=utf-8",".css":"text/css; charset=utf-8",".json":"application/json",".png":"image/png",".svg":"image/svg+xml",".ico":"image/x-icon",".woff2":"font/woff2",".woff":"font/woff",".ttf":"font/ttf"};async function ul(i,e,t,n,s){let r=i.slice(e.length)||"/";if(r.startsWith("/api/")||r.startsWith("/admin/"))return!1;let o=r==="/"||r===""||!en(r)?le(t,"index.html"):le(t,r);if(!o.startsWith(t))return!1;try{let a=await Ze(o),c=en(o).toLowerCase(),l=c===".html"||c==="",d=fo[c]??"application/octet-stream";return n.writeHead(200,{"Content-Type":d}),n.end(l?Le(a,s):a),!0}catch{return!1}}async function uo(i,e){try{let t=await Ze(i);return new Response(t,{headers:{"Content-Type":e,"Cache-Control":"public, max-age=3600"}})}catch{return new Response("Not Found",{status:404})}}function pl(i){let e=[],t=i.split("/").map(n=>n.startsWith(":")?(e.push(n.slice(1)),"([^/]+)"):n.replace(/[.*+?^${}()|[\]\\]/g,"\\$&"));return{pattern:new RegExp(`^${t.join("\\/")}$`),paramNames:e}}function ml(i){let t=`<!DOCTYPE html>
160
162
  <html lang="en">
161
163
  <head>
162
164
  <meta charset="UTF-8">
@@ -191,13 +193,13 @@ ${l.map(d=>` (${Q(c)},${Q(d)})`).join(`,
191
193
  </a>
192
194
  </div>
193
195
  </body>
194
- </html>`;return new Response(t,{status:404,headers:{"Content-Type":"text/html; charset=utf-8"}})}function jc(i,e,t,n,s){let r=[];for(let o of e)for(let[a,c]of Object.entries(o.routes)){let l=a.indexOf(" "),d=a.slice(0,l).toUpperCase(),u=a.slice(l+1),{pattern:p,paramNames:g}=kc(`${o.prefix}${u}`);r.push({method:d,pattern:p,paramNames:g,handler:c,rateLimit:o.rateLimit??!1,originCheck:o.originCheck??!1})}return async o=>{let a=Ni(o);if(a)return a;let l=new URL(o.url).pathname,d=o.method.toUpperCase();if(t&&r.find(g=>g.rateLimit&&l.match(g.pattern))){let g=o.headers.get("x-real-ip")??"";if(!t.checkRequest(g))return Ve(m(429,"Too Many Requests"),o)}for(let p of r){if(p.method!==d)continue;let g=l.match(p.pattern);if(!g)continue;if(p.originCheck){let h=await s(o),{adminAllowedOrigins:w}=await n.getSettings(h);if(!Wt(o,w??[]))return Ve(m(403,"Origin not allowed for the admin API"),o)}let E={};p.paramNames.forEach((h,w)=>{let R=g[w+1];R!=null&&(E[h]=R)});let _=await p.handler(o,E);return Tn(Ve(_,o))}let u=l.startsWith(i)&&!l.startsWith(`${i}/api/`)&&!l.startsWith(`${i}/admin/`);return Tn(Ve(u?Mc(i):m(404,"Not Found"),o))}}function $r(i){return typeof i=="object"&&i!==null&&!Array.isArray(i)}function Vr(i,e){let t={...i};for(let n of Object.keys(e)){let s=e[n],r=t[n];s!==void 0&&$r(s)&&$r(r)?t[n]=Vr(r,s):s!==void 0&&(t[n]=s)}return t}var xc={dashboard:!0,basePath:process.env.CONSENTI_BASE_PATH??"/consenti",...process.env.CONSENTI_MAX_BODY_SIZE?{maxBodySize:Number(process.env.CONSENTI_MAX_BODY_SIZE)}:{},storage:{driver:process.env.CONSENTI_DB_DRIVER??"json",path:process.env.CONSENTI_DB_PATH??"./consenti-data.json",...process.env.CONSENTI_DB_URI?{uri:process.env.CONSENTI_DB_URI}:{},...process.env.CONSENTI_DB_DATABASE?{database:process.env.CONSENTI_DB_DATABASE}:{},...process.env.CONSENTI_DB_HOST?{host:process.env.CONSENTI_DB_HOST}:{},...process.env.CONSENTI_DB_PORT?{port:Number(process.env.CONSENTI_DB_PORT)}:{},...process.env.CONSENTI_DB_USER?{user:process.env.CONSENTI_DB_USER}:{},...process.env.CONSENTI_DB_PASSWORD?{password:process.env.CONSENTI_DB_PASSWORD}:{}},auth:{mode:"local",adminEmail:process.env.CONSENTI_ADMIN_EMAIL??"user@consenti.dev",adminPassword:process.env.CONSENTI_ADMIN_PASSWORD??"Consenti@123",...process.env.CONSENTI_ADMIN_MASTER_SECRET?{masterSecret:process.env.CONSENTI_ADMIN_MASTER_SECRET}:{}},...process.env.CONSENTI_DATA_SIGNING_HASH?{compliance:{dataSigningHash:process.env.CONSENTI_DATA_SIGNING_HASH}}:{},rateLimit:{windowMs:process.env.CONSENTI_RATE_LIMIT_WINDOW_MS?Number(process.env.CONSENTI_RATE_LIMIT_WINDOW_MS):6e4,maxRequests:process.env.CONSENTI_RATE_LIMIT_MAX_REQUESTS?Number(process.env.CONSENTI_RATE_LIMIT_MAX_REQUESTS):60},branding:{appLogoPath:"./logo-dark.svg",appName:"Consenti",hidePoweredBy:!1}};function Fr(){return Uc(32).toString("hex")}function $c(i){switch(i.driver){case"mongodb":return new Ne(i);case"mysql":return new Me(i);case"postgresql":return new $e(i);case"node:sqlite":return new ot(i);case"better-sqlite3":case"sqlite":return new Ee(i);case"node-sqlite3-wasm":return new rt(i);default:return new ft(i)}}function b_(i){let e=Vr(xc,i),t=e.storage?.driver==="json",n=!i.auth&&!process.env.CONSENTI_ADMIN_PASSWORD;if(process.env.NODE_ENV==="production"){let U=[];if(n&&U.push('auth.adminPassword / CONSENTI_ADMIN_PASSWORD (currently the published default "Consenti@123")'),e.auth?.masterSecret||U.push("auth.masterSecret / CONSENTI_ADMIN_MASTER_SECRET"),e.compliance?.dataSigningHash||U.push("compliance.dataSigningHash / CONSENTI_DATA_SIGNING_HASH"),U.length>0)throw new Error(`[Consenti] Refusing to boot with NODE_ENV=production while unset or default: ${U.join("; ")}. These are fine to leave auto-generated for a local trial, but not for production \u2014 set them explicitly. See SECURITY.md.`)}t&&console.warn("[Consenti] Using the JSON file storage driver \u2014 fine for development, but switch to a database driver (node:sqlite, postgresql, mysql, mongodb) for production. Set storage.driver or CONSENTI_DB_DRIVER."),n&&console.warn("[Consenti] No auth config or CONSENTI_ADMIN_PASSWORD env var \u2014 using demo credentials. Set CONSENTI_ADMIN_EMAIL and CONSENTI_ADMIN_PASSWORD before going to production.");let s=e.storage,r=e.auth;r.masterSecret||(r.masterSecret=Fr(),console.warn("[Consenti] auth.masterSecret not set \u2014 generated a random one for this process. Admin sessions will be invalidated on every restart. Set auth.masterSecret or CONSENTI_ADMIN_MASTER_SECRET for production."));let a=ne(s.path??"./consenti-data",s.driver??"json").profilesDir,c=$c(s),l=new Dc,d=new _t(c),u=new ht(c),p=new yt(c),g=new wt(c),E=new Et(c),_=new Ct(c),h=new St(c),w=new et;e.plugins?.length&&w.register(e.plugins),e.compliance||(e.compliance={});let R=e.compliance;R.dataSigningHash||(R.dataSigningHash=Fr(),console.warn("[Consenti] compliance.dataSigningHash not set \u2014 generated a random one for this process. Ownership cookies, parental-consent tokens, and stored consent-record signatures will all be invalidated on every restart. Set compliance.dataSigningHash or CONSENTI_DATA_SIGNING_HASH for production."));let I=R.type??"auto",T=I==="auto",O=e.localeJsonCache?.enabled?new Dt(e.localeJsonCache.dir):void 0,b=T?new bt(R.geoDataProvider??"default",qn):void 0,M=b?ii(R.complianceMap,b):void 0;I==="auto"&&!R.geoDataProvider&&console.info("[Consenti] compliance.geoDataProvider not set \u2014 defaulting to timezone+language heuristic"),I!=="auto"&&R.geoDataProvider&&console.warn("[Consenti] compliance.geoDataProvider is ignored when compliance.type is a fixed group"),I!=="auto"&&R.complianceMap&&console.warn("[Consenti] compliance.complianceMap is ignored when compliance.type is a fixed group");let L=new Ot(d,E,"default",c,l,O,a,e.handleCache,e.s3Api),X=new Mt(p,"default",l,R.dataSigningHash),z=new jt(g,"default"),Te=new kt(u,p,d,E,"default",e.compliance,w,l,R.tcf,c,R.dataSigningHash,R.gpp),Nn=new xt(_,E),x=r.masterSecret??"",kn=new $t(c,x),Kt=e.rateLimit,Wr=Kt.enabled===!1?null:new qe(Kt.windowMs,Kt.maxRequests),Jr=new qe(15*6e4,10),Mn=e.multiTenant?.enabled===!0?U=>Mr(U,c,"default"):async U=>"default",$=e.basePath,jn=`${$}/api/v1`,xn=`${$}/admin/v1`,Yt=`${$}/api`,Ke=!!e.dashboard,de=ae(Ln,"dashboard"),Ye=Ke?Qn(zn(de,$,e.branding,e.compliance,r.mode)):"",$n=`<!DOCTYPE html><html><head><title>Consenti API Docs</title>
196
+ </html>`;return new Response(t,{status:404,headers:{"Content-Type":"text/html; charset=utf-8"}})}function gl(i,e,t,n,s){let r=[];for(let o of e)for(let[a,c]of Object.entries(o.routes)){let l=a.indexOf(" "),d=a.slice(0,l).toUpperCase(),u=a.slice(l+1),{pattern:p,paramNames:g}=pl(`${o.prefix}${u}`);r.push({method:d,pattern:p,paramNames:g,handler:c,rateLimit:o.rateLimit??!1,originCheck:o.originCheck??!1})}return async o=>{let a=rr(o);if(a)return a;let l=new URL(o.url).pathname,d=o.method.toUpperCase();if(t&&r.find(g=>g.rateLimit&&l.match(g.pattern))){let g=o.headers.get("x-real-ip")??"";if(!t.checkRequest(g))return Ye(m(429,"Too Many Requests"),o)}for(let p of r){if(p.method!==d)continue;let g=l.match(p.pattern);if(!g)continue;if(p.originCheck){let y=await s(o),{adminAllowedOrigins:w}=await n.getSettings(y);if(!Qt(o,w??[]))return Ye(m(403,"Origin not allowed for the admin API"),o)}let E={};p.paramNames.forEach((y,w)=>{let R=g[w+1];R!=null&&(E[y]=R)});let f=await p.handler(o,E);return Hn(Ye(f,o))}let u=l.startsWith(i)&&!l.startsWith(`${i}/api/`)&&!l.startsWith(`${i}/admin/`);return Hn(Ye(u?ml(i):m(404,"Not Found"),o))}}function po(i){return typeof i=="object"&&i!==null&&!Array.isArray(i)}function _o(i,e){let t={...i};for(let n of Object.keys(e)){let s=e[n],r=t[n];s!==void 0&&po(s)&&po(r)?t[n]=_o(r,s):s!==void 0&&(t[n]=s)}return t}var fl={dashboard:!0,basePath:process.env.CONSENTI_BASE_PATH??"/consenti",...process.env.CONSENTI_MAX_BODY_SIZE?{maxBodySize:Number(process.env.CONSENTI_MAX_BODY_SIZE)}:{},storage:{driver:process.env.CONSENTI_DB_DRIVER??"json",path:process.env.CONSENTI_DB_PATH??"./consenti-data.json",...process.env.CONSENTI_DB_URI?{uri:process.env.CONSENTI_DB_URI}:{},...process.env.CONSENTI_DB_DATABASE?{database:process.env.CONSENTI_DB_DATABASE}:{},...process.env.CONSENTI_DB_HOST?{host:process.env.CONSENTI_DB_HOST}:{},...process.env.CONSENTI_DB_PORT?{port:Number(process.env.CONSENTI_DB_PORT)}:{},...process.env.CONSENTI_DB_USER?{user:process.env.CONSENTI_DB_USER}:{},...process.env.CONSENTI_DB_PASSWORD?{password:process.env.CONSENTI_DB_PASSWORD}:{}},auth:{mode:"local",adminEmail:process.env.CONSENTI_ADMIN_EMAIL??"user@consenti.dev",adminPassword:process.env.CONSENTI_ADMIN_PASSWORD??"Consenti@123",...process.env.CONSENTI_ADMIN_MASTER_SECRET?{masterSecret:process.env.CONSENTI_ADMIN_MASTER_SECRET}:{}},...process.env.CONSENTI_DATA_SIGNING_HASH?{compliance:{dataSigningHash:process.env.CONSENTI_DATA_SIGNING_HASH}}:{},rateLimit:{windowMs:process.env.CONSENTI_RATE_LIMIT_WINDOW_MS?Number(process.env.CONSENTI_RATE_LIMIT_WINDOW_MS):6e4,maxRequests:process.env.CONSENTI_RATE_LIMIT_MAX_REQUESTS?Number(process.env.CONSENTI_RATE_LIMIT_MAX_REQUESTS):60},branding:{appLogoPath:"./logo-dark.svg",appName:"Consenti",hidePoweredBy:!1}};function mo(){return ll(32).toString("hex")}function _l(i){switch(i.driver){case"mongodb":return new Fe(i);case"mysql":return new Be(i);case"postgresql":return new Je(i);case"node:sqlite":return new pt(i);case"better-sqlite3":case"sqlite":return new Te(i);case"node-sqlite3-wasm":return new ut(i);default:return new Rt(i)}}function Ih(i){let e=_o(fl,i),t=e.storage?.driver==="json",n=!i.auth&&!process.env.CONSENTI_ADMIN_PASSWORD;if(process.env.NODE_ENV==="production"){let b=[];if(n&&b.push('auth.adminPassword / CONSENTI_ADMIN_PASSWORD (currently the published default "Consenti@123")'),e.auth?.masterSecret||b.push("auth.masterSecret / CONSENTI_ADMIN_MASTER_SECRET"),e.compliance?.dataSigningHash||b.push("compliance.dataSigningHash / CONSENTI_DATA_SIGNING_HASH"),b.length>0)throw new Error(`[Consenti] Refusing to boot with NODE_ENV=production while unset or default: ${b.join("; ")}. These are fine to leave auto-generated for a local trial, but not for production \u2014 set them explicitly. See SECURITY.md.`)}t&&console.warn("[Consenti] Using the JSON file storage driver \u2014 fine for development, but switch to a database driver (node:sqlite, postgresql, mysql, mongodb) for production. Set storage.driver or CONSENTI_DB_DRIVER."),n&&console.warn("[Consenti] No auth config or CONSENTI_ADMIN_PASSWORD env var \u2014 using demo credentials. Set CONSENTI_ADMIN_EMAIL and CONSENTI_ADMIN_PASSWORD before going to production.");let s=e.storage,r=e.auth;r.masterSecret||(r.masterSecret=mo(),console.warn("[Consenti] auth.masterSecret not set \u2014 generated a random one for this process. Admin sessions will be invalidated on every restart. Set auth.masterSecret or CONSENTI_ADMIN_MASTER_SECRET for production."));let a=ie(s.path??"./consenti-data",s.driver??"json").profilesDir,c=_l(s),l=new cl,d=new St(c),u=new Ct(c),p=new It(c),g=new Tt(c),E=new At(c),f=new Ot(c),y=new Pt(c),w=new ot;e.plugins?.length&&w.register(e.plugins),e.compliance||(e.compliance={});let R=e.compliance;R.dataSigningHash||(R.dataSigningHash=mo(),console.warn("[Consenti] compliance.dataSigningHash not set \u2014 generated a random one for this process. Ownership cookies, parental-consent tokens, and stored consent-record signatures will all be invalidated on every restart. Set compliance.dataSigningHash or CONSENTI_DATA_SIGNING_HASH for production."));let I=R.type??"auto",T=I==="auto",v=e.localeJsonCache?.enabled?new jt(e.localeJsonCache.dir):void 0,N=T?new xt(R.geoDataProvider??"default",cs):void 0,L=N?vi(R.complianceMap,N):void 0;I==="auto"&&!R.geoDataProvider&&console.info("[Consenti] compliance.geoDataProvider not set \u2014 defaulting to timezone+language heuristic"),I!=="auto"&&R.geoDataProvider&&console.warn("[Consenti] compliance.geoDataProvider is ignored when compliance.type is a fixed group"),I!=="auto"&&R.complianceMap&&console.warn("[Consenti] compliance.complianceMap is ignored when compliance.type is a fixed group");let U=new Mt(d,E,"default",c,l,v,a,e.handleCache,e.s3Api),V=new Vt(p,"default",l,R.dataSigningHash),Q=new Wt(g,"default"),be=new Bt(u,p,d,E,"default",e.compliance,w,l,R.tcf,c,R.dataSigningHash,R.gpp,(b,k)=>U.getArchivedSnapshot(b,k)),zn=new Jt(f,E),j=r.masterSecret??"",Qn=new Gt(c,j),tn=e.rateLimit,ho=tn.enabled===!1?null:new Xe(tn.windowMs,tn.maxRequests),yo=new Xe(15*6e4,10),Xn=e.multiTenant?.enabled===!0?b=>co(b,c,"default"):async b=>"default",$=e.basePath,Zn=`${$}/api/v1`,es=`${$}/admin/v1`,nn=`${$}/api`,et=!!e.dashboard,me=le(Yn,"dashboard"),tt=et?gs(ms(me,$,e.branding,e.compliance,r.mode)):"",ts=`<!DOCTYPE html><html><head><title>Consenti API Docs</title>
195
197
  <link rel="stylesheet" href="https://unpkg.com/swagger-ui-dist@5.17.14/swagger-ui.css" crossorigin="anonymous"></head>
196
198
  <body><div id="swagger-ui"></div>
197
199
  <script src="https://unpkg.com/swagger-ui-dist@5.17.14/swagger-ui-bundle.js" crossorigin="anonymous"></script>
198
200
  <script src="https://unpkg.com/swagger-ui-dist@5.17.14/swagger-ui-standalone-preset.js" crossorigin="anonymous"></script>
199
- <script>SwaggerUIBundle({urls:[{url:'${Yt}/openapi.json',name:'Public API (/consenti/api)'},{url:'${Yt}/openapi-admin.json',name:'Admin API (/consenti/admin)'}],'urls.primaryName':'Public API (/consenti/api)',dom_id:'#swagger-ui',presets:[SwaggerUIBundle.presets.apis,SwaggerUIStandalonePreset],layout:'StandaloneLayout'})</script>
200
- </body></html>`,qr=`User-agent: *
201
+ <script>SwaggerUIBundle({urls:[{url:'${nn}/openapi.json',name:'Public API (/consenti/api)'},{url:'${nn}/openapi-admin.json',name:'Admin API (/consenti/admin)'}],'urls.primaryName':'Public API (/consenti/api)',dom_id:'#swagger-ui',presets:[SwaggerUIBundle.presets.apis,SwaggerUIStandalonePreset],layout:'StandaloneLayout'})</script>
202
+ </body></html>`,wo=`User-agent: *
201
203
  Disallow: ${$}/
202
- `,Fn=jc($,[{prefix:Yt,rateLimit:!1,routes:{"GET /docs":async()=>new Response($n,{headers:{"Content-Type":"text/html; charset=utf-8"}}),"GET /openapi.json":async()=>new Response(JSON.stringify(tn,null,2),{headers:{"Content-Type":"application/json"}}),"GET /openapi-admin.json":async()=>new Response(JSON.stringify(nn,null,2),{headers:{"Content-Type":"application/json"}})}},{prefix:"",rateLimit:!1,routes:{"GET /robots.txt":async()=>new Response(qr,{headers:{"Content-Type":"text/plain; charset=utf-8"}})}},{prefix:jn,rateLimit:!0,routes:{...Ai(L,b,a,jn),...Fi(Te,X,L,Mn,c,R.dataSigningHash,l,R.parentalConsentTokenTtlDays??7),...Hi(z),...Bi(c,R.tcf),...Vi(c,R.gpp)}},{prefix:xn,rateLimit:!1,routes:{"GET /widget.js":async()=>xr(ae(Ln,"widget","widget.js"),"application/javascript; charset=utf-8"),"GET /widget.css":async()=>xr(ae(Ln,"widget","widget.css"),"text/css; charset=utf-8")}},{prefix:xn,rateLimit:!1,originCheck:!0,routes:{...ar(kn,c,r,x,Jr),...gr(L,c,r,x),...fr(c,r,x),..._r(c,r,x),...yr(Nn,c,r,x),...wr(c,r,x),...Er(c,r,x),...Rr(c,h,r,x),...Sr(c,r,x,L),...Ir(c,r,x),...Tr(c,r,x),...Ar(c,r,x),...vr(c,r,x,R.tcf),...Pr(c,r,x,R.gpp),...Dr(c,r,x),...Ur(c,r,x),...Lr(c,r,x),...kr(L,c,r,x,e,{usingJsonStorage:t,usingDefaultCredentials:n})}}],Wr,c,Mn),zt=null,Hn,Bn,Gr=new Promise((U,N)=>{Hn=U,Bn=N});c.connect().then(async()=>{if(r.mode==="local"&&r.adminEmail&&r.adminPassword&&await kn.bootstrap(r.adminEmail,r.adminPassword),await w.initialize({storage:c,config:e}),l.emit("ready"),Hn(),R.tcf?.enabled&&(Zn(),ns()),R.dataRetention?.purgeAfterDays){let U=R.dataRetention.purgeAfterDays,N=()=>c.purgeExpiredConsents(U).then(F=>{F>0&&console.warn(`[consenti] Purged ${F} consent records older than ${U} days`)}).catch(F=>console.warn("[consenti] Data retention purge failed:",F));N(),zt=setInterval(()=>{N()},1440*6e4)}}).catch(U=>{console.warn("[consenti] Storage connection failed:",U),Bn(U)});async function Qt(U,N){try{let te=U.headers["x-forwarded-proto"]==="https"?"https":"http",ue=U.headers.host??"localhost",B=new URL(U.url??"/",`${te}://${ue}`);if(Ke&&B.pathname.startsWith($)&&await Nc(B.pathname,$,de,N,Ye))return;let ce=await Ci(U,e.maxBodySize,e.trustedProxies),ze=await Fn(ce);if(B.pathname===`${$}/api/openapi.json`){N.writeHead(200,{"Content-Type":"application/json"}),N.end(JSON.stringify(tn,null,2));return}if(B.pathname===`${$}/api/openapi-admin.json`){N.writeHead(200,{"Content-Type":"application/json"}),N.end(JSON.stringify(nn,null,2));return}if(B.pathname===`${$}/api/docs`){N.writeHead(200,{"Content-Type":"text/html; charset=utf-8"}),N.end($n);return}if(ze.status===404&&Ke&&B.pathname.startsWith($)&&!B.pathname.startsWith($+"/api/")&&!B.pathname.startsWith($+"/admin/"))try{let Qe=await Ge(ae(de,"index.html"));N.writeHead(200,{"Content-Type":"text/html; charset=utf-8"}),N.end(ve(Qe,Ye));return}catch{}await Ii(ze,N)}catch(F){N.headersSent||(F instanceof Be?(N.writeHead(413,{"Content-Type":"application/json"}),N.end(JSON.stringify({error:"Payload Too Large"}))):(N.writeHead(500,{"Content-Type":"application/json"}),N.end(JSON.stringify({error:"Internal Server Error"}))))}}function Kr(U,N,F){let ue=U.headers["x-forwarded-proto"]==="https"?"https":"http",B=U.headers.host??"localhost",{pathname:ce}=new URL(U.url??"/",`${ue}://${B}`);if(ce!=="/robots.txt"&&!ce.startsWith($)){F();return}Qt(U,N).then(()=>{N.writableEnded||F()}).catch(F)}function Yr(U,N){return Qt(U,N)}let Vn=async(U,N)=>{U.all("/*",async(F,te)=>{await Qt(F.raw,te.raw),te.hijack()})};return Object.defineProperty(Vn,Symbol.for("skip-override"),{value:!0}),{router:Kr,handler:Yr,fastifyHandler:Vn,honoApp:{fetch:async U=>{if(Ke){let F=new URL(U.url).pathname;if(F.startsWith($)){let te=F.slice($.length)||"/";if(!te.startsWith("/api/")&&!te.startsWith("/admin/")){let ue=Gt(te)?ae(de,te):ae(de,"index.html");if(!ue.startsWith(de))return new Response("Forbidden",{status:403});try{let B=await Ge(ue),ce=Gt(ue).toLowerCase(),ze=ce===".html"||ce==="",Qe=Br[ce]??"application/octet-stream",zr=ze?ve(B,Ye):B;return new Response(zr,{headers:{"Content-Type":Qe}})}catch{try{let B=await Ge(ae(de,"index.html"));return new Response(ve(B,Ye),{headers:{"Content-Type":"text/html; charset=utf-8"}})}catch{return new Response("Dashboard not built. Run: npm run build --workspace=apps/api",{status:404,headers:{"Content-Type":"text/plain"}})}}}}}return Fn(U)}},services:{profile:L,consent:Te,visitor:X,user:Nn},storage:c,eventBus:l,ready:Gr,destroy:async()=>{zt&&clearInterval(zt),h.dispose(),R.tcf?.enabled&&(es(),ss()),M?.stop(),await w.destroy()}}}export{Un as CategoryAction,Dn as ConsentAction,Ne as MongoDBAdapter,Me as MySQLAdapter,$e as PostgreSQLAdapter,Ee as SQLiteAdapter,b_ as createConsenti,O_ as dashboardDir};
204
+ `,ns=gl($,[{prefix:nn,rateLimit:!1,routes:{"GET /docs":async()=>new Response(ts,{headers:{"Content-Type":"text/html; charset=utf-8"}}),"GET /openapi.json":async()=>new Response(JSON.stringify(ln,null,2),{headers:{"Content-Type":"application/json"}}),"GET /openapi-admin.json":async()=>new Response(JSON.stringify(dn,null,2),{headers:{"Content-Type":"application/json"}})}},{prefix:"",rateLimit:!1,routes:{"GET /robots.txt":async()=>new Response(wo,{headers:{"Content-Type":"text/plain; charset=utf-8"}})}},{prefix:Zn,rateLimit:!0,routes:{...Qi(U,N,a,Zn),...ur(be,V,U,Xn,c,R.dataSigningHash,l,R.parentalConsentTokenTtlDays??7),...pr(Q),...mr(c,R.tcf),...gr(c,R.gpp)}},{prefix:es,rateLimit:!1,routes:{"GET /widget.js":async()=>uo(le(Yn,"widget","widget.js"),"application/javascript; charset=utf-8"),"GET /widget.css":async()=>uo(le(Yn,"widget","widget.css"),"text/css; charset=utf-8")}},{prefix:es,rateLimit:!1,originCheck:!0,routes:{...Lr(Qn,c,r,j,yo),...Fr(U,c,r,j),...Hr(c,r,j),...Br(c,r,j),...Wr(zn,c,r,j),...Jr(c,r,j),...Gr(c,r,j),...qr(c,y,r,j),...Kr(c,r,j,U),...zr(c,r,j),...Qr(c,r,j),...Xr(c,r,j),...Zr(c,r,j,R.tcf),...eo(c,r,j,R.gpp),...so(c,r,j),...io(c,r,j),...ro(c,r,j),...ao(U,c,r,j,e,{usingJsonStorage:t,usingDefaultCredentials:n})}}],ho,c,Xn),sn=null,ss,is,Eo=new Promise((b,k)=>{ss=b,is=k});c.connect().then(async()=>{if(r.mode==="local"&&r.adminEmail&&r.adminPassword&&await Qn.bootstrap(r.adminEmail,r.adminPassword),await w.initialize({storage:c,config:e}),l.emit("ready"),ss(),R.tcf?.enabled&&(_s(),ws()),R.dataRetention?.purgeAfterDays){let b=R.dataRetention.purgeAfterDays,k=()=>c.purgeExpiredConsents(b).then(F=>{F>0&&console.warn(`[consenti] Purged ${F} consent records older than ${b} days`)}).catch(F=>console.warn("[consenti] Data retention purge failed:",F));k(),sn=setInterval(()=>{k()},1440*6e4)}}).catch(b=>{console.warn("[consenti] Storage connection failed:",b),is(b)});async function rn(b,k){try{let se=b.headers["x-forwarded-proto"]==="https"?"https":"http",ge=b.headers.host??"localhost",B=new URL(b.url??"/",`${se}://${ge}`);if(et&&B.pathname.startsWith($)&&await ul(B.pathname,$,me,k,tt))return;let de=await Gi(b,e.maxBodySize,e.trustedProxies),nt=await ns(de);if(B.pathname===`${$}/api/openapi.json`){k.writeHead(200,{"Content-Type":"application/json"}),k.end(JSON.stringify(ln,null,2));return}if(B.pathname===`${$}/api/openapi-admin.json`){k.writeHead(200,{"Content-Type":"application/json"}),k.end(JSON.stringify(dn,null,2));return}if(B.pathname===`${$}/api/docs`){k.writeHead(200,{"Content-Type":"text/html; charset=utf-8"}),k.end(ts);return}if(nt.status===404&&et&&B.pathname.startsWith($)&&!B.pathname.startsWith($+"/api/")&&!B.pathname.startsWith($+"/admin/"))try{let st=await Ze(le(me,"index.html"));k.writeHead(200,{"Content-Type":"text/html; charset=utf-8"}),k.end(Le(st,tt));return}catch{}await qi(nt,k)}catch(F){k.headersSent||(F instanceof Ke?(k.writeHead(413,{"Content-Type":"application/json"}),k.end(JSON.stringify({error:"Payload Too Large"}))):(k.writeHead(500,{"Content-Type":"application/json"}),k.end(JSON.stringify({error:"Internal Server Error"}))))}}function Ro(b,k,F){let ge=b.headers["x-forwarded-proto"]==="https"?"https":"http",B=b.headers.host??"localhost",{pathname:de}=new URL(b.url??"/",`${ge}://${B}`);if(de!=="/robots.txt"&&!de.startsWith($)){F();return}rn(b,k).then(()=>{k.writableEnded||F()}).catch(F)}function So(b,k){return rn(b,k)}let rs=async(b,k)=>{b.all("/*",async(F,se)=>{await rn(F.raw,se.raw),se.hijack()})};return Object.defineProperty(rs,Symbol.for("skip-override"),{value:!0}),{router:Ro,handler:So,fastifyHandler:rs,honoApp:{fetch:async b=>{if(et){let F=new URL(b.url).pathname;if(F.startsWith($)){let se=F.slice($.length)||"/";if(!se.startsWith("/api/")&&!se.startsWith("/admin/")){let ge=en(se)?le(me,se):le(me,"index.html");if(!ge.startsWith(me))return new Response("Forbidden",{status:403});try{let B=await Ze(ge),de=en(ge).toLowerCase(),nt=de===".html"||de==="",st=fo[de]??"application/octet-stream",Co=nt?Le(B,tt):B;return new Response(Co,{headers:{"Content-Type":st}})}catch{try{let B=await Ze(le(me,"index.html"));return new Response(Le(B,tt),{headers:{"Content-Type":"text/html; charset=utf-8"}})}catch{return new Response("Dashboard not built. Run: npm run build --workspace=apps/api",{status:404,headers:{"Content-Type":"text/plain"}})}}}}}return ns(b)}},services:{profile:U,consent:be,visitor:V,user:zn},storage:c,eventBus:l,ready:Eo,destroy:async()=>{sn&&clearInterval(sn),y.dispose(),R.tcf?.enabled&&(hs(),Es()),L?.stop(),await w.destroy()}}}export{Kn as CategoryAction,qn as ConsentAction,Fe as MongoDBAdapter,Be as MySQLAdapter,Je as PostgreSQLAdapter,Te as SQLiteAdapter,Ih as createConsenti,Ch as dashboardDir};
203
205
  //# sourceMappingURL=index.js.map