@consenti/api 0.2.0 → 0.3.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.
- package/README.md +387 -90
- package/dist/chunk-IY4L6MNB.js +2 -0
- package/dist/chunk-IY4L6MNB.js.map +1 -0
- package/dist/chunk-O5UG4OYI.js +2 -0
- package/dist/chunk-O5UG4OYI.js.map +1 -0
- package/dist/dashboard/assets/ApiConfig-CE-Noelz.js +1 -0
- package/dist/dashboard/assets/ArchivedProfileList-Bw-fx-BD.js +1 -0
- package/dist/dashboard/assets/AuditLog-D6HiTQ1o.js +1 -0
- package/dist/dashboard/assets/ChangePassword-D2rPkzD1.js +1 -0
- package/dist/dashboard/assets/ConfirmDialog-DTtw7VJ-.js +1 -0
- package/dist/dashboard/assets/ConsentList-BWOvPOMG.js +1 -0
- package/dist/dashboard/assets/ConsentTemplateEditor-BKIEnLmA.js +1 -0
- package/dist/dashboard/assets/ConsentTemplateList-DCys2nM3.js +1 -0
- package/dist/dashboard/assets/HeadingTagInput-BTjrdzlS.js +1 -0
- package/dist/dashboard/assets/HowItWorks-AeyjfQGO.js +1 -0
- package/dist/dashboard/assets/PermissionGate-Bhhf7SMh.js +1 -0
- package/dist/dashboard/assets/PreviewPane-Dq5K1h2L.js +35 -0
- package/dist/dashboard/assets/ProfileEditor-BXtaE0tL.js +2 -0
- package/dist/dashboard/assets/ProfileList-CWKSAgu0.js +1 -0
- package/dist/dashboard/assets/ProfileVersionHistory-1h8sUgog.js +3 -0
- package/dist/dashboard/assets/RecordDetailModal-FaFhzQXi.js +1 -0
- package/dist/dashboard/assets/Reports-DWEjQOKh.js +1 -0
- package/dist/dashboard/assets/RoleList-BSGoqlzp.js +1 -0
- package/dist/dashboard/assets/Select-DjJNwlbY.js +1 -0
- package/dist/dashboard/assets/Settings-CVYrlyLm.js +1 -0
- package/dist/dashboard/assets/SetupWizard-4NTb5bZj.js +1 -0
- package/dist/dashboard/assets/Table-DH_jh8_N.js +1 -0
- package/dist/dashboard/assets/TenantList-5AwFtoHf.js +1 -0
- package/dist/dashboard/assets/UITemplateEditor-9A55scBe.js +1 -0
- package/dist/dashboard/assets/UITemplateList-BS7X4u4F.js +1 -0
- package/dist/dashboard/assets/UserList-SPkMyFRy.js +1 -0
- package/dist/dashboard/assets/VendorList-C5fPhr6U.js +1 -0
- package/dist/dashboard/assets/VisitorList-COP1SyNd.js +1 -0
- package/dist/dashboard/assets/check-o5cp1s2J.js +1 -0
- package/dist/dashboard/assets/client-ymc9nwWr.js +1 -0
- package/dist/dashboard/assets/copy-C01KoUUH.js +1 -0
- package/dist/dashboard/assets/createLucideIcon-MdjI3m3u.js +1 -0
- package/dist/dashboard/assets/de-DET92Jb5.js +5 -0
- package/dist/dashboard/assets/debounce-DlvAAT2V.js +1 -0
- package/dist/dashboard/assets/dist-DlhLroYC.js +1 -0
- package/dist/dashboard/assets/es-CJNunrnU.js +5 -0
- package/dist/dashboard/assets/external-link-DVe00ZBK.js +1 -0
- package/dist/dashboard/assets/fr-akfTuf5x.js +5 -0
- package/dist/dashboard/assets/history-CHTts_m2.js +1 -0
- package/dist/dashboard/assets/index-DW3qOrry.css +2 -0
- package/dist/dashboard/assets/index-H_Zm0X-u.js +6 -0
- package/dist/dashboard/assets/ja-B0nLWkOd.js +5 -0
- package/dist/dashboard/assets/profiles-DmrdGv7t.js +1 -0
- package/dist/dashboard/assets/roles-C0dJPFl4.js +1 -0
- package/dist/dashboard/assets/search-Bd2RdDRh.js +1 -0
- package/dist/dashboard/assets/templates-B2xiivh4.js +1 -0
- package/dist/dashboard/assets/templates-CnMoLavN.js +1 -0
- package/dist/dashboard/assets/trash-2-BBnkdgm_.js +1 -0
- package/dist/dashboard/assets/trash-Dj1jhzHJ.js +1 -0
- package/dist/dashboard/assets/useHashQuery-B5ytMIMp.js +1 -0
- package/dist/dashboard/assets/users-czK8LRVh.js +1 -0
- package/dist/dashboard/assets/x-BRuyBEQP.js +1 -0
- package/dist/dashboard/assets/zh-CN-Dxg3G-BL.js +5 -0
- package/dist/dashboard/index.html +4 -2
- package/dist/dist-Z5RVTVBT.js +2 -0
- package/dist/dist-Z5RVTVBT.js.map +1 -0
- package/dist/index.cjs +89 -100
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +663 -128
- package/dist/index.d.ts +663 -128
- package/dist/index.js +90 -101
- package/dist/index.js.map +1 -1
- package/dist/profiles-XTYPWLVC.js +2 -0
- package/dist/profiles-XTYPWLVC.js.map +1 -0
- package/dist/widget/widget.css +1 -1
- package/dist/widget/widget.js +1 -1
- package/package.json +14 -14
- package/dist/dashboard/assets/de-DwE2aQfW.js +0 -5
- package/dist/dashboard/assets/es-Dq88TWkB.js +0 -5
- package/dist/dashboard/assets/fr-o_Pyg8eR.js +0 -5
- package/dist/dashboard/assets/index-BSsYDdb9.js +0 -40
- package/dist/dashboard/assets/index-bOTGevzl.css +0 -2
- package/dist/dashboard/assets/ja-CwgXYmpD.js +0 -5
- package/dist/dashboard/assets/zh-CN-ety9KoQ0.js +0 -5
package/dist/index.js
CHANGED
|
@@ -1,150 +1,139 @@
|
|
|
1
|
-
import{EventEmitter as yr}from"events";import{readFile as be}from"fs/promises";import{join as Y,extname as gt,dirname as hr}from"path";import{fileURLToPath as os}from"url";var Ne=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(i){console.warn(`[Consenti] Plugin "${n.name}" beforeConsentSave failed:`,i)}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(i){console.warn(`[Consenti] Plugin "${n.name}" beforeConsentUpdate failed:`,i)}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(i){console.warn(`[Consenti] Plugin "${n.name}" afterProfileFetch failed:`,i)}return t}async runBeforeUserCreate(e){let t=e;for(let n of this.plugins)if(n.beforeUserCreate)try{t=await n.beforeUserCreate(t)}catch(i){console.warn(`[Consenti] Plugin "${n.name}" beforeUserCreate failed:`,i)}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 Dr,existsSync as Lr,mkdirSync as Ur}from"fs";import{extname as Nr,join as Mr}from"path";function qt(s,e,t={},n={}){let i={};return n.type!==void 0&&(i.type=n.type),n.gpc!==void 0&&(i.gpc=n.gpc),{appName:t.appName??"Consenti",appLogoPath:t?.appLogoPath??"/logo-dark.svg",hidePoweredBy:t.hidePoweredBy??!1,basePath:e,compliance:i}}function Kt(s){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(s).replace(/<\/script>/gi,"<\\/script>")}),writable:false,configurable:false,enumerable:true})})()</script>`}function pe(s,e){let t=Buffer.from(s).toString("utf-8");return t.includes("</head>")?t.replace("</head>",`${e}</head>`):t.replace("</body>",`${e}</body>`)}var hs="https://vendor-list.consensu.org/v3/vendor-list.json";var X=null,Yt=0,me=null;function _s(s){return typeof s=="object"&&s!==null&&"vendors"in s&&"purposes"in s&&typeof s.vendors=="object"&&typeof s.purposes=="object"}async function Z(){if(X&&Date.now()-Yt<6048e5)return X;try{let s=await fetch(hs,{signal:AbortSignal.timeout(15e3)});if(!s.ok)return X;let e=await s.json();return _s(e)&&(X=e,Yt=Date.now()),X}catch{return X}}function zt(){me||(Z(),me=setInterval(()=>{Z()},6048e5))}function Qt(){me&&(clearInterval(me),me=null)}var Xt={ConsentRecord:{type:"object",properties:{id:{type:"string"},visitorId:{type:"string"},profileId:{type:"string"},profileVersion:{type:"integer"},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"}}}},Et={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"}}}}},components:{schemas:Xt}},wt={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",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/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"}}}},"/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"}}],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"}}}}},components:{securitySchemes:{BearerAuth:{type:"http",scheme:"bearer",bearerFormat:"JWT"}},schemas:Xt}};import{createHash as Zt,createHmac as Es,randomUUID as S,scryptSync as en,randomBytes as tn,timingSafeEqual as nn}from"crypto";function ee(){return`prof_${tn(20).toString("hex")}`}function sn(s){return Zt("sha256").update(s).digest("hex")}function on(s){return Zt("sha256").update(s).digest("hex")}function rn(s,e){return Es("sha256",e).update(s).digest("hex")}function ge(s){let e=tn(16).toString("hex"),t=en(s,e,64).toString("hex");return`${e}:${t}`}function an(s,e){let[t,n]=e.split(":");if(!t||!n)return!1;let i=en(s,t,64).toString("hex");return nn(Buffer.from(n,"hex"),Buffer.from(i,"hex"))}function Rt(s,e,t=3600){let n=Math.floor(Date.now()/1e3),i=btoa(JSON.stringify({alg:"HS256",typ:"JWT"})).replace(/=/g,""),o=btoa(JSON.stringify({...s,iat:n,exp:n+t})).replace(/=/g,""),r=rn(`${i}.${o}`,e).match(/.{2}/g).map(c=>String.fromCharCode(parseInt(c,16))).join(""),a=btoa(r).replace(/\+/g,"-").replace(/\//g,"_").replace(/=/g,"");return`${i}.${o}.${a}`}function Me(s,e){let t=s.split(".");if(t.length!==3)return null;let[n,i,o]=t;if(!n||!i||!o)return null;let r=rn(`${n}.${i}`,e),a=atob(o.replace(/-/g,"+").replace(/_/g,"/")),c=Array.from(a).map(d=>d.charCodeAt(0).toString(16).padStart(2,"0")).join(""),l=Buffer.from(r,"hex"),u=Buffer.from(c,"hex");if(l.length!==u.length||!nn(l,u))return null;try{let d=JSON.parse(atob(i)),p=d.exp;return typeof p=="number"&&Date.now()/1e3>p?null:d}catch{return null}}import{existsSync as ws,accessSync as Rs,constants as Ss,mkdirSync as Ge}from"fs";import{resolve as cn,extname as As,dirname as ln,join as xe}from"path";import{fileURLToPath as Ts}from"url";var Cs={"node:sqlite":"consenti.db","better-sqlite3":"consenti.db","node-sqlite3-wasm":"consenti.db",sqlite:"consenti.db",json:"consenti-data.json",mongodb:"",mysql:"",postgresql:""},vs=ln(Ts(import.meta.url));function Is(s){let e=s.includes("node_modules"),t=s.startsWith(cn(vs,"..",".."));return e||t}function W(s,e){let t=cn(s),n=As(t);if(n&&(console.warn(`[consenti] storage.path "${s}" looks like a file path (has extension "${n}"). Treating parent directory as storage root. Update your config to pass a directory path.`),t=ln(t)),Is(t))throw new Error(`[consenti] storage.path "${t}" is inside the package installation directory. Choose a writable directory outside node_modules.`);ws(t)||Ge(t,{recursive:!0});try{Rs(t,Ss.W_OK)}catch{throw new Error(`[consenti] storage.path "${t}" is not writable.`)}let i=xe(t,"db"),o=xe(t,"profiles"),r=xe(t,"logs");Ge(i,{recursive:!0}),Ge(o,{recursive:!0}),Ge(r,{recursive:!0});let a=Cs[e]??"consenti.db",c=a?xe(i,a):"";return{basePath:t,dbPath:c,profilesDir:o,logsDir:r}}var dn={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}]},un={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"},{n:"created_at",t:"ts",def:"$now"},{n:"updated_at",t:"ts",def:"$now",mysqlUpd:!0}]},Ps={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:"profile_version",t:"int"},{n:"locale",t:"text_sm"},{n:"consent_json",t:"json"},{n:"gpc_detected",t:"bool",def:!1},{n:"source",t:"text_sm"},{n:"created_at",t:"ts",def:"$now"},{n:"updated_at",t:"ts",def:"$now",mysqlUpd:!0}],uq:[{cols:["visitor_id","profile_id"]}]},bs={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:"profile_version",t:"int"},{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:"created_at",t:"ts",def:"$now"},{n:"updated_at",t:"ts",def:"$now",mysqlUpd:!0}],uq:[{cols:["visitor_id","profile_id"]}]},pn={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"}]},mn={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}]},Os={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:"created_at",t:"ts",def:"$now"},{n:"updated_at",t:"ts",def:"$now",mysqlUpd:!0}]},Ds={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}]},gn={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}]},fn={name:"permissions",cols:[{n:"id",t:"pk"},{n:"name",t:"text_md",unique:!0},{n:"description",t:"text",null:!0}]},Ls={name:"user_roles",cols:[{n:"user_id",t:"text",ref:"users"},{n:"role_id",t:"text",ref:"roles"}],pk:["user_id","role_id"]},Us={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"]},yn={name:"role_permissions",cols:[{n:"role_id",t:"text",ref:"roles"},{n:"permission_id",t:"text",ref:"permissions"}],pk:["role_id","permission_id"]},hn={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"}]},ks={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_at",t:"ts",def:"$now"}]},Ns={name:"cookie_templates",cols:[{n:"id",t:"pk"},{n:"tenant_id",t:"text",ref:"tenants",onDel:"CASCADE"},{n:"name",t:"text"},{n:"cookies_json",t:"json",def:"$arr"},{n:"created_at",t:"ts",def:"$now"},{n:"updated_at",t:"ts",def:"$now",mysqlUpd:!0}]},Ms={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",def:"$obj"},{n:"created_at",t:"ts",def:"$now"},{n:"updated_at",t:"ts",def:"$now",mysqlUpd:!0}]},Gs=[dn,un,Ps,pn,mn,Os,gn,fn,Ls,yn,hn],xs=[dn,un,bs,pn,mn,Ds,gn,fn,Us,yn,hn],_n=[ks],En=[Ns,Ms];function Fs(s){return s==="$now"?"DEFAULT (datetime('now'))":s==="$arr"?"DEFAULT '[]'":s==="$obj"?"DEFAULT '{}'":s===!0?"DEFAULT 1":s===!1?"DEFAULT 0":typeof s=="string"?`DEFAULT '${s}'`:`DEFAULT ${s}`}function Hs(s){if(s.t==="pk")return`${s.n} TEXT PRIMARY KEY`;let e=s.t==="int"||s.t==="bool"?"INTEGER":"TEXT",t=[`${s.n} ${e}`];return s.null||t.push("NOT NULL"),s.def!==void 0&&t.push(Fs(s.def)),s.unique&&t.push("UNIQUE"),s.ref&&(t.push(`REFERENCES ${s.ref}(id)`),s.onDel&&t.push(`ON DELETE ${s.onDel}`)),t.join(" ")}function js(s){let e=s.cols.map(t=>` ${Hs(t)}`);if(s.pk&&e.push(` PRIMARY KEY (${s.pk.join(", ")})`),s.uq)for(let t of s.uq)e.push(` UNIQUE(${t.cols.join(", ")})`);return`CREATE TABLE IF NOT EXISTS ${s.name} (
|
|
1
|
+
import{B as Dn,C as Ln,d as xt,e as $e,i as $t,q as fe,r as je,s as Pn,t as On,u as bn}from"./chunk-IY4L6MNB.js";import"./chunk-O5UG4OYI.js";import{EventEmitter as xa}from"events";import{readFile as Le}from"fs/promises";import{join as te,extname as Dt,dirname as $a}from"path";import{fileURLToPath as ar}from"url";var Fe=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 Xa,existsSync as Za,mkdirSync as ec}from"fs";import{extname as nc,join as sc}from"path";function Un(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 Nn(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 _e(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 yr="https://vendor-list.consensu.org/v3/vendor-list.json";var oe=null,kn=0,he=null;function wr(i){return typeof i=="object"&&i!==null&&"vendors"in i&&"purposes"in i&&typeof i.vendors=="object"&&typeof i.purposes=="object"}async function ae(){if(oe&&Date.now()-kn<6048e5)return oe;try{let i=await fetch(yr,{signal:AbortSignal.timeout(15e3)});if(!i.ok)return oe;let e=await i.json();return wr(e)&&(oe=e,kn=Date.now()),oe}catch{return oe}}function Mn(){he||(ae(),he=setInterval(()=>{ae()},6048e5))}function xn(){he&&(clearInterval(he),he=null)}var $n={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"}}}},jt={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/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:$n}},Ft={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",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.jwtSecret`, `consentSigningKey`, 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:$n}};import{createHash as Fn,createHmac as Er,randomUUID as A,scryptSync as Hn,randomBytes as Bn,timingSafeEqual as Ht}from"crypto";var jn="0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",Rr=16;function Sr(i){let e=Bn(i),t="";for(let n=0;n<i;n++)t+=jn[e[n]%jn.length];return t}function ye(i){return`${i}_${Sr(Rr)}`}function H(){return ye("prof")}function $(){return ye("visi")}function B(){return ye("cons")}function ne(){return ye("ctem")}function se(){return ye("utem")}function Wn(i){return Fn("sha256").update(i).digest("hex")}function Vn(i){return Fn("sha256").update(i).digest("hex")}function z(i,e){return Er("sha256",e).update(i).digest("hex")}function Jn(i,e,t){let n=Buffer.from(z(i,t),"hex"),s=Buffer.from(e,"hex");return n.length!==s.length?!1:Ht(n,s)}function we(i){let e=Bn(16).toString("hex"),t=Hn(i,e,64).toString("hex");return`${e}:${t}`}function qn(i,e){let[t,n]=e.split(":");if(!t||!n)return!1;let s=Hn(i,t,64).toString("hex");return Ht(Buffer.from(n,"hex"),Buffer.from(s,"hex"))}function Bt(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 He(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(""),d=Buffer.from(o,"hex"),l=Buffer.from(c,"hex");if(d.length!==l.length||!Ht(d,l))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 Cr,accessSync as Ar,constants as Ir,mkdirSync as Be}from"fs";import{resolve as Gn,extname as Tr,dirname as Kn,join as We}from"path";import{fileURLToPath as vr}from"url";var Pr={"node:sqlite":"consenti.db","better-sqlite3":"consenti.db","node-sqlite3-wasm":"consenti.db",sqlite:"consenti.db",json:"consenti-data.json",mongodb:"",mysql:"",postgresql:""},Or=Kn(vr(import.meta.url));function br(i){let e=i.includes("node_modules"),t=i.startsWith(Gn(Or,"..",".."));return e||t}function K(i,e){let t=Gn(i),n=Tr(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=Kn(t)),br(t))throw new Error(`[consenti] storage.path "${t}" is inside the package installation directory. Choose a writable directory outside node_modules.`);Cr(t)||Be(t,{recursive:!0});try{Ar(t,Ir.W_OK)}catch{throw new Error(`[consenti] storage.path "${t}" is not writable.`)}let s=We(t,"db"),r=We(t,"profiles"),o=We(t,"logs");Be(s,{recursive:!0}),Be(r,{recursive:!0}),Be(o,{recursive:!0});let a=Pr[e]??"consenti.db",c=a?We(s,a):"";return{basePath:t,dbPath:c,profilesDir:r,logsDir:o}}var Xn={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}]},Zn={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"]}]},es={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:"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"]}]},ts={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"]}]},ns={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"]}]},Wt={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}]},Dr={...Wt,cols:[...Wt.cols,{n:"allowed_tenants",t:"json",null:!0}]},is={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}]},rs={name:"permissions",cols:[{n:"id",t:"pk"},{n:"name",t:"text_md",unique:!0},{n:"description",t:"text",null:!0}]},os={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"]}]},cs={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"]}]},ls={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}]},ds={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:"updated_at",t:"ts",def:"$now",mysqlUpd:!0}],pk:["tenant_id"]},us={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}]},ps={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}]},Lr=[Xn,Zn,es,ts,ns,Dr,is,rs,os,as,cs,ls,us,ps,ds,ss],Vt=[Xn,Zn,es,ts,ns,Wt,is,rs,os,as,cs,ls,us,ps,ds,ss];function Ur(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 Nr(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(Ur(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 Yn(i){let e=i.cols.map(t=>` ${Nr(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
|
|
4
|
+
);`}function Jt(i,e){return`idx_${i.name}_${e.join("_")}`}function kr(i){return(i.idx??[]).map(e=>`CREATE INDEX IF NOT EXISTS ${Jt(i,e.cols)} ON ${i.name} (${e.cols.join(", ")});`).join(`
|
|
5
|
+
`)}function Mr(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 xr(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(Mr(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 zn(i){let e=i.cols.map(t=>` ${xr(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} (
|
|
5
6
|
${e.join(`,
|
|
6
7
|
`)}
|
|
7
|
-
);`}
|
|
8
|
+
);`}function $r(i){return(i.idx??[]).map(e=>`CREATE INDEX IF NOT EXISTS ${Jt(i,e.cols)} ON ${i.name} (${e.cols.join(", ")});`).join(`
|
|
9
|
+
`)}var jr={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 Fr(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 Hr(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":jr[i.t]??"TEXT",t=[`${i.n} ${e}`];return i.null||t.push("NOT NULL"),i.def!==void 0&&t.push(Fr(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 Qn(i){let e=i.cols.map(t=>` ${Hr(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
10
|
${e.join(`,
|
|
9
11
|
`)}
|
|
10
|
-
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;`}function
|
|
11
|
-
`)}function
|
|
12
|
-
`)
|
|
13
|
-
`)}
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
12
|
+
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;`}function Br(i){return(i.idx??[]).map(e=>`CREATE INDEX ${Jt(i,e.cols)} ON ${i.name} (${e.cols.join(", ")});`).join(`
|
|
13
|
+
`)}function Wr(i){let e=i.map(kr).filter(Boolean).join(`
|
|
14
|
+
`);return e?`${i.map(Yn).join(`
|
|
15
|
+
`)}
|
|
16
|
+
${e}`:i.map(Yn).join(`
|
|
17
|
+
`)}function Vr(i){let e=i.map($r).filter(Boolean).join(`
|
|
18
|
+
`);return e?`${i.map(zn).join(`
|
|
19
|
+
`)}
|
|
20
|
+
${e}`:i.map(zn).join(`
|
|
21
|
+
`)}function Jr(i){let e=i.map(Br).filter(Boolean).join(`
|
|
22
|
+
`);return e?`${i.map(Qn).join(`
|
|
23
|
+
`)}
|
|
24
|
+
${e}`:i.map(Qn).join(`
|
|
25
|
+
`)}function qr(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 ms=qr(Vt),ce=Wr(Lr),gs=Vr(Vt)+`
|
|
20
26
|
CREATE TABLE IF NOT EXISTS schema_version (version INTEGER PRIMARY KEY);
|
|
21
|
-
`,
|
|
27
|
+
`,fs=Jr(Vt)+`
|
|
22
28
|
CREATE TABLE IF NOT EXISTS schema_version (
|
|
23
29
|
version INT NOT NULL PRIMARY KEY
|
|
24
30
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
|
25
|
-
`,
|
|
26
|
-
${
|
|
27
|
-
`)};`,
|
|
28
|
-
${
|
|
29
|
-
`)};`,
|
|
30
|
-
${
|
|
31
|
+
`,J={id:"default",name:"Default",slug:"default"},V=[{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"}],Q=[{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"}],Gr=new Set(["role:delete","user:delete"]),Kr=new Set(["consent:view","visitor:view","audit:view","profile:view","export:run","stats:view"]),Yr=new Set(["consent:view","visitor:view","profile:view","stats:view"]),X=[...V.map(i=>({roleId:"role_super_admin",permissionId:i.id})),...V.filter(i=>!Gr.has(i.name)).map(i=>({roleId:"role_admin",permissionId:i.id})),...V.filter(i=>Kr.has(i.name)).map(i=>({roleId:"role_compliance_officer",permissionId:i.id})),...V.filter(i=>Yr.has(i.name)).map(i=>({roleId:"role_viewer",permissionId:i.id}))];function G(i){return`'${i.replace(/'/g,"''")}'`}function zr(){let{id:i,name:e,slug:t}=J,n=`INSERT OR IGNORE INTO tenants (id, name, slug) VALUES (${G(i)},${G(e)},${G(t)});`,s=`INSERT OR IGNORE INTO permissions (id, name, description) VALUES
|
|
32
|
+
${V.map(c=>` (${G(c.id)},${G(c.name)},${G(c.description)})`).join(`,
|
|
33
|
+
`)};`,r=`INSERT OR IGNORE INTO roles (id, tenant_id, name, description) VALUES
|
|
34
|
+
${Q.map(c=>` (${G(c.id)},${G(c.tenantId)},${G(c.name)},${G(c.description)})`).join(`,
|
|
35
|
+
`)};`,o=new Map;for(let c of X)o.has(c.roleId)||o.set(c.roleId,[]),o.get(c.roleId).push(c.permissionId);let a=[...o.entries()].map(([c,d])=>`INSERT OR IGNORE INTO role_permissions (role_id, permission_id) VALUES
|
|
36
|
+
${d.map(l=>` (${G(c)},${G(l)})`).join(`,
|
|
31
37
|
`)};`).join(`
|
|
32
|
-
`);return[n,
|
|
33
|
-
`)}var
|
|
38
|
+
`);return[n,s,r,a].join(`
|
|
39
|
+
`)}var le=zr();function k(i){return`${i.replace(/[\\%_]/g,e=>`\\${e}`)}%`}var qt=500,de=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}=K(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(ce),this.db.exec(le),this.db.exec("PRAGMA user_version = 1")),this.db.exec(`
|
|
34
40
|
CREATE INDEX IF NOT EXISTS idx_profiles_tenant_compliance_active
|
|
35
41
|
ON profiles (tenant_id, json_extract(profile_json,'$.complianceGroup'), json_extract(profile_json,'$.isActive'));
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
CREATE INDEX IF NOT EXISTS idx_consents_tenant_profile_date
|
|
39
|
-
|
|
40
|
-
CREATE INDEX IF NOT EXISTS idx_consents_visitor
|
|
41
|
-
ON consent_records (visitor_id);
|
|
42
|
-
CREATE INDEX IF NOT EXISTS idx_visitors_tenant
|
|
43
|
-
ON visitors (tenant_id);
|
|
44
|
-
CREATE INDEX IF NOT EXISTS idx_audit_tenant_date
|
|
45
|
-
ON audit_logs (tenant_id, created_at);
|
|
46
|
-
`)}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=ee(),n=new Date().toISOString();this.stmt("INSERT INTO profiles (id,tenant_id,name,default_locale,profile_json,created_at,updated_at) VALUES (?,?,?,?,?,?,?)").run(t,e.tenantId,e.name,e.defaultLocale,JSON.stringify(e.profileJson),n,n);let i=await this.getProfile(t);if(!i)throw new Error("Failed to create profile");return i}async updateProfile(e,t){if(!await this.getProfile(e))throw new Error(`Profile ${e} not found`);let i=new Date().toISOString(),o=["updated_at = ?","version = version + 1"],r=[i];t.name!=null&&(o.push("name = ?"),r.push(t.name)),t.defaultLocale!=null&&(o.push("default_locale = ?"),r.push(t.defaultLocale)),t.profileJson!=null&&(o.push("profile_json = ?"),r.push(JSON.stringify(t.profileJson))),r.push(e),this.stmt(`UPDATE profiles SET ${o.join(", ")} WHERE id = ?`).run(...r);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 = ? AND json_extract(profile_json, '$.complianceGroup') = ? AND json_extract(profile_json, '$.isActive') = 1 LIMIT 1").get(e,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,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}:{},createdAt:e.created_at,updatedAt:e.updated_at}}async createConsent(e){let t=S(),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,created_at,updated_at) VALUES (?,?,?,?,?,?,?,?,?,?,?)").run(t,e.tenantId,e.visitorId,e.profileId,e.profileVersion,e.locale,JSON.stringify(e.consentJson),e.gpcDetected?1:0,e.source,n,n);let i=S();this.stmt("INSERT INTO consent_history (id,tenant_id,consent_record_id,visitor_id,old_json,new_json,action,created_at) VALUES (?,?,?,?,?,?,?,?)").run(i,e.tenantId,t,e.visitorId,null,JSON.stringify(e.consentJson),"created",n);let o=await this.getConsent(e.visitorId);if(!o)throw new Error("Failed to create consent");return o}async updateConsent(e,t){let n=await this.getConsent(e);if(!n)throw new Error(`Consent for visitor ${e} not found`);let i=new Date().toISOString(),o=["updated_at = ?","consent_json = ?"],r=[i,JSON.stringify(t.consentJson)];t.locale!=null&&(o.push("locale = ?"),r.push(t.locale)),t.gpcDetected!=null&&(o.push("gpc_detected = ?"),r.push(t.gpcDetected?1:0)),r.push(e),this.stmt(`UPDATE consent_records SET ${o.join(", ")} WHERE visitor_id = ?`).run(...r);let a=S();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",i);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];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));let i=Math.min(e.limit??50,At),o=((e.page??1)-1)*i;return n.push(i,o),this.stmt(`SELECT * FROM consent_records WHERE ${t.join(" AND ")} ORDER BY created_at DESC LIMIT ? OFFSET ?`).all(...n).map(a=>this.mapConsent(a))}async*streamConsents(e){let n=0;for(;;){let i=this.stmt("SELECT * FROM consent_records WHERE tenant_id = ? LIMIT ? OFFSET ?").all(e.tenantId,500,n);if(i.length===0)break;for(let o of i)yield this.mapConsent(o);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}))}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=S(),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 i=await this.getVisitor(e.visitorId);if(!i)throw new Error("Failed to create visitor");return i}async updateVisitor(e,t){let n=new Date().toISOString(),i=["last_seen = ?"],o=[t.lastSeen??n];t.country!=null&&(i.push("country = ?"),o.push(t.country)),t.region!=null&&(i.push("region = ?"),o.push(t.region)),t.city!=null&&(i.push("city = ?"),o.push(t.city)),o.push(e),this.stmt(`UPDATE visitors SET ${i.join(", ")} WHERE visitor_id = ?`).run(...o);let r=await this.getVisitor(e);if(!r)throw new Error(`Visitor ${e} not found`);return r}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];e.from&&(t.push("first_seen >= ?"),n.push(e.from)),e.to&&(t.push("first_seen <= ?"),n.push(e.to));let i=Math.min(e.limit??50,At),o=((e.page??1)-1)*i;return n.push(i,o),this.stmt(`SELECT * FROM visitors WHERE ${t.join(" AND ")} ORDER BY last_seen DESC LIMIT ? OFFSET ?`).all(...n).map(a=>this.mapVisitor(a))}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=S(),n=new Date().toISOString(),i=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,i,n,n);let o=await this.getUserByEmail(e.email);if(!o)throw new Error("Failed to create user");return o}async updateUser(e,t){let n=new Date().toISOString(),i=["updated_at = ?"],o=[n];t.name!=null&&(i.push("name = ?"),o.push(t.name)),t.email!=null&&(i.push("email = ?"),o.push(t.email)),t.passwordHash!=null&&(i.push("password_hash = ?"),o.push(t.passwordHash)),t.isActive!=null&&(i.push("is_active = ?"),o.push(t.isActive?1:0)),"totpSecret"in t&&(i.push("totp_secret = ?"),o.push(t.totpSecret??null)),t.totpEnabled!=null&&(i.push("totp_enabled = ?"),o.push(t.totpEnabled?1:0)),"allowedTenants"in t&&(i.push("allowed_tenants = ?"),o.push(t.allowedTenants?.length?JSON.stringify(t.allowedTenants):null)),o.push(e),this.stmt(`UPDATE users SET ${i.join(", ")} WHERE id = ?`).run(...o);let r=this.stmt("SELECT * FROM users WHERE id = ?").get(e);if(!r)throw new Error(`User ${e} not found`);return this.mapUser(r)}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=S();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=[],i=[];t.name!=null&&(n.push("name = ?"),i.push(t.name)),t.description!=null&&(n.push("description = ?"),i.push(t.description)),n.length>0&&(i.push(e),this.stmt(`UPDATE roles SET ${n.join(", ")} WHERE id = ?`).run(...i));let o=this.stmt("SELECT * FROM roles WHERE id = ?").get(e);if(!o)throw new Error(`Role ${e} not found`);return{id:o.id,tenantId:o.tenant_id,name:o.name,...o.description!=null?{description:o.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(i=>({id:i.id,tenantId:i.tenant_id,name:i.name,...i.description!=null?{description:i.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=S(),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];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));let i=Math.min(e.limit??50,At),o=((e.page??1)-1)*i;return n.push(i,o),this.stmt(`SELECT * FROM audit_logs WHERE ${t.join(" AND ")} ORDER BY created_at DESC LIMIT ? OFFSET ?`).all(...n).map(a=>this.mapAuditLog(a))}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 i=500,o=0;for(;;){let r=this.stmt(`SELECT * FROM audit_logs WHERE ${t.join(" AND ")} ORDER BY created_at DESC LIMIT ? OFFSET ?`).all(...n,i,o);if(r.length===0)break;for(let a of r)yield this.mapAuditLog(a);o+=i}}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),i=this.stmt("SELECT COUNT(*) as n FROM visitors WHERE tenant_id = ?").get(e),o=t?.n??0,r=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 u of r){let d=Object.values(JSON.parse(u.consent_json)),p=d.length>0?d.filter(E=>E==="granted").length/d.length:0;p>=.8?a++:p<=.2&&c++}let l=r.length||1;return{totalConsents:o,gpcDetectedCount:n?.n??0,totalVisitors:i?.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 i of t){let o=JSON.parse(i.consent_json);for(let[r,a]of Object.entries(o))n[r]||(n[r]={granted:0,denied:0,objected:0}),a==="granted"?n[r].granted++:a==="denied"?n[r].denied++:a==="objected"&&n[r].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(i=>({date:i.date,count:i.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),i=t?.n??0,o=n?.n??0;return{detected:o,total:i,rate:i>0?Math.round(o/i*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=S(),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 i=this.stmt("SELECT * FROM tenants WHERE id = ?").get(t);if(!i)throw new Error("Failed to create tenant");return{id:i.id,name:i.name,slug:i.slug,createdAt:i.created_at,updatedAt:i.updated_at}}async updateTenant(e,t){let n=new Date().toISOString(),i=["updated_at = ?"],o=[n];t.name!=null&&(i.push("name = ?"),o.push(t.name)),t.slug!=null&&(i.push("slug = ?"),o.push(t.slug)),o.push(e),this.stmt(`UPDATE tenants SET ${i.join(", ")} WHERE id = ?`).run(...o);let r=this.stmt("SELECT * FROM tenants WHERE id = ?").get(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){this.stmt("DELETE FROM tenants WHERE id = ?").run(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:i}of n)this.stmt("DELETE FROM consent_history WHERE visitor_id = ?").run(i),this.stmt("DELETE FROM consent_records WHERE visitor_id = ?").run(i);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,createdAt:e.created_at}}async createApiKey(e){let t=S(),n=new Date().toISOString();this.stmt("INSERT INTO api_keys (id,tenant_id,key_hash,name,created_at) VALUES (?,?,?,?,?)").run(t,e.tenantId,e.keyHash,e.name,n);let i=this.stmt("SELECT * FROM api_keys WHERE id = ?").get(t);if(!i)throw new Error("Failed to create API key");return this.mapApiKey(i)}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 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))}mapCookieTemplate(e){return{id:e.id,tenantId:e.tenant_id,name:e.name,cookies:JSON.parse(e.cookies_json),createdAt:e.created_at,updatedAt:e.updated_at}}async createCookieTemplate(e){let t=S(),n=new Date().toISOString();return this.stmt("INSERT INTO cookie_templates (id,tenant_id,name,cookies_json,created_at,updated_at) VALUES (?,?,?,?,?,?)").run(t,e.tenantId,e.name,JSON.stringify(e.cookies),n,n),this.mapCookieTemplate(this.stmt("SELECT * FROM cookie_templates WHERE id = ?").get(t))}async updateCookieTemplate(e,t){let n=new Date().toISOString();t.name!==void 0&&this.stmt("UPDATE cookie_templates SET name = ?, updated_at = ? WHERE id = ?").run(t.name,n,e),t.cookies!==void 0&&this.stmt("UPDATE cookie_templates SET cookies_json = ?, updated_at = ? WHERE id = ?").run(JSON.stringify(t.cookies),n,e);let i=this.stmt("SELECT * FROM cookie_templates WHERE id = ?").get(e);if(!i)throw new Error("Cookie template not found");return this.mapCookieTemplate(i)}async deleteCookieTemplate(e){this.stmt("DELETE FROM cookie_templates WHERE id = ?").run(e)}async getCookieTemplate(e){let t=this.stmt("SELECT * FROM cookie_templates WHERE id = ?").get(e);return t?this.mapCookieTemplate(t):null}async getCookieTemplates(e){return this.stmt("SELECT * FROM cookie_templates WHERE tenant_id = ? ORDER BY name ASC").all(e).map(n=>this.mapCookieTemplate(n))}async copyCookieTemplate(e,t){let n=await this.getCookieTemplate(e);if(!n)throw new Error("Cookie template not found");return this.createCookieTemplate({tenantId:n.tenantId,name:t,cookies:n.cookies})}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=S(),n=new Date().toISOString(),{tenantId:i,name:o,...r}=e;return this.stmt("INSERT INTO ui_templates (id,tenant_id,name,settings_json,created_at,updated_at) VALUES (?,?,?,?,?,?)").run(t,i,o,JSON.stringify(r),n,n),this.mapUITemplate(this.stmt("SELECT * FROM ui_templates WHERE id = ?").get(t))}async updateUITemplate(e,t){let n=new Date().toISOString(),i=this.stmt("SELECT * FROM ui_templates WHERE id = ?").get(e);if(!i)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:o,...r}=t;if(Object.keys(r).length>0){let c={...JSON.parse(i.settings_json),...r};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:i,mainBanner:o,gpcBanner:r,preferenceModal:a}=n;return this.createUITemplate({tenantId:i,name:t,mainBanner:o,gpcBanner:r,preferenceModal:a})}mapProfileSummary(e){return{id:e.id,name:e.name,defaultLocale:e.default_locale,complianceGroup:e.compliance_group??null,version:e.version,isActive:e.is_active===1,cookieTemplateName:e.cookie_template_name,uiTemplateName:e.ui_template_name,createdAt:e.created_at,updatedAt:e.updated_at}}PROFILE_SUMMARY_SQL=`
|
|
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=H(),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
|
+
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.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=B(),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,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.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 l=k(e.q);n.push(l,l,l,l)}let s=t.join(" AND "),r=e.page??1,o=Math.min(e.limit??50,qt),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),d=this.stmt(`SELECT COUNT(*) AS total FROM consent_records WHERE ${s}`).get(...n);return{items:c.map(l=>this.mapConsentSummary(l)),total:d.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=$(),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 l=k(e.q);n.push(l,l)}let s=t.join(" AND "),r=e.page??1,o=Math.min(e.limit??50,qt),a=(r-1)*o,c=this.stmt(`SELECT * FROM visitors WHERE ${s} ORDER BY last_seen DESC LIMIT ? OFFSET ?`).all(...n,o,a),d=this.stmt(`SELECT COUNT(*) AS total FROM visitors WHERE ${s}`).get(...n);return{items:c.map(l=>this.mapVisitor(l)),total:d.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 l=k(e.q);n.push(l,l,l,l)}let s=t.join(" AND "),r=e.page??1,o=Math.min(e.limit??50,qt),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),d=this.stmt(`SELECT COUNT(*) AS total FROM audit_logs WHERE ${s}`).get(...n);return{items:c.map(l=>this.mapAuditSummary(l)),total:d.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 l of o){let u=Object.values(JSON.parse(l.consent_json)),p=u.length>0?u.filter(h=>h==="granted").length/u.length:0;p>=.8?a++:p<=.2&&c++}let d=o.length||1;return{totalConsents:r,gpcDetectedCount:n?.n??0,totalVisitors:s?.n??0,acceptedPct:Math.round(a/d*100),rejectedPct:Math.round(c/d*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 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}:{}}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, 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, updated_at = excluded.updated_at`).run(e,JSON.stringify(s.allowedOrigins??[]),JSON.stringify(s.adminAllowedOrigins??[]),s.setupCompleted?1:0,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}async purgeExpiredAuditLogs(e){let t=new Date(Date.now()-e*864e5).toISOString(),{count:n}=this.stmt("SELECT COUNT(*) AS count FROM audit_logs WHERE created_at < ?").get(t);return this.stmt("DELETE FROM audit_logs WHERE created_at < ?").run(t),n}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=ne(),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=se(),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=`
|
|
47
46
|
SELECT
|
|
48
|
-
p.id, p.name, p.default_locale, p.
|
|
47
|
+
p.id, p.name, p.default_locale, p.created_at, p.updated_at,
|
|
49
48
|
json_extract(p.profile_json, '$.complianceGroup') AS compliance_group,
|
|
49
|
+
json_extract(p.profile_json, '$.customComplianceGroup') AS custom_compliance_group,
|
|
50
50
|
json_extract(p.profile_json, '$.isActive') AS is_active,
|
|
51
|
-
json_extract(p.profile_json, '$.
|
|
51
|
+
json_extract(p.profile_json, '$.consentTemplateId') AS consent_template_id,
|
|
52
52
|
json_extract(p.profile_json, '$.uiTemplateId') AS ui_template_id,
|
|
53
|
-
ct.name AS
|
|
53
|
+
ct.name AS consent_template_name,
|
|
54
54
|
ut.name AS ui_template_name
|
|
55
55
|
FROM profiles p
|
|
56
|
-
LEFT JOIN
|
|
56
|
+
LEFT JOIN consent_templates ct ON ct.id = json_extract(p.profile_json, '$.consentTemplateId')
|
|
57
57
|
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
|
|
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,d=0,l=0,u={};for(let g of o){let f=Object.values(JSON.parse(g.consent_json)),w=f.length>0?f.filter(T=>T==="granted").length/f.length:0,R=f.length>0?f.filter(T=>T==="denied").length/f.length:0;a++;let I=g.locale;u[I]||(u[I]={total:0,granted:0,denied:0,managed:0}),u[I].total++,w>=.9?(c++,u[I].granted++):R>=.9?(d++,u[I].denied++):(l++,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),h=new Map;for(let g of p){let f=h.get(g.d)??{total:0,granted:0,denied:0,managed:0},w=Object.values(JSON.parse(g.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;f.total++,R>=.9?f.granted++:I>=.9?f.denied++:f.managed++,h.set(g.d,f)}let E=Array.from(h.entries()).map(([g,f])=>({date:g,...f}));return{total:a,granted:c,denied:d,managed:l,grantedPct:a>0?Math.round(c/a*1e3)/10:0,deniedPct:a>0?Math.round(d/a*1e3)/10:0,managedPct:a>0?Math.round(l/a*1e3)/10:0,byLocale:u,byDate:E}}};var Gt=500,Ve=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}=K(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(ce),this.db.exec(le),this.db.exec("PRAGMA user_version = 1")),this.db.exec(`
|
|
59
59
|
CREATE INDEX IF NOT EXISTS idx_profiles_tenant_compliance_active
|
|
60
60
|
ON profiles (tenant_id, json_extract(profile_json,'$.complianceGroup'), json_extract(profile_json,'$.isActive'));
|
|
61
|
-
CREATE INDEX IF NOT EXISTS idx_profiles_tenant
|
|
62
|
-
|
|
63
|
-
CREATE INDEX IF NOT EXISTS idx_consents_tenant_profile_date
|
|
64
|
-
ON consent_records (tenant_id, profile_id, created_at);
|
|
65
|
-
CREATE INDEX IF NOT EXISTS idx_consents_visitor
|
|
66
|
-
ON consent_records (visitor_id);
|
|
67
|
-
CREATE INDEX IF NOT EXISTS idx_visitors_tenant
|
|
68
|
-
ON visitors (tenant_id);
|
|
69
|
-
CREATE INDEX IF NOT EXISTS idx_audit_tenant_date
|
|
70
|
-
ON audit_logs (tenant_id, created_at);
|
|
71
|
-
`)}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=ee(),n=new Date().toISOString();this.stmt("INSERT INTO profiles (id,tenant_id,name,default_locale,profile_json,created_at,updated_at) VALUES (?,?,?,?,?,?,?)").run([t,e.tenantId,e.name,e.defaultLocale,JSON.stringify(e.profileJson),n,n]);let i=await this.getProfile(t);if(!i)throw new Error("Failed to create profile");return i}async updateProfile(e,t){if(!await this.getProfile(e))throw new Error(`Profile ${e} not found`);let i=new Date().toISOString(),o=["updated_at = ?","version = version + 1"],r=[i];t.name!=null&&(o.push("name = ?"),r.push(t.name)),t.defaultLocale!=null&&(o.push("default_locale = ?"),r.push(t.defaultLocale)),t.profileJson!=null&&(o.push("profile_json = ?"),r.push(JSON.stringify(t.profileJson))),r.push(e),this.stmt(`UPDATE profiles SET ${o.join(", ")} WHERE id = ?`).run(r);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(i=>i.profileJson.complianceGroup===t&&i.profileJson.isActive)??null}mapConsent(e){return{id:e.id,tenantId:e.tenant_id,visitorId:e.visitor_id,profileId:e.profile_id,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}:{},createdAt:e.created_at,updatedAt:e.updated_at}}async createConsent(e){let t=S(),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,created_at,updated_at) VALUES (?,?,?,?,?,?,?,?,?,?,?)").run([t,e.tenantId,e.visitorId,e.profileId,e.profileVersion,e.locale,JSON.stringify(e.consentJson),e.gpcDetected?1:0,e.source,n,n]);let i=S();this.stmt("INSERT INTO consent_history (id,tenant_id,consent_record_id,visitor_id,old_json,new_json,action,created_at) VALUES (?,?,?,?,?,?,?,?)").run([i,e.tenantId,t,e.visitorId,null,JSON.stringify(e.consentJson),"created",n]);let o=await this.getConsent(e.visitorId);if(!o)throw new Error("Failed to create consent");return o}async updateConsent(e,t){let n=await this.getConsent(e);if(!n)throw new Error(`Consent for visitor ${e} not found`);let i=new Date().toISOString(),o=["updated_at = ?","consent_json = ?"],r=[i,JSON.stringify(t.consentJson)];t.locale!=null&&(o.push("locale = ?"),r.push(t.locale)),t.gpcDetected!=null&&(o.push("gpc_detected = ?"),r.push(t.gpcDetected?1:0)),r.push(e),this.stmt(`UPDATE consent_records SET ${o.join(", ")} WHERE visitor_id = ?`).run(r);let a=S();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",i]);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];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));let i=Math.min(e.limit??50,Tt),o=((e.page??1)-1)*i;return this.stmt(`SELECT * FROM consent_records WHERE ${t.join(" AND ")} ORDER BY created_at DESC LIMIT ? OFFSET ?`).all([...n,i,o]).map(a=>this.mapConsent(a))}async*streamConsents(e){let n=0;for(;;){let i=this.stmt("SELECT * FROM consent_records WHERE tenant_id = ? LIMIT ? OFFSET ?").all([e.tenantId,500,n]);if(i.length===0)break;for(let o of i)yield this.mapConsent(o);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}))}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=S(),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 i=await this.getVisitor(e.visitorId);if(!i)throw new Error("Failed to create visitor");return i}async updateVisitor(e,t){let n=new Date().toISOString(),i=["last_seen = ?"],o=[t.lastSeen??n];t.country!=null&&(i.push("country = ?"),o.push(t.country)),t.region!=null&&(i.push("region = ?"),o.push(t.region)),t.city!=null&&(i.push("city = ?"),o.push(t.city)),o.push(e),this.stmt(`UPDATE visitors SET ${i.join(", ")} WHERE visitor_id = ?`).run(o);let r=await this.getVisitor(e);if(!r)throw new Error(`Visitor ${e} not found`);return r}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];e.from&&(t.push("first_seen >= ?"),n.push(e.from)),e.to&&(t.push("first_seen <= ?"),n.push(e.to));let i=Math.min(e.limit??50,Tt),o=((e.page??1)-1)*i;return this.stmt(`SELECT * FROM visitors WHERE ${t.join(" AND ")} ORDER BY last_seen DESC LIMIT ? OFFSET ?`).all([...n,i,o]).map(a=>this.mapVisitor(a))}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=S(),n=new Date().toISOString(),i=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,i,n,n]);let o=await this.getUserByEmail(e.email);if(!o)throw new Error("Failed to create user");return o}async updateUser(e,t){let n=new Date().toISOString(),i=["updated_at = ?"],o=[n];t.name!=null&&(i.push("name = ?"),o.push(t.name)),t.email!=null&&(i.push("email = ?"),o.push(t.email)),t.passwordHash!=null&&(i.push("password_hash = ?"),o.push(t.passwordHash)),t.isActive!=null&&(i.push("is_active = ?"),o.push(t.isActive?1:0)),"totpSecret"in t&&(i.push("totp_secret = ?"),o.push(t.totpSecret??null)),t.totpEnabled!=null&&(i.push("totp_enabled = ?"),o.push(t.totpEnabled?1:0)),"allowedTenants"in t&&(i.push("allowed_tenants = ?"),o.push(t.allowedTenants?.length?JSON.stringify(t.allowedTenants):null)),o.push(e),this.stmt(`UPDATE users SET ${i.join(", ")} WHERE id = ?`).run(o);let r=this.stmt("SELECT * FROM users WHERE id = ?").get(e);if(!r)throw new Error(`User ${e} not found`);return this.mapUser(r)}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=S();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=[],i=[];t.name!=null&&(n.push("name = ?"),i.push(t.name)),t.description!=null&&(n.push("description = ?"),i.push(t.description)),n.length>0&&(i.push(e),this.stmt(`UPDATE roles SET ${n.join(", ")} WHERE id = ?`).run(i));let o=this.stmt("SELECT * FROM roles WHERE id = ?").get(e);if(!o)throw new Error(`Role ${e} not found`);return{id:o.id,tenantId:o.tenant_id,name:o.name,...o.description!=null?{description:o.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(i=>({id:i.id,tenantId:i.tenant_id,name:i.name,...i.description!=null?{description:i.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=S(),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];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));let i=Math.min(e.limit??50,Tt),o=((e.page??1)-1)*i;return this.stmt(`SELECT * FROM audit_logs WHERE ${t.join(" AND ")} ORDER BY created_at DESC LIMIT ? OFFSET ?`).all([...n,i,o]).map(a=>this.mapAuditLog(a))}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 i=500,o=0;for(;;){let r=this.stmt(`SELECT * FROM audit_logs WHERE ${t.join(" AND ")} ORDER BY created_at DESC LIMIT ? OFFSET ?`).all([...n,i,o]);if(r.length===0)break;for(let a of r)yield this.mapAuditLog(a);o+=i}}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),i=this.stmt("SELECT COUNT(*) as n FROM visitors WHERE tenant_id = ?").get(e),o=t?.n??0,r=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 u of r){let d=Object.values(JSON.parse(u.consent_json)),p=d.length>0?d.filter(E=>E==="granted").length/d.length:0;p>=.8?a++:p<=.2&&c++}let l=r.length||1;return{totalConsents:o,gpcDetectedCount:n?.n??0,totalVisitors:i?.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 i of t){let o=JSON.parse(i.consent_json);for(let[r,a]of Object.entries(o))n[r]||(n[r]={granted:0,denied:0,objected:0}),a==="granted"?n[r].granted++:a==="denied"?n[r].denied++:a==="objected"&&n[r].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(i=>({date:i.date,count:i.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),i=t?.n??0,o=n?.n??0;return{detected:o,total:i,rate:i>0?Math.round(o/i*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=S(),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 i=this.stmt("SELECT * FROM tenants WHERE id = ?").get(t);if(!i)throw new Error("Failed to create tenant");return{id:i.id,name:i.name,slug:i.slug,createdAt:i.created_at,updatedAt:i.updated_at}}async updateTenant(e,t){let n=new Date().toISOString(),i=["updated_at = ?"],o=[n];t.name!=null&&(i.push("name = ?"),o.push(t.name)),t.slug!=null&&(i.push("slug = ?"),o.push(t.slug)),o.push(e),this.stmt(`UPDATE tenants SET ${i.join(", ")} WHERE id = ?`).run(o);let r=this.stmt("SELECT * FROM tenants WHERE id = ?").get(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){this.stmt("DELETE FROM tenants WHERE id = ?").run(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:i}of n)this.stmt("DELETE FROM consent_history WHERE visitor_id = ?").run(i),this.stmt("DELETE FROM consent_records WHERE visitor_id = ?").run(i);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,createdAt:e.created_at}}async createApiKey(e){let t=S(),n=new Date().toISOString();this.stmt("INSERT INTO api_keys (id,tenant_id,key_hash,name,created_at) VALUES (?,?,?,?,?)").run([t,e.tenantId,e.keyHash,e.name,n]);let i=this.stmt("SELECT * FROM api_keys WHERE id = ?").get(t);if(!i)throw new Error("Failed to create API key");return this.mapApiKey(i)}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 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))}mapCookieTemplate(e){return{id:e.id,tenantId:e.tenant_id,name:e.name,cookies:JSON.parse(e.cookies_json),createdAt:e.created_at,updatedAt:e.updated_at}}async createCookieTemplate(e){let t=S(),n=new Date().toISOString();return this.stmt("INSERT INTO cookie_templates (id,tenant_id,name,cookies_json,created_at,updated_at) VALUES (?,?,?,?,?,?)").run([t,e.tenantId,e.name,JSON.stringify(e.cookies),n,n]),this.mapCookieTemplate(this.stmt("SELECT * FROM cookie_templates WHERE id = ?").get(t))}async updateCookieTemplate(e,t){let n=new Date().toISOString();t.name!==void 0&&this.stmt("UPDATE cookie_templates SET name = ?, updated_at = ? WHERE id = ?").run([t.name,n,e]),t.cookies!==void 0&&this.stmt("UPDATE cookie_templates SET cookies_json = ?, updated_at = ? WHERE id = ?").run([JSON.stringify(t.cookies),n,e]);let i=this.stmt("SELECT * FROM cookie_templates WHERE id = ?").get(e);if(!i)throw new Error("Cookie template not found");return this.mapCookieTemplate(i)}async deleteCookieTemplate(e){this.stmt("DELETE FROM cookie_templates WHERE id = ?").run(e)}async getCookieTemplate(e){let t=this.stmt("SELECT * FROM cookie_templates WHERE id = ?").get(e);return t?this.mapCookieTemplate(t):null}async getCookieTemplates(e){return this.stmt("SELECT * FROM cookie_templates WHERE tenant_id = ? ORDER BY name ASC").all(e).map(n=>this.mapCookieTemplate(n))}async copyCookieTemplate(e,t){let n=await this.getCookieTemplate(e);if(!n)throw new Error("Cookie template not found");return this.createCookieTemplate({tenantId:n.tenantId,name:t,cookies:n.cookies})}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=S(),n=new Date().toISOString(),{tenantId:i,name:o,...r}=e;return this.stmt("INSERT INTO ui_templates (id,tenant_id,name,settings_json,created_at,updated_at) VALUES (?,?,?,?,?,?)").run([t,i,o,JSON.stringify(r),n,n]),this.mapUITemplate(this.stmt("SELECT * FROM ui_templates WHERE id = ?").get(t))}async updateUITemplate(e,t){let n=new Date().toISOString(),i=this.stmt("SELECT * FROM ui_templates WHERE id = ?").get(e);if(!i)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:o,...r}=t;if(Object.keys(r).length>0){let c={...JSON.parse(i.settings_json),...r};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:i,mainBanner:o,gpcBanner:r,preferenceModal:a}=n;return this.createUITemplate({tenantId:i,name:t,mainBanner:o,gpcBanner:r,preferenceModal:a})}mapProfileSummary(e){return{id:e.id,name:e.name,defaultLocale:e.default_locale,complianceGroup:e.compliance_group??null,version:e.version,isActive:e.is_active===1,cookieTemplateName:e.cookie_template_name,uiTemplateName:e.ui_template_name,createdAt:e.created_at,updatedAt:e.updated_at}}PROFILE_SUMMARY_SQL=`
|
|
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=H(),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.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=B(),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,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.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 l=k(e.q);n.push(l,l,l,l)}let s=t.join(" AND "),r=e.page??1,o=Math.min(e.limit??50,Gt),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]),d=this.stmt(`SELECT COUNT(*) AS total FROM consent_records WHERE ${s}`).get(n);return{items:c.map(l=>this.mapConsentSummary(l)),total:d?.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=$(),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 l=k(e.q);n.push(l,l)}let s=t.join(" AND "),r=e.page??1,o=Math.min(e.limit??50,Gt),a=(r-1)*o,c=this.stmt(`SELECT * FROM visitors WHERE ${s} ORDER BY last_seen DESC LIMIT ? OFFSET ?`).all([...n,o,a]),d=this.stmt(`SELECT COUNT(*) AS total FROM visitors WHERE ${s}`).get(n);return{items:c.map(l=>this.mapVisitor(l)),total:d?.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 l=k(e.q);n.push(l,l,l,l)}let s=t.join(" AND "),r=e.page??1,o=Math.min(e.limit??50,Gt),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]),d=this.stmt(`SELECT COUNT(*) AS total FROM audit_logs WHERE ${s}`).get(n);return{items:c.map(l=>this.mapAuditSummary(l)),total:d?.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 l of o){let u=Object.values(JSON.parse(l.consent_json)),p=u.length>0?u.filter(h=>h==="granted").length/u.length:0;p>=.8?a++:p<=.2&&c++}let d=o.length||1;return{totalConsents:r,gpcDetectedCount:n?.n??0,totalVisitors:s?.n??0,acceptedPct:Math.round(a/d*100),rejectedPct:Math.round(c/d*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 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}:{}}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, 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, updated_at = excluded.updated_at`).run([e,JSON.stringify(s.allowedOrigins??[]),JSON.stringify(s.adminAllowedOrigins??[]),s.setupCompleted?1:0,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}async purgeExpiredAuditLogs(e){let t=new Date(Date.now()-e*864e5).toISOString(),{count:n}=this.stmt("SELECT COUNT(*) AS count FROM audit_logs WHERE created_at < ?").get(t);return this.stmt("DELETE FROM audit_logs WHERE created_at < ?").run(t),n}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=ne(),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=se(),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=`
|
|
72
63
|
SELECT
|
|
73
|
-
p.id, p.name, p.default_locale, p.
|
|
64
|
+
p.id, p.name, p.default_locale, p.created_at, p.updated_at,
|
|
74
65
|
json_extract(p.profile_json, '$.complianceGroup') AS compliance_group,
|
|
66
|
+
json_extract(p.profile_json, '$.customComplianceGroup') AS custom_compliance_group,
|
|
75
67
|
json_extract(p.profile_json, '$.isActive') AS is_active,
|
|
76
|
-
json_extract(p.profile_json, '$.
|
|
68
|
+
json_extract(p.profile_json, '$.consentTemplateId') AS consent_template_id,
|
|
77
69
|
json_extract(p.profile_json, '$.uiTemplateId') AS ui_template_id,
|
|
78
|
-
ct.name AS
|
|
70
|
+
ct.name AS consent_template_name,
|
|
79
71
|
ut.name AS ui_template_name
|
|
80
72
|
FROM profiles p
|
|
81
|
-
LEFT JOIN
|
|
73
|
+
LEFT JOIN consent_templates ct ON ct.id = json_extract(p.profile_json, '$.consentTemplateId')
|
|
82
74
|
LEFT JOIN ui_templates ut ON ut.id = json_extract(p.profile_json, '$.uiTemplateId')
|
|
83
|
-
`;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
|
|
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,d=0,l=0,u={};for(let g of o){let f=Object.values(JSON.parse(g.consent_json)),w=f.length>0?f.filter(T=>T==="granted").length/f.length:0,R=f.length>0?f.filter(T=>T==="denied").length/f.length:0;a++;let I=g.locale;u[I]||(u[I]={total:0,granted:0,denied:0,managed:0}),u[I].total++,w>=.9?(c++,u[I].granted++):R>=.9?(d++,u[I].denied++):(l++,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),h=new Map;for(let g of p){let f=h.get(g.d)??{total:0,granted:0,denied:0,managed:0},w=Object.values(JSON.parse(g.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;f.total++,R>=.9?f.granted++:I>=.9?f.denied++:f.managed++,h.set(g.d,f)}let E=Array.from(h.entries()).map(([g,f])=>({date:g,...f}));return{total:a,granted:c,denied:d,managed:l,grantedPct:a>0?Math.round(c/a*1e3)/10:0,deniedPct:a>0?Math.round(d/a*1e3)/10:0,managedPct:a>0?Math.round(l/a*1e3)/10:0,byLocale:u,byDate:E}}};var Kt=500,Je=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}=K(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(ce),this.db.exec(le),this.db.exec("PRAGMA user_version = 1")),this.db.exec(`
|
|
84
76
|
CREATE INDEX IF NOT EXISTS idx_profiles_tenant_compliance_active
|
|
85
77
|
ON profiles (tenant_id, json_extract(profile_json,'$.complianceGroup'), json_extract(profile_json,'$.isActive'));
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
CREATE INDEX IF NOT EXISTS idx_consents_tenant_profile_date
|
|
89
|
-
|
|
90
|
-
CREATE INDEX IF NOT EXISTS idx_consents_visitor
|
|
91
|
-
ON consent_records (visitor_id);
|
|
92
|
-
CREATE INDEX IF NOT EXISTS idx_visitors_tenant
|
|
93
|
-
ON visitors (tenant_id);
|
|
94
|
-
CREATE INDEX IF NOT EXISTS idx_audit_tenant_date
|
|
95
|
-
ON audit_logs (tenant_id, created_at);
|
|
96
|
-
`)}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=ee(),n=new Date().toISOString();this.stmt("INSERT INTO profiles (id,tenant_id,name,default_locale,profile_json,created_at,updated_at) VALUES (?,?,?,?,?,?,?)").run(t,e.tenantId,e.name,e.defaultLocale,JSON.stringify(e.profileJson),n,n);let i=await this.getProfile(t);if(!i)throw new Error("Failed to create profile");return i}async updateProfile(e,t){if(!await this.getProfile(e))throw new Error(`Profile ${e} not found`);let i=new Date().toISOString(),o=["updated_at = ?","version = version + 1"],r=[i];t.name!=null&&(o.push("name = ?"),r.push(t.name)),t.defaultLocale!=null&&(o.push("default_locale = ?"),r.push(t.defaultLocale)),t.profileJson!=null&&(o.push("profile_json = ?"),r.push(JSON.stringify(t.profileJson))),r.push(e),this.stmt(`UPDATE profiles SET ${o.join(", ")} WHERE id = ?`).run(...r);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 = ? AND json_extract(profile_json, '$.complianceGroup') = ? AND json_extract(profile_json, '$.isActive') = 1 LIMIT 1").get(e,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,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}:{},createdAt:e.created_at,updatedAt:e.updated_at}}async createConsent(e){let t=S(),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,created_at,updated_at) VALUES (?,?,?,?,?,?,?,?,?,?,?)").run(t,e.tenantId,e.visitorId,e.profileId,e.profileVersion,e.locale,JSON.stringify(e.consentJson),e.gpcDetected?1:0,e.source,n,n);let i=S();this.stmt("INSERT INTO consent_history (id,tenant_id,consent_record_id,visitor_id,old_json,new_json,action,created_at) VALUES (?,?,?,?,?,?,?,?)").run(i,e.tenantId,t,e.visitorId,null,JSON.stringify(e.consentJson),"created",n);let o=await this.getConsent(e.visitorId);if(!o)throw new Error("Failed to create consent");return o}async updateConsent(e,t){let n=await this.getConsent(e);if(!n)throw new Error(`Consent for visitor ${e} not found`);let i=new Date().toISOString(),o=["updated_at = ?","consent_json = ?"],r=[i,JSON.stringify(t.consentJson)];t.locale!=null&&(o.push("locale = ?"),r.push(t.locale)),t.gpcDetected!=null&&(o.push("gpc_detected = ?"),r.push(t.gpcDetected?1:0)),r.push(e),this.stmt(`UPDATE consent_records SET ${o.join(", ")} WHERE visitor_id = ?`).run(...r);let a=S();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",i);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];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));let i=Math.min(e.limit??50,Ct),o=((e.page??1)-1)*i;return n.push(i,o),this.stmt(`SELECT * FROM consent_records WHERE ${t.join(" AND ")} ORDER BY created_at DESC LIMIT ? OFFSET ?`).all(...n).map(a=>this.mapConsent(a))}async*streamConsents(e){let n=0;for(;;){let i=this.stmt("SELECT * FROM consent_records WHERE tenant_id = ? LIMIT ? OFFSET ?").all(e.tenantId,500,n);if(i.length===0)break;for(let o of i)yield this.mapConsent(o);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}))}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=S(),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 i=await this.getVisitor(e.visitorId);if(!i)throw new Error("Failed to create visitor");return i}async updateVisitor(e,t){let n=new Date().toISOString(),i=["last_seen = ?"],o=[t.lastSeen??n];t.country!=null&&(i.push("country = ?"),o.push(t.country)),t.region!=null&&(i.push("region = ?"),o.push(t.region)),t.city!=null&&(i.push("city = ?"),o.push(t.city)),o.push(e),this.stmt(`UPDATE visitors SET ${i.join(", ")} WHERE visitor_id = ?`).run(...o);let r=await this.getVisitor(e);if(!r)throw new Error(`Visitor ${e} not found`);return r}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];e.from&&(t.push("first_seen >= ?"),n.push(e.from)),e.to&&(t.push("first_seen <= ?"),n.push(e.to));let i=Math.min(e.limit??50,Ct),o=((e.page??1)-1)*i;return n.push(i,o),this.stmt(`SELECT * FROM visitors WHERE ${t.join(" AND ")} ORDER BY last_seen DESC LIMIT ? OFFSET ?`).all(...n).map(a=>this.mapVisitor(a))}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=S(),n=new Date().toISOString(),i=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,i,n,n);let o=await this.getUserByEmail(e.email);if(!o)throw new Error("Failed to create user");return o}async updateUser(e,t){let n=new Date().toISOString(),i=["updated_at = ?"],o=[n];t.name!=null&&(i.push("name = ?"),o.push(t.name)),t.email!=null&&(i.push("email = ?"),o.push(t.email)),t.passwordHash!=null&&(i.push("password_hash = ?"),o.push(t.passwordHash)),t.isActive!=null&&(i.push("is_active = ?"),o.push(t.isActive?1:0)),"totpSecret"in t&&(i.push("totp_secret = ?"),o.push(t.totpSecret??null)),t.totpEnabled!=null&&(i.push("totp_enabled = ?"),o.push(t.totpEnabled?1:0)),"allowedTenants"in t&&(i.push("allowed_tenants = ?"),o.push(t.allowedTenants?.length?JSON.stringify(t.allowedTenants):null)),o.push(e),this.stmt(`UPDATE users SET ${i.join(", ")} WHERE id = ?`).run(...o);let r=this.stmt("SELECT * FROM users WHERE id = ?").get(e);if(!r)throw new Error(`User ${e} not found`);return this.mapUser(r)}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=S();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=[],i=[];t.name!=null&&(n.push("name = ?"),i.push(t.name)),t.description!=null&&(n.push("description = ?"),i.push(t.description)),n.length>0&&(i.push(e),this.stmt(`UPDATE roles SET ${n.join(", ")} WHERE id = ?`).run(...i));let o=this.stmt("SELECT * FROM roles WHERE id = ?").get(e);if(!o)throw new Error(`Role ${e} not found`);return{id:o.id,tenantId:o.tenant_id,name:o.name,...o.description!=null?{description:o.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(i=>({id:i.id,tenantId:i.tenant_id,name:i.name,...i.description!=null?{description:i.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=S(),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];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));let i=Math.min(e.limit??50,Ct),o=((e.page??1)-1)*i;return n.push(i,o),this.stmt(`SELECT * FROM audit_logs WHERE ${t.join(" AND ")} ORDER BY created_at DESC LIMIT ? OFFSET ?`).all(...n).map(a=>this.mapAuditLog(a))}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 i=500,o=0;for(;;){let r=this.stmt(`SELECT * FROM audit_logs WHERE ${t.join(" AND ")} ORDER BY created_at DESC LIMIT ? OFFSET ?`).all(...n,i,o);if(r.length===0)break;for(let a of r)yield this.mapAuditLog(a);o+=i}}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),i=this.stmt("SELECT COUNT(*) as n FROM visitors WHERE tenant_id = ?").get(e),o=t?.n??0,r=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 u of r){let d=Object.values(JSON.parse(u.consent_json)),p=d.length>0?d.filter(E=>E==="granted").length/d.length:0;p>=.8?a++:p<=.2&&c++}let l=r.length||1;return{totalConsents:o,gpcDetectedCount:n?.n??0,totalVisitors:i?.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 i of t){let o=JSON.parse(i.consent_json);for(let[r,a]of Object.entries(o))n[r]||(n[r]={granted:0,denied:0,objected:0}),a==="granted"?n[r].granted++:a==="denied"?n[r].denied++:a==="objected"&&n[r].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(i=>({date:i.date,count:i.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),i=t?.n??0,o=n?.n??0;return{detected:o,total:i,rate:i>0?Math.round(o/i*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=S(),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 i=this.stmt("SELECT * FROM tenants WHERE id = ?").get(t);if(!i)throw new Error("Failed to create tenant");return{id:i.id,name:i.name,slug:i.slug,createdAt:i.created_at,updatedAt:i.updated_at}}async updateTenant(e,t){let n=new Date().toISOString(),i=["updated_at = ?"],o=[n];t.name!=null&&(i.push("name = ?"),o.push(t.name)),t.slug!=null&&(i.push("slug = ?"),o.push(t.slug)),o.push(e),this.stmt(`UPDATE tenants SET ${i.join(", ")} WHERE id = ?`).run(...o);let r=this.stmt("SELECT * FROM tenants WHERE id = ?").get(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){this.stmt("DELETE FROM tenants WHERE id = ?").run(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:i}of n)this.stmt("DELETE FROM consent_history WHERE visitor_id = ?").run(i),this.stmt("DELETE FROM consent_records WHERE visitor_id = ?").run(i);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,createdAt:e.created_at}}async createApiKey(e){let t=S(),n=new Date().toISOString();this.stmt("INSERT INTO api_keys (id,tenant_id,key_hash,name,created_at) VALUES (?,?,?,?,?)").run(t,e.tenantId,e.keyHash,e.name,n);let i=this.stmt("SELECT * FROM api_keys WHERE id = ?").get(t);if(!i)throw new Error("Failed to create API key");return this.mapApiKey(i)}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 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))}mapCookieTemplate(e){return{id:e.id,tenantId:e.tenant_id,name:e.name,cookies:JSON.parse(e.cookies_json),createdAt:e.created_at,updatedAt:e.updated_at}}async createCookieTemplate(e){let t=S(),n=new Date().toISOString();return this.stmt("INSERT INTO cookie_templates (id,tenant_id,name,cookies_json,created_at,updated_at) VALUES (?,?,?,?,?,?)").run(t,e.tenantId,e.name,JSON.stringify(e.cookies),n,n),this.mapCookieTemplate(this.stmt("SELECT * FROM cookie_templates WHERE id = ?").get(t))}async updateCookieTemplate(e,t){let n=new Date().toISOString();t.name!==void 0&&this.stmt("UPDATE cookie_templates SET name = ?, updated_at = ? WHERE id = ?").run(t.name,n,e),t.cookies!==void 0&&this.stmt("UPDATE cookie_templates SET cookies_json = ?, updated_at = ? WHERE id = ?").run(JSON.stringify(t.cookies),n,e);let i=this.stmt("SELECT * FROM cookie_templates WHERE id = ?").get(e);if(!i)throw new Error("Cookie template not found");return this.mapCookieTemplate(i)}async deleteCookieTemplate(e){this.stmt("DELETE FROM cookie_templates WHERE id = ?").run(e)}async getCookieTemplate(e){let t=this.stmt("SELECT * FROM cookie_templates WHERE id = ?").get(e);return t?this.mapCookieTemplate(t):null}async getCookieTemplates(e){return this.stmt("SELECT * FROM cookie_templates WHERE tenant_id = ? ORDER BY name ASC").all(e).map(n=>this.mapCookieTemplate(n))}async copyCookieTemplate(e,t){let n=await this.getCookieTemplate(e);if(!n)throw new Error("Cookie template not found");return this.createCookieTemplate({tenantId:n.tenantId,name:t,cookies:n.cookies})}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=S(),n=new Date().toISOString(),{tenantId:i,name:o,...r}=e;return this.stmt("INSERT INTO ui_templates (id,tenant_id,name,settings_json,created_at,updated_at) VALUES (?,?,?,?,?,?)").run(t,i,o,JSON.stringify(r),n,n),this.mapUITemplate(this.stmt("SELECT * FROM ui_templates WHERE id = ?").get(t))}async updateUITemplate(e,t){let n=new Date().toISOString(),i=this.stmt("SELECT * FROM ui_templates WHERE id = ?").get(e);if(!i)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:o,...r}=t;if(Object.keys(r).length>0){let c={...JSON.parse(i.settings_json),...r};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:i,mainBanner:o,gpcBanner:r,preferenceModal:a}=n;return this.createUITemplate({tenantId:i,name:t,mainBanner:o,gpcBanner:r,preferenceModal:a})}mapProfileSummary(e){return{id:e.id,name:e.name,defaultLocale:e.default_locale,complianceGroup:e.compliance_group??null,version:e.version,isActive:e.is_active===1,cookieTemplateName:e.cookie_template_name,uiTemplateName:e.ui_template_name,createdAt:e.created_at,updatedAt:e.updated_at}}PROFILE_SUMMARY_SQL=`
|
|
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=H(),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
|
+
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.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=B(),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,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.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 l=k(e.q);n.push(l,l,l,l)}let s=t.join(" AND "),r=e.page??1,o=Math.min(e.limit??50,Kt),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),d=this.stmt(`SELECT COUNT(*) AS total FROM consent_records WHERE ${s}`).get(...n);return{items:c.map(l=>this.mapConsentSummary(l)),total:d.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=$(),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 l=k(e.q);n.push(l,l)}let s=t.join(" AND "),r=e.page??1,o=Math.min(e.limit??50,Kt),a=(r-1)*o,c=this.stmt(`SELECT * FROM visitors WHERE ${s} ORDER BY last_seen DESC LIMIT ? OFFSET ?`).all(...n,o,a),d=this.stmt(`SELECT COUNT(*) AS total FROM visitors WHERE ${s}`).get(...n);return{items:c.map(l=>this.mapVisitor(l)),total:d.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 l=k(e.q);n.push(l,l,l,l)}let s=t.join(" AND "),r=e.page??1,o=Math.min(e.limit??50,Kt),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),d=this.stmt(`SELECT COUNT(*) AS total FROM audit_logs WHERE ${s}`).get(...n);return{items:c.map(l=>this.mapAuditSummary(l)),total:d.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 l of o){let u=Object.values(JSON.parse(l.consent_json)),p=u.length>0?u.filter(h=>h==="granted").length/u.length:0;p>=.8?a++:p<=.2&&c++}let d=o.length||1;return{totalConsents:r,gpcDetectedCount:n?.n??0,totalVisitors:s?.n??0,acceptedPct:Math.round(a/d*100),rejectedPct:Math.round(c/d*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 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}:{}}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, 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, updated_at = excluded.updated_at`).run(e,JSON.stringify(s.allowedOrigins??[]),JSON.stringify(s.adminAllowedOrigins??[]),s.setupCompleted?1:0,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}async purgeExpiredAuditLogs(e){let t=new Date(Date.now()-e*864e5).toISOString(),{count:n}=this.stmt("SELECT COUNT(*) AS count FROM audit_logs WHERE created_at < ?").get(t);return this.stmt("DELETE FROM audit_logs WHERE created_at < ?").run(t),n}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=ne(),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=se(),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=`
|
|
97
82
|
SELECT
|
|
98
|
-
p.id, p.name, p.default_locale, p.
|
|
83
|
+
p.id, p.name, p.default_locale, p.created_at, p.updated_at,
|
|
99
84
|
json_extract(p.profile_json, '$.complianceGroup') AS compliance_group,
|
|
85
|
+
json_extract(p.profile_json, '$.customComplianceGroup') AS custom_compliance_group,
|
|
100
86
|
json_extract(p.profile_json, '$.isActive') AS is_active,
|
|
101
|
-
json_extract(p.profile_json, '$.
|
|
87
|
+
json_extract(p.profile_json, '$.consentTemplateId') AS consent_template_id,
|
|
102
88
|
json_extract(p.profile_json, '$.uiTemplateId') AS ui_template_id,
|
|
103
|
-
ct.name AS
|
|
89
|
+
ct.name AS consent_template_name,
|
|
104
90
|
ut.name AS ui_template_name
|
|
105
91
|
FROM profiles p
|
|
106
|
-
LEFT JOIN
|
|
92
|
+
LEFT JOIN consent_templates ct ON ct.id = json_extract(p.profile_json, '$.consentTemplateId')
|
|
107
93
|
LEFT JOIN ui_templates ut ON ut.id = json_extract(p.profile_json, '$.uiTemplateId')
|
|
108
|
-
`;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 findProfilesUsingCookieTemplate(e){return this.stmt(`${this.PROFILE_SUMMARY_SQL} WHERE json_extract(p.profile_json, '$.cookieTemplateId') = ? 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 = ?"],i=[e];t.profileId&&(n.push("cr.profile_id = ?"),i.push(t.profileId)),t.from&&(n.push("cr.created_at >= ?"),i.push(t.from)),t.to&&(n.push("cr.created_at <= ?"),i.push(t.to)),t.locale&&(n.push("cr.locale = ?"),i.push(t.locale));let o=n.join(" AND "),r=this.stmt(`SELECT cr.locale, cr.consent_json FROM consent_records cr WHERE ${o}`).all(...i),a=0,c=0,l=0,u=0,d={};for(let f of r){let _=JSON.parse(f.consent_json),h=Object.values(_),T=h.filter(V=>V==="granted").length,C=h.filter(V=>V==="denied").length,P=h.length>0?T/h.length:0;a++;let D=f.locale;d[D]||(d[D]={total:0,granted:0,denied:0,managed:0}),d[D].total++,P>=.9?(c++,d[D].granted++):C/(h.length||1)>=.9?(l++,d[D].denied++):(u++,d[D].managed++)}let p=this.stmt(`SELECT date(cr.created_at) as d, cr.consent_json FROM consent_records cr WHERE ${o} ORDER BY d ASC`).all(...i),E=new Map;for(let f of p){let _=E.get(f.d)??{total:0,granted:0,denied:0,managed:0},h=JSON.parse(f.consent_json),T=Object.values(h),C=T.length>0?T.filter(D=>D==="granted").length/T.length:0,P=T.length>0?T.filter(D=>D==="denied").length/T.length:0;_.total++,C>=.9?_.granted++:P>=.9?_.denied++:_.managed++,E.set(f.d,_)}let A=Array.from(E.entries()).map(([f,_])=>({date:f,..._}));return{total:a,granted:c,denied:l,managed:u,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(u/a*1e3)/10:0,byLocale:d,byDate:A}}};function je(s){return{id:s._id,tenantId:s.tenant_id,name:s.name,defaultLocale:s.default_locale,version:s.version,profileJson:s.profile_json,createdAt:s.created_at,updatedAt:s.updated_at}}function fe(s){return{id:s._id,tenantId:s.tenant_id,visitorId:s.visitor_id,profileId:s.profile_id,profileVersion:s.profile_version,locale:s.locale,consentJson:s.consent_json,gpcDetected:s.gpc_detected,source:s.source,createdAt:s.created_at,updatedAt:s.updated_at}}function to(s){return{id:s._id,tenantId:s.tenant_id,consentRecordId:s.consent_record_id,visitorId:s.visitor_id,oldJson:s.old_json,newJson:s.new_json,action:s.action,createdAt:s.created_at}}function $e(s){return{id:s._id,tenantId:s.tenant_id,visitorId:s.visitor_id,...s.country!=null?{country:s.country}:{},...s.region!=null?{region:s.region}:{},...s.city!=null?{city:s.city}:{},...s.ip_hash!=null?{ipHash:s.ip_hash}:{},...s.user_agent_hash!=null?{userAgentHash:s.user_agent_hash}:{},firstSeen:s.first_seen,lastSeen:s.last_seen}}function ye(s){return{id:s._id,tenantId:s.tenant_id,name:s.name,email:s.email,passwordHash:s.password_hash,isActive:s.is_active,createdAt:s.created_at,updatedAt:s.updated_at}}function Be(s){return{id:s._id,tenantId:s.tenant_id,name:s.name,...s.description!=null?{description:s.description}:{}}}function An(s){return{id:s._id,name:s.name,...s.description!=null?{description:s.description}:{}}}function Tn(s){return{id:s._id,tenantId:s.tenant_id,action:s.action,resourceType:s.resource_type,...s.user_id!=null?{userId:s.user_id}:{},...s.resource_id!=null?{resourceId:s.resource_id}:{},...s.old_data!=null?{oldData:s.old_data}:{},...s.new_data!=null?{newData:s.new_data}:{},createdAt:s.created_at}}var he=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(){await this.col("consent_records").createIndex({visitor_id:1,profile_id:1},{unique:!0}),await this.col("consent_records").createIndex({tenant_id:1}),await this.col("consent_records").createIndex({created_at:1}),await this.col("visitors").createIndex({visitor_id:1},{unique:!0}),await this.col("visitors").createIndex({tenant_id:1}),await this.col("users").createIndex({email:1},{unique:!0}),await this.col("audit_logs").createIndex({tenant_id:1,created_at:1}),await this.col("user_roles").createIndex({user_id:1}),await this.col("role_permissions").createIndex({role_id:1})}async seed(){let{id:e,name:t,slug:n}=F;await this.col("tenants").findOne({_id:e})||await this.col("tenants").insertOne({_id:e,name:t,slug:n});for(let o of x){let r={_id:o.id,name:o.name,description:o.description};await this.col("permissions").updateOne({_id:o.id},{$setOnInsert:r},{upsert:!0})}for(let o of q){let r={_id:o.id,tenant_id:o.tenantId,name:o.name,description:o.description};await this.col("roles").updateOne({_id:o.id},{$setOnInsert:r},{upsert:!0})}let i=this.col("role_permissions");for(let{roleId:o,permissionId:r}of K)await i.updateOne({role_id:o,permission_id:r},{$setOnInsert:{role_id:o,permission_id:r}},{upsert:!0})}async createProfile(e){let t=new Date().toISOString(),n={_id:S(),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(n),je(n)}async updateProfile(e,t){let i={updated_at:new Date().toISOString()};t.name!=null&&(i.name=t.name),t.defaultLocale!=null&&(i.default_locale=t.defaultLocale),t.profileJson!=null&&(i.profile_json=t.profileJson);let o=await this.col("profiles").findOneAndUpdate({_id:e},{$set:i,$inc:{version:1}},{returnDocument:"after"});if(!o)throw new Error(`Profile ${e} not found`);return je(o)}async deleteProfile(e){await this.col("profiles").deleteOne({_id:e})}async getProfile(e){let t=await this.col("profiles").findOne({_id:e});return t?je(t):null}async getProfiles(e){return(await this.col("profiles").find({tenant_id:e}).toArray()).map(je)}async findActiveProfileByComplianceGroup(e,t){return(await this.getProfiles(e)).find(i=>i.profileJson.complianceGroup===t&&i.profileJson.isActive)??null}async createConsent(e){let t=new Date().toISOString(),n={_id:S(),tenant_id:e.tenantId,visitor_id:e.visitorId,profile_id:e.profileId,profile_version:e.profileVersion,locale:e.locale,consent_json:e.consentJson,gpc_detected:e.gpcDetected,source:e.source,created_at:t,updated_at:t};return await this.col("consent_records").insertOne(n),await this.writeHistory(n._id,e.tenantId,e.visitorId,null,e.consentJson,"created"),fe(n)}async updateConsent(e,t){let n=await this.getConsent(e);if(!n)throw new Error(`Consent for visitor ${e} not found`);let o={updated_at:new Date().toISOString(),consent_json:t.consentJson};t.locale!=null&&(o.locale=t.locale),t.gpcDetected!=null&&(o.gpc_detected=t.gpcDetected);let r=await this.col("consent_records").findOneAndUpdate({visitor_id:e},{$set:o},{returnDocument:"after"});if(!r)throw new Error(`Consent for visitor ${e} not found`);return await this.writeHistory(r._id,r.tenant_id,e,n.consentJson,t.consentJson,"updated"),fe(r)}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?fe(t):null}async getConsents(e){let t=this.buildConsentFilter(e),n=e.page??1,i=e.limit??50;return(await this.col("consent_records").find(t).sort({created_at:-1}).skip((n-1)*i).limit(i).toArray()).map(fe)}async*streamConsents(e){let t=this.buildConsentFilter(e),n=this.col("consent_records").find(t).sort({created_at:1});for await(let i of n)yield fe(i)}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}return t}async writeHistory(e,t,n,i,o,r){let a={_id:S(),tenant_id:t,consent_record_id:e,visitor_id:n,old_json:i,new_json:o,action:r,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(to)}async createVisitor(e){let t=new Date().toISOString(),n={_id:S(),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),$e(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 i=await this.col("visitors").findOneAndUpdate({visitor_id:e},{$set:n},{returnDocument:"after"});if(!i)throw new Error(`Visitor ${e} not found`);return $e(i)}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?$e(t):null}async getVisitors(e){let t={tenant_id:e.tenantId};if(e.from!=null||e.to!=null){let r={};e.from!=null&&(r.$gte=e.from),e.to!=null&&(r.$lte=e.to),t.first_seen=r}let n=e.page??1,i=e.limit??50;return(await this.col("visitors").find(t).sort({first_seen:-1}).skip((n-1)*i).limit(i).toArray()).map($e)}async getOverviewStats(e){let[t,n,i]=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})]),o=[{$match:{tenant_id:e}},{$addFields:{values:{$objectToArray:"$consent_json"}}},{$unwind:"$values"},{$group:{_id:"$values.v",count:{$sum:1}}}],r=await this.col("consent_records").aggregate(o).toArray(),a=0,c=0,l=0;for(let u of r)l+=u.count,u._id==="granted"&&(a=u.count),u._id==="denied"&&(c=u.count);return{totalConsents:t,acceptedPct:l>0?Math.round(a/l*100):0,rejectedPct:l>0?Math.round(c/l*100):0,totalVisitors:i,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(),i={};for(let o of n){let r=o._id.cookie;i[r]||(i[r]={granted:0,denied:0,objected:0});let a=i[r];a&&(o._id.status==="granted"&&(a.granted+=o.count),o._id.status==="denied"&&(a.denied+=o.count),o._id.status==="objected"&&(a.objected+=o.count))}return i}async getTimeline(e,t=30){let n=new Date(Date.now()-t*864e5).toISOString(),i=[{$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(i).toArray()).map(r=>({date:r._id,count:r.count}))}async createUser(e){let t=new Date().toISOString(),n={_id:S(),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),ye(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 i=await this.col("users").findOneAndUpdate({_id:e},{$set:n},{returnDocument:"after"});if(!i)throw new Error(`User ${e} not found`);return ye(i)}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?ye(t):null}async getUserByEmail(e){let t=await this.col("users").findOne({email:e});return t?ye(t):null}async getUsers(e){return(await this.col("users").find({tenant_id:e}).toArray()).map(ye)}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(Be)}async createRole(e){let t={_id:S(),tenant_id:e.tenantId,name:e.name,...e.description!=null?{description:e.description}:{}};return await this.col("roles").insertOne(t),Be(t)}async updateRole(e,t){let n={};t.name!=null&&(n.name=t.name),t.description!=null&&(n.description=t.description);let i=await this.col("roles").findOneAndUpdate({_id:e},{$set:n},{returnDocument:"after"});if(!i)throw new Error(`Role ${e} not found`);return Be(i)}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(An)}async getPermissionsForRole(e){let n=(await this.col("role_permissions").find({role_id:e}).toArray()).map(o=>o.permission_id);return n.length===0?[]:(await this.col("permissions").find({_id:{$in:n}}).toArray()).map(An)}async getUserRoles(e,t){let n=t?{user_id:e,tenant_id:t}:{user_id:e},o=(await this.col("user_roles").find(n).toArray()).map(a=>a.role_id);return o.length===0?[]:(await this.col("roles").find({_id:{$in:o}}).toArray()).map(Be)}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:S(),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,i=e.limit??50;return(await this.col("audit_logs").find(t).sort({created_at:-1}).skip((n-1)*i).limit(i).toArray()).map(Tn)}async*streamAuditLogs(e){let t=this.buildAuditFilter(e),n=this.col("audit_logs").find(t).sort({created_at:1});for await(let i of n)yield Tn(i)}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}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(i=>({country:i._id,count:i.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}))}async createApiKey(e){let t={_id:S(),tenant_id:e.tenantId,key_hash:e.keyHash,name:e.name,is_active:!0,created_at:new Date().toISOString()};return await this.col("api_keys").insertOne(t),{id:t._id,tenantId:t.tenant_id,keyHash:t.key_hash,name:t.name,isActive:t.is_active,createdAt:t.created_at}}async getApiKeyByHash(e){let t=await this.col("api_keys").findOne({key_hash:e,is_active:!0});return t?{id:t._id,tenantId:t.tenant_id,keyHash:t.key_hash,name:t.name,isActive:t.is_active,createdAt:t.created_at}:null}async revokeApiKey(e){await this.col("api_keys").updateOne({_id:e},{$set:{is_active:!1}})}async getApiKeys(e){return(await this.col("api_keys").find({tenant_id:e}).sort({created_at:-1}).toArray()).map(n=>({id:n._id,tenantId:n.tenant_id,keyHash:n.key_hash,name:n.name,isActive:n.is_active,createdAt:n.created_at}))}async createTenant(e){let t=S(),n=new Date().toISOString(),i={_id:t,name:e.name,slug:e.slug,created_at:n,updated_at:n};return await this.col("tenants").insertOne(i),{id:t,name:e.name,slug:e.slug,createdAt:n,updatedAt:n}}async updateTenant(e,t){let i={updated_at:new Date().toISOString()};t.name!=null&&(i.name=t.name),t.slug!=null&&(i.slug=t.slug),await this.col("tenants").updateOne({_id:e},{$set:i});let o=await this.col("tenants").findOne({_id: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){await this.col("tenants").deleteOne({_id: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 i=n.map(o=>o.visitor_id);return await this.col("consent_history").deleteMany({visitor_id:{$in:i}}),await this.col("consent_records").deleteMany({visitor_id:{$in:i}}),n.length}async createCookieTemplate(){throw new Error("Not implemented")}async updateCookieTemplate(){throw new Error("Not implemented")}async deleteCookieTemplate(){throw new Error("Not implemented")}async getCookieTemplate(){throw new Error("Not implemented")}async getCookieTemplates(){throw new Error("Not implemented")}async copyCookieTemplate(){throw new Error("Not implemented")}async createUITemplate(){throw new Error("Not implemented")}async updateUITemplate(){throw new Error("Not implemented")}async deleteUITemplate(){throw new Error("Not implemented")}async getUITemplate(){throw new Error("Not implemented")}async getUITemplates(){throw new Error("Not implemented")}async copyUITemplate(){throw new Error("Not implemented")}async listProfilesSummary(){return[]}async findProfilesUsingCookieTemplate(){return[]}async findProfilesUsingUITemplate(){return[]}async getOptInStats(){throw new Error("Not implemented")}};function Ve(s){return{id:s.id,tenantId:s.tenant_id,name:s.name,defaultLocale:s.default_locale,version:s.version,profileJson:JSON.parse(s.profile_json),createdAt:s.created_at,updatedAt:s.updated_at}}function _e(s){return{id:s.id,tenantId:s.tenant_id,visitorId:s.visitor_id,profileId:s.profile_id,profileVersion:s.profile_version,locale:s.locale,consentJson:JSON.parse(s.consent_json),gpcDetected:s.gpc_detected===1,source:s.source,createdAt:s.created_at,updatedAt:s.updated_at,...s.age_verified!=null?{ageVerified:s.age_verified===1}:{},...s.parental_consent_token!=null?{parentalConsentToken:s.parental_consent_token}:{},...s.tcf_string!=null?{tcfString:s.tcf_string}:{}}}function no(s){return{id:s.id,tenantId:s.tenant_id,consentRecordId:s.consent_record_id,visitorId:s.visitor_id,oldJson:s.old_json?JSON.parse(s.old_json):null,newJson:JSON.parse(s.new_json),action:s.action,createdAt:s.created_at}}function We(s){return{id:s.id,tenantId:s.tenant_id,visitorId:s.visitor_id,...s.country!=null?{country:s.country}:{},...s.region!=null?{region:s.region}:{},...s.city!=null?{city:s.city}:{},...s.ip_hash!=null?{ipHash:s.ip_hash}:{},...s.user_agent_hash!=null?{userAgentHash:s.user_agent_hash}:{},firstSeen:s.first_seen,lastSeen:s.last_seen}}function Ee(s){return{id:s.id,tenantId:s.tenant_id,name:s.name,email:s.email,passwordHash:s.password_hash,isActive:s.is_active===1,createdAt:s.created_at,updatedAt:s.updated_at,totpEnabled:s.totp_enabled===1,...s.totp_secret!=null?{totpSecret:s.totp_secret}:{}}}function Je(s){return{id:s.id,tenantId:s.tenant_id,name:s.name,...s.description!=null?{description:s.description}:{}}}function Cn(s){return{id:s.id,name:s.name,...s.description!=null?{description:s.description}:{}}}function vn(s){return{id:s.id,tenantId:s.tenant_id,action:s.action,resourceType:s.resource_type,...s.user_id!=null?{userId:s.user_id}:{},...s.resource_id!=null?{resourceId:s.resource_id}:{},...s.old_data!=null?{oldData:JSON.parse(s.old_data)}:{},...s.new_data!=null?{newData:JSON.parse(s.new_data)}:{},createdAt:s.created_at}}var we=class{constructor(e){this.config=e}config;pool;async q(e,t=[]){let[n]=await this.pool.query(e,t);return n}async exec(e,t=[]){let[n]=await this.pool.query(e,t);return n}async connect(){let e=await import("mysql2/promise"),t=this.config;t.uri?this.pool=e.createPool(t.uri):this.pool=e.createPool({host:t.host??"localhost",port:t.port??3306,user:t.user??"root",password:t.password??"",database:t.database??"consenti"}),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 o of Sn.split(";").map(r=>r.trim()).filter(Boolean))await e.query(o);await this.seed(e),await e.query("INSERT IGNORE INTO schema_version (version) VALUES (2)");return}let[n]=await e.query("SELECT version FROM schema_version ORDER BY version DESC LIMIT 1"),i=n[0]?.version??0;if(i<2){let o=["ALTER TABLE consent_records ADD COLUMN IF NOT EXISTS age_verified TINYINT(1) NOT NULL DEFAULT 0","ALTER TABLE consent_records ADD COLUMN IF NOT EXISTS parental_consent_token TEXT","ALTER TABLE consent_records ADD COLUMN IF NOT EXISTS tcf_string TEXT","ALTER TABLE users ADD COLUMN IF NOT EXISTS totp_secret TEXT","ALTER TABLE users ADD COLUMN IF NOT EXISTS totp_enabled TINYINT(1) NOT NULL DEFAULT 0"];for(let r of o)await e.query(r);await e.query("INSERT IGNORE INTO schema_version (version) VALUES (2)")}i<3&&(await e.query("ALTER TABLE user_roles ADD COLUMN IF NOT EXISTS tenant_id VARCHAR(36) NOT NULL DEFAULT 'default'"),await e.query("INSERT IGNORE INTO schema_version (version) VALUES (3)"))}finally{e.release()}}async seed(e){await e.query("INSERT IGNORE INTO tenants (id, name, slug) VALUES (?,?,?)",[F.id,F.name,F.slug]);for(let t of x)await e.query("INSERT IGNORE INTO permissions (id, name, description) VALUES (?,?,?)",[t.id,t.name,t.description]);for(let t of q)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 K)await e.query("INSERT IGNORE INTO role_permissions (role_id, permission_id) VALUES (?,?)",[t,n])}async createProfile(e){let t=S();await this.exec("INSERT INTO profiles (id, tenant_id, name, default_locale, profile_json) VALUES (?,?,?,?,?)",[t,e.tenantId,e.name,e.defaultLocale,JSON.stringify(e.profileJson)]);let i=(await this.q("SELECT * FROM profiles WHERE id=?",[t]))[0];if(!i)throw new Error("Profile creation failed");return Ve(i)}async updateProfile(e,t){let n=["version = version + 1"],i=[];t.name!=null&&(n.push("name=?"),i.push(t.name)),t.defaultLocale!=null&&(n.push("default_locale=?"),i.push(t.defaultLocale)),t.profileJson!=null&&(n.push("profile_json=?"),i.push(JSON.stringify(t.profileJson))),i.push(e),await this.exec(`UPDATE profiles SET ${n.join(",")} WHERE id=?`,i);let r=(await this.q("SELECT * FROM profiles WHERE id=?",[e]))[0];if(!r)throw new Error(`Profile ${e} not found`);return Ve(r)}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]?Ve(t[0]):null}async getProfiles(e){return(await this.q("SELECT * FROM profiles WHERE tenant_id=? ORDER BY created_at DESC",[e])).map(Ve)}async findActiveProfileByComplianceGroup(e,t){return(await this.getProfiles(e)).find(i=>i.profileJson.complianceGroup===t&&i.profileJson.isActive)??null}async createConsent(e){let t=S();await this.exec("INSERT INTO consent_records (id, tenant_id, visitor_id, profile_id, profile_version, locale, consent_json, gpc_detected, source) VALUES (?,?,?,?,?,?,?,?,?)",[t,e.tenantId,e.visitorId,e.profileId,e.profileVersion,e.locale,JSON.stringify(e.consentJson),e.gpcDetected?1:0,e.source]);let i=(await this.q("SELECT * FROM consent_records WHERE id=?",[t]))[0];if(!i)throw new Error("Consent creation failed");return await this.writeHistory(t,e.tenantId,e.visitorId,null,e.consentJson,"created"),_e(i)}async updateConsent(e,t){let n=await this.getConsent(e);if(!n)throw new Error(`Consent for visitor ${e} not found`);let i=["consent_json=?"],o=[JSON.stringify(t.consentJson)];t.locale!=null&&(i.push("locale=?"),o.push(t.locale)),t.gpcDetected!=null&&(i.push("gpc_detected=?"),o.push(t.gpcDetected?1:0)),o.push(e),await this.exec(`UPDATE consent_records SET ${i.join(",")} WHERE visitor_id=?`,o);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"),_e(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]?_e(t[0]):null}async getConsents(e){let{sql:t,params:n}=this.buildConsentQuery(e),i=e.page??1,o=e.limit??50;return(await this.q(`${t} ORDER BY created_at DESC LIMIT ? OFFSET ?`,[...n,o,(i-1)*o])).map(_e)}async*streamConsents(e){let{sql:t,params:n}=this.buildConsentQuery(e),i=await this.q(`${t} ORDER BY created_at ASC`,n);for(let o of i)yield _e(o)}buildConsentQuery(e){let t="SELECT * FROM consent_records WHERE tenant_id=?",n=[e.tenantId];return 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)),{sql:t,params:n}}async writeHistory(e,t,n,i,o,r){await this.exec("INSERT INTO consent_history (id, tenant_id, consent_record_id, visitor_id, old_json, new_json, action) VALUES (?,?,?,?,?,?,?)",[S(),t,e,n,i?JSON.stringify(i):null,JSON.stringify(o),r])}async getConsentHistory(e){return(await this.q("SELECT * FROM consent_history WHERE visitor_id=? ORDER BY created_at ASC",[e])).map(no)}async createVisitor(e){let t=S();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 i=(await this.q("SELECT * FROM visitors WHERE id=?",[t]))[0];if(!i)throw new Error("Visitor creation failed");return We(i)}async updateVisitor(e,t){let n=["last_seen=NOW()"],i=[];t.country!=null&&(n.push("country=?"),i.push(t.country)),t.region!=null&&(n.push("region=?"),i.push(t.region)),t.city!=null&&(n.push("city=?"),i.push(t.city)),i.push(e),await this.exec(`UPDATE visitors SET ${n.join(",")} WHERE visitor_id=?`,i);let r=(await this.q("SELECT * FROM visitors WHERE visitor_id=?",[e]))[0];if(!r)throw new Error(`Visitor ${e} not found`);return We(r)}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]?We(t[0]):null}async getVisitors(e){let t="SELECT * FROM visitors WHERE tenant_id=?",n=[e.tenantId];e.from!=null&&(t+=" AND first_seen>=?",n.push(e.from)),e.to!=null&&(t+=" AND first_seen<=?",n.push(e.to));let i=e.page??1,o=e.limit??50;return(await this.q(`${t} ORDER BY first_seen DESC LIMIT ? OFFSET ?`,[...n,o,(i-1)*o])).map(We)}async getOverviewStats(e){let[[t],[n],[i]]=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])]),o=t?.total??0,r=n?.total??0,a=i?.total??0,c=await this.q(`SELECT jt.status, COUNT(*) AS count
|
|
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,d=0,l=0,u={};for(let g of o){let f=JSON.parse(g.consent_json),w=Object.values(f),R=w.filter(j=>j==="granted").length,I=w.filter(j=>j==="denied").length,T=w.length>0?R/w.length:0;a++;let v=g.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?(d++,u[v].denied++):(l++,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),h=new Map;for(let g of p){let f=h.get(g.d)??{total:0,granted:0,denied:0,managed:0},w=JSON.parse(g.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;f.total++,I>=.9?f.granted++:T>=.9?f.denied++:f.managed++,h.set(g.d,f)}let E=Array.from(h.entries()).map(([g,f])=>({date:g,...f}));return{total:a,granted:c,denied:d,managed:l,grantedPct:a>0?Math.round(c/a*1e3)/10:0,deniedPct:a>0?Math.round(d/a*1e3)/10:0,managedPct:a>0?Math.round(l/a*1e3)/10:0,byLocale:u,byDate:E}}};function Yt(i){return i.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}function qe(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 Ge(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.signature!=null?{signature:i.signature}:{}}}var Qr={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 Xr(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 Zr(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 Ke(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 Ee(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 Ye(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 hs(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 eo={tenant_id:1,user_id:1,action:1,resource_type:1,resource_id:1,created_at:1};function to(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 Re=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 ms){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}=J;await this.col("tenants").findOne({_id:e})||await this.col("tenants").insertOne({_id:e,name:t,slug:n});for(let r of V){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 Q){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 X)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:H(),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),qe(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 qe(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?qe(t):null}async getProfiles(e){return(await this.col("profiles").find({tenant_id:e}).toArray()).map(qe)}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:B(),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.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"),Ge(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"),Ge(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?Ge(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:Qr}).sort({created_at:-1}).skip((n-1)*s).limit(s).toArray(),this.col("consent_records").countDocuments(t)]);return{items:r.map(Xr),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 Ge(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:"^"+Yt(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(Zr)}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:$(),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),Ke(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 Ke(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?Ke(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:"^"+Yt(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(Ke),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,d=0;for(let l of o)d+=l.count,l._id==="granted"&&(a=l.count),l._id==="denied"&&(c=l.count);return{totalConsents:t,acceptedPct:d>0?Math.round(a/d*100):0,rejectedPct:d>0?Math.round(c/d*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),Ee(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 Ee(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?Ee(t):null}async getUserByEmail(e){let t=await this.col("users").findOne({email:e});return t?Ee(t):null}async getUsers(e){return(await this.col("users").find({tenant_id:e}).toArray()).map(Ee)}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(Ye)}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),Ye(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 Ye(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(_s)}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(_s)}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(Ye)}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:eo}).sort({created_at:-1}).skip((n-1)*s).limit(s).toArray(),this.col("audit_logs").countDocuments(t)]);return{items:r.map(to),total:o,page:n,limit:s}}async getAuditLogById(e){let t=await this.col("audit_logs").findOne({_id:e});return t?hs(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 hs(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:"^"+Yt(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}:{}}}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),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}async purgeExpiredAuditLogs(e){let n={created_at:{$lt:new Date(Date.now()-e*864e5).toISOString()}},s=await this.col("audit_logs").countDocuments(n);return await this.col("audit_logs").deleteMany(n),s}async createConsentTemplate(){throw new Error("Not implemented")}async updateConsentTemplate(){throw new Error("Not implemented")}async deleteConsentTemplate(){throw new Error("Not implemented")}async getConsentTemplate(){throw new Error("Not implemented")}async getConsentTemplates(){throw new Error("Not implemented")}async copyConsentTemplate(){throw new Error("Not implemented")}async createUITemplate(){throw new Error("Not implemented")}async updateUITemplate(){throw new Error("Not implemented")}async deleteUITemplate(){throw new Error("Not implemented")}async getUITemplate(){throw new Error("Not implemented")}async getUITemplates(){throw new Error("Not implemented")}async copyUITemplate(){throw new Error("Not implemented")}async listProfilesSummary(){return[]}async findProfilesUsingConsentTemplate(){return[]}async findProfilesUsingUITemplate(){return[]}async getOptInStats(){throw new Error("Not implemented")}};function ze(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 Qe(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.signature!=null?{signature:i.signature}:{}}}var no="id, tenant_id, visitor_id, profile_id, locale, gpc_detected, source, age_verified, created_at, updated_at";function so(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 io(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 Xe(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 Se(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 Ze(i){return{id:i.id,tenantId:i.tenant_id,name:i.name,...i.description!=null?{description:i.description}:{}}}function ys(i){return{id:i.id,name:i.name,...i.description!=null?{description:i.description}:{}}}function ws(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 ro="id, tenant_id, user_id, action, resource_type, resource_id, created_at";function oo(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 Ce=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 fs.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 (?,?,?)",[J.id,J.name,J.slug]);for(let t of V)await e.query("INSERT IGNORE INTO permissions (id, name, description) VALUES (?,?,?)",[t.id,t.name,t.description]);for(let t of Q)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 X)await e.query("INSERT IGNORE INTO role_permissions (role_id, permission_id) VALUES (?,?)",[t,n])}async createProfile(e){let t=H();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 ze(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 ze(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]?ze(t[0]):null}async getProfiles(e){return(await this.q("SELECT * FROM profiles WHERE tenant_id=? ORDER BY created_at DESC",[e])).map(ze)}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=B();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, 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.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"),Qe(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"),Qe(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]?Qe(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 ${no} ${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(so),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 Qe(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=k(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(io)}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=$();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 Xe(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 Xe(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]?Xe(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=k(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(Xe),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
|
|
109
95
|
FROM consent_records cr
|
|
110
96
|
JOIN JSON_TABLE(cr.consent_json, '$.*' COLUMNS (status VARCHAR(20) PATH '$')) AS jt
|
|
111
97
|
WHERE cr.tenant_id=?
|
|
112
|
-
GROUP BY jt.status`,[e]),
|
|
98
|
+
GROUP BY jt.status`,[e]),d=0,l=0,u=0;for(let p of c)u+=p.count,p.status==="granted"&&(d=p.count),p.status==="denied"&&(l=p.count);return{totalConsents:r,acceptedPct:u>0?Math.round(d/u*100):0,rejectedPct:u>0?Math.round(l/u*100):0,totalVisitors:a,gpcDetectedCount:o}}async getCategoryStats(e){let t=await this.q(`SELECT jt.cookie_id, jt.status, COUNT(*) AS count
|
|
113
99
|
FROM consent_records cr
|
|
114
100
|
JOIN JSON_TABLE(cr.consent_json, '$.*' COLUMNS (NESTED PATH '$' COLUMNS (
|
|
115
101
|
cookie_id VARCHAR(100) PATH '$.k',
|
|
116
102
|
status VARCHAR(20) PATH '$.v'
|
|
117
103
|
))) AS jt
|
|
118
104
|
WHERE cr.tenant_id=?
|
|
119
|
-
GROUP BY jt.cookie_id, jt.status`,[e]),n={};for(let
|
|
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(created_at) AS date, COUNT(*) AS count
|
|
120
106
|
FROM consent_records WHERE tenant_id=? AND created_at>=?
|
|
121
|
-
GROUP BY DATE(created_at) ORDER BY date ASC`,[e,n])).map(
|
|
107
|
+
GROUP BY DATE(created_at) ORDER BY date ASC`,[e,n])).map(r=>({date:String(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 Se(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 Se(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]?Se(t[0]):null}async getUserByEmail(e){let t=await this.q("SELECT * FROM users WHERE email=?",[e]);return t[0]?Se(t[0]):null}async getUsers(e){return(await this.q("SELECT * FROM users WHERE tenant_id=? ORDER BY created_at DESC",[e])).map(Se)}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(Ze)}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 Ze(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 Ze(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(ys)}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(ys)}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(Ze)}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 ${ro} ${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(oo),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]?ws(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 ws(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=k(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
|
|
122
108
|
JOIN visitors v ON v.visitor_id=cr.visitor_id
|
|
123
109
|
WHERE cr.tenant_id=? AND v.country IS NOT NULL
|
|
124
|
-
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]),
|
|
125
|
-
VALUES (
|
|
126
|
-
VALUES ($1,$2,$3,$4,$5,$6
|
|
127
|
-
VALUES ($1,$2,$3,$4,$5
|
|
128
|
-
VALUES ($1,$2,$3,$4,$5,$6,$7
|
|
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 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}:{}}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, 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), updated_at=NOW()`,[e,JSON.stringify(s.allowedOrigins??[]),JSON.stringify(s.adminAllowedOrigins??[]),s.setupCompleted?1:0]),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}async purgeExpiredAuditLogs(e){let t=new Date(Date.now()-e*864e5).toISOString(),n=await this.q("SELECT COUNT(*) AS total FROM audit_logs WHERE created_at < ?",[t]);return await this.exec("DELETE FROM audit_logs WHERE created_at < ?",[t]),n[0]?.total??0}async createConsentTemplate(){throw new Error("Not implemented")}async updateConsentTemplate(){throw new Error("Not implemented")}async deleteConsentTemplate(){throw new Error("Not implemented")}async getConsentTemplate(){throw new Error("Not implemented")}async getConsentTemplates(){throw new Error("Not implemented")}async copyConsentTemplate(){throw new Error("Not implemented")}async createUITemplate(){throw new Error("Not implemented")}async updateUITemplate(){throw new Error("Not implemented")}async deleteUITemplate(){throw new Error("Not implemented")}async getUITemplate(){throw new Error("Not implemented")}async getUITemplates(){throw new Error("Not implemented")}async copyUITemplate(){throw new Error("Not implemented")}async listProfilesSummary(){return[]}async findProfilesUsingConsentTemplate(){return[]}async findProfilesUsingUITemplate(){return[]}async getOptInStats(){throw new Error("Not implemented")}};function et(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 tt(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.signature!=null?{signature:i.signature}:{}}}var ao="id, tenant_id, visitor_id, profile_id, locale, gpc_detected, source, age_verified, created_at, updated_at";function co(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 lo(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 nt(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 Ae(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 Ie(i){return{id:i.id,tenantId:i.tenant_id,name:i.name,...i.description!=null?{description:i.description}:{}}}function Es(i){return{id:i.id,name:i.name,...i.description!=null?{description:i.description}:{}}}function Rs(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 uo="id, tenant_id, user_id, action, resource_type, resource_id, created_at";function po(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 Te=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 gs.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",[J.id,J.name,J.slug]);for(let t of V)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 Q)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 X)await e.query("INSERT INTO role_permissions VALUES ($1,$2) ON CONFLICT DO NOTHING",[t,n])}async createProfile(e){let t=H(),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 et(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 et(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]?et(t[0]):null}async getProfiles(e){return(await this.q("SELECT * FROM profiles WHERE tenant_id=$1 ORDER BY created_at DESC",[e])).map(et)}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=B(),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, signature)
|
|
113
|
+
VALUES ($1,$2,$3,$4,$5,$6::jsonb,$7,$8,$9,$10,$11,$12) 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.signature??null]))[0];if(!s)throw new Error("Consent creation failed");return await this.writeHistory(t,e.tenantId,e.visitorId,null,e.consentJson,"created"),tt(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"),tt(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]?tt(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 ${ao} ${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(co),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 tt(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(k(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(lo)}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=$(),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 nt(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 nt(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]?nt(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(k(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(nt),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
|
|
129
117
|
FROM consent_records cr,
|
|
130
118
|
jsonb_each_text(cr.consent_json) AS kv(key, val)
|
|
131
119
|
WHERE cr.tenant_id=$1
|
|
132
|
-
GROUP BY val`,[e]),
|
|
120
|
+
GROUP BY val`,[e]),d=0,l=0,u=0;for(let p of c)u+=Number(p.count),p.status==="granted"&&(d=Number(p.count)),p.status==="denied"&&(l=Number(p.count));return{totalConsents:r,acceptedPct:u>0?Math.round(d/u*100):0,rejectedPct:u>0?Math.round(l/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
|
|
133
121
|
FROM consent_records cr,
|
|
134
122
|
jsonb_each_text(cr.consent_json) AS kv(key, val)
|
|
135
123
|
WHERE cr.tenant_id=$1
|
|
136
|
-
GROUP BY kv.key, kv.val`,[e]),n={};for(let
|
|
124
|
+
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
|
|
137
125
|
FROM consent_records
|
|
138
126
|
WHERE tenant_id=$1 AND created_at>=$2
|
|
139
127
|
GROUP BY to_char(created_at, 'YYYY-MM-DD')
|
|
140
|
-
ORDER BY date ASC`,[e,n])).map(
|
|
141
|
-
VALUES ($1,$2,$3,$4,$5,$6,$7::jsonb,$8::jsonb)`,[
|
|
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 Ae(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 Ae(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]?Ae(t[0]):null}async getUserByEmail(e){let t=await this.q("SELECT * FROM users WHERE email=$1",[e]);return t[0]?Ae(t[0]):null}async getUsers(e){return(await this.q("SELECT * FROM users WHERE tenant_id=$1 ORDER BY created_at DESC",[e])).map(Ae)}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(Ie)}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 Ie(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 d=(await this.q("SELECT * FROM roles WHERE id=$1",[e]))[0];if(!d)throw new Error(`Role ${e} not found`);return Ie(d)}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 Ie(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(Es)}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(Es)}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(Ie)}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 ${uo} ${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(po),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]?Rs(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 Rs(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(k(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
|
|
142
130
|
JOIN visitors v ON v.visitor_id=cr.visitor_id
|
|
143
131
|
WHERE cr.tenant_id=$1 AND v.country IS NOT NULL
|
|
144
|
-
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]),i=Number(t?.total??0),o=Number(n?.total??0);return{detected:o,total:i,rate:i>0?Math.round(o/i*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}))}async createApiKey(e){let t=S(),i=(await this.q("INSERT INTO api_keys (id, tenant_id, key_hash, name) VALUES ($1,$2,$3,$4) RETURNING *",[t,e.tenantId,e.keyHash,e.name]))[0];if(!i)throw new Error("Failed to create API key");return{id:i.id,tenantId:i.tenant_id,keyHash:i.key_hash,name:i.name,isActive:i.is_active,createdAt:i.created_at}}async getApiKeyByHash(e){let n=(await this.q("SELECT * FROM api_keys WHERE key_hash=$1 AND is_active=TRUE",[e]))[0];return n?{id:n.id,tenantId:n.tenant_id,keyHash:n.key_hash,name:n.name,isActive:n.is_active,createdAt:n.created_at}:null}async revokeApiKey(e){await this.q("UPDATE api_keys SET is_active=FALSE 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=>({id:n.id,tenantId:n.tenant_id,keyHash:n.key_hash,name:n.name,isActive:n.is_active,createdAt:n.created_at}))}async createTenant(e){let t=S(),n=new Date().toISOString(),o=(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(!o)throw new Error("Failed to create tenant");return{id:o.id,name:o.name,slug:o.slug,createdAt:o.created_at,updatedAt:o.updated_at}}async updateTenant(e,t){let n=new Date().toISOString(),i=["updated_at=$1"],o=[n],r=2;t.name!=null&&(i.push(`name=$${r++}`),o.push(t.name)),t.slug!=null&&(i.push(`slug=$${r++}`),o.push(t.slug)),o.push(e);let c=(await this.q(`UPDATE tenants SET ${i.join(",")} WHERE id=$${r} RETURNING *`,o))[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 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 i=n.map(r=>r.visitor_id),o=i.map((r,a)=>`$${a+1}`).join(",");return await this.q(`DELETE FROM consent_history WHERE visitor_id IN (${o})`,i),await this.q(`DELETE FROM consent_records WHERE visitor_id IN (${o})`,i),i.length}async createCookieTemplate(){throw new Error("Not implemented")}async updateCookieTemplate(){throw new Error("Not implemented")}async deleteCookieTemplate(){throw new Error("Not implemented")}async getCookieTemplate(){throw new Error("Not implemented")}async getCookieTemplates(){throw new Error("Not implemented")}async copyCookieTemplate(){throw new Error("Not implemented")}async createUITemplate(){throw new Error("Not implemented")}async updateUITemplate(){throw new Error("Not implemented")}async deleteUITemplate(){throw new Error("Not implemented")}async getUITemplate(){throw new Error("Not implemented")}async getUITemplates(){throw new Error("Not implemented")}async copyUITemplate(){throw new Error("Not implemented")}async listProfilesSummary(){return[]}async findProfilesUsingCookieTemplate(){return[]}async findProfilesUsingUITemplate(){return[]}async getOptInStats(){throw new Error("Not implemented")}};import{readFile as so,writeFile as oo,rename as ro,mkdir as ao}from"fs/promises";import{randomUUID as j}from"crypto";var co=500;function bn(){return{_version:1,tenants:[],profiles:[],consents:[],consent_history:[],visitors:[],users:[],roles:[],permissions:[],user_roles:[],role_permissions:[],audit_logs:[],cookie_templates:[],ui_templates:[],api_keys:[]}}function lo(){return`prof_${j().replace(/-/g,"")}`}function vt(s,e,t){let n=Math.min(t,co),i=(e-1)*n;return s.slice(i,i+n)}var Ye=class{constructor(e){this.config=e}config;db=bn();filePath="";writeQueue=Promise.resolve();scheduleWrite(){this.writeQueue=this.writeQueue.then(async()=>{await ao(this.filePath.replace(/\/[^/]+$/,""),{recursive:!0});let e=`${this.filePath}.tmp`;await oo(e,JSON.stringify(this.db),"utf8"),await ro(e,this.filePath)}).catch(e=>{console.warn("[Consenti] JSON store write failed:",e)})}async connect(){let{dbPath:e}=W(this.config.path??"./consenti-data","json");this.filePath=e;try{let t=await so(this.filePath,"utf8");this.db=JSON.parse(t)}catch{this.db=bn()}await this.migrate()}async disconnect(){await this.writeQueue}async migrate(){this.seed()}seed(){let e=new Date().toISOString(),{id:t,name:n,slug:i}=F;this.db.tenants.find(o=>o.id===t)||this.db.tenants.push({id:t,name:n,slug:i,createdAt:e,updatedAt:e});for(let o of x)this.db.permissions.find(r=>r.id===o.id)||this.db.permissions.push({id:o.id,name:o.name,description:o.description});for(let o of q)this.db.roles.find(r=>r.id===o.id)||this.db.roles.push({id:o.id,tenantId:o.tenantId,name:o.name,description:o.description});for(let o of K)this.db.role_permissions.find(r=>r.roleId===o.roleId&&r.permissionId===o.permissionId)||this.db.role_permissions.push(o);this.scheduleWrite()}async createProfile(e){let t=new Date().toISOString(),n={id:lo(),tenantId:e.tenantId,name:e.name,defaultLocale:e.defaultLocale,version:1,profileJson:e.profileJson,createdAt:t,updatedAt:t};return this.db.profiles.push(n),this.scheduleWrite(),n}async updateProfile(e,t){let n=this.db.profiles.findIndex(r=>r.id===e);if(n===-1)throw new Error(`Profile ${e} not found`);let i=this.db.profiles[n],o={...i,...t.name!=null&&{name:t.name},...t.defaultLocale!=null&&{defaultLocale:t.defaultLocale},...t.profileJson!=null&&{profileJson:t.profileJson},version:i.version+1,updatedAt:new Date().toISOString()};return this.db.profiles[n]=o,this.scheduleWrite(),o}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.isActive)??null}async createConsent(e){let t=new Date().toISOString(),n=j(),i={id:n,tenantId:e.tenantId,visitorId:e.visitorId,profileId:e.profileId,profileVersion:e.profileVersion,locale:e.locale,consentJson:e.consentJson,gpcDetected:e.gpcDetected??!1,source:e.source,createdAt:t,updatedAt:t};return this.db.consents.push(i),this.db.consent_history.push({id:j(),tenantId:e.tenantId,consentRecordId:n,visitorId:e.visitorId,oldJson:null,newJson:e.consentJson,action:"created",createdAt:t}),this.scheduleWrite(),i}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 i=this.db.consents[n],o=new Date().toISOString(),r={...i,consentJson:t.consentJson,...t.locale!=null&&{locale:t.locale},...t.gpcDetected!=null&&{gpcDetected:t.gpcDetected},updatedAt:o};return this.db.consents[n]=r,this.db.consent_history.push({id:j(),tenantId:i.tenantId,consentRecordId:i.id,visitorId:e,oldJson:i.consentJson,newJson:t.consentJson,action:"updated",createdAt:o}),this.scheduleWrite(),r}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(n=>n.tenantId===e.tenantId);return e.profileId&&(t=t.filter(n=>n.profileId===e.profileId)),e.from&&(t=t.filter(n=>n.createdAt>=e.from)),e.to&&(t=t.filter(n=>n.createdAt<=e.to)),t.sort((n,i)=>i.createdAt.localeCompare(n.createdAt)),vt(t,e.page??1,e.limit??50)}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 createVisitor(e){let t=new Date().toISOString(),n={id:j(),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(r=>r.visitorId===e);if(n===-1)throw new Error(`Visitor ${e} not found`);let o={...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]=o,this.scheduleWrite(),o}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(n=>n.tenantId===e.tenantId);return e.from&&(t=t.filter(n=>n.firstSeen>=e.from)),e.to&&(t=t.filter(n=>n.firstSeen<=e.to)),t.sort((n,i)=>i.lastSeen.localeCompare(n.lastSeen)),vt(t,e.page??1,e.limit??50)}async createUser(e){let t=new Date().toISOString(),n={id:j(),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(r=>r.id===e);if(n===-1)throw new Error(`User ${e} not found`);let o={...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]=o,this.scheduleWrite(),o}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:j(),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(o=>o.id===e);if(n===-1)throw new Error(`Role ${e} not found`);let i={...this.db.roles[n],...t.name!=null&&{name:t.name},...t.description!=null&&{description:t.description}};return this.db.roles[n]=i,this.scheduleWrite(),i}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(o=>o.userId===e);t&&(n=n.filter(o=>o.tenantId===t));let i=new Set(n.map(o=>o.roleId));return this.db.roles.filter(o=>i.has(o.id))}async assignRole(e,t,n="default"){this.db.user_roles.find(i=>i.userId===e&&i.roleId===t&&i.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(i=>!(i.userId===e&&i.roleId===t&&(n==null||i.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:j(),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(n=>n.tenantId===e.tenantId);return e.action&&(t=t.filter(n=>n.action===e.action)),e.resourceType&&(t=t.filter(n=>n.resourceType===e.resourceType)),e.from&&(t=t.filter(n=>n.createdAt>=e.from)),e.to&&(t=t.filter(n=>n.createdAt<=e.to)),t.sort((n,i)=>i.createdAt.localeCompare(n.createdAt)),vt(t,e.page??1,e.limit??50)}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,i)=>i.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),i=0,o=0;for(let a of n){let c=Object.values(a.consentJson),l=c.length>0?c.filter(u=>u==="granted").length/c.length:0;l>=.8?i++:l<=.2&&o++}let r=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(i/r*100),rejectedPct:Math.round(o/r*100)}}async getCategoryStats(e){let t={};for(let n of this.db.consents.filter(i=>i.tenantId===e).slice(0,5e3))for(let[i,o]of Object.entries(n.consentJson))t[i]||(t[i]={granted:0,denied:0,objected:0}),o==="granted"?t[i].granted++:o==="denied"?t[i].denied++:o==="objected"&&t[i].objected++;return t}async getTimeline(e,t=30){let n=new Date(Date.now()-t*864e5).toISOString().slice(0,10),i=new Map;for(let o of this.db.consents.filter(r=>r.tenantId===e&&r.createdAt.slice(0,10)>=n)){let r=o.createdAt.slice(0,10);i.set(r,(i.get(r)??0)+1)}return[...i.entries()].sort(([o],[r])=>o.localeCompare(r)).map(([o,r])=>({date:o,count:r}))}async getCountries(e){let t=new Set(this.db.consents.filter(i=>i.tenantId===e).map(i=>i.visitorId)),n=new Map;for(let i of this.db.visitors.filter(o=>t.has(o.visitorId)&&o.country)){let o=i.country;n.set(o,(n.get(o)??0)+1)}return[...n.entries()].sort(([,i],[,o])=>o-i).slice(0,50).map(([i,o])=>({country:i,count:o}))}async getGpcStats(e){let t=this.db.consents.filter(o=>o.tenantId===e),n=t.filter(o=>o.gpcDetected).length,i=t.length;return{detected:n,total:i,rate:i>0?Math.round(n/i*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:j(),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(r=>r.id===e);if(n===-1)throw new Error(`Tenant ${e} not found`);let i=new Date().toISOString(),o={...this.db.tenants[n],...t.name!=null&&{name:t.name},...t.slug!=null&&{slug:t.slug},updatedAt:i};return this.db.tenants[n]=o,this.scheduleWrite(),o}async deleteTenant(e){this.db.tenants=this.db.tenants.filter(t=>t.id!==e),this.scheduleWrite()}async purgeExpiredConsents(e){let t=new Date(Date.now()-e*864e5).toISOString(),n=new Set(this.db.consents.filter(i=>i.createdAt<t).map(i=>i.visitorId));return n.size===0?0:(this.db.consent_history=this.db.consent_history.filter(i=>!n.has(i.visitorId)),this.db.consents=this.db.consents.filter(i=>!n.has(i.visitorId)),this.scheduleWrite(),n.size)}async createApiKey(e){let t={id:j(),tenantId:e.tenantId,keyHash:e.keyHash,name:e.name,isActive:!0,createdAt:new Date().toISOString()};return this.db.api_keys.push(t),this.scheduleWrite(),t}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,this.scheduleWrite())}async getApiKeys(e){return this.db.api_keys.filter(t=>t.tenantId===e).sort((t,n)=>n.createdAt.localeCompare(t.createdAt))}async createCookieTemplate(e){let t=new Date().toISOString(),n={id:j(),tenantId:e.tenantId,name:e.name,cookies:e.cookies,createdAt:t,updatedAt:t};return this.db.cookie_templates.push(n),this.scheduleWrite(),n}async updateCookieTemplate(e,t){let n=this.db.cookie_templates.findIndex(r=>r.id===e);if(n===-1)throw new Error("Cookie template not found");let i=new Date().toISOString(),o={...this.db.cookie_templates[n],...t.name!==void 0&&{name:t.name},...t.cookies!==void 0&&{cookies:t.cookies},updatedAt:i};return this.db.cookie_templates[n]=o,this.scheduleWrite(),o}async deleteCookieTemplate(e){this.db.cookie_templates=this.db.cookie_templates.filter(t=>t.id!==e),this.scheduleWrite()}async getCookieTemplate(e){return this.db.cookie_templates.find(t=>t.id===e)??null}async getCookieTemplates(e){return this.db.cookie_templates.filter(t=>t.tenantId===e).sort((t,n)=>t.name.localeCompare(n.name))}async copyCookieTemplate(e,t){let n=await this.getCookieTemplate(e);if(!n)throw new Error("Cookie template not found");return this.createCookieTemplate({tenantId:n.tenantId,name:t,cookies:n.cookies})}async createUITemplate(e){let t=new Date().toISOString(),{tenantId:n,name:i,...o}=e,r={id:j(),tenantId:n,name:i,...o,createdAt:t,updatedAt:t};return this.db.ui_templates.push(r),this.scheduleWrite(),r}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:i,...o}=t,r=new Date().toISOString(),a={...this.db.ui_templates[n],...t.name!==void 0&&{name:t.name},...o,updatedAt:r};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:i,mainBanner:o,gpcBanner:r,preferenceModal:a}=n;return this.createUITemplate({tenantId:i,name:t,...o!==void 0&&{mainBanner:o},...r!==void 0&&{gpcBanner:r},...a!==void 0&&{preferenceModal:a}})}profileToSummary(e){let t=e.profileJson,n=t.cookieTemplateId?this.db.cookie_templates.find(o=>o.id===t.cookieTemplateId):null,i=t.uiTemplateId?this.db.ui_templates.find(o=>o.id===t.uiTemplateId):null;return{id:e.id,name:e.name,defaultLocale:e.defaultLocale,complianceGroup:t.complianceGroup??null,version:e.version,isActive:t.isActive??!1,cookieTemplateName:n?.name??null,uiTemplateName:i?.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 findProfilesUsingCookieTemplate(e){return this.db.profiles.filter(t=>t.profileJson.cookieTemplateId===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(d=>d.tenantId===e);t.profileId&&(n=n.filter(d=>d.profileId===t.profileId)),t.from&&(n=n.filter(d=>d.createdAt>=t.from)),t.to&&(n=n.filter(d=>d.createdAt<=t.to)),t.locale&&(n=n.filter(d=>d.locale===t.locale));let i=0,o=0,r=0,a=0,c={},l=new Map;for(let d of n){let p=Object.values(d.consentJson),E=p.length>0?p.filter(T=>T==="granted").length/p.length:0,A=p.length>0?p.filter(T=>T==="denied").length/p.length:0;i++;let f=d.locale;c[f]||(c[f]={total:0,granted:0,denied:0,managed:0}),c[f].total++;let _=d.createdAt.slice(0,10),h=l.get(_)??{total:0,granted:0,denied:0,managed:0};h.total++,E>=.9?(o++,c[f].granted++,h.granted++):A>=.9?(r++,c[f].denied++,h.denied++):(a++,c[f].managed++,h.managed++),l.set(_,h)}let u=Array.from(l.entries()).sort(([d],[p])=>d.localeCompare(p)).map(([d,p])=>({date:d,...p}));return{total:i,granted:o,denied:r,managed:a,grantedPct:i>0?Math.round(o/i*1e3)/10:0,deniedPct:i>0?Math.round(r/i*1e3)/10:0,managedPct:i>0?Math.round(a/i*1e3)/10:0,byLocale:c,byDate:u}}};var ze=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 Qe=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 Xe=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 Ze=class{constructor(e){this.adapter=e}adapter;log(e){return this.adapter.createLog(e)}list(e){return this.adapter.getLogs(e)}};var et=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)}};function On(s,e){return{mainBanner:{...s.mainBanner,...e.mainBanner},preferenceModal:{...s.preferenceModal,...e.preferenceModal,buttons:e.preferenceModal?.buttons??s.preferenceModal.buttons,categories:e.preferenceModal?.categories??s.preferenceModal.categories},...e.gpcBanner!=null||s.gpcBanner!=null?{gpcBanner:{...s.gpcBanner??s.mainBanner,...e.gpcBanner}}:{}}}function It(s,e,t){let n=s[t];if(!n)throw new Error(`Default locale '${t}' not found in translations`);if(e===t)return n;let i=s[e];if(i)return On(n,i);let o=e.split("-")[0]??"",r=o?s[o]:void 0;return r?On(n,r):n}import{readdirSync as Vn,readFileSync as Wn,existsSync as Dt,statSync as Po}from"fs";import{join as z}from"path";import{Worker as Ro}from"worker_threads";import{fileURLToPath as So}from"url";import{join as Ao,dirname as To}from"path";import{existsSync as Co}from"fs";import{writeFileSync as go,readFileSync as fo,renameSync as yo,mkdirSync as Mn,readdirSync as ho,copyFileSync as Gn,rmSync as _o,existsSync as Ot}from"fs";import{join as $}from"path";import{createHmac as Dn,createHash as uo}from"crypto";function Pt(s){return uo("sha256").update(s).digest("hex")}function tt(s,e){return Dn("sha256",s).update(e).digest()}function po(s){let e=s.toISOString().replace(/[-:]/g,"").slice(0,15)+"Z";return{amzDate:e,dateStamp:e.slice(0,8)}}function mo(s,e,t,n){let i=tt(`AWS4${s}`,e),o=tt(i,t),r=tt(o,n);return tt(r,"aws4_request")}function Ln(s){let{method:e,key:t,body:n,contentType:i,config:o}=s,r=new Date,{amzDate:a,dateStamp:c}=po(r),l=`${o.bucketName}.s3.${o.region}.amazonaws.com`,u=Pt(n?Buffer.isBuffer(n)?n:Buffer.from(n,"utf8"):""),d={host:l,"x-amz-date":a,"x-amz-content-sha256":u};o.sessionToken&&(d["x-amz-security-token"]=o.sessionToken),i&&e==="PUT"&&(d["content-type"]=i);let p=Object.keys(d).sort(),E=p.map(D=>`${D}:${d[D]}
|
|
145
|
-
`).join(""),A=p.join(";"),f=[e,`/${encodeURIComponent(t).replace(/%2F/g,"/")}`,"",E,A,u].join(`
|
|
146
|
-
`),
|
|
147
|
-
`),T=mo(o.secretAccessKey,c,o.region,"s3"),C=Dn("sha256",T).update(h).digest("hex"),P=[`AWS4-HMAC-SHA256 Credential=${o.accessKeyId}/${_}`,`SignedHeaders=${A}`,`Signature=${C}`].join(", ");return{...d,authorization:P}}function Un(s,e){let t=encodeURIComponent(s).replace(/%2F/g,"/");return`https://${e.bucketName}.s3.${e.region}.amazonaws.com/${t}`}async function nt(s,e,t,n="application/json"){let i=Ln({method:"PUT",key:s,body:e,contentType:n,config:t}),o=typeof e=="string"?e:e.toString("utf8"),r=await fetch(Un(s,t),{method:"PUT",headers:{...i,"content-length":String(Buffer.byteLength(o,"utf8"))},body:o});if(!r.ok){let a=await r.text().catch(()=>"");throw new Error(`S3 PUT ${s} failed: ${r.status} ${a}`)}}async function bt(s,e){let t=Ln({method:"DELETE",key:s,config:e}),n=await fetch(Un(s,e),{method:"DELETE",headers:t});if(!n.ok&&n.status!==404){let i=await n.text().catch(()=>"");throw new Error(`S3 DELETE ${s} failed: ${n.status} ${i}`)}}function xn(s,e){return $(s,"profiles",e)}function Fn(s,e,t,n){return $(xn(s,e),t,String(n))}function Hn(s,e,t){return $(xn(s,e),t)}function Ce(s,e,t){return`profiles/${s}/${e}/${t}.json`}function kn(s,e){let t=s+".tmp";go(t,e,"utf8"),JSON.parse(e),yo(t,s)}async function Nn(s){let{storagePath:e,tenantId:t,profileId:n,version:i,profile:o,s3Config:r}=s,a=Fn(e,t,n,i);Mn(a,{recursive:!0});let c=o.locales??[],l=o.defaultLocale,u=o.translations??{};for(let d of c){let p=d===o.currentLocale?JSON.stringify(o,null,2):JSON.stringify({...o,currentLocale:d,...u[d]??{}},null,2),E=$(a,`${d}.json`);kn(E,p),d===l&&kn($(a,"default.json"),p),r?.enabled&&(await nt(Ce(t,`${n}/${i}`,d),p,r),d===l&&await nt(Ce(t,`${n}/${i}`,"default"),p,r))}if(!Ot($(a,"default.json"))){let d=c.includes(l)?l:c[0]??"";if(d){let p=$(a,`${d}.json`);Ot(p)&&Gn(p,$(a,"default.json"))}}}async function Eo(s){let{storagePath:e,tenantId:t,profileId:n,version:i,complianceGroup:o,s3Config:r}=s,a=Fn(e,t,n,i),c=Hn(e,t,o);Mn(c,{recursive:!0});let l=ho(a).filter(u=>u.endsWith(".json"));for(let u of l)if(Gn($(a,u),$(c,u)),r?.enabled){let d=fo($(c,u),"utf8");await nt(Ce(t,o,u.replace(".json","")),d,r)}}async function wo(s){let{storagePath:e,tenantId:t,complianceGroup:n,s3Config:i,profile:o}=s,r=Hn(e,t,n);if(Ot(r)){if(i?.enabled){let a=o.locales??[];for(let c of a)await bt(Ce(t,n,c),i);await bt(Ce(t,n,"default"),i)}_o(r,{recursive:!0,force:!0})}}async function jn(s){s.action==="write"?await Nn(s):s.action==="activate"?(await Nn(s),await Eo(s)):s.action==="deactivate"&&await wo(s)}var vo=To(So(import.meta.url)),$n=Ao(vo,"profile-json.worker.js"),Io=!Co($n);async function Bn(s){if(Io){await jn(s);return}return new Promise((e,t)=>{let n=new Ro($n);n.postMessage(s),n.once("message",i=>{n.terminate(),i.ok?e():t(new Error(`[profile-worker] ${i.action} failed: ${i.error}`))}),n.once("error",i=>{n.terminate(),t(i)})})}function bo(s,e){let t={};for(let[n,i]of Object.entries(e))t[n]={mainBanner:{position:s.mainBanner.position,...s.mainBanner.overlayOpacity!==void 0?{overlayOpacity:s.mainBanner.overlayOpacity}:{},...s.mainBanner.showClose!==void 0?{showClose:s.mainBanner.showClose}:{},...s.mainBanner.showLocaleSwitcher!==void 0?{showLocaleSwitcher:s.mainBanner.showLocaleSwitcher}:{},...s.mainBanner.headingTag!==void 0?{headingTag:s.mainBanner.headingTag}:{},...i.mainBanner.heading!==void 0?{heading:i.mainBanner.heading}:{},htmlText:i.mainBanner.htmlText,buttons:s.mainBanner.buttons.map((o,r)=>({text:i.mainBanner.buttonLabels?.[r]??o.text,style:o.style,action:o.action,...o.url!==void 0?{url:o.url}:{},...o.type!==void 0?{type:o.type}:{},...o.cookies!==void 0?{cookies:o.cookies}:{}}))},gpcBanner:{position:s.gpcBanner.position,...s.gpcBanner.overlayOpacity!==void 0?{overlayOpacity:s.gpcBanner.overlayOpacity}:{},...s.gpcBanner.showClose!==void 0?{showClose:s.gpcBanner.showClose}:{},...s.gpcBanner.showLocaleSwitcher!==void 0?{showLocaleSwitcher:s.gpcBanner.showLocaleSwitcher}:{},...s.gpcBanner.headingTag!==void 0?{headingTag:s.gpcBanner.headingTag}:{},...i.gpcBanner.heading!==void 0?{heading:i.gpcBanner.heading}:{},htmlText:i.gpcBanner.htmlText,buttons:s.gpcBanner.buttons.map((o,r)=>({text:i.gpcBanner.buttonLabels?.[r]??o.text,style:o.style,action:o.action,...o.url!==void 0?{url:o.url}:{},...o.type!==void 0?{type:o.type}:{},...o.cookies!==void 0?{cookies:o.cookies}:{}}))},preferenceModal:{...s.preferenceModal.position!==void 0?{position:s.preferenceModal.position}:{},...s.preferenceModal.overlayOpacity!==void 0?{overlayOpacity:s.preferenceModal.overlayOpacity}:{},...s.preferenceModal.showClose!==void 0?{showClose:s.preferenceModal.showClose}:{},...s.preferenceModal.showLocaleSwitcher!==void 0?{showLocaleSwitcher:s.preferenceModal.showLocaleSwitcher}:{},...s.preferenceModal.persistent!==void 0?{persistent:s.preferenceModal.persistent}:{},...s.preferenceModal.headingTag!==void 0?{headingTag:s.preferenceModal.headingTag}:{},...s.preferenceModal.mobileFullScreenBreakpoint!==void 0?{mobileFullScreenBreakpoint:s.preferenceModal.mobileFullScreenBreakpoint}:{},heading:i.preferenceModal.heading??"",...i.preferenceModal.subheading!==void 0?{subheading:i.preferenceModal.subheading}:{},htmlText:i.preferenceModal.htmlText??"",buttons:s.preferenceModal.buttons.map((o,r)=>({text:i.preferenceModal.buttonLabels?.[r]??o.text,style:o.style,action:o.action,...o.url!==void 0?{url:o.url}:{},...o.type!==void 0?{type:o.type}:{},...o.cookies!==void 0?{cookies:o.cookies}:{}})),categories:s.preferenceModal.categories.map(o=>{let r=i.preferenceModal.categories.find(a=>a.id===o.id);return{id:o.id,heading:r?.heading??o.id,...o.headingTag!==void 0?{headingTag:o.headingTag}:{},htmlText:r?.htmlText??"",...o.mandatory!==void 0?{mandatory:o.mandatory}:{},...o.type!==void 0?{type:o.type}:{},...o.legitimateInterest?.enabled?{legitimateInterest:{enabled:!0}}:{},cookies:o.cookies}})}};return t}function Oo(s){return s.legalBasis?s:s.mandatory?{...s,legalBasis:"mandatory"}:s.type?{...s,legalBasis:s.type}:s}var it=class{constructor(e,t,n="default",i,o,r,a,c,l){this.profiles=e;this.audit=t;this.tenantId=n;this.storage=i;this.eventBus=o;this.localeCache=r;this.profilesDir=a;this.handleCache=c;this.s3Config=l}profiles;audit;tenantId;storage;eventBus;localeCache;profilesDir;handleCache;s3Config;profileDir(e){return z(this.profilesDir??"",this.tenantId,e)}complianceGroupDir(e){return z(this.profilesDir??"",this.tenantId,e)}profileFilePaths(e,t){let n=this.complianceGroupDir(e);return t.map(i=>z(n,`${i}.json`)).concat(z(n,"default.json"))}async create(e){let t=await this.profiles.create({...e,tenantId:this.tenantId});if(await this.audit.log({tenantId:this.tenantId,action:"profile.created",resourceType:"profile",resourceId:t.id,newData:t}),this.eventBus?.emit("profile.created",t),this.profilesDir){let n=await this.getResolved(t.id);if(n){let i=t.profileJson.complianceGroup??"";await this.runWrite({action:"write",profile:n,profileId:t.id,version:t.version,complianceGroup:i});let o=this.profileFilePaths(i,n.locales);this.handleCache?.(o,t.version,!1),this.eventBus?.emit("cache:warm",{paths:o,version:t.version})}}return t}async update(e,t){let n=await this.profiles.get(e),i=await this.profiles.update(e,t);if(this.localeCache?.invalidate(e),await this.audit.log({tenantId:this.tenantId,action:"profile.updated",resourceType:"profile",resourceId:e,...n!=null?{oldData:n}:{},newData:i}),this.eventBus?.emit("profile.updated",{previous:n,current:i}),this.profilesDir){let o=await this.getResolved(i.id);if(o){let r=i.profileJson.complianceGroup??"",a=i.profileJson.isActive??!1,c=a?"activate":"write";if(await this.runWrite({action:c,profile:o,profileId:e,version:i.version,complianceGroup:r}),a){let l=this.profileFilePaths(r,o.locales);this.handleCache?.(l,i.version,!0),this.eventBus?.emit("cache:purge",{paths:l,version:i.version})}}}return i}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:t}:{}}),this.eventBus?.emit("profile.deleted",{id:e,previous:t}),this.profilesDir&&t){let n=t.profileJson.complianceGroup??"";if((t.profileJson.isActive??!1)&&n){let o=await this.getResolved(e).catch(()=>null),r=o?.locales??[];await this.runWrite({action:"deactivate",profile:o??{},profileId:e,version:t.version,complianceGroup:n});let a=this.profileFilePaths(n,r);this.handleCache?.(a,t.version,!0),this.eventBus?.emit("cache:purge",{paths:a,version:t.version})}}}async activate(e){let t=await this.profiles.get(e);if(!t)throw new Error(`Profile ${e} not found`);let n=t.profileJson,i=await this.profiles.update(e,{profileJson:{...t.profileJson,isActive:!0}});if(this.localeCache?.invalidate(e),this.profilesDir&&n.complianceGroup){let o=await this.getResolved(e);if(o){await this.runWrite({action:"activate",profile:o,profileId:e,version:i.version,complianceGroup:n.complianceGroup});let r=this.profileFilePaths(n.complianceGroup,o.locales);this.handleCache?.(r,i.version,!1),this.eventBus?.emit("cache:warm",{paths:r,version:i.version})}}return await this.audit.log({tenantId:this.tenantId,action:"profile.activated",resourceType:"profile",resourceId:e}),this.eventBus?.emit("profile.activated",i),i}async deactivate(e){let t=await this.profiles.get(e);if(!t)throw new Error(`Profile ${e} not found`);let n=t.profileJson,i=await this.profiles.update(e,{profileJson:{...t.profileJson,isActive:!1}});if(this.localeCache?.invalidate(e),this.profilesDir&&n.complianceGroup){let o=await this.getResolved(e),r=o?.locales??[];await this.runWrite({action:"deactivate",profile:o??{},profileId:e,version:t.version,complianceGroup:n.complianceGroup});let a=this.profileFilePaths(n.complianceGroup,r);this.handleCache?.(a,t.version,!0),this.eventBus?.emit("cache:purge",{paths:a,version:t.version})}return await this.audit.log({tenantId:this.tenantId,action:"profile.deactivated",resourceType:"profile",resourceId:e}),this.eventBus?.emit("profile.deactivated",i),i}async runWrite(e){if(!this.profilesDir)return;let t={action:e.action,profile:e.profile,storagePath:z(this.profilesDir,".."),tenantId:this.tenantId,profileId:e.profileId,version:e.version,complianceGroup:e.complianceGroup,...this.s3Config?.enabled?{s3Config:this.s3Config}:{}};await Bn(t)}async get(e,t){let n=await this.profiles.get(e);if(!n)return null;if(!t)return n;let i=It(n.profileJson.translations??{},t,n.defaultLocale);return{...n,profileJson:{...n.profileJson,translations:{[t]:i}}}}async getResolved(e,t){let n=await this.profiles.get(e);if(!n)return null;let i=t??n.defaultLocale;if(this.localeCache){let d=this.localeCache.read(e,i);if(d)return d}let o=n.profileJson,r=o.cookies??[],a=o.translations??{};if(o.cookieTemplateId&&this.storage){let d=await this.storage.getCookieTemplate(o.cookieTemplateId);d&&(r=d.cookies)}if(o.uiTemplateId&&o.localeContents&&this.storage){let d=await this.storage.getUITemplate(o.uiTemplateId);d&&(a=bo(d,o.localeContents))}let c=o.localeContents?Object.keys(o.localeContents):Object.keys(a),l=It(a,i,n.defaultLocale),u={id:n.id,version:n.version,defaultLocale:n.defaultLocale,currentLocale:i,locales:c,cookies:r.map(Oo),mainBanner:l.mainBanner,preferenceModal:l.preferenceModal,...l.gpcBanner!=null?{gpcBanner:l.gpcBanner}:{}};if(this.localeCache)try{this.localeCache.write(e,i,u)}catch{}return u}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([])}listVersions(e){if(!this.profilesDir)return[];let t=this.profileDir(e);if(!Dt(t))return[];let n=[];try{let i=Vn(t,{withFileTypes:!0});for(let o of i){if(!o.isDirectory())continue;let r=parseInt(o.name,10);if(isNaN(r))continue;let a=z(t,o.name),c=Vn(a).filter(u=>u.endsWith(".json")&&u!=="default.json").map(u=>u.replace(".json","")),l;try{l=Po(a).mtime.toISOString()}catch{l=new Date().toISOString()}n.push({version:r,createdAt:l,locales:c})}}catch{return[]}return n.sort((i,o)=>o.version-i.version)}getVersionFile(e,t,n){if(!this.profilesDir)return null;let i=z(this.profileDir(e),String(t),`${n}.json`);if(!Dt(i)){let o=z(this.profileDir(e),String(t),"default.json");return Dt(o)?Wn(o,"utf8"):null}return Wn(i,"utf8")}};var Jn={"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 Do={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"},st=class{constructor(e,t){this.map=t;this.provider=e??"default"}map;provider;async resolve(e){let t="",n,i,o=null;if(typeof this.provider=="function"){let a=await this.provider(e);t=a.country??"",n=a.region??void 0,o=a.locale}else switch(this.provider){case"default":case"timezone":({country:t}=Lo(e.timezone)),t||({country:t,region:n}=qn(e.language));break;case"language":({country:t,region:n}=qn(e.language));break;case"geoip":({country:t,region:n}=await Uo(e.ip));break;case"hosted-geoip-lite":({country:t,region:n}=await No(e.ip));break;case"maxmind":({country:t,region:n}=await ko(e.ip));break}let r={country:t,complianceGroup:this.resolveGroup(t,n)};return n!==void 0&&(r.region=n),i!==void 0&&(r.regulation=i),o!==null&&(r.locale=o),r}resolveGroup(e,t){if(!e)return"";let i=this.map.countries[e];return i?t!==void 0?i.overriddenRegions?.[t]?.complianceGroup??i.complianceGroup:i.default:""}};function qn(s){let t=(s.split(",")[0]?.trim().split(";")[0]?.trim()??"").split("-");if(t.length>=2){let i=(t[t.length-1]??"").toUpperCase();if(/^[A-Z]{2}$/.test(i))return{country:i}}return{country:Do[(t[0]??"").toLowerCase()]??""}}function Lo(s){return{country:Jn[s]??""}}async function Uo(s){if(!s)return{country:""};try{let t=(await import("geoip-lite")).lookup(s),n={country:t?.country??""},i=t?.region;return i&&(n.region=i),n}catch{return console.warn("[Consenti] geoip-lite not installed \u2014 country detection skipped. Run: npm install geoip-lite"),{country:""}}}async function ko(s){if(!s)return{country:""};try{let e=await import("maxmind"),t=process.env.MAXMIND_DB_PATH??"./GeoLite2-City.mmdb",i=(await e.open(t)).get(s),o=i?.country?.iso_code??"",r=i?.subdivisions?.[0]?.iso_code,a={country:o};return r&&(a.region=r),a}catch{return console.warn("[Consenti] maxmind not available \u2014 country detection skipped. See HOW_TO_MAXMIND.md"),{country:""}}}async function No(s){return s?new Promise(e=>{import("https").then(({default:t})=>{let n=`https://ipinfo.io/${encodeURIComponent(s)}/json`,i=t.get(n,{timeout:3e3},o=>{let r=[];o.on("data",a=>r.push(a)),o.on("end",()=>{try{let a=JSON.parse(Buffer.concat(r).toString()),c={country:a.country??""};a.region&&(c.region=a.region),e(c)}catch{e({country:""})}})});i.on("error",()=>e({country:""})),i.on("timeout",()=>{i.destroy(),e({country:""})})}).catch(()=>e({country:""}))}):{country:""}}import{existsSync as $o,readFileSync as Bo,readdirSync as Vo,unlinkSync as Wo}from"fs";import{join as zn}from"path";import{copyFileSync as Kn,writeFileSync as Mo,renameSync as Go,unlinkSync as Lt,readFileSync as xo,existsSync as Fo,mkdirSync as Ho}from"fs";import{dirname as jo}from"path";function Yn(s,e,t){let n=s+".bak",i=s+".tmp";Ho(jo(s),{recursive:!0});let o=Fo(s);o&&Kn(s,n);try{Mo(i,JSON.stringify(e,null,2),"utf8");let r=JSON.parse(xo(i,"utf8"));if(!t(r))throw new Error("Schema validation failed for "+s);if(Go(i,s),o)try{Lt(n)}catch{}}catch(r){if(o)try{Kn(n,s),Lt(n)}catch{}try{Lt(i)}catch{}throw r}}function Qn(s){return typeof s=="object"&&s!==null&&typeof s.id=="string"&&typeof s.version=="number"}var ot=class{constructor(e){this.dir=e}dir;read(e,t){let n=this.path(e,t);if(!$o(n))return null;try{let i=JSON.parse(Bo(n,"utf8"));return Qn(i)?i:null}catch{return null}}write(e,t,n){Yn(this.path(e,t),n,Qn)}rebuild(e,t){for(let[n,i]of t)try{this.write(e,n,i)}catch(o){console.warn(`[Consenti] locale cache rebuild failed for ${e}/${n}:`,o)}}invalidate(e){try{let t=`${e}_`;for(let n of Vo(this.dir))if(n.startsWith(t)&&n.endsWith(".json"))try{Wo(zn(this.dir,n))}catch{}}catch{}}path(e,t){return zn(this.dir,`${e}_${t}.json`)}};function Xn(s,e){let t=[];for(let[n,i]of Object.entries(s.consentJson)){let o=(e.profileJson.cookies??[]).find(r=>r.id===n);o&&(o.mandatory&&i!=="granted"&&t.push(`Cookie "${n}" is mandatory and cannot be "${i}"`),i==="objected"&&o.type!=="legitimate_interest"&&t.push(`Cookie "${n}" is type "${o.type??"consent"}" \u2014 'objected' is only valid for 'legitimate_interest'`))}return{valid:t.length===0,errors:t}}function Ut(s,e,t,n){let i={};for(let o of s??[]){let r=o.id;if(o.mandatory){i[r]="granted";continue}if(n==="cpra"){if(o.cpraCategory==="sensitive"){let c=e[r];i[r]=c==="granted"||c==="denied"||c==="objected"?c:"denied";continue}if(t&&(o.cpraCategory==="sale"||o.cpraCategory==="sharing")){i[r]="denied";continue}}if(t&&o.listenGpc&&n!=="dpdpa"){i[r]="denied";continue}let a=e[r];a==="granted"||a==="denied"||a==="objected"?i[r]=a:i[r]="denied"}return i}function Zn(s,e){let t=[];return s.profileVersion<e.version&&t.push("profile_version_mismatch"),Jo(s,e)&&t.push("consent_expired"),{valid:t.length===0,reasons:t,currentProfileVersion:e.version,consentProfileVersion:s.profileVersion}}function ei(s,e){let t=new Set,n=new Set;for(let i of s)if(e[i.id]==="granted"){i.tcfVendorId&&t.add(i.tcfVendorId);for(let o of i.tcfPurposes??[])n.add(o)}return{vendorConsents:[...t].sort((i,o)=>i-o),purposeConsents:[...n].sort((i,o)=>i-o)}}function Jo(s,e){let t=e.profileJson.cookies??[],n=new Date(s.createdAt).getTime(),i=Date.now();for(let o of t){if(!o.expiry)continue;let r=n+o.expiry*24*60*60*1e3;if(i>r)return!0}return!1}function ti(s){let e={v:2,ts:Math.floor(Date.now()/100),cmpId:s.cmpId,cmpV:s.cmpVersion,cs:s.consentScreen,lang:s.consentLanguage.toUpperCase().slice(0,2),vlV:s.vendorListVersion,pc:s.purposeConsents,vc:s.vendorConsents};return Buffer.from(JSON.stringify(e)).toString("base64url")}var rt=class{constructor(e,t,n,i,o="default",r,a,c,l){this.consents=e;this.visitors=t;this.profiles=n;this.audit=i;this.tenantId=o;this.compliance=r;this.pluginEngine=a;this.eventBus=c;this.tcfConfig=l}consents;visitors;profiles;audit;tenantId;compliance;pluginEngine;eventBus;tcfConfig;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,i=t.profileJson.cookies??[],o=e.consentJson;if(e.gpcDetected&&this.compliance?.gpc!==!1&&n!=="dpdpa"){o={...o};for(let p of i)n==="cpra"?(p.cpraCategory==="sale"||p.cpraCategory==="sharing")&&(o[p.id]="denied"):p.listenGpc&&(o[p.id]="denied")}let r=Xn({...e,consentJson:o},t);if(!r.valid)throw new Error(r.errors.join("; "));let a=Ut(i,o,e.gpcDetected,n),c;if(this.tcfConfig?.enabled&&i.some(p=>p.tcfVendorId)){let p=await Z(),{vendorConsents:E,purposeConsents:A}=ei(i,a);c=ti({cmpId:this.tcfConfig.cmpId,cmpVersion:this.tcfConfig.cmpVersion,consentScreen:1,consentLanguage:e.locale?.slice(0,2)??"en",vendorListVersion:p?.vendorListVersion??0,purposeConsents:A,vendorConsents:E})}let l={...e,tenantId:this.tenantId,consentJson:a,profileVersion:t.version,...c?{tcfString:c}:{}};this.pluginEngine&&(l=await this.pluginEngine.runBeforeConsentSave(l));let u=await this.consents.get(e.visitorId),d;if(u){let p=await(this.pluginEngine?this.pluginEngine.runBeforeConsentUpdate({consentJson:a,locale:e.locale,gpcDetected:e.gpcDetected}):Promise.resolve({consentJson:a,locale:e.locale,gpcDetected:e.gpcDetected}));d=await this.consents.update(e.visitorId,p)}else d=await this.consents.create(l);return await this.audit.log({tenantId:this.tenantId,action:u?"consent.updated":"consent.created",resourceType:"consent",resourceId:d.id,...u!=null?{oldData:u}:{},newData:d}),u?(this.eventBus?.emit("consent.updated",{previous:u,current:d}),await this.pluginEngine?.runAfterConsentUpdate(d)):(this.eventBus?.emit("consent.created",d),await this.pluginEngine?.runAfterConsentSave(d)),d}async update(e,t){let n=await this.consents.get(e);if(!n)throw new Error(`Consent for visitor ${e} not found`);let i=await this.profiles.get(n.profileId);if(!i)throw new Error(`Profile ${n.profileId} not found`);let o=i.profileJson.regulation,r=i.profileJson.cookies??[],a=t.gpcDetected??n.gpcDetected,c=t.consentJson;if(a&&this.compliance?.gpc!==!1&&o!=="dpdpa"){c={...c};for(let d of r)o==="cpra"?(d.cpraCategory==="sale"||d.cpraCategory==="sharing")&&(c[d.id]="denied"):d.listenGpc&&(c[d.id]="denied")}let l=Ut(r,c,a,o),u=await this.consents.update(e,{...t,consentJson:l});return await this.audit.log({tenantId:this.tenantId,action:"consent.updated",resourceType:"consent",resourceId:u.id,oldData:n,newData:u}),u}get(e){return this.consents.get(e)}async verify(e){let t=await this.consents.get(e);if(!t)return{valid:!1,reasons:["profile_version_mismatch"]};let n=await this.profiles.get(t.profileId);return n?Zn(t,n):{valid:!1,reasons:["profile_version_mismatch"]}}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 at=class{constructor(e,t="default",n){this.visitors=e;this.tenantId=t;this.eventBus=n}visitors;tenantId;eventBus;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:sn(e.ip)}:{},...e.userAgent?{userAgentHash:on(e.userAgent)}:{}},i=await this.visitors.create(n);return this.eventBus?.emit("visitor.created",i),i}};var ct=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:ge(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 i=await this.users.getById(e);if(!i)throw new Error(`User ${e} not found`);let o={};t.name!=null&&(o.name=t.name),t.email!=null&&(o.email=t.email),t.isActive!=null&&(o.isActive=t.isActive),t.password&&(o.passwordHash=ge(t.password)),t.allowedTenants!=null&&(o.allowedTenants=t.allowedTenants);let r=await this.users.update(e,o);return await this.audit.log({tenantId:this.tenantId,...n!=null?{userId:n}:{},action:"user.updated",resourceType:"user",resourceId:e,oldData:this.safe(i),newData:this.safe(r)}),this.safe(r)}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 ni=15*6e4,qo=5,lt=class{constructor(e,t){this.storage=e;this.secret=t;setInterval(()=>{let n=Date.now();for(let[i,o]of this.loginAttempts)n>=o.resetAt&&this.loginAttempts.delete(i)},ni).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 i=ge(t),o=await this.storage.createUser({tenantId:"default",name:"Admin",email:e,passwordHash:i});return await this.storage.assignRole(o.id,"role_super_admin"),o}async login(e,t){let n=Date.now(),i=this.loginAttempts.get(e);if(i&&n<i.resetAt&&i.count>=qo)return null;let o=await this.storage.getUserByEmail(e);if(!o||!o.isActive||!an(t,o.passwordHash)){let l=this.loginAttempts.get(e);return!l||n>=l.resetAt?this.loginAttempts.set(e,{count:1,resetAt:n+ni}):l.count+=1,null}this.loginAttempts.delete(e);let r=await this.storage.getUserRoles(o.id),a=await Promise.all(r.map(l=>this.storage.getPermissionsForRole(l.id))),c=[...new Set(a.flat().map(l=>l.name))];return Rt({sub:o.id,email:o.email,roles:r.map(l=>l.name),permissions:c,allowedTenants:o.allowedTenants??[]},this.secret)}signToken(e){return Rt(e,this.secret)}verify(e){return Me(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 ve=class extends Error{constructor(){super("Payload Too Large")}};function Ko(s,e){let t=s.socket?.remoteAddress??"";if(!e?.length||!e.includes(t))return t;let n=s.headers["x-forwarded-for"];return typeof n=="string"?n.split(",")[0]?.trim()??t:Array.isArray(n)?n[0]?.trim()??t:t}function y(s,e){return new Response(JSON.stringify(e),{status:s,headers:{"Content-Type":"application/json"}})}async function I(s){if(!(s.headers.get("content-type")??"").includes("application/json"))return null;try{return await s.json()}catch{return null}}function k(s,e){return s.searchParams.get(e)??void 0}function B(s,e,t){let n=s.searchParams.get(e);if(!n)return t;let i=parseInt(n,10);return isNaN(i)?t:i}async function ii(s,e=1048576,t){let i=s.headers["x-forwarded-proto"]==="https"?"https":"http",o=s.headers.host??"localhost",r=new URL(s.url??"/",`${i}://${o}`),a=new Headers;for(let[u,d]of Object.entries(s.headers))if(d)if(Array.isArray(d))for(let p of d)a.append(u,p);else a.set(u,d);a.set("x-real-ip",Ko(s,t));let c=null,l=(s.method??"GET").toUpperCase();if(l!=="GET"&&l!=="HEAD"){let u=[],d=0;for await(let p of s){if(d+=p.length,d>e)throw new ve;u.push(p)}u.length>0&&(c=Buffer.concat(u))}return new Request(r.toString(),{method:l,headers:a,body:c})}async function si(s,e){e.statusCode=s.status;let t=typeof s.headers.getSetCookie=="function"?s.headers.getSetCookie():s.headers.get("set-cookie")?[s.headers.get("set-cookie")]:[];s.headers.forEach((n,i)=>{i.toLowerCase()!=="set-cookie"&&e.setHeader(i,n)});for(let n of t)e.appendHeader("set-cookie",n);if(s.body){let n=s.body.getReader();try{for(;;){let{done:i,value:o}=await n.read();if(i)break;e.write(Buffer.from(o))}}finally{n.releaseLock()}e.end()}else e.end()}function m(s,e,t){let n={error:e};return t!=null&&process.env.NODE_ENV!=="production"&&(n.details=t),new Response(JSON.stringify(n),{status:s,headers:{"Content-Type":"application/json"}})}async function g(s){try{return await s()}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 ae,readFileSync as Yo}from"fs";import{join as ce}from"path";var zo="public, max-age=3600, stale-while-revalidate=60",oi={"Content-Type":"application/json","Cache-Control":zo};function kt(s){let e=Yo(s,"utf8");return new Response(e,{headers:oi})}function Qo(s,e,t){return new Response(null,{status:303,headers:{Location:`${s}/profiles/${e}/${t}/default`}})}function ri(s,e,t,n=""){return{"GET /profiles/:tenantId/:complianceGroupOrProfileId/:locale":async(i,o)=>g(async()=>{let{tenantId:r,complianceGroupOrProfileId:a,locale:c}=o;if(!r||!a||!c)return m(400,"Missing path parameters");if(t){let u=ce(t,r,a,`${c}.json`);if(ae(u))return kt(u);let d=ce(t,r,a,"default.json");if(ae(d))return Qo(n,r,a)}let l=await s.getResolved(a,c);return l?y(200,l):m(404,"Profile not found")}),"GET /profiles/:tenantId/:profileId/:version/:locale":async(i,o)=>g(async()=>{let{tenantId:r,profileId:a,version:c,locale:l}=o;if(!r||!a||!c||!l)return m(400,"Missing path parameters");let u=parseInt(c,10);if(isNaN(u)||u<1)return m(400,"Invalid version");if(t){let p=ce(t,r,a,c,`${l}.json`);if(ae(p))return kt(p);let E=ce(t,r,a,c,"default.json");if(ae(E))return kt(E)}let d=s.getVersionFile(a,u,l);return d?new Response(d,{headers:oi}):m(404,"Version not found")}),"GET /resolve-profile":async(i,o)=>g(async()=>{if(!e)return m(501,"Geo compliance routing not configured");let r=new URL(i.url),a=i.headers.get("x-forwarded-for")?.split(",")[0]?.trim()??i.headers.get("x-real-ip")??"",c=r.searchParams.get("tz")??"",l=r.searchParams.get("lang")??i.headers.get("accept-language")??"",u=r.searchParams.get("locale")??"",d=await e.resolve({ip:a,language:l,timezone:c});if(!d.complianceGroup)return m(404,"Could not resolve compliance group for this location");let p=await s.findActiveByComplianceGroup(d.complianceGroup);if(!p)return m(404,`No active profile found for compliance group: ${d.complianceGroup}`);let E=p.profileJson,A=E.localeContents?Object.keys(E.localeContents):[],f=p.defaultLocale;u&&A.includes(u)?f=u:d.locale&&A.includes(d.locale)&&(f=d.locale);let _=d.complianceGroup,h=p.tenantId,T,C=t?ce(t,h,_,`${f}.json`):"";if(t&&!ae(C)){let D=ce(t,h,_,"default.json");ae(D)&&(f=p.defaultLocale,T="locale_not_found",C=D)}let P=`${n}/profiles/${h}/${_}/${f}`;return y(200,{path:P,resolvedLocale:f,resolvedComplianceGroup:_,profileId:p.id,version:p.version,...T?{warning:T}:{}})}),"GET /profiles/:id":async(i,o)=>g(async()=>{let r=o.id;if(!r)return m(400,"Missing profile id");let a=await s.getResolved(r);return a?y(200,a):m(404,"Profile not found")}),"GET /profiles/:id/:locale":async(i,o)=>g(async()=>{let{id:r,locale:a}=o;if(!r)return m(400,"Missing profile id");let c=await s.getResolved(r,a);return c?y(200,c):m(404,"Profile not found")}),"GET /profiles/auto/:locale":async(i,o)=>g(async()=>{if(!e)return m(501,"Geo compliance routing not configured");let r=o.locale??"en",a=new URL(i.url),c=i.headers.get("x-forwarded-for")?.split(",")[0]?.trim()??i.headers.get("x-real-ip")??"",l=a.searchParams.get("tz")??"",u=i.headers.get("accept-language")??a.searchParams.get("lang")??"",d=await e.resolve({ip:c,language:u,timezone:l});if(!d.complianceGroup)return m(404,"Could not resolve compliance group for this location");let p=await s.findActiveByComplianceGroup(d.complianceGroup);if(!p)return m(404,`No active profile found for compliance group: ${d.complianceGroup}`);let E=await s.getResolved(p.id,r);return E?y(200,{...E,resolvedComplianceGroup:d.complianceGroup}):m(404,"Profile not found")})}}function Xo(s,e){let t=Object.entries(s).map(([n,i])=>`${n}:${i}`).join("|");return`ConsentTimestamp:${e}::${t}`}function Nt(s){let e=`consenti_${s.visitorId}_${s.profileId}`,t=Xo(s.consentJson,s.createdAt),n=process.env.NODE_ENV==="production"?"; Secure":"";return`${e}=${t}; SameSite=Lax${n}; Path=/; Max-Age=31536000`}function ai(s,e){let t=`consenti_${s}_${e}`,n=process.env.NODE_ENV==="production"?"; Secure":"";return`${t}=; SameSite=Lax${n}; Path=/; Max-Age=0`}function ci(s,e){let t;try{t=new URL(s).hostname}catch{return!1}return e.some(n=>{if(n.startsWith("*.")){let i=n.slice(1);return t===n.slice(2)||t.endsWith(i)}return t===n})}function li(s,e){let t=s??"*";return e&&e.length>0&&s&&(t=ci(s,e)?s:"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"}}function di(s){if(s.method==="OPTIONS"){let e=s.headers.get("origin");return new Response(null,{status:204,headers:li(e)})}return null}function dt(s,e){let t=e.headers.get("origin"),n=li(t),i=new Headers(s.headers);for(let[o,r]of Object.entries(n))i.set(o,r);return new Response(s.body,{status:s.status,statusText:s.statusText,headers:i})}function ui(s,e){if(e.length===0)return!0;let t=s.headers.get("origin");return t?ci(t,e):!0}var Zo=["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 Mt(s){let e=new Headers(s.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",Zo),e.set("Strict-Transport-Security","max-age=63072000; includeSubDomains; preload"),new Response(s.body,{status:s.status,statusText:s.statusText,headers:e})}var er=new Set(["granted","denied","objected"]);function pi(s){let e=Object.entries(s);for(let[t,n]of e){if(!t)return{valid:!1,error:"consentJson keys must be non-empty strings"};if(!er.has(n))return{valid:!1,error:`consentJson value "${String(n)}" is invalid; must be granted, denied, or objected`}}return{valid:!0}}function mi(s){if(!s||typeof s!="object")return{valid:!1,error:"Request body must be a JSON object"};let e=s;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"}:pi(e.consentJson)}function gi(s){if(!s||typeof s!="object")return{valid:!1,error:"Request body must be a JSON object"};let e=s;return!e.consentJson||typeof e.consentJson!="object"||Array.isArray(e.consentJson)?{valid:!1,error:"consentJson must be an object"}:pi(e.consentJson)}function fi(s,e,t){return{tenantId:t,visitorId:e,profileId:s.profileId,profileVersion:typeof s.profileVersion=="number"?s.profileVersion:1,locale:typeof s.locale=="string"?s.locale:"en",consentJson:s.consentJson,gpcDetected:!!s.gpcDetected,source:s.source??"banner"}}function yi(s){let e={consentJson:s.consentJson};return typeof s.locale=="string"&&(e.locale=s.locale),s.gpcDetected!=null&&(e.gpcDetected=!!s.gpcDetected),e}function ut(s,e){let t=s.headers.get("cookie")??"",n=`consenti_${e}_`;return t.split(";").some(o=>{let r=o.trim();return r.startsWith(n)?r.slice(r.indexOf("=")+1).startsWith("ConsentTimestamp:"):!1})?null:m(403,"Forbidden")}function hi(s,e,t,n=async()=>"default"){return{"POST /consent":async(i,o)=>g(async()=>{let r=await I(i),a=mi(r);if(!a.valid)return m(400,a.error??"Invalid request");let c=r,l=typeof c.profileId=="string"?c.profileId:null;if(!l)return m(400,"profileId is required");let u=await t.get(l);if(!u)return m(404,"Profile not found");let p=u.profileJson.allowedOrigins??[];if(!ui(i,p))return m(403,"Origin not allowed for this profile");let E=typeof c.visitorId=="string"?c.visitorId:S(),A=i.headers.get("x-real-ip")??"",f=i.headers.get("user-agent")??"",_=await n(i);await e.upsert({visitorId:E,ip:A,userAgent:f});let h=fi(c,E,_),T=await s.create(h),C=Nt(T);return new Response(JSON.stringify(T),{status:201,headers:{"Content-Type":"application/json","Set-Cookie":C}})}),"PUT /consent/:visitorId":async(i,o)=>g(async()=>{let r=o.visitorId;if(!r)return m(400,"Missing visitorId");let a=ut(i,r);if(a)return a;let c=await I(i),l=gi(c);if(!l.valid)return m(400,l.error??"Invalid request");let u=yi(c),d=await s.update(r,u),p=Nt(d);return new Response(JSON.stringify(d),{status:200,headers:{"Content-Type":"application/json","Set-Cookie":p}})}),"GET /consent/:visitorId":async(i,o)=>g(async()=>{let r=o.visitorId;if(!r)return m(400,"Missing visitorId");let a=ut(i,r);if(a)return a;let c=await s.get(r);return c?new Response(JSON.stringify(c),{status:200,headers:{"Content-Type":"application/json"}}):m(404,"Consent not found")}),"GET /consent/:visitorId/verify":async(i,o)=>g(async()=>{let r=o.visitorId;if(!r)return m(400,"Missing visitorId");let a=ut(i,r);if(a)return a;let c=await s.verify(r);return new Response(JSON.stringify(c),{status:200,headers:{"Content-Type":"application/json"}})}),"DELETE /consent/:visitorId":async(i,o)=>g(async()=>{let r=o.visitorId;if(!r)return m(400,"Missing visitorId");let a=ut(i,r);if(a)return a;let c=await s.get(r);await s.erase(r);let l={"Content-Type":"application/json"};return c&&(l["Set-Cookie"]=ai(r,c.profileId)),new Response(JSON.stringify({success:!0}),{status:200,headers:l})})}}import{randomBytes as Ui}from"crypto";async function v(s,e,t,n){let i=s.headers.get("authorization"),o=i?.startsWith("Bearer ")?i.slice(7):null;if(!o)return null;if(t.mode==="local"||t.mode==="jwt"){let r=Me(o,n);if(!r)return null;let a=r.sub,c=r.email,l=r.roles,u=r.permissions,d=r.allowedTenants;return typeof a!="string"||typeof c!="string"?null:{sub:a,email:c,roles:Array.isArray(l)?l:[],permissions:Array.isArray(u)?u:[],allowedTenants:Array.isArray(d)?d:[]}}if(t.mode==="custom"&&t.validateUser){let r=await t.validateUser(s);if(!r)return null;let a=await e.getUserRoles(r.id),c=await Promise.all(a.map(u=>e.getPermissionsForRole(u.id))),l=[...new Set(c.flat().map(u=>u.name))];return{sub:r.id,email:r.email,roles:a.map(u=>u.name),permissions:l,allowedTenants:[]}}return null}function b(s,e){return s?e&&!s.permissions.includes(e)?m(403,"Forbidden"):null:m(401,"Unauthorized")}import{randomBytes as tr,createHash as nr}from"crypto";function _i(s){return s.toString("base64").replace(/\+/g,"-").replace(/\//g,"_").replace(/=/g,"")}function Ei(s){return Buffer.from(s.replace(/-/g,"+").replace(/_/g,"/"),"base64").toString("utf8")}function wi(){let s=_i(tr(32)),e=_i(nr("sha256").update(s).digest());return{verifier:s,challenge:e}}async function Gt(s){let e=await fetch(`${s}/.well-known/openid-configuration`,{signal:AbortSignal.timeout(1e4)});if(!e.ok)throw new Error(`OIDC discovery failed for ${s}: ${e.status}`);return e.json()}function Ri(s,e,t,n,i){let o=new URLSearchParams({response_type:"code",client_id:s.clientId,redirect_uri:s.redirectUri,scope:"openid email profile",state:t,nonce:n,code_challenge:i,code_challenge_method:"S256"});return`${e}?${o.toString()}`}async function Si(s,e,t,n){let i=new URLSearchParams({grant_type:"authorization_code",client_id:s.clientId,client_secret:s.clientSecret,redirect_uri:s.redirectUri,code:t,code_verifier:n}),o=await fetch(e,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:i.toString(),signal:AbortSignal.timeout(1e4)});if(!o.ok){let r=await o.text();throw new Error(`OIDC token exchange failed: ${r}`)}return o.json()}async function Ai(s,e){let t=s.split(".");if(t.length!==3)throw new Error("Invalid ID token format");let n=JSON.parse(Ei(t[0])),i=n.alg??"RS256",o=await fetch(e,{signal:AbortSignal.timeout(1e4)});if(!o.ok)throw new Error(`Failed to fetch JWKS: ${o.status}`);let r=await o.json(),a=n.kid?r.keys.find(h=>h.kid===n.kid):r.keys.find(h=>h.use==="sig")??r.keys[0];if(!a)throw new Error("No matching key in JWKS");let c;if(i==="RS256"||i==="RS384"||i==="RS512"){let h=i==="RS256"?"SHA-256":i==="RS384"?"SHA-384":"SHA-512";c=await globalThis.crypto.subtle.importKey("jwk",a,{name:"RSASSA-PKCS1-v1_5",hash:{name:h}},!1,["verify"])}else if(i==="ES256"||i==="ES384"||i==="ES512"){let h=i==="ES256"?"P-256":i==="ES384"?"P-384":"P-521";c=await globalThis.crypto.subtle.importKey("jwk",a,{name:"ECDSA",namedCurve:h},!1,["verify"])}else throw new Error(`Unsupported OIDC token algorithm: ${i}`);let l=new TextEncoder().encode(`${t[0]}.${t[1]}`),u=(4-t[2].length%4)%4,d=t[2].replace(/-/g,"+").replace(/_/g,"/")+"=".repeat(u),p=Uint8Array.from(atob(d),h=>h.charCodeAt(0)),E=i.startsWith("ES")?{name:"ECDSA",hash:{name:`SHA-${i.slice(2)}`}}:{name:"RSASSA-PKCS1-v1_5"};if(!await globalThis.crypto.subtle.verify(E,c,p,l))throw new Error("ID token signature verification failed");let f=JSON.parse(Ei(t[1])),_=Math.floor(Date.now()/1e3);if(typeof f.exp=="number"&&f.exp<_)throw new Error("ID token has expired");return f}function Ti(s,e){let t=e?.email??"email",n=s[t];if(typeof n!="string")throw new Error("OIDC token missing email claim");return n}function Ci(s,e){let t=e?.roles??"consenti_roles",n=s[t];return Array.isArray(n)?n.filter(i=>typeof i=="string"):[]}var Ie=new Map;setInterval(()=>{let s=Date.now();for(let[e,t]of Ie)t.expiresAt<s&&Ie.delete(e)},5*6e4).unref();function vi(s,e,t){Ie.set(s,{verifier:e,nonce:t,expiresAt:Date.now()+10*6e4})}function Ii(s){let e=Ie.get(s);return Ie.delete(s),!e||e.expiresAt<Date.now()?null:{verifier:e.verifier,nonce:e.nonce}}function ir(s){return s.replace(/&/g,"&").replace(/"/g,""").replace(/</g,"<").replace(/>/g,">")}function Pi(s){return`<?xml version="1.0" encoding="UTF-8"?>
|
|
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 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}:{}}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, updated_at) VALUES ($1,$2::jsonb,$3::jsonb,$4,NOW())
|
|
133
|
+
ON CONFLICT (tenant_id) DO UPDATE SET allowed_origins_json=$2::jsonb, admin_allowed_origins_json=$3::jsonb, setup_completed=$4, updated_at=NOW()`,[e,JSON.stringify(s.allowedOrigins??[]),JSON.stringify(s.adminAllowedOrigins??[]),s.setupCompleted??!1]),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}async purgeExpiredAuditLogs(e){let t=new Date(Date.now()-e*864e5).toISOString(),n=await this.q("SELECT COUNT(*)::int AS total FROM audit_logs WHERE created_at < $1",[t]);return await this.q("DELETE FROM audit_logs WHERE created_at < $1",[t]),Number(n[0]?.total??0)}async createConsentTemplate(){throw new Error("Not implemented")}async updateConsentTemplate(){throw new Error("Not implemented")}async deleteConsentTemplate(){throw new Error("Not implemented")}async getConsentTemplate(){throw new Error("Not implemented")}async getConsentTemplates(){throw new Error("Not implemented")}async copyConsentTemplate(){throw new Error("Not implemented")}async createUITemplate(){throw new Error("Not implemented")}async updateUITemplate(){throw new Error("Not implemented")}async deleteUITemplate(){throw new Error("Not implemented")}async getUITemplate(){throw new Error("Not implemented")}async getUITemplates(){throw new Error("Not implemented")}async copyUITemplate(){throw new Error("Not implemented")}async listProfilesSummary(){return[]}async findProfilesUsingConsentTemplate(){return[]}async findProfilesUsingUITemplate(){return[]}async getOptInStats(){throw new Error("Not implemented")}};import{readFile as mo,writeFile as go,rename as fo,mkdir as _o}from"fs/promises";import{randomUUID as ie}from"crypto";var ho=500;function zt(){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 Qt(i,e,t){let n=Math.min(t,ho),s=(e-1)*n;return i.slice(s,s+n)}function yo(i){let{id:e,tenantId:t,visitorId:n,profileId:s,locale:r,gpcDetected:o,source:a,ageVerified:c,createdAt:d,updatedAt:l}=i;return{id:e,tenantId:t,visitorId:n,profileId:s,locale:r,gpcDetected:o,source:a,...c!=null?{ageVerified:c}:{},createdAt:d,updatedAt:l}}function wo(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 st=class{constructor(e){this.config=e}config;db=zt();filePath="";writeQueue=Promise.resolve();scheduleWrite(){this.writeQueue=this.writeQueue.then(async()=>{await _o(this.filePath.replace(/\/[^/]+$/,""),{recursive:!0});let e=`${this.filePath}.tmp`;await go(e,JSON.stringify(this.db),"utf8"),await fo(e,this.filePath)}).catch(e=>{console.warn("[Consenti] JSON store write failed:",e)})}async connect(){let{dbPath:e}=K(this.config.path??"./consenti-data","json");this.filePath=e;try{let t=await mo(this.filePath,"utf8");this.db={...zt(),...JSON.parse(t)}}catch{this.db=zt()}await this.migrate()}async disconnect(){await this.writeQueue}async migrate(){this.seed()}seed(){let e=new Date().toISOString(),{id:t,name:n,slug:s}=J;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 V)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 Q)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 X)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:H(),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=B(),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.signature!=null&&{signature:e.signature},createdAt:t,updatedAt:t};return this.db.consents.push(s),this.db.consent_history.push({id:ie(),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:ie(),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:Qt(t,n,s).map(yo),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:ie(),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:$(),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:Qt(t,n,s),total:t.length,page:n,limit:s}}async createUser(e){let t=new Date().toISOString(),n={id:ie(),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:ie(),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:ie(),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:Qt(t,n,s).map(wo),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),d=c.length>0?c.filter(l=>l==="granted").length/c.length:0;d>=.8?s++:d<=.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:ie(),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 purgeExpiredAuditLogs(e){let t=new Date(Date.now()-e*864e5).toISOString(),n=this.db.audit_logs.length;this.db.audit_logs=this.db.audit_logs.filter(r=>r.createdAt>=t);let s=n-this.db.audit_logs.length;return s>0&&this.scheduleWrite(),s}async createApiKey(e){let t=new Date().toISOString(),n={id:ie(),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:ne(),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:se(),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={},d=new Map;for(let u of n){let p=Object.values(u.consentJson),h=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 g=u.locale;c[g]||(c[g]={total:0,granted:0,denied:0,managed:0}),c[g].total++;let f=u.createdAt.slice(0,10),w=d.get(f)??{total:0,granted:0,denied:0,managed:0};w.total++,h>=.9?(r++,c[g].granted++,w.granted++):E>=.9?(o++,c[g].denied++,w.denied++):(a++,c[g].managed++,w.managed++),d.set(f,w)}let l=Array.from(d.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:l}}};var it=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 rt=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 ot=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 at=class{constructor(e){this.adapter=e}adapter;create(e){return this.adapter.createNoticeShown(e)}getForVisitor(e){return this.adapter.getNoticeShownForVisitor(e)}};var ct=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 lt=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 Eo=3e4,dt=class{constructor(e){this.storage=e}storage;cache=new lt(Eo);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 ut=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 ft,readFileSync as ks,existsSync as _t,statSync as Ms,lstatSync as Wo}from"fs";import{join as q}from"path";import{Worker as Mo}from"worker_threads";import{fileURLToPath as xo}from"url";import{join as $o,dirname as jo}from"path";import{existsSync as Fo}from"fs";import{writeFileSync as Ao,renameSync as Ps,mkdirSync as Os,copyFileSync as Io,rmSync as bs,existsSync as ve,readFileSync as To,lstatSync as Ds,symlinkSync as vo,unlinkSync as Po}from"fs";import{join as Z,resolve as Oo}from"path";import{randomUUID as bo}from"crypto";import{createHmac as Ss,createHash as Ro}from"crypto";function Xt(i){return Ro("sha256").update(i).digest("hex")}function pt(i,e){return Ss("sha256",i).update(e).digest()}function So(i){let e=i.toISOString().replace(/[-:]/g,"").slice(0,15)+"Z";return{amzDate:e,dateStamp:e.slice(0,8)}}function Co(i,e,t,n){let s=pt(`AWS4${i}`,e),r=pt(s,t),o=pt(r,n);return pt(o,"aws4_request")}function Cs(i){let{method:e,key:t,body:n,contentType:s,config:r}=i,o=new Date,{amzDate:a,dateStamp:c}=So(o),d=`${r.bucketName}.s3.${r.region}.amazonaws.com`,l=Xt(n?Buffer.isBuffer(n)?n:Buffer.from(n,"utf8"):""),u={host:d,"x-amz-date":a,"x-amz-content-sha256":l};r.sessionToken&&(u["x-amz-security-token"]=r.sessionToken),s&&e==="PUT"&&(u["content-type"]=s);let p=Object.keys(u).sort(),h=p.map(v=>`${v}:${u[v]}
|
|
134
|
+
`).join(""),E=p.join(";"),g=[e,`/${encodeURIComponent(t).replace(/%2F/g,"/")}`,"",h,E,l].join(`
|
|
135
|
+
`),f=`${c}/${r.region}/s3/aws4_request`,w=["AWS4-HMAC-SHA256",a,f,Xt(g)].join(`
|
|
136
|
+
`),R=Co(r.secretAccessKey,c,r.region,"s3"),I=Ss("sha256",R).update(w).digest("hex"),T=[`AWS4-HMAC-SHA256 Credential=${r.accessKeyId}/${f}`,`SignedHeaders=${E}`,`Signature=${I}`].join(", ");return{...u,authorization:T}}function As(i,e){let t=encodeURIComponent(i).replace(/%2F/g,"/");return`https://${e.bucketName}.s3.${e.region}.amazonaws.com/${t}`}async function mt(i,e,t,n="application/json"){let s=Cs({method:"PUT",key:i,body:e,contentType:n,config:t}),r=typeof e=="string"?e:e.toString("utf8"),o=await fetch(As(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 Is(i,e){let t=Cs({method:"DELETE",key:i,config:e}),n=await fetch(As(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 en(i,e){return Z(i,"profiles",e)}function Zt(i,e,t,n){return Z(en(i,e),t,String(n))}function Ls(i,e,t){return Z(en(i,e),t)}function gt(i,e,t){return`profiles/${i}/${e}/${t}.json`}function Ts(i,e){let t=i+".tmp";Ao(t,e,"utf8"),JSON.parse(e),Ps(t,i)}var Do=["gpcMode","complianceGroup","darkMode","allowReceipt","enhanceAccessibility","showFooterMetadata","allowedOrigins","complianceConfig","dpdpa","hidePoweredBy"];function Lo(i){let e={};for(let t of Do){let n=i[t];n!==void 0&&(e[t]=n)}return e}async function vs(i){let{storagePath:e,tenantId:t,profileId:n,version:s,profile:r,localeContent:o,previousVersion:a,s3Config:c}=i,d=Zt(e,t,n,s);Os(d,{recursive:!0});let{locales:l,defaultLocale:u,cookies:p,expiryDays:h}=r;for(let E of l){let g,f=o[E];if(f){let R={id:r.id,version:s,defaultLocale:u,currentLocale:E,locales:l,cookies:p,...h!==void 0?{expiryDays:h}:{},...Lo(r),mainBanner:f.mainBanner,...f.gpcBanner?{gpcBanner:f.gpcBanner}:{},preferenceModal:f.preferenceModal};g=JSON.stringify(R,null,2)}else if(a!==void 0){let R=Z(Zt(e,t,n,a),`${E}.json`);if(!ve(R))continue;g=To(R,"utf8")}else continue;let w=Z(d,`${E}.json`);Ts(w,g),E===u&&Ts(Z(d,"default.json"),g),c?.enabled&&(await mt(gt(t,`${n}/${s}`,E),g,c),E===u&&await mt(gt(t,`${n}/${s}`,"default"),g,c))}if(!ve(Z(d,"default.json"))){let E=l.includes(u)?u:l[0]??"";if(E){let g=Z(d,`${E}.json`);ve(g)&&Io(g,Z(d,"default.json"))}}}function Uo(i){ve(i)&&(Ds(i).isSymbolicLink()||bs(i,{recursive:!0,force:!0}))}async function No(i){let{storagePath:e,tenantId:t,profileId:n,version:s,complianceGroup:r,s3Config:o}=i,a=Oo(Zt(e,t,n,s)),c=Ls(e,t,r);Os(en(e,t),{recursive:!0}),Uo(c);let d=`${c}.new-${bo().slice(0,8)}`;if(vo(a,d,"junction"),Ps(d,c),o?.enabled){let l=JSON.stringify({profileId:n,version:s});await mt(gt(t,r,"pointer"),l,o)}}async function ko(i){let{storagePath:e,tenantId:t,complianceGroup:n,s3Config:s}=i,r=Ls(e,t,n);ve(r)&&(Ds(r).isSymbolicLink()?Po(r):bs(r,{recursive:!0,force:!0})),s?.enabled&&await Is(gt(t,n,"pointer"),s)}async function Us(i){i.action==="write"?await vs(i):i.action==="activate"?(await vs(i),await No(i)):i.action==="deactivate"&&await ko(i)}var Ho=jo(xo(import.meta.url)),Ns=$o(Ho,"profile-json.worker.js"),Bo=!Fo(Ns);async function tn(i){if(Bo){await Us(i);return}return new Promise((e,t)=>{let n=new Mo(Ns);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 nn(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 xs(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.hidePoweredBy!==void 0?{hidePoweredBy:i.hidePoweredBy}:{}}}var ht=class{constructor(e,t,n="default",s,r,o,a,c,d){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=d}profiles;audit;tenantId;storage;eventBus;localeCache;profilesDir;handleCache;s3Config;versionsRootDir(e){return q(this.profilesDir??"",this.tenantId,e)}complianceGroupDir(e){return q(this.profilesDir??"",this.tenantId,e)}groupKey(e){return e.complianceGroup||e.customComplianceGroup||""}profileFilePaths(e,t){let n=this.complianceGroupDir(e);return t.map(s=>q(n,`${s}.json`)).concat(q(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 nn(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},...t??{}},d=r.isActive??!1,u={action:d?"activate":"write",storagePath:q(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}:{},...xs(r)},localeContent:c,...s!==void 0?{previousVersion:s}:{},...this.s3Config?.enabled?{s3Config:this.s3Config}:{}};if(await tn(u),d){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 d=this.groupKey(c.profileJson);await this.writeLocaleFiles(c,s,d,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:q(this.profilesDir,".."),tenantId:this.tenantId,profileId:"",version:0,complianceGroup:e,profile:{id:"",defaultLocale:"",locales:[],cookies:{}},localeContent:{},...this.s3Config?.enabled?{s3Config:this.s3Config}:{}};await tn(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],d={mainBanner:r.mainBanner,...r.gpcBanner?{gpcBanner:r.gpcBanner}:{},preferenceModal:r.preferenceModal};if(s!==n.defaultLocale){let u=await this.getVersionFile(e,String(n.version),s);if(u)try{let p=JSON.parse(u);d={mainBanner:p.mainBanner,...p.gpcBanner?{gpcBanner:p.gpcBanner}:{},preferenceModal:p.preferenceModal}}catch{}}let l={id:n.id,version:n.version,defaultLocale:n.defaultLocale,currentLocale:s,locales:a,cookies:o,...r.expiryDays!==void 0?{expiryDays:r.expiryDays}:{},...xs(r),...d};if(this.localeCache)try{this.localeCache.write(e,s,l)}catch(u){console.warn(`[Consenti] locale cache write failed for ${e}/${s}:`,u)}return l}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(!_t(t))return[];let n=[];try{let s=ft(t,{withFileTypes:!0});for(let r of s){if(!r.isDirectory())continue;let o=Number(r.name);if(!Number.isInteger(o))continue;let a=q(t,r.name),c=ft(a).filter(l=>l.endsWith(".json")&&l!=="default.json").map(l=>l.replace(".json","")),d;try{d=Ms(a).mtime.toISOString()}catch{d=new Date().toISOString()}n.push({version:o,createdAt:d,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=q(this.versionsRootDir(e),t),r=q(s,`${n}.json`);if(!_t(r)){let o=q(s,"default.json");return _t(o)?ks(o,"utf8"):null}return ks(r,"utf8")}async listArchivedProfiles(){if(!this.profilesDir)return[];let e=q(this.profilesDir,this.tenantId);if(!_t(e))return[];let t=new Set((await this.profiles.list(this.tenantId)).map(r=>r.id)),n=[],s;try{s=ft(e,{withFileTypes:!0})}catch{return[]}for(let r of s){if(!r.isDirectory()||t.has(r.name))continue;let o=q(e,r.name);if(Wo(o).isSymbolicLink())continue;let a;try{a=ft(o,{withFileTypes:!0})}catch{continue}let c=a.filter(l=>l.isDirectory()&&Number.isInteger(Number(l.name)));if(c.length===0)continue;let d=new Date(0).toISOString();for(let l of c)try{let u=Ms(q(o,l.name)).mtime.toISOString();u>d&&(d=u)}catch{}n.push({id:r.name,versionCount:c.length,lastModified:d})}return n.sort((r,o)=>o.lastModified.localeCompare(r.lastModified))}async seedDefaultProfile(e){if(await this.profiles.findActiveByComplianceGroup(this.tenantId,e))return;let{DEFAULT_PROFILES:n,resolveLocaleTranslation:s}=await import("./profiles-XTYPWLVC.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(l=>s(e,l)!==void 0)],d={};for(let l of a){let u=s(e,l);u&&(d[l]={mainBanner:u.mainBanner,...u.gpcBanner?{gpcBanner:u.gpcBanner}:{},preferenceModal:u.preferenceModal})}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,...r.expiryDays!==void 0?{expiryDays:r.expiryDays}:{},locales:c,mainBanner:o.mainBanner,...o.gpcBanner?{gpcBanner:o.gpcBanner}:{},preferenceModal:o.preferenceModal,isActive:!0},...Object.keys(d).length>0?{localeContent:d}:{}})}async seedAllDefaults(){let{COMPLIANCE_GROUP_IDS:e}=await import("./dist-Z5RVTVBT.js");await Promise.all(e.map(t=>this.seedDefaultProfile(t)))}};var $s={"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 Vo={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"},yt=class{constructor(e,t){this.map=t;this.provider=e??"default"}map;provider;async resolve(e){let t="",n,s,r=null;if(typeof this.provider=="function"){let a=await this.provider(e);t=a.country??"",n=a.region??void 0,r=a.locale}else switch(this.provider){case"default":case"timezone":({country:t}=Jo(e.timezone)),t||({country:t,region:n}=js(e.language));break;case"language":({country:t,region:n}=js(e.language));break;case"geoip":({country:t,region:n}=await qo(e.ip));break;case"hosted-geoip-lite":({country:t,region:n}=await Ko(e.ip));break;case"maxmind":({country:t,region:n}=await Go(e.ip));break}let o={country:t,complianceGroup:this.resolveGroup(t,n)};return n!==void 0&&(o.region=n),s!==void 0&&(o.regulation=s),r!==null&&(o.locale=r),o}resolveGroup(e,t){if(!e)return"";let s=this.map.countries[e];return s?t!==void 0?s.overriddenRegions?.[t]?.complianceGroup??s.complianceGroup:s.default:""}};function js(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:Vo[(t[0]??"").toLowerCase()]??""}}function Jo(i){return{country:$s[i]??""}}async function qo(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 Go(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 Ko(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:""}}import{existsSync as ta,readFileSync as na,readdirSync as sa,unlinkSync as ia}from"fs";import{join as Bs}from"path";import{copyFileSync as Fs,writeFileSync as Yo,renameSync as zo,unlinkSync as sn,readFileSync as Qo,existsSync as Xo,mkdirSync as Zo}from"fs";import{dirname as ea}from"path";function Hs(i,e,t){let n=i+".bak",s=i+".tmp";Zo(ea(i),{recursive:!0});let r=Xo(i);r&&Fs(i,n);try{Yo(s,JSON.stringify(e,null,2),"utf8");let o=JSON.parse(Qo(s,"utf8"));if(!t(o))throw new Error("Schema validation failed for "+i);if(zo(s,i),r)try{sn(n)}catch{}}catch(o){if(r)try{Fs(n,i),sn(n)}catch{}try{sn(s)}catch{}throw o}}function Ws(i){return typeof i=="object"&&i!==null&&typeof i.id=="string"&&typeof i.version=="number"}var wt=class{constructor(e){this.dir=e}dir;read(e,t){let n=this.path(e,t);if(!ta(n))return null;try{let s=JSON.parse(na(n,"utf8"));return Ws(s)?s:null}catch{return null}}write(e,t,n){Hs(this.path(e,t),n,Ws)}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 sa(this.dir))if(n.startsWith(t)&&n.endsWith(".json"))try{ia(Bs(this.dir,n))}catch{}}catch{}}path(e,t){return Bs(this.dir,`${e}_${t}.json`)}};function Vs(i,e,t){let n=[];for(let[s,r]of Object.entries(i.consentJson)){if(!e[s])continue;let o=je(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(Pn(o,e)){r[o]="granted";continue}if(s==="cpra"){if(a.cpraCategory==="sensitive"){let d=t[o];r[o]=d==="granted"||d==="denied"||d==="objected"?d:"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 on(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 Js(i,e,t,n){let s=[];return(!t||t.id!==e.id)&&s.push("profile_changed"),ra(i,e)&&s.push("consent_expired"),n&&i.signature&&z(on(i),n)!==i.signature&&s.push("hmac_invalid"),{valid:s.length===0,reasons:s,...t?{currentProfileId:t.id}:{},consentProfileId:e.id}}function qs(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 ra(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 Et=class{constructor(e,t,n,s,r="default",o,a,c,d,l,u){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=d;this.storage=l;this.consentSigningKey=u}consents;visitors;profiles;audit;tenantId;compliance;pluginEngine;eventBus;tcfConfig;storage;consentSigningKey;sign(e){if(this.consentSigningKey)return z(on(e),this.consentSigningKey)}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?nn(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=fe(r),a=e.consentJson;if(e.gpcDetected&&this.compliance?.gpc!==!1&&n!=="dpdpa"){a={...a};for(let[g,f]of Object.entries(s))n==="cpra"?(f.cpraCategory==="sale"||f.cpraCategory==="sharing")&&(a[g]="denied"):f.listenGpc&&(a[g]="denied")}let c=Vs({...e,consentJson:a},s,o);if(!c.valid)throw new Error(c.errors.join("; "));let d=rn(s,o,a,e.gpcDetected,n),l;if(this.tcfConfig?.enabled&&Object.values(s).some(g=>g.tcfVendorId)){let g=await ae(),{vendorConsents:f,purposeConsents:w}=qs(s,d);l=Ln({cmpId:this.tcfConfig.cmpId,cmpVersion:this.tcfConfig.cmpVersion,consentScreen:1,consentLanguage:e.locale?.slice(0,2)??"en",vendorListVersion:g?.vendorListVersion??0,purposeConsents:w,vendorConsents:f})}let u=this.sign({tenantId:this.tenantId,visitorId:e.visitorId,profileId:e.profileId,locale:e.locale,consentJson:d}),p={...e,tenantId:this.tenantId,consentJson:d,...l?{tcfString:l}:{},...u?{signature:u}:{}};this.pluginEngine&&(p=await this.pluginEngine.runBeforeConsentSave(p));let h=await this.consents.get(e.visitorId),E;if(h){let g=await(this.pluginEngine?this.pluginEngine.runBeforeConsentUpdate({consentJson:d,locale:e.locale,gpcDetected:e.gpcDetected,...u?{signature:u}:{}}):Promise.resolve({consentJson:d,locale:e.locale,gpcDetected:e.gpcDetected,...u?{signature:u}:{}}));E=await this.consents.update(e.visitorId,g)}else E=await this.consents.create(p);return await this.audit.log({tenantId:this.tenantId,action:h?"consent.updated":"consent.created",resourceType:"consent",resourceId:E.id,...h!=null?{oldData:h}:{},newData:E}),h?(this.eventBus?.emit("consent.updated",{previous:h,current:E}),await this.pluginEngine?.runAfterConsentUpdate(E)):(this.eventBus?.emit("consent.created",E),await this.pluginEngine?.runAfterConsentSave(E)),E}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=fe(a),d=t.gpcDetected??n.gpcDetected,l=t.consentJson;if(d&&this.compliance?.gpc!==!1&&r!=="dpdpa"){l={...l};for(let[f,w]of Object.entries(o))r==="cpra"?(w.cpraCategory==="sale"||w.cpraCategory==="sharing")&&(l[f]="denied"):w.listenGpc&&(l[f]="denied")}let u=rn(o,c,l,d,r),p=this.sign({tenantId:this.tenantId,visitorId:e,profileId:n.profileId,locale:t.locale??n.locale,consentJson:u}),h={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(h):Promise.resolve(h)),g=await this.consents.update(e,E);return await this.audit.log({tenantId:this.tenantId,action:"consent.updated",resourceType:"consent",resourceId:g.id,oldData:n,newData:g}),this.eventBus?.emit("consent.updated",{previous:n,current:g}),await this.pluginEngine?.runAfterConsentUpdate(g),g}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 Js(t,n,r,this.consentSigningKey)}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 Rt=class{constructor(e,t="default",n){this.visitors=e;this.tenantId=t;this.eventBus=n}visitors;tenantId;eventBus;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:Wn(e.ip)}:{},...e.userAgent?{userAgentHash:Vn(e.userAgent)}:{}},s=await this.visitors.create(n);return this.eventBus?.emit("visitor.created",s),s}};var St=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 Ct=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:we(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=we(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 Gs=15*6e4,oa=5,Ks=1800,At=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)},Gs).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=we(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>=oa)return null;let r=await this.storage.getUserByEmail(e);if(!r||!r.isActive||!qn(t,r.passwordHash)){let d=this.loginAttempts.get(e);return!d||n>=d.resetAt?this.loginAttempts.set(e,{count:1,resetAt:n+Gs}):d.count+=1,null}this.loginAttempts.delete(e);let o=await this.storage.getUserRoles(r.id),a=await Promise.all(o.map(d=>this.storage.getPermissionsForRole(d.id))),c=[...new Set(a.flat().map(d=>d.name))];return Bt({sub:r.id,email:r.email,roles:o.map(d=>d.name),permissions:c,allowedTenants:r.allowedTenants??[]},this.secret,Ks)}signToken(e){return Bt(e,this.secret,Ks)}verify(e){return He(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 Pe=class extends Error{constructor(){super("Payload Too Large")}};function aa(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 y(i,e){return new Response(JSON.stringify(e),{status:i,headers:{"Content-Type":"application/json"}})}async function O(i){if(!(i.headers.get("content-type")??"").includes("application/json"))return null;try{return await i.json()}catch{return null}}function U(i,e){return i.searchParams.get(e)??void 0}function It(i,e,t){let n=i.searchParams.get(e);if(!n)return t;let s=parseInt(n,10);return isNaN(s)?t:s}var Ys=500;function ue(i,e=50){let t=It(i,"limit",e);return t<1||t>Ys?{error:`limit must be between 1 and ${Ys}`}:{page:Math.max(1,It(i,"page",1)),limit:t}}async function zs(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[l,u]of Object.entries(i.headers))if(u)if(Array.isArray(u))for(let p of u)a.append(l,p);else a.set(l,u);a.set("x-real-ip",aa(i,t));let c=null,d=(i.method??"GET").toUpperCase();if(d!=="GET"&&d!=="HEAD"){let l=[],u=0;for await(let p of i){if(u+=p.length,u>e)throw new Pe;l.push(p)}l.length>0&&(c=Buffer.concat(l))}return new Request(o.toString(),{method:d,headers:a,body:c})}async function Qs(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&&process.env.NODE_ENV!=="production"&&(n.details=t),new Response(JSON.stringify(n),{status:i,headers:{"Content-Type":"application/json"}})}async function _(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 Tt,readFileSync as ca}from"fs";import{join as vt}from"path";var la="public, max-age=3600, stale-while-revalidate=60",Xs={"Content-Type":"application/json","Cache-Control":la};function da(i){let e=ca(i,"utf8");return new Response(e,{headers:Xs})}function ua(i,e,t){return new Response(null,{status:303,headers:{Location:`${i}/profiles/${e}/${t}/default`}})}function Zs(i,e,t,n=""){return{"GET /profiles/:tenantId/:complianceGroupOrProfileId/:locale":async(s,r)=>_(async()=>{let{tenantId:o,complianceGroupOrProfileId:a,locale:c}=r;if(!o||!a||!c)return m(400,"Missing path parameters");if(t){let l=vt(t,o,a,`${c}.json`);if(Tt(l))return da(l);let u=vt(t,o,a,"default.json");if(Tt(u))return ua(n,o,a)}let d=await i.getResolved(a,c);return d?y(200,d):m(404,"Profile not found")}),"GET /profiles/:tenantId/:profileId/:entryId/:locale":async(s,r)=>_(async()=>{let{profileId:o,entryId:a,locale:c}=r;if(!o||!a||!c)return m(400,"Missing path parameters");let d=await i.getVersionFile(o,a,c);return d?new Response(d,{headers:Xs}):m(404,"Version not found")}),"GET /resolve-profile":async(s,r)=>_(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")??"",d=o.searchParams.get("lang")??s.headers.get("accept-language")??"",l=o.searchParams.get("locale")??"",u=o.searchParams.get("data");if(u)try{let v=JSON.parse(Buffer.from(u,"base64").toString("utf8"));v.timezone&&(c=v.timezone),v.language&&(d=v.language),v.locale&&(l=v.locale)}catch{}let p=await e.resolve({ip:a,language:d,timezone:c});if(!p.complianceGroup)return y(200,{path:null,complianceGroup:"opt-in",locale:l||"en",found:!1});let h=await i.findActiveByComplianceGroup(p.complianceGroup);if(!h)return y(200,{path:null,complianceGroup:p.complianceGroup,locale:l||"en",found:!1});let E=h.profileJson.locales??[],g=h.defaultLocale;if(l&&E.includes(l))g=l;else if(E.length>0){let v=(l||d).split("-")[0]??"",j=E.find(F=>F===v||F.startsWith(`${v}-`));j&&(g=j)}let f=p.complianceGroup,w=h.tenantId,R=!1,I=t?vt(t,w,f,`${g}.json`):"";if(t&&Tt(I))R=!0;else if(t){let v=vt(t,w,f,`${h.defaultLocale}.json`);Tt(v)&&(g=h.defaultLocale,I=v,R=!0)}let T=R?`${n}/profiles/${w}/${f}/${g}`:null;return y(200,{path:T,complianceGroup:f,locale:g,found:R})}),"GET /profiles/:id":async(s,r)=>_(async()=>{let o=r.id;if(!o)return m(400,"Missing profile id");let a=await i.getResolved(o);return a?y(200,a):m(404,"Profile not found")}),"GET /profiles/:id/:locale":async(s,r)=>_(async()=>{let{id:o,locale:a}=r;if(!o)return m(400,"Missing profile id");let c=await i.getResolved(o,a);return c?y(200,c):m(404,"Profile not found")}),"GET /profiles/auto/:locale":async(s,r)=>_(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")??"",d=a.searchParams.get("tz")??"",l=s.headers.get("accept-language")??a.searchParams.get("lang")??"",u=await e.resolve({ip:c,language:l,timezone:d});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 h=await i.getResolved(p.id,o);return h?y(200,{...h,resolvedComplianceGroup:u.complianceGroup}):m(404,"Profile not found")})}}function ei(){return process.env.NODE_ENV==="production"?"SameSite=None; Secure":"SameSite=Lax"}function pa(i,e,t){let n=z(`${i}.${e}`,t);return`${e}.${n}`}function an(i,e,t){let n=`consenti_${i}`,s=pa(i,e,t);return`${n}=${s}; ${ei()}; Path=/; Max-Age=31536000`}function ti(i){return`${`consenti_${i}`}=; ${ei()}; Path=/; Max-Age=0`}function ni(i,e,t){let n=i.split(".");if(n.length!==2)return!1;let[s,r]=n;return!s||!r?!1:Jn(`${e}.${s}`,r,t)}function ma(i){if(!i.includes("://"))return i;try{return new URL(i).hostname}catch{return i}}function si(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===ma(n)})}function ii(i,e){let t=i??"*";return e&&e.length>0&&i&&(t=si(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 ri(i){if(i.method==="OPTIONS"){let e=i.headers.get("origin");return new Response(null,{status:204,headers:ii(e)})}return null}function Oe(i,e){let t=e.headers.get("origin"),n=ii(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 Pt(i,e){if(e.length===0)return!0;let t=i.headers.get("origin");return t?si(t,e):!0}var ga=["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 cn(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",ga),e.set("Strict-Transport-Security","max-age=63072000; includeSubDomains; preload"),new Response(i.body,{status:i.status,statusText:i.statusText,headers:e})}var fa=new Set(["granted","denied","objected"]);function oi(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(!fa.has(n))return{valid:!1,error:`consentJson value "${String(n)}" is invalid; must be granted, denied, or objected`}}return{valid:!0}}function ai(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"}:oi(e.consentJson)}function ci(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"}:oi(e.consentJson)}function li(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 di(i){let e={consentJson:i.consentJson};return typeof i.locale=="string"&&(e.locale=i.locale),i.gpcDetected!=null&&(e.gpcDetected=!!i.gpcDetected),e}function Ot(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:ni(a.slice(c+1),e,t)})?null:m(403,"Forbidden")}function ui(i,e,t,n=async()=>"default",s,r=""){return{"POST /consent":async(o,a)=>_(async()=>{let c=await O(o),d=ai(c);if(!d.valid)return m(400,d.error??"Invalid request");let l=c,u=typeof l.profileId=="string"?l.profileId:null;if(!u)return m(400,"profileId is required");let p=await t.get(u);if(!p)return m(404,"Profile not found");let h=await n(o),g=p.profileJson.allowedOrigins??[];if(g.length===0&&s&&(g=(await s.getSettings(h)).allowedOrigins??[]),!Pt(o,g))return m(403,"Origin not allowed for this profile");let f=typeof l.visitorId=="string"?l.visitorId:$(),w=o.headers.get("x-real-ip")??"",R=o.headers.get("user-agent")??"";await e.upsert({visitorId:f,ip:w,userAgent:R});let I=li(l,f,h),T=await i.create(I),v=an(f,T.id,r);return new Response(JSON.stringify(T),{status:201,headers:{"Content-Type":"application/json","Set-Cookie":v}})}),"PUT /consent/:visitorId":async(o,a)=>_(async()=>{let c=a.visitorId;if(!c)return m(400,"Missing visitorId");let d=Ot(o,c,r);if(d)return d;let l=await O(o),u=ci(l);if(!u.valid)return m(400,u.error??"Invalid request");let p=di(l),h=await i.update(c,p),E=an(c,h.id,r);return new Response(JSON.stringify(h),{status:200,headers:{"Content-Type":"application/json","Set-Cookie":E}})}),"GET /consent/:visitorId":async(o,a)=>_(async()=>{let c=a.visitorId;if(!c)return m(400,"Missing visitorId");let d=Ot(o,c,r);if(d)return d;let l=await i.get(c);return l?new Response(JSON.stringify(l),{status:200,headers:{"Content-Type":"application/json"}}):m(404,"Consent not found")}),"GET /consent/:visitorId/verify":async(o,a)=>_(async()=>{let c=a.visitorId;if(!c)return m(400,"Missing visitorId");let d=Ot(o,c,r);if(d)return d;let l=await i.verify(c);return new Response(JSON.stringify(l),{status:200,headers:{"Content-Type":"application/json"}})}),"DELETE /consent/:visitorId":async(o,a)=>_(async()=>{let c=a.visitorId;if(!c)return m(400,"Missing visitorId");let d=Ot(o,c,r);if(d)return d;let l=await i.get(c);await i.erase(c);let u={"Content-Type":"application/json"};return l&&(u["Set-Cookie"]=ti(c)),new Response(JSON.stringify({success:!0}),{status:200,headers:u})})}}var _a=!1;function pi(i){return{"POST /notice-shown":async(e,t)=>_(async()=>{if(!_a)return m(404,"Not found");let n=await O(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"}})})}}import{randomBytes as Pi}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(t.mode==="local"||t.mode==="jwt"){let o=He(r,n);if(!o)return null;let a=o.sub,c=o.email,d=o.roles,l=o.permissions,u=o.allowedTenants;return typeof a!="string"||typeof c!="string"?null:{sub:a,email:c,roles:Array.isArray(d)?d:[],permissions:Array.isArray(l)?l:[],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(l=>e.getPermissionsForRole(l.id))),d=[...new Set(c.flat().map(l=>l.name))];return{sub:o.id,email:o.email,roles:a.map(l=>l.name),permissions:d,allowedTenants:[]}}return null}function b(i,e){return i?e&&!i.permissions.includes(e)?m(403,"Forbidden"):null:m(401,"Unauthorized")}import{randomBytes as ha,createHash as ya}from"crypto";function mi(i){return i.toString("base64").replace(/\+/g,"-").replace(/\//g,"_").replace(/=/g,"")}function gi(i){return Buffer.from(i.replace(/-/g,"+").replace(/_/g,"/"),"base64").toString("utf8")}function fi(){let i=mi(ha(32)),e=mi(ya("sha256").update(i).digest());return{verifier:i,challenge:e}}async function ln(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 _i(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 hi(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(gi(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 d=new TextEncoder().encode(`${t[0]}.${t[1]}`),l=(4-t[2].length%4)%4,u=t[2].replace(/-/g,"+").replace(/_/g,"/")+"=".repeat(l),p=Uint8Array.from(atob(u),w=>w.charCodeAt(0)),h=s.startsWith("ES")?{name:"ECDSA",hash:{name:`SHA-${s.slice(2)}`}}:{name:"RSASSA-PKCS1-v1_5"};if(!await globalThis.crypto.subtle.verify(h,c,p,d))throw new Error("ID token signature verification failed");let g=JSON.parse(gi(t[1])),f=Math.floor(Date.now()/1e3);if(typeof g.exp=="number"&&g.exp<f)throw new Error("ID token has expired");return g}function wi(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 Ei(i,e){let t=e?.roles??"consenti_roles",n=i[t];return Array.isArray(n)?n.filter(s=>typeof s=="string"):[]}var be=new Map;setInterval(()=>{let i=Date.now();for(let[e,t]of be)t.expiresAt<i&&be.delete(e)},5*6e4).unref();function Ri(i,e,t){be.set(i,{verifier:e,nonce:t,expiresAt:Date.now()+10*6e4})}function Si(i){let e=be.get(i);return be.delete(i),!e||e.expiresAt<Date.now()?null:{verifier:e.verifier,nonce:e.nonce}}function wa(i){return i.replace(/&/g,"&").replace(/"/g,""").replace(/</g,"<").replace(/>/g,">")}function Ci(i){return`<?xml version="1.0" encoding="UTF-8"?>
|
|
148
137
|
<EntityDescriptor xmlns="urn:oasis:names:tc:SAML:2.0:metadata"
|
|
149
138
|
entityID="saml:sp:consenti">
|
|
150
139
|
<SPSSODescriptor
|
|
@@ -154,19 +143,19 @@ ${l.map(u=>` (${H(c)},${H(u)})`).join(`,
|
|
|
154
143
|
<NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress</NameIDFormat>
|
|
155
144
|
<AssertionConsumerService
|
|
156
145
|
Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
|
|
157
|
-
Location="${
|
|
146
|
+
Location="${wa(i.callbackUrl)}"
|
|
158
147
|
index="0"
|
|
159
148
|
isDefault="true"/>
|
|
160
149
|
</SPSSODescriptor>
|
|
161
|
-
</EntityDescriptor>`}async function bi(s,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}]}),i=t.IdentityProvider({entityID:e.issuer,singleSignOnService:[{Binding:"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect",Location:e.entryPoint}],signingCert:e.cert}),o=await n.parseLoginResponse(i,"post",{body:{SAMLResponse:s}}),r=o.extract.nameID,a=o.extract.attributes.roles,c=Array.isArray(a)?a.filter(l=>typeof l=="string"):[];return{email:r,roles:c}}catch(n){return console.warn("[Consenti] SAML response validation failed:",n),null}}import{createHmac as sr,randomBytes as or,timingSafeEqual as rr}from"crypto";var xt="ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";function ar(s){let e="",t=0,n=0;for(let i of s)for(n=n<<8|i,t+=8;t>=5;)e+=xt[n>>>t-5&31],t-=5;return t>0&&(e+=xt[n<<5-t&31]),e}function cr(s){let e=s.toUpperCase().replace(/[^A-Z2-7]/g,""),t=[],n=0,i=0;for(let o of e){let r=xt.indexOf(o);r<0||(i=i<<5|r,n+=5,n>=8&&(t.push(i>>>n-8&255),n-=8))}return Buffer.from(t)}function lr(s,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 i=sr("sha1",s).update(t).digest(),o=i[i.length-1]&15,r=(i[o]&127)<<24|(i[o+1]&255)<<16|(i[o+2]&255)<<8|i[o+3]&255;return String(r%1e6).padStart(6,"0")}function Oi(){return ar(or(20))}var pt=new Map;setInterval(()=>{let s=Date.now();for(let[e,t]of pt)s>=t&&pt.delete(e)},9e4).unref();function Di(s,e,t=1){if(pt.has(e))return!1;let n=Math.floor(Date.now()/3e4),i=cr(s),o=Buffer.from(e);for(let r=-t;r<=t;r++){let a=Buffer.from(lr(i,n+r));if(a.length===o.length&&rr(a,o))return pt.set(e,Date.now()+9e4),!0}return!1}function Li(s,e,t="Consenti"){let n=encodeURIComponent(`${t}:${e}`),i=new URLSearchParams({secret:s,issuer:t,algorithm:"SHA1",digits:"6",period:"30"});return`otpauth://totp/${n}?${i.toString()}`}function ki(s){return s.toString("base64").replace(/\+/g,"-").replace(/\//g,"_").replace(/=/g,"")}function Ni(s,e,t,n,i){function o(r){if(!i)return null;let a=r.headers.get("x-real-ip")??"";return i.checkRequest(a)?null:m(429,"Too Many Requests")}return{"POST /auth/login":async(r,a)=>g(async()=>{let c=o(r);if(c)return c;if(t.mode!=="local")return m(400,"Login not available in this auth mode");let l=await I(r);if(!l||typeof l!="object")return m(400,"Invalid body");let u=l;if(typeof u.email!="string"||typeof u.password!="string")return m(400,"email and password are required");let d=await s.login(u.email,u.password);return d?y(200,{token:d}):m(401,"Invalid credentials")}),"GET /auth/me":async(r,a)=>g(async()=>{let c=await v(r,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 u=await e.getUserRoles(l.id);return y(200,{id:l.id,email:l.email,name:l.name,isActive:l.isActive,totpEnabled:l.totpEnabled??!1,roles:u.map(d=>d.name),permissions:c.permissions})}),"POST /auth/logout":async(r,a)=>g(async()=>y(200,{success:!0})),"GET /auth/oidc/authorize":async(r,a)=>g(async()=>{if(t.mode!=="oidc"||!t.oidc)return m(400,"OIDC not configured");let c=await Gt(t.oidc.issuer),l=ki(Ui(16)),u=ki(Ui(16)),{verifier:d,challenge:p}=wi();vi(l,d,u);let E=Ri(t.oidc,c.authorization_endpoint,l,u,p);return new Response(null,{status:302,headers:{Location:E}})}),"GET /auth/oidc/callback":async(r,a)=>g(async()=>{if(t.mode!=="oidc"||!t.oidc)return m(400,"OIDC not configured");let c=new URL(r.url),l=c.searchParams.get("code"),u=c.searchParams.get("state");if(!l||!u)return m(400,"Missing code or state");let d=Ii(u);if(!d)return m(400,"Invalid or expired state");let p=await Gt(t.oidc.issuer),E=await Si(t.oidc,p.token_endpoint,l,d.verifier),A=await Ai(E.id_token,p.jwks_uri),f=Ti(A,t.oidc.claimsMapping),_=Ci(A,t.oidc.claimsMapping),h=await e.getUserByEmail(f);if(h||(h=await e.createUser({tenantId:"default",name:f,email:f,passwordHash:""})),_.length>0){let D=await e.getRoles("default");for(let V of _){let U=D.find(Oe=>Oe.name===V);U&&await e.assignRole(h.id,U.id).catch(()=>{})}}let T=await e.getUserRoles(h.id),C=[];for(let D of T){let V=await e.getPermissionsForRole(D.id);C.push(...V.map(U=>U.name))}let P=s.signToken({sub:h.id,email:h.email,roles:T.map(D=>D.name),permissions:[...new Set(C)]});return y(200,{token:P})}),"GET /auth/saml/metadata":async(r,a)=>g(async()=>{if(t.mode!=="saml"||!t.saml)return m(400,"SAML not configured");let c=Pi(t.saml);return new Response(c,{status:200,headers:{"Content-Type":"application/xml"}})}),"POST /auth/saml/acs":async(r,a)=>g(async()=>{let c=o(r);if(c)return c;if(t.mode!=="saml"||!t.saml)return m(400,"SAML not configured");let u=new URLSearchParams(await r.text()).get("SAMLResponse");if(typeof u!="string")return m(400,"Missing SAMLResponse");let d=await bi(u,t.saml);if(!d)return m(401,"SAML validation failed");let p=await e.getUserByEmail(d.email);p||(p=await e.createUser({tenantId:"default",name:d.email,email:d.email,passwordHash:""}));let E=await e.getUserRoles(p.id),A=[];for(let _ of E){let h=await e.getPermissionsForRole(_.id);A.push(...h.map(T=>T.name))}let f=s.signToken({sub:p.id,email:p.email,roles:E.map(_=>_.name),permissions:[...new Set(A)]});return y(200,{token:f})}),"POST /auth/totp/setup":async(r,a)=>g(async()=>{let c=await v(r,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 u=Oi(),d=Li(u,l.email);return await e.updateUser(l.id,{totpSecret:u,totpEnabled:!1}),y(200,{qrUrl:d,message:"Scan QR code, then verify with POST /auth/totp/verify"})}),"POST /auth/totp/verify":async(r,a)=>g(async()=>{let c=o(r);if(c)return c;let l=await v(r,e,t,n);if(!l)return m(401,"Unauthorized");let u=await e.getUserById(l.sub);if(!u)return m(401,"User not found");if(!u.totpSecret)return m(400,"TOTP not set up \u2014 call POST /auth/totp/setup first");let d=await I(r);if(!d||typeof d!="object")return m(400,"Invalid body");let p=d;return typeof p.token!="string"?m(400,"token is required"):Di(u.totpSecret,p.token)?(await e.updateUser(u.id,{totpEnabled:!0}),y(200,{success:!0,message:"TOTP enabled successfully"})):m(400,"Invalid TOTP token")}),"POST /auth/totp/disable":async(r,a)=>g(async()=>{let c=await v(r,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}),y(200,{success:!0,message:"TOTP disabled"})):m(401,"User not found")})}}var Mi={"opt-out-strict":{label:"Opt-out Strict (CPRA / California)",description:"California CPRA: opt-out with sale/sharing/sensitive categories required. GPC must be honored. Stricter than general US state laws.",compliances:["cpra"],defaultGpc:"honor",gpcAutoHonor:!0,allowsLegitimateInterest:!1,allowsTcf:!1,requiresCpraCategory:!0,requiresDpdpaDisclosure:!1},"opt-out":{label:"Opt-out (US State Laws)",description:"US state privacy laws. Collect first, allow opt-out of sale/share. Covers CCPA and all state-level equivalents.",compliances:["ccpa","vcdpa","cpa-co","ctdpa","ucpa","icdpa-ia","icdpa-in","tipa","mcdpa-mt","mcdpa-mn","ocpa","tdpsa","dpdpa-de","njdpa","nhpa","ndpa-ne","modpa","kcdpa","ridtpa"],defaultGpc:"honor",gpcAutoHonor:!0,allowsLegitimateInterest:!1,allowsTcf:!1,requiresCpraCategory:!1,requiresDpdpaDisclosure:!1},"notice-only":{label:"Notice Only",description:"A privacy notice is generally sufficient. No dedicated cookie banner law is encoded for this jurisdiction.",compliances:["privacy-fj","privacy-pg"],defaultGpc:"ignore",gpcAutoHonor:!1,allowsLegitimateInterest:!0,allowsTcf:!1,requiresCpraCategory:!1,requiresDpdpaDisclosure:!1},"opt-in":{label:"Standard Opt-in (GDPR)",description:"GDPR / ePrivacy style: prior consent required before non-essential cookies. Granular consent, easy reject, easy withdrawal.",compliances:["gdpr","eprivacy","uk-gdpr","pecr","revfadp","kvkk","pdpa-th","pdpl-sa","uae-pdpl","difc-dpl","adgm-dpr","qpdl-qa","pdpl-bh","pdpl-om","law25"],defaultGpc:"ignore",gpcAutoHonor:!1,allowsLegitimateInterest:!0,allowsTcf:!0,requiresCpraCategory:!1,requiresDpdpaDisclosure:!1},"opt-in-dpdpa":{label:"Opt-in (India DPDPA)",description:"India's Digital Personal Data Protection Act. Opt-in consent with data fiduciary disclosure. No legitimate interest basis.",compliances:["dpdpa"],defaultGpc:"ignore",gpcAutoHonor:!1,allowsLegitimateInterest:!1,allowsTcf:!1,requiresCpraCategory:!1,requiresDpdpaDisclosure:!0},"opt-in-china":{label:"Opt-in (China PIPL / DSL)",description:"China PIPL + DSL + CSL: strict opt-in with purpose specification, data minimisation, and cross-border transfer controls.",compliances:["pipl","dsl-cn","csl-cn"],defaultGpc:"ignore",gpcAutoHonor:!1,allowsLegitimateInterest:!1,allowsTcf:!1,requiresCpraCategory:!1,requiresDpdpaDisclosure:!1},"opt-in-brazil":{label:"Opt-in (Brazil LGPD)",description:"Brazil LGPD: opt-in consent required. Legitimate interest is a valid basis. Analytics and advertising may differ in treatment.",compliances:["lgpd"],defaultGpc:"ignore",gpcAutoHonor:!1,allowsLegitimateInterest:!0,allowsTcf:!1,requiresCpraCategory:!1,requiresDpdpaDisclosure:!1},"general-privacy-consent":{label:"General Privacy Consent",description:"Privacy notice or consent rules exist but the jurisdiction does not have a GDPR/PECR-style strict cookie banner law. Documenting legal basis is best practice.",compliances:["pipeda","popia","appi","pipa-kr","pdpa-sg","pdpa-my","dpa-ph","pdp-id","pdpd-vn","privacy-au","privacy-nz","pdpl-hk","pdpa-mo","pdpl-tw","privacy-bn","privacy-lk","privacy-np","privacy-kw","privacy-il","ndpa-ng","dpa-ke","dpa-gh","dpa-ug","dpa-rw","dpa-mu","dpa-bw","dpa-zm","dpa-zw","dpa-eg","dpa-ma","dpa-tn","dpa-dz","lfpdppp","lpdp-ar","lpdp-pe","law1581-co","law18331-uy","law19628-cl","lopdp-ec","lpdp-cr","data-protection-pa","data-protection-do"],defaultGpc:"ignore",gpcAutoHonor:!1,allowsLegitimateInterest:!0,allowsTcf:!1,requiresCpraCategory:!1,requiresDpdpaDisclosure:!1}};var Gi={"opt-in":{errors:[{rule:"legal-basis-required",field:"legalBasis",check:s=>!s.legalBasis,message:s=>`Cookie "${s}" must have a legal basis set (Consent, Legitimate Interest, or Strictly Necessary) for GDPR compliance.`},{rule:"li-description-required",field:"legitimateInterest.description",check:s=>s.legalBasis==="legitimate_interest"&&!s.legitimateInterest?.description,message:s=>`Cookie "${s}" uses Legitimate Interest but has no balancing test description. GDPR requires documenting your LI assessment.`}],warnings:[{rule:"li-description-recommended",field:"legitimateInterest.description",check:s=>s.legalBasis==="legitimate_interest"&&!s.legitimateInterest?.description,message:s=>`Cookie "${s}": adding a Legitimate Interest description improves transparency and is a GDPR best practice.`,suggestion:"Add a brief description of your legitimate interest balancing test."}]},"opt-out":{errors:[],warnings:[{rule:"gpc-recommended",field:"listenGpc",check:s=>s.legalBasis!=="mandatory"&&!s.listenGpc,message:s=>`Cookie "${s}" does not listen for the GPC signal. US state privacy laws require honoring GPC for opt-out of sale/sharing.`,suggestion:'Enable "Listen for GPC signal" on this cookie.'}]},"opt-out-strict":{errors:[{rule:"cpra-category-required",field:"cpraCategory",check:s=>s.legalBasis!=="mandatory"&&!s.cpraCategory,message:s=>`Cookie "${s}" must have a CPRA category (Sale, Sharing, or Sensitive) assigned. Required under CPRA for all non-necessary cookies.`}],warnings:[{rule:"gpc-strict-required",field:"listenGpc",check:s=>s.legalBasis!=="mandatory"&&!s.listenGpc,message:s=>`Cookie "${s}" does not listen for the GPC signal. Under CPRA, non-mandatory cookies must honor GPC as a Do Not Sell/Share signal.`,suggestion:'Enable "Listen for GPC signal" on this cookie.'}]},"opt-in-dpdpa":{errors:[{rule:"no-li-allowed",field:"legalBasis",check:s=>s.legalBasis==="legitimate_interest",message:s=>`Cookie "${s}" uses Legitimate Interest, which is not a valid legal basis under India's DPDPA. Use Consent or Strictly Necessary.`},{rule:"legal-basis-required",field:"legalBasis",check:s=>!s.legalBasis,message:s=>`Cookie "${s}" must have a legal basis set (Consent or Strictly Necessary) for DPDPA compliance.`}],warnings:[]},"opt-in-china":{errors:[{rule:"legal-basis-required",field:"legalBasis",check:s=>!s.legalBasis,message:s=>`Cookie "${s}" must have a legal basis set for China PIPL compliance. Consent is the primary valid basis.`}],warnings:[{rule:"li-limited-under-pipl",field:"legalBasis",check:s=>s.legalBasis==="legitimate_interest",message:s=>`Cookie "${s}" uses Legitimate Interest. Under China's PIPL, LI is narrowly scoped and data processing purposes must be strictly documented.`,suggestion:"Consider using Consent for this cookie to ensure PIPL compliance. Document the specific legitimate interest basis if retaining LI."}]},"opt-in-brazil":{errors:[{rule:"legal-basis-required",field:"legalBasis",check:s=>!s.legalBasis,message:s=>`Cookie "${s}" must have a legal basis set. Brazil's LGPD requires one of ten legal bases; Consent or Legitimate Interest are most common for cookies.`}],warnings:[{rule:"li-description-recommended",field:"legitimateInterest.description",check:s=>s.legalBasis==="legitimate_interest"&&!s.legitimateInterest?.description,message:s=>`Cookie "${s}" uses Legitimate Interest. LGPD requires documenting the basis and conducting an impact assessment.`,suggestion:"Add a Legitimate Interest description to document the LGPD basis and your impact assessment."}]},"general-privacy-consent":{errors:[],warnings:[{rule:"legal-basis-recommended",field:"legalBasis",check:s=>!s.legalBasis,message:s=>`Cookie "${s}" has no legal basis documented. While a strict cookie banner may not be legally required in this jurisdiction, documenting legal basis is a privacy best practice.`,suggestion:"Set a legal basis to improve compliance documentation and future-proof against stricter enforcement."}]},"notice-only":{errors:[],warnings:[]}},mt={version:"2026-07",countries:{AT:{name:"Austria",type:"country",complianceGroup:"opt-in",default:"opt-in",description:"Austria (GDPR)",locales:["de-AT"]},BE:{name:"Belgium",type:"country",complianceGroup:"opt-in",default:"opt-in",description:"Belgium (GDPR)",locales:["nl-BE","fr-BE"]},BG:{name:"Bulgaria",type:"country",complianceGroup:"opt-in",default:"opt-in",description:"Bulgaria (GDPR)",locales:["bg-BG"]},HR:{name:"Croatia",type:"country",complianceGroup:"opt-in",default:"opt-in",description:"Croatia (GDPR)",locales:["hr-HR"]},CY:{name:"Cyprus",type:"country",complianceGroup:"opt-in",default:"opt-in",description:"Cyprus (GDPR)",locales:["el-CY"]},CZ:{name:"Czechia",type:"country",complianceGroup:"opt-in",default:"opt-in",description:"Czechia (GDPR)",locales:["cs-CZ"]},DK:{name:"Denmark",type:"country",complianceGroup:"opt-in",default:"opt-in",description:"Denmark (GDPR)",locales:["da-DK"]},EE:{name:"Estonia",type:"country",complianceGroup:"opt-in",default:"opt-in",description:"Estonia (GDPR)",locales:["et-EE"]},FI:{name:"Finland",type:"country",complianceGroup:"opt-in",default:"opt-in",description:"Finland (GDPR)",locales:["fi-FI"]},FR:{name:"France",type:"country",complianceGroup:"opt-in",default:"opt-in",description:"France (GDPR)",locales:["fr-FR"]},DE:{name:"Germany",type:"country",complianceGroup:"opt-in",default:"opt-in",description:"Germany (GDPR)",locales:["de-DE"]},GR:{name:"Greece",type:"country",complianceGroup:"opt-in",default:"opt-in",description:"Greece (GDPR)",locales:["el-GR"]},HU:{name:"Hungary",type:"country",complianceGroup:"opt-in",default:"opt-in",description:"Hungary (GDPR)",locales:["hu-HU"]},IE:{name:"Ireland",type:"country",complianceGroup:"opt-in",default:"opt-in",description:"Ireland (GDPR)",locales:["en"]},IT:{name:"Italy",type:"country",complianceGroup:"opt-in",default:"opt-in",description:"Italy (GDPR)",locales:["it-IT"]},LV:{name:"Latvia",type:"country",complianceGroup:"opt-in",default:"opt-in",description:"Latvia (GDPR)",locales:["lv-LV"]},LT:{name:"Lithuania",type:"country",complianceGroup:"opt-in",default:"opt-in",description:"Lithuania (GDPR)",locales:["lt-LT"]},LU:{name:"Luxembourg",type:"country",complianceGroup:"opt-in",default:"opt-in",description:"Luxembourg (GDPR)",locales:["fr-LU","de-LU"]},MT:{name:"Malta",type:"country",complianceGroup:"opt-in",default:"opt-in",description:"Malta (GDPR)",locales:["mt-MT","en"]},NL:{name:"Netherlands",type:"country",complianceGroup:"opt-in",default:"opt-in",description:"Netherlands (GDPR)",locales:["nl-NL"]},PL:{name:"Poland",type:"country",complianceGroup:"opt-in",default:"opt-in",description:"Poland (GDPR)",locales:["pl-PL"]},PT:{name:"Portugal",type:"country",complianceGroup:"opt-in",default:"opt-in",description:"Portugal (GDPR)",locales:["pt-PT"]},RO:{name:"Romania",type:"country",complianceGroup:"opt-in",default:"opt-in",description:"Romania (GDPR)",locales:["ro-RO"]},SK:{name:"Slovakia",type:"country",complianceGroup:"opt-in",default:"opt-in",description:"Slovakia (GDPR)",locales:["sk-SK"]},SI:{name:"Slovenia",type:"country",complianceGroup:"opt-in",default:"opt-in",description:"Slovenia (GDPR)",locales:["sl-SI"]},ES:{name:"Spain",type:"country",complianceGroup:"opt-in",default:"opt-in",description:"Spain (GDPR)",locales:["es-ES"]},SE:{name:"Sweden",type:"country",complianceGroup:"opt-in",default:"opt-in",description:"Sweden (GDPR)",locales:["sv-SE"]},IS:{name:"Iceland",type:"country",complianceGroup:"opt-in",default:"opt-in",description:"Iceland (GDPR via EEA)",locales:["is-IS"]},LI:{name:"Liechtenstein",type:"country",complianceGroup:"opt-in",default:"opt-in",description:"Liechtenstein (GDPR via EEA)",locales:["de-LI"]},NO:{name:"Norway",type:"country",complianceGroup:"opt-in",default:"opt-in",description:"Norway (GDPR via EEA)",locales:["nb-NO"]},AD:{name:"Andorra",type:"country",complianceGroup:"opt-in",default:"opt-in",description:"Andorra (GDPR-aligned)",locales:["ca-AD"]},AL:{name:"Albania",type:"country",complianceGroup:"opt-in",default:"opt-in",description:"Albania (GDPR-aligned)",locales:["sq-AL"]},BA:{name:"Bosnia and Herzegovina",type:"country",complianceGroup:"opt-in",default:"opt-in",description:"Bosnia and Herzegovina (GDPR-aligned)",locales:["bs-BA","sr-Latn-BA"]},GG:{name:"Guernsey",type:"country",complianceGroup:"opt-in",default:"opt-in",description:"Guernsey (DPA based on GDPR)",locales:["en"]},GI:{name:"Gibraltar",type:"country",complianceGroup:"opt-in",default:"opt-in",description:"Gibraltar (DPA based on UK GDPR)",locales:["en"]},GL:{name:"Greenland",type:"country",complianceGroup:"opt-in",default:"opt-in",description:"Greenland (Danish GDPR implementation)",locales:["da-GL"]},MC:{name:"Monaco",type:"country",complianceGroup:"opt-in",default:"opt-in",description:"Monaco (GDPR-aligned)",locales:["fr-MC"]},ME:{name:"Montenegro",type:"country",complianceGroup:"opt-in",default:"opt-in",description:"Montenegro (GDPR-aligned)",locales:["sr-Latn-ME"]},MK:{name:"North Macedonia",type:"country",complianceGroup:"opt-in",default:"opt-in",description:"North Macedonia (GDPR-aligned)",locales:["mk-MK"]},RS:{name:"Serbia",type:"country",complianceGroup:"opt-in",default:"opt-in",description:"Serbia (GDPR-aligned)",locales:["sr-RS"]},SM:{name:"San Marino",type:"country",complianceGroup:"opt-in",default:"opt-in",description:"San Marino (GDPR-aligned)",locales:["it-SM"]},TR:{name:"T\xFCrkiye",type:"country",complianceGroup:"opt-in",default:"opt-in",description:"T\xFCrkiye (KVKK)",locales:["tr-TR"]},CA:{name:"Canada",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Canada (PIPEDA / Law 25)",locales:["en","fr-CA"],overriddenRegions:{QC:{name:"Quebec",type:"region",complianceGroup:"opt-in",description:"Quebec \u2014 Law 25 (GDPR-style opt-in)"}}},US:{name:"United States",type:"country",complianceGroup:"opt-out",default:"opt-out-strict",description:"United States \u2014 opt-out-strict (CPRA) used when state cannot be detected",locales:["en","es-US"],overriddenRegions:{CA:{name:"California",type:"region",complianceGroup:"opt-out-strict",description:"California \u2014 CPRA/CCPA"},VA:{name:"Virginia",type:"region",complianceGroup:"opt-out",description:"Virginia \u2014 VCDPA"},CO:{name:"Colorado",type:"region",complianceGroup:"opt-out",description:"Colorado \u2014 CPA"},CT:{name:"Connecticut",type:"region",complianceGroup:"opt-out",description:"Connecticut \u2014 CTDPA"},UT:{name:"Utah",type:"region",complianceGroup:"opt-out",description:"Utah \u2014 UCPA"},TX:{name:"Texas",type:"region",complianceGroup:"opt-out",description:"Texas \u2014 TDPSA"},DE:{name:"Delaware",type:"region",complianceGroup:"opt-out",description:"Delaware \u2014 DPDPA"},NJ:{name:"New Jersey",type:"region",complianceGroup:"opt-out",description:"New Jersey \u2014 NJDPA"},OR:{name:"Oregon",type:"region",complianceGroup:"opt-out",description:"Oregon \u2014 OCPA"},MN:{name:"Minnesota",type:"region",complianceGroup:"opt-out",description:"Minnesota \u2014 MCDPA"},MD:{name:"Maryland",type:"region",complianceGroup:"opt-out",description:"Maryland \u2014 MODPA"},KY:{name:"Kentucky",type:"region",complianceGroup:"opt-out",description:"Kentucky \u2014 KCDPA"},TN:{name:"Tennessee",type:"region",complianceGroup:"opt-out",description:"Tennessee \u2014 TIPA"},NE:{name:"Nebraska",type:"region",complianceGroup:"opt-out",description:"Nebraska \u2014 NDPA"},IA:{name:"Iowa",type:"region",complianceGroup:"opt-out",description:"Iowa \u2014 ICDPA"},IN:{name:"Indiana",type:"region",complianceGroup:"opt-out",description:"Indiana \u2014 ICDPA"},MT:{name:"Montana",type:"region",complianceGroup:"opt-out",description:"Montana \u2014 MCDPA"},NH:{name:"New Hampshire",type:"region",complianceGroup:"opt-out",description:"New Hampshire \u2014 NHPA"},RI:{name:"Rhode Island",type:"region",complianceGroup:"opt-out",description:"Rhode Island \u2014 RIDTPA"}}},AR:{name:"Argentina",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Argentina (LPDP)",locales:["es-AR"]},BO:{name:"Bolivia",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Bolivia",locales:["es-BO"]},BR:{name:"Brazil",type:"country",complianceGroup:"opt-in-brazil",default:"opt-in-brazil",description:"Brazil (LGPD)",locales:["pt-BR"]},CL:{name:"Chile",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Chile (Law 19.628)",locales:["es-CL"]},CO:{name:"Colombia",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Colombia (Law 1581)",locales:["es-CO"]},CR:{name:"Costa Rica",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Costa Rica (LPDP)",locales:["es-CR"]},DO:{name:"Dominican Republic",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Dominican Republic",locales:["es-DO"]},EC:{name:"Ecuador",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Ecuador (LOPDP)",locales:["es-EC"]},GF:{name:"French Guiana",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"French Guiana",locales:["fr-GF"]},GT:{name:"Guatemala",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Guatemala",locales:["es-GT"]},GY:{name:"Guyana",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Guyana",locales:["en"]},HN:{name:"Honduras",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Honduras",locales:["es-HN"]},HT:{name:"Haiti",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Haiti",locales:["fr-HT"]},JM:{name:"Jamaica",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Jamaica",locales:["en"]},MX:{name:"Mexico",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Mexico (LFPDPPP)",locales:["es-MX"]},NI:{name:"Nicaragua",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Nicaragua",locales:["es-NI"]},PA:{name:"Panama",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Panama",locales:["es-PA"]},PE:{name:"Peru",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Peru (LPDP)",locales:["es-PE"]},PY:{name:"Paraguay",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Paraguay",locales:["es-PY"]},SR:{name:"Suriname",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Suriname",locales:["nl-SR"]},SV:{name:"El Salvador",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"El Salvador",locales:["es-SV"]},TT:{name:"Trinidad and Tobago",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Trinidad and Tobago",locales:["en"]},UY:{name:"Uruguay",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Uruguay (Law 18.331)",locales:["es-UY"]},VE:{name:"Venezuela",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Venezuela",locales:["es-VE"]},AG:{name:"Antigua and Barbuda",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Antigua and Barbuda",locales:["en"]},BB:{name:"Barbados",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Barbados",locales:["en"]},BL:{name:"Saint Barth\xE9lemy",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Saint Barth\xE9lemy",locales:["fr-BL"]},BM:{name:"Bermuda",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Bermuda",locales:["en"]},BS:{name:"Bahamas",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Bahamas",locales:["en"]},CU:{name:"Cuba",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Cuba",locales:["es-CU"]},DM:{name:"Dominica",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Dominica",locales:["en"]},GD:{name:"Grenada",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Grenada",locales:["en"]},KN:{name:"Saint Kitts and Nevis",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Saint Kitts and Nevis",locales:["en"]},LC:{name:"Saint Lucia",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Saint Lucia",locales:["en"]},PM:{name:"Saint Pierre and Miquelon",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Saint Pierre and Miquelon",locales:["fr-PM"]},PR:{name:"Puerto Rico",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Puerto Rico",locales:["es-PR","en"]},VC:{name:"Saint Vincent and the Grenadines",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Saint Vincent and the Grenadines",locales:["en"]},IN:{name:"India",type:"country",complianceGroup:"opt-in-dpdpa",default:"opt-in-dpdpa",description:"India (DPDPA)",locales:["hi-IN","en","bn-IN","ta-IN"]},CN:{name:"China",type:"country",complianceGroup:"opt-in-china",default:"opt-in-china",description:"China (PIPL + DSL + CSL)",locales:["zh-CN"]},BN:{name:"Brunei",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Brunei (Personal Data Protection Order)",locales:["ms-BN","en"]},ID:{name:"Indonesia",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Indonesia (PDP Law)",locales:["id-ID"]},KH:{name:"Cambodia",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Cambodia",locales:["km-KH"]},LA:{name:"Laos",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Laos",locales:["lo-LA"]},MM:{name:"Myanmar",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Myanmar",locales:["my-MM"]},MY:{name:"Malaysia",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Malaysia (PDPA)",locales:["ms-MY","zh-MY","en"]},PH:{name:"Philippines",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Philippines (DPA)",locales:["fil-PH","en"]},SG:{name:"Singapore",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Singapore (PDPA)",locales:["zh-SG","ms-SG","ta-SG","en"]},TH:{name:"Thailand",type:"country",complianceGroup:"opt-in",default:"opt-in",description:"Thailand (PDPA \u2014 GDPR-style opt-in)",locales:["th-TH"]},TL:{name:"Timor-Leste",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Timor-Leste",locales:["pt-TL"]},VN:{name:"Vietnam",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Vietnam (PDPD)",locales:["vi-VN"]},HK:{name:"Hong Kong",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Hong Kong SAR (PDPO)",locales:["zh-HK"]},JP:{name:"Japan",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Japan (APPI)",locales:["ja-JP"]},KP:{name:"North Korea",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"North Korea",locales:["ko-KP"]},KR:{name:"South Korea",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"South Korea (PIPA)",locales:["ko-KR"]},MN:{name:"Mongolia",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Mongolia",locales:["mn-MN"]},MO:{name:"Macau",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Macau SAR (PDPA)",locales:["zh-MO","pt-MO"]},TW:{name:"Taiwan",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Taiwan (PDPL)",locales:["zh-TW"]},AF:{name:"Afghanistan",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Afghanistan",locales:["fa-AF"]},BD:{name:"Bangladesh",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Bangladesh",locales:["bn-BD"]},BT:{name:"Bhutan",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Bhutan",locales:["dz-BT"]},IO:{name:"British Indian Ocean Territory",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"British Indian Ocean Territory",locales:["en"]},LK:{name:"Sri Lanka",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Sri Lanka",locales:["si-LK","ta-LK"]},MV:{name:"Maldives",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Maldives",locales:["dv-MV"]},NP:{name:"Nepal",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Nepal",locales:["ne-NP"]},PK:{name:"Pakistan",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Pakistan",locales:["ur-PK","en"]},AM:{name:"Armenia",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Armenia",locales:["hy-AM"]},AZ:{name:"Azerbaijan",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Azerbaijan",locales:["az-AZ"]},GE:{name:"Georgia",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Georgia",locales:["ka-GE"]},KG:{name:"Kyrgyzstan",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Kyrgyzstan",locales:["ky-KG","ru-KG"]},KZ:{name:"Kazakhstan",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Kazakhstan",locales:["kk-KZ","ru-KZ"]},TJ:{name:"Tajikistan",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Tajikistan",locales:["tg-TJ"]},TM:{name:"Turkmenistan",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Turkmenistan",locales:["tk-TM"]},UZ:{name:"Uzbekistan",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Uzbekistan",locales:["uz-UZ"]},AE:{name:"United Arab Emirates",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"United Arab Emirates (UAE PDPL / DIFC DPL / ADGM DPR)",locales:["ar-AE"]},BH:{name:"Bahrain",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Bahrain (PDPL)",locales:["ar-BH"]},IL:{name:"Israel",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Israel (Privacy Protection Law)",locales:["he-IL","ar-IL"]},IQ:{name:"Iraq",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Iraq",locales:["ar-IQ"]},IR:{name:"Iran",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Iran",locales:["fa-IR"]},JO:{name:"Jordan",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Jordan",locales:["ar-JO"]},KW:{name:"Kuwait",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Kuwait",locales:["ar-KW"]},LB:{name:"Lebanon",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Lebanon",locales:["ar-LB"]},OM:{name:"Oman",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Oman (PDPL)",locales:["ar-OM"]},PS:{name:"Palestine",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Palestine",locales:["ar-PS"]},QA:{name:"Qatar",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Qatar (PDPL)",locales:["ar-QA"]},SA:{name:"Saudi Arabia",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Saudi Arabia (PDPL)",locales:["ar-SA"]},SY:{name:"Syria",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Syria",locales:["ar-SY"]},YE:{name:"Yemen",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Yemen",locales:["ar-YE"]},BF:{name:"Burkina Faso",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Burkina Faso",locales:["fr-BF"]},BJ:{name:"Benin",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Benin",locales:["fr-BJ"]},CI:{name:"C\xF4te d'Ivoire",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"C\xF4te d'Ivoire",locales:["fr-CI"]},CM:{name:"Cameroon",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Cameroon",locales:["fr-CM","en"]},CV:{name:"Cabo Verde",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Cabo Verde",locales:["pt-CV"]},GA:{name:"Gabon",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Gabon",locales:["fr-GA"]},GH:{name:"Ghana",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Ghana (DPA)",locales:["en"]},GM:{name:"Gambia",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Gambia",locales:["en"]},GN:{name:"Guinea",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Guinea",locales:["fr-GN"]},GQ:{name:"Equatorial Guinea",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Equatorial Guinea",locales:["es-GQ","fr-GQ"]},GW:{name:"Guinea-Bissau",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Guinea-Bissau",locales:["pt-GW"]},LR:{name:"Liberia",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Liberia",locales:["en"]},ML:{name:"Mali",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Mali",locales:["fr-ML"]},MR:{name:"Mauritania",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Mauritania",locales:["ar-MR"]},NE:{name:"Niger",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Niger",locales:["fr-NE"]},NG:{name:"Nigeria",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Nigeria (NDPA)",locales:["en"]},SL:{name:"Sierra Leone",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Sierra Leone",locales:["en"]},SN:{name:"Senegal",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Senegal",locales:["fr-SN"]},TG:{name:"Togo",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Togo",locales:["fr-TG"]},BI:{name:"Burundi",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Burundi",locales:["fr-BI"]},DJ:{name:"Djibouti",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Djibouti",locales:["fr-DJ","ar-DJ"]},ER:{name:"Eritrea",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Eritrea",locales:["ti-ER"]},ET:{name:"Ethiopia",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Ethiopia",locales:["am-ET"]},KE:{name:"Kenya",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Kenya (DPA)",locales:["sw-KE","en"]},KM:{name:"Comoros",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Comoros",locales:["ar-KM","fr-KM"]},MG:{name:"Madagascar",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Madagascar",locales:["fr-MG"]},MU:{name:"Mauritius",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Mauritius (DPA)",locales:["en","fr-MU"]},MW:{name:"Malawi",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Malawi",locales:["en"]},MZ:{name:"Mozambique",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Mozambique",locales:["pt-MZ"]},RW:{name:"Rwanda",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Rwanda (DPA)",locales:["rw-RW","en"]},SC:{name:"Seychelles",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Seychelles",locales:["fr-SC","en"]},SO:{name:"Somalia",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Somalia",locales:["so-SO"]},SS:{name:"South Sudan",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"South Sudan",locales:["en"]},ST:{name:"S\xE3o Tom\xE9 and Pr\xEDncipe",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"S\xE3o Tom\xE9 and Pr\xEDncipe",locales:["pt-ST"]},TZ:{name:"Tanzania",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Tanzania",locales:["sw-TZ","en"]},UG:{name:"Uganda",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Uganda (DPA)",locales:["en"]},AO:{name:"Angola",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Angola",locales:["pt-AO"]},BW:{name:"Botswana",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Botswana (DPA)",locales:["en"]},LS:{name:"Lesotho",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Lesotho",locales:["en"]},NA:{name:"Namibia",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Namibia",locales:["en"]},SZ:{name:"Eswatini",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Eswatini",locales:["en"]},ZA:{name:"South Africa",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"South Africa (POPIA)",locales:["en","af-ZA"]},ZM:{name:"Zambia",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Zambia (DPA)",locales:["en"]},ZW:{name:"Zimbabwe",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Zimbabwe (DPA)",locales:["en"]},DZ:{name:"Algeria",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Algeria",locales:["ar-DZ","fr-DZ"]},EG:{name:"Egypt",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Egypt (DPA)",locales:["ar-EG"]},LY:{name:"Libya",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Libya",locales:["ar-LY"]},MA:{name:"Morocco",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Morocco",locales:["ar-MA","fr-MA"]},SD:{name:"Sudan",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Sudan",locales:["ar-SD"]},TN:{name:"Tunisia",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Tunisia (DPA)",locales:["ar-TN","fr-TN"]},CD:{name:"DR Congo",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"DR Congo",locales:["fr-CD"]},CF:{name:"Central African Republic",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Central African Republic",locales:["fr-CF"]},CG:{name:"Republic of Congo",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Republic of Congo",locales:["fr-CG"]},TD:{name:"Chad",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Chad",locales:["fr-TD","ar-TD"]},BY:{name:"Belarus",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Belarus",locales:["be-BY","ru-BY"]},MD:{name:"Moldova",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Moldova",locales:["ro-MD"]},RU:{name:"Russia",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Russia",locales:["ru-RU"]},UA:{name:"Ukraine",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Ukraine",locales:["uk-UA"]},VA:{name:"Holy See",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Holy See",locales:["it-VA"]},AU:{name:"Australia",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Australia (Privacy Act)",locales:["en"]},FJ:{name:"Fiji",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Fiji",locales:["en"]},FM:{name:"Micronesia",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Micronesia",locales:["en"]},KI:{name:"Kiribati",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Kiribati",locales:["en"]},MH:{name:"Marshall Islands",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Marshall Islands",locales:["en"]},MP:{name:"Northern Mariana Islands",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Northern Mariana Islands",locales:["en"]},NC:{name:"New Caledonia",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"New Caledonia",locales:["fr-NC"]},NF:{name:"Norfolk Island",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Norfolk Island",locales:["en"]},NR:{name:"Nauru",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Nauru",locales:["en"]},NU:{name:"Niue",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Niue",locales:["en"]},NZ:{name:"New Zealand",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"New Zealand (Privacy Act)",locales:["en"]},PF:{name:"French Polynesia",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"French Polynesia",locales:["fr-PF"]},PG:{name:"Papua New Guinea",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Papua New Guinea",locales:["en"]},PW:{name:"Palau",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Palau",locales:["en"]},SB:{name:"Solomon Islands",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Solomon Islands",locales:["en"]},TO:{name:"Tonga",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Tonga",locales:["en"]},TV:{name:"Tuvalu",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Tuvalu",locales:["en"]},VU:{name:"Vanuatu",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Vanuatu",locales:["fr-VU","en"]},WS:{name:"Samoa",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Samoa",locales:["en"]},UK:{name:"United Kingdom",type:"country",complianceGroup:"opt-in",default:"opt-in",description:"United Kingdom (UK GDPR + PECR)",locales:["en"]},CH:{name:"Switzerland",type:"country",complianceGroup:"opt-in",default:"opt-in",description:"Switzerland (revFADP)",locales:["de-CH","fr-CH","it-CH"]}}},xl=Object.entries(mt.countries).reduce(function(s,[e,t]){return s[e]=t.name,s},{}),Fl=Object.values(mt.countries).reduce(function(s,e){return e.locales.forEach(t=>{s[t]=e.name}),s},{});function xi(s){if(!s||typeof s!="object")return{valid:!1,error:"Request body must be a JSON object"};let e=s;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.cookieTemplateId!="string"||!t.cookieTemplateId?{valid:!1,error:"profileJson.cookieTemplateId is required"}:typeof t.uiTemplateId!="string"||!t.uiTemplateId?{valid:!1,error:"profileJson.uiTemplateId is required"}:{valid:!0}}function Fi(s){return!s||typeof s!="object"?{valid:!1,error:"Request body must be a JSON object"}:{valid:!0}}function Hi(s,e){return{tenantId:e,name:s.name,defaultLocale:s.defaultLocale,profileJson:s.profileJson}}function ji(s){let e={};return typeof s.name=="string"&&(e.name=s.name),typeof s.defaultLocale=="string"&&(e.defaultLocale=s.defaultLocale),s.profileJson!=null&&(e.profileJson=s.profileJson),e}function le(s,e){let t=Gi[e];if(!t)return{valid:!0,errors:[],warnings:[]};let n=[],i=[];for(let o of s){let r=o;for(let a of t.errors)a.check(r)&&n.push({cookieId:o.id,field:a.field,message:a.message(o.id),rule:a.rule});for(let a of t.warnings)a.check(r)&&i.push({cookieId:o.id,field:a.field,message:a.message(o.id),suggestion:a.suggestion})}return{valid:n.length===0,errors:n,warnings:i}}function $i(s,e,t,n){async function i(r,a){let c=await v(r,e,t,n);return{user:c,denied:b(c,a)}}function o(r,a){return r?r.allowedTenants.length===0||r.allowedTenants.includes(a):!1}return{"GET /profiles":async(r,a)=>g(async()=>{let{user:c,denied:l}=await i(r,"profile:view");return l||(o(c,"default")?new URL(r.url).searchParams.get("summary")==="1"?y(200,await s.listSummary()):y(200,await s.list()):y(200,[]))}),"GET /profiles/:id":async(r,a)=>g(async()=>{let{denied:c}=await i(r,"profile:view");if(c)return c;let l=await s.get(a.id??"");return l?y(200,l):m(404,"Profile not found")}),"POST /profiles/validate":async(r,a)=>g(async()=>{let{denied:c}=await i(r,"profile:view");if(c)return c;let l=await I(r);return!l.complianceGroup||!Array.isArray(l.cookies)?m(400,"complianceGroup and cookies are required"):y(200,le(l.cookies,l.complianceGroup))}),"GET /compliance-coverage":async(r,a)=>g(async()=>{let{denied:c}=await i(r,"profile:view");if(c)return c;let l={};for(let[u,d]of Object.entries(Mi)){let p=await s.findActiveByComplianceGroup(u);l[u]={label:d.label,compliances:d.compliances,activeProfile:p?{id:p.id,name:p.name}:null}}return y(200,{groups:l})}),"POST /profiles":async(r,a)=>g(async()=>{let{denied:c}=await i(r,"profile:create");if(c)return c;let l=await I(r),u=xi(l);if(!u.valid)return m(400,u.error??"Invalid body");let d=Hi(l,"default"),p=d.profileJson.complianceGroup,E=typeof l.choice=="string"?l.choice:void 0;if(p){let f=d.profileJson.cookies??[],_=le(f,p);if(!_.valid)return m(422,"Profile does not meet compliance requirements",{errors:_.errors,warnings:_.warnings});let h=await s.findActiveByComplianceGroup(p);if(h){if(!E)return y(200,{conflict:{id:h.id,name:h.name},requiresChoice:!0});E==="deactivate"&&await s.deactivate(h.id)}let T=await s.create(d);return y(201,{profile:T,warnings:_.warnings})}let A=await s.create(d);return y(201,A)}),"PUT /profiles/:id":async(r,a)=>g(async()=>{let{denied:c}=await i(r,"profile:update");if(c)return c;let l=a.id??"",u=await I(r),d=Fi(u);if(!d.valid)return m(400,d.error??"Invalid body");let p=ji(u),E=p.profileJson?.complianceGroup,A=typeof u.choice=="string"?u.choice:void 0;if(E){let _=p.profileJson?.cookies??[],h=le(_,E);if(!h.valid)return m(422,"Profile does not meet compliance requirements",{errors:h.errors,warnings:h.warnings});let T=await s.findActiveByComplianceGroup(E);if(T&&T.id!==l){if(!A)return y(200,{conflict:{id:T.id,name:T.name},requiresChoice:!0});A==="deactivate"&&await s.deactivate(T.id)}let C=await s.update(l,p);return y(200,{profile:C,warnings:h.warnings})}let f=await s.update(l,p);return y(200,f)}),"DELETE /profiles/:id":async(r,a)=>g(async()=>{let{denied:c}=await i(r,"profile:delete");return c||(await s.delete(a.id??""),y(200,{success:!0}))}),"POST /profiles/:id/activate":async(r,a)=>g(async()=>{let{denied:c}=await i(r,"profile:update");if(c)return c;let l=a.id??"",u=await I(r),d=typeof u?.choice=="string"?u.choice:void 0,p=await s.get(l);if(!p)return m(404,"Profile not found");let E=p.profileJson.complianceGroup;if(E){let f=await s.findActiveByComplianceGroup(E);if(f&&f.id!==l){if(!d)return y(200,{conflict:{id:f.id,name:f.name},requiresChoice:!0});d==="deactivate"&&await s.deactivate(f.id)}}let A=await s.activate(l);return y(200,A)}),"POST /profiles/:id/deactivate":async(r,a)=>g(async()=>{let{denied:c}=await i(r,"profile:update");if(c)return c;let l=await s.deactivate(a.id??"");return y(200,l)}),"GET /profiles/:id/versions":async(r,a)=>g(async()=>{let{denied:c}=await i(r,"profile:view");return c||y(200,s.listVersions(a.id??""))}),"GET /profiles/:id/versions/:version":async(r,a)=>g(async()=>{let{denied:c}=await i(r,"profile:view");if(c)return c;let u=new URL(r.url).searchParams.get("locale")??"default",d=parseInt(a.version??"0",10);if(isNaN(d)||d<1)return m(400,"Invalid version number");let p=s.getVersionFile(a.id??"",d,u);return p?new Response(p,{headers:{"Content-Type":"application/json"}}):m(404,"Version file not found")})}}function Bi(s,e,t){async function n(i,o){let r=await v(i,s,e,t);return{user:r,denied:b(r,o)}}return{"GET /consents":async(i,o)=>g(async()=>{let{user:r,denied:a}=await n(i,"consent:view");if(a)return a;if(r?.allowedTenants.length&&!r.allowedTenants.includes("default"))return y(200,{items:[],total:0,page:1,limit:50});let c=new URL(i.url),l=k(c,"profileId"),u=k(c,"from"),d=k(c,"to"),p=B(c,"limit",50);if(p>500)return m(400,"limit must not exceed 500");let E={tenantId:"default",page:B(c,"page",1),limit:p,...l!==void 0?{profileId:l}:{},...u!==void 0?{from:u}:{},...d!==void 0?{to:d}:{}},A=await s.getConsents(E);return y(200,A)}),"GET /consents/:visitorId":async(i,o)=>g(async()=>{let{user:r,denied:a}=await n(i,"consent:view");if(a)return a;if(r?.allowedTenants.length&&!r.allowedTenants.includes("default"))return m(403,"Forbidden");let c=await s.getConsent(o.visitorId??"");return c?y(200,c):m(404,"Consent not found")}),"GET /consents/:visitorId/history":async(i,o)=>g(async()=>{let{user:r,denied:a}=await n(i,"consent:view");if(a)return a;if(r?.allowedTenants.length&&!r.allowedTenants.includes("default"))return m(403,"Forbidden");let c=await s.getConsentHistory(o.visitorId??"");return y(200,c)})}}function Vi(s,e,t){async function n(i,o){let r=await v(i,s,e,t);return{user:r,denied:b(r,o)}}return{"GET /visitors":async(i,o)=>g(async()=>{let{user:r,denied:a}=await n(i,"visitor:view");if(a)return a;if(r?.allowedTenants.length&&!r.allowedTenants.includes("default"))return y(200,{items:[],total:0,page:1,limit:50});let c=new URL(i.url),l=k(c,"from"),u=k(c,"to"),d=B(c,"limit",50);if(d>500)return m(400,"limit must not exceed 500");let p={tenantId:"default",page:B(c,"page",1),limit:d,...l!==void 0?{from:l}:{},...u!==void 0?{to:u}:{}},E=await s.getVisitors(p);return y(200,E)})}}function Wi(s,e,t,n){async function i(o,r){let a=await v(o,e,t,n);return{user:a,denied:b(a,r)}}return{"GET /users":async(o,r)=>g(async()=>{let{denied:a}=await i(o,"user:view");if(a)return a;let c=await s.list();return y(200,c)}),"GET /users/:id":async(o,r)=>g(async()=>{let{denied:a}=await i(o,"user:view");if(a)return a;let c=await s.get(r.id??"");if(!c)return m(404,"User not found");let l=await s.getRoles(c.id);return y(200,{...c,roles:l.map(u=>({id:u.id,name:u.name}))})}),"POST /users":async(o,r)=>g(async()=>{let{user:a,denied:c}=await i(o,"user:create");if(c)return c;let l=await I(o);if(!l||typeof l!="object")return m(400,"Invalid body");let u=l;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 d=await s.create({name:u.name,email:u.email,password:u.password,...typeof u.roleId=="string"?{roleId:u.roleId}:{},...Array.isArray(u.allowedTenants)?{allowedTenants:u.allowedTenants}:{}},a.sub);return y(201,d)}),"PUT /users/:id":async(o,r)=>g(async()=>{let{user:a,denied:c}=await i(o,"user:update");if(c)return c;let l=r.id??"",u=await I(o);if(!u||typeof u!="object")return m(400,"Invalid body");let d=u;if(typeof d.password=="string"&&d.password.length<12)return m(400,"password must be at least 12 characters");let p=await s.update(l,{...typeof d.name=="string"?{name:d.name}:{},...typeof d.email=="string"?{email:d.email}:{},...d.isActive!=null?{isActive:!!d.isActive}:{},...typeof d.password=="string"?{password:d.password}:{},...Array.isArray(d.allowedTenants)?{allowedTenants:d.allowedTenants}:{}},a.sub);return y(200,p)}),"DELETE /users/:id":async(o,r)=>g(async()=>{let{user:a,denied:c}=await i(o,"user:delete");if(c)return c;let l=r.id??"";return l===a.sub?m(400,"Cannot delete your own account"):(await s.delete(l,a.sub),y(200,{success:!0}))}),"POST /users/:id/roles":async(o,r)=>g(async()=>{let{user:a,denied:c}=await i(o,"role:update");if(c)return c;let l=await I(o);if(!l||typeof l!="object")return m(400,"Invalid body");let u=l;if(typeof u.roleId!="string")return m(400,"roleId is required");let d=typeof u.tenantId=="string"?u.tenantId:void 0;return await s.assignRole(r.id??"",u.roleId,d),await e.createLog({tenantId:"default",userId:a.sub,action:"user.role_assigned",resourceType:"user",...r.id!=null?{resourceId:r.id}:{},newData:{roleId:u.roleId}}),y(200,{success:!0})}),"DELETE /users/:id/roles/:roleId":async(o,r)=>g(async()=>{let{user:a,denied:c}=await i(o,"role:update");if(c)return c;let u=new URL(o.url).searchParams.get("tenantId")??void 0;return await s.revokeRole(r.id??"",r.roleId??"",u),await e.createLog({tenantId:"default",userId:a.sub,action:"user.role_revoked",resourceType:"user",...r.id!=null?{resourceId:r.id}:{},oldData:{roleId:r.roleId}}),y(200,{success:!0})})}}function Ji(s,e,t){async function n(i,o){let r=await v(i,s,e,t);return{user:r,denied:b(r,o)}}return{"GET /roles":async(i,o)=>g(async()=>{let{denied:r}=await n(i,"role:view");if(r)return r;let a=await s.getRoles("default");return y(200,a)}),"GET /roles/:id/permissions":async(i,o)=>g(async()=>{let{denied:r}=await n(i,"role:view");if(r)return r;let a=await s.getPermissionsForRole(o.id??"");return y(200,a)}),"POST /roles":async(i,o)=>g(async()=>{let{user:r,denied:a}=await n(i,"role:create");if(a)return a;let c=await I(i);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 u=await s.createRole({tenantId:"default",name:l.name,...typeof l.description=="string"?{description:l.description}:{}});return await s.createLog({tenantId:"default",userId:r.sub,action:"role.created",resourceType:"role",resourceId:u.id,newData:u}),y(201,u)}),"PUT /roles/:id":async(i,o)=>g(async()=>{let{user:r,denied:a}=await n(i,"role:update");if(a)return a;let c=await I(i);if(!c||typeof c!="object")return m(400,"Invalid body");let l=c,u=await s.updateRole(o.id??"",{...typeof l.name=="string"?{name:l.name}:{},...typeof l.description=="string"?{description:l.description}:{}});return await s.createLog({tenantId:"default",userId:r.sub,action:"role.updated",resourceType:"role",resourceId:u.id,newData:u}),y(200,u)}),"DELETE /roles/:id":async(i,o)=>g(async()=>{let{user:r,denied:a}=await n(i,"role:delete");return a||(await s.deleteRole(o.id??""),await s.createLog({tenantId:"default",userId:r.sub,action:"role.deleted",resourceType:"role",...o.id!=null?{resourceId:o.id}:{}}),y(200,{success:!0}))}),"GET /permissions":async(i,o)=>g(async()=>{let{denied:r}=await n(i,"role:view");if(r)return r;let a=await s.getAllPermissions();return y(200,a)}),"POST /roles/:id/permissions":async(i,o)=>g(async()=>{let{denied:r}=await n(i,"role:update");if(r)return r;let a=await I(i);if(!a||typeof a!="object")return m(400,"Invalid body");let c=a;return typeof c.permissionId!="string"?m(400,"permissionId is required"):(await s.assignPermissionToRole(o.id??"",c.permissionId),y(200,{success:!0}))}),"DELETE /roles/:id/permissions/:permissionId":async(i,o)=>g(async()=>{let{denied:r}=await n(i,"role:update");return r||(await s.revokePermissionFromRole(o.id??"",o.permissionId??""),y(200,{success:!0}))})}}function qi(s,e,t){async function n(i,o){let r=await v(i,s,e,t);return{user:r,denied:b(r,o)}}return{"GET /audit":async(i,o)=>g(async()=>{let{user:r,denied:a}=await n(i,"audit:view");if(a)return a;if(r?.allowedTenants.length&&!r.allowedTenants.includes("default"))return y(200,{items:[],total:0,page:1,limit:50});let c=new URL(i.url),l=k(c,"action"),u=k(c,"resourceType"),d=k(c,"from"),p=k(c,"to"),E=B(c,"limit",50);if(E>500)return m(400,"limit must not exceed 500");let A={tenantId:"default",page:B(c,"page",1),limit:E,...l!==void 0?{action:l}:{},...u!==void 0?{resourceType:u}:{},...d!==void 0?{from:d}:{},...p!==void 0?{to:p}:{}},f=await s.getLogs(A);return y(200,f)})}}function Ki(s,e,t){async function n(i){let o=await v(i,s,e,t);return{denied:b(o,"consent:view")}}return{"GET /stats/overview":async(i,o)=>g(async()=>{let{denied:r}=await n(i);if(r)return r;let a=await s.getOverviewStats("default");return y(200,a)}),"GET /stats/categories":async(i,o)=>g(async()=>{let{denied:r}=await n(i);if(r)return r;let a=await s.getCategoryStats("default");return y(200,a)}),"GET /stats/timeline":async(i,o)=>g(async()=>{let{denied:r}=await n(i);if(r)return r;let a=new URL(i.url),c=B(a,"days",30),l=await s.getTimeline("default",c);return y(200,l)}),"GET /stats/countries":async(i,o)=>g(async()=>{let{denied:r}=await n(i);if(r)return r;let a=await s.getCountries("default");return y(200,a)}),"GET /stats/gpc":async(i,o)=>g(async()=>{let{denied:r}=await n(i);if(r)return r;let a=await s.getGpcStats("default");return y(200,a)}),"GET /stats/aggregate":async(i,o)=>g(async()=>{let{denied:r}=await n(i);if(r)return r;let a=await s.getTenants(),c=await Promise.all(a.map(async u=>{let d=await s.getOverviewStats(u.id);return{tenantId:u.id,tenantName:u.name,...d}})),l=c.reduce((u,d)=>({totalConsents:u.totalConsents+d.totalConsents,totalVisitors:u.totalVisitors+d.totalVisitors,gpcDetectedCount:u.gpcDetectedCount+d.gpcDetectedCount}),{totalConsents:0,totalVisitors:0,gpcDetectedCount:0});return y(200,{aggregate:l,perTenant:c})})}}function Ft(s){let e=s==null?"":String(s);return e.includes(",")||e.includes('"')||e.includes(`
|
|
162
|
-
`)?`"${e.replace(/"/g,'""')}"`:e}function
|
|
163
|
-
`}function
|
|
164
|
-
`}function
|
|
165
|
-
`,f=new ReadableStream({async start(
|
|
166
|
-
`,{status:200,headers:{"Content-Type":"text/csv; charset=utf-8","Content-Disposition":`attachment; filename="${
|
|
150
|
+
</EntityDescriptor>`}async function Ai(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(d=>typeof d=="string"):[];return{email:o,roles:c}}catch(n){return console.warn("[Consenti] SAML response validation failed:",n),null}}import{createHmac as Ea,randomBytes as Ra,timingSafeEqual as Sa}from"crypto";var dn="ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";function Ca(i){let e="",t=0,n=0;for(let s of i)for(n=n<<8|s,t+=8;t>=5;)e+=dn[n>>>t-5&31],t-=5;return t>0&&(e+=dn[n<<5-t&31]),e}function Aa(i){let e=i.toUpperCase().replace(/[^A-Z2-7]/g,""),t=[],n=0,s=0;for(let r of e){let o=dn.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 Ia(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=Ea("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 Ii(){return Ca(Ra(20))}var bt=new Map;setInterval(()=>{let i=Date.now();for(let[e,t]of bt)i>=t&&bt.delete(e)},9e4).unref();function Ti(i,e,t=1){if(bt.has(e))return!1;let n=Math.floor(Date.now()/3e4),s=Aa(i),r=Buffer.from(e);for(let o=-t;o<=t;o++){let a=Buffer.from(Ia(s,n+o));if(a.length===r.length&&Sa(a,r))return bt.set(e,Date.now()+9e4),!0}return!1}function vi(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 Oi(i){return i.toString("base64").replace(/\+/g,"-").replace(/\//g,"_").replace(/=/g,"")}function bi(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)=>_(async()=>{let c=r(o);if(c)return c;if(t.mode!=="local")return m(400,"Login not available in this auth mode");let d=await O(o);if(!d||typeof d!="object")return m(400,"Invalid body");let l=d;if(typeof l.email!="string"||typeof l.password!="string")return m(400,"email and password are required");let u=await i.login(l.email,l.password);return u?y(200,{token:u}):m(401,"Invalid credentials")}),"GET /auth/me":async(o,a)=>_(async()=>{let c=await P(o,e,t,n);if(!c)return m(401,"Unauthorized");let d=await e.getUserById(c.sub);if(!d)return m(401,"User not found");let l=await e.getUserRoles(d.id);return y(200,{id:d.id,email:d.email,name:d.name,isActive:d.isActive,totpEnabled:d.totpEnabled??!1,roles:l.map(u=>u.name),permissions:c.permissions})}),"POST /auth/logout":async(o,a)=>_(async()=>y(200,{success:!0})),"POST /auth/refresh":async(o,a)=>_(async()=>{let c=await P(o,e,t,n);if(!c)return m(401,"Unauthorized");let d=i.signToken({sub:c.sub,email:c.email,roles:c.roles,permissions:c.permissions,allowedTenants:c.allowedTenants});return y(200,{token:d})}),"GET /auth/oidc/authorize":async(o,a)=>_(async()=>{if(t.mode!=="oidc"||!t.oidc)return m(400,"OIDC not configured");let c=await ln(t.oidc.issuer),d=Oi(Pi(16)),l=Oi(Pi(16)),{verifier:u,challenge:p}=fi();Ri(d,u,l);let h=_i(t.oidc,c.authorization_endpoint,d,l,p);return new Response(null,{status:302,headers:{Location:h}})}),"GET /auth/oidc/callback":async(o,a)=>_(async()=>{if(t.mode!=="oidc"||!t.oidc)return m(400,"OIDC not configured");let c=new URL(o.url),d=c.searchParams.get("code"),l=c.searchParams.get("state");if(!d||!l)return m(400,"Missing code or state");let u=Si(l);if(!u)return m(400,"Invalid or expired state");let p=await ln(t.oidc.issuer),h=await hi(t.oidc,p.token_endpoint,d,u.verifier),E=await yi(h.id_token,p.jwks_uri),g=wi(E,t.oidc.claimsMapping),f=Ei(E,t.oidc.claimsMapping),w=await e.getUserByEmail(g);if(w||(w=await e.createUser({tenantId:"default",name:g,email:g,passwordHash:""})),f.length>0){let v=await e.getRoles("default");for(let j of f){let F=v.find(Ue=>Ue.name===j);F&&await e.assignRole(w.id,F.id).catch(()=>{})}}let R=await e.getUserRoles(w.id),I=[];for(let v of R){let j=await e.getPermissionsForRole(v.id);I.push(...j.map(F=>F.name))}let T=i.signToken({sub:w.id,email:w.email,roles:R.map(v=>v.name),permissions:[...new Set(I)]});return y(200,{token:T})}),"GET /auth/saml/metadata":async(o,a)=>_(async()=>{if(t.mode!=="saml"||!t.saml)return m(400,"SAML not configured");let c=Ci(t.saml);return new Response(c,{status:200,headers:{"Content-Type":"application/xml"}})}),"POST /auth/saml/acs":async(o,a)=>_(async()=>{let c=r(o);if(c)return c;if(t.mode!=="saml"||!t.saml)return m(400,"SAML not configured");let l=new URLSearchParams(await o.text()).get("SAMLResponse");if(typeof l!="string")return m(400,"Missing SAMLResponse");let u=await Ai(l,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 h=await e.getUserRoles(p.id),E=[];for(let f of h){let w=await e.getPermissionsForRole(f.id);E.push(...w.map(R=>R.name))}let g=i.signToken({sub:p.id,email:p.email,roles:h.map(f=>f.name),permissions:[...new Set(E)]});return y(200,{token:g})}),"POST /auth/totp/setup":async(o,a)=>_(async()=>{let c=await P(o,e,t,n);if(!c)return m(401,"Unauthorized");let d=await e.getUserById(c.sub);if(!d)return m(401,"User not found");let l=Ii(),u=vi(l,d.email);return await e.updateUser(d.id,{totpSecret:l,totpEnabled:!1}),y(200,{qrUrl:u,message:"Scan QR code, then verify with POST /auth/totp/verify"})}),"POST /auth/totp/verify":async(o,a)=>_(async()=>{let c=r(o);if(c)return c;let d=await P(o,e,t,n);if(!d)return m(401,"Unauthorized");let l=await e.getUserById(d.sub);if(!l)return m(401,"User not found");if(!l.totpSecret)return m(400,"TOTP not set up \u2014 call POST /auth/totp/setup first");let u=await O(o);if(!u||typeof u!="object")return m(400,"Invalid body");let p=u;return typeof p.token!="string"?m(400,"token is required"):Ti(l.totpSecret,p.token)?(await e.updateUser(l.id,{totpEnabled:!0}),y(200,{success:!0,message:"TOTP enabled successfully"})):m(400,"Invalid TOTP token")}),"POST /auth/totp/disable":async(o,a)=>_(async()=>{let c=await P(o,e,t,n);if(!c)return m(401,"Unauthorized");let d=await e.getUserById(c.sub);return d?(await e.updateUser(d.id,{totpSecret:null,totpEnabled:!1}),y(200,{success:!0,message:"TOTP disabled"})):m(401,"User not found")})}}function Di(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 Li(i){return!i||typeof i!="object"?{valid:!1,error:"Request body must be a JSON object"}:{valid:!0}}function Ui(i,e){return{tenantId:e,name:i.name,defaultLocale:i.defaultLocale,profileJson:i.profileJson,...i.localeContent!=null?{localeContent:i.localeContent}:{}}}function Ni(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 pe(i,e,t){let n=On[t];if(!n)return{valid:!0,errors:[],warnings:[]};let s=[],r=[],o=fe(e);for(let[a,c]of Object.entries(e)){let d=c;for(let l of n.categoryErrors)l.check(d,void 0)&&s.push({cookieId:a,field:l.field,message:l.message(a),rule:l.rule});for(let l of n.categoryWarnings)l.check(d,void 0)&&r.push({cookieId:a,field:l.field,message:l.message(a),suggestion:l.suggestion})}for(let[a,c]of Object.entries(i)){let d=c,l=je(a,o);for(let u of n.cookieErrors)u.check(d,l)&&s.push({cookieId:a,field:u.field,message:u.message(a),rule:u.rule});for(let u of n.cookieWarnings)u.check(d,l)&&r.push({cookieId:a,field:u.field,message:u.message(a),suggestion:u.suggestion})}return{valid:s.length===0,errors:s,warnings:r}}function ki(i,e,t){let n=[];Dn(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 Ta(i,e){let t=[];e.heading?.trim()||t.push({locale:i,section:"preferenceModal",field:"heading",message:"Heading 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 un(i){let e=[];for(let[t,n]of Object.entries(i))e.push(...ki(t,"mainBanner",n.mainBanner)),n.gpcBanner&&e.push(...ki(t,"gpcBanner",n.gpcBanner)),e.push(...Ta(t,n.preferenceModal));return{valid:e.length===0,errors:e}}function Mi(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 xi(i,e,t,n){async function s(o,a){let c=await P(o,e,t,n);return{user:c,denied:b(c,a)}}function r(o,a){return o?o.allowedTenants.length===0||o.allowedTenants.includes(a):!1}return{"GET /profiles":async(o,a)=>_(async()=>{let{user:c,denied:d}=await s(o,"profile:view");return d||(r(c,"default")?new URL(o.url).searchParams.get("summary")==="1"?y(200,await i.listSummary()):y(200,await i.list()):y(200,[]))}),"GET /profiles/archived":async(o,a)=>_(async()=>{let{denied:c}=await s(o,"profile:view");return c||y(200,await i.listArchivedProfiles())}),"GET /profiles/:id":async(o,a)=>_(async()=>{let{denied:c}=await s(o,"profile:view");if(c)return c;let d=await i.get(a.id??"");return d?y(200,d):m(404,"Profile not found")}),"POST /profiles/validate":async(o,a)=>_(async()=>{let{denied:c}=await s(o,"profile:view");if(c)return c;let d=await O(o);return!d.complianceGroup||!d.cookies||typeof d.cookies!="object"||!d.categories||typeof d.categories!="object"?m(400,"complianceGroup, cookies, and categories are required"):y(200,pe(d.cookies,d.categories,d.complianceGroup))}),"GET /compliance-coverage":async(o,a)=>_(async()=>{let{denied:c}=await s(o,"profile:view");if(c)return c;let d={};for(let[l,u]of Object.entries($e)){let p=await i.findActiveByComplianceGroup(l);d[l]={label:u.label,compliances:u.compliances,activeProfile:p?{id:p.id,name:p.name}:null}}return y(200,{groups:d})}),"POST /profiles":async(o,a)=>_(async()=>{let{denied:c}=await s(o,"profile:create");if(c)return c;let d=await O(o),l=Di(d);if(!l.valid)return m(400,l.error??"Invalid body");let u=Ui(d,"default"),p=un(Mi(u.profileJson,u.localeContent));if(!p.valid)return m(422,"Profile content is missing required fields",{errors:p.errors});let h=u.profileJson.complianceGroup,E=u.profileJson.customComplianceGroup,g=typeof d.choice=="string"?d.choice:void 0;if(h){let w=u.profileJson.cookies??{},R=u.profileJson.preferenceModal?.categories??{},I=pe(w,R,h);if(!I.valid)return m(422,"Profile does not meet compliance requirements",{errors:I.errors,warnings:I.warnings});let T=await i.findActiveByComplianceGroup(h);if(T){if(!g)return y(200,{conflict:{id:T.id,name:T.name},requiresChoice:!0});g==="deactivate"&&await i.deactivate(T.id)}let v=await i.create(u);return y(201,{profile:v,warnings:I.warnings})}if(E){let w=await i.findActiveByComplianceGroup(E);if(w){if(!g)return y(200,{conflict:{id:w.id,name:w.name},requiresChoice:!0});g==="deactivate"&&await i.deactivate(w.id)}}let f=await i.create(u);return y(201,f)}),"PUT /profiles/:id":async(o,a)=>_(async()=>{let{denied:c}=await s(o,"profile:update");if(c)return c;let d=a.id??"",l=await O(o),u=Li(l);if(!u.valid)return m(400,u.error??"Invalid body");let p=Ni(l),h=Mi(p.profileJson,p.localeContent);if(Object.keys(h).length>0){let R=un(h);if(!R.valid)return m(422,"Profile content is missing required fields",{errors:R.errors})}let E=p.profileJson?.complianceGroup,g=p.profileJson?.customComplianceGroup,f=typeof l.choice=="string"?l.choice:void 0;if(E){let R=p.profileJson?.cookies??{},I=p.profileJson?.preferenceModal?.categories??{},T=pe(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&&v.id!==d){if(!f)return y(200,{conflict:{id:v.id,name:v.name},requiresChoice:!0});f==="deactivate"&&await i.deactivate(v.id)}let j=await i.update(d,p);return y(200,{profile:j,warnings:T.warnings})}if(g){let R=await i.findActiveByComplianceGroup(g);if(R&&R.id!==d){if(!f)return y(200,{conflict:{id:R.id,name:R.name},requiresChoice:!0});f==="deactivate"&&await i.deactivate(R.id)}}let w=await i.update(d,p);return y(200,w)}),"DELETE /profiles/:id":async(o,a)=>_(async()=>{let{denied:c}=await s(o,"profile:delete");return c||(await i.delete(a.id??""),y(200,{success:!0}))}),"POST /profiles/:id/copy":async(o,a)=>_(async()=>{let{denied:c}=await s(o,"profile:create");if(c)return c;let d=a.id??"";if(!await i.get(d))return m(404,"Profile not found");let u=await O(o),p=typeof u?.name=="string"?u.name:void 0,h=await i.copy(d,p);return y(201,h)}),"POST /profiles/:id/activate":async(o,a)=>_(async()=>{let{denied:c}=await s(o,"profile:update");if(c)return c;let d=a.id??"",l=await O(o),u=typeof l?.choice=="string"?l.choice:void 0,p=await i.get(d);if(!p)return m(404,"Profile not found");let h=p.profileJson,E=h.complianceGroup||h.customComplianceGroup;if(E){let f=await i.findActiveByComplianceGroup(E);if(f&&f.id!==d){if(!u)return y(200,{conflict:{id:f.id,name:f.name},requiresChoice:!0});u==="deactivate"&&await i.deactivate(f.id)}}let g=await i.activate(d);return y(200,g)}),"POST /profiles/:id/deactivate":async(o,a)=>_(async()=>{let{denied:c}=await s(o,"profile:update");if(c)return c;let d=await i.deactivate(a.id??"");return y(200,d)}),"GET /profiles/:id/versions":async(o,a)=>_(async()=>{let{denied:c}=await s(o,"profile:view");return c||y(200,await i.listVersions(a.id??""))}),"GET /profiles/:id/versions/:entryId":async(o,a)=>_(async()=>{let{denied:c}=await s(o,"profile:view");if(c)return c;let l=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,l);return p?new Response(p,{headers:{"Content-Type":"application/json"}}):m(404,"Version file not found")})}}function $i(i,e,t){async function n(s,r){let o=await P(s,i,e,t);return{user:o,denied:b(o,r)}}return{"GET /consents":async(s,r)=>_(async()=>{let{user:o,denied:a}=await n(s,"consent:view");if(a)return a;if(o?.allowedTenants.length&&!o.allowedTenants.includes("default"))return y(200,{items:[],total:0,page:1,limit:50});let c=new URL(s.url),d=U(c,"profileId"),l=U(c,"from"),u=U(c,"to"),p=U(c,"q"),h=ue(c);if("error"in h)return m(400,h.error);let{page:E,limit:g}=h,f={tenantId:"default",page:E,limit:g,...d!==void 0?{profileId:d}:{},...l!==void 0?{from:l}:{},...u!==void 0?{to:u}:{},...p!==void 0?{q:p}:{}},w=await i.getConsents(f);return y(200,w)}),"GET /consents/:visitorId":async(s,r)=>_(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?y(200,c):m(404,"Consent not found")}),"GET /consents/:visitorId/history":async(s,r)=>_(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 y(200,c)})}}var va=!1;function ji(i,e,t){async function n(s,r){let o=await P(s,i,e,t);return{user:o,denied:b(o,r)}}return{"GET /visitors":async(s,r)=>_(async()=>{let{user:o,denied:a}=await n(s,"visitor:view");if(a)return a;if(o?.allowedTenants.length&&!o.allowedTenants.includes("default"))return y(200,{items:[],total:0,page:1,limit:50});let c=new URL(s.url),d=U(c,"from"),l=U(c,"to"),u=U(c,"q"),p=ue(c);if("error"in p)return m(400,p.error);let{page:h,limit:E}=p,g={tenantId:"default",page:h,limit:E,...d!==void 0?{from:d}:{},...l!==void 0?{to:l}:{},...u!==void 0?{q:u}:{}},f=await i.getVisitors(g);return y(200,f)}),"GET /visitors/:visitorId/notice-shown":async(s,r)=>_(async()=>{if(!va)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 y(200,a)})}}function Fi(i,e,t,n){async function s(r,o){let a=await P(r,e,t,n);return{user:a,denied:b(a,o)}}return{"GET /users":async(r,o)=>_(async()=>{let{denied:a}=await s(r,"user:view");if(a)return a;let c=await i.list();return y(200,c)}),"GET /users/:id":async(r,o)=>_(async()=>{let{denied:a}=await s(r,"user:view");if(a)return a;let c=await i.get(o.id??"");if(!c)return m(404,"User not found");let d=await i.getRoles(c.id);return y(200,{...c,roles:d.map(l=>({id:l.id,name:l.name}))})}),"POST /users":async(r,o)=>_(async()=>{let{user:a,denied:c}=await s(r,"user:create");if(c)return c;let d=await O(r);if(!d||typeof d!="object")return m(400,"Invalid body");let l=d;if(typeof l.name!="string"||!l.name)return m(400,"name is required");if(typeof l.email!="string"||!l.email)return m(400,"email is required");if(typeof l.password!="string"||!l.password)return m(400,"password is required");if(l.password.length<12)return m(400,"password must be at least 12 characters");let u=await i.create({name:l.name,email:l.email,password:l.password,...typeof l.roleId=="string"?{roleId:l.roleId}:{},...Array.isArray(l.allowedTenants)?{allowedTenants:l.allowedTenants}:{}},a.sub);return y(201,u)}),"PUT /users/:id":async(r,o)=>_(async()=>{let{user:a,denied:c}=await s(r,"user:update");if(c)return c;let d=o.id??"",l=await O(r);if(!l||typeof l!="object")return m(400,"Invalid body");let u=l;if(typeof u.password=="string"&&u.password.length<12)return m(400,"password must be at least 12 characters");let p=await i.update(d,{...typeof u.name=="string"?{name:u.name}:{},...typeof u.email=="string"?{email:u.email}:{},...u.isActive!=null?{isActive:!!u.isActive}:{},...typeof u.password=="string"?{password:u.password}:{},...Array.isArray(u.allowedTenants)?{allowedTenants:u.allowedTenants}:{}},a.sub);return y(200,p)}),"DELETE /users/:id":async(r,o)=>_(async()=>{let{user:a,denied:c}=await s(r,"user:delete");if(c)return c;let d=o.id??"";return d===a.sub?m(400,"Cannot delete your own account"):(await i.delete(d,a.sub),y(200,{success:!0}))}),"POST /users/:id/roles":async(r,o)=>_(async()=>{let{user:a,denied:c}=await s(r,"role:update");if(c)return c;let d=await O(r);if(!d||typeof d!="object")return m(400,"Invalid body");let l=d;if(typeof l.roleId!="string")return m(400,"roleId is required");let u=typeof l.tenantId=="string"?l.tenantId:void 0;return await i.assignRole(o.id??"",l.roleId,u),await e.createLog({tenantId:"default",userId:a.sub,action:"user.role_assigned",resourceType:"user",...o.id!=null?{resourceId:o.id}:{},newData:{roleId:l.roleId}}),y(200,{success:!0})}),"DELETE /users/:id/roles/:roleId":async(r,o)=>_(async()=>{let{user:a,denied:c}=await s(r,"role:update");if(c)return c;let l=new URL(r.url).searchParams.get("tenantId")??void 0;return await i.revokeRole(o.id??"",o.roleId??"",l),await e.createLog({tenantId:"default",userId:a.sub,action:"user.role_revoked",resourceType:"user",...o.id!=null?{resourceId:o.id}:{},oldData:{roleId:o.roleId}}),y(200,{success:!0})})}}function Hi(i,e,t){async function n(s,r){let o=await P(s,i,e,t);return{user:o,denied:b(o,r)}}return{"GET /roles":async(s,r)=>_(async()=>{let{denied:o}=await n(s,"role:view");if(o)return o;let a=await i.getRoles("default");return y(200,a)}),"GET /roles/:id/permissions":async(s,r)=>_(async()=>{let{denied:o}=await n(s,"role:view");if(o)return o;let a=await i.getPermissionsForRole(r.id??"");return y(200,a)}),"POST /roles":async(s,r)=>_(async()=>{let{user:o,denied:a}=await n(s,"role:create");if(a)return a;let c=await O(s);if(!c||typeof c!="object")return m(400,"Invalid body");let d=c;if(typeof d.name!="string"||!d.name)return m(400,"name is required");let l=await i.createRole({tenantId:"default",name:d.name,...typeof d.description=="string"?{description:d.description}:{}});return await i.createLog({tenantId:"default",userId:o.sub,action:"role.created",resourceType:"role",resourceId:l.id,newData:l}),y(201,l)}),"PUT /roles/:id":async(s,r)=>_(async()=>{let{user:o,denied:a}=await n(s,"role:update");if(a)return a;let c=await O(s);if(!c||typeof c!="object")return m(400,"Invalid body");let d=c,l=await i.updateRole(r.id??"",{...typeof d.name=="string"?{name:d.name}:{},...typeof d.description=="string"?{description:d.description}:{}});return await i.createLog({tenantId:"default",userId:o.sub,action:"role.updated",resourceType:"role",resourceId:l.id,newData:l}),y(200,l)}),"DELETE /roles/:id":async(s,r)=>_(async()=>{let{user:o,denied:a}=await n(s,"role:delete");return a||(await i.deleteRole(r.id??""),await i.createLog({tenantId:"default",userId:o.sub,action:"role.deleted",resourceType:"role",...r.id!=null?{resourceId:r.id}:{}}),y(200,{success:!0}))}),"GET /permissions":async(s,r)=>_(async()=>{let{denied:o}=await n(s,"role:view");if(o)return o;let a=await i.getAllPermissions();return y(200,a)}),"POST /roles/:id/permissions":async(s,r)=>_(async()=>{let{denied:o}=await n(s,"role:update");if(o)return o;let a=await O(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),y(200,{success:!0}))}),"DELETE /roles/:id/permissions/:permissionId":async(s,r)=>_(async()=>{let{denied:o}=await n(s,"role:update");return o||(await i.revokePermissionFromRole(r.id??"",r.permissionId??""),y(200,{success:!0}))})}}function Bi(i,e,t){async function n(s,r){let o=await P(s,i,e,t);return{user:o,denied:b(o,r)}}return{"GET /audit":async(s,r)=>_(async()=>{let{user:o,denied:a}=await n(s,"audit:view");if(a)return a;if(o?.allowedTenants.length&&!o.allowedTenants.includes("default"))return y(200,{items:[],total:0,page:1,limit:50});let c=new URL(s.url),d=U(c,"action"),l=U(c,"resourceType"),u=U(c,"from"),p=U(c,"to"),h=U(c,"q"),E=ue(c);if("error"in E)return m(400,E.error);let{page:g,limit:f}=E,w={tenantId:"default",page:g,limit:f,...d!==void 0?{action:d}:{},...l!==void 0?{resourceType:l}:{},...u!==void 0?{from:u}:{},...p!==void 0?{to:p}:{},...h!==void 0?{q:h}:{}},R=await i.getLogs(w);return y(200,R)}),"GET /audit/:id":async(s,r)=>_(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"):y(200,c)})}}function Wi(i,e,t,n){async function s(r){let o=await P(r,i,t,n);return{denied:b(o,"consent:view")}}return{"GET /stats/overview":async(r,o)=>_(async()=>{let{denied:a}=await s(r);if(a)return a;let c=await e.getOverview("default");return y(200,c)}),"GET /stats/categories":async(r,o)=>_(async()=>{let{denied:a}=await s(r);if(a)return a;let c=await e.getCategories("default");return y(200,c)}),"GET /stats/timeline":async(r,o)=>_(async()=>{let{denied:a}=await s(r);if(a)return a;let c=new URL(r.url),d=It(c,"days",30),l=await e.getTimeline("default",d);return y(200,l)}),"GET /stats/countries":async(r,o)=>_(async()=>{let{denied:a}=await s(r);if(a)return a;let c=await e.getCountries("default");return y(200,c)}),"GET /stats/gpc":async(r,o)=>_(async()=>{let{denied:a}=await s(r);if(a)return a;let c=await e.getGpc("default");return y(200,c)}),"GET /stats/aggregate":async(r,o)=>_(async()=>{let{denied:a}=await s(r);if(a)return a;let c=await e.getTenants(),d=await Promise.all(c.map(async u=>{let p=await e.getOverview(u.id);return{tenantId:u.id,tenantName:u.name,...p}})),l=d.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 y(200,{aggregate:l,perTenant:d})})}}function pn(i){let e=i==null?"":String(i);return e.includes(",")||e.includes('"')||e.includes(`
|
|
151
|
+
`)?`"${e.replace(/"/g,'""')}"`:e}function Pa(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(pn).join(",")+`
|
|
152
|
+
`}function Oa(i){return[i.id,i.userId??"",i.action,i.resourceType,i.resourceId??"",i.createdAt].map(pn).join(",")+`
|
|
153
|
+
`}function Vi(i,e,t,n){async function s(r){let o=await P(r,i,e,t);return{denied:b(o,"export:run")}}return{"GET /export/consents":async(r,o)=>_(async()=>{let{denied:a}=await s(r);if(a)return a;let c=new URL(r.url),d=U(c,"format")??"csv",l=U(c,"profileId"),u=U(c,"from"),p=U(c,"to"),h={tenantId:"default",...l!==void 0?{profileId:l}:{},...u!==void 0?{from:u}:{},...p!==void 0?{to:p}:{}};if(d==="json"){let w=[];for await(let R of i.streamConsents(h))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,g=`id,visitor_id,profile_id,locale,consent_json,gpc_detected,source,signature,created_at,updated_at
|
|
154
|
+
`,f=new ReadableStream({async start(w){w.enqueue(E.encode(g));for await(let R of i.streamConsents(h))w.enqueue(E.encode(Pa(R)));w.close()}});return new Response(f,{status:200,headers:{"Content-Type":"text/csv; charset=utf-8","Content-Disposition":'attachment; filename="consents.csv"'}})}),"GET /export/consents/xlsx":async(r,o)=>_(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 d=new URL(r.url),l=d.searchParams.get("profileId")??void 0,u=d.searchParams.get("from")??void 0,p=d.searchParams.get("to")??void 0,h={tenantId:"default",...l!==void 0?{profileId:l}:{},...u!==void 0?{from:u}:{},...p!==void 0?{to:p}:{}},E=[];for await(let R of i.streamConsents(h))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 g=c.utils.book_new(),f=c.utils.json_to_sheet(E);c.utils.book_append_sheet(g,f,"Consents");let w=c.write(g,{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)=>_(async()=>{let{denied:a}=await s(r);if(a)return a;let c=o.profileId??"",d=await i.getProfile(c);if(!d)return m(404,"Profile not found");let l=d.profileJson,u=l.locales?.length?l.locales:[d.defaultLocale],p={};for(let E of u){if(E===d.defaultLocale){p[E]={mainBanner:l.mainBanner,...l.gpcBanner?{gpcBanner:l.gpcBanner}:{},preferenceModal:l.preferenceModal};continue}let g=await n.getVersionFile(c,String(d.version),E);if(g)try{let f=JSON.parse(g);p[E]={mainBanner:f.mainBanner,...f.gpcBanner?{gpcBanner:f.gpcBanner}:{},preferenceModal:f.preferenceModal}}catch{}}if(Object.keys(p).length===0)return new Response(`locale,field,value
|
|
155
|
+
`,{status:200,headers:{"Content-Type":"text/csv; charset=utf-8","Content-Disposition":`attachment; filename="${c}-translations.csv"`}});let h=["locale,section,field,value"];for(let[E,g]of Object.entries(p))for(let[f,w]of Object.entries(g))if(typeof w=="object"&&w!==null)for(let[R,I]of Object.entries(w))typeof I=="string"&&h.push([E,f,R,I].map(pn).join(","));return new Response(h.join(`
|
|
167
156
|
`)+`
|
|
168
|
-
`,{status:200,headers:{"Content-Type":"text/csv; charset=utf-8","Content-Disposition":`attachment; filename="${
|
|
169
|
-
`,A=new ReadableStream({async start(f){f.enqueue(p.encode(E));for await(let _ of s.streamAuditLogs(d))f.enqueue(p.encode(ur(_)));f.close()}});return new Response(A,{status:200,headers:{"Content-Type":"text/csv; charset=utf-8","Content-Disposition":'attachment; filename="audit.csv"'}})})}}import{randomBytes as pr,createHash as mr}from"crypto";function gr(){let s=`ck_live_${pr(24).toString("hex")}`,e=mr("sha256").update(s).digest("hex");return{raw:s,hash:e}}function zi(s,e,t){async function n(i){let o=await v(i,s,e,t);return{user:o,denied:b(o,"settings:update")}}return{"GET /apikeys":async(i,o)=>g(async()=>{let{denied:r}=await n(i);if(r)return r;let a=await s.getApiKeys("default");return y(200,a.map(c=>({id:c.id,name:c.name,isActive:c.isActive,createdAt:c.createdAt})))}),"POST /apikeys":async(i,o)=>g(async()=>{let{denied:r}=await n(i);if(r)return r;let a=await i.json();if(!a.name)return y(400,{error:"name is required"});let{raw:c,hash:l}=gr(),u=await s.createApiKey({tenantId:a.tenantId??"default",name:a.name,keyHash:l});return y(201,{id:u.id,name:u.name,key:c,createdAt:u.createdAt})}),"DELETE /apikeys/:id":async(i,o)=>g(async()=>{let{denied:r}=await n(i);return r||(await s.revokeApiKey(o.id??""),y(204,null))})}}function Qi(s,e,t){async function n(i,o){let r=await v(i,s,e,t);return{user:r,denied:b(r,o)}}return{"GET /tenants":async(i,o)=>g(async()=>{let{denied:r}=await n(i,"settings:update");if(r)return r;let a=await s.getTenants();return y(200,a)}),"POST /tenants":async(i,o)=>g(async()=>{let{denied:r}=await n(i,"settings:update");if(r)return r;let a=await I(i);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 s.createTenant({name:c.name,slug:c.slug});return y(201,l)}),"PUT /tenants/:id":async(i,o)=>g(async()=>{let{denied:r}=await n(i,"settings:update");if(r)return r;let a=await I(i);if(!a||typeof a!="object")return m(400,"Invalid body");let c=a,l=await s.updateTenant(o.id??"",{...typeof c.name=="string"?{name:c.name}:{},...typeof c.slug=="string"?{slug:c.slug}:{}});return y(200,l)}),"DELETE /tenants/:id":async(i,o)=>g(async()=>{let{denied:r}=await n(i,"settings:update");if(r)return r;let a=o.id??"";return a==="default"?m(400,"Cannot delete the default tenant"):(await s.deleteTenant(a),y(200,{success:!0}))})}}function Xi(s,e,t){async function n(i){let o=await v(i,s,e,t);return{denied:b(o,"consent:view")}}return{"GET /tcf/vendors":async(i,o)=>g(async()=>{let{denied:r}=await n(i);if(r)return r;let a=await Z();if(!a)return y(503,{error:"GVL not available. Enable tcf.enabled in config."});let c=new URL(i.url),l=c.searchParams.get("q")?.toLowerCase(),u=Math.max(1,parseInt(c.searchParams.get("page")??"1",10)),d=Math.min(200,Math.max(1,parseInt(c.searchParams.get("limit")??"100",10))),p=Object.values(a.vendors),E=l?p.filter(T=>T.name.toLowerCase().includes(l)):p,A=E.length,f=Math.max(1,Math.ceil(A/d)),_=Math.min(u,f),h=(_-1)*d;return y(200,{vendorListVersion:a.vendorListVersion,total:A,page:_,totalPages:f,vendors:E.slice(h,h+d)})}),"GET /tcf/purposes":async(i,o)=>g(async()=>{let{denied:r}=await n(i);if(r)return r;let a=await Z();return a?y(200,{purposes:Object.values(a.purposes)}):y(503,{error:"GVL not available. Enable tcf.enabled in config."})})}}function Zi(s,e,t,n="default"){async function i(o,r){let a=await v(o,s,e,t);return{user:a,denied:b(a,r)}}return{"GET /cookie-templates":async o=>g(async()=>{let{denied:r}=await i(o,"profile:view");return r||y(200,await s.getCookieTemplates(n))}),"GET /cookie-templates/:id":async(o,r)=>g(async()=>{let{denied:a}=await i(o,"profile:view");if(a)return a;let c=await s.getCookieTemplate(r.id);return c?y(200,c):m(404,"Cookie template not found")}),"POST /cookie-templates":async o=>g(async()=>{let{denied:r}=await i(o,"profile:create");if(r)return r;let a=await I(o);if(!a?.name||typeof a.name!="string")return m(400,"name is required");if(!Array.isArray(a.cookies))return m(400,"cookies must be an array");let c=await s.createCookieTemplate({tenantId:n,name:a.name,cookies:a.cookies});return y(201,c)}),"PUT /cookie-templates/:id":async(o,r)=>g(async()=>{let{denied:a}=await i(o,"profile:update");if(a)return a;let c=r.id,l=await I(o),u={};if(l?.name!==void 0&&typeof l.name=="string"&&(u.name=l.name),l?.cookies!==void 0&&Array.isArray(l.cookies)&&(u.cookies=l.cookies),Array.isArray(l?.cookies)){let p=await s.getCookieTemplate(c);if(p){let E=new Set(l.cookies.map(f=>f.id)),A=p.cookies.map(f=>f.id).filter(f=>!E.has(f));if(A.length>0){let f=await s.findProfilesUsingCookieTemplate(c),_=[];for(let h of f){if(!h.complianceGroup)continue;le(l.cookies,h.complianceGroup).valid||_.push({id:h.id,name:h.name,complianceGroup:h.complianceGroup})}if(_.length>0)return m(422,"Removing these cookies would break compliance for affected profiles",{blockingProfiles:_,removedCookieIds:A})}}}let d=await s.updateCookieTemplate(c,u);return y(200,d)}),"DELETE /cookie-templates/:id":async(o,r)=>g(async()=>{let{denied:a}=await i(o,"profile:delete");if(a)return a;let c=r.id,l=await s.findProfilesUsingCookieTemplate(c);return l.length>0?m(422,"Cannot delete: template is used by profiles",{profiles:l.map(u=>({id:u.id,name:u.name,isActive:u.isActive}))}):(await s.deleteCookieTemplate(c),y(200,{ok:!0}))}),"POST /cookie-templates/:id/copy":async(o,r)=>g(async()=>{let{denied:a}=await i(o,"profile:create");if(a)return a;let c=await I(o),l=await s.getCookieTemplate(r.id);if(!l)return m(404,"Cookie template not found");let u=typeof c?.name=="string"&&c.name.trim()?c.name.trim():`${l.name} (Copy)`,d=await s.copyCookieTemplate(r.id,u);return y(201,d)}),"GET /cookie-templates/:id/profile-usage":async(o,r)=>g(async()=>{let{denied:a}=await i(o,"profile:view");if(a)return a;let c=await s.findProfilesUsingCookieTemplate(r.id);return y(200,c)})}}function es(s,e,t,n="default"){async function i(o,r){let a=await v(o,s,e,t);return{user:a,denied:b(a,r)}}return{"GET /ui-templates":async o=>g(async()=>{let{denied:r}=await i(o,"profile:view");return r||y(200,await s.getUITemplates(n))}),"GET /ui-templates/:id":async(o,r)=>g(async()=>{let{denied:a}=await i(o,"profile:view");if(a)return a;let c=await s.getUITemplate(r.id);return c?y(200,c):m(404,"UI template not found")}),"POST /ui-templates":async o=>g(async()=>{let{denied:r}=await i(o,"profile:create");if(r)return r;let a=await I(o);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 s.createUITemplate({tenantId:n,name:a.name,mainBanner:a.mainBanner,gpcBanner:a.gpcBanner,preferenceModal:a.preferenceModal});return y(201,c)}),"PUT /ui-templates/:id":async(o,r)=>g(async()=>{let{denied:a}=await i(o,"profile:update");if(a)return a;let c=await I(o),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 u=await s.updateUITemplate(r.id,l);return y(200,u)}),"DELETE /ui-templates/:id":async(o,r)=>g(async()=>{let{denied:a}=await i(o,"profile:delete");if(a)return a;let c=r.id,l=await s.findProfilesUsingUITemplate(c);return l.length>0?m(422,"Cannot delete: template is used by profiles",{profiles:l.map(u=>({id:u.id,name:u.name,isActive:u.isActive}))}):(await s.deleteUITemplate(c),y(200,{ok:!0}))}),"POST /ui-templates/:id/copy":async(o,r)=>g(async()=>{let{denied:a}=await i(o,"profile:create");if(a)return a;let c=await I(o),l=await s.getUITemplate(r.id);if(!l)return m(404,"UI template not found");let u=typeof c?.name=="string"&&c.name.trim()?c.name.trim():`${l.name} (Copy)`,d=await s.copyUITemplate(r.id,u);return y(201,d)}),"GET /ui-templates/:id/profile-usage":async(o,r)=>g(async()=>{let{denied:a}=await i(o,"profile:view");if(a)return a;let c=await s.findProfilesUsingUITemplate(r.id);return y(200,c)})}}function ts(s,e,t,n="default"){async function i(o,r){let a=await v(o,s,e,t);return{user:a,denied:b(a,r)}}return{"GET /analytics/opt-in":async(o,r)=>g(async()=>{let{denied:a}=await i(o,"stats:view");if(a)return a;let c=new URL(o.url),l=c.searchParams.get("tenantId")??n,u={},d=c.searchParams.get("profileId"),p=c.searchParams.get("complianceGroup"),E=c.searchParams.get("from"),A=c.searchParams.get("to"),f=c.searchParams.get("locale");d&&(u.profileId=d),p&&(u.complianceGroup=p),E&&(u.from=E),A&&(u.to=A),f&&(u.locale=f);let _=await s.getOptInStats(l,u);return y(200,_)})}}import{createHash as fr}from"crypto";async function ns(s,e,t){let n=s.headers.get("authorization");if(!n?.startsWith("Bearer ck_"))return t;let i=n.slice(7),o=fr("sha256").update(i).digest("hex");return(await e.getApiKeyByHash(o))?.tenantId??t}var Pe=class{constructor(e=6e4,t=60){this.windowMs=e;this.maxRequests=t;setInterval(()=>{let n=Date.now();for(let[i,o]of this.store)n>=o.resetAt&&this.store.delete(i)},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 Ht=hr(os(import.meta.url)),Ip=os(new URL("./dashboard",import.meta.url)),rs={".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 _r(s,e,t,n,i){let o=s.slice(e.length)||"/";if(o.startsWith("/api/")||o.startsWith("/admin/"))return!1;let r=o==="/"||o===""||!gt(o)?Y(t,"index.html"):Y(t,o);if(!r.startsWith(t))return!1;try{let a=await be(r),c=gt(r).toLowerCase(),l=c===".html"||c==="",u=rs[c]??"application/octet-stream";return n.writeHead(200,{"Content-Type":u}),n.end(l?pe(a,i):a),!0}catch{return!1}}async function is(s,e){try{let t=await be(s);return new Response(t,{headers:{"Content-Type":e,"Cache-Control":"public, max-age=3600"}})}catch{return new Response("Not Found",{status:404})}}function Er(s){let e=[],t=s.split("/").map(n=>n.startsWith(":")?(e.push(n.slice(1)),"([^/]+)"):n.replace(/[.*+?^${}()|[\]\\]/g,"\\$&"));return{pattern:new RegExp(`^${t.join("\\/")}$`),paramNames:e}}function wr(s){let t=`<!DOCTYPE html>
|
|
157
|
+
`,{status:200,headers:{"Content-Type":"text/csv; charset=utf-8","Content-Disposition":`attachment; filename="${c}-translations.csv"`}})}),"GET /export/audit":async(r,o)=>_(async()=>{let{denied:a}=await s(r);if(a)return a;let c=new URL(r.url),d=U(c,"format")??"csv",l=U(c,"from"),u=U(c,"to"),p={tenantId:"default",...l!==void 0?{from:l}:{},...u!==void 0?{to:u}:{}};if(d==="json"){let f=[];for await(let w of i.streamAuditLogs(p))f.push(w);return new Response(JSON.stringify(f),{status:200,headers:{"Content-Type":"application/json","Content-Disposition":'attachment; filename="audit.json"'}})}let h=new TextEncoder,E=`id,user_id,action,resource_type,resource_id,created_at
|
|
158
|
+
`,g=new ReadableStream({async start(f){f.enqueue(h.encode(E));for await(let w of i.streamAuditLogs(p))f.enqueue(h.encode(Oa(w)));f.close()}});return new Response(g,{status:200,headers:{"Content-Type":"text/csv; charset=utf-8","Content-Disposition":'attachment; filename="audit.csv"'}})})}}import{randomBytes as Ji,createHash as ba}from"crypto";function Da(){let i=`ck_live_${Ji(12).toString("hex")}${Ji(12).toString("hex")}}`,e=ba("sha256").update(i).digest("hex");return{raw:i,hash:e}}function qi(i,e,t){async function n(s){let r=await P(s,i,e,t);return{user:r,denied:b(r,"settings:update")}}return{"GET /apikeys":async(s,r)=>_(async()=>{let{denied:o}=await n(s);if(o)return o;let a=await i.getApiKeys("default"),c=new Date,d=await Promise.all(a.map(async l=>{let u=l.isActive&&!!l.expireBy&&new Date(l.expireBy)<=c;return u&&await i.revokeApiKey(l.id).catch(()=>{}),{id:l.id,name:l.name,isActive:u?!1:l.isActive,createdAt:l.createdAt,updatedAt:l.updatedAt,...l.createdBy!==void 0?{createdBy:l.createdBy}:{},...l.expireBy!==void 0?{expireBy:l.expireBy}:{}}}));return y(200,d)}),"POST /apikeys":async(s,r)=>_(async()=>{let{user:o,denied:a}=await n(s);if(a)return a;let c=await s.json();if(!c.name)return y(400,{error:"name is required"});if(c.expireBy!==void 0&&Number.isNaN(new Date(c.expireBy).getTime()))return y(400,{error:"expireBy must be a valid date"});let{raw:d,hash:l}=Da(),u=await i.createApiKey({tenantId:c.tenantId??"default",name:c.name,keyHash:l,...o?{createdBy:o.sub}:{},...c.expireBy!==void 0?{expireBy:c.expireBy}:{}});return y(201,{id:u.id,name:u.name,key:d,createdAt:u.createdAt})}),"DELETE /apikeys/:id":async(s,r)=>_(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)=>_(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)=>_(async()=>{let{denied:o}=await n(s);return o||(await i.deleteApiKey(r.id??""),new Response(null,{status:204}))})}}function Gi(i,e,t){async function n(s,r){let o=await P(s,i,e,t);return{user:o,denied:b(o,r)}}return{"GET /tenants":async(s,r)=>_(async()=>{let{denied:o}=await n(s,"settings:update");if(o)return o;let a=await i.getTenants();return y(200,a)}),"POST /tenants":async(s,r)=>_(async()=>{let{denied:o}=await n(s,"settings:update");if(o)return o;let a=await O(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 d=await i.createTenant({name:c.name,slug:c.slug});return y(201,d)}),"PUT /tenants/:id":async(s,r)=>_(async()=>{let{denied:o}=await n(s,"settings:update");if(o)return o;let a=await O(s);if(!a||typeof a!="object")return m(400,"Invalid body");let c=a,d=await i.updateTenant(r.id??"",{...typeof c.name=="string"?{name:c.name}:{},...typeof c.slug=="string"?{slug:c.slug}:{}});return y(200,d)}),"DELETE /tenants/:id":async(s,r)=>_(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),y(200,{success:!0}))})}}function Ki(i,e,t){async function n(s){let r=await P(s,i,e,t);return{user:r,denied:b(r,"settings:update")}}return{"GET /settings":async(s,r)=>_(async()=>{let{denied:o}=await n(s);if(o)return o;let a=await i.getSettings("default");return y(200,a)}),"PATCH /settings":async(s,r)=>_(async()=>{let{denied:o}=await n(s);if(o)return o;let a=await O(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 d=await i.updateSettings("default",{...c.allowedOrigins!==void 0?{allowedOrigins:c.allowedOrigins}:{},...c.adminAllowedOrigins!==void 0?{adminAllowedOrigins:c.adminAllowedOrigins}:{}});return y(200,d)})}}function Yi(i,e,t){async function n(s){let r=await P(s,i,e,t);return{denied:b(r,"consent:view")}}return{"GET /tcf/vendors":async(s,r)=>_(async()=>{let{denied:o}=await n(s);if(o)return o;let a=await ae();if(!a)return y(503,{error:"GVL not available. Enable tcf.enabled in config."});let c=new URL(s.url),d=c.searchParams.get("q")?.toLowerCase(),l=Math.max(1,parseInt(c.searchParams.get("page")??"1",10)),u=Math.min(200,Math.max(1,parseInt(c.searchParams.get("limit")??"100",10))),p=Object.values(a.vendors),h=d?p.filter(R=>R.name.toLowerCase().includes(d)):p,E=h.length,g=Math.max(1,Math.ceil(E/u)),f=Math.min(l,g),w=(f-1)*u;return y(200,{vendorListVersion:a.vendorListVersion,total:E,page:f,totalPages:g,vendors:h.slice(w,w+u)})}),"GET /tcf/purposes":async(s,r)=>_(async()=>{let{denied:o}=await n(s);if(o)return o;let a=await ae();return a?y(200,{purposes:Object.values(a.purposes)}):y(503,{error:"GVL not available. Enable tcf.enabled in config."})})}}var zi=["mandatory","consent","legitimate_interest"],Qi=["sale","sharing","sensitive"];function La(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(!$t.includes(n.purpose))return{valid:!1,error:`cookies["${e}"].purpose must be one of: ${$t.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&&!Qi.includes(n.cpraCategory))return{valid:!1,error:`cookies["${e}"].cpraCategory must be one of: ${Qi.join(", ")}`}}return{valid:!0}}function Ua(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(!zi.includes(s.legalBasis))return{valid:!1,error:`categories["${t}"].legalBasis must be one of: ${zi.join(", ")}`};if(!Array.isArray(s.cookies))return{valid:!1,error:`categories["${t}"].cookies must be an array of parameter ids`}}return{valid:!0}}function Na(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 ka(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 mn(i,e){let t=La(i);if(!t.valid)return t;let n=Ua(e);if(!n.valid)return n;let s=i,r=e,o=Na(s,r);return o.valid?ka(s,r):o}function Xi(i,e,t,n="default"){async function s(r,o){let a=await P(r,i,e,t);return{user:a,denied:b(a,o)}}return{"GET /consent-templates":async r=>_(async()=>{let{denied:o}=await s(r,"profile:view");return o||y(200,await i.getConsentTemplates(n))}),"GET /consent-templates/:id":async(r,o)=>_(async()=>{let{denied:a}=await s(r,"profile:view");if(a)return a;let c=await i.getConsentTemplate(o.id);return c?y(200,c):m(404,"Consent template not found")}),"POST /consent-templates":async r=>_(async()=>{let{denied:o}=await s(r,"profile:create");if(o)return o;let a=await O(r);if(!a?.name||typeof a.name!="string")return m(400,"name is required");let c=mn(a.cookies,a.categories);if(!c.valid)return m(400,c.error);let d=await i.createConsentTemplate({tenantId:n,name:a.name,cookies:a.cookies,categories:a.categories});return y(201,d)}),"PUT /consent-templates/:id":async(r,o)=>_(async()=>{let{denied:a}=await s(r,"profile:update");if(a)return a;let c=o.id,d=await O(r),l=await i.getConsentTemplate(c);if(!l)return m(404,"Consent template not found");let u={};d?.name!==void 0&&typeof d.name=="string"&&(u.name=d.name);let p=d?.cookies!==void 0,h=d?.categories!==void 0,E=p?d.cookies:l.cookies,g=h?d.categories:l.categories;if(p||h){let w=mn(E,g);if(!w.valid)return m(400,w.error);p&&(u.cookies=E),h&&(u.categories=g);let R=await i.findProfilesUsingConsentTemplate(c),I=[];for(let T of R){if(!T.complianceGroup)continue;pe(E,g,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 f=await i.updateConsentTemplate(c,u);return y(200,f)}),"DELETE /consent-templates/:id":async(r,o)=>_(async()=>{let{denied:a}=await s(r,"profile:delete");if(a)return a;let c=o.id,d=await i.findProfilesUsingConsentTemplate(c);return d.length>0?m(422,"Cannot delete: template is used by profiles",{profiles:d.map(l=>({id:l.id,name:l.name,isActive:l.isActive}))}):(await i.deleteConsentTemplate(c),y(200,{ok:!0}))}),"POST /consent-templates/:id/copy":async(r,o)=>_(async()=>{let{denied:a}=await s(r,"profile:create");if(a)return a;let c=await O(r),d=await i.getConsentTemplate(o.id);if(!d)return m(404,"Consent template not found");let l=typeof c?.name=="string"&&c.name.trim()?c.name.trim():`${d.name} (Copy)`,u=await i.copyConsentTemplate(o.id,l);return y(201,u)}),"GET /consent-templates/:id/profile-usage":async(r,o)=>_(async()=>{let{denied:a}=await s(r,"profile:view");if(a)return a;let c=await i.findProfilesUsingConsentTemplate(o.id);return y(200,c)})}}function Zi(i,e,t,n="default"){async function s(r,o){let a=await P(r,i,e,t);return{user:a,denied:b(a,o)}}return{"GET /ui-templates":async r=>_(async()=>{let{denied:o}=await s(r,"profile:view");return o||y(200,await i.getUITemplates(n))}),"GET /ui-templates/:id":async(r,o)=>_(async()=>{let{denied:a}=await s(r,"profile:view");if(a)return a;let c=await i.getUITemplate(o.id);return c?y(200,c):m(404,"UI template not found")}),"POST /ui-templates":async r=>_(async()=>{let{denied:o}=await s(r,"profile:create");if(o)return o;let a=await O(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 y(201,c)}),"PUT /ui-templates/:id":async(r,o)=>_(async()=>{let{denied:a}=await s(r,"profile:update");if(a)return a;let c=await O(r),d={};typeof c?.name=="string"&&(d.name=c.name),c?.mainBanner&&(d.mainBanner=c.mainBanner),c?.gpcBanner&&(d.gpcBanner=c.gpcBanner),c?.preferenceModal&&(d.preferenceModal=c.preferenceModal);let l=await i.updateUITemplate(o.id,d);return y(200,l)}),"DELETE /ui-templates/:id":async(r,o)=>_(async()=>{let{denied:a}=await s(r,"profile:delete");if(a)return a;let c=o.id,d=await i.findProfilesUsingUITemplate(c);return d.length>0?m(422,"Cannot delete: template is used by profiles",{profiles:d.map(l=>({id:l.id,name:l.name,isActive:l.isActive}))}):(await i.deleteUITemplate(c),y(200,{ok:!0}))}),"POST /ui-templates/:id/copy":async(r,o)=>_(async()=>{let{denied:a}=await s(r,"profile:create");if(a)return a;let c=await O(r),d=await i.getUITemplate(o.id);if(!d)return m(404,"UI template not found");let l=typeof c?.name=="string"&&c.name.trim()?c.name.trim():`${d.name} (Copy)`,u=await i.copyUITemplate(o.id,l);return y(201,u)}),"GET /ui-templates/:id/profile-usage":async(r,o)=>_(async()=>{let{denied:a}=await s(r,"profile:view");if(a)return a;let c=await i.findProfilesUsingUITemplate(o.id);return y(200,c)})}}function er(i,e,t,n="default"){async function s(r,o){let a=await P(r,i,e,t);return{user:a,denied:b(a,o)}}return{"GET /analytics/opt-in":async(r,o)=>_(async()=>{let{denied:a}=await s(r,"stats:view");if(a)return a;let c=new URL(r.url),d=c.searchParams.get("tenantId")??n,l={},u=c.searchParams.get("profileId"),p=c.searchParams.get("complianceGroup"),h=c.searchParams.get("from"),E=c.searchParams.get("to"),g=c.searchParams.get("locale");u&&(l.profileId=u),p&&(l.complianceGroup=p),h&&(l.from=h),E&&(l.to=E),g&&(l.locale=g);let f=await i.getOptInStats(d,l);return y(200,f)})}}var ee="\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022";function tr(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:ee}:{},...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:ee}:{},...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:ee}:{},...t.jwtSecret!==void 0?{jwtSecret:ee}:{},...t.oidc?{oidc:{issuer:t.oidc.issuer,clientId:t.oidc.clientId,clientSecret:ee,redirectUri:t.oidc.redirectUri}}:{},...t.saml?{saml:{issuer:t.saml.issuer,entryPoint:t.saml.entryPoint,cert:ee,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.complianceMapUrl!==void 0?{complianceMapUrl:n.complianceMapUrl}:{}}}:{},...i.multiTenant?{multiTenant:i.multiTenant}:{},...i.plugins?{plugins:{count:i.plugins.length}}:{},...i.ageGate?{ageGate:i.ageGate}:{},...i.tcf?{tcf:i.tcf}:{},...i.dataRetention?{dataRetention:i.dataRetention}:{},...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:ee,secretAccessKey:ee}}:{},...i.consentSigningKey!==void 0?{consentSigningKey:ee}:{}}}function nr(i,e,t,n,s,r){async function o(c){let d=await P(c,e,t,n);return{denied:b(d,"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,d)=>_(async()=>{let{denied:l}=await o(c);if(l)return l;let u=await e.getSettings("default");return y(200,{completed:u.setupCompleted===!0})}),"GET /setup/config":async(c,d)=>_(async()=>{let{denied:l}=await o(c);return l||y(200,{config:tr(s),...r})}),"GET /setup/compliance-groups":async(c,d)=>_(async()=>{let{denied:l}=await o(c);if(l)return l;let u=xt.map(p=>({id:p,...$e[p]}));return y(200,u)}),"POST /setup/seed-profiles":async(c,d)=>_(async()=>{let{denied:l}=await o(c);if(l)return l;let u=await a();if(u)return u;let h=(await O(c))?.groups;if(!Array.isArray(h)||h.some(g=>typeof g!="string"))return m(400,"groups must be an array of compliance group ids");let E=h.filter(g=>!xt.includes(g));return E.length>0?m(400,`Unknown compliance group id(s): ${E.join(", ")}`):(await Promise.all(h.map(g=>i.seedDefaultProfile(g))),y(200,{seeded:h}))}),"POST /setup/complete":async(c,d)=>_(async()=>{let{denied:l}=await o(c);if(l)return l;let u=await a();if(u)return u;let p=await e.updateSettings("default",{setupCompleted:!0});return y(200,p)})}}import{createHash as Ma}from"crypto";async function sr(i,e,t){let n=i.headers.get("authorization");if(!n?.startsWith("Bearer ck_"))return t;let s=n.slice(7),r=Ma("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 De=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 gn=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 ir(i,e){return e.length>0&&e.every(t=>i[t]==="granted")}var fn=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=ir(e.consentJson,s)?"granted":"denied",o=t&&ir(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 _n=$a(ar(import.meta.url)),xg=ar(new URL("./dashboard",import.meta.url)),cr={".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 ja(i,e,t,n,s){let r=i.slice(e.length)||"/";if(r.startsWith("/api/")||r.startsWith("/admin/"))return!1;let o=r==="/"||r===""||!Dt(r)?te(t,"index.html"):te(t,r);if(!o.startsWith(t))return!1;try{let a=await Le(o),c=Dt(o).toLowerCase(),d=c===".html"||c==="",l=cr[c]??"application/octet-stream";return n.writeHead(200,{"Content-Type":l}),n.end(d?_e(a,s):a),!0}catch{return!1}}async function rr(i,e){try{let t=await Le(i);return new Response(t,{headers:{"Content-Type":e,"Cache-Control":"public, max-age=3600"}})}catch{return new Response("Not Found",{status:404})}}function Fa(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 Ha(i){let t=`<!DOCTYPE html>
|
|
170
159
|
<html lang="en">
|
|
171
160
|
<head>
|
|
172
161
|
<meta charset="UTF-8">
|
|
@@ -195,19 +184,19 @@ ${l.map(u=>` (${H(c)},${H(u)})`).join(`,
|
|
|
195
184
|
<h1>404</h1>
|
|
196
185
|
<h2>Page not found</h2>
|
|
197
186
|
<p>The admin dashboard is not enabled on this instance.<br>Use the API or enable <code style="background:#1e2330;padding:.1rem .35rem;border-radius:.25rem;font-size:.8rem">dashboard: true</code> in your config.</p>
|
|
198
|
-
<a href="${`${
|
|
187
|
+
<a href="${`${i}/api/docs`}">
|
|
199
188
|
<svg viewBox="0 0 24 24"><path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/><polyline points="10 9 9 9 8 9"/></svg>
|
|
200
189
|
View API Documentation
|
|
201
190
|
</a>
|
|
202
191
|
</div>
|
|
203
192
|
</body>
|
|
204
|
-
</html>`;return new Response(t,{status:404,headers:{"Content-Type":"text/html; charset=utf-8"}})}function
|
|
193
|
+
</html>`;return new Response(t,{status:404,headers:{"Content-Type":"text/html; charset=utf-8"}})}function Ba(i,e,t,n,s){let r=[];for(let o of e)for(let[a,c]of Object.entries(o.routes)){let d=a.indexOf(" "),l=a.slice(0,d).toUpperCase(),u=a.slice(d+1),{pattern:p,paramNames:h}=Fa(`${o.prefix}${u}`);r.push({method:l,pattern:p,paramNames:h,handler:c,rateLimit:o.rateLimit??!1,originCheck:o.originCheck??!1})}return async o=>{let a=ri(o);if(a)return a;let d=new URL(o.url).pathname,l=o.method.toUpperCase();if(t&&r.find(h=>h.rateLimit&&d.match(h.pattern))){let h=o.headers.get("x-real-ip")??"";if(!t.checkRequest(h))return Oe(m(429,"Too Many Requests"),o)}for(let p of r){if(p.method!==l)continue;let h=d.match(p.pattern);if(!h)continue;if(p.originCheck){let f=await s(o),{adminAllowedOrigins:w}=await n.getSettings(f);if(!Pt(o,w??[]))return Oe(m(403,"Origin not allowed for the admin API"),o)}let E={};p.paramNames.forEach((f,w)=>{let R=h[w+1];R!=null&&(E[f]=R)});let g=await p.handler(o,E);return cn(Oe(g,o))}let u=d.startsWith(i)&&!d.startsWith(`${i}/api/`)&&!d.startsWith(`${i}/admin/`);return cn(Oe(u?Ha(i):m(404,"Not Found"),o))}}function or(i){return typeof i=="object"&&i!==null&&!Array.isArray(i)}function lr(i,e){let t={...i};for(let n of Object.keys(e)){let s=e[n],r=t[n];s!==void 0&&or(s)&&or(r)?t[n]=lr(r,s):s!==void 0&&(t[n]=s)}return t}var Wa={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_JWT_SECRET?{jwtSecret:process.env.CONSENTI_ADMIN_JWT_SECRET}:{}},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 Va(i){switch(i.driver){case"mongodb":return new Re(i);case"mysql":return new Ce(i);case"postgresql":return new Te(i);case"node:sqlite":return new Je(i);case"better-sqlite3":case"sqlite":return new de(i);case"node-sqlite3-wasm":return new Ve(i);default:return new st(i)}}function $g(i){let e=lr(Wa,i),t=e.storage?.driver==="json",n=!i.auth&&!process.env.CONSENTI_ADMIN_PASSWORD;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,a=K(s.path??"./consenti-data",s.driver??"json").profilesDir,c=Va(s),d=new xa,l=new it(c),u=new rt(c),p=new ot(c),h=new at(c),E=new ct(c),g=new ut(c),f=new dt(c),w=new Fe;e.plugins?.length&&w.register(e.plugins);let R=e.compliance??{},I=R.type??"auto",T=I==="auto",v=e.localeJsonCache?.enabled?new wt(e.localeJsonCache.dir):void 0,j=T?new yt(R.geoDataProvider??"default",bn):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");let F=new ht(l,E,"default",c,d,v,a,e.handleCache,e.s3Api),Ue=new Rt(p,"default",d),dr=new St(h,"default"),hn=new Et(u,p,l,E,"default",e.compliance,w,d,e.tcf,c,e.consentSigningKey),yn=new Ct(g,E),M=r.jwtSecret??"",wn=new At(c,M),Lt=e.rateLimit,ur=Lt.enabled===!1?null:new De(Lt.windowMs,Lt.maxRequests),pr=new De(15*6e4,10),En=e.multiTenant?.enabled===!0?L=>sr(L,c,"default"):async L=>"default",x=e.basePath,Rn=`${x}/api/v1`,Sn=`${x}/admin`,Ut=`${x}/api`,Ne=!!e.dashboard,re=te(_n,"dashboard"),ke=Ne?Nn(Un(re,x,e.branding,e.compliance,r.mode)):"",Cn=`<!DOCTYPE html><html><head><title>Consenti API Docs</title>
|
|
205
194
|
<link rel="stylesheet" href="https://unpkg.com/swagger-ui-dist@5.17.14/swagger-ui.css" crossorigin="anonymous"></head>
|
|
206
195
|
<body><div id="swagger-ui"></div>
|
|
207
196
|
<script src="https://unpkg.com/swagger-ui-dist@5.17.14/swagger-ui-bundle.js" crossorigin="anonymous"></script>
|
|
208
197
|
<script src="https://unpkg.com/swagger-ui-dist@5.17.14/swagger-ui-standalone-preset.js" crossorigin="anonymous"></script>
|
|
209
|
-
<script>SwaggerUIBundle({urls:[{url:'${
|
|
210
|
-
</body></html>`,
|
|
211
|
-
Disallow: ${
|
|
212
|
-
`,
|
|
198
|
+
<script>SwaggerUIBundle({urls:[{url:'${Ut}/openapi.json',name:'Public API (/consenti/api)'},{url:'${Ut}/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>
|
|
199
|
+
</body></html>`,mr=`User-agent: *
|
|
200
|
+
Disallow: ${x}/
|
|
201
|
+
`,An=Ba(x,[{prefix:Ut,rateLimit:!1,routes:{"GET /docs":async()=>new Response(Cn,{headers:{"Content-Type":"text/html; charset=utf-8"}}),"GET /openapi.json":async()=>new Response(JSON.stringify(jt,null,2),{headers:{"Content-Type":"application/json"}}),"GET /openapi-admin.json":async()=>new Response(JSON.stringify(Ft,null,2),{headers:{"Content-Type":"application/json"}})}},{prefix:"",rateLimit:!1,routes:{"GET /robots.txt":async()=>new Response(mr,{headers:{"Content-Type":"text/plain; charset=utf-8"}})}},{prefix:Rn,rateLimit:!0,routes:{...Zs(F,j,a,Rn),...ui(hn,Ue,F,En,c,M),...pi(dr)}},{prefix:Sn,rateLimit:!1,routes:{"GET /widget.js":async()=>rr(te(_n,"widget","widget.js"),"application/javascript; charset=utf-8"),"GET /widget.css":async()=>rr(te(_n,"widget","widget.css"),"text/css; charset=utf-8")}},{prefix:Sn,rateLimit:!1,originCheck:!0,routes:{...bi(wn,c,r,M,pr),...xi(F,c,r,M),...$i(c,r,M),...ji(c,r,M),...Fi(yn,c,r,M),...Hi(c,r,M),...Bi(c,r,M),...Wi(c,f,r,M),...Vi(c,r,M,F),...qi(c,r,M),...Gi(c,r,M),...Ki(c,r,M),...Yi(c,r,M),...Xi(c,r,M),...Zi(c,r,M),...er(c,r,M),...nr(F,c,r,M,e,{usingJsonStorage:t,usingDefaultCredentials:n})}}],ur,c,En),Nt=null,kt=null,In,Tn,gr=new Promise((L,D)=>{In=L,Tn=D});c.connect().then(async()=>{if(r.mode==="local"&&r.adminEmail&&r.adminPassword&&await wn.bootstrap(r.adminEmail,r.adminPassword),await w.initialize({storage:c,config:e}),d.emit("ready"),In(),e.tcf?.enabled&&Mn(),e.dataRetention?.purgeAfterDays){let L=e.dataRetention.purgeAfterDays,D=()=>c.purgeExpiredConsents(L).then(N=>{N>0&&console.warn(`[consenti] Purged ${N} consent records older than ${L} days`)}).catch(N=>console.warn("[consenti] Data retention purge failed:",N));D(),Nt=setInterval(()=>{D()},1440*6e4)}if(e.dataRetention?.auditLogPurgeAfterDays){let L=e.dataRetention.auditLogPurgeAfterDays,D=()=>c.purgeExpiredAuditLogs(L).then(N=>{N>0&&console.warn(`[consenti] Purged ${N} audit log entries older than ${L} days`)}).catch(N=>console.warn("[consenti] Audit log retention purge failed:",N));D(),kt=setInterval(()=>{D()},1440*6e4)}}).catch(L=>{console.warn("[consenti] Storage connection failed:",L),Tn(L)});async function Mt(L,D){try{let Y=L.headers["x-forwarded-proto"]==="https"?"https":"http",me=L.headers.host??"localhost",W=new URL(L.url??"/",`${Y}://${me}`);if(Ne&&W.pathname.startsWith(x)&&await ja(W.pathname,x,re,D,ke))return;let ge=await zs(L,e.maxBodySize,e.trustedProxies),Me=await An(ge);if(W.pathname===`${x}/api/openapi.json`){D.writeHead(200,{"Content-Type":"application/json"}),D.end(JSON.stringify(jt,null,2));return}if(W.pathname===`${x}/api/openapi-admin.json`){D.writeHead(200,{"Content-Type":"application/json"}),D.end(JSON.stringify(Ft,null,2));return}if(W.pathname===`${x}/api/docs`){D.writeHead(200,{"Content-Type":"text/html; charset=utf-8"}),D.end(Cn);return}if(Me.status===404&&Ne&&W.pathname.startsWith(x)&&!W.pathname.startsWith(x+"/api/")&&!W.pathname.startsWith(x+"/admin/"))try{let xe=await Le(te(re,"index.html"));D.writeHead(200,{"Content-Type":"text/html; charset=utf-8"}),D.end(_e(xe,ke));return}catch{}await Qs(Me,D)}catch(N){D.headersSent||(N instanceof Pe?(D.writeHead(413,{"Content-Type":"application/json"}),D.end(JSON.stringify({error:"Payload Too Large"}))):(D.writeHead(500,{"Content-Type":"application/json"}),D.end(JSON.stringify({error:"Internal Server Error"}))))}}function fr(L,D,N){Mt(L,D).then(()=>{D.writableEnded||N()}).catch(N)}function _r(L,D){return Mt(L,D)}let vn=async(L,D)=>{L.all("/*",async(N,Y)=>{await Mt(N.raw,Y.raw),Y.hijack()})};return Object.defineProperty(vn,Symbol.for("skip-override"),{value:!0}),{router:fr,handler:_r,fastifyHandler:vn,honoApp:{fetch:async L=>{if(Ne){let N=new URL(L.url).pathname;if(N.startsWith(x)){let Y=N.slice(x.length)||"/";if(!Y.startsWith("/api/")&&!Y.startsWith("/admin/")){let me=Dt(Y)?te(re,Y):te(re,"index.html");if(!me.startsWith(re))return new Response("Forbidden",{status:403});try{let W=await Le(me),ge=Dt(me).toLowerCase(),Me=ge===".html"||ge==="",xe=cr[ge]??"application/octet-stream",hr=Me?_e(W,ke):W;return new Response(hr,{headers:{"Content-Type":xe}})}catch{try{let W=await Le(te(re,"index.html"));return new Response(_e(W,ke),{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 An(L)}},services:{profile:F,consent:hn,visitor:Ue,user:yn},storage:c,eventBus:d,ready:gr,destroy:async()=>{Nt&&clearInterval(Nt),kt&&clearInterval(kt),f.dispose(),e.tcf?.enabled&&xn(),await w.destroy()}}}export{fn as CategoryAction,gn as ConsentAction,Re as MongoDBAdapter,Ce as MySQLAdapter,Te as PostgreSQLAdapter,de as SQLiteAdapter,$g as createConsenti,xg as dashboardDir};
|
|
213
202
|
//# sourceMappingURL=index.js.map
|