nucleus-core-ts 0.10.49 → 0.10.50
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/dist/index.js
CHANGED
|
@@ -444,12 +444,12 @@ ${strMDSAlgs}`)}if(attestationStatementAlg!==void 0&&authenticatorGetInfo?.algor
|
|
|
444
444
|
AND c2.column_name = 'owner_type'
|
|
445
445
|
AND c1.table_schema = ${effectiveSchema}
|
|
446
446
|
AND c1.data_type = 'uuid'
|
|
447
|
-
`),polyTables=(Array.isArray(polyResult)?polyResult:polyResult.rows||[]).map((r)=>String(r.table_name||""));logger2.info("[AUTH] Change User ID - discovered schema references",{fkConstraints:fkConstraints.map((f)=>`${f.table_name}.${f.column_name} (${f.constraint_name})`),userIdColumns:userIdColumns.map((u)=>`${u.table_name}.${u.column_name}`),auditColumns:auditColumns.map((a)=>`${a.table_name}.${a.column_name}`),polyTables});let allUpdates=new Map,addUpdate=(tableName,columnName)=>{if(!allUpdates.has(tableName))allUpdates.set(tableName,new Set);allUpdates.get(tableName)?.add(columnName)};for(let fk of fkConstraints)addUpdate(fk.table_name,fk.column_name);for(let col13 of userIdColumns)addUpdate(col13.table_name,col13.column_name);for(let col13 of auditColumns)addUpdate(col13.table_name,col13.column_name);logger2.info("[AUTH] Change User ID - all column updates to execute",{updates:Array.from(allUpdates.entries()).map(([t,cols])=>`${t}: [${Array.from(cols).join(", ")}]`)}),await db.transaction(async(tx)=>{for(let fk of fkConstraints)await tx.execute(sql9.raw(`ALTER TABLE "${effectiveSchema}"."${fk.table_name}" DROP CONSTRAINT "${fk.constraint_name}"`));await tx.execute(sql9.raw(`UPDATE "${effectiveSchema}"."users" SET "id" = '${newId}' WHERE "id" = '${currentId}'`));for(let[tableName,columns]of allUpdates.entries())for(let columnName of columns)await tx.execute(sql9.raw(`UPDATE "${effectiveSchema}"."${tableName}" SET "${columnName}" = '${newId}' WHERE "${columnName}" = '${currentId}'`));for(let tableName of polyTables)await tx.execute(sql9.raw(`UPDATE "${effectiveSchema}"."${tableName}" SET "owner_id" = '${newId}' WHERE "owner_id" = '${currentId}' AND "owner_type" IN ('user', 'users', 'User')`));for(let fk of fkConstraints){let onDelete=fk.delete_rule==="CASCADE"?"ON DELETE CASCADE":fk.delete_rule==="SET NULL"?"ON DELETE SET NULL":"";await tx.execute(sql9.raw(`ALTER TABLE "${effectiveSchema}"."${fk.table_name}" ADD CONSTRAINT "${fk.constraint_name}" FOREIGN KEY ("${fk.column_name}") REFERENCES "${effectiveSchema}"."users" ("id") ${onDelete}`))}});let allAffectedTables=[...new Set([...fkConstraints.map((f)=>f.table_name),...userIdColumns.map((u)=>u.table_name),...auditColumns.map((a)=>a.table_name),...polyTables])];return logger2.info("[AUTH] User ID changed successfully",{godminId:requestingUserId,currentId,newId,email:targetUser.email,affectedTables:allAffectedTables,fkConstraintsDroppedAndReadded:fkConstraints.length}),await logger2.audit({entityName:"users",entityId:newId,operation:"CHANGE_USER_ID",userId:requestingUserId,summary:`Godmin changed user ID: ${currentId} \u2192 ${newId} (${targetUser.email})`,oldValues:{id:currentId},newValues:{id:newId},ipAddress:ctx.request.headers.get("x-forwarded-for")?.split(",")[0]?.trim()||"unknown",userAgent:ctx.request.headers.get("user-agent")||"unknown",path:new URL(ctx.request.url).pathname,query:""}),{success:!0,data:{oldId:currentId,newId,email:targetUser.email,affectedTables:allAffectedTables}}}catch(error3){return logger2.error("[AUTH] Failed to change user ID",{error:error3 instanceof Error?error3.message:String(error3),stack:error3 instanceof Error?error3.stack:void 0,currentId,newId}),ctx.set.status=500,{success:!1,message:error3 instanceof Error?error3.message:"Failed to change user ID"}}}})];return collectRoutes([{routes:declared}])}var init_changeUserId=__esm(()=>{init_esm3();init_routeCollector();init_requireGodmin();init_resolveMutationSchema()});function decideCreateUserAuthz(params){let{callerIsGod,callerRoleNames,allowRoles,requestedRoleNames,godminRoleName}=params,callerIsGodmin=callerIsGod||callerRoleNames.includes(godminRoleName);if(!(callerIsGodmin||callerRoleNames.some((n)=>allowRoles.includes(n))))return{allowed:!1,status:403,reason:"not_privileged"};if(requestedRoleNames.includes(godminRoleName)&&!callerIsGodmin)return{allowed:!1,status:403,reason:"godmin_assignment_forbidden"};if(!callerIsGodmin){let heldByCaller=new Set(callerRoleNames);if(!requestedRoleNames.every((r)=>heldByCaller.has(r)))return{allowed:!1,status:403,reason:"role_above_caller"}}return{allowed:!0,callerIsGodmin}}import{eq as eq42,inArray as inArray15}from"drizzle-orm";function createCreateUserRoute(config,createUserConfig){let{db,logger:logger2}=config;if(createUserConfig?.enabled===!1)return collectRoutes();let route=createUserConfig?.route||"/auth/admin/create-user",allowRoles=createUserConfig?.allowRoles??[],declared=[defineRoute({method:"POST",path:"/",body:CreateUserBodySchema,detail:{tags:["Authentication","Admin"],summary:"Admin Create User",description:"Create a fully active user with a password, roles, and optional profile. Admin-only. No email is sent."},handler:async(ctx)=>{let resolved=resolveAuthTablesForRequest(ctx.request,config),{usersTable,rolesTable:resolvedRolesTable,userRolesTable:resolvedUserRolesTable,schemaTables}=resolved,profilesTable=schemaTables?.profiles,rolesTable=resolvedRolesTable??config.rolesTable??null,userRolesTable=resolvedUserRolesTable??config.userRolesTable??null;if(!db||!usersTable)return ctx.set.status=500,{success:!1,message:"Database not configured"};let requestingUserId=ctx.request.headers.get("x-user-id");if(!requestingUserId)return ctx.set.status=401,{success:!1,message:"Unauthorized"};let requestingUser=(await db.select().from(usersTable).where(eq42(usersTable.id,requestingUserId)).limit(1))[0];if(!requestingUser)return ctx.set.status=401,{success:!1,message:"Unauthorized"};let callerIsGod=Boolean(requestingUser.isGod),callerRoleNames=[];if(!callerIsGod&&rolesTable&&userRolesTable){let rolesCols=rolesTable,userRolesCols=userRolesTable;callerRoleNames=(await db.select({name:rolesCols.name}).from(userRolesTable).innerJoin(rolesTable,eq42(rolesCols.id,userRolesCols.roleId)).where(eq42(userRolesCols.userId,requestingUserId))).map((r)=>r.name)}let requestedRoleIds=ctx.body.roleIds??[],foundRoles=[];if(requestedRoleIds.length>0&&rolesTable){let rolesCols=rolesTable;foundRoles=(await db.select({id:rolesCols.id,name:rolesCols.name}).from(rolesTable).where(inArray15(rolesCols.id,requestedRoleIds))).map((r)=>({id:r.id,name:r.name}))}let decision=decideCreateUserAuthz({callerIsGod,callerRoleNames,allowRoles,requestedRoleNames:foundRoles.map((r)=>r.name),godminRoleName:GODMIN_ROLE_NAME});if(!decision.allowed)return logger2.warn("[AUTH] create-user denied",{callerId:requestingUserId,reason:decision.reason}),ctx.set.status=decision.status,{success:!1,message:decision.reason==="godmin_assignment_forbidden"?"Forbidden: assigning the godmin role requires godmin privileges":"Forbidden"};let{email:rawEmail,password:password4,profile}=ctx.body,email=rawEmail.toLowerCase().trim(),passwordValidation=validatePasswordStrength(password4,config.authentication?.passwordPolicy,email);if(!passwordValidation.valid)return ctx.set.status=400,{success:!1,message:"Password too weak",errors:passwordValidation.errors};if((await db.select().from(usersTable).where(eq42(usersTable.email,email)).limit(1)).length>0)return logger2.warn("[AUTH] create-user failed \u2014 email already exists",{email}),ctx.set.status=409,{success:!1,message:"Email already registered"};let now=new Date,hashedPassword=await hashPassword(password4),insertValues={email,password:hashedPassword,emailVerified:!0,verifiedAt:now,isLocked:!1,createdAt:now,updatedAt:now},newUser=(await db.insert(usersTable).values(insertValues).returning())[0];if(!newUser)return logger2.error("[AUTH] create-user insert returned nothing",{email}),ctx.set.status=500,{success:!1,message:"Failed to create user"};let newUserId=newUser.id;if(requestedRoleIds.length>0&&rolesTable&&userRolesTable)try{let validRoleIds=foundRoles.map((r)=>r.id);if(validRoleIds.length>0)await db.insert(userRolesTable).values(validRoleIds.map((roleId)=>({userId:newUserId,roleId})));let skipped=requestedRoleIds.filter((id)=>!validRoleIds.includes(id));if(skipped.length>0)logger2.warn("[AUTH] create-user: some roleIds were not found",{userId:newUserId,skipped});if(foundRoles.some((r)=>r.name===GODMIN_ROLE_NAME))await db.update(usersTable).set({isGod:!0}).where(eq42(usersTable.id,newUserId))}catch(err){logger2.warn("[AUTH] create-user: failed to assign roles \u2014 continuing",{userId:newUserId,error:err instanceof Error?err.message:String(err)})}else{let createDefaultRoles=resolveDefaultRoleNames(config.authentication);if(createDefaultRoles.length>0)await assignDefaultRoles({db,userId:newUserId,roleNames:createDefaultRoles,rolesTable,userRolesTable,logger:logger2})}if(profile&&profilesTable)try{await db.insert(profilesTable).values({userId:newUserId,firstName:profile.firstName??null,lastName:profile.lastName??null,phoneNumber:profile.phoneNumber??null,createdAt:now,updatedAt:now})}catch(err){logger2.warn("[AUTH] create-user: failed to create profile \u2014 continuing",{userId:newUserId,error:err instanceof Error?err.message:String(err)})}let reqUrl=new URL(ctx.request.url),auditIp=ctx.request.headers.get("x-forwarded-for")?.split(",")[0]?.trim()||ctx.request.headers.get("x-real-ip")?.trim()||"unknown",auditUa=ctx.request.headers.get("user-agent")||"unknown";return logger2.audit({entityName:"users",entityId:newUserId,operation:"ADMIN_CREATE_USER",userId:requestingUserId,summary:`Admin ${requestingUserId} created user ${email}`,ipAddress:auditIp,userAgent:auditUa,path:reqUrl.pathname,query:reqUrl.search}),logger2.info("[AUTH] Admin created user",{newUserId,email,createdBy:requestingUserId}),{success:!0,message:"User created successfully",data:{id:newUserId,email}}}})];return collectRoutes([{prefix:route,routes:declared}])}var CreateUserBodySchema;var init_createUser=__esm(()=>{init_esm3();init_Authorization();init_routeCollector();init_assignDefaultRole();init_utils6();CreateUserBodySchema=Type.Object({email:Type.String({format:"email"}),password:Type.String({minLength:8}),roleIds:Type.Optional(Type.Array(Type.String())),profile:Type.Optional(Type.Object({firstName:Type.Optional(Type.String()),lastName:Type.Optional(Type.String()),phoneNumber:Type.Optional(Type.String())}))})});import{eq as eq43}from"drizzle-orm";function createHardDeleteUserRoute(config){let{db,logger:logger2}=config,declared=[defineRoute({method:"DELETE",path:"/auth/admin/hard-delete/:userId",params:HardDeleteUserParamsSchema,detail:{tags:["Authentication","Admin"],summary:"Hard Delete User",description:"Permanently delete a user and all related data (roles, sessions, profile, files, etc.). Godmin-only. Irreversible."},handler:async(ctx)=>{let resolved=resolveAuthTablesForRequest(ctx.request,config),{usersTable,userRolesTable,sessionsTable,oauthAccountsTable,apiKeysTable,schemaTables}=resolved;if(!db||!usersTable)return ctx.set.status=500,{success:!1,message:"Database not configured"};let requestingUserId=ctx.request.headers.get("x-user-id");if(!requestingUserId)return ctx.set.status=401,{success:!1,message:"Unauthorized"};if(!(await db.select().from(usersTable).where(eq43(usersTable.id,requestingUserId)).limit(1))[0]?.isGod)return ctx.set.status=403,{success:!1,message:"Forbidden \u2014 only godmin can hard-delete users"};let{userId:targetUserId}=ctx.params;if(targetUserId===requestingUserId)return ctx.set.status=400,{success:!1,message:"Cannot hard-delete yourself"};let targetUser=(await db.select().from(usersTable).where(eq43(usersTable.id,targetUserId)).limit(1))[0];if(!targetUser)return ctx.set.status=404,{success:!1,message:"User not found"};if(targetUser.isGod)return ctx.set.status=403,{success:!1,message:"Cannot hard-delete a godmin user"};let deletedRelations=[],col13=(table,name2)=>table[name2],profilesTable=schemaTables?.profiles,addressesTable=schemaTables?.addresses,phonesTable=schemaTables?.phones,filesTable=schemaTables?.files,runCascade=async(exec)=>{deletedRelations.length=0;let del=async(table,columnName,label)=>{if(!table)return;await exec.delete(table).where(eq43(col13(table,columnName),targetUserId)),deletedRelations.push(label)};await del(userRolesTable,"userId","user_roles"),await del(sessionsTable,"userId","sessions"),await del(oauthAccountsTable,"userId","oauth_accounts"),await del(apiKeysTable,"userId","api_keys"),await del(profilesTable,"userId","profiles"),await del(addressesTable,"ownerId","addresses"),await del(phonesTable,"ownerId","phones"),await del(filesTable,"uploadedBy","files"),await exec.delete(usersTable).where(eq43(usersTable.id,targetUserId))};try{let txDb=db;if(typeof txDb.transaction==="function")await txDb.transaction(async(tx)=>{await runCascade(tx)});else await runCascade(db)}catch(err){return logger2.error("[AUTH] hard-delete failed; transaction rolled back",{userId:targetUserId,error:err instanceof Error?err.message:String(err)}),ctx.set.status=500,{success:!1,message:"Hard delete failed and was rolled back"}}let reqUrl=new URL(ctx.request.url);return logger2.audit({entityName:"users",entityId:targetUserId,operation:"ADMIN_HARD_DELETE_USER",userId:requestingUserId,summary:`Godmin ${requestingUserId} hard-deleted user ${targetUser.email} (${targetUserId}). Cleaned: ${deletedRelations.join(", ")}`,ipAddress:ctx.request.headers.get("x-forwarded-for")?.split(",")[0]?.trim()||ctx.request.headers.get("x-real-ip")?.trim()||"unknown",userAgent:ctx.request.headers.get("user-agent")||"unknown",path:reqUrl.pathname,query:reqUrl.search}),logger2.info("[AUTH] Hard-deleted user",{targetUserId,email:targetUser.email,deletedBy:requestingUserId,cleanedRelations:deletedRelations}),{success:!0,message:"User permanently deleted",data:{userId:targetUserId,email:targetUser.email,cleanedRelations:deletedRelations}}}})];return collectRoutes([{routes:declared}])}var HardDeleteUserParamsSchema;var init_hardDeleteUser=__esm(()=>{init_esm3();init_routeCollector();HardDeleteUserParamsSchema=Type.Object({userId:Type.String()})});var exports_fetchUserRolesAndClaims={};__export(exports_fetchUserRolesAndClaims,{fetchUserRolesAndClaims:()=>fetchUserRolesAndClaims});import{and as and22,eq as eq44,inArray as inArray16}from"drizzle-orm";var noopLogger,fetchUserRolesAndClaims=async(db,userId,resolved,logger2=noopLogger)=>{let{userRolesTable,rolesTable,roleClaimsTable,claimsTable,usersTable}=resolved,result={roles:[],claims:[],claimScopes:{}};if(!userRolesTable||!rolesTable)return result;let roleIds=(await db.select().from(userRolesTable).where(and22(eq44(userRolesTable.userId,userId),activeRowsPredicate(userRolesTable)))).map((r)=>r.roleId);if(roleIds.length===0)return result;let roleRows=await db.select().from(rolesTable).where(inArray16(rolesTable.id,roleIds));if(result.roles=roleRows.map((r)=>r.name),roleClaimsTable&&claimsTable){let rows=(await db.select({action:claimsTable.action,scope:roleClaimsTable.scope}).from(roleClaimsTable).innerJoin(claimsTable,eq44(roleClaimsTable.claimId,claimsTable.id)).where(and22(inArray16(roleClaimsTable.roleId,roleIds),activeRowsPredicate(roleClaimsTable)))).filter((r)=>typeof r.action==="string"&&r.action.length>0).map((r)=>({action:r.action,scope:r.scope??null})),userData;if(rows.some((r)=>!!r.scope)&&usersTable)try{userData=(await db.select().from(usersTable).where(eq44(usersTable.id,userId)))[0]}catch(err){logger2.warn("[Authorization] Failed to load user row for scope resolution",{userId,error:err instanceof Error?err.message:String(err)})}let{claimScopes,dropped}=resolveClaimScopes(rows,userData,logger2),droppedSet=new Set(dropped);result.claims=Array.from(new Set(rows.map((r)=>r.action))).filter((a)=>!droppedSet.has(a)),result.claimScopes=claimScopes}return result};var init_fetchUserRolesAndClaims=__esm(()=>{init_activeRows();init_Middleware();noopLogger={debug:()=>{},info:()=>{},warn:()=>{},error:()=>{}}});var IMPERSONATION_COOKIE_NAMES;var init_impersonationCookies=__esm(()=>{IMPERSONATION_COOKIE_NAMES=["nucleus_impersonating_as","nucleus_godmin_id","nucleus_impersonating_email","nucleus_imp_sig"]});import{eq as eq45}from"drizzle-orm";function createImpersonateRoute(config,signAccessToken,signRefreshToken,verifyRefreshToken,createSession,saveSessionToDb,cookieConfig){let{db,logger:logger2}=config,requireGodmin2=createRequireGodmin(config),cookies={accessTokenName:cookieConfig?.accessTokenName||"access_token",refreshTokenName:cookieConfig?.refreshTokenName||"refresh_token",sessionTokenName:cookieConfig?.sessionTokenName||"session_token",accessTokenMaxAge:cookieConfig?.accessTokenMaxAge||900,refreshTokenMaxAge:cookieConfig?.refreshTokenMaxAge||604800,sessionTokenMaxAge:cookieConfig?.sessionTokenMaxAge||900,secure:cookieConfig?.secure??!0,sameSite:cookieConfig?.sameSite||"lax",path:cookieConfig?.path||"/",domain:cookieConfig?.domain},routePostAuthAdminImpersonate=defineRoute({method:"POST",path:"/auth/admin/impersonate",body:Type.Object({targetUserId:Type.String()}),detail:{tags:["Authentication"],summary:"Impersonate User",description:"Godmin: start a session as another user"},handler:async(ctx)=>{let{usersTable}=resolveAuthTablesForRequest(ctx.request,config);if(!db||!usersTable)return ctx.set.status=500,{success:!1,message:"Database not configured"};let{targetUserId}=ctx.body,gate=await requireGodmin2(ctx.request);if(!gate.ok)return ctx.set.status=gate.status,{success:!1,message:gate.message};let{callerId:requestingUserId,caller:requestingUser}=gate;if(requestingUserId===targetUserId)return ctx.set.status=400,{success:!1,message:"Cannot impersonate yourself"};let targetUser=(await db.select().from(usersTable).where(eq45(usersTable.id,targetUserId)).limit(1))[0];if(!targetUser)return ctx.set.status=404,{success:!1,message:"Target user not found"};let impRoles=[],impClaims=[],impClaimScopes={};if(config.db)try{let resolved=resolveAuthTablesForRequest(ctx.request,config),rc=await fetchUserRolesAndClaims(config.db,targetUserId,resolved);impRoles=rc.roles,impClaims=rc.claims,impClaimScopes=rc.claimScopes}catch{}let reqIp=ctx.request.headers.get("x-forwarded-for")?.split(",")[0]?.trim()||"unknown",sessionParams={userId:targetUserId,deviceInfo:{deviceName:"Impersonation Session",browserName:"Admin",osName:"Admin",ipAddress:reqIp},loginMethod:"impersonation"},sessionId=await createSession(sessionParams),accessToken=signAccessToken(targetUserId,impRoles.length>0?impRoles:void 0,impClaims.length>0?impClaims:void 0,ctx.request.headers.get("x-tenant-schema")??void 0,impClaimScopes,sessionId),refreshToken=signRefreshToken(targetUserId,void 0,sessionId);if(saveSessionToDb){let impSchemaName=ctx.request.headers.get("x-tenant-schema")||void 0;await saveSessionToDb(sessionId,sessionParams,impSchemaName)}logger2.info("[AUTH] Impersonation started",{godminId:requestingUserId,targetUserId,targetEmail:targetUser.email}),await logger2.audit({entityName:"users",entityId:targetUserId,operation:"IMPERSONATE_START",userId:requestingUserId,summary:`Godmin ${requestingUser.email} started impersonating ${targetUser.email}`,ipAddress:ctx.request.headers.get("x-forwarded-for")?.split(",")[0]?.trim()||"unknown",userAgent:ctx.request.headers.get("user-agent")||"unknown",path:new URL(ctx.request.url).pathname,query:""});let securePart=cookies.secure?"; Secure":"",domainPart=cookies.domain?`; Domain=${cookies.domain}`:"",cookieOptions=`; Path=${cookies.path}; HttpOnly; SameSite=${cookies.sameSite}${securePart}${domainPart}`,cookiesToSet=[`${cookies.accessTokenName}=${accessToken}${cookieOptions}; Max-Age=${cookies.accessTokenMaxAge}`,`${cookies.refreshTokenName}=${refreshToken}${cookieOptions}; Max-Age=${cookies.refreshTokenMaxAge}`,`${cookies.sessionTokenName}=${sessionId}${cookieOptions}; Max-Age=${cookies.sessionTokenMaxAge}`,`nucleus_impersonating_as=${targetUserId}${cookieOptions}; Max-Age=${cookies.sessionTokenMaxAge}`,`nucleus_godmin_id=${requestingUserId}${cookieOptions}; Max-Age=${cookies.sessionTokenMaxAge}`,`nucleus_impersonating_email=${encodeURIComponent(String(targetUser.email))}${cookieOptions}; Max-Age=${cookies.sessionTokenMaxAge}`,`nucleus_imp_sig=${signRefreshToken(requestingUserId)}${cookieOptions}; Max-Age=${cookies.refreshTokenMaxAge}`],jsonBody=JSON.stringify(withEnvelopeFlags({success:!0,data:{targetUser:{id:targetUser.id,email:targetUser.email},godminId:requestingUserId,sessionId}})),headers=new Headers;headers.set("Content-Type","application/json");for(let cookie of cookiesToSet)headers.append("Set-Cookie",cookie);return new Response(jsonBody,{status:200,headers})}}),routePostAuthAdminImpersonateStop=defineRoute({method:"POST",path:"/auth/admin/impersonate/stop",detail:{tags:["Authentication"],summary:"Stop Impersonation",description:"Godmin: restore own session after impersonation"},handler:async(ctx)=>{let{usersTable}=resolveAuthTablesForRequest(ctx.request,config);if(!db||!usersTable)return ctx.set.status=500,{success:!1,message:"Database not configured"};let cookieHeader=ctx.request.headers.get("cookie")||"",impSig=Object.fromEntries(cookieHeader.split(";").map((c)=>{let[k,...v]=c.trim().split("=");return[k?.trim(),v.join("=")]})).nucleus_imp_sig,verified=impSig?verifyRefreshToken(impSig):{valid:!1};if(!verified.valid||!verified.payload?.sub)return ctx.set.status=401,{success:!1,message:"No valid impersonation session"};let godminId=verified.payload.sub,godminUser=(await db.select().from(usersTable).where(eq45(usersTable.id,godminId)).limit(1))[0];if(!godminUser)return ctx.set.status=404,{success:!1,message:"Godmin user not found"};if(godminUser.isGod!==!0)return ctx.set.status=403,{success:!1,message:"Forbidden: godmin privileges required"};let stateRefusal=accountStateRefusal(godminUser);if(stateRefusal)return ctx.set.status=401,{success:!1,message:stateRefusal};let godminRoles=[],godminClaims=[],godminClaimScopes={};if(config.db)try{let resolvedStop=resolveAuthTablesForRequest(ctx.request,config),rcStop=await fetchUserRolesAndClaims(config.db,godminId,resolvedStop);godminRoles=rcStop.roles,godminClaims=rcStop.claims,godminClaimScopes=rcStop.claimScopes}catch{}let stopReqIp=ctx.request.headers.get("x-forwarded-for")?.split(",")[0]?.trim()||"unknown",sessionParams={userId:godminId,deviceInfo:{deviceName:"Restored Admin Session",browserName:"Admin",osName:"Admin",ipAddress:stopReqIp},loginMethod:"impersonation_stop"},sessionId=await createSession(sessionParams),accessToken=signAccessToken(godminId,godminRoles.length>0?godminRoles:void 0,godminClaims.length>0?godminClaims:void 0,ctx.request.headers.get("x-tenant-schema")??void 0,godminClaimScopes,sessionId),refreshToken=signRefreshToken(godminId,void 0,sessionId);if(saveSessionToDb){let stopSchemaName=ctx.request.headers.get("x-tenant-schema")||void 0;await saveSessionToDb(sessionId,sessionParams,stopSchemaName)}logger2.info("[AUTH] Impersonation stopped",{godminId}),await logger2.audit({entityName:"users",entityId:godminId,operation:"IMPERSONATE_STOP",userId:godminId,summary:`Godmin ${godminUser.email} stopped impersonation`,ipAddress:ctx.request.headers.get("x-forwarded-for")?.split(",")[0]?.trim()||"unknown",userAgent:ctx.request.headers.get("user-agent")||"unknown",path:new URL(ctx.request.url).pathname,query:""});let securePart=cookies.secure?"; Secure":"",domainPart=cookies.domain?`; Domain=${cookies.domain}`:"",cookieOptions=`; Path=${cookies.path}; HttpOnly; SameSite=${cookies.sameSite}${securePart}${domainPart}`,expiredOptions=`; Path=${cookies.path}; HttpOnly; SameSite=${cookies.sameSite}${securePart}${domainPart}; Max-Age=0`,cookiesToSet=[`${cookies.accessTokenName}=${accessToken}${cookieOptions}; Max-Age=${cookies.accessTokenMaxAge}`,`${cookies.refreshTokenName}=${refreshToken}${cookieOptions}; Max-Age=${cookies.refreshTokenMaxAge}`,`${cookies.sessionTokenName}=${sessionId}${cookieOptions}; Max-Age=${cookies.sessionTokenMaxAge}`,...IMPERSONATION_COOKIE_NAMES.map((name2)=>`${name2}=deleted${expiredOptions}`)],jsonBody=JSON.stringify(withEnvelopeFlags({success:!0,data:{godminUser:{id:godminUser.id,email:godminUser.email},sessionId}})),headers=new Headers;headers.set("Content-Type","application/json");for(let cookie of cookiesToSet)headers.append("Set-Cookie",cookie);return new Response(jsonBody,{status:200,headers})}});return collectRoutes([{routes:[routePostAuthAdminImpersonate,routePostAuthAdminImpersonateStop]}])}var init_impersonate=__esm(()=>{init_esm3();init_response();init_routeCollector();init_fetchUserRolesAndClaims();init_impersonationCookies();init_requireGodmin()});function clearedLockoutState(){return{failedLoginAttempts:0,isLocked:!1,lockedUntil:null}}function planPasswordRotation(input){let protectedRoles=new Set(input.protectedRoles.filter((name2)=>name2.trim()!=="")),plan={rotate:[],skipped:[]};for(let user of input.users){if(user.id===input.callerId){plan.skipped.push({id:user.id,reason:"caller"});continue}if(user.isGod){plan.skipped.push({id:user.id,reason:"godmin"});continue}if(user.roleNames.some((name2)=>protectedRoles.has(name2))){plan.skipped.push({id:user.id,reason:"protected_role"});continue}if(input.includeInactive!==!0&&user.isActive===!1){plan.skipped.push({id:user.id,reason:"inactive"});continue}plan.rotate.push(user.id)}return plan}function splitByRecoverability(users,rotate){let rotating=new Set(rotate),selfService=[],needsAdmin=[];for(let user of users){if(!rotating.has(user.id))continue;if(String(user.email??"").trim()==="")needsAdmin.push(user.id);else selfService.push(user.id)}return{selfService,needsAdmin}}import{randomBytes as randomBytes5}from"crypto";import{eq as eq46}from"drizzle-orm";function createPasswordAdminRoutes(config,options){let{db,logger:logger2}=config,log4=logger2.scoped(AUTH_LOGIN),passwordToolsEnabled=options?.passwordTools===!0,requireGodmin2=createRequireGodmin(config),routePostAuthAdminRotatePasswords=defineRoute({method:"POST",path:"/auth/admin/rotate-passwords",body:Type.Object({confirm:Type.Optional(Type.Boolean()),protectedRoles:Type.Optional(Type.Array(Type.String())),includeInactive:Type.Optional(Type.Boolean())}),detail:{tags:["Authentication"],summary:"Rotate every password except the ones named",description:"Without `confirm: true` this only reports what it would do. Confirmed, it gives every selected account a different random password, signs those sessions out, and tells nobody the new values \u2014 recovery is the reset link or an administrator."},handler:async(ctx)=>{if(!passwordToolsEnabled)return notEnabled(ctx);let gate=await requireGodmin2(ctx.request);if(!gate.ok)return ctx.set.status=gate.status,{success:!1,message:gate.message};let resolved=resolveAuthTablesForRequest(ctx.request,config),usersTable=resolved.usersTable,body=ctx.body,users=await db?.select().from(usersTable),roleNamesByUser=await loadRoleNames(resolved,users),candidates=users.map((row)=>({id:String(row.id),roleNames:roleNamesByUser.get(String(row.id))??[],isGod:row.isGod===!0,email:row.email??null,isActive:row.isActive})),plan=planPasswordRotation({users:candidates,callerId:gate.callerId,protectedRoles:body.protectedRoles??[],includeInactive:body.includeInactive===!0}),recovery=splitByRecoverability(candidates,plan.rotate);if(body.confirm!==!0)return{success:!0,data:{preview:!0,willRotate:plan.rotate.length,skipped:plan.skipped.length,skippedReasons:countReasons(plan.skipped),canResetThemselves:recovery.selfService.length,needAdministrator:recovery.needsAdmin.length}};if(plan.rotate.length===0)return{success:!0,data:{preview:!1,rotated:0,signedOut:0}};let rotated=0;for(let userId of plan.rotate){let hashed=await hashPassword(randomBytes5(24).toString("base64url"));await db?.update(usersTable).set({password:hashed,updatedAt:new Date}).where(eq46(usersTable.id,userId)),rotated+=1}let signedOut=await revokeSessions(resolved,plan.rotate);return log4.warn("Bulk password rotation",{by:gate.callerId,rotated,signedOut,skipped:plan.skipped.length}),{success:!0,data:{preview:!1,rotated,signedOut,canResetThemselves:recovery.selfService.length,needAdministrator:recovery.needsAdmin.length}}}}),routePostAuthAdminSetUserPassword=defineRoute({method:"POST",path:"/auth/admin/set-user-password",body:Type.Object({userId:Type.String(),password:Type.String()}),detail:{tags:["Authentication"],summary:"Set one account's password",description:"For the person who has no email address and cannot use the reset link. Applies the same strength policy as a self-service change and ends that account\u2019s existing sessions."},handler:async(ctx)=>{if(!passwordToolsEnabled)return notEnabled(ctx);let gate=await requireGodmin2(ctx.request);if(!gate.ok)return ctx.set.status=gate.status,{success:!1,message:gate.message};let resolved=resolveAuthTablesForRequest(ctx.request,config),usersTable=resolved.usersTable,{userId,password:password4}=ctx.body,strength=validatePasswordStrength(password4);if(!strength.valid)return ctx.set.status=400,{success:!1,message:strength.errors?.join(" ")??"Password is too weak"};let rows=await db?.select().from(usersTable).where(eq46(usersTable.id,userId)).limit(1);if(!rows||rows.length===0)return ctx.set.status=404,{success:!1,message:"User not found"};await db?.update(usersTable).set({password:await hashPassword(password4),updatedAt:new Date,...clearedLockoutState()}).where(eq46(usersTable.id,userId));let signedOut=await revokeSessions(resolved,[userId]);return log4.warn("Password set by administrator",{by:gate.callerId,userId,signedOut}),{success:!0,data:{signedOut,lockoutCleared:!0}}}});return collectRoutes([{routes:[routePostAuthAdminRotatePasswords,routePostAuthAdminSetUserPassword]}]);function notEnabled(ctx){return ctx.set.status=404,{success:!1,message:"Not found"}}async function loadRoleNames(resolved,users){let byUser=new Map,userRoles=resolved.userRolesTable,roles=resolved.rolesTable;if(!db||!userRoles||!roles||users.length===0)return byUser;let roleRows=await db.select().from(roles),roleNameById=new Map(roleRows.map((row)=>[String(row.id),String(row.name??"")])),links=await db.select().from(userRoles);for(let link of links){let userId=String(link.userId??""),name2=roleNameById.get(String(link.roleId??""));if(!userId||!name2)continue;byUser.set(userId,[...byUser.get(userId)??[],name2])}return byUser}async function revokeSessions(resolved,userIds){return revokeUserSessions({db,sessionsTable:resolved.sessionsTable,onError:(error3)=>log4.error("Sessions could not be revoked after a password change",{error:error3})},userIds)}}function countReasons(skipped){let counts={};for(let item of skipped)counts[item.reason]=(counts[item.reason]??0)+1;return counts}var init_passwordAdmin=__esm(()=>{init_esm3();init_scopes();init_routeCollector();init_utils6();init_requireGodmin();init_revokeUserSessions()});import{eq as eq47}from"drizzle-orm";function createUserStateRoutes(config){let{db,logger:logger2}=config,log4=logger2.scoped(AUTH_LOGIN),requireGodmin2=createRequireGodmin(config);async function loadTarget(request,userId){let resolved=resolveAuthTablesForRequest(request,config),usersTable=resolved.usersTable;if(!db||!usersTable)return{ok:!1,status:500,message:"Database not configured"};let user=(await db.select().from(usersTable).where(eq47(usersTable.id,userId)).limit(1))?.[0];if(!user)return{ok:!1,status:404,message:"User not found"};return{ok:!0,usersTable,sessionsTable:resolved.sessionsTable??null,user}}async function writeState(usersTable,userId,changes){await db?.update(usersTable).set({...changes,updatedAt:new Date}).where(eq47(usersTable.id,userId))}let routePostAuthAdminLockUser=defineRoute({method:"POST",path:"/auth/admin/lock-user",body:Type.Object({userId:Type.String(),until:Type.Optional(Type.String())}),detail:{tags:["Authentication"],summary:"Lock one account",description:"Refuses further login attempts. `until` is an ISO timestamp for a temporary lock; omit it to lock indefinitely. Cannot be used on the calling administrator."},handler:async(ctx)=>{let gate=await requireGodmin2(ctx.request);if(!gate.ok)return ctx.set.status=gate.status,{success:!1,message:gate.message};let{userId,until}=ctx.body,target2=await loadTarget(ctx.request,userId);if(!target2.ok)return ctx.set.status=target2.status,{success:!1,message:target2.message};if(userId===gate.callerId)return ctx.set.status=400,{success:!1,message:"An administrator cannot lock their own account"};let lockedUntil=until?new Date(until):null;if(until&&Number.isNaN(lockedUntil?.getTime()))return ctx.set.status=400,{success:!1,message:"`until` is not a valid date"};await writeState(target2.usersTable,userId,{isLocked:!0,lockedUntil});let signedOut=await revokeUserSessions({db,sessionsTable:target2.sessionsTable,onError:(error3)=>log4.error("Sessions could not be revoked after locking an account",{error:error3,userId})},[userId]);return log4.warn("Account locked by administrator",{by:gate.callerId,userId,until:lockedUntil,signedOut}),{success:!0,data:{isLocked:!0,lockedUntil,signedOut}}}}),routePostAuthAdminUnlockUser=defineRoute({method:"POST",path:"/auth/admin/unlock-user",body:Type.Object({userId:Type.String()}),detail:{tags:["Authentication"],summary:"Release one account from lockout",description:"Clears isLocked, lockedUntil AND failedLoginAttempts together, without touching the password. Clearing only isLocked is not a release: the login route sends any account at or above the captcha threshold through the captcha gate before it checks the password, and the counter only clears on a login that succeeds."},handler:async(ctx)=>{let gate=await requireGodmin2(ctx.request);if(!gate.ok)return ctx.set.status=gate.status,{success:!1,message:gate.message};let{userId}=ctx.body,target2=await loadTarget(ctx.request,userId);if(!target2.ok)return ctx.set.status=target2.status,{success:!1,message:target2.message};let wasLocked=target2.user.isLocked===!0,clearedFailedAttempts=Number(target2.user.failedLoginAttempts??0);return await writeState(target2.usersTable,userId,clearedLockoutState()),log4.warn("Account lockout cleared by administrator",{by:gate.callerId,userId,wasLocked,clearedFailedAttempts}),{success:!0,data:{wasLocked,clearedFailedAttempts}}}}),routePostAuthAdminMarkEmailVerified=defineRoute({method:"POST",path:"/auth/admin/mark-email-verified",body:Type.Object({userId:Type.String()}),detail:{tags:["Authentication"],summary:"Mark one account as email-verified",description:"For an installation with no working mail transport, where the verification message can never arrive. Idempotent: an account that is already verified keeps its original timestamp."},handler:async(ctx)=>{let gate=await requireGodmin2(ctx.request);if(!gate.ok)return ctx.set.status=gate.status,{success:!1,message:gate.message};let{userId}=ctx.body,target2=await loadTarget(ctx.request,userId);if(!target2.ok)return ctx.set.status=target2.status,{success:!1,message:target2.message};let alreadyVerified=Boolean(target2.user.verifiedAt),verifiedAt=alreadyVerified?target2.user.verifiedAt:new Date;if(!alreadyVerified)await writeState(target2.usersTable,userId,{verifiedAt,emailVerified:!0});return log4.warn("Email marked verified by administrator",{by:gate.callerId,userId,alreadyVerified}),{success:!0,data:{verifiedAt,alreadyVerified}}}});return collectRoutes([{routes:[routePostAuthAdminLockUser,routePostAuthAdminUnlockUser,routePostAuthAdminMarkEmailVerified]}])}var init_userState=__esm(()=>{init_esm3();init_scopes();init_routeCollector();init_requireGodmin();init_revokeUserSessions()});import{and as and23,count as count2,eq as eq48,sql as sql10}from"drizzle-orm";function createApiKeyRoutes(app,config,apiKeysConfig){let basePath=apiKeysConfig.route||"/auth/api-keys",apiKeysTable=config.apiKeysTable,{db,logger:logger2}=config;if(!apiKeysTable||!db){logger2.warn("[API_KEYS] api_keys table or database not available. Skipping API key routes.");return}let getResolvedConfig=(request)=>{let resolved=resolveAuthTablesForRequest(request,config),reqApiKeysTable=resolved.apiKeysTable??apiKeysTable;return{config:{...config,usersTable:resolved.usersTable??config.usersTable,userRolesTable:resolved.userRolesTable,rolesTable:resolved.rolesTable,roleClaimsTable:resolved.roleClaimsTable,claimsTable:resolved.claimsTable,apiKeysTable:reqApiKeysTable,schemaTables:resolved.schemaTables},apiKeysTable:reqApiKeysTable}},keyPrefix=apiKeysConfig.keyPrefix||"nk_live",maxKeysPerUser=apiKeysConfig.maxKeysPerUser||10,preventManagement=apiKeysConfig.preventApiKeyManagement??!1,routePostRoot=defineRoute({method:"POST",path:"/",handler:async({request,set:set2,body:rawBody})=>{let resolved=getResolvedConfig(request),reqApiKeysTable=resolved.apiKeysTable,reqConfig=resolved.config;if(preventManagement)return set2.status=403,{isSuccess:!1,message:"API key management is disabled via configuration",data:null};if(request.headers.get("x-auth-type")==="api_key")return set2.status=403,{isSuccess:!1,message:"API keys cannot be created using another API key",data:null};let userId=request.headers.get("x-user-id");if(!userId)return set2.status=401,{isSuccess:!1,message:"Unauthenticated",data:null};let body;try{body=rawBody??{}}catch{return set2.status=400,{isSuccess:!1,message:"Invalid request body",data:null}}if(!body.name||typeof body.name!=="string"||body.name.trim().length===0)return set2.status=400,{isSuccess:!1,message:"Name is required",data:null};if(body.ownerType==="application"){if(!(apiKeysConfig.allowApplicationKeys??!0))return set2.status=403,{isSuccess:!1,message:"Application API keys are not allowed",data:null};if(!body.applicationName||body.applicationName.trim().length===0)return set2.status=400,{isSuccess:!1,message:"Application name is required for application keys",data:null}}if(((await db.select({count:count2()}).from(reqApiKeysTable).where(and23(eq48(col13(reqApiKeysTable,"userId"),userId),eq48(col13(reqApiKeysTable,"isActive"),!0))))[0]?.count||0)>=maxKeysPerUser)return set2.status=400,{isSuccess:!1,message:`Maximum API key limit reached (${maxKeysPerUser})`,data:null};let userRoles=await getUserRoleNames(reqConfig,userId),userClaims=await getUserClaimActions(reqConfig,userId),isGodmin4=userRoles.some((r)=>isGodminRole(r)),requestedRoles=body.allowedRoles||[],requestedClaims=body.allowedClaims||[],requestedScopes=body.allowedScopes||[],stripGodmin=(roles)=>roles.filter((r)=>!isGodminRole(r)),finalRoles,finalClaims;if(isGodmin4)finalRoles=requestedRoles.length>0?stripGodmin(requestedRoles):[],finalClaims=requestedClaims.length>0?requestedClaims:userClaims;else{let validRoles=intersectPermissions(userRoles,requestedRoles),validClaims=intersectPermissions(userClaims,requestedClaims);if(requestedRoles.length>0&&validRoles.length!==requestedRoles.length){let disallowed=requestedRoles.filter((r)=>!userRoles.includes(r));return set2.status=403,{isSuccess:!1,message:`Cannot assign roles you do not have: ${disallowed.join(", ")}`,data:null}}if(requestedClaims.length>0&&validClaims.length!==requestedClaims.length){let disallowed=requestedClaims.filter((c)=>!userClaims.includes(c));return set2.status=403,{isSuccess:!1,message:`Cannot assign claims you do not have: ${disallowed.join(", ")}`,data:null}}if(requestedScopes.length>0){let userScopes=await getUserScopes(reqConfig,userId);if(intersectPermissions(userScopes,requestedScopes).length!==requestedScopes.length){let disallowed=requestedScopes.filter((s)=>!userScopes.includes(s));return set2.status=403,{isSuccess:!1,message:`Cannot assign scopes you do not have: ${disallowed.join(", ")}`,data:null}}}finalRoles=stripGodmin(requestedRoles.length>0?validRoles:userRoles),finalClaims=requestedClaims.length>0?validClaims:userClaims}let expiresAt=null,expiresInStr=body.expiresIn||apiKeysConfig.defaultExpiresIn;if(expiresInStr){let seconds=parseTimeToSeconds2(expiresInStr);expiresAt=new Date(Date.now()+seconds*1000)}let generated=generateApiKey(keyPrefix);try{let createdId=(await db.insert(reqApiKeysTable).values({userId,name:body.name.trim(),description:body.description?.trim()||null,keyHash:generated.keyHash,keyPreview:generated.keyPreview,ownerType:body.ownerType||"personal",applicationName:body.applicationName?.trim()||null,expiresAt,isActive:!0,usageCount:0,createdAt:new Date,updatedAt:new Date}).returning())[0]?.id;if(finalRoles.length>0||finalClaims.length>0||requestedScopes.length>0){let jsonbUpdate={};if(finalRoles.length>0)jsonbUpdate.allowedRoles=JSON.stringify(finalRoles);if(finalClaims.length>0)jsonbUpdate.allowedClaims=JSON.stringify(finalClaims);if(requestedScopes.length>0)jsonbUpdate.allowedScopes=JSON.stringify(requestedScopes);await db.update(reqApiKeysTable).set(jsonbUpdate).where(eq48(col13(reqApiKeysTable,"keyHash"),generated.keyHash))}return logger2.info("[API_KEYS] API key created",{keyId:createdId,userId,ownerType:body.ownerType||"personal",applicationName:body.applicationName,rolesCount:finalRoles.length,claimsCount:finalClaims.length,expiresAt:expiresAt?.toISOString()||"never"}),{isSuccess:!0,message:"API key created successfully. Save this key \u2014 it will not be shown again.",data:{id:createdId,key:generated.rawKey,keyPreview:generated.keyPreview,name:body.name.trim(),ownerType:body.ownerType||"personal",applicationName:body.applicationName||null,allowedRoles:finalRoles,allowedClaims:finalClaims,allowedScopes:requestedScopes,expiresAt:expiresAt?.toISOString()||null}}}catch(err){let errDetail;try{errDetail=JSON.stringify(err,Object.getOwnPropertyNames(err))}catch{errDetail=err instanceof Error?err.message:String(err)}return logger2.error("[API_KEYS] Failed to create API key",{error:errDetail,userId}),set2.status=500,{isSuccess:!1,message:"Failed to create API key",data:null}}}}),routeGetRoot=defineRoute({method:"GET",path:"/",detail:{tags:["API keys"],summary:"List the caller's API keys",description:"Every key the caller owns, revoked ones included \u2014 a revoked key is history, not "+"garbage, and it is the only place `revokedAt` / `revokedReason` can be read. Filter on `isActive` for the live set; that is also what `maxKeysPerUser` counts."},handler:async({request,set:set2})=>{let{apiKeysTable:reqApiKeysTable}=getResolvedConfig(request),userId=request.headers.get("x-user-id");if(!userId)return set2.status=401,{isSuccess:!1,message:"Unauthenticated",data:null};let items=(await db.select().from(reqApiKeysTable).where(eq48(col13(reqApiKeysTable,"userId"),userId)).orderBy(sql10`created_at DESC`)).map((row)=>sanitizeKeyForResponse(row));return{isSuccess:!0,data:{items,totalCount:items.length}}}}),routeGetId=defineRoute({method:"GET",path:"/:id",detail:{tags:["API keys"],summary:"Read one API key",description:"A REVOKED key still answers 200 here \u2014 revoking does not delete the row. Read "+"`isActive` / `revokedAt` rather than the status to decide whether the key is live. 404 means no such key, or a key belonging to somebody else."},handler:async({params,request,set:set2})=>{let{apiKeysTable:reqApiKeysTable}=getResolvedConfig(request),userId=request.headers.get("x-user-id");if(!userId)return set2.status=401,{isSuccess:!1,message:"Unauthenticated",data:null};let keyId=params.id,record3=(await db.select().from(reqApiKeysTable).where(and23(eq48(col13(reqApiKeysTable,"id"),keyId),eq48(col13(reqApiKeysTable,"userId"),userId))).limit(1))[0];if(!record3)return set2.status=404,{isSuccess:!1,message:"API key not found",data:null};return{isSuccess:!0,data:sanitizeKeyForResponse(record3)}}}),routePatchId=defineRoute({method:"PATCH",path:"/:id",handler:async({params,request,set:set2,body:rawBody})=>{let resolved=getResolvedConfig(request),reqApiKeysTable=resolved.apiKeysTable,reqConfig=resolved.config;if(preventManagement)return set2.status=403,{isSuccess:!1,message:"API key management is disabled via configuration",data:null};if(request.headers.get("x-auth-type")==="api_key")return set2.status=403,{isSuccess:!1,message:"API keys cannot be modified using another API key",data:null};let userId=request.headers.get("x-user-id");if(!userId)return set2.status=401,{isSuccess:!1,message:"Unauthenticated",data:null};let keyId=params.id,body;try{body=rawBody??{}}catch{return set2.status=400,{isSuccess:!1,message:"Invalid request body",data:null}}let existing=(await db.select().from(reqApiKeysTable).where(and23(eq48(col13(reqApiKeysTable,"id"),keyId),eq48(col13(reqApiKeysTable,"userId"),userId))).limit(1))[0];if(!existing)return set2.status=404,{isSuccess:!1,message:"API key not found",data:null};if(existing.revokedAt)return set2.status=400,{isSuccess:!1,message:"Cannot modify a revoked API key",data:null};let userRoles=await getUserRoleNames(reqConfig,userId),userClaims=await getUserClaimActions(reqConfig,userId),updatePayload={updatedAt:new Date};if(body.name!==void 0){if(typeof body.name!=="string"||body.name.trim().length===0)return set2.status=400,{isSuccess:!1,message:"Name cannot be empty",data:null};updatePayload.name=body.name.trim()}if(body.description!==void 0)updatePayload.description=body.description?.trim()||null;if(body.allowedRoles!==void 0){if(intersectPermissions(userRoles,body.allowedRoles).length!==body.allowedRoles.length){let disallowed=body.allowedRoles.filter((r)=>!userRoles.includes(r));return set2.status=403,{isSuccess:!1,message:`Cannot assign roles you do not have: ${disallowed.join(", ")}`,data:null}}updatePayload.allowedRoles=body.allowedRoles.filter((r)=>!isGodminRole(r))}if(body.allowedClaims!==void 0){if(intersectPermissions(userClaims,body.allowedClaims).length!==body.allowedClaims.length){let disallowed=body.allowedClaims.filter((c)=>!userClaims.includes(c));return set2.status=403,{isSuccess:!1,message:`Cannot assign claims you do not have: ${disallowed.join(", ")}`,data:null}}updatePayload.allowedClaims=body.allowedClaims}if(body.allowedScopes!==void 0){if(body.allowedScopes.length>0){let userScopes=await getUserScopes(reqConfig,userId);if(intersectPermissions(userScopes,body.allowedScopes).length!==body.allowedScopes.length){let disallowed=body.allowedScopes.filter((s)=>!userScopes.includes(s));return set2.status=403,{isSuccess:!1,message:`Cannot assign scopes you do not have: ${disallowed.join(", ")}`,data:null}}}updatePayload.allowedScopes=body.allowedScopes}try{await db.update(reqApiKeysTable).set(updatePayload).where(eq48(col13(reqApiKeysTable,"id"),keyId)),logger2.info("[API_KEYS] API key updated",{keyId,userId});let updated=await db.select().from(reqApiKeysTable).where(eq48(col13(reqApiKeysTable,"id"),keyId)).limit(1);return{isSuccess:!0,message:"API key updated successfully",data:sanitizeKeyForResponse(updated[0])}}catch(err){return logger2.error("[API_KEYS] Failed to update API key",{error:err,keyId,userId}),set2.status=500,{isSuccess:!1,message:"Failed to update API key",data:null}}}}),revokeApiKey=async({params,request,set:set2,body:rawBody})=>{let{apiKeysTable:reqApiKeysTable}=getResolvedConfig(request);if(request.headers.get("x-auth-type")==="api_key")return set2.status=403,{isSuccess:!1,message:"API keys cannot be revoked using another API key",data:null};let userId=request.headers.get("x-user-id");if(!userId)return set2.status=401,{isSuccess:!1,message:"Unauthenticated",data:null};let keyId=params.id,body={};try{if(rawBody)body=rawBody}catch{}let record3=(await db.select().from(reqApiKeysTable).where(and23(eq48(col13(reqApiKeysTable,"id"),keyId),eq48(col13(reqApiKeysTable,"userId"),userId))).limit(1))[0];if(!record3)return set2.status=404,{isSuccess:!1,message:"API key not found",data:null};if(record3.revokedAt)return set2.status=400,{isSuccess:!1,message:"API key is already revoked",data:null};let revokedAt=new Date,revokedReason=body.reason||"user_revoked";try{await db.update(reqApiKeysTable).set({isActive:!1,revokedAt,revokedReason,updatedAt:revokedAt}).where(eq48(col13(reqApiKeysTable,"id"),keyId)),logger2.info("[API_KEYS] API key revoked",{keyId,userId,reason:revokedReason});let revoked=(await db.select().from(reqApiKeysTable).where(eq48(col13(reqApiKeysTable,"id"),keyId)).limit(1))[0]??{...record3,isActive:!1,revokedAt,revokedReason,updatedAt:revokedAt};return{isSuccess:!0,message:"API key revoked. It can no longer authenticate. This is a revoke, not a delete \u2014 "+`the record is retained as history and still reads at GET ${basePath}/${keyId}.`,data:sanitizeKeyForResponse(revoked)}}catch(err){return logger2.error("[API_KEYS] Failed to revoke API key",{error:err,keyId,userId}),set2.status=500,{isSuccess:!1,message:"Failed to revoke API key",data:null}}},revokeDetail=(summary)=>({tags:["API keys"],summary,description:"Revokes the key: it stops authenticating immediately, on this request. It is NOT a "+"delete \u2014 the row is retained as the audit record (`revokedAt`, `revokedReason`, "+"`usageCount`, `lastUsedAt`), so `GET /:id` still answers 200 with `isActive:false` and the list still carries it. The answer carries the revoked record. Revoking an "+"already-revoked key answers 400, not 404 \u2014 the row is still there. Revoking is "+"deliberately NOT blocked by `preventApiKeyManagement`: being unable to kill a leaked key because management is off would be worse than the rule it enforces."}),routePostIdRevoke=defineRoute({method:"POST",path:"/:id/revoke",detail:revokeDetail("Revoke an API key"),handler:(ctx)=>revokeApiKey(ctx)}),routeDeleteId=defineRoute({method:"DELETE",path:"/:id",detail:revokeDetail("Revoke an API key (alias of POST /:id/revoke)"),handler:(ctx)=>revokeApiKey(ctx)});mountRoutes(app,[{prefix:basePath,routes:[routePostRoot,routeGetRoot,routeGetId,routePatchId,routePostIdRevoke,routeDeleteId]}]),logger2.info(`[API_KEYS] Routes registered at ${basePath}`)}var col13=(table,name2)=>table[name2],getUserRoleNames=async(config,userId)=>{if(!config.db||!config.userRolesTable||!config.rolesTable)return[];return(await config.db.select({name:col13(config.rolesTable,"name")}).from(config.userRolesTable).innerJoin(config.rolesTable,eq48(col13(config.rolesTable,"id"),col13(config.userRolesTable,"roleId"))).where(eq48(col13(config.userRolesTable,"userId"),userId))).map((r)=>r.name).filter((n)=>n!==void 0)},getUserClaimActions=async(config,userId)=>{if(!config.db||!config.userRolesTable||!config.roleClaimsTable||!config.claimsTable)return[];let rows=await config.db.select({action:col13(config.claimsTable,"action")}).from(config.userRolesTable).innerJoin(config.roleClaimsTable,eq48(col13(config.roleClaimsTable,"roleId"),col13(config.userRolesTable,"roleId"))).innerJoin(config.claimsTable,eq48(col13(config.claimsTable,"id"),col13(config.roleClaimsTable,"claimId"))).where(eq48(col13(config.userRolesTable,"userId"),userId)),uniqueActions=new Set(rows.map((r)=>r.action));return Array.from(uniqueActions).filter((a)=>a!==void 0)},getUserScopes=async(config,userId)=>{if(!config.db||!config.userRolesTable||!config.roleClaimsTable)return[];let rows=await config.db.select({scope:col13(config.roleClaimsTable,"scope")}).from(config.userRolesTable).innerJoin(config.roleClaimsTable,eq48(col13(config.roleClaimsTable,"roleId"),col13(config.userRolesTable,"roleId"))).where(eq48(col13(config.userRolesTable,"userId"),userId)),uniqueScopes=new Set(rows.map((r)=>r.scope).filter((s)=>s!==null&&s!==void 0&&s.length>0));return Array.from(uniqueScopes)},sanitizeKeyForResponse=(record3)=>{let{keyHash:_keyHash,...safe}=record3;return safe};var init_apiKeys=__esm(()=>{init_ApiKey();init_GodminSetup();init_routeMount();init_utils5()});function createCaptchaRoutes(config){let{captchaService,logger:logger2,basePath="/auth/captcha"}=config;if(!captchaService.isEnabled())return collectRoutes();let warnedUnattributable=!1,warnUnattributableOnce=()=>{if(warnedUnattributable)return;warnedUnattributable=!0,logger2.warn("[CAPTCHA] No client address on the request \u2014 the per-IP generate limit is OFF. "+"Every caller would otherwise share one bucket, which locks out an entire office behind one egress. Configure the proxy or ingress to forward X-Forwarded-For.")},routeGetBasepathGenerate=defineRoute({method:"GET",path:"/generate",query:GenerateCaptchaQuerySchema,detail:{tags:["Captcha"],summary:"Generate Captcha",description:"Generate a new captcha challenge"},handler:async(ctx)=>{let{type:type4,difficulty}=ctx.query,ipAddress=ctx.request.headers.get("x-forwarded-for")?.split(",")[0]?.trim()||ctx.request.headers.get("x-real-ip")?.trim()||ctx.request.headers.get("cf-connecting-ip")?.trim()||void 0;if(!ipAddress)warnUnattributableOnce();let result=await captchaService.generate(type4,difficulty,ipAddress);if(result.rateLimited)return ctx.set.status=429,{success:!1,message:result.message??"Too many requests. Please try again later."};return logger2.info("[CAPTCHA] Challenge generated via endpoint",{challengeId:result.challengeId,type:result.type,ipAddress}),{success:!0,data:{challengeId:result.challengeId,type:result.type,question:result.question,expiresAt:result.expiresAt,...result.imageData?{imageData:result.imageData}:{},...result.puzzleData?{puzzleData:{pieces:result.puzzleData.pieces}}:{}}}}}),routePostBasepathValidate=defineRoute({method:"POST",path:"/validate",body:ValidateCaptchaBodySchema,detail:{tags:["Captcha"],summary:"Validate Captcha",description:"Validate a captcha answer"},handler:async(ctx)=>{let{challengeId,answer}=ctx.body,result=await captchaService.validate(challengeId,answer);if(!result.valid)ctx.set.status=400;return result}});return collectRoutes([{prefix:basePath,routes:[routeGetBasepathGenerate,routePostBasepathValidate]}])}var GenerateCaptchaQuerySchema,ValidateCaptchaBodySchema;var init_captcha=__esm(()=>{init_esm3();init_routeCollector();GenerateCaptchaQuerySchema=Type.Object({type:Type.Optional(Type.Union([Type.Literal("math"),Type.Literal("image"),Type.Literal("puzzle"),Type.Literal("text")])),difficulty:Type.Optional(Type.Union([Type.Literal("easy"),Type.Literal("medium"),Type.Literal("hard")]))}),ValidateCaptchaBodySchema=Type.Object({challengeId:Type.String(),answer:Type.String()})});import{eq as eq49}from"drizzle-orm";function createCheckRoute(config,checkConfig){let{db,logger:logger2}=config,route=checkConfig.route||"/auth/check",routePostCheck=defineRoute({method:"POST",path:"/",detail:{tags:["Auth"],summary:"Check authorization for a given entity and method",description:"Allows consumer/resource servers to perform full claim checks (including scope) against the IDP's authorization system."},handler:async(ctx)=>{let{body,request,set:set2}=ctx,userId=request.headers.get("x-user-id");if(!userId)return set2.status=401,{isSuccess:!1,message:"Unauthenticated",data:null};if(!db)return set2.status=503,{isSuccess:!1,message:"Authorization service unavailable",data:null};let{schemaTables}=resolveAuthTablesForRequest(request,config);if(!schemaTables)return set2.status=503,{isSuccess:!1,message:"Authorization tables not available",data:null};let{entity,method,fields,relations}=body;if(!entity||!method)return set2.status=400,{isSuccess:!1,message:"entity and method are required",data:null};let result=await checkAuthorization({userId,method,entity,requestedFields:fields,requestedRelations:relations,db,schemaTables,logger:logger2,getUserData:async()=>{let usersTbl=schemaTables[AUTHORIZATION_TABLE_KEYS.users];if(!usersTbl||!db)return;let idCol=usersTbl.id;if(!idCol)return;return(await db.select().from(usersTbl).where(eq49(idCol,userId)).limit(1))[0]}});return{isSuccess:!0,message:result.authorized?"Authorized":"Forbidden",data:{authorized:result.authorized,reason:result.reason,allowedFields:result.allowedFields,allowedRelations:result.allowedRelations,scopeFilters:result.scopeFilters}}}});return collectRoutes([{prefix:route,routes:[routePostCheck]}])}var init_check=__esm(()=>{init_Authorization();init_routeCollector()});import{sql as sql11}from"drizzle-orm";function createEmailVerificationRoutes(config){let{authConfig,registerConfig,getEmailService,appName}=config,{db,logger:logger2}=authConfig;if(!registerConfig.enabled||!registerConfig.emailVerification?.enabled)return collectRoutes();let verifyRoute="/verify-email",resendRoute="/resend-verification",routeGetVerifyroute=defineRoute({method:"GET",path:"/",query:Type.Object({token:Type.String()}),detail:{tags:["Authentication"],summary:"Verify Email",description:"Verify user email address using the verification token"},handler:async(ctx)=>{let{usersTable}=resolveAuthTablesForRequest(ctx.request,authConfig);if(!db||!usersTable)return ctx.set.status=500,{success:!1,message:"Database not configured"};let token=ctx.query.token;if(!token)return ctx.set.status=400,{success:!1,message:"Verification token is required"};let users=await db.select().from(usersTable).where(sql11`email_verification_token = ${hashVerificationToken(token)}`).limit(1);if(users.length===0)return logger2.warn("[AUTH] Email verification failed - invalid token"),ctx.set.status=400,{success:!1,message:"Invalid or expired verification token"};let user=users[0],tokenExpiresAt=user.emailVerificationTokenExpiresAt;if(tokenExpiresAt&&new Date>new Date(tokenExpiresAt))return logger2.warn("[AUTH] Email verification failed - token expired",{userId:user.id,email:user.email}),ctx.set.status=400,{success:!1,message:"Verification token has expired. Please request a new one."};await db.update(usersTable).set({verifiedAt:new Date,emailVerificationToken:null,emailVerificationTokenExpiresAt:null}).where(sql11`id = ${user.id}`),logger2.info("[AUTH] Email verified successfully",{userId:user.id,email:user.email});let emailService=getEmailService?.()??null;if(registerConfig.emailVerification?.templates?.welcome?.enabled&&emailService?.isAvailable())emailService.sendWelcomeEmail(user.email,user.email.split("@")[0]||"User",appName||"Nucleus").catch((err)=>{logger2.error("[AUTH] Failed to send welcome email after verification",{email:user.email,error:err})});return{success:!0,message:"Email verified successfully. You can now log in.",data:{redirectUrl:registerConfig.emailVerification?.redirectUrl||"/login",verified:!0}}}}),routePostResendroute=defineRoute({method:"POST",path:"/",body:Type.Object({email:Type.String({format:"email"})}),detail:{tags:["Authentication"],summary:"Resend Verification Email",description:"Resend the email verification link to the user"},handler:async(ctx)=>{let{usersTable}=resolveAuthTablesForRequest(ctx.request,authConfig);if(!db||!usersTable)return ctx.set.status=500,{success:!1,message:"Database not configured"};let emailService=getEmailService?.()??null;if(!emailService?.isAvailable())return ctx.set.status=503,{success:!1,message:"Email service not available"};let{email}=ctx.body,antiEnum=authConfig.authentication?.preventUserEnumeration===!0,uniformResend={success:!0,message:"If your email is registered and not yet verified, a verification email has been sent."},users=await db.select().from(usersTable).where(sql11`email = ${email}`).limit(1);if(users.length===0)return antiEnum?uniformResend:{success:!0,message:"If your email is registered, you will receive a verification email."};let user=users[0];if(user.verifiedAt){if(antiEnum)return uniformResend;return ctx.set.status=409,{success:!1,message:"Email is already verified"}}let maxAttempts=registerConfig.emailVerification?.maxResendAttempts||3,currentAttempts=user.emailVerificationAttempts||0;if(currentAttempts>=maxAttempts){if(logger2.warn("[AUTH] Resend verification failed - max attempts reached",{email,attempts:currentAttempts}),antiEnum)return uniformResend;return ctx.set.status=429,{success:!1,message:"Maximum resend attempts reached. Please contact support.",data:{maxAttemptsReached:!0,attemptsUsed:currentAttempts,maxAttempts}}}let resendCooldown=registerConfig.emailVerification?.resendCooldown||"60s",cooldownMs=parseTimeToMs(resendCooldown),lastSentAt=user.emailVerificationSentAt;if(lastSentAt){let timeSinceLastSent=Date.now()-new Date(lastSentAt).getTime();if(timeSinceLastSent<cooldownMs){let waitSeconds=Math.ceil((cooldownMs-timeSinceLastSent)/1000);if(antiEnum)return uniformResend;return ctx.set.status=429,{success:!1,message:`Please wait ${waitSeconds} seconds before requesting another verification email.`,data:{cooldownRemaining:waitSeconds,canResendAt:new Date(Date.now()+waitSeconds*1000).toISOString()}}}}let verificationToken=generateVerificationToken(),tokenExpiresIn=registerConfig.emailVerification?.tokenExpiresIn||"24h",tokenExpiresAt=new Date(Date.now()+parseTimeToMs(tokenExpiresIn));await db.update(usersTable).set({emailVerificationToken:hashVerificationToken(verificationToken),emailVerificationTokenExpiresAt:tokenExpiresAt,emailVerificationSentAt:new Date,emailVerificationAttempts:currentAttempts+1}).where(sql11`id = ${user.id}`);let verificationLink=buildEmailActionLink({request:ctx.request,configuredUrl:registerConfig.emailVerification?.redirectUrl,path:"/verify-email",query:{token:verificationToken},allowedOrigins:authConfig.authentication?.trustedAppOrigins}),result=await emailService.sendVerificationEmail(email,email.split("@")[0]||"User",verificationLink,appName||"Nucleus");if(result.success){logger2.info("[AUTH] Verification email resent",{email});let cooldownSeconds=Math.ceil(cooldownMs/1000);if(antiEnum)return uniformResend;return{success:!0,message:"Verification email sent. Please check your inbox.",data:{cooldownSeconds,canResendAt:new Date(Date.now()+cooldownMs).toISOString(),attemptsRemaining:maxAttempts-(currentAttempts+1)}}}if(logger2.error("[AUTH] Failed to resend verification email",{email,error:result.error}),antiEnum)return uniformResend;return{success:!1,message:"Failed to send verification email. Please try again later."}}});return collectRoutes([{prefix:verifyRoute,routes:[routeGetVerifyroute]},{prefix:resendRoute,routes:[routePostResendroute]}])}var init_emailVerification=__esm(()=>{init_esm3();init_routeCollector();init_utils6()});import crypto7 from"crypto";function generateMagicToken(){return crypto7.randomBytes(32).toString("hex")}function hashToken(token,purpose){let input=purpose?`${purpose}:${token}`:token;return crypto7.createHash("sha256").update(input).digest("hex")}function parseTimeToMs3(time2){let match=time2.match(/^(\d+)(s|m|h|d)$/);if(!match?.[1]||!match[2])return 900000;let value2=parseInt(match[1],10);switch(match[2]){case"s":return value2*1000;case"m":return value2*60*1000;case"h":return value2*60*60*1000;case"d":return value2*24*60*60*1000;default:return 900000}}var TOKEN_PURPOSE;var init_utils8=__esm(()=>{TOKEN_PURPOSE={magicLink:"magic-link",invite:"invite"}});import{eq as eq50}from"drizzle-orm";function createInviteRoute(config,inviteConfig,getEmailService,storeMagicToken,appName,getMagicToken){let{db,logger:logger2}=config,route=inviteConfig.route||"/auth/invite",expiresIn=inviteConfig.tokenExpiresIn||"7d",redirectUrl=inviteConfig.redirectUrl||"";if(!inviteConfig.enabled)return collectRoutes();let declared=[],routePostRoute=defineRoute({method:"POST",path:"/",body:InviteBodySchema,detail:{tags:["Authentication"],summary:"Invite User",description:"Invite a new user by sending them an email with a magic link to set their password"},handler:async(ctx)=>{let resolved=resolveAuthTablesForRequest(ctx.request,config),{usersTable}=resolved;if(!db||!usersTable)return ctx.set.status=500,{success:!1,message:"Database not configured"};let emailService=getEmailService?.();if(!emailService?.isAvailable())return logger2.error("[AUTH] Invite requested but email service not available"),ctx.set.status=503,{success:!1,message:"Email service not available"};let{email:rawEmail}=ctx.body,email=rawEmail.toLowerCase(),antiEnum=config.authentication?.preventUserEnumeration===!0,uniformInvite={success:!0,message:"If the email address is eligible, an invitation has been sent."},existingUsers=await db.select().from(usersTable).where(eq50(usersTable.email,email)).limit(1),userId;if(existingUsers.length>0){let existingUser=existingUsers[0];if(existingUser.verifiedAt||existingUser.password){if(logger2.warn("[AUTH] Invite failed - user already verified",{email}),antiEnum)return uniformInvite;return ctx.set.status=409,{success:!1,message:"User with this email is already verified"}}userId=existingUser.id,logger2.info("[AUTH] Resending invitation to existing unverified user",{userId,email})}else{let insertValues={email,password:null,emailVerified:!1,isLocked:!1,createdAt:new Date,updatedAt:new Date},newUser=(await db.insert(usersTable).values(insertValues).returning())[0];if(!newUser)return logger2.error("[AUTH] Failed to create invited user",{email}),ctx.set.status=500,{success:!1,message:"Failed to create user"};userId=newUser.id,logger2.info("[AUTH] Invited user created",{userId,email});let inviteDefaultRoles=resolveDefaultRoleNames(config.authentication);if(inviteDefaultRoles.length>0)await assignDefaultRoles({db,userId,roleNames:inviteDefaultRoles,rolesTable:resolved.rolesTable??config.rolesTable??null,userRolesTable:resolved.userRolesTable??config.userRolesTable??null,logger:logger2})}let token=generateMagicToken(),tokenHash=hashToken(token,TOKEN_PURPOSE.invite),expiresAt=new Date(Date.now()+parseTimeToMs3(expiresIn)),reqSchemaName=ctx.request.headers.get("x-tenant-schema")||void 0;await storeMagicToken({userId,email,tokenHash,expiresAt},reqSchemaName);let inviteLink=redirectUrl?buildEmailActionLink({request:ctx.request,configuredUrl:redirectUrl,path:extractConfiguredPath(redirectUrl,"/set-password"),query:{token,invite:"true"},allowedOrigins:config.authentication?.trustedAppOrigins}):`/set-password?token=${token}&invite=true`,sendResult=await emailService.sendInvitationEmail(email,inviteLink,appName||"Nucleus");if(!sendResult.success){if(logger2.error("[AUTH] Failed to send invitation email",{email,error:sendResult.error}),antiEnum)return uniformInvite;return{success:!0,message:"User created but failed to send invitation email",data:{id:userId,email}}}if(logger2.info("[AUTH] Invitation email sent",{email,userId}),antiEnum)return uniformInvite;return{success:!0,message:"Invitation sent successfully",data:{id:userId,email}}}});if(declared.push(routePostRoute),getMagicToken){let routePostRouteVerify=defineRoute({method:"POST",path:"/verify",body:Type.Object({token:Type.String()}),detail:{tags:["Authentication"],summary:"Verify Invite Token",description:"Validate an invite token without consuming it. Returns user info if valid."},handler:async(ctx)=>{let{token}=ctx.body;if(!token)return ctx.set.status=400,{success:!1,message:"Token is required"};let tokenHash=hashToken(token,TOKEN_PURPOSE.invite),invSchemaName=ctx.request.headers.get("x-tenant-schema")||void 0,storedToken=await getMagicToken(tokenHash,invSchemaName);if(!storedToken)return logger2.warn("[AUTH] Invalid invite verify token"),ctx.set.status=400,{success:!1,message:"Invalid or expired token"};if(new Date>storedToken.expiresAt)return logger2.warn("[AUTH] Expired invite verify token",{email:storedToken.email}),ctx.set.status=400,{success:!1,message:"Invalid or expired token"};return logger2.info("[AUTH] Invite token verified (not consumed)",{email:storedToken.email,userId:storedToken.userId}),{success:!0,data:{userId:storedToken.userId,email:storedToken.email}}}});declared.push(routePostRouteVerify)}return collectRoutes([{prefix:route,routes:declared}])}var InviteBodySchema;var init_invite=__esm(()=>{init_esm3();init_routeCollector();init_assignDefaultRole();init_utils8();InviteBodySchema=Type.Object({email:Type.String({format:"email"})})});var exports_DeviceTrust={};__export(exports_DeviceTrust,{selectUsableDeviceRow:()=>selectUsableDeviceRow,readDeviceTokenFromCookieHeader:()=>readDeviceTokenFromCookieHeader,mintDeviceToken:()=>mintDeviceToken,isDeviceRowUsable:()=>isDeviceRowUsable,isApprovalMethod:()=>isApprovalMethod,hashDeviceToken:()=>hashDeviceToken,deviceCookieName:()=>deviceCookieName,decideDeviceApproval:()=>decideDeviceApproval,buildDeviceTokenCookie:()=>buildDeviceTokenCookie,buildClearedDeviceTokenCookie:()=>buildClearedDeviceTokenCookie,DEVICE_TOKEN_BYTES:()=>DEVICE_TOKEN_BYTES});import{createHash as createHash4,randomBytes as randomBytes6}from"crypto";function hashDeviceToken(raw){return createHash4("sha256").update(raw).digest("hex")}function mintDeviceToken(){let raw=randomBytes6(DEVICE_TOKEN_BYTES).toString("hex");return{raw,hash:hashDeviceToken(raw)}}function isDeviceRowUsable(row,now){if(!row||row.status!=="trusted")return!1;if((row.revoked_at??row.revokedAt)!=null)return!1;let expRaw=row.expires_at??row.expiresAt;if(expRaw==null)return!1;let exp=expRaw instanceof Date?expRaw:new Date(expRaw);if(Number.isNaN(exp.getTime())||exp<=now)return!1;return!0}function selectUsableDeviceRow(rows,expectedUserId,now){if(!expectedUserId)return null;for(let row of rows??[]){if((row.user_id??row.userId)!==expectedUserId)continue;if(isDeviceRowUsable(row,now))return row}return null}function buildDeviceTokenCookie(raw,opts){let prefix=opts.cookiePrefix??"",name2=`${prefix}${opts.cookieName??"device_token"}`,sameSite=opts.sameSite??"Lax",secure=prefix==="__Secure-"||prefix==="__Host-"||opts.secure!==!1,allowDomain=prefix!=="__Host-",parts=[`${name2}=${raw}`,"Path=/","HttpOnly",`SameSite=${sameSite}`];if(secure)parts.push("Secure");if(allowDomain&&opts.domain)parts.push(`Domain=${opts.domain}`);return parts.push(`Max-Age=${Math.max(0,Math.floor(opts.maxAgeSeconds))}`),parts.join("; ")}function buildClearedDeviceTokenCookie(opts){return buildDeviceTokenCookie("",{...opts,maxAgeSeconds:0})}function decideDeviceApproval(params){let NO_APPROVAL={requiresApproval:!1,deny:!1,mintToken:!1,trustImmediately:!1},TRUST_NOW={requiresApproval:!1,deny:!1,mintToken:!0,trustImmediately:!0};if(params.knownDevice)return NO_APPROVAL;if(params.isImpersonation||!isApprovalMethod(params.loginMethod,params.approvalMethods))return NO_APPROVAL;if(!params.hasValidFingerprint&¶ms.onUnidentifiable==="deny")return{requiresApproval:!1,deny:!0,mintToken:!1,trustImmediately:!1};if(params.trustNewDevices!==!1||params.isEmailExempt||params.isFirstEverDevice)return TRUST_NOW;return{requiresApproval:!0,deny:!1,mintToken:!0,trustImmediately:!1}}function deviceCookieName(cfg){return`${cfg?.cookiePrefix??""}${cfg?.cookieName??"device_token"}`}function readDeviceTokenFromCookieHeader(cookieHeader,cfg){if(!cookieHeader)return;let name2=deviceCookieName(cfg);for(let part of cookieHeader.split(";")){let eq51=part.indexOf("=");if(eq51<0)continue;if(part.slice(0,eq51).trim()===name2)return part.slice(eq51+1).trim()}return}function isApprovalMethod(method,approvalMethods){if(!method)return!1;for(let m of approvalMethods){if(m===method)return!0;if(m.endsWith(":*")&&method.startsWith(m.slice(0,-1)))return!0}return!1}var DEVICE_TOKEN_BYTES=32;var init_DeviceTrust=()=>{};import{createHash as createHash5}from"crypto";function hashRefreshToken(token){return createHash5("sha256").update(token).digest("hex")}function classifyRefreshToken(input){let grace=input.graceSeconds??DEFAULT_REUSE_GRACE_SECONDS;if(!input.currentHash)return{action:"adopt"};if(input.presentedHash===input.currentHash)return{action:"rotate"};if(input.previousHash&&input.presentedHash===input.previousHash){let rotatedAt=input.previousRotatedAt?Date.parse(input.previousRotatedAt):Number.NaN;if(Number.isFinite(rotatedAt)&&input.nowMs-rotatedAt<=grace*1000)return{action:"grace"};return{action:"revoke",reason:"refresh token was superseded and is being replayed outside the grace window"}}return{action:"revoke",reason:"refresh token does not match this session, current or previous"}}var DEFAULT_REUSE_GRACE_SECONDS=30;var init_reuseDetection=()=>{};async function recordIssuedRefreshToken(sessionId,refreshToken,options){if(!sessionId||!refreshToken)return;try{let result=await updateSession({sessionId,updates:{refreshTokenHash:hashRefreshToken(refreshToken),...options?.previousHash?{previousRefreshTokenHash:options.previousHash,previousRefreshTokenAt:new Date().toISOString()}:{}}});if(!result.success)logger.warn("[AUTH] Could not record the issued refresh token; reuse detection is off",{sessionId,error:result.error})}catch(error3){logger.warn("[AUTH] Could not record the issued refresh token; reuse detection is off",{sessionId,error:error3 instanceof Error?error3.message:String(error3)})}}var init_recordIssued=__esm(()=>{init_Update();init_Logger2();init_reuseDetection()});async function resolveUserIdByIdentifier(input){let value2=input.value.trim();if(value2==="")return null;for(let source of input.sources){if(!source.table||!source.column||!source.userColumn)continue;let found=(await input.lookup(source,value2)).filter((id)=>String(id??"").trim()!=="");if(found.length>1)return null;if(found.length===1)return String(found[0])}return null}function looksLikeAlternateIdentifier(value2){let trimmed=value2.trim();return trimmed!==""&&!trimmed.includes("@")}var init_identifier=()=>{};import{eq as eq51}from"drizzle-orm";async function lookupIdentifier(db,schemaTables,source,value2){let table=pickByName(schemaTables,source.table);if(!table)return[];let column=pickByName(table,source.column),userColumn=pickByName(table,source.userColumn);if(!column||!userColumn)return[];return(await db.select({userId:userColumn}).from(table).where(eq51(column,value2)).limit(2)).map((row)=>String(row.userId??""))}var init_identifierQuery=__esm(()=>{init_identifier()});var LoginBodySchema,LoginResponseSchema;var init_types12=__esm(()=>{init_esm3();LoginBodySchema=Type.Object({email:Type.String({minLength:1}),password:Type.String({minLength:1}),rememberMe:Type.Optional(Type.Boolean()),captchaId:Type.Optional(Type.String()),captchaAnswer:Type.Optional(Type.String()),deviceHint:Type.Optional(Type.String())}),LoginResponseSchema=Type.Object({success:Type.Boolean(),message:Type.Optional(Type.String()),data:Type.Optional(Type.Object({user:Type.Object({id:Type.String(),email:Type.String()}),accessToken:Type.String(),refreshToken:Type.String()}))})});import{eq as eq52}from"drizzle-orm";function createLoginRoute(config,loginConfig,signAccessToken,signRefreshToken,createSession,saveSessionToDb,cookieConfig,tokenResponseConfig,captchaService){let{db,logger:logger2}=config,log4=logger2.scoped(AUTH_LOGIN),route=loginConfig.route||"/auth/login",cookies={accessTokenName:cookieConfig?.accessTokenName||"access_token",refreshTokenName:cookieConfig?.refreshTokenName||"refresh_token",sessionTokenName:cookieConfig?.sessionTokenName||"session_token",accessTokenMaxAge:cookieConfig?.accessTokenMaxAge||900,refreshTokenMaxAge:cookieConfig?.refreshTokenMaxAge||604800,sessionTokenMaxAge:cookieConfig?.sessionTokenMaxAge||900,secure:cookieConfig?.secure??!0,httpOnly:cookieConfig?.httpOnly??!0,sameSite:cookieConfig?.sameSite||"strict",path:cookieConfig?.path||"/",domain:cookieConfig?.domain};if(!loginConfig.enabled)return collectRoutes();let identifierSources=loginConfig.identifierSources??[],rememberMeAllowed=loginConfig.rememberMe!==!1,routePostRoute=defineRoute({method:"POST",path:"/",body:LoginBodySchema,detail:{tags:["Authentication"],summary:"Login",description:"Authenticate user with email and password"},handler:async(ctx)=>{let resolved=resolveAuthTablesForRequest(ctx.request,config),usersTable=resolved.usersTable;if(!db||!usersTable)return ctx.set.status=500,{success:!1,message:"Database not configured"};let{email:rawEmail,password:password4,rememberMe:rememberMeRequested,captchaId,captchaAnswer,deviceHint}=ctx.body,rememberMe=rememberMeAllowed?rememberMeRequested:!1,email=rawEmail.toLowerCase(),user=(await db.select().from(usersTable).where(eq52(usersTable.email,email)).limit(1))[0];if(!user&&identifierSources.length>0&&looksLikeAlternateIdentifier(rawEmail)){let resolvedId=await resolveUserIdByIdentifier({sources:identifierSources,value:rawEmail,lookup:(source,value2)=>lookupIdentifier(db,resolved.schemaTables,source,value2)});if(resolvedId)user=(await db.select().from(usersTable).where(eq52(usersTable.id,resolvedId)).limit(1))[0]}let captchaEnabledForLogin=loginConfig.captcha?.enabled!==!1,captchaThreshold=loginConfig.captcha?.requireAfterFailures,currentFailedAttempts=user?.failedLoginAttempts||0,captchaRequired=Boolean(captchaService?.isEnabled())&&captchaEnabledForLogin&&(captchaThreshold===void 0||currentFailedAttempts>=captchaThreshold);if(captchaRequired&&captchaService){if(!captchaId||!captchaAnswer)return ctx.set.status=400,{success:!1,message:"Captcha is required",requiresCaptcha:!0};let captchaResult=await captchaService.validate(captchaId,captchaAnswer);if(!captchaResult.valid)return ctx.set.status=400,{success:!1,message:captchaResult.message||"Invalid captcha",attemptsRemaining:captchaResult.attemptsRemaining,requiresCaptcha:!0}}let reqUrl=new URL(ctx.request.url),auditIp=ctx.request.headers.get("x-client-ip")?.trim()||"unknown",auditUa=ctx.request.headers.get("user-agent")||"unknown";if(!user)return await verifyPassword(password4,await getTimingEqualizerHash()).catch(()=>!1),log4.warn("Login failed - user not found",{email}),await logger2.audit({entityName:"users",operation:"LOGIN_FAILED",summary:`Login failed: user not found (${email})`,ipAddress:auditIp,userAgent:auditUa,path:reqUrl.pathname,query:reqUrl.search}),ctx.set.status=401,{success:!1,message:"Invalid email or password",requiresCaptcha:captchaRequired};if(user.isActive===!1)return log4.warn("Login failed - account inactive",{email,userId:user.id}),ctx.set.status=403,{success:!1,message:"Account is not active"};if(user.isLocked){let lockedUntil=user.lockedUntil;if(lockedUntil&&new Date(lockedUntil)<new Date)await db.update(usersTable).set({isLocked:!1,lockedUntil:null,failedLoginAttempts:0}).where(eq52(usersTable.id,user.id)),log4.info("Account lock expired, auto-unlocked",{userId:user.id,email});else return log4.warn("Login failed - account locked",{email,userId:user.id}),await logger2.audit({entityName:"users",entityId:user.id,operation:"LOGIN_FAILED",userId:user.id,summary:`Login failed: account locked (${email})`,ipAddress:auditIp,userAgent:auditUa,path:reqUrl.pathname,query:reqUrl.search}),ctx.set.status=403,{success:!1,message:"Account is locked"}}let userCohortId=user.cohortId;if(userCohortId&&db){let cohortsTable=resolved.schemaTables?.userCohorts??resolved.schemaTables?.user_cohorts;if(cohortsTable){let cohortExpiresAt=(await db.select().from(cohortsTable).where(eq52(cohortsTable.id,userCohortId)).limit(1))[0]?.expiresAt;if(cohortExpiresAt&&new Date(cohortExpiresAt)<new Date)return log4.warn("Login failed - cohort expired",{email,userId:user.id,cohortId:userCohortId,expiresAt:cohortExpiresAt}),await logger2.audit({entityName:"users",entityId:user.id,operation:"LOGIN_FAILED",userId:user.id,summary:`Login failed: cohort expired (${email})`,ipAddress:auditIp,userAgent:auditUa,path:reqUrl.pathname,query:reqUrl.search}),ctx.set.status=403,{success:!1,message:"Your access has expired. Please contact your administrator."}}}if(!await verifyPassword(password4,user.password)){let failedAttempts=(user.failedLoginAttempts||0)+1;await db.update(usersTable).set({failedLoginAttempts:failedAttempts,isLocked:failedAttempts>=5,lockedUntil:failedAttempts>=5?new Date(Date.now()+1800000):null}).where(eq52(usersTable.id,user.id)),log4.warn("Login failed - invalid password",{email,failedAttempts}),await logger2.audit({entityName:"users",entityId:user.id,operation:"LOGIN_FAILED",userId:user.id,summary:`Login failed: invalid password (${email}, attempt ${failedAttempts})`,ipAddress:auditIp,userAgent:auditUa,path:reqUrl.pathname,query:reqUrl.search});let captchaWillBeRequired=Boolean(captchaService?.isEnabled())&&captchaEnabledForLogin&&(captchaThreshold===void 0||failedAttempts>=captchaThreshold);return ctx.set.status=401,{success:!1,message:"Invalid email or password",requiresCaptcha:captchaWillBeRequired}}await db.update(usersTable).set({failedLoginAttempts:0,lastLoginAt:new Date,loginCount:(user.loginCount||0)+1}).where(eq52(usersTable.id,user.id));let ipAddress=ctx.request.headers.get("x-client-ip")?.trim()||"127.0.0.1",isPrivateOrLoopback=(ip)=>!ip||ip==="127.0.0.1"||ip==="::1"||ip==="localhost"||ip.startsWith("10.")||ip.startsWith("192.168.")||ip.startsWith("172."),userAgent=ctx.request.headers.get("user-agent")||"Unknown Browser";log4.info("Parsed device info",{ipAddress,userAgent});let deviceInfo=parseUserAgentForLogin(userAgent,ipAddress,deviceHint,deviceContextFromRequest(ctx.request).clientHints);if(!isPrivateOrLoopback(ipAddress)){let abortCtrl=new AbortController,geoTimeout=setTimeout(()=>abortCtrl.abort(),2000);fetch(`https://ip-api.com/json/${ipAddress}?fields=country,city`,{signal:abortCtrl.signal}).then(async(res)=>{if(clearTimeout(geoTimeout),!res.ok)return;let geo=await res.json();if(geo.country)deviceInfo.locationCountry=geo.country;if(geo.city)deviceInfo.locationCity=geo.city}).catch(()=>{clearTimeout(geoTimeout)})}let userRoles=[],userClaims=[],userClaimScopes={};if(db)try{let{fetchUserRolesAndClaims:fetchUserRolesAndClaims2}=await Promise.resolve().then(() => (init_fetchUserRolesAndClaims(),exports_fetchUserRolesAndClaims)),rc=await fetchUserRolesAndClaims2(db,user.id,resolved);userRoles=rc.roles,userClaims=rc.claims,userClaimScopes=rc.claimScopes}catch(roleError){log4.error("Failed to fetch user roles/claims for JWT",{userId:user.id,error:roleError instanceof Error?roleError.message:String(roleError),stack:roleError instanceof Error?roleError.stack:void 0})}let requestOrigin=ctx.request.headers.get("origin")||ctx.request.headers.get("referer")?.replace(/\/[^/]*$/,"")||void 0,sessionParams={userId:user.id,deviceInfo,loginMethod:"password",rememberMe,requestOrigin},sessionId=await createSession(sessionParams),accessToken=signAccessToken(user.id,userRoles.length>0?userRoles:void 0,userClaims.length>0?userClaims:void 0,ctx.request.headers.get("x-tenant-schema")??void 0,userClaimScopes,sessionId),refreshToken=signRefreshToken(user.id,rememberMe,sessionId);await recordIssuedRefreshToken(sessionId,refreshToken);let requiresApproval=!1,pendingSessionId=sessionId,deviceTokenCookie;if(saveSessionToDb){let reqSchemaName=ctx.request.headers.get("x-tenant-schema")||void 0,dtCfg=config.authentication?.deviceTrust;if(dtCfg?.enabled)sessionParams.deviceToken=readDeviceTokenFromCookieHeader(ctx.request.headers.get("cookie"),dtCfg);let saveResult=await saveSessionToDb(sessionId,sessionParams,reqSchemaName);if(saveResult?.denied)return log4.warn("Login blocked by device-trust (unidentifiable device)",{userId:user.id}),ctx.set.status=403,{success:!1,message:"This device could not be recognized. Please contact support."};if(deviceTokenCookie=saveResult?.deviceTokenCookie,saveResult?.requiresApproval){if(requiresApproval=!0,saveResult.sessionId)pendingSessionId=saveResult.sessionId}}if(requiresApproval){ctx.set.status=202;let approvalHeaders={"Content-Type":"application/json"};if(deviceTokenCookie)approvalHeaders["Set-Cookie"]=deviceTokenCookie;return new Response(JSON.stringify({success:!1,requiresApproval:!0,sessionId:pendingSessionId,message:"Login requires approval. Please check your email to approve this device."}),{status:202,headers:approvalHeaders})}log4.info("Login successful",{userId:user.id,email,rememberMe}),await logger2.audit({entityName:"users",entityId:user.id,operation:"LOGIN",userId:user.id,summary:`${email} logged in successfully`,ipAddress,userAgent,path:reqUrl.pathname,query:reqUrl.search});let tokenConfig={accessToken:{setHeadersEnabled:tokenResponseConfig?.accessToken?.setHeadersEnabled??!0,returnJson:tokenResponseConfig?.accessToken?.returnJson??!0},refreshToken:{setHeadersEnabled:tokenResponseConfig?.refreshToken?.setHeadersEnabled??!0,returnJson:tokenResponseConfig?.refreshToken?.returnJson??!0},sessionToken:{setHeadersEnabled:tokenResponseConfig?.sessionToken?.setHeadersEnabled??!0,returnJson:tokenResponseConfig?.sessionToken?.returnJson??!0}},securePart=cookies.secure?"; Secure":"",domainPart=cookies.domain?`; Domain=${cookies.domain}`:"",cookieOptions=`; Path=${cookies.path}; HttpOnly; SameSite=${cookies.sameSite}${securePart}${domainPart}`,cookiesToSet=[];if(tokenConfig.accessToken.setHeadersEnabled)cookiesToSet.push(`${cookies.accessTokenName}=${accessToken}${cookieOptions}; Max-Age=${cookies.accessTokenMaxAge}`);if(tokenConfig.refreshToken.setHeadersEnabled)cookiesToSet.push(`${cookies.refreshTokenName}=${refreshToken}${cookieOptions}; Max-Age=${cookies.refreshTokenMaxAge}`);if(tokenConfig.sessionToken.setHeadersEnabled)cookiesToSet.push(`${cookies.sessionTokenName}=${sessionId}${cookieOptions}; Max-Age=${cookies.sessionTokenMaxAge}`);if(deviceTokenCookie)cookiesToSet.push(deviceTokenCookie);ctx.set.headers["x-session-id"]=sessionId;let responseData={user:{id:user.id,email:user.email}};if(tokenConfig.accessToken.returnJson)responseData.accessToken=accessToken;if(tokenConfig.refreshToken.returnJson)responseData.refreshToken=refreshToken;if(tokenConfig.sessionToken.returnJson)responseData.sessionId=sessionId;let jsonBody=JSON.stringify(withEnvelopeFlags({success:!0,data:responseData})),headers=new Headers;headers.set("Content-Type","application/json"),headers.set("x-session-id",sessionId);for(let cookie of cookiesToSet)headers.append("Set-Cookie",cookie);return new Response(jsonBody,{status:200,headers})}});return collectRoutes([{prefix:route,routes:[routePostRoute]}])}var dummyHashPromise=null,getTimingEqualizerHash=()=>{if(!dummyHashPromise)dummyHashPromise=hashPassword("nucleus-login-timing-equalizer");return dummyHashPromise};var init_login=__esm(()=>{init_routeCollector();init_response();init_DeviceTrust();init_scopes();init_recordIssued();init_utils6();init_identifier();init_identifierQuery();init_types12();init_deviceInfo();init_utils7();init_types12()});function clearAuthCookies(cookieConfig){let path7=cookieConfig?.path||"/",sameSite=cookieConfig?.sameSite||"Strict",securePart=cookieConfig?.secure!==!1?"; Secure":"",domainPart=cookieConfig?.domain?`; Domain=${cookieConfig.domain}`:"",accessName=cookieConfig?.accessTokenName||"access_token",refreshName=cookieConfig?.refreshTokenName||"refresh_token",sessionName=cookieConfig?.sessionTokenName||"session_token",clearOpts=`; Path=${path7}; HttpOnly; SameSite=${sameSite}${securePart}${domainPart}; Max-Age=0`;return[`${accessName}=${clearOpts}`,`${refreshName}=${clearOpts}`,`${sessionName}=${clearOpts}`,...IMPERSONATION_COOKIE_NAMES.map((name2)=>`${name2}=${clearOpts}`)]}var init_utils9=__esm(()=>{init_impersonationCookies()});var LogoutResponseSchema;var init_types13=__esm(()=>{init_esm3();LogoutResponseSchema=Type.Object({success:Type.Boolean(),message:Type.Optional(Type.String())})});function createLogoutRoute(config,logoutConfig,destroySession,revokeSessionInDb,cookieConfig){let{logger:logger2}=config,route=logoutConfig.route||"/auth/logout";if(!logoutConfig.enabled)return collectRoutes();let routePostRoute=defineRoute({method:"POST",path:"/",detail:{tags:["Authentication"],summary:"Logout",description:"Logout and invalidate session"},handler:async(ctx)=>{let sessionCookieName=cookieConfig?.sessionTokenName||"session_token",fromCookie=(ctx.request.headers.get("cookie")??"").split(";").map((one)=>one.trim()).find((one)=>one.startsWith(`${sessionCookieName}=`))?.slice(sessionCookieName.length+1),sessionId=ctx.request.headers.get("x-session-id")||fromCookie||null,userId=ctx.request.headers.get("x-user-id");if(sessionId){if(await destroySession(sessionId),revokeSessionInDb){let reqSchemaName=ctx.request.headers.get("x-tenant-schema")||void 0;await revokeSessionInDb(sessionId,"user_logout",reqSchemaName)}}let clearCookies=clearAuthCookies(cookieConfig),responseHeaders=new Headers;responseHeaders.set("Content-Type","application/json");for(let cookie of clearCookies)responseHeaders.append("Set-Cookie",cookie);logger2.info("[AUTH] Logout successful",{userId,sessionId});let reqUrl=new URL(ctx.request.url);logger2.audit({entityName:"users",entityId:userId||void 0,operation:"LOGOUT",userId:userId||void 0,summary:`User logged out${sessionId?` (session: ${sessionId.substring(0,8)}...)`:""}`,ipAddress:ctx.request.headers.get("x-forwarded-for")?.split(",")[0]?.trim()||ctx.request.headers.get("x-real-ip")?.trim()||"unknown",userAgent:ctx.request.headers.get("user-agent")||"unknown",path:reqUrl.pathname,query:reqUrl.search});let jsonBody=JSON.stringify(withEnvelopeFlags({success:!0,message:"Logged out successfully"}));return new Response(jsonBody,{status:200,headers:responseHeaders})}});return collectRoutes([{prefix:route,routes:[routePostRoute]}])}var init_logout=__esm(()=>{init_response();init_routeCollector();init_utils9();init_types13()});var MagicLinkRequestSchema,MagicLinkVerifySchema,MagicLinkResponseSchema,MagicLinkVerifyResponseSchema;var init_types14=__esm(()=>{init_esm3();MagicLinkRequestSchema=Type.Object({email:Type.String({format:"email"})}),MagicLinkVerifySchema=Type.Object({token:Type.String()}),MagicLinkResponseSchema=Type.Object({success:Type.Boolean(),message:Type.Optional(Type.String())}),MagicLinkVerifyResponseSchema=Type.Object({success:Type.Boolean(),message:Type.Optional(Type.String()),data:Type.Optional(Type.Object({user:Type.Object({id:Type.String(),email:Type.String()}),accessToken:Type.String(),refreshToken:Type.String()}))})});import{eq as eq53}from"drizzle-orm";function createMagicLinkRoute(config,magicLinkConfig,getEmailService,signAccessToken,signRefreshToken,createSession,storeMagicToken,getMagicToken,deleteMagicToken,appName,cookieConfig,saveSessionToDb){let{db,logger:logger2}=config,requestRoute=magicLinkConfig.route||"/auth/magic-link",verifyRoute=magicLinkConfig.verifyRoute||"/auth/magic-link/verify",expiresIn=magicLinkConfig.expiresIn||"15m",redirectUrl=magicLinkConfig.redirectUrl||"";if(!magicLinkConfig.enabled)return collectRoutes();let routePostRequestroute=defineRoute({method:"POST",path:"/",body:MagicLinkRequestSchema,detail:{tags:["Authentication"],summary:"Request Magic Link",description:"Send a magic link to the user's email for passwordless login"},handler:async(ctx)=>{let{usersTable}=resolveAuthTablesForRequest(ctx.request,config);if(!db||!usersTable)return ctx.set.status=500,{success:!1,message:"Database not configured"};let emailService=getEmailService?.();if(!emailService?.isAvailable())return logger2.error("[AUTH] Magic link requested but email service not available"),ctx.set.status=503,{success:!1,message:"Email service not available"};let{email:rawEmail}=ctx.body,email=rawEmail.toLowerCase(),user=(await db.select().from(usersTable).where(eq53(usersTable.email,email)).limit(1))[0];if(!user)return logger2.info("[AUTH] Magic link requested for non-existent email",{email}),{success:!0,message:"If an account exists, a magic link has been sent"};if(user.isLocked)return logger2.warn("[AUTH] Magic link requested for locked account",{email}),{success:!0,message:"If an account exists, a magic link has been sent"};let token=generateMagicToken(),tokenHash=hashToken(token,TOKEN_PURPOSE.magicLink),expiresAt=new Date(Date.now()+parseTimeToMs3(expiresIn)),reqSchemaName=ctx.request.headers.get("x-tenant-schema")||void 0;await storeMagicToken({userId:user.id,email,tokenHash,expiresAt},reqSchemaName);let magicLink=redirectUrl?buildEmailActionLink({request:ctx.request,configuredUrl:redirectUrl,path:extractConfiguredPath(redirectUrl,"/verify-magic-link"),query:{token},allowedOrigins:config.authentication?.trustedAppOrigins}):`${verifyRoute}?token=${token}`,result=await emailService.sendMagicLinkEmail(email,magicLink,appName);if(!result.success)return logger2.error("[AUTH] Failed to send magic link email",{email,error:result.error}),ctx.set.status=500,{success:!1,message:result.error?`The email could not be sent: ${result.error}`:"The email could not be sent"};return logger2.info("[AUTH] Magic link sent",{email,userId:user.id}),{success:!0,message:"If an account exists, a magic link has been sent"}}}),routeGetVerifyroute=defineRoute({method:"GET",path:"/",query:MagicLinkVerifySchema,detail:{tags:["Authentication"],summary:"Verify Magic Link",description:"Verify magic link token and login user"},handler:async(ctx)=>{let{usersTable}=resolveAuthTablesForRequest(ctx.request,config);if(!db||!usersTable)return ctx.set.status=500,{success:!1,message:"Database not configured"};let token=ctx.query.token;if(!token)return ctx.set.status=400,{success:!1,message:"Token is required"};let tokenHash=hashToken(token,TOKEN_PURPOSE.magicLink),reqSchemaName=ctx.request.headers.get("x-tenant-schema")||void 0,storedToken=await getMagicToken(tokenHash,reqSchemaName);if(!storedToken)return logger2.warn("[AUTH] Invalid magic link token"),ctx.set.status=400,{success:!1,message:"Invalid or expired token"};if(new Date>storedToken.expiresAt)return await deleteMagicToken(tokenHash,reqSchemaName),logger2.warn("[AUTH] Expired magic link token",{email:storedToken.email}),ctx.set.status=400,{success:!1,message:"Invalid or expired token"};let user=(await db.select().from(usersTable).where(eq53(usersTable.id,storedToken.userId)).limit(1))[0];if(!user)return await deleteMagicToken(tokenHash,reqSchemaName),ctx.set.status=404,{success:!1,message:"User not found"};if(user.isActive===!1)return await deleteMagicToken(tokenHash,reqSchemaName),logger2.warn("[AUTH] Magic link denied: account inactive",{email:storedToken.email}),ctx.set.status=400,{success:!1,message:"Invalid or expired token"};if(user.isLocked){let lockedUntil=user.lockedUntil;if(!(lockedUntil&&new Date(lockedUntil)<new Date))return await deleteMagicToken(tokenHash,reqSchemaName),logger2.warn("[AUTH] Magic link denied: account locked",{email:storedToken.email}),ctx.set.status=400,{success:!1,message:"Invalid or expired token"}}await deleteMagicToken(tokenHash,reqSchemaName),await db.update(usersTable).set({lastLoginAt:new Date,loginCount:(user.loginCount||0)+1,emailVerified:!0}).where(eq53(usersTable.id,user.id));let ipAddress=ctx.request.headers.get("x-forwarded-for")?.split(",")[0]?.trim()||ctx.request.headers.get("x-real-ip")?.trim()||"unknown",userAgent=ctx.request.headers.get("user-agent")||"",mlUserRoles=[],mlUserClaims=[],mlUserClaimScopes={};if(db)try{let resolved=resolveAuthTablesForRequest(ctx.request,config),rc=await fetchUserRolesAndClaims(db,user.id,resolved);mlUserRoles=rc.roles,mlUserClaims=rc.claims,mlUserClaimScopes=rc.claimScopes}catch{}let sessionParams={userId:user.id,deviceInfo:ensureDeviceInfo({ipAddress,userAgent},deviceContextFromRequest(ctx.request).clientHints),loginMethod:"magic_link",rememberMe:!0},sessionId=await createSession(sessionParams),accessToken=signAccessToken(user.id,mlUserRoles.length>0?mlUserRoles:void 0,mlUserClaims.length>0?mlUserClaims:void 0,ctx.request.headers.get("x-tenant-schema")??void 0,mlUserClaimScopes,sessionId),refreshToken=signRefreshToken(user.id,void 0,sessionId),requiresApproval=!1,approvalSessionId=sessionId,deviceTokenCookie;if(saveSessionToDb){let reqSchemaName2=ctx.request.headers.get("x-tenant-schema")||void 0,dtCfg=config.authentication?.deviceTrust;if(dtCfg?.enabled)sessionParams.deviceToken=readDeviceTokenFromCookieHeader(ctx.request.headers.get("cookie"),dtCfg);let saveResult=await saveSessionToDb(sessionId,sessionParams,reqSchemaName2);if(saveResult?.denied)return ctx.set.status=403,{success:!1,message:"This device could not be recognized. Please contact support."};if(deviceTokenCookie=saveResult?.deviceTokenCookie,saveResult?.requiresApproval){if(requiresApproval=!0,saveResult.sessionId)approvalSessionId=saveResult.sessionId}}if(requiresApproval){if(deviceTokenCookie)ctx.set.headers["Set-Cookie"]=deviceTokenCookie;return logger2.info("[AUTH] Magic link login requires device approval",{userId:user.id,email:user.email,approvalSessionId}),ctx.set.status=202,{success:!1,requiresApproval:!0,sessionId:approvalSessionId,message:"Login requires approval. Please check your email to approve this device."}}logger2.info("[AUTH] Magic link login successful",{userId:user.id,email:user.email});let cookies={accessTokenName:cookieConfig?.accessTokenName||"access_token",refreshTokenName:cookieConfig?.refreshTokenName||"refresh_token",sessionTokenName:cookieConfig?.sessionTokenName||"session_token",accessTokenMaxAge:cookieConfig?.accessTokenMaxAge||900,refreshTokenMaxAge:cookieConfig?.refreshTokenMaxAge||604800,sessionTokenMaxAge:cookieConfig?.sessionTokenMaxAge||2592000,secure:cookieConfig?.secure??!0,httpOnly:cookieConfig?.httpOnly??!0,sameSite:cookieConfig?.sameSite||"strict",path:cookieConfig?.path||"/",domain:cookieConfig?.domain},securePart=cookies.secure?"; Secure":"",domainPart=cookies.domain?`; Domain=${cookies.domain}`:"",cookieOptions=`; Path=${cookies.path}; HttpOnly; SameSite=${cookies.sameSite}${securePart}${domainPart}`,responseHeaders=new Headers;if(responseHeaders.set("Content-Type","application/json"),responseHeaders.set("x-session-id",sessionId),responseHeaders.append("Set-Cookie",`${cookies.accessTokenName}=${accessToken}${cookieOptions}; Max-Age=${cookies.accessTokenMaxAge}`),responseHeaders.append("Set-Cookie",`${cookies.refreshTokenName}=${refreshToken}${cookieOptions}; Max-Age=${cookies.refreshTokenMaxAge}`),responseHeaders.append("Set-Cookie",`${cookies.sessionTokenName}=${sessionId}${cookieOptions}; Max-Age=${cookies.sessionTokenMaxAge}`),deviceTokenCookie)responseHeaders.append("Set-Cookie",deviceTokenCookie);let jsonBody=JSON.stringify(withEnvelopeFlags({success:!0,data:{user:{id:user.id,email:user.email},accessToken,refreshToken,sessionId}}));return new Response(jsonBody,{status:200,headers:responseHeaders})}});return collectRoutes([{prefix:requestRoute,routes:[routePostRequestroute]},{prefix:verifyRoute,routes:[routeGetVerifyroute]}])}var init_magicLink=__esm(()=>{init_DeviceTrust();init_response();init_routeCollector();init_fetchUserRolesAndClaims();init_deviceInfo();init_types14();init_utils8();init_types14()});import{eq as eq54}from"drizzle-orm";function createMeRoute(config,meConfig,_schemaTables,_schemaRelations,_databaseUrl){let{db,logger:logger2}=config,route=meConfig.route||"/auth/me";if(!meConfig.enabled)return collectRoutes();let routeGetRoute=defineRoute({method:"GET",path:"/",detail:{tags:["Authentication"],summary:"Get current user",description:"Get the currently authenticated user with profile, addresses, phones and files"},handler:async(ctx)=>{let resolved=resolveAuthTablesForRequest(ctx.request,config),usersTable=resolved.usersTable,reqSchemaTables=resolved.schemaTables;if(!db||!usersTable)return ctx.set.status=500,{success:!1,message:"Database not configured"};let userId=ctx.request.headers.get("x-user-id");if(!userId)return ctx.set.status=401,{success:!1,message:"Unauthorized"};let user=(await db.select().from(usersTable).where(eq54(usersTable.id,userId)).limit(1))[0];if(!user)return ctx.set.status=404,{success:!1,message:"User not found"};let{password:rawPassword,...restUser}=user,hasPassword=typeof rawPassword==="string"&&rawPassword.length>0,safeUser={...stripUnclaimedSensitive(redactSensitiveOutput(restUser)),hasPassword},profile=null,addresses=[],phones=[],files=[],roles=[],claims=[];if(meConfig.includeProfile&&reqSchemaTables){let profilesTable=reqSchemaTables.profiles;if(profilesTable&&db)profile=(await db.select().from(profilesTable).where(eq54(profilesTable.userId,userId)).limit(1))[0]||null}if(meConfig.includeAddresses&&reqSchemaTables){let addressesTable=reqSchemaTables.addresses;if(addressesTable&&db)addresses=await db.select().from(addressesTable).where(eq54(addressesTable.ownerId,userId))}if(meConfig.includePhones&&reqSchemaTables){let phonesTable=reqSchemaTables.phones;if(phonesTable&&db)phones=await db.select().from(phonesTable).where(eq54(phonesTable.ownerId,userId))}if(meConfig.includeFiles&&reqSchemaTables){let filesTable=reqSchemaTables.files;if(filesTable&&db)files=await db.select().from(filesTable).where(eq54(filesTable.uploadedBy,userId))}if(meConfig.includeRoles&&reqSchemaTables){let{userRoles:userRolesTable,roles:rolesTable}=reqSchemaTables;if(userRolesTable&&rolesTable&&db){let roleIds=(await db.select().from(userRolesTable).where(eq54(userRolesTable.userId,userId))).map((ur)=>ur.roleId);if(roleIds.length>0){let{inArray:inArray17}=await import("drizzle-orm");roles=await db.select().from(rolesTable).where(inArray17(rolesTable.id,roleIds))}}}if(meConfig.includeClaims&&reqSchemaTables){let{userRoles:userRolesTable,roleClaims:roleClaimsTable,claims:claimsTable}=reqSchemaTables;if(userRolesTable&&roleClaimsTable&&claimsTable&&db){let urCols=userRolesTable,rcCols=roleClaimsTable,cCols=claimsTable,roleIds=(await db.select({roleId:urCols.roleId}).from(userRolesTable).where(eq54(urCols.userId,userId))).map((r)=>r.roleId);if(roleIds.length>0){let{inArray:inArray17}=await import("drizzle-orm"),claimRows=await db.select({action:cCols.action,scope:rcCols.scope}).from(roleClaimsTable).innerJoin(claimsTable,eq54(rcCols.claimId,cCols.id)).where(inArray17(rcCols.roleId,roleIds)),seen=new Set;for(let row of claimRows){let r=row,key2=`${r.action}|${r.scope??""}`;if(!seen.has(key2))seen.add(key2),claims.push({action:r.action,scope:r.scope??null})}}}}logger2.debug("[AUTH] Me endpoint accessed",{userId});let responseData={success:!0,data:{user:safeUser,profile:stripUnclaimedSensitive(profile),addresses:stripUnclaimedSensitive(addresses),phones:stripUnclaimedSensitive(phones),files:stripUnclaimedSensitive(files),roles:stripUnclaimedSensitive(roles),claims}};return JSON.parse(JSON.stringify(responseData,(_key,value2)=>typeof value2==="bigint"?Number(value2):value2))}});return collectRoutes([{prefix:route,routes:[routeGetRoute]}])}var init_me=__esm(()=>{init_routeCollector();init_utils5();init_sensitiveColumns()});var exports_accountState={};__export(exports_accountState,{evaluateAccountState:()=>evaluateAccountState});function evaluateAccountState(user,now=new Date){if(user.isActive===!1)return{allowed:!1,reason:"inactive",message:"Account is not active"};if(user.isLocked===!0){let raw=user.lockedUntil,lockedUntil=raw instanceof Date?raw:raw?new Date(raw):null;if(!lockedUntil||Number.isNaN(lockedUntil.getTime())||lockedUntil>now)return{allowed:!1,reason:"locked",message:"Account is locked"}}return{allowed:!0}}import{eq as eq55}from"drizzle-orm";async function applyProfileSync(args){let{usersTable,profilesTable,userId,rawProfile,config,isNewUser,logger:logger2}=args,db=args.db;try{if(config.enabled===!1)return;if(!isNewUser&&config.syncOnEveryLogin===!1)return;if(!config.mappings||config.mappings.length===0)return;let userIdCol=usersTable.id,currentUser=(await db.select().from(usersTable).where(eq55(userIdCol,userId)).limit(1))[0]??{},currentProfile={},profileExists=!1;if(profilesTable){let profileUserIdCol=profilesTable.userId,profileRows=await db.select().from(profilesTable).where(eq55(profileUserIdCol,userId)).limit(1);if(profileRows[0])currentProfile=profileRows[0],profileExists=!0}let{users,profiles,errors:errors2}=resolveProfileSyncUpdates({mappings:config.mappings,rawProfile,currentUser,currentProfile});for(let message of errors2)logger2.warn(`[OAUTH] profileSync: ${message}`);if(Object.keys(users).length>0)await db.update(usersTable).set({...users,updatedAt:new Date}).where(eq55(userIdCol,userId)),logger2.info("[OAUTH] profileSync updated users",{userId,columns:Object.keys(users)});if(profilesTable&&Object.keys(profiles).length>0){if(profileExists){let profileUserIdCol=profilesTable.userId;await db.update(profilesTable).set({...profiles,updatedAt:new Date}).where(eq55(profileUserIdCol,userId))}else if(config.createProfileIfMissing!==!1){let now=new Date;await db.insert(profilesTable).values({userId,firstName:rawProfile.givenName??"",lastName:rawProfile.surname??"",...profiles,createdAt:now,updatedAt:now})}else return;logger2.info("[OAUTH] profileSync updated profiles",{userId,columns:Object.keys(profiles),created:!profileExists})}}catch(err){logger2.warn("[OAUTH] profileSync failed \u2014 login continues",{userId,error:err instanceof Error?err.message:String(err)})}}var init_applyProfileSync=()=>{};import{and as and24,eq as eq56}from"drizzle-orm";function buildOAuthStateCookie(value2,opts){let securePart=opts.secure?"; Secure":"",domainPart=opts.domain?`; Domain=${opts.domain}`:"";return`${OAUTH_STATE_COOKIE}=${value2}; Path=/; HttpOnly; SameSite=Lax; Max-Age=${opts.maxAge}${securePart}${domainPart}`}function createOAuthRoutes(config,oauthService,signAccessToken,signRefreshToken,createSession,saveSessionToDb,cookieConfig,tokenResponseConfig,getEmailService,storeMagicToken,appName){let{db,logger:logger2}=config,basePath=oauthService.basePath;for(let provider of oauthService.getEnabledProviders()){let p=oauthService.config.providers[provider];logger2.info("[OAUTH] Provider loaded",{provider,clientIdSuffix:p?.clientId?p.clientId.slice(-4):"none",tenantId:p?.tenantId??"common",redirectUri:p?.redirectUri,scopes:p?.scopes})}let cookies={accessTokenName:cookieConfig?.accessTokenName||"access_token",refreshTokenName:cookieConfig?.refreshTokenName||"refresh_token",sessionTokenName:cookieConfig?.sessionTokenName||"session_token",accessTokenMaxAge:cookieConfig?.accessTokenMaxAge||900,refreshTokenMaxAge:cookieConfig?.refreshTokenMaxAge||604800,sessionTokenMaxAge:cookieConfig?.sessionTokenMaxAge||900,secure:cookieConfig?.secure??!0,httpOnly:cookieConfig?.httpOnly??!0,sameSite:cookieConfig?.sameSite||"lax",path:cookieConfig?.path||"/",domain:cookieConfig?.domain},tokenConfig={accessToken:{setHeadersEnabled:tokenResponseConfig?.accessToken?.setHeadersEnabled??!0,returnJson:tokenResponseConfig?.accessToken?.returnJson??!0},refreshToken:{setHeadersEnabled:tokenResponseConfig?.refreshToken?.setHeadersEnabled??!0,returnJson:tokenResponseConfig?.refreshToken?.returnJson??!0},sessionToken:{setHeadersEnabled:tokenResponseConfig?.sessionToken?.setHeadersEnabled??!0,returnJson:tokenResponseConfig?.sessionToken?.returnJson??!0}},routeGetProvider=defineRoute({method:"GET",path:"/:provider",detail:{tags:["Authentication"],summary:"OAuth Redirect",description:"Redirects to the OAuth provider authorization page"},handler:async(ctx)=>{let provider=ctx.params.provider;if(!oauthService.isProviderEnabled(provider))return ctx.set.status=404,{success:!1,message:`OAuth provider "${provider}" is not enabled`};let redirectUrl=ctx.query.redirect_url,authUrl=await oauthService.buildAuthorizationUrl(provider,void 0,redirectUrl);logger2.debug("[OAUTH] Redirecting to provider",{provider,authUrl:authUrl.replace(/(client_id=)[^&]+/,"$1***")});let issuedState=new URL(authUrl).searchParams.get("state");if(issuedState)ctx.set.headers["Set-Cookie"]=buildOAuthStateCookie(issuedState,{maxAge:600,secure:cookies.secure,domain:cookies.domain});return ctx.set.status=302,ctx.set.headers.Location=authUrl,null}}),routeGetProviderCallback=defineRoute({method:"GET",path:"/:provider/callback",detail:{tags:["Authentication"],summary:"OAuth Callback",description:"Handles the OAuth provider callback, creates session and sets cookies"},handler:async(ctx)=>{let provider=ctx.params.provider,query=ctx.query,{code,state,error:error3,error_description}=query,portalOrigin=resolveAppOrigin(ctx.request,oauthService.successRedirectUrl,config.authentication?.trustedAppOrigins),errorRedirect=absolutizeRedirect(oauthService.errorRedirectUrl,portalOrigin);if(error3)return logger2.warn("[OAUTH] Provider returned error",{provider,error:error3,error_description}),ctx.set.status=302,ctx.set.headers.Location=`${errorRedirect}?error=${encodeURIComponent(error_description||error3)}`,null;if(!code||!state)return ctx.set.status=302,ctx.set.headers.Location=`${errorRedirect}?error=missing_code_or_state`,null;let statePayload=await oauthService.consumeState(state);if(!statePayload)return logger2.warn("[OAUTH] Invalid or expired state",{provider,state}),ctx.set.status=302,ctx.set.headers.Location=`${errorRedirect}?error=invalid_state`,null;let cookieState=readCookie(ctx.request,OAUTH_STATE_COOKIE),clearStateCookie=buildOAuthStateCookie("",{maxAge:0,secure:cookies.secure,domain:cookies.domain});if(!cookieState||cookieState!==state)return logger2.warn("[OAUTH] State cookie missing or mismatched (possible CSRF)",{provider}),ctx.set.status=302,ctx.set.headers["Set-Cookie"]=clearStateCookie,ctx.set.headers.Location=`${errorRedirect}?error=invalid_state`,null;if(statePayload.provider!==provider)return logger2.warn("[OAUTH] State provider mismatch",{expected:statePayload.provider,got:provider}),ctx.set.status=302,ctx.set.headers.Location=`${errorRedirect}?error=provider_mismatch`,null;let resolved=resolveAuthTablesForRequest(ctx.request,config),usersTable=resolved.usersTable,oauthTable=resolved.oauthAccountsTable??config.oauthAccountsTable;if(!db||!usersTable)return ctx.set.status=302,ctx.set.headers.Location=`${errorRedirect}?error=server_error`,null;logger2.debug("[OAUTH] Exchanging code",{provider,codeLength:code.length,statePresent:!!state});let callbackResult;try{callbackResult=await oauthService.exchangeCode(provider,code)}catch(err){let e=err;return logger2.error("[OAUTH] Code exchange failed",err,{provider,errorMessage:e?.message,errorName:e?.name,errorCauseCode:e?.cause?.code,errorCauseMessage:e?.cause?.message}),ctx.set.status=302,ctx.set.headers.Location=`${errorRedirect}?error=token_exchange_failed`,null}let{profile,tokens}=callbackResult,userId=null;if(statePayload.linkUserId&&oauthService.allowAccountLinking){if(userId=statePayload.linkUserId,oauthTable){let existing=await db.select().from(oauthTable).where(and24(eq56(oauthTable.provider,provider),eq56(oauthTable.providerAccountId,profile.providerAccountId))).limit(1);if(existing.length>0){let existingRecord=existing[0];if(existingRecord.userId!==userId)return ctx.set.status=302,ctx.set.headers.Location=`${errorRedirect}?error=account_already_linked_to_another_user`,null;await db.update(oauthTable).set({accessToken:tokens.accessToken,refreshToken:tokens.refreshToken??null,tokenExpiresAt:tokens.expiresAt??null,scope:tokens.scope??null,rawProfile:profile.rawProfile,lastUsedAt:new Date}).where(eq56(oauthTable.id,existingRecord.id))}else await db.insert(oauthTable).values({userId,provider,providerAccountId:profile.providerAccountId,providerEmail:profile.email??null,providerName:profile.name??null,providerAvatarUrl:profile.avatarUrl??null,accessToken:tokens.accessToken,refreshToken:tokens.refreshToken??null,tokenExpiresAt:tokens.expiresAt??null,scope:tokens.scope??null,rawProfile:profile.rawProfile,isPrimary:!1,lastUsedAt:new Date})}let linkSyncConfig=oauthService.profileSync;if(linkSyncConfig){let linkSyncTables=resolveAuthTablesForRequest(ctx.request,config);await applyProfileSync({db,usersTable,profilesTable:linkSyncTables.schemaTables?.profiles,userId,rawProfile:profile.rawProfile,config:linkSyncConfig,isNewUser:!1,logger:logger2})}logger2.info("[OAUTH] Account linked successfully",{userId,provider});let successUrl2=absolutizeRedirect(resolveSafeOAuthRedirect(statePayload.redirectUrl,oauthService.successRedirectUrl,config.authentication?.trustedAppOrigins),portalOrigin);return ctx.set.status=302,ctx.set.headers.Location=`${successUrl2}?linked=true&provider=${provider}`,null}if(oauthTable){let existingOAuth=await db.select().from(oauthTable).where(and24(eq56(oauthTable.provider,provider),eq56(oauthTable.providerAccountId,profile.providerAccountId))).limit(1);if(existingOAuth.length>0){let oauthRecord=existingOAuth[0];userId=oauthRecord.userId,await db.update(oauthTable).set({accessToken:tokens.accessToken,refreshToken:tokens.refreshToken??null,tokenExpiresAt:tokens.expiresAt??null,scope:tokens.scope??null,rawProfile:profile.rawProfile,lastUsedAt:new Date}).where(eq56(oauthTable.id,oauthRecord.id))}}if(!userId&&profile.email){let normalizedOAuthEmail=profile.email.toLowerCase(),existingUsers=await db.select().from(usersTable).where(eq56(usersTable.email,normalizedOAuthEmail)).limit(1);if(existingUsers.length>0&&profile.emailVerified!==!0)return logger2.warn("[OAUTH] Refusing account merge: provider email not verified",{provider,email:profile.email}),ctx.set.status=302,ctx.set.headers.Location=`${errorRedirect}?error=email_not_verified`,null;if(existingUsers.length>0){if(userId=existingUsers[0].id,oauthTable)await db.insert(oauthTable).values({userId,provider,providerAccountId:profile.providerAccountId,providerEmail:profile.email??null,providerName:profile.name??null,providerAvatarUrl:profile.avatarUrl??null,accessToken:tokens.accessToken,refreshToken:tokens.refreshToken??null,tokenExpiresAt:tokens.expiresAt??null,scope:tokens.scope??null,rawProfile:profile.rawProfile,isPrimary:!1,lastUsedAt:new Date}).onConflictDoNothing();logger2.info("[OAUTH] Merged OAuth account with existing user by email",{userId,provider,email:profile.email})}}let createdNewUser=!1;if(!userId){if(!oauthService.autoCreateUser)return ctx.set.status=302,ctx.set.headers.Location=`${errorRedirect}?error=user_not_found`,null;let userEmail=(profile.email??`${provider}_${profile.providerAccountId}@oauth.local`).toLowerCase(),providerEmailVerified=profile.emailVerified===!0;if(userId=(await db.insert(usersTable).values({email:userEmail,password:null,emailVerified:providerEmailVerified,verifiedAt:providerEmailVerified?new Date:null,isActive:!0}).returning())[0].id,oauthTable)await db.insert(oauthTable).values({userId,provider,providerAccountId:profile.providerAccountId,providerEmail:profile.email??null,providerName:profile.name??null,providerAvatarUrl:profile.avatarUrl??null,accessToken:tokens.accessToken,refreshToken:tokens.refreshToken??null,tokenExpiresAt:tokens.expiresAt??null,scope:tokens.scope??null,rawProfile:profile.rawProfile,isPrimary:!0,lastUsedAt:new Date});if(createdNewUser=!0,logger2.info("[OAUTH] Created new user via OAuth",{userId,provider,email:profile.email}),oauthService.sendInviteOnCreate&&profile.email&&getEmailService?.()?.isAvailable()&&storeMagicToken)try{let rawToken=generateMagicToken(),tokenHash=hashToken(rawToken,TOKEN_PURPOSE.invite),expiresAt=new Date(Date.now()+parseTimeToMs3("7d")),oauthSchemaName=ctx.request.headers.get("x-tenant-schema")||void 0;await storeMagicToken({userId,email:profile.email,tokenHash,expiresAt},oauthSchemaName);let inviteLink=buildEmailActionLink({request:ctx.request,configuredUrl:oauthService.successRedirectUrl,path:"/set-password",query:{token:rawToken},allowedOrigins:config.authentication?.trustedAppOrigins});await getEmailService?.()?.sendEmail({to:profile.email,subject:`Welcome to ${appName??"the platform"} \u2014 Set your password`,html:`
|
|
447
|
+
`),polyTables=(Array.isArray(polyResult)?polyResult:polyResult.rows||[]).map((r)=>String(r.table_name||""));logger2.info("[AUTH] Change User ID - discovered schema references",{fkConstraints:fkConstraints.map((f)=>`${f.table_name}.${f.column_name} (${f.constraint_name})`),userIdColumns:userIdColumns.map((u)=>`${u.table_name}.${u.column_name}`),auditColumns:auditColumns.map((a)=>`${a.table_name}.${a.column_name}`),polyTables});let allUpdates=new Map,addUpdate=(tableName,columnName)=>{if(!allUpdates.has(tableName))allUpdates.set(tableName,new Set);allUpdates.get(tableName)?.add(columnName)};for(let fk of fkConstraints)addUpdate(fk.table_name,fk.column_name);for(let col13 of userIdColumns)addUpdate(col13.table_name,col13.column_name);for(let col13 of auditColumns)addUpdate(col13.table_name,col13.column_name);logger2.info("[AUTH] Change User ID - all column updates to execute",{updates:Array.from(allUpdates.entries()).map(([t,cols])=>`${t}: [${Array.from(cols).join(", ")}]`)}),await db.transaction(async(tx)=>{for(let fk of fkConstraints)await tx.execute(sql9.raw(`ALTER TABLE "${effectiveSchema}"."${fk.table_name}" DROP CONSTRAINT "${fk.constraint_name}"`));await tx.execute(sql9.raw(`UPDATE "${effectiveSchema}"."users" SET "id" = '${newId}' WHERE "id" = '${currentId}'`));for(let[tableName,columns]of allUpdates.entries())for(let columnName of columns)await tx.execute(sql9.raw(`UPDATE "${effectiveSchema}"."${tableName}" SET "${columnName}" = '${newId}' WHERE "${columnName}" = '${currentId}'`));for(let tableName of polyTables)await tx.execute(sql9.raw(`UPDATE "${effectiveSchema}"."${tableName}" SET "owner_id" = '${newId}' WHERE "owner_id" = '${currentId}' AND "owner_type" IN ('user', 'users', 'User')`));for(let fk of fkConstraints){let onDelete=fk.delete_rule==="CASCADE"?"ON DELETE CASCADE":fk.delete_rule==="SET NULL"?"ON DELETE SET NULL":"";await tx.execute(sql9.raw(`ALTER TABLE "${effectiveSchema}"."${fk.table_name}" ADD CONSTRAINT "${fk.constraint_name}" FOREIGN KEY ("${fk.column_name}") REFERENCES "${effectiveSchema}"."users" ("id") ${onDelete}`))}});let allAffectedTables=[...new Set([...fkConstraints.map((f)=>f.table_name),...userIdColumns.map((u)=>u.table_name),...auditColumns.map((a)=>a.table_name),...polyTables])];return logger2.info("[AUTH] User ID changed successfully",{godminId:requestingUserId,currentId,newId,email:targetUser.email,affectedTables:allAffectedTables,fkConstraintsDroppedAndReadded:fkConstraints.length}),await logger2.audit({entityName:"users",entityId:newId,operation:"CHANGE_USER_ID",userId:requestingUserId,summary:`Godmin changed user ID: ${currentId} \u2192 ${newId} (${targetUser.email})`,oldValues:{id:currentId},newValues:{id:newId},ipAddress:ctx.request.headers.get("x-forwarded-for")?.split(",")[0]?.trim()||"unknown",userAgent:ctx.request.headers.get("user-agent")||"unknown",path:new URL(ctx.request.url).pathname,query:""}),{success:!0,data:{oldId:currentId,newId,email:targetUser.email,affectedTables:allAffectedTables}}}catch(error3){return logger2.error("[AUTH] Failed to change user ID",{error:error3 instanceof Error?error3.message:String(error3),stack:error3 instanceof Error?error3.stack:void 0,currentId,newId}),ctx.set.status=500,{success:!1,message:error3 instanceof Error?error3.message:"Failed to change user ID"}}}})];return collectRoutes([{routes:declared}])}var init_changeUserId=__esm(()=>{init_esm3();init_routeCollector();init_requireGodmin();init_resolveMutationSchema()});function decideCreateUserAuthz(params){let{callerIsGod,callerRoleNames,allowRoles,requestedRoleNames,godminRoleName}=params,callerIsGodmin=callerIsGod||callerRoleNames.includes(godminRoleName);if(!(callerIsGodmin||callerRoleNames.some((n)=>allowRoles.includes(n))))return{allowed:!1,status:403,reason:"not_privileged"};if(requestedRoleNames.includes(godminRoleName)&&!callerIsGodmin)return{allowed:!1,status:403,reason:"godmin_assignment_forbidden"};if(!callerIsGodmin){let heldByCaller=new Set(callerRoleNames);if(!requestedRoleNames.every((r)=>heldByCaller.has(r)))return{allowed:!1,status:403,reason:"role_above_caller"}}return{allowed:!0,callerIsGodmin}}import{eq as eq42,inArray as inArray16}from"drizzle-orm";function createCreateUserRoute(config,createUserConfig){let{db,logger:logger2}=config;if(createUserConfig?.enabled===!1)return collectRoutes();let route=createUserConfig?.route||"/auth/admin/create-user",allowRoles=createUserConfig?.allowRoles??[],declared=[defineRoute({method:"POST",path:"/",body:CreateUserBodySchema,detail:{tags:["Authentication","Admin"],summary:"Admin Create User",description:"Create a fully active user with a password, roles, and optional profile. Admin-only. No email is sent."},handler:async(ctx)=>{let resolved=resolveAuthTablesForRequest(ctx.request,config),{usersTable,rolesTable:resolvedRolesTable,userRolesTable:resolvedUserRolesTable,schemaTables}=resolved,profilesTable=schemaTables?.profiles,rolesTable=resolvedRolesTable??config.rolesTable??null,userRolesTable=resolvedUserRolesTable??config.userRolesTable??null;if(!db||!usersTable)return ctx.set.status=500,{success:!1,message:"Database not configured"};let requestingUserId=ctx.request.headers.get("x-user-id");if(!requestingUserId)return ctx.set.status=401,{success:!1,message:"Unauthorized"};let requestingUser=(await db.select().from(usersTable).where(eq42(usersTable.id,requestingUserId)).limit(1))[0];if(!requestingUser)return ctx.set.status=401,{success:!1,message:"Unauthorized"};let callerIsGod=Boolean(requestingUser.isGod),callerRoleNames=[];if(!callerIsGod&&rolesTable&&userRolesTable){let rolesCols=rolesTable,userRolesCols=userRolesTable;callerRoleNames=(await db.select({name:rolesCols.name}).from(userRolesTable).innerJoin(rolesTable,eq42(rolesCols.id,userRolesCols.roleId)).where(eq42(userRolesCols.userId,requestingUserId))).map((r)=>r.name)}let requestedRoleIds=ctx.body.roleIds??[],foundRoles=[];if(requestedRoleIds.length>0&&rolesTable){let rolesCols=rolesTable;foundRoles=(await db.select({id:rolesCols.id,name:rolesCols.name}).from(rolesTable).where(inArray16(rolesCols.id,requestedRoleIds))).map((r)=>({id:r.id,name:r.name}))}let decision=decideCreateUserAuthz({callerIsGod,callerRoleNames,allowRoles,requestedRoleNames:foundRoles.map((r)=>r.name),godminRoleName:GODMIN_ROLE_NAME});if(!decision.allowed)return logger2.warn("[AUTH] create-user denied",{callerId:requestingUserId,reason:decision.reason}),ctx.set.status=decision.status,{success:!1,message:decision.reason==="godmin_assignment_forbidden"?"Forbidden: assigning the godmin role requires godmin privileges":"Forbidden"};let{email:rawEmail,password:password4,profile}=ctx.body,email=rawEmail.toLowerCase().trim(),passwordValidation=validatePasswordStrength(password4,config.authentication?.passwordPolicy,email);if(!passwordValidation.valid)return ctx.set.status=400,{success:!1,message:"Password too weak",errors:passwordValidation.errors};if((await db.select().from(usersTable).where(eq42(usersTable.email,email)).limit(1)).length>0)return logger2.warn("[AUTH] create-user failed \u2014 email already exists",{email}),ctx.set.status=409,{success:!1,message:"Email already registered"};let now=new Date,hashedPassword=await hashPassword(password4),insertValues={email,password:hashedPassword,emailVerified:!0,verifiedAt:now,isLocked:!1,createdAt:now,updatedAt:now},newUser=(await db.insert(usersTable).values(insertValues).returning())[0];if(!newUser)return logger2.error("[AUTH] create-user insert returned nothing",{email}),ctx.set.status=500,{success:!1,message:"Failed to create user"};let newUserId=newUser.id;if(requestedRoleIds.length>0&&rolesTable&&userRolesTable)try{let validRoleIds=foundRoles.map((r)=>r.id);if(validRoleIds.length>0)await db.insert(userRolesTable).values(validRoleIds.map((roleId)=>({userId:newUserId,roleId})));let skipped=requestedRoleIds.filter((id)=>!validRoleIds.includes(id));if(skipped.length>0)logger2.warn("[AUTH] create-user: some roleIds were not found",{userId:newUserId,skipped});if(foundRoles.some((r)=>r.name===GODMIN_ROLE_NAME))await db.update(usersTable).set({isGod:!0}).where(eq42(usersTable.id,newUserId))}catch(err){logger2.warn("[AUTH] create-user: failed to assign roles \u2014 continuing",{userId:newUserId,error:err instanceof Error?err.message:String(err)})}else{let createDefaultRoles=resolveDefaultRoleNames(config.authentication);if(createDefaultRoles.length>0)await assignDefaultRoles({db,userId:newUserId,roleNames:createDefaultRoles,rolesTable,userRolesTable,logger:logger2})}if(profile&&profilesTable)try{await db.insert(profilesTable).values({userId:newUserId,firstName:profile.firstName??null,lastName:profile.lastName??null,phoneNumber:profile.phoneNumber??null,createdAt:now,updatedAt:now})}catch(err){logger2.warn("[AUTH] create-user: failed to create profile \u2014 continuing",{userId:newUserId,error:err instanceof Error?err.message:String(err)})}let reqUrl=new URL(ctx.request.url),auditIp=ctx.request.headers.get("x-forwarded-for")?.split(",")[0]?.trim()||ctx.request.headers.get("x-real-ip")?.trim()||"unknown",auditUa=ctx.request.headers.get("user-agent")||"unknown";return logger2.audit({entityName:"users",entityId:newUserId,operation:"ADMIN_CREATE_USER",userId:requestingUserId,summary:`Admin ${requestingUserId} created user ${email}`,ipAddress:auditIp,userAgent:auditUa,path:reqUrl.pathname,query:reqUrl.search}),logger2.info("[AUTH] Admin created user",{newUserId,email,createdBy:requestingUserId}),{success:!0,message:"User created successfully",data:{id:newUserId,email}}}})];return collectRoutes([{prefix:route,routes:declared}])}var CreateUserBodySchema;var init_createUser=__esm(()=>{init_esm3();init_Authorization();init_routeCollector();init_assignDefaultRole();init_utils6();CreateUserBodySchema=Type.Object({email:Type.String({format:"email"}),password:Type.String({minLength:8}),roleIds:Type.Optional(Type.Array(Type.String())),profile:Type.Optional(Type.Object({firstName:Type.Optional(Type.String()),lastName:Type.Optional(Type.String()),phoneNumber:Type.Optional(Type.String())}))})});import{eq as eq43}from"drizzle-orm";function createHardDeleteUserRoute(config){let{db,logger:logger2}=config,declared=[defineRoute({method:"DELETE",path:"/auth/admin/hard-delete/:userId",params:HardDeleteUserParamsSchema,detail:{tags:["Authentication","Admin"],summary:"Hard Delete User",description:"Permanently delete a user and all related data (roles, sessions, profile, files, etc.). Godmin-only. Irreversible."},handler:async(ctx)=>{let resolved=resolveAuthTablesForRequest(ctx.request,config),{usersTable,userRolesTable,sessionsTable,oauthAccountsTable,apiKeysTable,schemaTables}=resolved;if(!db||!usersTable)return ctx.set.status=500,{success:!1,message:"Database not configured"};let requestingUserId=ctx.request.headers.get("x-user-id");if(!requestingUserId)return ctx.set.status=401,{success:!1,message:"Unauthorized"};if(!(await db.select().from(usersTable).where(eq43(usersTable.id,requestingUserId)).limit(1))[0]?.isGod)return ctx.set.status=403,{success:!1,message:"Forbidden \u2014 only godmin can hard-delete users"};let{userId:targetUserId}=ctx.params;if(targetUserId===requestingUserId)return ctx.set.status=400,{success:!1,message:"Cannot hard-delete yourself"};let targetUser=(await db.select().from(usersTable).where(eq43(usersTable.id,targetUserId)).limit(1))[0];if(!targetUser)return ctx.set.status=404,{success:!1,message:"User not found"};if(targetUser.isGod)return ctx.set.status=403,{success:!1,message:"Cannot hard-delete a godmin user"};let deletedRelations=[],col13=(table,name2)=>table[name2],profilesTable=schemaTables?.profiles,addressesTable=schemaTables?.addresses,phonesTable=schemaTables?.phones,filesTable=schemaTables?.files,runCascade=async(exec)=>{deletedRelations.length=0;let del=async(table,columnName,label)=>{if(!table)return;await exec.delete(table).where(eq43(col13(table,columnName),targetUserId)),deletedRelations.push(label)};await del(userRolesTable,"userId","user_roles"),await del(sessionsTable,"userId","sessions"),await del(oauthAccountsTable,"userId","oauth_accounts"),await del(apiKeysTable,"userId","api_keys"),await del(profilesTable,"userId","profiles"),await del(addressesTable,"ownerId","addresses"),await del(phonesTable,"ownerId","phones"),await del(filesTable,"uploadedBy","files"),await exec.delete(usersTable).where(eq43(usersTable.id,targetUserId))};try{let txDb=db;if(typeof txDb.transaction==="function")await txDb.transaction(async(tx)=>{await runCascade(tx)});else await runCascade(db)}catch(err){return logger2.error("[AUTH] hard-delete failed; transaction rolled back",{userId:targetUserId,error:err instanceof Error?err.message:String(err)}),ctx.set.status=500,{success:!1,message:"Hard delete failed and was rolled back"}}let reqUrl=new URL(ctx.request.url);return logger2.audit({entityName:"users",entityId:targetUserId,operation:"ADMIN_HARD_DELETE_USER",userId:requestingUserId,summary:`Godmin ${requestingUserId} hard-deleted user ${targetUser.email} (${targetUserId}). Cleaned: ${deletedRelations.join(", ")}`,ipAddress:ctx.request.headers.get("x-forwarded-for")?.split(",")[0]?.trim()||ctx.request.headers.get("x-real-ip")?.trim()||"unknown",userAgent:ctx.request.headers.get("user-agent")||"unknown",path:reqUrl.pathname,query:reqUrl.search}),logger2.info("[AUTH] Hard-deleted user",{targetUserId,email:targetUser.email,deletedBy:requestingUserId,cleanedRelations:deletedRelations}),{success:!0,message:"User permanently deleted",data:{userId:targetUserId,email:targetUser.email,cleanedRelations:deletedRelations}}}})];return collectRoutes([{routes:declared}])}var HardDeleteUserParamsSchema;var init_hardDeleteUser=__esm(()=>{init_esm3();init_routeCollector();HardDeleteUserParamsSchema=Type.Object({userId:Type.String()})});var exports_fetchUserRolesAndClaims={};__export(exports_fetchUserRolesAndClaims,{fetchUserRolesAndClaims:()=>fetchUserRolesAndClaims});import{and as and22,eq as eq44,inArray as inArray17}from"drizzle-orm";var noopLogger,fetchUserRolesAndClaims=async(db,userId,resolved,logger2=noopLogger)=>{let{userRolesTable,rolesTable,roleClaimsTable,claimsTable,usersTable}=resolved,result={roles:[],claims:[],claimScopes:{}};if(!userRolesTable||!rolesTable)return result;let roleIds=(await db.select().from(userRolesTable).where(and22(eq44(userRolesTable.userId,userId),activeRowsPredicate(userRolesTable)))).map((r)=>r.roleId);if(roleIds.length===0)return result;let roleRows=await db.select().from(rolesTable).where(inArray17(rolesTable.id,roleIds));if(result.roles=roleRows.map((r)=>r.name),roleClaimsTable&&claimsTable){let rows=(await db.select({action:claimsTable.action,scope:roleClaimsTable.scope}).from(roleClaimsTable).innerJoin(claimsTable,eq44(roleClaimsTable.claimId,claimsTable.id)).where(and22(inArray17(roleClaimsTable.roleId,roleIds),activeRowsPredicate(roleClaimsTable)))).filter((r)=>typeof r.action==="string"&&r.action.length>0).map((r)=>({action:r.action,scope:r.scope??null})),userData;if(rows.some((r)=>!!r.scope)&&usersTable)try{userData=(await db.select().from(usersTable).where(eq44(usersTable.id,userId)))[0]}catch(err){logger2.warn("[Authorization] Failed to load user row for scope resolution",{userId,error:err instanceof Error?err.message:String(err)})}let{claimScopes,dropped}=resolveClaimScopes(rows,userData,logger2),droppedSet=new Set(dropped);result.claims=Array.from(new Set(rows.map((r)=>r.action))).filter((a)=>!droppedSet.has(a)),result.claimScopes=claimScopes}return result};var init_fetchUserRolesAndClaims=__esm(()=>{init_activeRows();init_Middleware();noopLogger={debug:()=>{},info:()=>{},warn:()=>{},error:()=>{}}});var IMPERSONATION_COOKIE_NAMES;var init_impersonationCookies=__esm(()=>{IMPERSONATION_COOKIE_NAMES=["nucleus_impersonating_as","nucleus_godmin_id","nucleus_impersonating_email","nucleus_imp_sig"]});import{eq as eq45}from"drizzle-orm";function createImpersonateRoute(config,signAccessToken,signRefreshToken,verifyRefreshToken,createSession,saveSessionToDb,cookieConfig){let{db,logger:logger2}=config,requireGodmin2=createRequireGodmin(config),cookies={accessTokenName:cookieConfig?.accessTokenName||"access_token",refreshTokenName:cookieConfig?.refreshTokenName||"refresh_token",sessionTokenName:cookieConfig?.sessionTokenName||"session_token",accessTokenMaxAge:cookieConfig?.accessTokenMaxAge||900,refreshTokenMaxAge:cookieConfig?.refreshTokenMaxAge||604800,sessionTokenMaxAge:cookieConfig?.sessionTokenMaxAge||900,secure:cookieConfig?.secure??!0,sameSite:cookieConfig?.sameSite||"lax",path:cookieConfig?.path||"/",domain:cookieConfig?.domain},routePostAuthAdminImpersonate=defineRoute({method:"POST",path:"/auth/admin/impersonate",body:Type.Object({targetUserId:Type.String()}),detail:{tags:["Authentication"],summary:"Impersonate User",description:"Godmin: start a session as another user"},handler:async(ctx)=>{let{usersTable}=resolveAuthTablesForRequest(ctx.request,config);if(!db||!usersTable)return ctx.set.status=500,{success:!1,message:"Database not configured"};let{targetUserId}=ctx.body,gate=await requireGodmin2(ctx.request);if(!gate.ok)return ctx.set.status=gate.status,{success:!1,message:gate.message};let{callerId:requestingUserId,caller:requestingUser}=gate;if(requestingUserId===targetUserId)return ctx.set.status=400,{success:!1,message:"Cannot impersonate yourself"};let targetUser=(await db.select().from(usersTable).where(eq45(usersTable.id,targetUserId)).limit(1))[0];if(!targetUser)return ctx.set.status=404,{success:!1,message:"Target user not found"};let impRoles=[],impClaims=[],impClaimScopes={};if(config.db)try{let resolved=resolveAuthTablesForRequest(ctx.request,config),rc=await fetchUserRolesAndClaims(config.db,targetUserId,resolved);impRoles=rc.roles,impClaims=rc.claims,impClaimScopes=rc.claimScopes}catch{}let reqIp=ctx.request.headers.get("x-forwarded-for")?.split(",")[0]?.trim()||"unknown",sessionParams={userId:targetUserId,deviceInfo:{deviceName:"Impersonation Session",browserName:"Admin",osName:"Admin",ipAddress:reqIp},loginMethod:"impersonation"},sessionId=await createSession(sessionParams),accessToken=signAccessToken(targetUserId,impRoles.length>0?impRoles:void 0,impClaims.length>0?impClaims:void 0,ctx.request.headers.get("x-tenant-schema")??void 0,impClaimScopes,sessionId),refreshToken=signRefreshToken(targetUserId,void 0,sessionId);if(saveSessionToDb){let impSchemaName=ctx.request.headers.get("x-tenant-schema")||void 0;await saveSessionToDb(sessionId,sessionParams,impSchemaName)}logger2.info("[AUTH] Impersonation started",{godminId:requestingUserId,targetUserId,targetEmail:targetUser.email}),await logger2.audit({entityName:"users",entityId:targetUserId,operation:"IMPERSONATE_START",userId:requestingUserId,summary:`Godmin ${requestingUser.email} started impersonating ${targetUser.email}`,ipAddress:ctx.request.headers.get("x-forwarded-for")?.split(",")[0]?.trim()||"unknown",userAgent:ctx.request.headers.get("user-agent")||"unknown",path:new URL(ctx.request.url).pathname,query:""});let securePart=cookies.secure?"; Secure":"",domainPart=cookies.domain?`; Domain=${cookies.domain}`:"",cookieOptions=`; Path=${cookies.path}; HttpOnly; SameSite=${cookies.sameSite}${securePart}${domainPart}`,cookiesToSet=[`${cookies.accessTokenName}=${accessToken}${cookieOptions}; Max-Age=${cookies.accessTokenMaxAge}`,`${cookies.refreshTokenName}=${refreshToken}${cookieOptions}; Max-Age=${cookies.refreshTokenMaxAge}`,`${cookies.sessionTokenName}=${sessionId}${cookieOptions}; Max-Age=${cookies.sessionTokenMaxAge}`,`nucleus_impersonating_as=${targetUserId}${cookieOptions}; Max-Age=${cookies.sessionTokenMaxAge}`,`nucleus_godmin_id=${requestingUserId}${cookieOptions}; Max-Age=${cookies.sessionTokenMaxAge}`,`nucleus_impersonating_email=${encodeURIComponent(String(targetUser.email))}${cookieOptions}; Max-Age=${cookies.sessionTokenMaxAge}`,`nucleus_imp_sig=${signRefreshToken(requestingUserId)}${cookieOptions}; Max-Age=${cookies.refreshTokenMaxAge}`],jsonBody=JSON.stringify(withEnvelopeFlags({success:!0,data:{targetUser:{id:targetUser.id,email:targetUser.email},godminId:requestingUserId,sessionId}})),headers=new Headers;headers.set("Content-Type","application/json");for(let cookie of cookiesToSet)headers.append("Set-Cookie",cookie);return new Response(jsonBody,{status:200,headers})}}),routePostAuthAdminImpersonateStop=defineRoute({method:"POST",path:"/auth/admin/impersonate/stop",detail:{tags:["Authentication"],summary:"Stop Impersonation",description:"Godmin: restore own session after impersonation"},handler:async(ctx)=>{let{usersTable}=resolveAuthTablesForRequest(ctx.request,config);if(!db||!usersTable)return ctx.set.status=500,{success:!1,message:"Database not configured"};let cookieHeader=ctx.request.headers.get("cookie")||"",impSig=Object.fromEntries(cookieHeader.split(";").map((c)=>{let[k,...v]=c.trim().split("=");return[k?.trim(),v.join("=")]})).nucleus_imp_sig,verified=impSig?verifyRefreshToken(impSig):{valid:!1};if(!verified.valid||!verified.payload?.sub)return ctx.set.status=401,{success:!1,message:"No valid impersonation session"};let godminId=verified.payload.sub,godminUser=(await db.select().from(usersTable).where(eq45(usersTable.id,godminId)).limit(1))[0];if(!godminUser)return ctx.set.status=404,{success:!1,message:"Godmin user not found"};if(godminUser.isGod!==!0)return ctx.set.status=403,{success:!1,message:"Forbidden: godmin privileges required"};let stateRefusal=accountStateRefusal(godminUser);if(stateRefusal)return ctx.set.status=401,{success:!1,message:stateRefusal};let godminRoles=[],godminClaims=[],godminClaimScopes={};if(config.db)try{let resolvedStop=resolveAuthTablesForRequest(ctx.request,config),rcStop=await fetchUserRolesAndClaims(config.db,godminId,resolvedStop);godminRoles=rcStop.roles,godminClaims=rcStop.claims,godminClaimScopes=rcStop.claimScopes}catch{}let stopReqIp=ctx.request.headers.get("x-forwarded-for")?.split(",")[0]?.trim()||"unknown",sessionParams={userId:godminId,deviceInfo:{deviceName:"Restored Admin Session",browserName:"Admin",osName:"Admin",ipAddress:stopReqIp},loginMethod:"impersonation_stop"},sessionId=await createSession(sessionParams),accessToken=signAccessToken(godminId,godminRoles.length>0?godminRoles:void 0,godminClaims.length>0?godminClaims:void 0,ctx.request.headers.get("x-tenant-schema")??void 0,godminClaimScopes,sessionId),refreshToken=signRefreshToken(godminId,void 0,sessionId);if(saveSessionToDb){let stopSchemaName=ctx.request.headers.get("x-tenant-schema")||void 0;await saveSessionToDb(sessionId,sessionParams,stopSchemaName)}logger2.info("[AUTH] Impersonation stopped",{godminId}),await logger2.audit({entityName:"users",entityId:godminId,operation:"IMPERSONATE_STOP",userId:godminId,summary:`Godmin ${godminUser.email} stopped impersonation`,ipAddress:ctx.request.headers.get("x-forwarded-for")?.split(",")[0]?.trim()||"unknown",userAgent:ctx.request.headers.get("user-agent")||"unknown",path:new URL(ctx.request.url).pathname,query:""});let securePart=cookies.secure?"; Secure":"",domainPart=cookies.domain?`; Domain=${cookies.domain}`:"",cookieOptions=`; Path=${cookies.path}; HttpOnly; SameSite=${cookies.sameSite}${securePart}${domainPart}`,expiredOptions=`; Path=${cookies.path}; HttpOnly; SameSite=${cookies.sameSite}${securePart}${domainPart}; Max-Age=0`,cookiesToSet=[`${cookies.accessTokenName}=${accessToken}${cookieOptions}; Max-Age=${cookies.accessTokenMaxAge}`,`${cookies.refreshTokenName}=${refreshToken}${cookieOptions}; Max-Age=${cookies.refreshTokenMaxAge}`,`${cookies.sessionTokenName}=${sessionId}${cookieOptions}; Max-Age=${cookies.sessionTokenMaxAge}`,...IMPERSONATION_COOKIE_NAMES.map((name2)=>`${name2}=deleted${expiredOptions}`)],jsonBody=JSON.stringify(withEnvelopeFlags({success:!0,data:{godminUser:{id:godminUser.id,email:godminUser.email},sessionId}})),headers=new Headers;headers.set("Content-Type","application/json");for(let cookie of cookiesToSet)headers.append("Set-Cookie",cookie);return new Response(jsonBody,{status:200,headers})}});return collectRoutes([{routes:[routePostAuthAdminImpersonate,routePostAuthAdminImpersonateStop]}])}var init_impersonate=__esm(()=>{init_esm3();init_response();init_routeCollector();init_fetchUserRolesAndClaims();init_impersonationCookies();init_requireGodmin()});function clearedLockoutState(){return{failedLoginAttempts:0,isLocked:!1,lockedUntil:null}}function planPasswordRotation(input){let protectedRoles=new Set(input.protectedRoles.filter((name2)=>name2.trim()!=="")),plan={rotate:[],skipped:[]};for(let user of input.users){if(user.id===input.callerId){plan.skipped.push({id:user.id,reason:"caller"});continue}if(user.isGod){plan.skipped.push({id:user.id,reason:"godmin"});continue}if(user.roleNames.some((name2)=>protectedRoles.has(name2))){plan.skipped.push({id:user.id,reason:"protected_role"});continue}if(input.includeInactive!==!0&&user.isActive===!1){plan.skipped.push({id:user.id,reason:"inactive"});continue}plan.rotate.push(user.id)}return plan}function splitByRecoverability(users,rotate){let rotating=new Set(rotate),selfService=[],needsAdmin=[];for(let user of users){if(!rotating.has(user.id))continue;if(String(user.email??"").trim()==="")needsAdmin.push(user.id);else selfService.push(user.id)}return{selfService,needsAdmin}}import{randomBytes as randomBytes5}from"crypto";import{eq as eq46}from"drizzle-orm";function createPasswordAdminRoutes(config,options){let{db,logger:logger2}=config,log4=logger2.scoped(AUTH_LOGIN),passwordToolsEnabled=options?.passwordTools===!0,requireGodmin2=createRequireGodmin(config),routePostAuthAdminRotatePasswords=defineRoute({method:"POST",path:"/auth/admin/rotate-passwords",body:Type.Object({confirm:Type.Optional(Type.Boolean()),protectedRoles:Type.Optional(Type.Array(Type.String())),includeInactive:Type.Optional(Type.Boolean())}),detail:{tags:["Authentication"],summary:"Rotate every password except the ones named",description:"Without `confirm: true` this only reports what it would do. Confirmed, it gives every selected account a different random password, signs those sessions out, and tells nobody the new values \u2014 recovery is the reset link or an administrator."},handler:async(ctx)=>{if(!passwordToolsEnabled)return notEnabled(ctx);let gate=await requireGodmin2(ctx.request);if(!gate.ok)return ctx.set.status=gate.status,{success:!1,message:gate.message};let resolved=resolveAuthTablesForRequest(ctx.request,config),usersTable=resolved.usersTable,body=ctx.body,users=await db?.select().from(usersTable),roleNamesByUser=await loadRoleNames(resolved,users),candidates=users.map((row)=>({id:String(row.id),roleNames:roleNamesByUser.get(String(row.id))??[],isGod:row.isGod===!0,email:row.email??null,isActive:row.isActive})),plan=planPasswordRotation({users:candidates,callerId:gate.callerId,protectedRoles:body.protectedRoles??[],includeInactive:body.includeInactive===!0}),recovery=splitByRecoverability(candidates,plan.rotate);if(body.confirm!==!0)return{success:!0,data:{preview:!0,willRotate:plan.rotate.length,skipped:plan.skipped.length,skippedReasons:countReasons(plan.skipped),canResetThemselves:recovery.selfService.length,needAdministrator:recovery.needsAdmin.length}};if(plan.rotate.length===0)return{success:!0,data:{preview:!1,rotated:0,signedOut:0}};let rotated=0;for(let userId of plan.rotate){let hashed=await hashPassword(randomBytes5(24).toString("base64url"));await db?.update(usersTable).set({password:hashed,updatedAt:new Date}).where(eq46(usersTable.id,userId)),rotated+=1}let signedOut=await revokeSessions(resolved,plan.rotate);return log4.warn("Bulk password rotation",{by:gate.callerId,rotated,signedOut,skipped:plan.skipped.length}),{success:!0,data:{preview:!1,rotated,signedOut,canResetThemselves:recovery.selfService.length,needAdministrator:recovery.needsAdmin.length}}}}),routePostAuthAdminSetUserPassword=defineRoute({method:"POST",path:"/auth/admin/set-user-password",body:Type.Object({userId:Type.String(),password:Type.String()}),detail:{tags:["Authentication"],summary:"Set one account's password",description:"For the person who has no email address and cannot use the reset link. Applies the same strength policy as a self-service change and ends that account\u2019s existing sessions."},handler:async(ctx)=>{if(!passwordToolsEnabled)return notEnabled(ctx);let gate=await requireGodmin2(ctx.request);if(!gate.ok)return ctx.set.status=gate.status,{success:!1,message:gate.message};let resolved=resolveAuthTablesForRequest(ctx.request,config),usersTable=resolved.usersTable,{userId,password:password4}=ctx.body,strength=validatePasswordStrength(password4);if(!strength.valid)return ctx.set.status=400,{success:!1,message:strength.errors?.join(" ")??"Password is too weak"};let rows=await db?.select().from(usersTable).where(eq46(usersTable.id,userId)).limit(1);if(!rows||rows.length===0)return ctx.set.status=404,{success:!1,message:"User not found"};await db?.update(usersTable).set({password:await hashPassword(password4),updatedAt:new Date,...clearedLockoutState()}).where(eq46(usersTable.id,userId));let signedOut=await revokeSessions(resolved,[userId]);return log4.warn("Password set by administrator",{by:gate.callerId,userId,signedOut}),{success:!0,data:{signedOut,lockoutCleared:!0}}}});return collectRoutes([{routes:[routePostAuthAdminRotatePasswords,routePostAuthAdminSetUserPassword]}]);function notEnabled(ctx){return ctx.set.status=404,{success:!1,message:"Not found"}}async function loadRoleNames(resolved,users){let byUser=new Map,userRoles=resolved.userRolesTable,roles=resolved.rolesTable;if(!db||!userRoles||!roles||users.length===0)return byUser;let roleRows=await db.select().from(roles),roleNameById=new Map(roleRows.map((row)=>[String(row.id),String(row.name??"")])),links=await db.select().from(userRoles);for(let link of links){let userId=String(link.userId??""),name2=roleNameById.get(String(link.roleId??""));if(!userId||!name2)continue;byUser.set(userId,[...byUser.get(userId)??[],name2])}return byUser}async function revokeSessions(resolved,userIds){return revokeUserSessions({db,sessionsTable:resolved.sessionsTable,onError:(error3)=>log4.error("Sessions could not be revoked after a password change",{error:error3})},userIds)}}function countReasons(skipped){let counts={};for(let item of skipped)counts[item.reason]=(counts[item.reason]??0)+1;return counts}var init_passwordAdmin=__esm(()=>{init_esm3();init_scopes();init_routeCollector();init_utils6();init_requireGodmin();init_revokeUserSessions()});import{eq as eq47}from"drizzle-orm";function createUserStateRoutes(config){let{db,logger:logger2}=config,log4=logger2.scoped(AUTH_LOGIN),requireGodmin2=createRequireGodmin(config);async function loadTarget(request,userId){let resolved=resolveAuthTablesForRequest(request,config),usersTable=resolved.usersTable;if(!db||!usersTable)return{ok:!1,status:500,message:"Database not configured"};let user=(await db.select().from(usersTable).where(eq47(usersTable.id,userId)).limit(1))?.[0];if(!user)return{ok:!1,status:404,message:"User not found"};return{ok:!0,usersTable,sessionsTable:resolved.sessionsTable??null,user}}async function writeState(usersTable,userId,changes){await db?.update(usersTable).set({...changes,updatedAt:new Date}).where(eq47(usersTable.id,userId))}let routePostAuthAdminLockUser=defineRoute({method:"POST",path:"/auth/admin/lock-user",body:Type.Object({userId:Type.String(),until:Type.Optional(Type.String())}),detail:{tags:["Authentication"],summary:"Lock one account",description:"Refuses further login attempts. `until` is an ISO timestamp for a temporary lock; omit it to lock indefinitely. Cannot be used on the calling administrator."},handler:async(ctx)=>{let gate=await requireGodmin2(ctx.request);if(!gate.ok)return ctx.set.status=gate.status,{success:!1,message:gate.message};let{userId,until}=ctx.body,target2=await loadTarget(ctx.request,userId);if(!target2.ok)return ctx.set.status=target2.status,{success:!1,message:target2.message};if(userId===gate.callerId)return ctx.set.status=400,{success:!1,message:"An administrator cannot lock their own account"};let lockedUntil=until?new Date(until):null;if(until&&Number.isNaN(lockedUntil?.getTime()))return ctx.set.status=400,{success:!1,message:"`until` is not a valid date"};await writeState(target2.usersTable,userId,{isLocked:!0,lockedUntil});let signedOut=await revokeUserSessions({db,sessionsTable:target2.sessionsTable,onError:(error3)=>log4.error("Sessions could not be revoked after locking an account",{error:error3,userId})},[userId]);return log4.warn("Account locked by administrator",{by:gate.callerId,userId,until:lockedUntil,signedOut}),{success:!0,data:{isLocked:!0,lockedUntil,signedOut}}}}),routePostAuthAdminUnlockUser=defineRoute({method:"POST",path:"/auth/admin/unlock-user",body:Type.Object({userId:Type.String()}),detail:{tags:["Authentication"],summary:"Release one account from lockout",description:"Clears isLocked, lockedUntil AND failedLoginAttempts together, without touching the password. Clearing only isLocked is not a release: the login route sends any account at or above the captcha threshold through the captcha gate before it checks the password, and the counter only clears on a login that succeeds."},handler:async(ctx)=>{let gate=await requireGodmin2(ctx.request);if(!gate.ok)return ctx.set.status=gate.status,{success:!1,message:gate.message};let{userId}=ctx.body,target2=await loadTarget(ctx.request,userId);if(!target2.ok)return ctx.set.status=target2.status,{success:!1,message:target2.message};let wasLocked=target2.user.isLocked===!0,clearedFailedAttempts=Number(target2.user.failedLoginAttempts??0);return await writeState(target2.usersTable,userId,clearedLockoutState()),log4.warn("Account lockout cleared by administrator",{by:gate.callerId,userId,wasLocked,clearedFailedAttempts}),{success:!0,data:{wasLocked,clearedFailedAttempts}}}}),routePostAuthAdminMarkEmailVerified=defineRoute({method:"POST",path:"/auth/admin/mark-email-verified",body:Type.Object({userId:Type.String()}),detail:{tags:["Authentication"],summary:"Mark one account as email-verified",description:"For an installation with no working mail transport, where the verification message can never arrive. Idempotent: an account that is already verified keeps its original timestamp."},handler:async(ctx)=>{let gate=await requireGodmin2(ctx.request);if(!gate.ok)return ctx.set.status=gate.status,{success:!1,message:gate.message};let{userId}=ctx.body,target2=await loadTarget(ctx.request,userId);if(!target2.ok)return ctx.set.status=target2.status,{success:!1,message:target2.message};let alreadyVerified=Boolean(target2.user.verifiedAt),verifiedAt=alreadyVerified?target2.user.verifiedAt:new Date;if(!alreadyVerified)await writeState(target2.usersTable,userId,{verifiedAt,emailVerified:!0});return log4.warn("Email marked verified by administrator",{by:gate.callerId,userId,alreadyVerified}),{success:!0,data:{verifiedAt,alreadyVerified}}}});return collectRoutes([{routes:[routePostAuthAdminLockUser,routePostAuthAdminUnlockUser,routePostAuthAdminMarkEmailVerified]}])}var init_userState=__esm(()=>{init_esm3();init_scopes();init_routeCollector();init_requireGodmin();init_revokeUserSessions()});import{and as and23,count as count2,eq as eq48,sql as sql10}from"drizzle-orm";function createApiKeyRoutes(app,config,apiKeysConfig){let basePath=apiKeysConfig.route||"/auth/api-keys",apiKeysTable=config.apiKeysTable,{db,logger:logger2}=config;if(!apiKeysTable||!db){logger2.warn("[API_KEYS] api_keys table or database not available. Skipping API key routes.");return}let getResolvedConfig=(request)=>{let resolved=resolveAuthTablesForRequest(request,config),reqApiKeysTable=resolved.apiKeysTable??apiKeysTable;return{config:{...config,usersTable:resolved.usersTable??config.usersTable,userRolesTable:resolved.userRolesTable,rolesTable:resolved.rolesTable,roleClaimsTable:resolved.roleClaimsTable,claimsTable:resolved.claimsTable,apiKeysTable:reqApiKeysTable,schemaTables:resolved.schemaTables},apiKeysTable:reqApiKeysTable}},keyPrefix=apiKeysConfig.keyPrefix||"nk_live",maxKeysPerUser=apiKeysConfig.maxKeysPerUser||10,preventManagement=apiKeysConfig.preventApiKeyManagement??!1,routePostRoot=defineRoute({method:"POST",path:"/",handler:async({request,set:set2,body:rawBody})=>{let resolved=getResolvedConfig(request),reqApiKeysTable=resolved.apiKeysTable,reqConfig=resolved.config;if(preventManagement)return set2.status=403,{isSuccess:!1,message:"API key management is disabled via configuration",data:null};if(request.headers.get("x-auth-type")==="api_key")return set2.status=403,{isSuccess:!1,message:"API keys cannot be created using another API key",data:null};let userId=request.headers.get("x-user-id");if(!userId)return set2.status=401,{isSuccess:!1,message:"Unauthenticated",data:null};let body;try{body=rawBody??{}}catch{return set2.status=400,{isSuccess:!1,message:"Invalid request body",data:null}}if(!body.name||typeof body.name!=="string"||body.name.trim().length===0)return set2.status=400,{isSuccess:!1,message:"Name is required",data:null};if(body.ownerType==="application"){if(!(apiKeysConfig.allowApplicationKeys??!0))return set2.status=403,{isSuccess:!1,message:"Application API keys are not allowed",data:null};if(!body.applicationName||body.applicationName.trim().length===0)return set2.status=400,{isSuccess:!1,message:"Application name is required for application keys",data:null}}if(((await db.select({count:count2()}).from(reqApiKeysTable).where(and23(eq48(col13(reqApiKeysTable,"userId"),userId),eq48(col13(reqApiKeysTable,"isActive"),!0))))[0]?.count||0)>=maxKeysPerUser)return set2.status=400,{isSuccess:!1,message:`Maximum API key limit reached (${maxKeysPerUser})`,data:null};let userRoles=await getUserRoleNames(reqConfig,userId),userClaims=await getUserClaimActions(reqConfig,userId),isGodmin4=userRoles.some((r)=>isGodminRole(r)),requestedRoles=body.allowedRoles||[],requestedClaims=body.allowedClaims||[],requestedScopes=body.allowedScopes||[],stripGodmin=(roles)=>roles.filter((r)=>!isGodminRole(r)),finalRoles,finalClaims;if(isGodmin4)finalRoles=requestedRoles.length>0?stripGodmin(requestedRoles):[],finalClaims=requestedClaims.length>0?requestedClaims:userClaims;else{let validRoles=intersectPermissions(userRoles,requestedRoles),validClaims=intersectPermissions(userClaims,requestedClaims);if(requestedRoles.length>0&&validRoles.length!==requestedRoles.length){let disallowed=requestedRoles.filter((r)=>!userRoles.includes(r));return set2.status=403,{isSuccess:!1,message:`Cannot assign roles you do not have: ${disallowed.join(", ")}`,data:null}}if(requestedClaims.length>0&&validClaims.length!==requestedClaims.length){let disallowed=requestedClaims.filter((c)=>!userClaims.includes(c));return set2.status=403,{isSuccess:!1,message:`Cannot assign claims you do not have: ${disallowed.join(", ")}`,data:null}}if(requestedScopes.length>0){let userScopes=await getUserScopes(reqConfig,userId);if(intersectPermissions(userScopes,requestedScopes).length!==requestedScopes.length){let disallowed=requestedScopes.filter((s)=>!userScopes.includes(s));return set2.status=403,{isSuccess:!1,message:`Cannot assign scopes you do not have: ${disallowed.join(", ")}`,data:null}}}finalRoles=stripGodmin(requestedRoles.length>0?validRoles:userRoles),finalClaims=requestedClaims.length>0?validClaims:userClaims}let expiresAt=null,expiresInStr=body.expiresIn||apiKeysConfig.defaultExpiresIn;if(expiresInStr){let seconds=parseTimeToSeconds2(expiresInStr);expiresAt=new Date(Date.now()+seconds*1000)}let generated=generateApiKey(keyPrefix);try{let createdId=(await db.insert(reqApiKeysTable).values({userId,name:body.name.trim(),description:body.description?.trim()||null,keyHash:generated.keyHash,keyPreview:generated.keyPreview,ownerType:body.ownerType||"personal",applicationName:body.applicationName?.trim()||null,expiresAt,isActive:!0,usageCount:0,createdAt:new Date,updatedAt:new Date}).returning())[0]?.id;if(finalRoles.length>0||finalClaims.length>0||requestedScopes.length>0){let jsonbUpdate={};if(finalRoles.length>0)jsonbUpdate.allowedRoles=JSON.stringify(finalRoles);if(finalClaims.length>0)jsonbUpdate.allowedClaims=JSON.stringify(finalClaims);if(requestedScopes.length>0)jsonbUpdate.allowedScopes=JSON.stringify(requestedScopes);await db.update(reqApiKeysTable).set(jsonbUpdate).where(eq48(col13(reqApiKeysTable,"keyHash"),generated.keyHash))}return logger2.info("[API_KEYS] API key created",{keyId:createdId,userId,ownerType:body.ownerType||"personal",applicationName:body.applicationName,rolesCount:finalRoles.length,claimsCount:finalClaims.length,expiresAt:expiresAt?.toISOString()||"never"}),{isSuccess:!0,message:"API key created successfully. Save this key \u2014 it will not be shown again.",data:{id:createdId,key:generated.rawKey,keyPreview:generated.keyPreview,name:body.name.trim(),ownerType:body.ownerType||"personal",applicationName:body.applicationName||null,allowedRoles:finalRoles,allowedClaims:finalClaims,allowedScopes:requestedScopes,expiresAt:expiresAt?.toISOString()||null}}}catch(err){let errDetail;try{errDetail=JSON.stringify(err,Object.getOwnPropertyNames(err))}catch{errDetail=err instanceof Error?err.message:String(err)}return logger2.error("[API_KEYS] Failed to create API key",{error:errDetail,userId}),set2.status=500,{isSuccess:!1,message:"Failed to create API key",data:null}}}}),routeGetRoot=defineRoute({method:"GET",path:"/",detail:{tags:["API keys"],summary:"List the caller's API keys",description:"Every key the caller owns, revoked ones included \u2014 a revoked key is history, not "+"garbage, and it is the only place `revokedAt` / `revokedReason` can be read. Filter on `isActive` for the live set; that is also what `maxKeysPerUser` counts."},handler:async({request,set:set2})=>{let{apiKeysTable:reqApiKeysTable}=getResolvedConfig(request),userId=request.headers.get("x-user-id");if(!userId)return set2.status=401,{isSuccess:!1,message:"Unauthenticated",data:null};let items=(await db.select().from(reqApiKeysTable).where(eq48(col13(reqApiKeysTable,"userId"),userId)).orderBy(sql10`created_at DESC`)).map((row)=>sanitizeKeyForResponse(row));return{isSuccess:!0,data:{items,totalCount:items.length}}}}),routeGetId=defineRoute({method:"GET",path:"/:id",detail:{tags:["API keys"],summary:"Read one API key",description:"A REVOKED key still answers 200 here \u2014 revoking does not delete the row. Read "+"`isActive` / `revokedAt` rather than the status to decide whether the key is live. 404 means no such key, or a key belonging to somebody else."},handler:async({params,request,set:set2})=>{let{apiKeysTable:reqApiKeysTable}=getResolvedConfig(request),userId=request.headers.get("x-user-id");if(!userId)return set2.status=401,{isSuccess:!1,message:"Unauthenticated",data:null};let keyId=params.id,record3=(await db.select().from(reqApiKeysTable).where(and23(eq48(col13(reqApiKeysTable,"id"),keyId),eq48(col13(reqApiKeysTable,"userId"),userId))).limit(1))[0];if(!record3)return set2.status=404,{isSuccess:!1,message:"API key not found",data:null};return{isSuccess:!0,data:sanitizeKeyForResponse(record3)}}}),routePatchId=defineRoute({method:"PATCH",path:"/:id",handler:async({params,request,set:set2,body:rawBody})=>{let resolved=getResolvedConfig(request),reqApiKeysTable=resolved.apiKeysTable,reqConfig=resolved.config;if(preventManagement)return set2.status=403,{isSuccess:!1,message:"API key management is disabled via configuration",data:null};if(request.headers.get("x-auth-type")==="api_key")return set2.status=403,{isSuccess:!1,message:"API keys cannot be modified using another API key",data:null};let userId=request.headers.get("x-user-id");if(!userId)return set2.status=401,{isSuccess:!1,message:"Unauthenticated",data:null};let keyId=params.id,body;try{body=rawBody??{}}catch{return set2.status=400,{isSuccess:!1,message:"Invalid request body",data:null}}let existing=(await db.select().from(reqApiKeysTable).where(and23(eq48(col13(reqApiKeysTable,"id"),keyId),eq48(col13(reqApiKeysTable,"userId"),userId))).limit(1))[0];if(!existing)return set2.status=404,{isSuccess:!1,message:"API key not found",data:null};if(existing.revokedAt)return set2.status=400,{isSuccess:!1,message:"Cannot modify a revoked API key",data:null};let userRoles=await getUserRoleNames(reqConfig,userId),userClaims=await getUserClaimActions(reqConfig,userId),updatePayload={updatedAt:new Date};if(body.name!==void 0){if(typeof body.name!=="string"||body.name.trim().length===0)return set2.status=400,{isSuccess:!1,message:"Name cannot be empty",data:null};updatePayload.name=body.name.trim()}if(body.description!==void 0)updatePayload.description=body.description?.trim()||null;if(body.allowedRoles!==void 0){if(intersectPermissions(userRoles,body.allowedRoles).length!==body.allowedRoles.length){let disallowed=body.allowedRoles.filter((r)=>!userRoles.includes(r));return set2.status=403,{isSuccess:!1,message:`Cannot assign roles you do not have: ${disallowed.join(", ")}`,data:null}}updatePayload.allowedRoles=body.allowedRoles.filter((r)=>!isGodminRole(r))}if(body.allowedClaims!==void 0){if(intersectPermissions(userClaims,body.allowedClaims).length!==body.allowedClaims.length){let disallowed=body.allowedClaims.filter((c)=>!userClaims.includes(c));return set2.status=403,{isSuccess:!1,message:`Cannot assign claims you do not have: ${disallowed.join(", ")}`,data:null}}updatePayload.allowedClaims=body.allowedClaims}if(body.allowedScopes!==void 0){if(body.allowedScopes.length>0){let userScopes=await getUserScopes(reqConfig,userId);if(intersectPermissions(userScopes,body.allowedScopes).length!==body.allowedScopes.length){let disallowed=body.allowedScopes.filter((s)=>!userScopes.includes(s));return set2.status=403,{isSuccess:!1,message:`Cannot assign scopes you do not have: ${disallowed.join(", ")}`,data:null}}}updatePayload.allowedScopes=body.allowedScopes}try{await db.update(reqApiKeysTable).set(updatePayload).where(eq48(col13(reqApiKeysTable,"id"),keyId)),logger2.info("[API_KEYS] API key updated",{keyId,userId});let updated=await db.select().from(reqApiKeysTable).where(eq48(col13(reqApiKeysTable,"id"),keyId)).limit(1);return{isSuccess:!0,message:"API key updated successfully",data:sanitizeKeyForResponse(updated[0])}}catch(err){return logger2.error("[API_KEYS] Failed to update API key",{error:err,keyId,userId}),set2.status=500,{isSuccess:!1,message:"Failed to update API key",data:null}}}}),revokeApiKey=async({params,request,set:set2,body:rawBody})=>{let{apiKeysTable:reqApiKeysTable}=getResolvedConfig(request);if(request.headers.get("x-auth-type")==="api_key")return set2.status=403,{isSuccess:!1,message:"API keys cannot be revoked using another API key",data:null};let userId=request.headers.get("x-user-id");if(!userId)return set2.status=401,{isSuccess:!1,message:"Unauthenticated",data:null};let keyId=params.id,body={};try{if(rawBody)body=rawBody}catch{}let record3=(await db.select().from(reqApiKeysTable).where(and23(eq48(col13(reqApiKeysTable,"id"),keyId),eq48(col13(reqApiKeysTable,"userId"),userId))).limit(1))[0];if(!record3)return set2.status=404,{isSuccess:!1,message:"API key not found",data:null};if(record3.revokedAt)return set2.status=400,{isSuccess:!1,message:"API key is already revoked",data:null};let revokedAt=new Date,revokedReason=body.reason||"user_revoked";try{await db.update(reqApiKeysTable).set({isActive:!1,revokedAt,revokedReason,updatedAt:revokedAt}).where(eq48(col13(reqApiKeysTable,"id"),keyId)),logger2.info("[API_KEYS] API key revoked",{keyId,userId,reason:revokedReason});let revoked=(await db.select().from(reqApiKeysTable).where(eq48(col13(reqApiKeysTable,"id"),keyId)).limit(1))[0]??{...record3,isActive:!1,revokedAt,revokedReason,updatedAt:revokedAt};return{isSuccess:!0,message:"API key revoked. It can no longer authenticate. This is a revoke, not a delete \u2014 "+`the record is retained as history and still reads at GET ${basePath}/${keyId}.`,data:sanitizeKeyForResponse(revoked)}}catch(err){return logger2.error("[API_KEYS] Failed to revoke API key",{error:err,keyId,userId}),set2.status=500,{isSuccess:!1,message:"Failed to revoke API key",data:null}}},revokeDetail=(summary)=>({tags:["API keys"],summary,description:"Revokes the key: it stops authenticating immediately, on this request. It is NOT a "+"delete \u2014 the row is retained as the audit record (`revokedAt`, `revokedReason`, "+"`usageCount`, `lastUsedAt`), so `GET /:id` still answers 200 with `isActive:false` and the list still carries it. The answer carries the revoked record. Revoking an "+"already-revoked key answers 400, not 404 \u2014 the row is still there. Revoking is "+"deliberately NOT blocked by `preventApiKeyManagement`: being unable to kill a leaked key because management is off would be worse than the rule it enforces."}),routePostIdRevoke=defineRoute({method:"POST",path:"/:id/revoke",detail:revokeDetail("Revoke an API key"),handler:(ctx)=>revokeApiKey(ctx)}),routeDeleteId=defineRoute({method:"DELETE",path:"/:id",detail:revokeDetail("Revoke an API key (alias of POST /:id/revoke)"),handler:(ctx)=>revokeApiKey(ctx)});mountRoutes(app,[{prefix:basePath,routes:[routePostRoot,routeGetRoot,routeGetId,routePatchId,routePostIdRevoke,routeDeleteId]}]),logger2.info(`[API_KEYS] Routes registered at ${basePath}`)}var col13=(table,name2)=>table[name2],getUserRoleNames=async(config,userId)=>{if(!config.db||!config.userRolesTable||!config.rolesTable)return[];return(await config.db.select({name:col13(config.rolesTable,"name")}).from(config.userRolesTable).innerJoin(config.rolesTable,eq48(col13(config.rolesTable,"id"),col13(config.userRolesTable,"roleId"))).where(eq48(col13(config.userRolesTable,"userId"),userId))).map((r)=>r.name).filter((n)=>n!==void 0)},getUserClaimActions=async(config,userId)=>{if(!config.db||!config.userRolesTable||!config.roleClaimsTable||!config.claimsTable)return[];let rows=await config.db.select({action:col13(config.claimsTable,"action")}).from(config.userRolesTable).innerJoin(config.roleClaimsTable,eq48(col13(config.roleClaimsTable,"roleId"),col13(config.userRolesTable,"roleId"))).innerJoin(config.claimsTable,eq48(col13(config.claimsTable,"id"),col13(config.roleClaimsTable,"claimId"))).where(eq48(col13(config.userRolesTable,"userId"),userId)),uniqueActions=new Set(rows.map((r)=>r.action));return Array.from(uniqueActions).filter((a)=>a!==void 0)},getUserScopes=async(config,userId)=>{if(!config.db||!config.userRolesTable||!config.roleClaimsTable)return[];let rows=await config.db.select({scope:col13(config.roleClaimsTable,"scope")}).from(config.userRolesTable).innerJoin(config.roleClaimsTable,eq48(col13(config.roleClaimsTable,"roleId"),col13(config.userRolesTable,"roleId"))).where(eq48(col13(config.userRolesTable,"userId"),userId)),uniqueScopes=new Set(rows.map((r)=>r.scope).filter((s)=>s!==null&&s!==void 0&&s.length>0));return Array.from(uniqueScopes)},sanitizeKeyForResponse=(record3)=>{let{keyHash:_keyHash,...safe}=record3;return safe};var init_apiKeys=__esm(()=>{init_ApiKey();init_GodminSetup();init_routeMount();init_utils5()});function createCaptchaRoutes(config){let{captchaService,logger:logger2,basePath="/auth/captcha"}=config;if(!captchaService.isEnabled())return collectRoutes();let warnedUnattributable=!1,warnUnattributableOnce=()=>{if(warnedUnattributable)return;warnedUnattributable=!0,logger2.warn("[CAPTCHA] No client address on the request \u2014 the per-IP generate limit is OFF. "+"Every caller would otherwise share one bucket, which locks out an entire office behind one egress. Configure the proxy or ingress to forward X-Forwarded-For.")},routeGetBasepathGenerate=defineRoute({method:"GET",path:"/generate",query:GenerateCaptchaQuerySchema,detail:{tags:["Captcha"],summary:"Generate Captcha",description:"Generate a new captcha challenge"},handler:async(ctx)=>{let{type:type4,difficulty}=ctx.query,ipAddress=ctx.request.headers.get("x-forwarded-for")?.split(",")[0]?.trim()||ctx.request.headers.get("x-real-ip")?.trim()||ctx.request.headers.get("cf-connecting-ip")?.trim()||void 0;if(!ipAddress)warnUnattributableOnce();let result=await captchaService.generate(type4,difficulty,ipAddress);if(result.rateLimited)return ctx.set.status=429,{success:!1,message:result.message??"Too many requests. Please try again later."};return logger2.info("[CAPTCHA] Challenge generated via endpoint",{challengeId:result.challengeId,type:result.type,ipAddress}),{success:!0,data:{challengeId:result.challengeId,type:result.type,question:result.question,expiresAt:result.expiresAt,...result.imageData?{imageData:result.imageData}:{},...result.puzzleData?{puzzleData:{pieces:result.puzzleData.pieces}}:{}}}}}),routePostBasepathValidate=defineRoute({method:"POST",path:"/validate",body:ValidateCaptchaBodySchema,detail:{tags:["Captcha"],summary:"Validate Captcha",description:"Validate a captcha answer"},handler:async(ctx)=>{let{challengeId,answer}=ctx.body,result=await captchaService.validate(challengeId,answer);if(!result.valid)ctx.set.status=400;return result}});return collectRoutes([{prefix:basePath,routes:[routeGetBasepathGenerate,routePostBasepathValidate]}])}var GenerateCaptchaQuerySchema,ValidateCaptchaBodySchema;var init_captcha=__esm(()=>{init_esm3();init_routeCollector();GenerateCaptchaQuerySchema=Type.Object({type:Type.Optional(Type.Union([Type.Literal("math"),Type.Literal("image"),Type.Literal("puzzle"),Type.Literal("text")])),difficulty:Type.Optional(Type.Union([Type.Literal("easy"),Type.Literal("medium"),Type.Literal("hard")]))}),ValidateCaptchaBodySchema=Type.Object({challengeId:Type.String(),answer:Type.String()})});import{eq as eq49}from"drizzle-orm";function createCheckRoute(config,checkConfig){let{db,logger:logger2}=config,route=checkConfig.route||"/auth/check",routePostCheck=defineRoute({method:"POST",path:"/",detail:{tags:["Auth"],summary:"Check authorization for a given entity and method",description:"Allows consumer/resource servers to perform full claim checks (including scope) against the IDP's authorization system."},handler:async(ctx)=>{let{body,request,set:set2}=ctx,userId=request.headers.get("x-user-id");if(!userId)return set2.status=401,{isSuccess:!1,message:"Unauthenticated",data:null};if(!db)return set2.status=503,{isSuccess:!1,message:"Authorization service unavailable",data:null};let{schemaTables}=resolveAuthTablesForRequest(request,config);if(!schemaTables)return set2.status=503,{isSuccess:!1,message:"Authorization tables not available",data:null};let{entity,method,fields,relations}=body;if(!entity||!method)return set2.status=400,{isSuccess:!1,message:"entity and method are required",data:null};let result=await checkAuthorization({userId,method,entity,requestedFields:fields,requestedRelations:relations,db,schemaTables,logger:logger2,getUserData:async()=>{let usersTbl=schemaTables[AUTHORIZATION_TABLE_KEYS.users];if(!usersTbl||!db)return;let idCol=usersTbl.id;if(!idCol)return;return(await db.select().from(usersTbl).where(eq49(idCol,userId)).limit(1))[0]}});return{isSuccess:!0,message:result.authorized?"Authorized":"Forbidden",data:{authorized:result.authorized,reason:result.reason,allowedFields:result.allowedFields,allowedRelations:result.allowedRelations,scopeFilters:result.scopeFilters}}}});return collectRoutes([{prefix:route,routes:[routePostCheck]}])}var init_check=__esm(()=>{init_Authorization();init_routeCollector()});import{sql as sql11}from"drizzle-orm";function createEmailVerificationRoutes(config){let{authConfig,registerConfig,getEmailService,appName}=config,{db,logger:logger2}=authConfig;if(!registerConfig.enabled||!registerConfig.emailVerification?.enabled)return collectRoutes();let verifyRoute="/verify-email",resendRoute="/resend-verification",routeGetVerifyroute=defineRoute({method:"GET",path:"/",query:Type.Object({token:Type.String()}),detail:{tags:["Authentication"],summary:"Verify Email",description:"Verify user email address using the verification token"},handler:async(ctx)=>{let{usersTable}=resolveAuthTablesForRequest(ctx.request,authConfig);if(!db||!usersTable)return ctx.set.status=500,{success:!1,message:"Database not configured"};let token=ctx.query.token;if(!token)return ctx.set.status=400,{success:!1,message:"Verification token is required"};let users=await db.select().from(usersTable).where(sql11`email_verification_token = ${hashVerificationToken(token)}`).limit(1);if(users.length===0)return logger2.warn("[AUTH] Email verification failed - invalid token"),ctx.set.status=400,{success:!1,message:"Invalid or expired verification token"};let user=users[0],tokenExpiresAt=user.emailVerificationTokenExpiresAt;if(tokenExpiresAt&&new Date>new Date(tokenExpiresAt))return logger2.warn("[AUTH] Email verification failed - token expired",{userId:user.id,email:user.email}),ctx.set.status=400,{success:!1,message:"Verification token has expired. Please request a new one."};await db.update(usersTable).set({verifiedAt:new Date,emailVerificationToken:null,emailVerificationTokenExpiresAt:null}).where(sql11`id = ${user.id}`),logger2.info("[AUTH] Email verified successfully",{userId:user.id,email:user.email});let emailService=getEmailService?.()??null;if(registerConfig.emailVerification?.templates?.welcome?.enabled&&emailService?.isAvailable())emailService.sendWelcomeEmail(user.email,user.email.split("@")[0]||"User",appName||"Nucleus").catch((err)=>{logger2.error("[AUTH] Failed to send welcome email after verification",{email:user.email,error:err})});return{success:!0,message:"Email verified successfully. You can now log in.",data:{redirectUrl:registerConfig.emailVerification?.redirectUrl||"/login",verified:!0}}}}),routePostResendroute=defineRoute({method:"POST",path:"/",body:Type.Object({email:Type.String({format:"email"})}),detail:{tags:["Authentication"],summary:"Resend Verification Email",description:"Resend the email verification link to the user"},handler:async(ctx)=>{let{usersTable}=resolveAuthTablesForRequest(ctx.request,authConfig);if(!db||!usersTable)return ctx.set.status=500,{success:!1,message:"Database not configured"};let emailService=getEmailService?.()??null;if(!emailService?.isAvailable())return ctx.set.status=503,{success:!1,message:"Email service not available"};let{email}=ctx.body,antiEnum=authConfig.authentication?.preventUserEnumeration===!0,uniformResend={success:!0,message:"If your email is registered and not yet verified, a verification email has been sent."},users=await db.select().from(usersTable).where(sql11`email = ${email}`).limit(1);if(users.length===0)return antiEnum?uniformResend:{success:!0,message:"If your email is registered, you will receive a verification email."};let user=users[0];if(user.verifiedAt){if(antiEnum)return uniformResend;return ctx.set.status=409,{success:!1,message:"Email is already verified"}}let maxAttempts=registerConfig.emailVerification?.maxResendAttempts||3,currentAttempts=user.emailVerificationAttempts||0;if(currentAttempts>=maxAttempts){if(logger2.warn("[AUTH] Resend verification failed - max attempts reached",{email,attempts:currentAttempts}),antiEnum)return uniformResend;return ctx.set.status=429,{success:!1,message:"Maximum resend attempts reached. Please contact support.",data:{maxAttemptsReached:!0,attemptsUsed:currentAttempts,maxAttempts}}}let resendCooldown=registerConfig.emailVerification?.resendCooldown||"60s",cooldownMs=parseTimeToMs(resendCooldown),lastSentAt=user.emailVerificationSentAt;if(lastSentAt){let timeSinceLastSent=Date.now()-new Date(lastSentAt).getTime();if(timeSinceLastSent<cooldownMs){let waitSeconds=Math.ceil((cooldownMs-timeSinceLastSent)/1000);if(antiEnum)return uniformResend;return ctx.set.status=429,{success:!1,message:`Please wait ${waitSeconds} seconds before requesting another verification email.`,data:{cooldownRemaining:waitSeconds,canResendAt:new Date(Date.now()+waitSeconds*1000).toISOString()}}}}let verificationToken=generateVerificationToken(),tokenExpiresIn=registerConfig.emailVerification?.tokenExpiresIn||"24h",tokenExpiresAt=new Date(Date.now()+parseTimeToMs(tokenExpiresIn));await db.update(usersTable).set({emailVerificationToken:hashVerificationToken(verificationToken),emailVerificationTokenExpiresAt:tokenExpiresAt,emailVerificationSentAt:new Date,emailVerificationAttempts:currentAttempts+1}).where(sql11`id = ${user.id}`);let verificationLink=buildEmailActionLink({request:ctx.request,configuredUrl:registerConfig.emailVerification?.redirectUrl,path:"/verify-email",query:{token:verificationToken},allowedOrigins:authConfig.authentication?.trustedAppOrigins}),result=await emailService.sendVerificationEmail(email,email.split("@")[0]||"User",verificationLink,appName||"Nucleus");if(result.success){logger2.info("[AUTH] Verification email resent",{email});let cooldownSeconds=Math.ceil(cooldownMs/1000);if(antiEnum)return uniformResend;return{success:!0,message:"Verification email sent. Please check your inbox.",data:{cooldownSeconds,canResendAt:new Date(Date.now()+cooldownMs).toISOString(),attemptsRemaining:maxAttempts-(currentAttempts+1)}}}if(logger2.error("[AUTH] Failed to resend verification email",{email,error:result.error}),antiEnum)return uniformResend;return{success:!1,message:"Failed to send verification email. Please try again later."}}});return collectRoutes([{prefix:verifyRoute,routes:[routeGetVerifyroute]},{prefix:resendRoute,routes:[routePostResendroute]}])}var init_emailVerification=__esm(()=>{init_esm3();init_routeCollector();init_utils6()});import crypto7 from"crypto";function generateMagicToken(){return crypto7.randomBytes(32).toString("hex")}function hashToken(token,purpose){let input=purpose?`${purpose}:${token}`:token;return crypto7.createHash("sha256").update(input).digest("hex")}function parseTimeToMs3(time2){let match=time2.match(/^(\d+)(s|m|h|d)$/);if(!match?.[1]||!match[2])return 900000;let value2=parseInt(match[1],10);switch(match[2]){case"s":return value2*1000;case"m":return value2*60*1000;case"h":return value2*60*60*1000;case"d":return value2*24*60*60*1000;default:return 900000}}var TOKEN_PURPOSE;var init_utils8=__esm(()=>{TOKEN_PURPOSE={magicLink:"magic-link",invite:"invite"}});import{eq as eq50}from"drizzle-orm";function createInviteRoute(config,inviteConfig,getEmailService,storeMagicToken,appName,getMagicToken){let{db,logger:logger2}=config,route=inviteConfig.route||"/auth/invite",expiresIn=inviteConfig.tokenExpiresIn||"7d",redirectUrl=inviteConfig.redirectUrl||"";if(!inviteConfig.enabled)return collectRoutes();let declared=[],routePostRoute=defineRoute({method:"POST",path:"/",body:InviteBodySchema,detail:{tags:["Authentication"],summary:"Invite User",description:"Invite a new user by sending them an email with a magic link to set their password"},handler:async(ctx)=>{let resolved=resolveAuthTablesForRequest(ctx.request,config),{usersTable}=resolved;if(!db||!usersTable)return ctx.set.status=500,{success:!1,message:"Database not configured"};let emailService=getEmailService?.();if(!emailService?.isAvailable())return logger2.error("[AUTH] Invite requested but email service not available"),ctx.set.status=503,{success:!1,message:"Email service not available"};let{email:rawEmail}=ctx.body,email=rawEmail.toLowerCase(),antiEnum=config.authentication?.preventUserEnumeration===!0,uniformInvite={success:!0,message:"If the email address is eligible, an invitation has been sent."},existingUsers=await db.select().from(usersTable).where(eq50(usersTable.email,email)).limit(1),userId;if(existingUsers.length>0){let existingUser=existingUsers[0];if(existingUser.verifiedAt||existingUser.password){if(logger2.warn("[AUTH] Invite failed - user already verified",{email}),antiEnum)return uniformInvite;return ctx.set.status=409,{success:!1,message:"User with this email is already verified"}}userId=existingUser.id,logger2.info("[AUTH] Resending invitation to existing unverified user",{userId,email})}else{let insertValues={email,password:null,emailVerified:!1,isLocked:!1,createdAt:new Date,updatedAt:new Date},newUser=(await db.insert(usersTable).values(insertValues).returning())[0];if(!newUser)return logger2.error("[AUTH] Failed to create invited user",{email}),ctx.set.status=500,{success:!1,message:"Failed to create user"};userId=newUser.id,logger2.info("[AUTH] Invited user created",{userId,email});let inviteDefaultRoles=resolveDefaultRoleNames(config.authentication);if(inviteDefaultRoles.length>0)await assignDefaultRoles({db,userId,roleNames:inviteDefaultRoles,rolesTable:resolved.rolesTable??config.rolesTable??null,userRolesTable:resolved.userRolesTable??config.userRolesTable??null,logger:logger2})}let token=generateMagicToken(),tokenHash=hashToken(token,TOKEN_PURPOSE.invite),expiresAt=new Date(Date.now()+parseTimeToMs3(expiresIn)),reqSchemaName=ctx.request.headers.get("x-tenant-schema")||void 0;await storeMagicToken({userId,email,tokenHash,expiresAt},reqSchemaName);let inviteLink=redirectUrl?buildEmailActionLink({request:ctx.request,configuredUrl:redirectUrl,path:extractConfiguredPath(redirectUrl,"/set-password"),query:{token,invite:"true"},allowedOrigins:config.authentication?.trustedAppOrigins}):`/set-password?token=${token}&invite=true`,sendResult=await emailService.sendInvitationEmail(email,inviteLink,appName||"Nucleus");if(!sendResult.success){if(logger2.error("[AUTH] Failed to send invitation email",{email,error:sendResult.error}),antiEnum)return uniformInvite;return{success:!0,message:"User created but failed to send invitation email",data:{id:userId,email}}}if(logger2.info("[AUTH] Invitation email sent",{email,userId}),antiEnum)return uniformInvite;return{success:!0,message:"Invitation sent successfully",data:{id:userId,email}}}});if(declared.push(routePostRoute),getMagicToken){let routePostRouteVerify=defineRoute({method:"POST",path:"/verify",body:Type.Object({token:Type.String()}),detail:{tags:["Authentication"],summary:"Verify Invite Token",description:"Validate an invite token without consuming it. Returns user info if valid."},handler:async(ctx)=>{let{token}=ctx.body;if(!token)return ctx.set.status=400,{success:!1,message:"Token is required"};let tokenHash=hashToken(token,TOKEN_PURPOSE.invite),invSchemaName=ctx.request.headers.get("x-tenant-schema")||void 0,storedToken=await getMagicToken(tokenHash,invSchemaName);if(!storedToken)return logger2.warn("[AUTH] Invalid invite verify token"),ctx.set.status=400,{success:!1,message:"Invalid or expired token"};if(new Date>storedToken.expiresAt)return logger2.warn("[AUTH] Expired invite verify token",{email:storedToken.email}),ctx.set.status=400,{success:!1,message:"Invalid or expired token"};return logger2.info("[AUTH] Invite token verified (not consumed)",{email:storedToken.email,userId:storedToken.userId}),{success:!0,data:{userId:storedToken.userId,email:storedToken.email}}}});declared.push(routePostRouteVerify)}return collectRoutes([{prefix:route,routes:declared}])}var InviteBodySchema;var init_invite=__esm(()=>{init_esm3();init_routeCollector();init_assignDefaultRole();init_utils8();InviteBodySchema=Type.Object({email:Type.String({format:"email"})})});var exports_DeviceTrust={};__export(exports_DeviceTrust,{selectUsableDeviceRow:()=>selectUsableDeviceRow,readDeviceTokenFromCookieHeader:()=>readDeviceTokenFromCookieHeader,mintDeviceToken:()=>mintDeviceToken,isDeviceRowUsable:()=>isDeviceRowUsable,isApprovalMethod:()=>isApprovalMethod,hashDeviceToken:()=>hashDeviceToken,deviceCookieName:()=>deviceCookieName,decideDeviceApproval:()=>decideDeviceApproval,buildDeviceTokenCookie:()=>buildDeviceTokenCookie,buildClearedDeviceTokenCookie:()=>buildClearedDeviceTokenCookie,DEVICE_TOKEN_BYTES:()=>DEVICE_TOKEN_BYTES});import{createHash as createHash4,randomBytes as randomBytes6}from"crypto";function hashDeviceToken(raw){return createHash4("sha256").update(raw).digest("hex")}function mintDeviceToken(){let raw=randomBytes6(DEVICE_TOKEN_BYTES).toString("hex");return{raw,hash:hashDeviceToken(raw)}}function isDeviceRowUsable(row,now){if(!row||row.status!=="trusted")return!1;if((row.revoked_at??row.revokedAt)!=null)return!1;let expRaw=row.expires_at??row.expiresAt;if(expRaw==null)return!1;let exp=expRaw instanceof Date?expRaw:new Date(expRaw);if(Number.isNaN(exp.getTime())||exp<=now)return!1;return!0}function selectUsableDeviceRow(rows,expectedUserId,now){if(!expectedUserId)return null;for(let row of rows??[]){if((row.user_id??row.userId)!==expectedUserId)continue;if(isDeviceRowUsable(row,now))return row}return null}function buildDeviceTokenCookie(raw,opts){let prefix=opts.cookiePrefix??"",name2=`${prefix}${opts.cookieName??"device_token"}`,sameSite=opts.sameSite??"Lax",secure=prefix==="__Secure-"||prefix==="__Host-"||opts.secure!==!1,allowDomain=prefix!=="__Host-",parts=[`${name2}=${raw}`,"Path=/","HttpOnly",`SameSite=${sameSite}`];if(secure)parts.push("Secure");if(allowDomain&&opts.domain)parts.push(`Domain=${opts.domain}`);return parts.push(`Max-Age=${Math.max(0,Math.floor(opts.maxAgeSeconds))}`),parts.join("; ")}function buildClearedDeviceTokenCookie(opts){return buildDeviceTokenCookie("",{...opts,maxAgeSeconds:0})}function decideDeviceApproval(params){let NO_APPROVAL={requiresApproval:!1,deny:!1,mintToken:!1,trustImmediately:!1},TRUST_NOW={requiresApproval:!1,deny:!1,mintToken:!0,trustImmediately:!0};if(params.knownDevice)return NO_APPROVAL;if(params.isImpersonation||!isApprovalMethod(params.loginMethod,params.approvalMethods))return NO_APPROVAL;if(!params.hasValidFingerprint&¶ms.onUnidentifiable==="deny")return{requiresApproval:!1,deny:!0,mintToken:!1,trustImmediately:!1};if(params.trustNewDevices!==!1||params.isEmailExempt||params.isFirstEverDevice)return TRUST_NOW;return{requiresApproval:!0,deny:!1,mintToken:!0,trustImmediately:!1}}function deviceCookieName(cfg){return`${cfg?.cookiePrefix??""}${cfg?.cookieName??"device_token"}`}function readDeviceTokenFromCookieHeader(cookieHeader,cfg){if(!cookieHeader)return;let name2=deviceCookieName(cfg);for(let part of cookieHeader.split(";")){let eq51=part.indexOf("=");if(eq51<0)continue;if(part.slice(0,eq51).trim()===name2)return part.slice(eq51+1).trim()}return}function isApprovalMethod(method,approvalMethods){if(!method)return!1;for(let m of approvalMethods){if(m===method)return!0;if(m.endsWith(":*")&&method.startsWith(m.slice(0,-1)))return!0}return!1}var DEVICE_TOKEN_BYTES=32;var init_DeviceTrust=()=>{};import{createHash as createHash5}from"crypto";function hashRefreshToken(token){return createHash5("sha256").update(token).digest("hex")}function classifyRefreshToken(input){let grace=input.graceSeconds??DEFAULT_REUSE_GRACE_SECONDS;if(!input.currentHash)return{action:"adopt"};if(input.presentedHash===input.currentHash)return{action:"rotate"};if(input.previousHash&&input.presentedHash===input.previousHash){let rotatedAt=input.previousRotatedAt?Date.parse(input.previousRotatedAt):Number.NaN;if(Number.isFinite(rotatedAt)&&input.nowMs-rotatedAt<=grace*1000)return{action:"grace"};return{action:"revoke",reason:"refresh token was superseded and is being replayed outside the grace window"}}return{action:"revoke",reason:"refresh token does not match this session, current or previous"}}var DEFAULT_REUSE_GRACE_SECONDS=30;var init_reuseDetection=()=>{};async function recordIssuedRefreshToken(sessionId,refreshToken,options){if(!sessionId||!refreshToken)return;try{let result=await updateSession({sessionId,updates:{refreshTokenHash:hashRefreshToken(refreshToken),...options?.previousHash?{previousRefreshTokenHash:options.previousHash,previousRefreshTokenAt:new Date().toISOString()}:{}}});if(!result.success)logger.warn("[AUTH] Could not record the issued refresh token; reuse detection is off",{sessionId,error:result.error})}catch(error3){logger.warn("[AUTH] Could not record the issued refresh token; reuse detection is off",{sessionId,error:error3 instanceof Error?error3.message:String(error3)})}}var init_recordIssued=__esm(()=>{init_Update();init_Logger2();init_reuseDetection()});async function resolveUserIdByIdentifier(input){let value2=input.value.trim();if(value2==="")return null;for(let source of input.sources){if(!source.table||!source.column||!source.userColumn)continue;let found=(await input.lookup(source,value2)).filter((id)=>String(id??"").trim()!=="");if(found.length>1)return null;if(found.length===1)return String(found[0])}return null}function looksLikeAlternateIdentifier(value2){let trimmed=value2.trim();return trimmed!==""&&!trimmed.includes("@")}var init_identifier=()=>{};import{eq as eq51}from"drizzle-orm";async function lookupIdentifier(db,schemaTables,source,value2){let table=pickByName(schemaTables,source.table);if(!table)return[];let column=pickByName(table,source.column),userColumn=pickByName(table,source.userColumn);if(!column||!userColumn)return[];return(await db.select({userId:userColumn}).from(table).where(eq51(column,value2)).limit(2)).map((row)=>String(row.userId??""))}var init_identifierQuery=__esm(()=>{init_identifier()});var LoginBodySchema,LoginResponseSchema;var init_types12=__esm(()=>{init_esm3();LoginBodySchema=Type.Object({email:Type.String({minLength:1}),password:Type.String({minLength:1}),rememberMe:Type.Optional(Type.Boolean()),captchaId:Type.Optional(Type.String()),captchaAnswer:Type.Optional(Type.String()),deviceHint:Type.Optional(Type.String())}),LoginResponseSchema=Type.Object({success:Type.Boolean(),message:Type.Optional(Type.String()),data:Type.Optional(Type.Object({user:Type.Object({id:Type.String(),email:Type.String()}),accessToken:Type.String(),refreshToken:Type.String()}))})});import{eq as eq52}from"drizzle-orm";function createLoginRoute(config,loginConfig,signAccessToken,signRefreshToken,createSession,saveSessionToDb,cookieConfig,tokenResponseConfig,captchaService){let{db,logger:logger2}=config,log4=logger2.scoped(AUTH_LOGIN),route=loginConfig.route||"/auth/login",cookies={accessTokenName:cookieConfig?.accessTokenName||"access_token",refreshTokenName:cookieConfig?.refreshTokenName||"refresh_token",sessionTokenName:cookieConfig?.sessionTokenName||"session_token",accessTokenMaxAge:cookieConfig?.accessTokenMaxAge||900,refreshTokenMaxAge:cookieConfig?.refreshTokenMaxAge||604800,sessionTokenMaxAge:cookieConfig?.sessionTokenMaxAge||900,secure:cookieConfig?.secure??!0,httpOnly:cookieConfig?.httpOnly??!0,sameSite:cookieConfig?.sameSite||"strict",path:cookieConfig?.path||"/",domain:cookieConfig?.domain};if(!loginConfig.enabled)return collectRoutes();let identifierSources=loginConfig.identifierSources??[],rememberMeAllowed=loginConfig.rememberMe!==!1,routePostRoute=defineRoute({method:"POST",path:"/",body:LoginBodySchema,detail:{tags:["Authentication"],summary:"Login",description:"Authenticate user with email and password"},handler:async(ctx)=>{let resolved=resolveAuthTablesForRequest(ctx.request,config),usersTable=resolved.usersTable;if(!db||!usersTable)return ctx.set.status=500,{success:!1,message:"Database not configured"};let{email:rawEmail,password:password4,rememberMe:rememberMeRequested,captchaId,captchaAnswer,deviceHint}=ctx.body,rememberMe=rememberMeAllowed?rememberMeRequested:!1,email=rawEmail.toLowerCase(),user=(await db.select().from(usersTable).where(eq52(usersTable.email,email)).limit(1))[0];if(!user&&identifierSources.length>0&&looksLikeAlternateIdentifier(rawEmail)){let resolvedId=await resolveUserIdByIdentifier({sources:identifierSources,value:rawEmail,lookup:(source,value2)=>lookupIdentifier(db,resolved.schemaTables,source,value2)});if(resolvedId)user=(await db.select().from(usersTable).where(eq52(usersTable.id,resolvedId)).limit(1))[0]}let captchaEnabledForLogin=loginConfig.captcha?.enabled!==!1,captchaThreshold=loginConfig.captcha?.requireAfterFailures,currentFailedAttempts=user?.failedLoginAttempts||0,captchaRequired=Boolean(captchaService?.isEnabled())&&captchaEnabledForLogin&&(captchaThreshold===void 0||currentFailedAttempts>=captchaThreshold);if(captchaRequired&&captchaService){if(!captchaId||!captchaAnswer)return ctx.set.status=400,{success:!1,message:"Captcha is required",requiresCaptcha:!0};let captchaResult=await captchaService.validate(captchaId,captchaAnswer);if(!captchaResult.valid)return ctx.set.status=400,{success:!1,message:captchaResult.message||"Invalid captcha",attemptsRemaining:captchaResult.attemptsRemaining,requiresCaptcha:!0}}let reqUrl=new URL(ctx.request.url),auditIp=ctx.request.headers.get("x-client-ip")?.trim()||"unknown",auditUa=ctx.request.headers.get("user-agent")||"unknown";if(!user)return await verifyPassword(password4,await getTimingEqualizerHash()).catch(()=>!1),log4.warn("Login failed - user not found",{email}),await logger2.audit({entityName:"users",operation:"LOGIN_FAILED",summary:`Login failed: user not found (${email})`,ipAddress:auditIp,userAgent:auditUa,path:reqUrl.pathname,query:reqUrl.search}),ctx.set.status=401,{success:!1,message:"Invalid email or password",requiresCaptcha:captchaRequired};if(user.isActive===!1)return log4.warn("Login failed - account inactive",{email,userId:user.id}),ctx.set.status=403,{success:!1,message:"Account is not active"};if(user.isLocked){let lockedUntil=user.lockedUntil;if(lockedUntil&&new Date(lockedUntil)<new Date)await db.update(usersTable).set({isLocked:!1,lockedUntil:null,failedLoginAttempts:0}).where(eq52(usersTable.id,user.id)),log4.info("Account lock expired, auto-unlocked",{userId:user.id,email});else return log4.warn("Login failed - account locked",{email,userId:user.id}),await logger2.audit({entityName:"users",entityId:user.id,operation:"LOGIN_FAILED",userId:user.id,summary:`Login failed: account locked (${email})`,ipAddress:auditIp,userAgent:auditUa,path:reqUrl.pathname,query:reqUrl.search}),ctx.set.status=403,{success:!1,message:"Account is locked"}}let userCohortId=user.cohortId;if(userCohortId&&db){let cohortsTable=resolved.schemaTables?.userCohorts??resolved.schemaTables?.user_cohorts;if(cohortsTable){let cohortExpiresAt=(await db.select().from(cohortsTable).where(eq52(cohortsTable.id,userCohortId)).limit(1))[0]?.expiresAt;if(cohortExpiresAt&&new Date(cohortExpiresAt)<new Date)return log4.warn("Login failed - cohort expired",{email,userId:user.id,cohortId:userCohortId,expiresAt:cohortExpiresAt}),await logger2.audit({entityName:"users",entityId:user.id,operation:"LOGIN_FAILED",userId:user.id,summary:`Login failed: cohort expired (${email})`,ipAddress:auditIp,userAgent:auditUa,path:reqUrl.pathname,query:reqUrl.search}),ctx.set.status=403,{success:!1,message:"Your access has expired. Please contact your administrator."}}}if(!await verifyPassword(password4,user.password)){let failedAttempts=(user.failedLoginAttempts||0)+1;await db.update(usersTable).set({failedLoginAttempts:failedAttempts,isLocked:failedAttempts>=5,lockedUntil:failedAttempts>=5?new Date(Date.now()+1800000):null}).where(eq52(usersTable.id,user.id)),log4.warn("Login failed - invalid password",{email,failedAttempts}),await logger2.audit({entityName:"users",entityId:user.id,operation:"LOGIN_FAILED",userId:user.id,summary:`Login failed: invalid password (${email}, attempt ${failedAttempts})`,ipAddress:auditIp,userAgent:auditUa,path:reqUrl.pathname,query:reqUrl.search});let captchaWillBeRequired=Boolean(captchaService?.isEnabled())&&captchaEnabledForLogin&&(captchaThreshold===void 0||failedAttempts>=captchaThreshold);return ctx.set.status=401,{success:!1,message:"Invalid email or password",requiresCaptcha:captchaWillBeRequired}}await db.update(usersTable).set({failedLoginAttempts:0,lastLoginAt:new Date,loginCount:(user.loginCount||0)+1}).where(eq52(usersTable.id,user.id));let ipAddress=ctx.request.headers.get("x-client-ip")?.trim()||"127.0.0.1",isPrivateOrLoopback=(ip)=>!ip||ip==="127.0.0.1"||ip==="::1"||ip==="localhost"||ip.startsWith("10.")||ip.startsWith("192.168.")||ip.startsWith("172."),userAgent=ctx.request.headers.get("user-agent")||"Unknown Browser";log4.info("Parsed device info",{ipAddress,userAgent});let deviceInfo=parseUserAgentForLogin(userAgent,ipAddress,deviceHint,deviceContextFromRequest(ctx.request).clientHints);if(!isPrivateOrLoopback(ipAddress)){let abortCtrl=new AbortController,geoTimeout=setTimeout(()=>abortCtrl.abort(),2000);fetch(`https://ip-api.com/json/${ipAddress}?fields=country,city`,{signal:abortCtrl.signal}).then(async(res)=>{if(clearTimeout(geoTimeout),!res.ok)return;let geo=await res.json();if(geo.country)deviceInfo.locationCountry=geo.country;if(geo.city)deviceInfo.locationCity=geo.city}).catch(()=>{clearTimeout(geoTimeout)})}let userRoles=[],userClaims=[],userClaimScopes={};if(db)try{let{fetchUserRolesAndClaims:fetchUserRolesAndClaims2}=await Promise.resolve().then(() => (init_fetchUserRolesAndClaims(),exports_fetchUserRolesAndClaims)),rc=await fetchUserRolesAndClaims2(db,user.id,resolved);userRoles=rc.roles,userClaims=rc.claims,userClaimScopes=rc.claimScopes}catch(roleError){log4.error("Failed to fetch user roles/claims for JWT",{userId:user.id,error:roleError instanceof Error?roleError.message:String(roleError),stack:roleError instanceof Error?roleError.stack:void 0})}let requestOrigin=ctx.request.headers.get("origin")||ctx.request.headers.get("referer")?.replace(/\/[^/]*$/,"")||void 0,sessionParams={userId:user.id,deviceInfo,loginMethod:"password",rememberMe,requestOrigin},sessionId=await createSession(sessionParams),accessToken=signAccessToken(user.id,userRoles.length>0?userRoles:void 0,userClaims.length>0?userClaims:void 0,ctx.request.headers.get("x-tenant-schema")??void 0,userClaimScopes,sessionId),refreshToken=signRefreshToken(user.id,rememberMe,sessionId);await recordIssuedRefreshToken(sessionId,refreshToken);let requiresApproval=!1,pendingSessionId=sessionId,deviceTokenCookie;if(saveSessionToDb){let reqSchemaName=ctx.request.headers.get("x-tenant-schema")||void 0,dtCfg=config.authentication?.deviceTrust;if(dtCfg?.enabled)sessionParams.deviceToken=readDeviceTokenFromCookieHeader(ctx.request.headers.get("cookie"),dtCfg);let saveResult=await saveSessionToDb(sessionId,sessionParams,reqSchemaName);if(saveResult?.denied)return log4.warn("Login blocked by device-trust (unidentifiable device)",{userId:user.id}),ctx.set.status=403,{success:!1,message:"This device could not be recognized. Please contact support."};if(deviceTokenCookie=saveResult?.deviceTokenCookie,saveResult?.requiresApproval){if(requiresApproval=!0,saveResult.sessionId)pendingSessionId=saveResult.sessionId}}if(requiresApproval){ctx.set.status=202;let approvalHeaders={"Content-Type":"application/json"};if(deviceTokenCookie)approvalHeaders["Set-Cookie"]=deviceTokenCookie;return new Response(JSON.stringify({success:!1,requiresApproval:!0,sessionId:pendingSessionId,message:"Login requires approval. Please check your email to approve this device."}),{status:202,headers:approvalHeaders})}log4.info("Login successful",{userId:user.id,email,rememberMe}),await logger2.audit({entityName:"users",entityId:user.id,operation:"LOGIN",userId:user.id,summary:`${email} logged in successfully`,ipAddress,userAgent,path:reqUrl.pathname,query:reqUrl.search});let tokenConfig={accessToken:{setHeadersEnabled:tokenResponseConfig?.accessToken?.setHeadersEnabled??!0,returnJson:tokenResponseConfig?.accessToken?.returnJson??!0},refreshToken:{setHeadersEnabled:tokenResponseConfig?.refreshToken?.setHeadersEnabled??!0,returnJson:tokenResponseConfig?.refreshToken?.returnJson??!0},sessionToken:{setHeadersEnabled:tokenResponseConfig?.sessionToken?.setHeadersEnabled??!0,returnJson:tokenResponseConfig?.sessionToken?.returnJson??!0}},securePart=cookies.secure?"; Secure":"",domainPart=cookies.domain?`; Domain=${cookies.domain}`:"",cookieOptions=`; Path=${cookies.path}; HttpOnly; SameSite=${cookies.sameSite}${securePart}${domainPart}`,cookiesToSet=[];if(tokenConfig.accessToken.setHeadersEnabled)cookiesToSet.push(`${cookies.accessTokenName}=${accessToken}${cookieOptions}; Max-Age=${cookies.accessTokenMaxAge}`);if(tokenConfig.refreshToken.setHeadersEnabled)cookiesToSet.push(`${cookies.refreshTokenName}=${refreshToken}${cookieOptions}; Max-Age=${cookies.refreshTokenMaxAge}`);if(tokenConfig.sessionToken.setHeadersEnabled)cookiesToSet.push(`${cookies.sessionTokenName}=${sessionId}${cookieOptions}; Max-Age=${cookies.sessionTokenMaxAge}`);if(deviceTokenCookie)cookiesToSet.push(deviceTokenCookie);ctx.set.headers["x-session-id"]=sessionId;let responseData={user:{id:user.id,email:user.email}};if(tokenConfig.accessToken.returnJson)responseData.accessToken=accessToken;if(tokenConfig.refreshToken.returnJson)responseData.refreshToken=refreshToken;if(tokenConfig.sessionToken.returnJson)responseData.sessionId=sessionId;let jsonBody=JSON.stringify(withEnvelopeFlags({success:!0,data:responseData})),headers=new Headers;headers.set("Content-Type","application/json"),headers.set("x-session-id",sessionId);for(let cookie of cookiesToSet)headers.append("Set-Cookie",cookie);return new Response(jsonBody,{status:200,headers})}});return collectRoutes([{prefix:route,routes:[routePostRoute]}])}var dummyHashPromise=null,getTimingEqualizerHash=()=>{if(!dummyHashPromise)dummyHashPromise=hashPassword("nucleus-login-timing-equalizer");return dummyHashPromise};var init_login=__esm(()=>{init_routeCollector();init_response();init_DeviceTrust();init_scopes();init_recordIssued();init_utils6();init_identifier();init_identifierQuery();init_types12();init_deviceInfo();init_utils7();init_types12()});function clearAuthCookies(cookieConfig){let path7=cookieConfig?.path||"/",sameSite=cookieConfig?.sameSite||"Strict",securePart=cookieConfig?.secure!==!1?"; Secure":"",domainPart=cookieConfig?.domain?`; Domain=${cookieConfig.domain}`:"",accessName=cookieConfig?.accessTokenName||"access_token",refreshName=cookieConfig?.refreshTokenName||"refresh_token",sessionName=cookieConfig?.sessionTokenName||"session_token",clearOpts=`; Path=${path7}; HttpOnly; SameSite=${sameSite}${securePart}${domainPart}; Max-Age=0`;return[`${accessName}=${clearOpts}`,`${refreshName}=${clearOpts}`,`${sessionName}=${clearOpts}`,...IMPERSONATION_COOKIE_NAMES.map((name2)=>`${name2}=${clearOpts}`)]}var init_utils9=__esm(()=>{init_impersonationCookies()});var LogoutResponseSchema;var init_types13=__esm(()=>{init_esm3();LogoutResponseSchema=Type.Object({success:Type.Boolean(),message:Type.Optional(Type.String())})});function createLogoutRoute(config,logoutConfig,destroySession,revokeSessionInDb,cookieConfig){let{logger:logger2}=config,route=logoutConfig.route||"/auth/logout";if(!logoutConfig.enabled)return collectRoutes();let routePostRoute=defineRoute({method:"POST",path:"/",detail:{tags:["Authentication"],summary:"Logout",description:"Logout and invalidate session"},handler:async(ctx)=>{let sessionCookieName=cookieConfig?.sessionTokenName||"session_token",fromCookie=(ctx.request.headers.get("cookie")??"").split(";").map((one)=>one.trim()).find((one)=>one.startsWith(`${sessionCookieName}=`))?.slice(sessionCookieName.length+1),sessionId=ctx.request.headers.get("x-session-id")||fromCookie||null,userId=ctx.request.headers.get("x-user-id");if(sessionId){if(await destroySession(sessionId),revokeSessionInDb){let reqSchemaName=ctx.request.headers.get("x-tenant-schema")||void 0;await revokeSessionInDb(sessionId,"user_logout",reqSchemaName)}}let clearCookies=clearAuthCookies(cookieConfig),responseHeaders=new Headers;responseHeaders.set("Content-Type","application/json");for(let cookie of clearCookies)responseHeaders.append("Set-Cookie",cookie);logger2.info("[AUTH] Logout successful",{userId,sessionId});let reqUrl=new URL(ctx.request.url);logger2.audit({entityName:"users",entityId:userId||void 0,operation:"LOGOUT",userId:userId||void 0,summary:`User logged out${sessionId?` (session: ${sessionId.substring(0,8)}...)`:""}`,ipAddress:ctx.request.headers.get("x-forwarded-for")?.split(",")[0]?.trim()||ctx.request.headers.get("x-real-ip")?.trim()||"unknown",userAgent:ctx.request.headers.get("user-agent")||"unknown",path:reqUrl.pathname,query:reqUrl.search});let jsonBody=JSON.stringify(withEnvelopeFlags({success:!0,message:"Logged out successfully"}));return new Response(jsonBody,{status:200,headers:responseHeaders})}});return collectRoutes([{prefix:route,routes:[routePostRoute]}])}var init_logout=__esm(()=>{init_response();init_routeCollector();init_utils9();init_types13()});var MagicLinkRequestSchema,MagicLinkVerifySchema,MagicLinkResponseSchema,MagicLinkVerifyResponseSchema;var init_types14=__esm(()=>{init_esm3();MagicLinkRequestSchema=Type.Object({email:Type.String({format:"email"})}),MagicLinkVerifySchema=Type.Object({token:Type.String()}),MagicLinkResponseSchema=Type.Object({success:Type.Boolean(),message:Type.Optional(Type.String())}),MagicLinkVerifyResponseSchema=Type.Object({success:Type.Boolean(),message:Type.Optional(Type.String()),data:Type.Optional(Type.Object({user:Type.Object({id:Type.String(),email:Type.String()}),accessToken:Type.String(),refreshToken:Type.String()}))})});import{eq as eq53}from"drizzle-orm";function createMagicLinkRoute(config,magicLinkConfig,getEmailService,signAccessToken,signRefreshToken,createSession,storeMagicToken,getMagicToken,deleteMagicToken,appName,cookieConfig,saveSessionToDb){let{db,logger:logger2}=config,requestRoute=magicLinkConfig.route||"/auth/magic-link",verifyRoute=magicLinkConfig.verifyRoute||"/auth/magic-link/verify",expiresIn=magicLinkConfig.expiresIn||"15m",redirectUrl=magicLinkConfig.redirectUrl||"";if(!magicLinkConfig.enabled)return collectRoutes();let routePostRequestroute=defineRoute({method:"POST",path:"/",body:MagicLinkRequestSchema,detail:{tags:["Authentication"],summary:"Request Magic Link",description:"Send a magic link to the user's email for passwordless login"},handler:async(ctx)=>{let{usersTable}=resolveAuthTablesForRequest(ctx.request,config);if(!db||!usersTable)return ctx.set.status=500,{success:!1,message:"Database not configured"};let emailService=getEmailService?.();if(!emailService?.isAvailable())return logger2.error("[AUTH] Magic link requested but email service not available"),ctx.set.status=503,{success:!1,message:"Email service not available"};let{email:rawEmail}=ctx.body,email=rawEmail.toLowerCase(),user=(await db.select().from(usersTable).where(eq53(usersTable.email,email)).limit(1))[0];if(!user)return logger2.info("[AUTH] Magic link requested for non-existent email",{email}),{success:!0,message:"If an account exists, a magic link has been sent"};if(user.isLocked)return logger2.warn("[AUTH] Magic link requested for locked account",{email}),{success:!0,message:"If an account exists, a magic link has been sent"};let token=generateMagicToken(),tokenHash=hashToken(token,TOKEN_PURPOSE.magicLink),expiresAt=new Date(Date.now()+parseTimeToMs3(expiresIn)),reqSchemaName=ctx.request.headers.get("x-tenant-schema")||void 0;await storeMagicToken({userId:user.id,email,tokenHash,expiresAt},reqSchemaName);let magicLink=redirectUrl?buildEmailActionLink({request:ctx.request,configuredUrl:redirectUrl,path:extractConfiguredPath(redirectUrl,"/verify-magic-link"),query:{token},allowedOrigins:config.authentication?.trustedAppOrigins}):`${verifyRoute}?token=${token}`,result=await emailService.sendMagicLinkEmail(email,magicLink,appName);if(!result.success)return logger2.error("[AUTH] Failed to send magic link email",{email,error:result.error}),ctx.set.status=500,{success:!1,message:result.error?`The email could not be sent: ${result.error}`:"The email could not be sent"};return logger2.info("[AUTH] Magic link sent",{email,userId:user.id}),{success:!0,message:"If an account exists, a magic link has been sent"}}}),routeGetVerifyroute=defineRoute({method:"GET",path:"/",query:MagicLinkVerifySchema,detail:{tags:["Authentication"],summary:"Verify Magic Link",description:"Verify magic link token and login user"},handler:async(ctx)=>{let{usersTable}=resolveAuthTablesForRequest(ctx.request,config);if(!db||!usersTable)return ctx.set.status=500,{success:!1,message:"Database not configured"};let token=ctx.query.token;if(!token)return ctx.set.status=400,{success:!1,message:"Token is required"};let tokenHash=hashToken(token,TOKEN_PURPOSE.magicLink),reqSchemaName=ctx.request.headers.get("x-tenant-schema")||void 0,storedToken=await getMagicToken(tokenHash,reqSchemaName);if(!storedToken)return logger2.warn("[AUTH] Invalid magic link token"),ctx.set.status=400,{success:!1,message:"Invalid or expired token"};if(new Date>storedToken.expiresAt)return await deleteMagicToken(tokenHash,reqSchemaName),logger2.warn("[AUTH] Expired magic link token",{email:storedToken.email}),ctx.set.status=400,{success:!1,message:"Invalid or expired token"};let user=(await db.select().from(usersTable).where(eq53(usersTable.id,storedToken.userId)).limit(1))[0];if(!user)return await deleteMagicToken(tokenHash,reqSchemaName),ctx.set.status=404,{success:!1,message:"User not found"};if(user.isActive===!1)return await deleteMagicToken(tokenHash,reqSchemaName),logger2.warn("[AUTH] Magic link denied: account inactive",{email:storedToken.email}),ctx.set.status=400,{success:!1,message:"Invalid or expired token"};if(user.isLocked){let lockedUntil=user.lockedUntil;if(!(lockedUntil&&new Date(lockedUntil)<new Date))return await deleteMagicToken(tokenHash,reqSchemaName),logger2.warn("[AUTH] Magic link denied: account locked",{email:storedToken.email}),ctx.set.status=400,{success:!1,message:"Invalid or expired token"}}await deleteMagicToken(tokenHash,reqSchemaName),await db.update(usersTable).set({lastLoginAt:new Date,loginCount:(user.loginCount||0)+1,emailVerified:!0}).where(eq53(usersTable.id,user.id));let ipAddress=ctx.request.headers.get("x-forwarded-for")?.split(",")[0]?.trim()||ctx.request.headers.get("x-real-ip")?.trim()||"unknown",userAgent=ctx.request.headers.get("user-agent")||"",mlUserRoles=[],mlUserClaims=[],mlUserClaimScopes={};if(db)try{let resolved=resolveAuthTablesForRequest(ctx.request,config),rc=await fetchUserRolesAndClaims(db,user.id,resolved);mlUserRoles=rc.roles,mlUserClaims=rc.claims,mlUserClaimScopes=rc.claimScopes}catch{}let sessionParams={userId:user.id,deviceInfo:ensureDeviceInfo({ipAddress,userAgent},deviceContextFromRequest(ctx.request).clientHints),loginMethod:"magic_link",rememberMe:!0},sessionId=await createSession(sessionParams),accessToken=signAccessToken(user.id,mlUserRoles.length>0?mlUserRoles:void 0,mlUserClaims.length>0?mlUserClaims:void 0,ctx.request.headers.get("x-tenant-schema")??void 0,mlUserClaimScopes,sessionId),refreshToken=signRefreshToken(user.id,void 0,sessionId),requiresApproval=!1,approvalSessionId=sessionId,deviceTokenCookie;if(saveSessionToDb){let reqSchemaName2=ctx.request.headers.get("x-tenant-schema")||void 0,dtCfg=config.authentication?.deviceTrust;if(dtCfg?.enabled)sessionParams.deviceToken=readDeviceTokenFromCookieHeader(ctx.request.headers.get("cookie"),dtCfg);let saveResult=await saveSessionToDb(sessionId,sessionParams,reqSchemaName2);if(saveResult?.denied)return ctx.set.status=403,{success:!1,message:"This device could not be recognized. Please contact support."};if(deviceTokenCookie=saveResult?.deviceTokenCookie,saveResult?.requiresApproval){if(requiresApproval=!0,saveResult.sessionId)approvalSessionId=saveResult.sessionId}}if(requiresApproval){if(deviceTokenCookie)ctx.set.headers["Set-Cookie"]=deviceTokenCookie;return logger2.info("[AUTH] Magic link login requires device approval",{userId:user.id,email:user.email,approvalSessionId}),ctx.set.status=202,{success:!1,requiresApproval:!0,sessionId:approvalSessionId,message:"Login requires approval. Please check your email to approve this device."}}logger2.info("[AUTH] Magic link login successful",{userId:user.id,email:user.email});let cookies={accessTokenName:cookieConfig?.accessTokenName||"access_token",refreshTokenName:cookieConfig?.refreshTokenName||"refresh_token",sessionTokenName:cookieConfig?.sessionTokenName||"session_token",accessTokenMaxAge:cookieConfig?.accessTokenMaxAge||900,refreshTokenMaxAge:cookieConfig?.refreshTokenMaxAge||604800,sessionTokenMaxAge:cookieConfig?.sessionTokenMaxAge||2592000,secure:cookieConfig?.secure??!0,httpOnly:cookieConfig?.httpOnly??!0,sameSite:cookieConfig?.sameSite||"strict",path:cookieConfig?.path||"/",domain:cookieConfig?.domain},securePart=cookies.secure?"; Secure":"",domainPart=cookies.domain?`; Domain=${cookies.domain}`:"",cookieOptions=`; Path=${cookies.path}; HttpOnly; SameSite=${cookies.sameSite}${securePart}${domainPart}`,responseHeaders=new Headers;if(responseHeaders.set("Content-Type","application/json"),responseHeaders.set("x-session-id",sessionId),responseHeaders.append("Set-Cookie",`${cookies.accessTokenName}=${accessToken}${cookieOptions}; Max-Age=${cookies.accessTokenMaxAge}`),responseHeaders.append("Set-Cookie",`${cookies.refreshTokenName}=${refreshToken}${cookieOptions}; Max-Age=${cookies.refreshTokenMaxAge}`),responseHeaders.append("Set-Cookie",`${cookies.sessionTokenName}=${sessionId}${cookieOptions}; Max-Age=${cookies.sessionTokenMaxAge}`),deviceTokenCookie)responseHeaders.append("Set-Cookie",deviceTokenCookie);let jsonBody=JSON.stringify(withEnvelopeFlags({success:!0,data:{user:{id:user.id,email:user.email},accessToken,refreshToken,sessionId}}));return new Response(jsonBody,{status:200,headers:responseHeaders})}});return collectRoutes([{prefix:requestRoute,routes:[routePostRequestroute]},{prefix:verifyRoute,routes:[routeGetVerifyroute]}])}var init_magicLink=__esm(()=>{init_DeviceTrust();init_response();init_routeCollector();init_fetchUserRolesAndClaims();init_deviceInfo();init_types14();init_utils8();init_types14()});import{eq as eq54}from"drizzle-orm";function createMeRoute(config,meConfig,_schemaTables,_schemaRelations,_databaseUrl){let{db,logger:logger2}=config,route=meConfig.route||"/auth/me";if(!meConfig.enabled)return collectRoutes();let routeGetRoute=defineRoute({method:"GET",path:"/",detail:{tags:["Authentication"],summary:"Get current user",description:"Get the currently authenticated user with profile, addresses, phones and files"},handler:async(ctx)=>{let resolved=resolveAuthTablesForRequest(ctx.request,config),usersTable=resolved.usersTable,reqSchemaTables=resolved.schemaTables;if(!db||!usersTable)return ctx.set.status=500,{success:!1,message:"Database not configured"};let userId=ctx.request.headers.get("x-user-id");if(!userId)return ctx.set.status=401,{success:!1,message:"Unauthorized"};let user=(await db.select().from(usersTable).where(eq54(usersTable.id,userId)).limit(1))[0];if(!user)return ctx.set.status=404,{success:!1,message:"User not found"};let{password:rawPassword,...restUser}=user,hasPassword=typeof rawPassword==="string"&&rawPassword.length>0,safeUser={...stripUnclaimedSensitive(redactSensitiveOutput(restUser)),hasPassword},profile=null,addresses=[],phones=[],files=[],roles=[],claims=[];if(meConfig.includeProfile&&reqSchemaTables){let profilesTable=reqSchemaTables.profiles;if(profilesTable&&db)profile=(await db.select().from(profilesTable).where(eq54(profilesTable.userId,userId)).limit(1))[0]||null}if(meConfig.includeAddresses&&reqSchemaTables){let addressesTable=reqSchemaTables.addresses;if(addressesTable&&db)addresses=await db.select().from(addressesTable).where(eq54(addressesTable.ownerId,userId))}if(meConfig.includePhones&&reqSchemaTables){let phonesTable=reqSchemaTables.phones;if(phonesTable&&db)phones=await db.select().from(phonesTable).where(eq54(phonesTable.ownerId,userId))}if(meConfig.includeFiles&&reqSchemaTables){let filesTable=reqSchemaTables.files;if(filesTable&&db)files=await db.select().from(filesTable).where(eq54(filesTable.uploadedBy,userId))}if(meConfig.includeRoles&&reqSchemaTables){let{userRoles:userRolesTable,roles:rolesTable}=reqSchemaTables;if(userRolesTable&&rolesTable&&db){let roleIds=(await db.select().from(userRolesTable).where(eq54(userRolesTable.userId,userId))).map((ur)=>ur.roleId);if(roleIds.length>0){let{inArray:inArray18}=await import("drizzle-orm");roles=await db.select().from(rolesTable).where(inArray18(rolesTable.id,roleIds))}}}if(meConfig.includeClaims&&reqSchemaTables){let{userRoles:userRolesTable,roleClaims:roleClaimsTable,claims:claimsTable}=reqSchemaTables;if(userRolesTable&&roleClaimsTable&&claimsTable&&db){let urCols=userRolesTable,rcCols=roleClaimsTable,cCols=claimsTable,roleIds=(await db.select({roleId:urCols.roleId}).from(userRolesTable).where(eq54(urCols.userId,userId))).map((r)=>r.roleId);if(roleIds.length>0){let{inArray:inArray18}=await import("drizzle-orm"),claimRows=await db.select({action:cCols.action,scope:rcCols.scope}).from(roleClaimsTable).innerJoin(claimsTable,eq54(rcCols.claimId,cCols.id)).where(inArray18(rcCols.roleId,roleIds)),seen=new Set;for(let row of claimRows){let r=row,key2=`${r.action}|${r.scope??""}`;if(!seen.has(key2))seen.add(key2),claims.push({action:r.action,scope:r.scope??null})}}}}logger2.debug("[AUTH] Me endpoint accessed",{userId});let responseData={success:!0,data:{user:safeUser,profile:stripUnclaimedSensitive(profile),addresses:stripUnclaimedSensitive(addresses),phones:stripUnclaimedSensitive(phones),files:stripUnclaimedSensitive(files),roles:stripUnclaimedSensitive(roles),claims}};return JSON.parse(JSON.stringify(responseData,(_key,value2)=>typeof value2==="bigint"?Number(value2):value2))}});return collectRoutes([{prefix:route,routes:[routeGetRoute]}])}var init_me=__esm(()=>{init_routeCollector();init_utils5();init_sensitiveColumns()});var exports_accountState={};__export(exports_accountState,{evaluateAccountState:()=>evaluateAccountState});function evaluateAccountState(user,now=new Date){if(user.isActive===!1)return{allowed:!1,reason:"inactive",message:"Account is not active"};if(user.isLocked===!0){let raw=user.lockedUntil,lockedUntil=raw instanceof Date?raw:raw?new Date(raw):null;if(!lockedUntil||Number.isNaN(lockedUntil.getTime())||lockedUntil>now)return{allowed:!1,reason:"locked",message:"Account is locked"}}return{allowed:!0}}import{eq as eq55}from"drizzle-orm";async function applyProfileSync(args){let{usersTable,profilesTable,userId,rawProfile,config,isNewUser,logger:logger2}=args,db=args.db;try{if(config.enabled===!1)return;if(!isNewUser&&config.syncOnEveryLogin===!1)return;if(!config.mappings||config.mappings.length===0)return;let userIdCol=usersTable.id,currentUser=(await db.select().from(usersTable).where(eq55(userIdCol,userId)).limit(1))[0]??{},currentProfile={},profileExists=!1;if(profilesTable){let profileUserIdCol=profilesTable.userId,profileRows=await db.select().from(profilesTable).where(eq55(profileUserIdCol,userId)).limit(1);if(profileRows[0])currentProfile=profileRows[0],profileExists=!0}let{users,profiles,errors:errors2}=resolveProfileSyncUpdates({mappings:config.mappings,rawProfile,currentUser,currentProfile});for(let message of errors2)logger2.warn(`[OAUTH] profileSync: ${message}`);if(Object.keys(users).length>0)await db.update(usersTable).set({...users,updatedAt:new Date}).where(eq55(userIdCol,userId)),logger2.info("[OAUTH] profileSync updated users",{userId,columns:Object.keys(users)});if(profilesTable&&Object.keys(profiles).length>0){if(profileExists){let profileUserIdCol=profilesTable.userId;await db.update(profilesTable).set({...profiles,updatedAt:new Date}).where(eq55(profileUserIdCol,userId))}else if(config.createProfileIfMissing!==!1){let now=new Date;await db.insert(profilesTable).values({userId,firstName:rawProfile.givenName??"",lastName:rawProfile.surname??"",...profiles,createdAt:now,updatedAt:now})}else return;logger2.info("[OAUTH] profileSync updated profiles",{userId,columns:Object.keys(profiles),created:!profileExists})}}catch(err){logger2.warn("[OAUTH] profileSync failed \u2014 login continues",{userId,error:err instanceof Error?err.message:String(err)})}}var init_applyProfileSync=()=>{};import{and as and24,eq as eq56}from"drizzle-orm";function buildOAuthStateCookie(value2,opts){let securePart=opts.secure?"; Secure":"",domainPart=opts.domain?`; Domain=${opts.domain}`:"";return`${OAUTH_STATE_COOKIE}=${value2}; Path=/; HttpOnly; SameSite=Lax; Max-Age=${opts.maxAge}${securePart}${domainPart}`}function createOAuthRoutes(config,oauthService,signAccessToken,signRefreshToken,createSession,saveSessionToDb,cookieConfig,tokenResponseConfig,getEmailService,storeMagicToken,appName){let{db,logger:logger2}=config,basePath=oauthService.basePath;for(let provider of oauthService.getEnabledProviders()){let p=oauthService.config.providers[provider];logger2.info("[OAUTH] Provider loaded",{provider,clientIdSuffix:p?.clientId?p.clientId.slice(-4):"none",tenantId:p?.tenantId??"common",redirectUri:p?.redirectUri,scopes:p?.scopes})}let cookies={accessTokenName:cookieConfig?.accessTokenName||"access_token",refreshTokenName:cookieConfig?.refreshTokenName||"refresh_token",sessionTokenName:cookieConfig?.sessionTokenName||"session_token",accessTokenMaxAge:cookieConfig?.accessTokenMaxAge||900,refreshTokenMaxAge:cookieConfig?.refreshTokenMaxAge||604800,sessionTokenMaxAge:cookieConfig?.sessionTokenMaxAge||900,secure:cookieConfig?.secure??!0,httpOnly:cookieConfig?.httpOnly??!0,sameSite:cookieConfig?.sameSite||"lax",path:cookieConfig?.path||"/",domain:cookieConfig?.domain},tokenConfig={accessToken:{setHeadersEnabled:tokenResponseConfig?.accessToken?.setHeadersEnabled??!0,returnJson:tokenResponseConfig?.accessToken?.returnJson??!0},refreshToken:{setHeadersEnabled:tokenResponseConfig?.refreshToken?.setHeadersEnabled??!0,returnJson:tokenResponseConfig?.refreshToken?.returnJson??!0},sessionToken:{setHeadersEnabled:tokenResponseConfig?.sessionToken?.setHeadersEnabled??!0,returnJson:tokenResponseConfig?.sessionToken?.returnJson??!0}},routeGetProvider=defineRoute({method:"GET",path:"/:provider",detail:{tags:["Authentication"],summary:"OAuth Redirect",description:"Redirects to the OAuth provider authorization page"},handler:async(ctx)=>{let provider=ctx.params.provider;if(!oauthService.isProviderEnabled(provider))return ctx.set.status=404,{success:!1,message:`OAuth provider "${provider}" is not enabled`};let redirectUrl=ctx.query.redirect_url,authUrl=await oauthService.buildAuthorizationUrl(provider,void 0,redirectUrl);logger2.debug("[OAUTH] Redirecting to provider",{provider,authUrl:authUrl.replace(/(client_id=)[^&]+/,"$1***")});let issuedState=new URL(authUrl).searchParams.get("state");if(issuedState)ctx.set.headers["Set-Cookie"]=buildOAuthStateCookie(issuedState,{maxAge:600,secure:cookies.secure,domain:cookies.domain});return ctx.set.status=302,ctx.set.headers.Location=authUrl,null}}),routeGetProviderCallback=defineRoute({method:"GET",path:"/:provider/callback",detail:{tags:["Authentication"],summary:"OAuth Callback",description:"Handles the OAuth provider callback, creates session and sets cookies"},handler:async(ctx)=>{let provider=ctx.params.provider,query=ctx.query,{code,state,error:error3,error_description}=query,portalOrigin=resolveAppOrigin(ctx.request,oauthService.successRedirectUrl,config.authentication?.trustedAppOrigins),errorRedirect=absolutizeRedirect(oauthService.errorRedirectUrl,portalOrigin);if(error3)return logger2.warn("[OAUTH] Provider returned error",{provider,error:error3,error_description}),ctx.set.status=302,ctx.set.headers.Location=`${errorRedirect}?error=${encodeURIComponent(error_description||error3)}`,null;if(!code||!state)return ctx.set.status=302,ctx.set.headers.Location=`${errorRedirect}?error=missing_code_or_state`,null;let statePayload=await oauthService.consumeState(state);if(!statePayload)return logger2.warn("[OAUTH] Invalid or expired state",{provider,state}),ctx.set.status=302,ctx.set.headers.Location=`${errorRedirect}?error=invalid_state`,null;let cookieState=readCookie(ctx.request,OAUTH_STATE_COOKIE),clearStateCookie=buildOAuthStateCookie("",{maxAge:0,secure:cookies.secure,domain:cookies.domain});if(!cookieState||cookieState!==state)return logger2.warn("[OAUTH] State cookie missing or mismatched (possible CSRF)",{provider}),ctx.set.status=302,ctx.set.headers["Set-Cookie"]=clearStateCookie,ctx.set.headers.Location=`${errorRedirect}?error=invalid_state`,null;if(statePayload.provider!==provider)return logger2.warn("[OAUTH] State provider mismatch",{expected:statePayload.provider,got:provider}),ctx.set.status=302,ctx.set.headers.Location=`${errorRedirect}?error=provider_mismatch`,null;let resolved=resolveAuthTablesForRequest(ctx.request,config),usersTable=resolved.usersTable,oauthTable=resolved.oauthAccountsTable??config.oauthAccountsTable;if(!db||!usersTable)return ctx.set.status=302,ctx.set.headers.Location=`${errorRedirect}?error=server_error`,null;logger2.debug("[OAUTH] Exchanging code",{provider,codeLength:code.length,statePresent:!!state});let callbackResult;try{callbackResult=await oauthService.exchangeCode(provider,code)}catch(err){let e=err;return logger2.error("[OAUTH] Code exchange failed",err,{provider,errorMessage:e?.message,errorName:e?.name,errorCauseCode:e?.cause?.code,errorCauseMessage:e?.cause?.message}),ctx.set.status=302,ctx.set.headers.Location=`${errorRedirect}?error=token_exchange_failed`,null}let{profile,tokens}=callbackResult,userId=null;if(statePayload.linkUserId&&oauthService.allowAccountLinking){if(userId=statePayload.linkUserId,oauthTable){let existing=await db.select().from(oauthTable).where(and24(eq56(oauthTable.provider,provider),eq56(oauthTable.providerAccountId,profile.providerAccountId))).limit(1);if(existing.length>0){let existingRecord=existing[0];if(existingRecord.userId!==userId)return ctx.set.status=302,ctx.set.headers.Location=`${errorRedirect}?error=account_already_linked_to_another_user`,null;await db.update(oauthTable).set({accessToken:tokens.accessToken,refreshToken:tokens.refreshToken??null,tokenExpiresAt:tokens.expiresAt??null,scope:tokens.scope??null,rawProfile:profile.rawProfile,lastUsedAt:new Date}).where(eq56(oauthTable.id,existingRecord.id))}else await db.insert(oauthTable).values({userId,provider,providerAccountId:profile.providerAccountId,providerEmail:profile.email??null,providerName:profile.name??null,providerAvatarUrl:profile.avatarUrl??null,accessToken:tokens.accessToken,refreshToken:tokens.refreshToken??null,tokenExpiresAt:tokens.expiresAt??null,scope:tokens.scope??null,rawProfile:profile.rawProfile,isPrimary:!1,lastUsedAt:new Date})}let linkSyncConfig=oauthService.profileSync;if(linkSyncConfig){let linkSyncTables=resolveAuthTablesForRequest(ctx.request,config);await applyProfileSync({db,usersTable,profilesTable:linkSyncTables.schemaTables?.profiles,userId,rawProfile:profile.rawProfile,config:linkSyncConfig,isNewUser:!1,logger:logger2})}logger2.info("[OAUTH] Account linked successfully",{userId,provider});let successUrl2=absolutizeRedirect(resolveSafeOAuthRedirect(statePayload.redirectUrl,oauthService.successRedirectUrl,config.authentication?.trustedAppOrigins),portalOrigin);return ctx.set.status=302,ctx.set.headers.Location=`${successUrl2}?linked=true&provider=${provider}`,null}if(oauthTable){let existingOAuth=await db.select().from(oauthTable).where(and24(eq56(oauthTable.provider,provider),eq56(oauthTable.providerAccountId,profile.providerAccountId))).limit(1);if(existingOAuth.length>0){let oauthRecord=existingOAuth[0];userId=oauthRecord.userId,await db.update(oauthTable).set({accessToken:tokens.accessToken,refreshToken:tokens.refreshToken??null,tokenExpiresAt:tokens.expiresAt??null,scope:tokens.scope??null,rawProfile:profile.rawProfile,lastUsedAt:new Date}).where(eq56(oauthTable.id,oauthRecord.id))}}if(!userId&&profile.email){let normalizedOAuthEmail=profile.email.toLowerCase(),existingUsers=await db.select().from(usersTable).where(eq56(usersTable.email,normalizedOAuthEmail)).limit(1);if(existingUsers.length>0&&profile.emailVerified!==!0)return logger2.warn("[OAUTH] Refusing account merge: provider email not verified",{provider,email:profile.email}),ctx.set.status=302,ctx.set.headers.Location=`${errorRedirect}?error=email_not_verified`,null;if(existingUsers.length>0){if(userId=existingUsers[0].id,oauthTable)await db.insert(oauthTable).values({userId,provider,providerAccountId:profile.providerAccountId,providerEmail:profile.email??null,providerName:profile.name??null,providerAvatarUrl:profile.avatarUrl??null,accessToken:tokens.accessToken,refreshToken:tokens.refreshToken??null,tokenExpiresAt:tokens.expiresAt??null,scope:tokens.scope??null,rawProfile:profile.rawProfile,isPrimary:!1,lastUsedAt:new Date}).onConflictDoNothing();logger2.info("[OAUTH] Merged OAuth account with existing user by email",{userId,provider,email:profile.email})}}let createdNewUser=!1;if(!userId){if(!oauthService.autoCreateUser)return ctx.set.status=302,ctx.set.headers.Location=`${errorRedirect}?error=user_not_found`,null;let userEmail=(profile.email??`${provider}_${profile.providerAccountId}@oauth.local`).toLowerCase(),providerEmailVerified=profile.emailVerified===!0;if(userId=(await db.insert(usersTable).values({email:userEmail,password:null,emailVerified:providerEmailVerified,verifiedAt:providerEmailVerified?new Date:null,isActive:!0}).returning())[0].id,oauthTable)await db.insert(oauthTable).values({userId,provider,providerAccountId:profile.providerAccountId,providerEmail:profile.email??null,providerName:profile.name??null,providerAvatarUrl:profile.avatarUrl??null,accessToken:tokens.accessToken,refreshToken:tokens.refreshToken??null,tokenExpiresAt:tokens.expiresAt??null,scope:tokens.scope??null,rawProfile:profile.rawProfile,isPrimary:!0,lastUsedAt:new Date});if(createdNewUser=!0,logger2.info("[OAUTH] Created new user via OAuth",{userId,provider,email:profile.email}),oauthService.sendInviteOnCreate&&profile.email&&getEmailService?.()?.isAvailable()&&storeMagicToken)try{let rawToken=generateMagicToken(),tokenHash=hashToken(rawToken,TOKEN_PURPOSE.invite),expiresAt=new Date(Date.now()+parseTimeToMs3("7d")),oauthSchemaName=ctx.request.headers.get("x-tenant-schema")||void 0;await storeMagicToken({userId,email:profile.email,tokenHash,expiresAt},oauthSchemaName);let inviteLink=buildEmailActionLink({request:ctx.request,configuredUrl:oauthService.successRedirectUrl,path:"/set-password",query:{token:rawToken},allowedOrigins:config.authentication?.trustedAppOrigins});await getEmailService?.()?.sendEmail({to:profile.email,subject:`Welcome to ${appName??"the platform"} \u2014 Set your password`,html:`
|
|
448
448
|
<p>Hi${profile.name?` ${profile.name}`:""},</p>
|
|
449
449
|
<p>Your account has been created via ${provider} login. You can optionally set a password to also sign in with your email and password.</p>
|
|
450
450
|
<p><a href="${inviteLink}">Set your password</a></p>
|
|
451
451
|
<p>This link expires in 7 days. If you don't want to set a password, you can always sign in with ${provider}.</p>
|
|
452
|
-
`}),logger2.info("[OAUTH] Invite email sent to new OAuth user",{userId,email:profile.email,provider})}catch(emailErr){logger2.warn("[OAUTH] Failed to send invite email",{userId,error:emailErr})}}{let stateUser=(await db.select().from(usersTable).where(eq56(usersTable.id,userId)).limit(1))[0],state2=stateUser?evaluateAccountState(stateUser):{allowed:!1,reason:"inactive",message:"Account is not active"};if(!state2.allowed)return logger2.warn(`[OAUTH] Login blocked - account ${state2.reason}`,{userId,provider}),ctx.set.status=302,ctx.set.headers.Location=`${errorRedirect}?error=account_${state2.reason}`,null}let defaultRoleNames=resolveDefaultRoleNames(config.authentication);if(defaultRoleNames.length>0){let resolvedForRole=resolveAuthTablesForRequest(ctx.request,config);await assignDefaultRoles({db,userId,roleNames:defaultRoleNames,rolesTable:resolvedForRole.rolesTable??config.rolesTable??null,userRolesTable:resolvedForRole.userRolesTable??config.userRolesTable??null,logger:logger2})}let syncConfig=oauthService.profileSync;if(syncConfig){let syncTables=resolveAuthTablesForRequest(ctx.request,config);await applyProfileSync({db,usersTable,profilesTable:syncTables.schemaTables?.profiles,userId,rawProfile:profile.rawProfile,config:syncConfig,isNewUser:createdNewUser,logger:logger2})}await db.update(usersTable).set({lastLoginAt:new Date}).where(eq56(usersTable.id,userId));let ipAddress=ctx.request.headers.get("cf-connecting-ip")?.trim()||ctx.request.headers.get("x-forwarded-for")?.split(",")[0]?.trim()||ctx.request.headers.get("x-real-ip")?.trim()||"127.0.0.1",userAgent=ctx.request.headers.get("user-agent")||"Unknown Browser",deviceInfo=parseUserAgentForLogin(userAgent,ipAddress,void 0,deviceContextFromRequest(ctx.request).clientHints),oauthUserRoles=[],oauthUserClaims=[],oauthUserClaimScopes={};if(config.db)try{let resolved2=resolveAuthTablesForRequest(ctx.request,config),rc=await fetchUserRolesAndClaims(config.db,userId,resolved2);oauthUserRoles=rc.roles,oauthUserClaims=rc.claims,oauthUserClaimScopes=rc.claimScopes}catch{}let requestOrigin=ctx.request.headers.get("origin")||ctx.request.headers.get("referer")?.replace(/\/[^/]*$/,"")||void 0,sessionParams={userId,deviceInfo,loginMethod:`oauth:${provider}`,rememberMe:!0,requestOrigin},sessionId=await createSession(sessionParams),accessToken=signAccessToken(userId,oauthUserRoles.length>0?oauthUserRoles:void 0,oauthUserClaims.length>0?oauthUserClaims:void 0,ctx.request.headers.get("x-tenant-schema")??void 0,oauthUserClaimScopes,sessionId),refreshToken=signRefreshToken(userId,void 0,sessionId),requiresApproval=!1,approvalSessionId=sessionId,deviceTokenCookie;if(saveSessionToDb){let oauthSchemaName=ctx.request.headers.get("x-tenant-schema")||void 0,dtCfg=config.authentication?.deviceTrust;if(dtCfg?.enabled)sessionParams.deviceToken=readDeviceTokenFromCookieHeader(ctx.request.headers.get("cookie"),dtCfg);let saveResult=await saveSessionToDb(sessionId,sessionParams,oauthSchemaName);if(saveResult?.denied)return logger2.warn("[OAUTH] Login blocked by device-trust",{userId,provider}),ctx.set.status=302,ctx.set.headers.Location=`${errorRedirect}?error=device_not_recognized`,null;if(deviceTokenCookie=saveResult?.deviceTokenCookie,saveResult?.requiresApproval){if(requiresApproval=!0,saveResult.sessionId)approvalSessionId=saveResult.sessionId}}if(requiresApproval){if(deviceTokenCookie)ctx.set.headers["Set-Cookie"]=deviceTokenCookie;logger2.info("[OAUTH] Login requires device approval",{userId,provider,approvalSessionId});let pendingUrl=absolutizeRedirect(resolveSafeOAuthRedirect(statePayload.redirectUrl,oauthService.successRedirectUrl,config.authentication?.trustedAppOrigins),portalOrigin);return ctx.set.status=302,ctx.set.headers.Location=`${pendingUrl}?requires_approval=true&provider=${provider}&session_id=${approvalSessionId}`,null}await logger2.audit({entityName:"users",entityId:userId,operation:"LOGIN",userId,summary:`${profile.email??profile.providerAccountId} logged in via OAuth (${provider})`,ipAddress,userAgent,path:`${basePath}/${provider}/callback`,query:""});let securePart=cookies.secure?"; Secure":"",domainPart=cookies.domain?`; Domain=${cookies.domain}`:"",cookieOptions=`; Path=${cookies.path}; HttpOnly; SameSite=${cookies.sameSite}${securePart}${domainPart}`,cookiesToSet=[];if(tokenConfig.accessToken.setHeadersEnabled)cookiesToSet.push(`${cookies.accessTokenName}=${accessToken}${cookieOptions}; Max-Age=${cookies.accessTokenMaxAge}`);if(tokenConfig.refreshToken.setHeadersEnabled)cookiesToSet.push(`${cookies.refreshTokenName}=${refreshToken}${cookieOptions}; Max-Age=${cookies.refreshTokenMaxAge}`);if(tokenConfig.sessionToken.setHeadersEnabled)cookiesToSet.push(`${cookies.sessionTokenName}=${sessionId}${cookieOptions}; Max-Age=${cookies.sessionTokenMaxAge}`);if(deviceTokenCookie)cookiesToSet.push(deviceTokenCookie);let successUrl=absolutizeRedirect(resolveSafeOAuthRedirect(statePayload.redirectUrl,oauthService.successRedirectUrl,config.authentication?.trustedAppOrigins),portalOrigin),headers=new Headers;headers.set("Location",successUrl);for(let cookie of cookiesToSet)headers.append("Set-Cookie",cookie);return new Response(null,{status:302,headers})}}),routeGetProviders=defineRoute({method:"GET",path:"/providers",detail:{tags:["Authentication"],summary:"List OAuth Providers",description:"Returns the list of enabled OAuth providers"},handler:()=>{return{success:!0,data:{providers:oauthService.getEnabledProviders()}}}}),routeGetLinkProvider=defineRoute({method:"GET",path:"/link/:provider",detail:{tags:["Authentication"],summary:"Link OAuth Account",description:"Redirects to provider to link an OAuth account to the current user"},handler:async(ctx)=>{let provider=ctx.params.provider;if(!oauthService.isProviderEnabled(provider))return ctx.set.status=404,{success:!1,message:`OAuth provider "${provider}" is not enabled`};if(!oauthService.allowAccountLinking)return ctx.set.status=403,{success:!1,message:"Account linking is disabled"};let userId=ctx.request.headers.get("x-user-id");if(!userId)return ctx.set.status=401,{success:!1,message:"Authentication required to link accounts"};let redirectUrl=ctx.query.redirect_url,authUrl=await oauthService.buildAuthorizationUrl(provider,userId,redirectUrl),issuedState=new URL(authUrl).searchParams.get("state");if(issuedState)ctx.set.headers["Set-Cookie"]=buildOAuthStateCookie(issuedState,{maxAge:600,secure:cookies.secure,domain:cookies.domain});return ctx.set.status=302,ctx.set.headers.Location=authUrl,null}}),routeDeleteUnlinkProvider=defineRoute({method:"DELETE",path:"/unlink/:provider",detail:{tags:["Authentication"],summary:"Unlink OAuth Account",description:"Removes a linked OAuth account from the current user"},handler:async(ctx)=>{let provider=ctx.params.provider,userId=ctx.request.headers.get("x-user-id");if(!userId)return ctx.set.status=401,{success:!1,message:"Authentication required"};if(!db)return ctx.set.status=500,{success:!1,message:"Database not configured"};let oauthTable=config.oauthAccountsTable;if(!oauthTable)return ctx.set.status=500,{success:!1,message:"OAuth accounts table not configured"};if((await db.select().from(oauthTable).where(and24(eq56(oauthTable.userId,userId),eq56(oauthTable.provider,provider))).limit(1)).length===0)return ctx.set.status=404,{success:!1,message:`No linked ${provider} account found`};let{usersTable:resolvedUsersTable}=resolveAuthTablesForRequest(ctx.request,config);if(!resolvedUsersTable)return ctx.set.status=500,{success:!1,message:"Users table not configured"};let hasPassword=!!(await db.select().from(resolvedUsersTable).where(eq56(resolvedUsersTable.id,userId)).limit(1))[0]?.password,allOAuthAccounts=await db.select().from(oauthTable).where(eq56(oauthTable.userId,userId));if(!hasPassword&&allOAuthAccounts.length<=1)return ctx.set.status=400,{success:!1,message:"Cannot unlink the only login method. Set a password first."};return await db.delete(oauthTable).where(and24(eq56(oauthTable.userId,userId),eq56(oauthTable.provider,provider))),logger2.info("[OAUTH] Account unlinked",{userId,provider}),{success:!0,message:`${provider} account unlinked successfully`}}}),routeGetAccounts=defineRoute({method:"GET",path:"/accounts",detail:{tags:["Authentication"],summary:"List Linked OAuth Accounts",description:"Returns all OAuth accounts linked to the current user"},handler:async(ctx)=>{let userId=ctx.request.headers.get("x-user-id");if(!userId)return ctx.set.status=401,{success:!1,message:"Authentication required"};if(!db)return ctx.set.status=500,{success:!1,message:"Database not configured"};let oauthTable=config.oauthAccountsTable;if(!oauthTable)return{success:!0,data:{accounts:[]}};return{success:!0,data:{accounts:(await db.select().from(oauthTable).where(eq56(oauthTable.userId,userId))).map((a)=>({id:a.id,provider:a.provider,providerEmail:a.providerEmail,providerName:a.providerName,providerAvatarUrl:a.providerAvatarUrl,isPrimary:a.isPrimary,lastUsedAt:a.lastUsedAt,createdAt:a.createdAt}))}}}});return collectRoutes([{prefix:basePath,routes:[routeGetProvider,routeGetProviderCallback,routeGetProviders,routeGetLinkProvider,routeDeleteUnlinkProvider,routeGetAccounts]}])}var OAUTH_STATE_COOKIE="oauth_state",readCookie=(request,name2)=>{let raw=request.headers.get("cookie");if(!raw)return;for(let part of raw.split(";")){let trimmed=part.trim(),eq57=trimmed.indexOf("=");if(eq57>0&&trimmed.slice(0,eq57)===name2)return trimmed.slice(eq57+1)}return},resolveSafeOAuthRedirect=(candidate,fallback,allowedOrigins)=>{if(!candidate)return fallback;let normalized=candidate.replace(/[\t\n\r]/g,"");if(!normalized)return fallback;if(normalized.startsWith("/")&&normalized[1]!=="/"&&normalized[1]!=="\\")return normalized;try{let candHost=new URL(normalized).host.toLowerCase(),trusted=[];try{trusted.push(new URL(fallback).host.toLowerCase())}catch{}for(let o of allowedOrigins??[])try{trusted.push(new URL(o.includes("://")?o:`https://${o}`).host.toLowerCase())}catch{}for(let t of trusted)if(t.startsWith("*.")){let base=t.slice(2);if(candHost===base||candHost.endsWith(`.${base}`))return normalized}else if(candHost===t)return normalized}catch{}return fallback},absolutizeRedirect=(target2,portalOrigin)=>portalOrigin&&target2.startsWith("/")&&target2[1]!=="/"?`${portalOrigin}${target2}`:target2;var init_oauth=__esm(()=>{init_DeviceTrust();init_routeCollector();init_assignDefaultRole();init_fetchUserRolesAndClaims();init_deviceInfo();init_utils7();init_utils8();init_applyProfileSync()});var PasswordChangeBodySchema,PasswordChangeResponseSchema;var init_types15=__esm(()=>{init_esm3();PasswordChangeBodySchema=Type.Object({currentPassword:Type.Optional(Type.String()),newPassword:Type.String({minLength:8}),confirmPassword:Type.String({minLength:8})}),PasswordChangeResponseSchema=Type.Object({success:Type.Boolean(),message:Type.Optional(Type.String())})});function validatePasswordsMatch(newPassword,confirmPassword){return newPassword===confirmPassword}var exports_store={};__export(exports_store,{userHasNoTrustedDevices:()=>userHasNoTrustedDevices,touchDevice:()=>touchDevice,revokeUserDevices:()=>revokeUserDevices,revokeDevicesBySessionIds:()=>revokeDevicesBySessionIds,revokeDeviceById:()=>revokeDeviceById,rejectDeviceForSession:()=>rejectDeviceForSession,listUserDevices:()=>listUserDevices,insertDeviceRow:()=>insertDeviceRow,findDeviceRowsByRawToken:()=>findDeviceRowsByRawToken,activateDeviceForSession:()=>activateDeviceForSession});import{and as and25,eq as eq57,inArray as inArray17}from"drizzle-orm";async function findDeviceRowsByRawToken(db,table,rawToken){let hash=hashDeviceToken(rawToken);return await db.select().from(table).where(eq57(col14(table,"tokenHash"),hash)).limit(2)}async function insertDeviceRow(db,table,v){let now=new Date;await db.insert(table).values({id:v.id,userId:v.userId,tokenHash:v.tokenHash,status:v.status,originSessionId:v.originSessionId??null,label:v.label??null,deviceFingerprint:v.deviceFingerprint??null,firstSeenIp:v.firstSeenIp??null,lastSeenIp:v.lastSeenIp??v.firstSeenIp??null,lastSeenAt:now,expiresAt:v.expiresAt,isActive:!0,createdAt:now,updatedAt:now})}async function activateDeviceForSession(db,table,sessionId){return(await db.update(table).set({status:"trusted",updatedAt:new Date}).where(and25(eq57(col14(table,"originSessionId"),sessionId),eq57(col14(table,"status"),"pending"))).returning({id:col14(table,"id")})).length}async function rejectDeviceForSession(db,table,sessionId){await db.update(table).set({status:"revoked",revokedAt:new Date,revokedReason:"replaced",isActive:!1,updatedAt:new Date}).where(and25(eq57(col14(table,"originSessionId"),sessionId),eq57(col14(table,"status"),"pending")))}async function touchDevice(db,table,id,ip){await db.update(table).set({lastSeenAt:new Date,lastSeenIp:ip??null,updatedAt:new Date}).where(eq57(col14(table,"id"),id))}async function revokeWhere(db,table,where,reason){await db.update(table).set({status:"revoked",revokedAt:new Date,revokedReason:reason,isActive:!1,updatedAt:new Date}).where(where)}async function revokeUserDevices(db,table,userId,reason){if(!userId)return;let notAlready=and25(eq57(col14(table,"userId"),userId),eq57(col14(table,"status"),"trusted")),alsoPending=and25(eq57(col14(table,"userId"),userId),eq57(col14(table,"status"),"pending"));await revokeWhere(db,table,notAlready,reason),await revokeWhere(db,table,alsoPending,reason)}async function revokeDevicesBySessionIds(db,table,sessionIds,reason){if(!sessionIds?.length)return;await revokeWhere(db,table,inArray17(col14(table,"originSessionId"),sessionIds),reason)}async function revokeDeviceById(db,table,id,userId,reason){return(await db.update(table).set({status:"revoked",revokedAt:new Date,revokedReason:reason,isActive:!1,updatedAt:new Date}).where(and25(eq57(col14(table,"id"),id),eq57(col14(table,"userId"),userId))).returning({id:col14(table,"id")})).length}async function listUserDevices(db,table,userId){return(await db.select().from(table).where(eq57(col14(table,"userId"),userId))).map((r)=>{let{tokenHash:_omit,...safe}=r;return safe})}async function userHasNoTrustedDevices(db,table,userId){return(await db.select({id:col14(table,"id")}).from(table).where(and25(eq57(col14(table,"userId"),userId),eq57(col14(table,"status"),"trusted"))).limit(1)).length===0}var col14=(t,name2)=>t[name2];var init_store3=__esm(()=>{init_DeviceTrust()});import{eq as eq58}from"drizzle-orm";function createPasswordChangeRoute(config,passwordChangeConfig){let{db,logger:logger2}=config,log4=logger2.scoped(AUTH_PASSWORD_CHANGE),route=passwordChangeConfig.route||"/auth/password-change";if(!passwordChangeConfig.enabled)return collectRoutes();let routePostRoute=defineRoute({method:"POST",path:"/",body:PasswordChangeBodySchema,detail:{tags:["Authentication"],summary:"Change Password",description:"Change password for authenticated user"},handler:async(ctx)=>{let{usersTable,sessionsTable}=resolveAuthTablesForRequest(ctx.request,config);if(!db||!usersTable)return ctx.set.status=500,{success:!1,message:"Database not configured"};let userId=ctx.request.headers.get("x-user-id");if(!userId)return ctx.set.status=401,{success:!1,message:"Authentication required"};let{currentPassword,newPassword,confirmPassword}=ctx.body;if(!validatePasswordsMatch(newPassword,confirmPassword))return ctx.set.status=422,{success:!1,message:"New passwords do not match"};let strength=validatePasswordStrength(newPassword,config.authentication?.passwordPolicy);if(!strength.valid)return ctx.set.status=400,{success:!1,message:"Password too weak",errors:strength.errors};let user=(await db.select().from(usersTable).where(eq58(usersTable.id,userId)).limit(1))[0];if(!user)return ctx.set.status=404,{success:!1,message:"User not found"};let identityError=identityRuleError(newPassword,config.authentication?.passwordPolicy,typeof user.email==="string"?user.email:void 0);if(identityError)return ctx.set.status=400,{success:!1,message:"Password too weak",errors:[identityError]};let existingHash=user.password??"",userHasPassword=existingHash.length>0,isInitialSet=!userHasPassword,reqUrl=new URL(ctx.request.url),auditIp=ctx.request.headers.get("x-forwarded-for")?.split(",")[0]?.trim()||ctx.request.headers.get("x-real-ip")?.trim()||"unknown",auditUa=ctx.request.headers.get("user-agent")||"unknown";if(userHasPassword){if(!currentPassword)return ctx.set.status=400,{success:!1,message:"Current password is required"};if(!await verifyPassword(currentPassword,existingHash))return log4.warn("Password change failed - invalid current password",{userId}),logger2.audit({entityName:"users",entityId:userId,operation:"PASSWORD_CHANGE_FAILED",userId,summary:"Password change failed: invalid current password",ipAddress:auditIp,userAgent:auditUa,path:reqUrl.pathname,query:reqUrl.search}),ctx.set.status=400,{success:!1,message:"Current password is incorrect"}}let hashedNewPassword=await hashPassword(newPassword);if(await db.update(usersTable).set({password:hashedNewPassword,updatedAt:new Date}).where(eq58(usersTable.id,userId)),!isInitialSet&&sessionsTable)try{await db.update(sessionsTable).set({isActive:!1,revokedAt:new Date,revokedReason:"password_change"}).where(eq58(sessionsTable.userId,userId))}catch(err){log4.warn("Failed to revoke sessions after password change",{userId,error:err instanceof Error?err.message:String(err)})}if(!isInitialSet&&config.authentication?.deviceTrust?.enabled)try{let{schemaTables}=resolveAuthTablesForRequest(ctx.request,config),dtTable=schemaTables?.trustedDevices??schemaTables?.trusted_devices;if(dtTable&&db){let{revokeUserDevices:revokeUserDevices2}=await Promise.resolve().then(() => (init_store3(),exports_store));await revokeUserDevices2(db,dtTable,userId,"password_reset")}}catch(err){log4.warn("Failed to revoke device tokens after password change",{userId,error:err instanceof Error?err.message:String(err)})}return log4.info(isInitialSet?"Initial password set successful":"Password change successful",{userId}),logger2.audit({entityName:"users",entityId:userId,operation:isInitialSet?"PASSWORD_SET":"PASSWORD_CHANGE",userId,summary:isInitialSet?"Initial password set successfully":"Password changed successfully",ipAddress:auditIp,userAgent:auditUa,path:reqUrl.pathname,query:reqUrl.search}),{success:!0,message:isInitialSet?"Password set successfully":"Password changed successfully"}}});return collectRoutes([{prefix:route,routes:[routePostRoute]}])}var init_passwordChange=__esm(()=>{init_passwordPolicy();init_scopes();init_routeCollector();init_utils7();init_utils6();init_types15();init_types15()});function describeResetEmail(outcome){if(!outcome||outcome.sent)return{sent:!0,level:"info",message:"Password reset email sent"};let base=SENTENCES[outcome.reason],detail=outcome.detail?.trim();return{sent:!1,level:outcome.reason==="exempt"?"info":"warn",message:detail?`${base} (${detail})`:base}}var SENTENCES;var init_outcome=__esm(()=>{SENTENCES={no_provider:"Password reset email NOT sent \u2014 no email provider is configured. The token was stored, so a link exists, but nothing carried it.",exempt:"Password reset email not sent \u2014 the address is on an exempt domain this install never mails.",rejected:"Password reset email NOT sent \u2014 the provider refused the recipient. The address is accepted at login but not deliverable.",error:"Password reset email NOT sent \u2014 the send failed."}});var PasswordResetRequestSchema,PasswordResetConfirmSchema,PasswordResetResponseSchema;var init_types16=__esm(()=>{init_esm3();PasswordResetRequestSchema=Type.Object({email:Type.String({format:"email"})}),PasswordResetConfirmSchema=Type.Object({token:Type.String(),newPassword:Type.String({minLength:8}),confirmPassword:Type.String({minLength:8})}),PasswordResetResponseSchema=Type.Object({success:Type.Boolean(),message:Type.Optional(Type.String())})});import{randomBytes as randomBytes7}from"crypto";function generateResetToken(){return randomBytes7(32).toString("hex")}function isTokenExpired(expiresAt){return new Date>expiresAt}var init_utils10=()=>{};function createPasswordResetRoute(config,passwordResetConfig,storeResetToken,getResetToken,deleteResetToken,sendResetEmail){let{db,logger:logger2}=config,log4=logger2.scoped(AUTH_PASSWORD_RESET),baseRoute=passwordResetConfig.route||"/auth/password-reset";if(!passwordResetConfig.enabled)return collectRoutes();let routePostBaserouteRequest=defineRoute({method:"POST",path:"/request",body:PasswordResetRequestSchema,detail:{tags:["Authentication"],summary:"Request Password Reset",description:"Request a password reset email"},handler:async(ctx)=>{let{usersTable}=resolveAuthTablesForRequest(ctx.request,config);if(!db||!usersTable)return ctx.set.status=500,{success:!1,message:"Database not configured"};let{email:rawEmail}=ctx.body,email=rawEmail.toLowerCase(),{eq:eq59}=await import("drizzle-orm"),user=(await db.select().from(usersTable).where(eq59(usersTable.email,email)).limit(1))[0];if(!user)return{success:!0,message:"If email exists, reset link will be sent"};let token=generateResetToken(),expiresAt=new Date(Date.now()+3600000),reqSchemaName=ctx.request.headers.get("x-tenant-schema")||void 0;await storeResetToken(user.id,token,expiresAt,reqSchemaName);let delivery={sent:!1,level:"warn",message:"Password reset email NOT sent \u2014 no send helper is configured."};if(sendResetEmail)try{delivery=describeResetEmail(await sendResetEmail(email,token,ctx.request))}catch(error3){delivery=describeResetEmail({sent:!1,reason:"error",detail:error3 instanceof Error?error3.message:String(error3)})}if(delivery.level==="warn")log4.warn(delivery.message,{email});else log4.info(delivery.message,{email});log4.info("Password reset requested",{userId:user.id,email});let reqUrl=new URL(ctx.request.url);return logger2.audit({entityName:"users",entityId:user.id,operation:"PASSWORD_RESET_REQUEST",userId:user.id,summary:`Password reset requested for ${email} \u2014 ${delivery.sent?"email sent":"EMAIL NOT SENT"}`,ipAddress:ctx.request.headers.get("x-forwarded-for")?.split(",")[0]?.trim()||ctx.request.headers.get("x-real-ip")?.trim()||"unknown",userAgent:ctx.request.headers.get("user-agent")||"unknown",path:reqUrl.pathname,query:reqUrl.search}),{success:!0,message:"If email exists, reset link will be sent"}}}),routePostBaserouteConfirm=defineRoute({method:"POST",path:"/confirm",body:PasswordResetConfirmSchema,detail:{tags:["Authentication"],summary:"Confirm Password Reset",description:"Reset password with token"},handler:async(ctx)=>{let{usersTable,sessionsTable}=resolveAuthTablesForRequest(ctx.request,config);if(!db||!usersTable)return ctx.set.status=500,{success:!1,message:"Database not configured"};let{token,newPassword,confirmPassword}=ctx.body;if(newPassword!==confirmPassword)return ctx.set.status=400,{success:!1,message:"Passwords do not match"};let strength=validatePasswordStrength(newPassword,config.authentication?.passwordPolicy);if(!strength.valid)return ctx.set.status=400,{success:!1,message:"Password too weak",errors:strength.errors};let reqSchemaName=ctx.request.headers.get("x-tenant-schema")||void 0,resetData=await getResetToken(token,reqSchemaName);if(!resetData)return ctx.set.status=400,{success:!1,message:"Invalid or expired reset token"};if(isTokenExpired(resetData.expiresAt))return await deleteResetToken(token,reqSchemaName),ctx.set.status=400,{success:!1,message:"Reset token has expired"};let{eq:eq59}=await import("drizzle-orm");if(config.authentication?.passwordPolicy?.preventUserInfoInPassword){let ownerEmail=(await db.select().from(usersTable).where(eq59(usersTable.id,resetData.userId)).limit(1))[0]?.email,identityError=identityRuleError(newPassword,config.authentication?.passwordPolicy,typeof ownerEmail==="string"?ownerEmail:void 0);if(identityError)return ctx.set.status=400,{success:!1,message:"Password too weak",errors:[identityError]}}let hashedPassword=await hashPassword(newPassword);if(await db.update(usersTable).set({password:hashedPassword}).where(eq59(usersTable.id,resetData.userId)),await deleteResetToken(token,reqSchemaName),sessionsTable)try{await db.update(sessionsTable).set({isActive:!1,revokedAt:new Date,revokedReason:"password_reset"}).where(eq59(sessionsTable.userId,resetData.userId))}catch(err){log4.warn("Failed to revoke sessions after password reset",{userId:resetData.userId,error:err instanceof Error?err.message:String(err)})}try{if(config.authentication?.deviceTrust?.enabled){let{schemaTables}=resolveAuthTablesForRequest(ctx.request,config),dtTable=schemaTables?.trustedDevices??schemaTables?.trusted_devices;if(dtTable&&db){let{revokeUserDevices:revokeUserDevices2}=await Promise.resolve().then(() => (init_store3(),exports_store));await revokeUserDevices2(db,dtTable,resetData.userId,"password_reset")}}}catch(err){log4.warn("Failed to revoke device tokens after password reset",{userId:resetData.userId,error:err instanceof Error?err.message:String(err)})}log4.info("Password reset successful",{userId:resetData.userId});let reqUrl=new URL(ctx.request.url);return logger2.audit({entityName:"users",entityId:resetData.userId,operation:"PASSWORD_RESET",userId:resetData.userId,summary:"Password reset completed successfully",ipAddress:ctx.request.headers.get("x-forwarded-for")?.split(",")[0]?.trim()||ctx.request.headers.get("x-real-ip")?.trim()||"unknown",userAgent:ctx.request.headers.get("user-agent")||"unknown",path:reqUrl.pathname,query:reqUrl.search}),{success:!0,message:"Password has been reset"}}});return collectRoutes([{prefix:baseRoute,routes:[routePostBaserouteRequest,routePostBaserouteConfirm]}])}var init_passwordReset=__esm(()=>{init_passwordPolicy();init_scopes();init_routeCollector();init_utils6();init_outcome();init_types16();init_utils10();init_types16()});var PasswordSetBodySchema,PasswordSetResponseSchema;var init_types17=__esm(()=>{init_esm3();PasswordSetBodySchema=Type.Object({newPassword:Type.String({minLength:8}),userId:Type.Optional(Type.String()),token:Type.Optional(Type.String())}),PasswordSetResponseSchema=Type.Object({success:Type.Boolean(),message:Type.String()})});import{eq as eq59}from"drizzle-orm";function createPasswordSetRoute(config,passwordSetConfig,getMagicToken,deleteMagicToken){let{db,logger:logger2}=config,log4=logger2.scoped(AUTH_PASSWORD_SET),route=passwordSetConfig.route||"/auth/password-set";if(!passwordSetConfig.enabled)return collectRoutes();let routePostRoute=defineRoute({method:"POST",path:"/",body:PasswordSetBodySchema,detail:{tags:["Authentication"],summary:"Set Password",description:"Set password for the first time (for invited users who do not have a password yet)"},handler:async(ctx)=>{log4.info("Password set request received");let{usersTable}=resolveAuthTablesForRequest(ctx.request,config);if(!db||!usersTable)return log4.error("Password set failed - database not configured"),ctx.set.status=500,{success:!1,message:"Database not configured"};let{newPassword,token}=ctx.body,strength=validatePasswordStrength(newPassword,config.authentication?.passwordPolicy);if(!strength.valid)return ctx.set.status=400,{success:!1,message:"Password too weak",errors:strength.errors};let userId=null;if(token&&getMagicToken&&deleteMagicToken){let reqSchemaName=ctx.request.headers.get("x-tenant-schema")||void 0,tokenHash=hashToken(token,TOKEN_PURPOSE.invite),storedToken=await getMagicToken(tokenHash,reqSchemaName);if(!storedToken)return log4.warn("Password set failed - invalid token"),ctx.set.status=400,{success:!1,message:"Invalid or expired token"};if(new Date>storedToken.expiresAt)return await deleteMagicToken(tokenHash,reqSchemaName),log4.warn("Password set failed - expired token",{email:storedToken.email}),ctx.set.status=400,{success:!1,message:"Invalid or expired token"};userId=storedToken.userId,log4.info("Password set - userId resolved from token",{userId,email:storedToken.email})}else userId=ctx.request.headers.get("x-user-id");if(!userId)return log4.warn("Password set failed - no token and no authenticated user"),ctx.set.status=401,{success:!1,message:"Valid token or authentication required"};let user=(await db.select().from(usersTable).where(eq59(usersTable.id,userId)).limit(1))[0];if(log4.info("Password set - user found",{found:!!user,hasPassword:!!user?.password}),!user)return ctx.set.status=404,{success:!1,message:"User not found"};let identityError=identityRuleError(newPassword,config.authentication?.passwordPolicy,typeof user.email==="string"?user.email:void 0);if(identityError)return ctx.set.status=400,{success:!1,message:"Password too weak",errors:[identityError]};if(user.password)return log4.warn("Password set failed - user already has password",{userId}),ctx.set.status=409,{success:!1,message:"Password already set. Use password change instead."};let hashedPassword=await hashPassword(newPassword);log4.info("Password set - updating user with verifiedAt",{userId});let updateResult=await db.update(usersTable).set({password:hashedPassword,verifiedAt:new Date,updatedAt:new Date}).where(eq59(usersTable.id,userId));if(log4.info("Password set successful for invited user",{userId,updateResult}),token&&deleteMagicToken){let setSchemaName=ctx.request.headers.get("x-tenant-schema")||void 0,tokenHash=hashToken(token,TOKEN_PURPOSE.invite);await deleteMagicToken(tokenHash,setSchemaName),log4.info("Invite token consumed after password set",{userId})}return{success:!0,message:"Password set successfully"}}});return collectRoutes([{prefix:route,routes:[routePostRoute]}])}var init_passwordSet=__esm(()=>{init_passwordPolicy();init_scopes();init_routeCollector();init_utils8();init_utils6();init_types17();init_types17()});function parseTimeToSeconds3(time2){let match=time2.match(/^(\d+)([smhd])$/);if(!match)return 900;let value2=match[1],unit=match[2];if(!value2||!unit)return 900;let num3=parseInt(value2,10);switch(unit){case"s":return num3;case"m":return num3*60;case"h":return num3*3600;case"d":return num3*86400;default:return 900}}var RefreshResponseSchema;var init_types18=__esm(()=>{init_esm3();RefreshResponseSchema=Type.Object({success:Type.Boolean(),message:Type.Optional(Type.String()),data:Type.Optional(Type.Object({accessToken:Type.String()}))})});import{eq as eq60}from"drizzle-orm";function createRefreshRoute(config,refreshConfig,verifyRefreshToken,signAccessToken,signRefreshToken,tokenResponseConfig,cookieMaxAgeBufferSeconds,cookieConfig){let{logger:logger2,authentication}=config,route=refreshConfig.route||"/auth/refresh";if(!refreshConfig.enabled)return collectRoutes();let routePostRoute=defineRoute({method:"POST",path:"/",detail:{tags:["Authentication"],summary:"Refresh Token",description:"Get a new access token using a refresh token. Browsers send it as the refresh cookie; a header-only client sends it as `Authorization: Bearer <refresh token>`."},handler:async(ctx)=>{let refreshTokenName=authentication?.refreshToken?.name||"refresh_token",accessTokenName=authentication?.accessToken?.name||"access_token",refreshToken=null,cookieHeader=ctx.request.headers.get("cookie");if(cookieHeader)refreshToken=cookieHeader.split(";").reduce((acc,cookie)=>{let trimmed=cookie.trim(),eqIndex=trimmed.indexOf("=");if(eqIndex>0)acc[trimmed.slice(0,eqIndex)]=trimmed.slice(eqIndex+1);return acc},{})[refreshTokenName]||null;if(!refreshToken){let authorization=ctx.request.headers.get("authorization")??"";refreshToken=/^Bearer\s+(.+)$/i.exec(authorization.trim())?.[1]?.trim()||null}if(!refreshToken)return logger2.debug("[AUTH] Refresh skipped - no refresh cookie or bearer token on the request"),ctx.set.status=401,{success:!1,message:"Refresh token required"};let result=verifyRefreshToken(refreshToken);if(!result.valid||!result.payload)return logger2.warn("[AUTH] Refresh failed - invalid token"),ctx.set.status=401,{success:!1,message:"Invalid refresh token"};let userId=result.payload.sub,resolved=resolveAuthTablesForRequest(ctx.request,config),statelessMode=authentication?.mode==="consumer",boundSessionId=typeof result.payload.sessionId==="string"?result.payload.sessionId:"",sessionRememberMe,supersededHash;if(!statelessMode){if(!boundSessionId)return logger2.warn("[AUTH] Refresh blocked - token carries no session",{userId}),ctx.set.status=401,{success:!1,message:"Refresh token is not bound to a session"};let session=await refreshSessionIsAlive({sessionId:boundSessionId,db:config.db,sessionsTable:resolved.sessionsTable});if(!session.alive)return logger2.warn("[AUTH] Refresh blocked - session is no longer active",{userId,sessionId:boundSessionId,reason:session.reason}),ctx.set.status=401,{success:!1,message:"Session is no longer active"};sessionRememberMe=session.rememberMe;let verdict=classifyRefreshToken({presentedHash:hashRefreshToken(refreshToken),currentHash:session.refreshTokenHash,previousHash:session.previousRefreshTokenHash,previousRotatedAt:session.previousRefreshTokenAt,nowMs:Date.now()});if(verdict.action==="revoke")return logger2.warn("[AUTH] Refresh token reuse detected - ending the session",{userId,sessionId:boundSessionId,reason:verdict.reason}),await deleteSession({sessionId:boundSessionId}).catch(()=>{return}),ctx.set.status=401,{success:!1,message:"Session is no longer active"};supersededHash=session.refreshTokenHash}let userRoles=[],userClaims=[],userClaimScopes={};if(config.db){try{let rc=await fetchUserRolesAndClaims(config.db,userId,resolved);userRoles=rc.roles,userClaims=rc.claims,userClaimScopes=rc.claimScopes}catch(roleError){logger2.error("[AUTH] Failed to fetch roles/claims during refresh",{userId,error:roleError instanceof Error?roleError.message:String(roleError)})}try{let usersTable=resolved.usersTable;if(usersTable){let user=(await config.db.select().from(usersTable).where(eq60(usersTable.id,userId)).limit(1))[0];if(!user||user.isActive===!1)return logger2.warn("[AUTH] Refresh blocked - account inactive",{userId}),ctx.set.status=401,{success:!1,message:"Account is not active"};let lockedUntilRaw=user.lockedUntil,lockedUntil=lockedUntilRaw?new Date(lockedUntilRaw):null;if(user.isLocked===!0&&(!lockedUntil||lockedUntil>new Date))return logger2.warn("[AUTH] Refresh blocked - account locked",{userId}),ctx.set.status=401,{success:!1,message:"Account is locked"};let userCohortId=user.cohortId;if(userCohortId){let cohortsTable=resolved.schemaTables?.userCohorts??resolved.schemaTables?.user_cohorts;if(cohortsTable){let cohortExpiresAt=(await config.db.select().from(cohortsTable).where(eq60(cohortsTable.id,userCohortId)).limit(1))[0]?.expiresAt;if(cohortExpiresAt&&new Date(cohortExpiresAt)<new Date)return logger2.warn("[AUTH] Refresh blocked - cohort expired",{userId,cohortId:userCohortId}),ctx.set.status=401,{success:!1,message:"Your access has expired. Please contact your administrator."}}}}}catch(cohortErr){logger2.error("[AUTH] Failed cohort expiry check during refresh",{userId,error:cohortErr instanceof Error?cohortErr.message:String(cohortErr)})}}let newAccessToken=signAccessToken(userId,userRoles.length>0?userRoles:void 0,userClaims.length>0?userClaims:void 0,ctx.request.headers.get("x-tenant-schema")??void 0,userClaimScopes,boundSessionId||void 0),newRefreshToken=signRefreshToken?signRefreshToken(userId,sessionRememberMe,boundSessionId||void 0):null;await recordIssuedRefreshToken(boundSessionId||void 0,newRefreshToken,{previousHash:supersededHash});let accessSetHeaders=tokenResponseConfig?.accessToken?.setHeadersEnabled??!0,accessReturnJson=tokenResponseConfig?.accessToken?.returnJson??!0,refreshSetHeaders=tokenResponseConfig?.refreshToken?.setHeadersEnabled??!0,refreshReturnJson=tokenResponseConfig?.refreshToken?.returnJson??!1,accessExpiresIn=authentication?.accessToken?.expiresIn||"15m",accessMaxAge=Math.max(0,parseTimeToSeconds3(accessExpiresIn)-(cookieMaxAgeBufferSeconds??0)),refreshTtl=longerLifetime(sessionRememberMe?authentication?.rememberMe?.expiresIn:void 0,authentication?.refreshToken?.expiresIn,604800),refreshMaxAge=cookieMaxAge(refreshTtl),headers=new Headers;headers.set("Content-Type","application/json");let cookiePath=cookieConfig?.path||"/",cookieSameSite=cookieConfig?.sameSite||"Strict",securePart=cookieConfig?.secure!==!1?"; Secure":"",domainPart=cookieConfig?.domain?`; Domain=${cookieConfig.domain}`:"",cookieOpts=`; Path=${cookiePath}; HttpOnly; SameSite=${cookieSameSite}${securePart}${domainPart}`;if(accessSetHeaders)headers.append("Set-Cookie",`${accessTokenName}=${newAccessToken}${cookieOpts}; Max-Age=${accessMaxAge}`);if(newRefreshToken&&refreshSetHeaders)headers.append("Set-Cookie",`${refreshTokenName}=${newRefreshToken}${cookieOpts}; Max-Age=${refreshMaxAge}`);logger2.info("[AUTH] Token refresh successful",{userId,rotatedRefreshToken:!!newRefreshToken});let responseData={};if(accessReturnJson)responseData.accessToken=newAccessToken;if(newRefreshToken&&refreshReturnJson)responseData.refreshToken=newRefreshToken;let jsonBody=JSON.stringify(withEnvelopeFlags({success:!0,data:responseData}));return new Response(jsonBody,{status:200,headers})}});return collectRoutes([{prefix:route,routes:[routePostRoute]}])}var init_refresh=__esm(()=>{init_Delete2();init_response();init_routeCollector();init_lifetime();init_fetchUserRolesAndClaims();init_recordIssued();init_reuseDetection();init_sessionBinding();init_types18()});var RegisterBodySchema,RegisterResponseSchema;var init_types19=__esm(()=>{init_esm3();RegisterBodySchema=Type.Object({email:Type.String({format:"email"}),password:Type.String({minLength:8}),confirmPassword:Type.Optional(Type.String({minLength:8})),firstName:Type.Optional(Type.String({maxLength:100})),lastName:Type.Optional(Type.String({maxLength:100})),createProfile:Type.Optional(Type.Boolean())}),RegisterResponseSchema=Type.Object({success:Type.Boolean(),message:Type.Optional(Type.String()),data:Type.Optional(Type.Object({user:Type.Object({id:Type.String(),email:Type.String()})}))})});import{eq as eq61}from"drizzle-orm";function createRegisterRoute(config,registerConfig,sendWelcomeEmail,signAccessToken,signRefreshToken,createSession,cookieConfig,tokenResponseConfig,getEmailService,appName,saveSessionToDb){let{db,logger:logger2}=config,log4=logger2.scoped(AUTH_REGISTER),route=registerConfig.route||"/auth/register",cookies={accessTokenName:cookieConfig?.accessTokenName||"access_token",refreshTokenName:cookieConfig?.refreshTokenName||"refresh_token",sessionTokenName:cookieConfig?.sessionTokenName||"session_token",accessTokenMaxAge:cookieConfig?.accessTokenMaxAge||900,refreshTokenMaxAge:cookieConfig?.refreshTokenMaxAge||604800,sessionTokenMaxAge:cookieConfig?.sessionTokenMaxAge||900,secure:cookieConfig?.secure??!0,httpOnly:cookieConfig?.httpOnly??!0,sameSite:cookieConfig?.sameSite||"strict",path:cookieConfig?.path||"/",domain:cookieConfig?.domain};if(!registerConfig.enabled)return collectRoutes();let routePostRoute=defineRoute({method:"POST",path:"/",body:RegisterBodySchema,detail:{tags:["Authentication"],summary:"Register",description:"Register a new user account"},handler:async(ctx)=>{let resolved=resolveAuthTablesForRequest(ctx.request,config),usersTable=resolved.usersTable;if(!db||!usersTable)return ctx.set.status=500,{success:!1,message:"Database not configured"};let{email:rawEmail,password:password4,confirmPassword,firstName,lastName,createProfile}=ctx.body,email=rawEmail.toLowerCase();if(confirmPassword&&password4!==confirmPassword)return ctx.set.status=400,{success:!1,message:"Passwords do not match"};let passwordValidation=validatePasswordStrength(password4,config.authentication?.passwordPolicy,email);if(!passwordValidation.valid)return ctx.set.status=400,{success:!1,message:"Password too weak",errors:passwordValidation.errors};let existingUsers=await db.select().from(usersTable).where(eq61(usersTable.email,email)).limit(1),reqUrl=new URL(ctx.request.url),auditIp=ctx.request.headers.get("x-forwarded-for")?.split(",")[0]?.trim()||ctx.request.headers.get("x-real-ip")?.trim()||"unknown",auditUa=ctx.request.headers.get("user-agent")||"unknown";if(existingUsers.length>0){log4.warn("Registration failed - email exists",{email}),logger2.audit({entityName:"users",operation:"REGISTER_FAILED",summary:`Registration failed: email already exists (${email})`,ipAddress:auditIp,userAgent:auditUa,path:reqUrl.pathname,query:reqUrl.search});let verifGatesSignup=registerConfig.emailVerification?.enabled&&getEmailService?.()?.isAvailable();if(config.authentication?.preventUserEnumeration&&verifGatesSignup)return{success:!0,message:"Registration successful. Please check your email to verify your account."};return ctx.set.status=409,{success:!1,message:"Email already registered"}}let hashedPassword=await hashPassword(password4),domainExempt=isEmailExempt(email,config.authentication?.emailExemptDomains),emailService=getEmailService?.()??null,emailVerificationEnabled=!domainExempt&®isterConfig.emailVerification?.enabled&&emailService?.isAvailable(),verificationToken=emailVerificationEnabled?generateVerificationToken():null,tokenExpiresIn=registerConfig.emailVerification?.tokenExpiresIn||"24h",tokenExpiresAt=emailVerificationEnabled?new Date(Date.now()+parseTimeToMs(tokenExpiresIn)):null,insertValues={email,password:hashedPassword};if(emailVerificationEnabled&&verificationToken)insertValues.emailVerificationToken=hashVerificationToken(verificationToken),insertValues.emailVerificationTokenExpiresAt=tokenExpiresAt,insertValues.emailVerificationSentAt=new Date,insertValues.emailVerificationAttempts=1;let newUser=(await db.insert(usersTable).values(insertValues).returning())[0],registerDefaultRoles=resolveDefaultRoleNames(config.authentication);if(registerDefaultRoles.length>0)await assignDefaultRoles({db,userId:newUser.id,roleNames:registerDefaultRoles,rolesTable:resolved.rolesTable??config.rolesTable??null,userRolesTable:resolved.userRolesTable??config.userRolesTable??null,logger:logger2});let wantsProfile=registerConfig.createProfileOnRegister||createProfile,profilesTable=resolved.schemaTables?.profiles;if(wantsProfile&&profilesTable&&(firstName||lastName))try{let profileNow=new Date;await db.insert(profilesTable).values({userId:newUser.id,firstName:firstName??"",lastName:lastName??"",createdAt:profileNow,updatedAt:profileNow})}catch(err){log4.warn("Failed to create profile on register \u2014 continuing",{userId:newUser.id,error:err instanceof Error?err.message:String(err)})}if(log4.info("Registration successful",{userId:newUser.id,email,emailVerificationEnabled}),logger2.audit({entityName:"users",entityId:newUser.id,operation:"REGISTER",userId:newUser.id,summary:`New user registered: ${email}`,ipAddress:auditIp,userAgent:auditUa,path:reqUrl.pathname,query:reqUrl.search}),emailVerificationEnabled&&emailService&&verificationToken){let verificationLink=buildEmailActionLink({request:ctx.request,configuredUrl:registerConfig.emailVerification?.redirectUrl,path:"/verify-email",query:{token:verificationToken},allowedOrigins:config.authentication?.trustedAppOrigins});emailService.sendVerificationEmail(email,email.split("@")[0]||"User",verificationLink,appName||"Nucleus",config.authentication?.emailVerificationTemplates?.verification?.subject).then((result2)=>{if(result2.success)log4.info("Verification email sent",{email});else log4.error("Failed to send verification email",{email,error:result2.error})}).catch((err)=>{log4.error("Failed to send verification email",{email,error:err})});let resendCooldown=registerConfig.emailVerification?.resendCooldown||"60s",cooldownSeconds=parseTimeToMs(resendCooldown)/1000,maxResendAttempts=registerConfig.emailVerification?.maxResendAttempts||3;if(config.authentication?.preventUserEnumeration)return{success:!0,message:"Registration successful. Please check your email to verify your account."};return{success:!0,message:"Registration successful. Please check your email to verify your account.",data:{user:{id:newUser.id,email:newUser.email},emailVerificationRequired:!0,verification:{cooldownSeconds,canResendAt:new Date(Date.now()+cooldownSeconds*1000).toISOString(),attemptsRemaining:maxResendAttempts-1,maxAttempts:maxResendAttempts}}}}if(sendWelcomeEmail)sendWelcomeEmail(email,firstName||email.split("@")[0]||"User").catch((err)=>{log4.error("Failed to send welcome email",{email,error:err})});if(signAccessToken&&signRefreshToken&&createSession){let ipAddress=ctx.request.headers.get("x-forwarded-for")?.split(",")[0]?.trim()||ctx.request.headers.get("x-real-ip")?.trim()||"unknown",userAgent=ctx.request.headers.get("user-agent")||"",sessionParams={userId:newUser.id,deviceInfo:ensureDeviceInfo({ipAddress,userAgent},deviceContextFromRequest(ctx.request).clientHints),loginMethod:"register",rememberMe:!0},sessionId=await createSession(sessionParams),accessToken=signAccessToken(newUser.id,void 0,void 0,ctx.request.headers.get("x-tenant-schema")??void 0,void 0,sessionId),refreshToken=signRefreshToken(newUser.id,void 0,sessionId),deviceTokenCookie;if(saveSessionToDb){let reqSchemaName=ctx.request.headers.get("x-tenant-schema")||void 0,dtCfg=config.authentication?.deviceTrust;if(dtCfg?.enabled)sessionParams.deviceToken=readDeviceTokenFromCookieHeader(ctx.request.headers.get("cookie"),dtCfg);deviceTokenCookie=(await saveSessionToDb(sessionId,sessionParams,reqSchemaName))?.deviceTokenCookie}let tokenConfig={accessToken:{setHeadersEnabled:tokenResponseConfig?.accessToken?.setHeadersEnabled??!0,returnJson:tokenResponseConfig?.accessToken?.returnJson??!0},refreshToken:{setHeadersEnabled:tokenResponseConfig?.refreshToken?.setHeadersEnabled??!0,returnJson:tokenResponseConfig?.refreshToken?.returnJson??!0},sessionToken:{setHeadersEnabled:tokenResponseConfig?.sessionToken?.setHeadersEnabled??!0,returnJson:tokenResponseConfig?.sessionToken?.returnJson??!0}},securePart=cookies.secure?"; Secure":"",domainPart=cookies.domain?`; Domain=${cookies.domain}`:"",cookieOptions=`; Path=${cookies.path}; HttpOnly; SameSite=${cookies.sameSite}${securePart}${domainPart}`,cookiesToSet=[];if(tokenConfig.accessToken.setHeadersEnabled)cookiesToSet.push(`${cookies.accessTokenName}=${accessToken}${cookieOptions}; Max-Age=${cookies.accessTokenMaxAge}`);if(tokenConfig.refreshToken.setHeadersEnabled)cookiesToSet.push(`${cookies.refreshTokenName}=${refreshToken}${cookieOptions}; Max-Age=${cookies.refreshTokenMaxAge}`);if(tokenConfig.sessionToken.setHeadersEnabled)cookiesToSet.push(`${cookies.sessionTokenName}=${sessionId}${cookieOptions}; Max-Age=${cookies.sessionTokenMaxAge}`);if(deviceTokenCookie)cookiesToSet.push(deviceTokenCookie);let responseData={user:{id:newUser.id,email:newUser.email}};if(tokenConfig.accessToken.returnJson)responseData.accessToken=accessToken;if(tokenConfig.refreshToken.returnJson)responseData.refreshToken=refreshToken;if(tokenConfig.sessionToken.returnJson)responseData.sessionId=sessionId;let jsonBody=JSON.stringify(withEnvelopeFlags({success:!0,data:responseData})),headers=new Headers;headers.set("Content-Type","application/json"),headers.set("x-session-id",sessionId);for(let cookie of cookiesToSet)headers.append("Set-Cookie",cookie);return new Response(jsonBody,{status:200,headers})}return{success:!0,data:{user:{id:newUser.id,email:newUser.email}}}}});return collectRoutes([{prefix:route,routes:[routePostRoute]}])}var init_register=__esm(()=>{init_DeviceTrust();init_scopes();init_response();init_routeCollector();init_assignDefaultRole();init_deviceInfo();init_types19();init_utils6();init_types19()});var SessionResponseSchema,SessionListResponseSchema,RevokeSessionBodySchema,RevokeAllSessionsBodySchema,SessionActivityResponseSchema,AdminRevokeSessionsBodySchema;var init_types20=__esm(()=>{init_esm3();SessionResponseSchema=Type.Object({id:Type.String(),deviceName:Type.Optional(Type.String()),deviceType:Type.Optional(Type.String()),browserName:Type.Optional(Type.String()),browserVersion:Type.Optional(Type.String()),osName:Type.Optional(Type.String()),osVersion:Type.Optional(Type.String()),ipAddress:Type.String(),locationCountry:Type.Optional(Type.String()),locationCity:Type.Optional(Type.String()),lastActivityAt:Type.String(),createdAt:Type.String(),isCurrent:Type.Boolean(),loginMethod:Type.Optional(Type.String()),trustScore:Type.Optional(Type.Number())}),SessionListResponseSchema=Type.Object({success:Type.Boolean(),data:Type.Optional(Type.Object({sessions:Type.Array(SessionResponseSchema),currentSessionId:Type.Optional(Type.String()),totalCount:Type.Number()})),message:Type.Optional(Type.String())}),RevokeSessionBodySchema=Type.Object({reason:Type.Optional(Type.String())}),RevokeAllSessionsBodySchema=Type.Object({excludeCurrent:Type.Optional(Type.Boolean()),reason:Type.Optional(Type.String())}),SessionActivityResponseSchema=Type.Object({success:Type.Boolean(),data:Type.Optional(Type.Object({recentActivity:Type.Array(Type.Object({sessionId:Type.String(),action:Type.String(),ipAddress:Type.String(),timestamp:Type.String(),deviceInfo:Type.Optional(Type.String())}))})),message:Type.Optional(Type.String())}),AdminRevokeSessionsBodySchema=Type.Optional(Type.Object({sessionId:Type.Optional(Type.String()),reason:Type.Optional(Type.String())}))});function formatSessionForResponse(session){let get=(snake,camel3)=>session[snake]??session[camel3],getDate=(snake,camel3)=>{let val=session[snake]??session[camel3];if(!val)return;return val instanceof Date?val.toISOString():String(val)};return{id:session.id,deviceName:get("device_name","deviceName"),deviceType:get("device_type","deviceType"),deviceFingerprint:get("device_fingerprint","deviceFingerprint"),browserName:get("browser_name","browserName"),browserVersion:get("browser_version","browserVersion"),osName:get("os_name","osName"),osVersion:get("os_version","osVersion"),ipAddress:get("ip_address","ipAddress"),locationCountry:get("location_country","locationCountry"),locationCity:get("location_city","locationCity"),lastActivityAt:getDate("last_activity_at","lastActivityAt"),createdAt:getDate("created_at","createdAt"),isCurrent:get("is_current","isCurrent"),loginMethod:get("login_method","loginMethod"),trustScore:get("trust_score","trustScore")}}var init_utils11=()=>{};import{and as and26,desc as desc6,eq as eq62,isNull as isNull5}from"drizzle-orm";function createSessionsRoute(config,sessionsConfig,sessionsTable){let{db,logger:logger2}=config,log4=logger2.scoped(AUTH_SESSION),baseRoute=sessionsConfig.route||"/auth/sessions";if(!sessionsConfig.enabled||!sessionsTable)return collectRoutes();let tableColumns=sessionsTable,_col=(name2)=>{let snakeCase=name2.replace(/([A-Z])/g,"_$1").toLowerCase();return tableColumns[name2]||tableColumns[snakeCase]},getSessionsForRequest=(request)=>{let resolved=resolveAuthTablesForRequest(request,config),reqTable=resolved.sessionsTable||sessionsTable,reqCols=reqTable,reqCol=(name2)=>{let snakeCase=name2.replace(/([A-Z])/g,"_$1").toLowerCase();return reqCols[name2]||reqCols[snakeCase]},trustedDevicesTable=resolved.schemaTables?.trustedDevices??resolved.schemaTables?.trusted_devices;return{sessionsTable:reqTable,col:reqCol,trustedDevicesTable}},routeGetRoot=defineRoute({method:"GET",path:"/",detail:{tags:["Authentication"],summary:"List active sessions",description:"Get all active sessions for the current user"},handler:async(ctx)=>{let{sessionsTable:sessionsTable2,col:col15}=getSessionsForRequest(ctx.request);if(!db)return ctx.set.status=500,{success:!1,message:"Database not configured"};let userId=ctx.request.headers.get("x-user-id");if(!userId)return ctx.set.status=401,{success:!1,message:"Authentication required"};let currentSessionId=ctx.request.headers.get("x-session-id"),sessions=await db.select().from(sessionsTable2).where(and26(eq62(col15("userId"),userId),eq62(col15("isActive"),!0),isNull5(col15("revokedAt")))).orderBy(desc6(col15("lastActivityAt"))),realSessions=sessions.filter((s)=>{let sess=s,fp=(sess.deviceFingerprint||"").toLowerCase(),ip=sess.ipAddress||"";return!((!fp||fp==="--"||fp==="--unknown"||fp.includes("bot/crawler")||fp.includes("headless")||fp.includes("unknown-unknown"))&&(ip==="127.0.0.1"||ip==="::1"||ip==="localhost"||!ip))}),formattedSessions=realSessions.map((s)=>{let session=s,formatted=formatSessionForResponse(session);return formatted.isCurrent=session.id===currentSessionId,formatted});return log4.info("Sessions list retrieved",{userId,totalInDb:sessions.length,filteredCount:realSessions.length,hiddenBotSessions:sessions.length-realSessions.length}),{success:!0,data:{sessions:formattedSessions,currentSessionId,totalCount:realSessions.length}}}}),routeGetCurrent=defineRoute({method:"GET",path:"/current",detail:{tags:["Authentication"],summary:"Get current session",description:"Get details of the current session"},handler:async(ctx)=>{let{sessionsTable:sessionsTable2,col:col15}=getSessionsForRequest(ctx.request);if(!db)return ctx.set.status=500,{success:!1,message:"Database not configured"};let userId=ctx.request.headers.get("x-user-id"),sessionId=ctx.request.headers.get("x-session-id");if(!userId||!sessionId)return ctx.set.status=401,{success:!1,message:"Authentication required"};let sessions=await db.select().from(sessionsTable2).where(eq62(col15("id"),sessionId)).limit(1);if(sessions.length===0)return ctx.set.status=404,{success:!1,message:"Session not found"};let session=sessions[0],formatted=formatSessionForResponse(session);return formatted.isCurrent=!0,{success:!0,data:formatted}}}),routeDeleteSessionid=defineRoute({method:"DELETE",path:"/:sessionId",body:RevokeSessionBodySchema,detail:{tags:["Authentication"],summary:"Revoke session",description:"Revoke a specific session by ID"},handler:async(ctx)=>{let{sessionsTable:sessionsTable2,col:col15}=getSessionsForRequest(ctx.request);if(!db)return ctx.set.status=500,{success:!1,message:"Database not configured"};let userId=ctx.request.headers.get("x-user-id");if(!userId)return ctx.set.status=401,{success:!1,message:"Authentication required"};let{sessionId}=ctx.params,body=ctx.body,currentSessionId=ctx.request.headers.get("x-session-id");if((await db.select().from(sessionsTable2).where(and26(eq62(col15("id"),sessionId),eq62(col15("userId"),userId))).limit(1)).length===0)return ctx.set.status=404,{success:!1,message:"Session not found"};let isCurrentSession=sessionId===currentSessionId;await db.update(sessionsTable2).set({isActive:!1,revokedAt:new Date,revokedReason:isCurrentSession?"user_logout":body.reason||"user_revoked"}).where(eq62(col15("id"),sessionId)),log4.info("Session revoked",{userId,sessionId,isCurrentSession,reason:body.reason||"user_revoked"});let reqUrl=new URL(ctx.request.url);return logger2.audit({entityName:"user_sessions",entityId:sessionId,operation:isCurrentSession?"LOGOUT":"SESSION_REVOKE",userId:userId||void 0,summary:isCurrentSession?"User logged out via session revoke":`Session revoked (${sessionId.substring(0,8)}...)`,ipAddress:ctx.request.headers.get("x-forwarded-for")?.split(",")[0]?.trim()||ctx.request.headers.get("x-real-ip")?.trim()||"unknown",userAgent:ctx.request.headers.get("user-agent")||"unknown",path:reqUrl.pathname,query:reqUrl.search}),{success:!0,message:isCurrentSession?"Logged out successfully":"Session revoked successfully"}}}),routeDeleteAll=defineRoute({method:"DELETE",path:"/all",body:RevokeAllSessionsBodySchema,detail:{tags:["Authentication"],summary:"Revoke all sessions",description:"Revoke all sessions for the current user (optionally exclude current)"},handler:async(ctx)=>{let{sessionsTable:sessionsTable2,col:col15}=getSessionsForRequest(ctx.request);if(!db)return ctx.set.status=500,{success:!1,message:"Database not configured"};let userId=ctx.request.headers.get("x-user-id");if(!userId)return ctx.set.status=401,{success:!1,message:"Authentication required"};let body=ctx.body,currentSessionId=ctx.request.headers.get("x-session-id"),query=db.update(sessionsTable2).set({isActive:!1,revokedAt:new Date,revokedReason:body.reason||"user_revoked"}).where(and26(eq62(col15("userId"),userId),eq62(col15("isActive"),!0)));if(body.excludeCurrent&¤tSessionId){let{ne:ne3}=await import("drizzle-orm");query=db.update(sessionsTable2).set({isActive:!1,revokedAt:new Date,revokedReason:body.reason||"user_revoked"}).where(and26(eq62(col15("userId"),userId),eq62(col15("isActive"),!0),ne3(col15("id"),currentSessionId)))}await query,log4.info("All sessions revoked",{userId,excludeCurrent:body.excludeCurrent,reason:body.reason||"user_revoked"});let reqUrl=new URL(ctx.request.url);return logger2.audit({entityName:"user_sessions",operation:"SESSION_REVOKE_ALL",userId:userId||void 0,summary:body.excludeCurrent?"All other sessions revoked":"All sessions revoked",ipAddress:ctx.request.headers.get("x-forwarded-for")?.split(",")[0]?.trim()||ctx.request.headers.get("x-real-ip")?.trim()||"unknown",userAgent:ctx.request.headers.get("user-agent")||"unknown",path:reqUrl.pathname,query:reqUrl.search}),{success:!0,message:body.excludeCurrent?"All other sessions revoked successfully":"All sessions revoked successfully"}}}),routeGetStats=defineRoute({method:"GET",path:"/stats",detail:{tags:["Authentication"],summary:"Session statistics",description:"Get session statistics for the current user"},handler:async(ctx)=>{let{sessionsTable:sessionsTable2,col:col15}=getSessionsForRequest(ctx.request);if(!db)return ctx.set.status=500,{success:!1,message:"Database not configured"};let userId=ctx.request.headers.get("x-user-id");if(!userId)return ctx.set.status=401,{success:!1,message:"Authentication required"};let{count:count3,countDistinct}=await import("drizzle-orm"),activeSessions=await db.select({count:count3()}).from(sessionsTable2).where(and26(eq62(col15("userId"),userId),eq62(col15("isActive"),!0),isNull5(col15("revokedAt")))),uniqueDevices=await db.select({count:countDistinct(col15("deviceFingerprint"))}).from(sessionsTable2).where(and26(eq62(col15("userId"),userId),eq62(col15("isActive"),!0))),uniqueIps=await db.select({count:countDistinct(col15("ipAddress"))}).from(sessionsTable2).where(and26(eq62(col15("userId"),userId),eq62(col15("isActive"),!0)));return{success:!0,data:{activeSessions:activeSessions[0]?.count||0,uniqueDevices:uniqueDevices[0]?.count||0,uniqueIpAddresses:uniqueIps[0]?.count||0}}}}),APPROVAL_TOKEN_TTL_MS=86400000,buildResultHtml=(action,success,message)=>{let icon=success?action==="approve"?"\u2705":"\uD83D\uDEAB":"\u274C",title=success?action==="approve"?"Device Approved":"Device Rejected":"Action Failed",bName=sessionsConfig.brandName||"Nucleus";return`<!DOCTYPE html>
|
|
452
|
+
`}),logger2.info("[OAUTH] Invite email sent to new OAuth user",{userId,email:profile.email,provider})}catch(emailErr){logger2.warn("[OAUTH] Failed to send invite email",{userId,error:emailErr})}}{let stateUser=(await db.select().from(usersTable).where(eq56(usersTable.id,userId)).limit(1))[0],state2=stateUser?evaluateAccountState(stateUser):{allowed:!1,reason:"inactive",message:"Account is not active"};if(!state2.allowed)return logger2.warn(`[OAUTH] Login blocked - account ${state2.reason}`,{userId,provider}),ctx.set.status=302,ctx.set.headers.Location=`${errorRedirect}?error=account_${state2.reason}`,null}let defaultRoleNames=resolveDefaultRoleNames(config.authentication);if(defaultRoleNames.length>0){let resolvedForRole=resolveAuthTablesForRequest(ctx.request,config);await assignDefaultRoles({db,userId,roleNames:defaultRoleNames,rolesTable:resolvedForRole.rolesTable??config.rolesTable??null,userRolesTable:resolvedForRole.userRolesTable??config.userRolesTable??null,logger:logger2})}let syncConfig=oauthService.profileSync;if(syncConfig){let syncTables=resolveAuthTablesForRequest(ctx.request,config);await applyProfileSync({db,usersTable,profilesTable:syncTables.schemaTables?.profiles,userId,rawProfile:profile.rawProfile,config:syncConfig,isNewUser:createdNewUser,logger:logger2})}await db.update(usersTable).set({lastLoginAt:new Date}).where(eq56(usersTable.id,userId));let ipAddress=ctx.request.headers.get("cf-connecting-ip")?.trim()||ctx.request.headers.get("x-forwarded-for")?.split(",")[0]?.trim()||ctx.request.headers.get("x-real-ip")?.trim()||"127.0.0.1",userAgent=ctx.request.headers.get("user-agent")||"Unknown Browser",deviceInfo=parseUserAgentForLogin(userAgent,ipAddress,void 0,deviceContextFromRequest(ctx.request).clientHints),oauthUserRoles=[],oauthUserClaims=[],oauthUserClaimScopes={};if(config.db)try{let resolved2=resolveAuthTablesForRequest(ctx.request,config),rc=await fetchUserRolesAndClaims(config.db,userId,resolved2);oauthUserRoles=rc.roles,oauthUserClaims=rc.claims,oauthUserClaimScopes=rc.claimScopes}catch{}let requestOrigin=ctx.request.headers.get("origin")||ctx.request.headers.get("referer")?.replace(/\/[^/]*$/,"")||void 0,sessionParams={userId,deviceInfo,loginMethod:`oauth:${provider}`,rememberMe:!0,requestOrigin},sessionId=await createSession(sessionParams),accessToken=signAccessToken(userId,oauthUserRoles.length>0?oauthUserRoles:void 0,oauthUserClaims.length>0?oauthUserClaims:void 0,ctx.request.headers.get("x-tenant-schema")??void 0,oauthUserClaimScopes,sessionId),refreshToken=signRefreshToken(userId,void 0,sessionId),requiresApproval=!1,approvalSessionId=sessionId,deviceTokenCookie;if(saveSessionToDb){let oauthSchemaName=ctx.request.headers.get("x-tenant-schema")||void 0,dtCfg=config.authentication?.deviceTrust;if(dtCfg?.enabled)sessionParams.deviceToken=readDeviceTokenFromCookieHeader(ctx.request.headers.get("cookie"),dtCfg);let saveResult=await saveSessionToDb(sessionId,sessionParams,oauthSchemaName);if(saveResult?.denied)return logger2.warn("[OAUTH] Login blocked by device-trust",{userId,provider}),ctx.set.status=302,ctx.set.headers.Location=`${errorRedirect}?error=device_not_recognized`,null;if(deviceTokenCookie=saveResult?.deviceTokenCookie,saveResult?.requiresApproval){if(requiresApproval=!0,saveResult.sessionId)approvalSessionId=saveResult.sessionId}}if(requiresApproval){if(deviceTokenCookie)ctx.set.headers["Set-Cookie"]=deviceTokenCookie;logger2.info("[OAUTH] Login requires device approval",{userId,provider,approvalSessionId});let pendingUrl=absolutizeRedirect(resolveSafeOAuthRedirect(statePayload.redirectUrl,oauthService.successRedirectUrl,config.authentication?.trustedAppOrigins),portalOrigin);return ctx.set.status=302,ctx.set.headers.Location=`${pendingUrl}?requires_approval=true&provider=${provider}&session_id=${approvalSessionId}`,null}await logger2.audit({entityName:"users",entityId:userId,operation:"LOGIN",userId,summary:`${profile.email??profile.providerAccountId} logged in via OAuth (${provider})`,ipAddress,userAgent,path:`${basePath}/${provider}/callback`,query:""});let securePart=cookies.secure?"; Secure":"",domainPart=cookies.domain?`; Domain=${cookies.domain}`:"",cookieOptions=`; Path=${cookies.path}; HttpOnly; SameSite=${cookies.sameSite}${securePart}${domainPart}`,cookiesToSet=[];if(tokenConfig.accessToken.setHeadersEnabled)cookiesToSet.push(`${cookies.accessTokenName}=${accessToken}${cookieOptions}; Max-Age=${cookies.accessTokenMaxAge}`);if(tokenConfig.refreshToken.setHeadersEnabled)cookiesToSet.push(`${cookies.refreshTokenName}=${refreshToken}${cookieOptions}; Max-Age=${cookies.refreshTokenMaxAge}`);if(tokenConfig.sessionToken.setHeadersEnabled)cookiesToSet.push(`${cookies.sessionTokenName}=${sessionId}${cookieOptions}; Max-Age=${cookies.sessionTokenMaxAge}`);if(deviceTokenCookie)cookiesToSet.push(deviceTokenCookie);let successUrl=absolutizeRedirect(resolveSafeOAuthRedirect(statePayload.redirectUrl,oauthService.successRedirectUrl,config.authentication?.trustedAppOrigins),portalOrigin),headers=new Headers;headers.set("Location",successUrl);for(let cookie of cookiesToSet)headers.append("Set-Cookie",cookie);return new Response(null,{status:302,headers})}}),routeGetProviders=defineRoute({method:"GET",path:"/providers",detail:{tags:["Authentication"],summary:"List OAuth Providers",description:"Returns the list of enabled OAuth providers"},handler:()=>{return{success:!0,data:{providers:oauthService.getEnabledProviders()}}}}),routeGetLinkProvider=defineRoute({method:"GET",path:"/link/:provider",detail:{tags:["Authentication"],summary:"Link OAuth Account",description:"Redirects to provider to link an OAuth account to the current user"},handler:async(ctx)=>{let provider=ctx.params.provider;if(!oauthService.isProviderEnabled(provider))return ctx.set.status=404,{success:!1,message:`OAuth provider "${provider}" is not enabled`};if(!oauthService.allowAccountLinking)return ctx.set.status=403,{success:!1,message:"Account linking is disabled"};let userId=ctx.request.headers.get("x-user-id");if(!userId)return ctx.set.status=401,{success:!1,message:"Authentication required to link accounts"};let redirectUrl=ctx.query.redirect_url,authUrl=await oauthService.buildAuthorizationUrl(provider,userId,redirectUrl),issuedState=new URL(authUrl).searchParams.get("state");if(issuedState)ctx.set.headers["Set-Cookie"]=buildOAuthStateCookie(issuedState,{maxAge:600,secure:cookies.secure,domain:cookies.domain});return ctx.set.status=302,ctx.set.headers.Location=authUrl,null}}),routeDeleteUnlinkProvider=defineRoute({method:"DELETE",path:"/unlink/:provider",detail:{tags:["Authentication"],summary:"Unlink OAuth Account",description:"Removes a linked OAuth account from the current user"},handler:async(ctx)=>{let provider=ctx.params.provider,userId=ctx.request.headers.get("x-user-id");if(!userId)return ctx.set.status=401,{success:!1,message:"Authentication required"};if(!db)return ctx.set.status=500,{success:!1,message:"Database not configured"};let oauthTable=config.oauthAccountsTable;if(!oauthTable)return ctx.set.status=500,{success:!1,message:"OAuth accounts table not configured"};if((await db.select().from(oauthTable).where(and24(eq56(oauthTable.userId,userId),eq56(oauthTable.provider,provider))).limit(1)).length===0)return ctx.set.status=404,{success:!1,message:`No linked ${provider} account found`};let{usersTable:resolvedUsersTable}=resolveAuthTablesForRequest(ctx.request,config);if(!resolvedUsersTable)return ctx.set.status=500,{success:!1,message:"Users table not configured"};let hasPassword=!!(await db.select().from(resolvedUsersTable).where(eq56(resolvedUsersTable.id,userId)).limit(1))[0]?.password,allOAuthAccounts=await db.select().from(oauthTable).where(eq56(oauthTable.userId,userId));if(!hasPassword&&allOAuthAccounts.length<=1)return ctx.set.status=400,{success:!1,message:"Cannot unlink the only login method. Set a password first."};return await db.delete(oauthTable).where(and24(eq56(oauthTable.userId,userId),eq56(oauthTable.provider,provider))),logger2.info("[OAUTH] Account unlinked",{userId,provider}),{success:!0,message:`${provider} account unlinked successfully`}}}),routeGetAccounts=defineRoute({method:"GET",path:"/accounts",detail:{tags:["Authentication"],summary:"List Linked OAuth Accounts",description:"Returns all OAuth accounts linked to the current user"},handler:async(ctx)=>{let userId=ctx.request.headers.get("x-user-id");if(!userId)return ctx.set.status=401,{success:!1,message:"Authentication required"};if(!db)return ctx.set.status=500,{success:!1,message:"Database not configured"};let oauthTable=config.oauthAccountsTable;if(!oauthTable)return{success:!0,data:{accounts:[]}};return{success:!0,data:{accounts:(await db.select().from(oauthTable).where(eq56(oauthTable.userId,userId))).map((a)=>({id:a.id,provider:a.provider,providerEmail:a.providerEmail,providerName:a.providerName,providerAvatarUrl:a.providerAvatarUrl,isPrimary:a.isPrimary,lastUsedAt:a.lastUsedAt,createdAt:a.createdAt}))}}}});return collectRoutes([{prefix:basePath,routes:[routeGetProvider,routeGetProviderCallback,routeGetProviders,routeGetLinkProvider,routeDeleteUnlinkProvider,routeGetAccounts]}])}var OAUTH_STATE_COOKIE="oauth_state",readCookie=(request,name2)=>{let raw=request.headers.get("cookie");if(!raw)return;for(let part of raw.split(";")){let trimmed=part.trim(),eq57=trimmed.indexOf("=");if(eq57>0&&trimmed.slice(0,eq57)===name2)return trimmed.slice(eq57+1)}return},resolveSafeOAuthRedirect=(candidate,fallback,allowedOrigins)=>{if(!candidate)return fallback;let normalized=candidate.replace(/[\t\n\r]/g,"");if(!normalized)return fallback;if(normalized.startsWith("/")&&normalized[1]!=="/"&&normalized[1]!=="\\")return normalized;try{let candHost=new URL(normalized).host.toLowerCase(),trusted=[];try{trusted.push(new URL(fallback).host.toLowerCase())}catch{}for(let o of allowedOrigins??[])try{trusted.push(new URL(o.includes("://")?o:`https://${o}`).host.toLowerCase())}catch{}for(let t of trusted)if(t.startsWith("*.")){let base=t.slice(2);if(candHost===base||candHost.endsWith(`.${base}`))return normalized}else if(candHost===t)return normalized}catch{}return fallback},absolutizeRedirect=(target2,portalOrigin)=>portalOrigin&&target2.startsWith("/")&&target2[1]!=="/"?`${portalOrigin}${target2}`:target2;var init_oauth=__esm(()=>{init_DeviceTrust();init_routeCollector();init_assignDefaultRole();init_fetchUserRolesAndClaims();init_deviceInfo();init_utils7();init_utils8();init_applyProfileSync()});var PasswordChangeBodySchema,PasswordChangeResponseSchema;var init_types15=__esm(()=>{init_esm3();PasswordChangeBodySchema=Type.Object({currentPassword:Type.Optional(Type.String()),newPassword:Type.String({minLength:8}),confirmPassword:Type.String({minLength:8})}),PasswordChangeResponseSchema=Type.Object({success:Type.Boolean(),message:Type.Optional(Type.String())})});function validatePasswordsMatch(newPassword,confirmPassword){return newPassword===confirmPassword}var exports_store={};__export(exports_store,{userHasNoTrustedDevices:()=>userHasNoTrustedDevices,touchDevice:()=>touchDevice,revokeUserDevices:()=>revokeUserDevices,revokeDevicesBySessionIds:()=>revokeDevicesBySessionIds,revokeDeviceById:()=>revokeDeviceById,rejectDeviceForSession:()=>rejectDeviceForSession,listUserDevices:()=>listUserDevices,insertDeviceRow:()=>insertDeviceRow,findDeviceRowsByRawToken:()=>findDeviceRowsByRawToken,activateDeviceForSession:()=>activateDeviceForSession});import{and as and25,eq as eq57,inArray as inArray18}from"drizzle-orm";async function findDeviceRowsByRawToken(db,table,rawToken){let hash=hashDeviceToken(rawToken);return await db.select().from(table).where(eq57(col14(table,"tokenHash"),hash)).limit(2)}async function insertDeviceRow(db,table,v){let now=new Date;await db.insert(table).values({id:v.id,userId:v.userId,tokenHash:v.tokenHash,status:v.status,originSessionId:v.originSessionId??null,label:v.label??null,deviceFingerprint:v.deviceFingerprint??null,firstSeenIp:v.firstSeenIp??null,lastSeenIp:v.lastSeenIp??v.firstSeenIp??null,lastSeenAt:now,expiresAt:v.expiresAt,isActive:!0,createdAt:now,updatedAt:now})}async function activateDeviceForSession(db,table,sessionId){return(await db.update(table).set({status:"trusted",updatedAt:new Date}).where(and25(eq57(col14(table,"originSessionId"),sessionId),eq57(col14(table,"status"),"pending"))).returning({id:col14(table,"id")})).length}async function rejectDeviceForSession(db,table,sessionId){await db.update(table).set({status:"revoked",revokedAt:new Date,revokedReason:"replaced",isActive:!1,updatedAt:new Date}).where(and25(eq57(col14(table,"originSessionId"),sessionId),eq57(col14(table,"status"),"pending")))}async function touchDevice(db,table,id,ip){await db.update(table).set({lastSeenAt:new Date,lastSeenIp:ip??null,updatedAt:new Date}).where(eq57(col14(table,"id"),id))}async function revokeWhere(db,table,where,reason){await db.update(table).set({status:"revoked",revokedAt:new Date,revokedReason:reason,isActive:!1,updatedAt:new Date}).where(where)}async function revokeUserDevices(db,table,userId,reason){if(!userId)return;let notAlready=and25(eq57(col14(table,"userId"),userId),eq57(col14(table,"status"),"trusted")),alsoPending=and25(eq57(col14(table,"userId"),userId),eq57(col14(table,"status"),"pending"));await revokeWhere(db,table,notAlready,reason),await revokeWhere(db,table,alsoPending,reason)}async function revokeDevicesBySessionIds(db,table,sessionIds,reason){if(!sessionIds?.length)return;await revokeWhere(db,table,inArray18(col14(table,"originSessionId"),sessionIds),reason)}async function revokeDeviceById(db,table,id,userId,reason){return(await db.update(table).set({status:"revoked",revokedAt:new Date,revokedReason:reason,isActive:!1,updatedAt:new Date}).where(and25(eq57(col14(table,"id"),id),eq57(col14(table,"userId"),userId))).returning({id:col14(table,"id")})).length}async function listUserDevices(db,table,userId){return(await db.select().from(table).where(eq57(col14(table,"userId"),userId))).map((r)=>{let{tokenHash:_omit,...safe}=r;return safe})}async function userHasNoTrustedDevices(db,table,userId){return(await db.select({id:col14(table,"id")}).from(table).where(and25(eq57(col14(table,"userId"),userId),eq57(col14(table,"status"),"trusted"))).limit(1)).length===0}var col14=(t,name2)=>t[name2];var init_store3=__esm(()=>{init_DeviceTrust()});import{eq as eq58}from"drizzle-orm";function createPasswordChangeRoute(config,passwordChangeConfig){let{db,logger:logger2}=config,log4=logger2.scoped(AUTH_PASSWORD_CHANGE),route=passwordChangeConfig.route||"/auth/password-change";if(!passwordChangeConfig.enabled)return collectRoutes();let routePostRoute=defineRoute({method:"POST",path:"/",body:PasswordChangeBodySchema,detail:{tags:["Authentication"],summary:"Change Password",description:"Change password for authenticated user"},handler:async(ctx)=>{let{usersTable,sessionsTable}=resolveAuthTablesForRequest(ctx.request,config);if(!db||!usersTable)return ctx.set.status=500,{success:!1,message:"Database not configured"};let userId=ctx.request.headers.get("x-user-id");if(!userId)return ctx.set.status=401,{success:!1,message:"Authentication required"};let{currentPassword,newPassword,confirmPassword}=ctx.body;if(!validatePasswordsMatch(newPassword,confirmPassword))return ctx.set.status=422,{success:!1,message:"New passwords do not match"};let strength=validatePasswordStrength(newPassword,config.authentication?.passwordPolicy);if(!strength.valid)return ctx.set.status=400,{success:!1,message:"Password too weak",errors:strength.errors};let user=(await db.select().from(usersTable).where(eq58(usersTable.id,userId)).limit(1))[0];if(!user)return ctx.set.status=404,{success:!1,message:"User not found"};let identityError=identityRuleError(newPassword,config.authentication?.passwordPolicy,typeof user.email==="string"?user.email:void 0);if(identityError)return ctx.set.status=400,{success:!1,message:"Password too weak",errors:[identityError]};let existingHash=user.password??"",userHasPassword=existingHash.length>0,isInitialSet=!userHasPassword,reqUrl=new URL(ctx.request.url),auditIp=ctx.request.headers.get("x-forwarded-for")?.split(",")[0]?.trim()||ctx.request.headers.get("x-real-ip")?.trim()||"unknown",auditUa=ctx.request.headers.get("user-agent")||"unknown";if(userHasPassword){if(!currentPassword)return ctx.set.status=400,{success:!1,message:"Current password is required"};if(!await verifyPassword(currentPassword,existingHash))return log4.warn("Password change failed - invalid current password",{userId}),logger2.audit({entityName:"users",entityId:userId,operation:"PASSWORD_CHANGE_FAILED",userId,summary:"Password change failed: invalid current password",ipAddress:auditIp,userAgent:auditUa,path:reqUrl.pathname,query:reqUrl.search}),ctx.set.status=400,{success:!1,message:"Current password is incorrect"}}let hashedNewPassword=await hashPassword(newPassword);if(await db.update(usersTable).set({password:hashedNewPassword,updatedAt:new Date}).where(eq58(usersTable.id,userId)),!isInitialSet&&sessionsTable)try{await db.update(sessionsTable).set({isActive:!1,revokedAt:new Date,revokedReason:"password_change"}).where(eq58(sessionsTable.userId,userId))}catch(err){log4.warn("Failed to revoke sessions after password change",{userId,error:err instanceof Error?err.message:String(err)})}if(!isInitialSet&&config.authentication?.deviceTrust?.enabled)try{let{schemaTables}=resolveAuthTablesForRequest(ctx.request,config),dtTable=schemaTables?.trustedDevices??schemaTables?.trusted_devices;if(dtTable&&db){let{revokeUserDevices:revokeUserDevices2}=await Promise.resolve().then(() => (init_store3(),exports_store));await revokeUserDevices2(db,dtTable,userId,"password_reset")}}catch(err){log4.warn("Failed to revoke device tokens after password change",{userId,error:err instanceof Error?err.message:String(err)})}return log4.info(isInitialSet?"Initial password set successful":"Password change successful",{userId}),logger2.audit({entityName:"users",entityId:userId,operation:isInitialSet?"PASSWORD_SET":"PASSWORD_CHANGE",userId,summary:isInitialSet?"Initial password set successfully":"Password changed successfully",ipAddress:auditIp,userAgent:auditUa,path:reqUrl.pathname,query:reqUrl.search}),{success:!0,message:isInitialSet?"Password set successfully":"Password changed successfully"}}});return collectRoutes([{prefix:route,routes:[routePostRoute]}])}var init_passwordChange=__esm(()=>{init_passwordPolicy();init_scopes();init_routeCollector();init_utils7();init_utils6();init_types15();init_types15()});function describeResetEmail(outcome){if(!outcome||outcome.sent)return{sent:!0,level:"info",message:"Password reset email sent"};let base=SENTENCES[outcome.reason],detail=outcome.detail?.trim();return{sent:!1,level:outcome.reason==="exempt"?"info":"warn",message:detail?`${base} (${detail})`:base}}var SENTENCES;var init_outcome=__esm(()=>{SENTENCES={no_provider:"Password reset email NOT sent \u2014 no email provider is configured. The token was stored, so a link exists, but nothing carried it.",exempt:"Password reset email not sent \u2014 the address is on an exempt domain this install never mails.",rejected:"Password reset email NOT sent \u2014 the provider refused the recipient. The address is accepted at login but not deliverable.",error:"Password reset email NOT sent \u2014 the send failed."}});var PasswordResetRequestSchema,PasswordResetConfirmSchema,PasswordResetResponseSchema;var init_types16=__esm(()=>{init_esm3();PasswordResetRequestSchema=Type.Object({email:Type.String({format:"email"})}),PasswordResetConfirmSchema=Type.Object({token:Type.String(),newPassword:Type.String({minLength:8}),confirmPassword:Type.String({minLength:8})}),PasswordResetResponseSchema=Type.Object({success:Type.Boolean(),message:Type.Optional(Type.String())})});import{randomBytes as randomBytes7}from"crypto";function generateResetToken(){return randomBytes7(32).toString("hex")}function isTokenExpired(expiresAt){return new Date>expiresAt}var init_utils10=()=>{};function createPasswordResetRoute(config,passwordResetConfig,storeResetToken,getResetToken,deleteResetToken,sendResetEmail){let{db,logger:logger2}=config,log4=logger2.scoped(AUTH_PASSWORD_RESET),baseRoute=passwordResetConfig.route||"/auth/password-reset";if(!passwordResetConfig.enabled)return collectRoutes();let routePostBaserouteRequest=defineRoute({method:"POST",path:"/request",body:PasswordResetRequestSchema,detail:{tags:["Authentication"],summary:"Request Password Reset",description:"Request a password reset email"},handler:async(ctx)=>{let{usersTable}=resolveAuthTablesForRequest(ctx.request,config);if(!db||!usersTable)return ctx.set.status=500,{success:!1,message:"Database not configured"};let{email:rawEmail}=ctx.body,email=rawEmail.toLowerCase(),{eq:eq59}=await import("drizzle-orm"),user=(await db.select().from(usersTable).where(eq59(usersTable.email,email)).limit(1))[0];if(!user)return{success:!0,message:"If email exists, reset link will be sent"};let token=generateResetToken(),expiresAt=new Date(Date.now()+3600000),reqSchemaName=ctx.request.headers.get("x-tenant-schema")||void 0;await storeResetToken(user.id,token,expiresAt,reqSchemaName);let delivery={sent:!1,level:"warn",message:"Password reset email NOT sent \u2014 no send helper is configured."};if(sendResetEmail)try{delivery=describeResetEmail(await sendResetEmail(email,token,ctx.request))}catch(error3){delivery=describeResetEmail({sent:!1,reason:"error",detail:error3 instanceof Error?error3.message:String(error3)})}if(delivery.level==="warn")log4.warn(delivery.message,{email});else log4.info(delivery.message,{email});log4.info("Password reset requested",{userId:user.id,email});let reqUrl=new URL(ctx.request.url);return logger2.audit({entityName:"users",entityId:user.id,operation:"PASSWORD_RESET_REQUEST",userId:user.id,summary:`Password reset requested for ${email} \u2014 ${delivery.sent?"email sent":"EMAIL NOT SENT"}`,ipAddress:ctx.request.headers.get("x-forwarded-for")?.split(",")[0]?.trim()||ctx.request.headers.get("x-real-ip")?.trim()||"unknown",userAgent:ctx.request.headers.get("user-agent")||"unknown",path:reqUrl.pathname,query:reqUrl.search}),{success:!0,message:"If email exists, reset link will be sent"}}}),routePostBaserouteConfirm=defineRoute({method:"POST",path:"/confirm",body:PasswordResetConfirmSchema,detail:{tags:["Authentication"],summary:"Confirm Password Reset",description:"Reset password with token"},handler:async(ctx)=>{let{usersTable,sessionsTable}=resolveAuthTablesForRequest(ctx.request,config);if(!db||!usersTable)return ctx.set.status=500,{success:!1,message:"Database not configured"};let{token,newPassword,confirmPassword}=ctx.body;if(newPassword!==confirmPassword)return ctx.set.status=400,{success:!1,message:"Passwords do not match"};let strength=validatePasswordStrength(newPassword,config.authentication?.passwordPolicy);if(!strength.valid)return ctx.set.status=400,{success:!1,message:"Password too weak",errors:strength.errors};let reqSchemaName=ctx.request.headers.get("x-tenant-schema")||void 0,resetData=await getResetToken(token,reqSchemaName);if(!resetData)return ctx.set.status=400,{success:!1,message:"Invalid or expired reset token"};if(isTokenExpired(resetData.expiresAt))return await deleteResetToken(token,reqSchemaName),ctx.set.status=400,{success:!1,message:"Reset token has expired"};let{eq:eq59}=await import("drizzle-orm");if(config.authentication?.passwordPolicy?.preventUserInfoInPassword){let ownerEmail=(await db.select().from(usersTable).where(eq59(usersTable.id,resetData.userId)).limit(1))[0]?.email,identityError=identityRuleError(newPassword,config.authentication?.passwordPolicy,typeof ownerEmail==="string"?ownerEmail:void 0);if(identityError)return ctx.set.status=400,{success:!1,message:"Password too weak",errors:[identityError]}}let hashedPassword=await hashPassword(newPassword);if(await db.update(usersTable).set({password:hashedPassword}).where(eq59(usersTable.id,resetData.userId)),await deleteResetToken(token,reqSchemaName),sessionsTable)try{await db.update(sessionsTable).set({isActive:!1,revokedAt:new Date,revokedReason:"password_reset"}).where(eq59(sessionsTable.userId,resetData.userId))}catch(err){log4.warn("Failed to revoke sessions after password reset",{userId:resetData.userId,error:err instanceof Error?err.message:String(err)})}try{if(config.authentication?.deviceTrust?.enabled){let{schemaTables}=resolveAuthTablesForRequest(ctx.request,config),dtTable=schemaTables?.trustedDevices??schemaTables?.trusted_devices;if(dtTable&&db){let{revokeUserDevices:revokeUserDevices2}=await Promise.resolve().then(() => (init_store3(),exports_store));await revokeUserDevices2(db,dtTable,resetData.userId,"password_reset")}}}catch(err){log4.warn("Failed to revoke device tokens after password reset",{userId:resetData.userId,error:err instanceof Error?err.message:String(err)})}log4.info("Password reset successful",{userId:resetData.userId});let reqUrl=new URL(ctx.request.url);return logger2.audit({entityName:"users",entityId:resetData.userId,operation:"PASSWORD_RESET",userId:resetData.userId,summary:"Password reset completed successfully",ipAddress:ctx.request.headers.get("x-forwarded-for")?.split(",")[0]?.trim()||ctx.request.headers.get("x-real-ip")?.trim()||"unknown",userAgent:ctx.request.headers.get("user-agent")||"unknown",path:reqUrl.pathname,query:reqUrl.search}),{success:!0,message:"Password has been reset"}}});return collectRoutes([{prefix:baseRoute,routes:[routePostBaserouteRequest,routePostBaserouteConfirm]}])}var init_passwordReset=__esm(()=>{init_passwordPolicy();init_scopes();init_routeCollector();init_utils6();init_outcome();init_types16();init_utils10();init_types16()});var PasswordSetBodySchema,PasswordSetResponseSchema;var init_types17=__esm(()=>{init_esm3();PasswordSetBodySchema=Type.Object({newPassword:Type.String({minLength:8}),userId:Type.Optional(Type.String()),token:Type.Optional(Type.String())}),PasswordSetResponseSchema=Type.Object({success:Type.Boolean(),message:Type.String()})});import{eq as eq59}from"drizzle-orm";function createPasswordSetRoute(config,passwordSetConfig,getMagicToken,deleteMagicToken){let{db,logger:logger2}=config,log4=logger2.scoped(AUTH_PASSWORD_SET),route=passwordSetConfig.route||"/auth/password-set";if(!passwordSetConfig.enabled)return collectRoutes();let routePostRoute=defineRoute({method:"POST",path:"/",body:PasswordSetBodySchema,detail:{tags:["Authentication"],summary:"Set Password",description:"Set password for the first time (for invited users who do not have a password yet)"},handler:async(ctx)=>{log4.info("Password set request received");let{usersTable}=resolveAuthTablesForRequest(ctx.request,config);if(!db||!usersTable)return log4.error("Password set failed - database not configured"),ctx.set.status=500,{success:!1,message:"Database not configured"};let{newPassword,token}=ctx.body,strength=validatePasswordStrength(newPassword,config.authentication?.passwordPolicy);if(!strength.valid)return ctx.set.status=400,{success:!1,message:"Password too weak",errors:strength.errors};let userId=null;if(token&&getMagicToken&&deleteMagicToken){let reqSchemaName=ctx.request.headers.get("x-tenant-schema")||void 0,tokenHash=hashToken(token,TOKEN_PURPOSE.invite),storedToken=await getMagicToken(tokenHash,reqSchemaName);if(!storedToken)return log4.warn("Password set failed - invalid token"),ctx.set.status=400,{success:!1,message:"Invalid or expired token"};if(new Date>storedToken.expiresAt)return await deleteMagicToken(tokenHash,reqSchemaName),log4.warn("Password set failed - expired token",{email:storedToken.email}),ctx.set.status=400,{success:!1,message:"Invalid or expired token"};userId=storedToken.userId,log4.info("Password set - userId resolved from token",{userId,email:storedToken.email})}else userId=ctx.request.headers.get("x-user-id");if(!userId)return log4.warn("Password set failed - no token and no authenticated user"),ctx.set.status=401,{success:!1,message:"Valid token or authentication required"};let user=(await db.select().from(usersTable).where(eq59(usersTable.id,userId)).limit(1))[0];if(log4.info("Password set - user found",{found:!!user,hasPassword:!!user?.password}),!user)return ctx.set.status=404,{success:!1,message:"User not found"};let identityError=identityRuleError(newPassword,config.authentication?.passwordPolicy,typeof user.email==="string"?user.email:void 0);if(identityError)return ctx.set.status=400,{success:!1,message:"Password too weak",errors:[identityError]};if(user.password)return log4.warn("Password set failed - user already has password",{userId}),ctx.set.status=409,{success:!1,message:"Password already set. Use password change instead."};let hashedPassword=await hashPassword(newPassword);log4.info("Password set - updating user with verifiedAt",{userId});let updateResult=await db.update(usersTable).set({password:hashedPassword,verifiedAt:new Date,updatedAt:new Date}).where(eq59(usersTable.id,userId));if(log4.info("Password set successful for invited user",{userId,updateResult}),token&&deleteMagicToken){let setSchemaName=ctx.request.headers.get("x-tenant-schema")||void 0,tokenHash=hashToken(token,TOKEN_PURPOSE.invite);await deleteMagicToken(tokenHash,setSchemaName),log4.info("Invite token consumed after password set",{userId})}return{success:!0,message:"Password set successfully"}}});return collectRoutes([{prefix:route,routes:[routePostRoute]}])}var init_passwordSet=__esm(()=>{init_passwordPolicy();init_scopes();init_routeCollector();init_utils8();init_utils6();init_types17();init_types17()});function parseTimeToSeconds3(time2){let match=time2.match(/^(\d+)([smhd])$/);if(!match)return 900;let value2=match[1],unit=match[2];if(!value2||!unit)return 900;let num3=parseInt(value2,10);switch(unit){case"s":return num3;case"m":return num3*60;case"h":return num3*3600;case"d":return num3*86400;default:return 900}}var RefreshResponseSchema;var init_types18=__esm(()=>{init_esm3();RefreshResponseSchema=Type.Object({success:Type.Boolean(),message:Type.Optional(Type.String()),data:Type.Optional(Type.Object({accessToken:Type.String()}))})});import{eq as eq60}from"drizzle-orm";function createRefreshRoute(config,refreshConfig,verifyRefreshToken,signAccessToken,signRefreshToken,tokenResponseConfig,cookieMaxAgeBufferSeconds,cookieConfig){let{logger:logger2,authentication}=config,route=refreshConfig.route||"/auth/refresh";if(!refreshConfig.enabled)return collectRoutes();let routePostRoute=defineRoute({method:"POST",path:"/",detail:{tags:["Authentication"],summary:"Refresh Token",description:"Get a new access token using a refresh token. Browsers send it as the refresh cookie; a header-only client sends it as `Authorization: Bearer <refresh token>`."},handler:async(ctx)=>{let refreshTokenName=authentication?.refreshToken?.name||"refresh_token",accessTokenName=authentication?.accessToken?.name||"access_token",refreshToken=null,cookieHeader=ctx.request.headers.get("cookie");if(cookieHeader)refreshToken=cookieHeader.split(";").reduce((acc,cookie)=>{let trimmed=cookie.trim(),eqIndex=trimmed.indexOf("=");if(eqIndex>0)acc[trimmed.slice(0,eqIndex)]=trimmed.slice(eqIndex+1);return acc},{})[refreshTokenName]||null;if(!refreshToken){let authorization=ctx.request.headers.get("authorization")??"";refreshToken=/^Bearer\s+(.+)$/i.exec(authorization.trim())?.[1]?.trim()||null}if(!refreshToken)return logger2.debug("[AUTH] Refresh skipped - no refresh cookie or bearer token on the request"),ctx.set.status=401,{success:!1,message:"Refresh token required"};let result=verifyRefreshToken(refreshToken);if(!result.valid||!result.payload)return logger2.warn("[AUTH] Refresh failed - invalid token"),ctx.set.status=401,{success:!1,message:"Invalid refresh token"};let userId=result.payload.sub,resolved=resolveAuthTablesForRequest(ctx.request,config),statelessMode=authentication?.mode==="consumer",boundSessionId=typeof result.payload.sessionId==="string"?result.payload.sessionId:"",sessionRememberMe,supersededHash;if(!statelessMode){if(!boundSessionId)return logger2.warn("[AUTH] Refresh blocked - token carries no session",{userId}),ctx.set.status=401,{success:!1,message:"Refresh token is not bound to a session"};let session=await refreshSessionIsAlive({sessionId:boundSessionId,db:config.db,sessionsTable:resolved.sessionsTable});if(!session.alive)return logger2.warn("[AUTH] Refresh blocked - session is no longer active",{userId,sessionId:boundSessionId,reason:session.reason}),ctx.set.status=401,{success:!1,message:"Session is no longer active"};sessionRememberMe=session.rememberMe;let verdict=classifyRefreshToken({presentedHash:hashRefreshToken(refreshToken),currentHash:session.refreshTokenHash,previousHash:session.previousRefreshTokenHash,previousRotatedAt:session.previousRefreshTokenAt,nowMs:Date.now()});if(verdict.action==="revoke")return logger2.warn("[AUTH] Refresh token reuse detected - ending the session",{userId,sessionId:boundSessionId,reason:verdict.reason}),await deleteSession({sessionId:boundSessionId}).catch(()=>{return}),ctx.set.status=401,{success:!1,message:"Session is no longer active"};supersededHash=session.refreshTokenHash}let userRoles=[],userClaims=[],userClaimScopes={};if(config.db){try{let rc=await fetchUserRolesAndClaims(config.db,userId,resolved);userRoles=rc.roles,userClaims=rc.claims,userClaimScopes=rc.claimScopes}catch(roleError){logger2.error("[AUTH] Failed to fetch roles/claims during refresh",{userId,error:roleError instanceof Error?roleError.message:String(roleError)})}try{let usersTable=resolved.usersTable;if(usersTable){let user=(await config.db.select().from(usersTable).where(eq60(usersTable.id,userId)).limit(1))[0];if(!user||user.isActive===!1)return logger2.warn("[AUTH] Refresh blocked - account inactive",{userId}),ctx.set.status=401,{success:!1,message:"Account is not active"};let lockedUntilRaw=user.lockedUntil,lockedUntil=lockedUntilRaw?new Date(lockedUntilRaw):null;if(user.isLocked===!0&&(!lockedUntil||lockedUntil>new Date))return logger2.warn("[AUTH] Refresh blocked - account locked",{userId}),ctx.set.status=401,{success:!1,message:"Account is locked"};let userCohortId=user.cohortId;if(userCohortId){let cohortsTable=resolved.schemaTables?.userCohorts??resolved.schemaTables?.user_cohorts;if(cohortsTable){let cohortExpiresAt=(await config.db.select().from(cohortsTable).where(eq60(cohortsTable.id,userCohortId)).limit(1))[0]?.expiresAt;if(cohortExpiresAt&&new Date(cohortExpiresAt)<new Date)return logger2.warn("[AUTH] Refresh blocked - cohort expired",{userId,cohortId:userCohortId}),ctx.set.status=401,{success:!1,message:"Your access has expired. Please contact your administrator."}}}}}catch(cohortErr){logger2.error("[AUTH] Failed cohort expiry check during refresh",{userId,error:cohortErr instanceof Error?cohortErr.message:String(cohortErr)})}}let newAccessToken=signAccessToken(userId,userRoles.length>0?userRoles:void 0,userClaims.length>0?userClaims:void 0,ctx.request.headers.get("x-tenant-schema")??void 0,userClaimScopes,boundSessionId||void 0),newRefreshToken=signRefreshToken?signRefreshToken(userId,sessionRememberMe,boundSessionId||void 0):null;await recordIssuedRefreshToken(boundSessionId||void 0,newRefreshToken,{previousHash:supersededHash});let accessSetHeaders=tokenResponseConfig?.accessToken?.setHeadersEnabled??!0,accessReturnJson=tokenResponseConfig?.accessToken?.returnJson??!0,refreshSetHeaders=tokenResponseConfig?.refreshToken?.setHeadersEnabled??!0,refreshReturnJson=tokenResponseConfig?.refreshToken?.returnJson??!1,accessExpiresIn=authentication?.accessToken?.expiresIn||"15m",accessMaxAge=Math.max(0,parseTimeToSeconds3(accessExpiresIn)-(cookieMaxAgeBufferSeconds??0)),refreshTtl=longerLifetime(sessionRememberMe?authentication?.rememberMe?.expiresIn:void 0,authentication?.refreshToken?.expiresIn,604800),refreshMaxAge=cookieMaxAge(refreshTtl),headers=new Headers;headers.set("Content-Type","application/json");let cookiePath=cookieConfig?.path||"/",cookieSameSite=cookieConfig?.sameSite||"Strict",securePart=cookieConfig?.secure!==!1?"; Secure":"",domainPart=cookieConfig?.domain?`; Domain=${cookieConfig.domain}`:"",cookieOpts=`; Path=${cookiePath}; HttpOnly; SameSite=${cookieSameSite}${securePart}${domainPart}`;if(accessSetHeaders)headers.append("Set-Cookie",`${accessTokenName}=${newAccessToken}${cookieOpts}; Max-Age=${accessMaxAge}`);if(newRefreshToken&&refreshSetHeaders)headers.append("Set-Cookie",`${refreshTokenName}=${newRefreshToken}${cookieOpts}; Max-Age=${refreshMaxAge}`);logger2.info("[AUTH] Token refresh successful",{userId,rotatedRefreshToken:!!newRefreshToken});let responseData={};if(accessReturnJson)responseData.accessToken=newAccessToken;if(newRefreshToken&&refreshReturnJson)responseData.refreshToken=newRefreshToken;let jsonBody=JSON.stringify(withEnvelopeFlags({success:!0,data:responseData}));return new Response(jsonBody,{status:200,headers})}});return collectRoutes([{prefix:route,routes:[routePostRoute]}])}var init_refresh=__esm(()=>{init_Delete2();init_response();init_routeCollector();init_lifetime();init_fetchUserRolesAndClaims();init_recordIssued();init_reuseDetection();init_sessionBinding();init_types18()});var RegisterBodySchema,RegisterResponseSchema;var init_types19=__esm(()=>{init_esm3();RegisterBodySchema=Type.Object({email:Type.String({format:"email"}),password:Type.String({minLength:8}),confirmPassword:Type.Optional(Type.String({minLength:8})),firstName:Type.Optional(Type.String({maxLength:100})),lastName:Type.Optional(Type.String({maxLength:100})),createProfile:Type.Optional(Type.Boolean())}),RegisterResponseSchema=Type.Object({success:Type.Boolean(),message:Type.Optional(Type.String()),data:Type.Optional(Type.Object({user:Type.Object({id:Type.String(),email:Type.String()})}))})});import{eq as eq61}from"drizzle-orm";function createRegisterRoute(config,registerConfig,sendWelcomeEmail,signAccessToken,signRefreshToken,createSession,cookieConfig,tokenResponseConfig,getEmailService,appName,saveSessionToDb){let{db,logger:logger2}=config,log4=logger2.scoped(AUTH_REGISTER),route=registerConfig.route||"/auth/register",cookies={accessTokenName:cookieConfig?.accessTokenName||"access_token",refreshTokenName:cookieConfig?.refreshTokenName||"refresh_token",sessionTokenName:cookieConfig?.sessionTokenName||"session_token",accessTokenMaxAge:cookieConfig?.accessTokenMaxAge||900,refreshTokenMaxAge:cookieConfig?.refreshTokenMaxAge||604800,sessionTokenMaxAge:cookieConfig?.sessionTokenMaxAge||900,secure:cookieConfig?.secure??!0,httpOnly:cookieConfig?.httpOnly??!0,sameSite:cookieConfig?.sameSite||"strict",path:cookieConfig?.path||"/",domain:cookieConfig?.domain};if(!registerConfig.enabled)return collectRoutes();let routePostRoute=defineRoute({method:"POST",path:"/",body:RegisterBodySchema,detail:{tags:["Authentication"],summary:"Register",description:"Register a new user account"},handler:async(ctx)=>{let resolved=resolveAuthTablesForRequest(ctx.request,config),usersTable=resolved.usersTable;if(!db||!usersTable)return ctx.set.status=500,{success:!1,message:"Database not configured"};let{email:rawEmail,password:password4,confirmPassword,firstName,lastName,createProfile}=ctx.body,email=rawEmail.toLowerCase();if(confirmPassword&&password4!==confirmPassword)return ctx.set.status=400,{success:!1,message:"Passwords do not match"};let passwordValidation=validatePasswordStrength(password4,config.authentication?.passwordPolicy,email);if(!passwordValidation.valid)return ctx.set.status=400,{success:!1,message:"Password too weak",errors:passwordValidation.errors};let existingUsers=await db.select().from(usersTable).where(eq61(usersTable.email,email)).limit(1),reqUrl=new URL(ctx.request.url),auditIp=ctx.request.headers.get("x-forwarded-for")?.split(",")[0]?.trim()||ctx.request.headers.get("x-real-ip")?.trim()||"unknown",auditUa=ctx.request.headers.get("user-agent")||"unknown";if(existingUsers.length>0){log4.warn("Registration failed - email exists",{email}),logger2.audit({entityName:"users",operation:"REGISTER_FAILED",summary:`Registration failed: email already exists (${email})`,ipAddress:auditIp,userAgent:auditUa,path:reqUrl.pathname,query:reqUrl.search});let verifGatesSignup=registerConfig.emailVerification?.enabled&&getEmailService?.()?.isAvailable();if(config.authentication?.preventUserEnumeration&&verifGatesSignup)return{success:!0,message:"Registration successful. Please check your email to verify your account."};return ctx.set.status=409,{success:!1,message:"Email already registered"}}let hashedPassword=await hashPassword(password4),domainExempt=isEmailExempt(email,config.authentication?.emailExemptDomains),emailService=getEmailService?.()??null,emailVerificationEnabled=!domainExempt&®isterConfig.emailVerification?.enabled&&emailService?.isAvailable(),verificationToken=emailVerificationEnabled?generateVerificationToken():null,tokenExpiresIn=registerConfig.emailVerification?.tokenExpiresIn||"24h",tokenExpiresAt=emailVerificationEnabled?new Date(Date.now()+parseTimeToMs(tokenExpiresIn)):null,insertValues={email,password:hashedPassword};if(emailVerificationEnabled&&verificationToken)insertValues.emailVerificationToken=hashVerificationToken(verificationToken),insertValues.emailVerificationTokenExpiresAt=tokenExpiresAt,insertValues.emailVerificationSentAt=new Date,insertValues.emailVerificationAttempts=1;let newUser=(await db.insert(usersTable).values(insertValues).returning())[0],registerDefaultRoles=resolveDefaultRoleNames(config.authentication);if(registerDefaultRoles.length>0)await assignDefaultRoles({db,userId:newUser.id,roleNames:registerDefaultRoles,rolesTable:resolved.rolesTable??config.rolesTable??null,userRolesTable:resolved.userRolesTable??config.userRolesTable??null,logger:logger2});let wantsProfile=registerConfig.createProfileOnRegister||createProfile,profilesTable=resolved.schemaTables?.profiles;if(wantsProfile&&profilesTable&&(firstName||lastName))try{let profileNow=new Date;await db.insert(profilesTable).values({userId:newUser.id,firstName:firstName??"",lastName:lastName??"",createdAt:profileNow,updatedAt:profileNow})}catch(err){log4.warn("Failed to create profile on register \u2014 continuing",{userId:newUser.id,error:err instanceof Error?err.message:String(err)})}if(log4.info("Registration successful",{userId:newUser.id,email,emailVerificationEnabled}),logger2.audit({entityName:"users",entityId:newUser.id,operation:"REGISTER",userId:newUser.id,summary:`New user registered: ${email}`,ipAddress:auditIp,userAgent:auditUa,path:reqUrl.pathname,query:reqUrl.search}),emailVerificationEnabled&&emailService&&verificationToken){let verificationLink=buildEmailActionLink({request:ctx.request,configuredUrl:registerConfig.emailVerification?.redirectUrl,path:"/verify-email",query:{token:verificationToken},allowedOrigins:config.authentication?.trustedAppOrigins});emailService.sendVerificationEmail(email,email.split("@")[0]||"User",verificationLink,appName||"Nucleus",config.authentication?.emailVerificationTemplates?.verification?.subject).then((result2)=>{if(result2.success)log4.info("Verification email sent",{email});else log4.error("Failed to send verification email",{email,error:result2.error})}).catch((err)=>{log4.error("Failed to send verification email",{email,error:err})});let resendCooldown=registerConfig.emailVerification?.resendCooldown||"60s",cooldownSeconds=parseTimeToMs(resendCooldown)/1000,maxResendAttempts=registerConfig.emailVerification?.maxResendAttempts||3;if(config.authentication?.preventUserEnumeration)return{success:!0,message:"Registration successful. Please check your email to verify your account."};return{success:!0,message:"Registration successful. Please check your email to verify your account.",data:{user:{id:newUser.id,email:newUser.email},emailVerificationRequired:!0,verification:{cooldownSeconds,canResendAt:new Date(Date.now()+cooldownSeconds*1000).toISOString(),attemptsRemaining:maxResendAttempts-1,maxAttempts:maxResendAttempts}}}}if(sendWelcomeEmail)sendWelcomeEmail(email,firstName||email.split("@")[0]||"User").catch((err)=>{log4.error("Failed to send welcome email",{email,error:err})});if(signAccessToken&&signRefreshToken&&createSession){let ipAddress=ctx.request.headers.get("x-forwarded-for")?.split(",")[0]?.trim()||ctx.request.headers.get("x-real-ip")?.trim()||"unknown",userAgent=ctx.request.headers.get("user-agent")||"",sessionParams={userId:newUser.id,deviceInfo:ensureDeviceInfo({ipAddress,userAgent},deviceContextFromRequest(ctx.request).clientHints),loginMethod:"register",rememberMe:!0},sessionId=await createSession(sessionParams),accessToken=signAccessToken(newUser.id,void 0,void 0,ctx.request.headers.get("x-tenant-schema")??void 0,void 0,sessionId),refreshToken=signRefreshToken(newUser.id,void 0,sessionId),deviceTokenCookie;if(saveSessionToDb){let reqSchemaName=ctx.request.headers.get("x-tenant-schema")||void 0,dtCfg=config.authentication?.deviceTrust;if(dtCfg?.enabled)sessionParams.deviceToken=readDeviceTokenFromCookieHeader(ctx.request.headers.get("cookie"),dtCfg);deviceTokenCookie=(await saveSessionToDb(sessionId,sessionParams,reqSchemaName))?.deviceTokenCookie}let tokenConfig={accessToken:{setHeadersEnabled:tokenResponseConfig?.accessToken?.setHeadersEnabled??!0,returnJson:tokenResponseConfig?.accessToken?.returnJson??!0},refreshToken:{setHeadersEnabled:tokenResponseConfig?.refreshToken?.setHeadersEnabled??!0,returnJson:tokenResponseConfig?.refreshToken?.returnJson??!0},sessionToken:{setHeadersEnabled:tokenResponseConfig?.sessionToken?.setHeadersEnabled??!0,returnJson:tokenResponseConfig?.sessionToken?.returnJson??!0}},securePart=cookies.secure?"; Secure":"",domainPart=cookies.domain?`; Domain=${cookies.domain}`:"",cookieOptions=`; Path=${cookies.path}; HttpOnly; SameSite=${cookies.sameSite}${securePart}${domainPart}`,cookiesToSet=[];if(tokenConfig.accessToken.setHeadersEnabled)cookiesToSet.push(`${cookies.accessTokenName}=${accessToken}${cookieOptions}; Max-Age=${cookies.accessTokenMaxAge}`);if(tokenConfig.refreshToken.setHeadersEnabled)cookiesToSet.push(`${cookies.refreshTokenName}=${refreshToken}${cookieOptions}; Max-Age=${cookies.refreshTokenMaxAge}`);if(tokenConfig.sessionToken.setHeadersEnabled)cookiesToSet.push(`${cookies.sessionTokenName}=${sessionId}${cookieOptions}; Max-Age=${cookies.sessionTokenMaxAge}`);if(deviceTokenCookie)cookiesToSet.push(deviceTokenCookie);let responseData={user:{id:newUser.id,email:newUser.email}};if(tokenConfig.accessToken.returnJson)responseData.accessToken=accessToken;if(tokenConfig.refreshToken.returnJson)responseData.refreshToken=refreshToken;if(tokenConfig.sessionToken.returnJson)responseData.sessionId=sessionId;let jsonBody=JSON.stringify(withEnvelopeFlags({success:!0,data:responseData})),headers=new Headers;headers.set("Content-Type","application/json"),headers.set("x-session-id",sessionId);for(let cookie of cookiesToSet)headers.append("Set-Cookie",cookie);return new Response(jsonBody,{status:200,headers})}return{success:!0,data:{user:{id:newUser.id,email:newUser.email}}}}});return collectRoutes([{prefix:route,routes:[routePostRoute]}])}var init_register=__esm(()=>{init_DeviceTrust();init_scopes();init_response();init_routeCollector();init_assignDefaultRole();init_deviceInfo();init_types19();init_utils6();init_types19()});var SessionResponseSchema,SessionListResponseSchema,RevokeSessionBodySchema,RevokeAllSessionsBodySchema,SessionActivityResponseSchema,AdminRevokeSessionsBodySchema;var init_types20=__esm(()=>{init_esm3();SessionResponseSchema=Type.Object({id:Type.String(),deviceName:Type.Optional(Type.String()),deviceType:Type.Optional(Type.String()),browserName:Type.Optional(Type.String()),browserVersion:Type.Optional(Type.String()),osName:Type.Optional(Type.String()),osVersion:Type.Optional(Type.String()),ipAddress:Type.String(),locationCountry:Type.Optional(Type.String()),locationCity:Type.Optional(Type.String()),lastActivityAt:Type.String(),createdAt:Type.String(),isCurrent:Type.Boolean(),loginMethod:Type.Optional(Type.String()),trustScore:Type.Optional(Type.Number())}),SessionListResponseSchema=Type.Object({success:Type.Boolean(),data:Type.Optional(Type.Object({sessions:Type.Array(SessionResponseSchema),currentSessionId:Type.Optional(Type.String()),totalCount:Type.Number()})),message:Type.Optional(Type.String())}),RevokeSessionBodySchema=Type.Object({reason:Type.Optional(Type.String())}),RevokeAllSessionsBodySchema=Type.Object({excludeCurrent:Type.Optional(Type.Boolean()),reason:Type.Optional(Type.String())}),SessionActivityResponseSchema=Type.Object({success:Type.Boolean(),data:Type.Optional(Type.Object({recentActivity:Type.Array(Type.Object({sessionId:Type.String(),action:Type.String(),ipAddress:Type.String(),timestamp:Type.String(),deviceInfo:Type.Optional(Type.String())}))})),message:Type.Optional(Type.String())}),AdminRevokeSessionsBodySchema=Type.Optional(Type.Object({sessionId:Type.Optional(Type.String()),reason:Type.Optional(Type.String())}))});function formatSessionForResponse(session){let get=(snake,camel3)=>session[snake]??session[camel3],getDate=(snake,camel3)=>{let val=session[snake]??session[camel3];if(!val)return;return val instanceof Date?val.toISOString():String(val)};return{id:session.id,deviceName:get("device_name","deviceName"),deviceType:get("device_type","deviceType"),deviceFingerprint:get("device_fingerprint","deviceFingerprint"),browserName:get("browser_name","browserName"),browserVersion:get("browser_version","browserVersion"),osName:get("os_name","osName"),osVersion:get("os_version","osVersion"),ipAddress:get("ip_address","ipAddress"),locationCountry:get("location_country","locationCountry"),locationCity:get("location_city","locationCity"),lastActivityAt:getDate("last_activity_at","lastActivityAt"),createdAt:getDate("created_at","createdAt"),isCurrent:get("is_current","isCurrent"),loginMethod:get("login_method","loginMethod"),trustScore:get("trust_score","trustScore")}}var init_utils11=()=>{};import{and as and26,desc as desc6,eq as eq62,isNull as isNull5}from"drizzle-orm";function createSessionsRoute(config,sessionsConfig,sessionsTable){let{db,logger:logger2}=config,log4=logger2.scoped(AUTH_SESSION),baseRoute=sessionsConfig.route||"/auth/sessions";if(!sessionsConfig.enabled||!sessionsTable)return collectRoutes();let tableColumns=sessionsTable,_col=(name2)=>{let snakeCase=name2.replace(/([A-Z])/g,"_$1").toLowerCase();return tableColumns[name2]||tableColumns[snakeCase]},getSessionsForRequest=(request)=>{let resolved=resolveAuthTablesForRequest(request,config),reqTable=resolved.sessionsTable||sessionsTable,reqCols=reqTable,reqCol=(name2)=>{let snakeCase=name2.replace(/([A-Z])/g,"_$1").toLowerCase();return reqCols[name2]||reqCols[snakeCase]},trustedDevicesTable=resolved.schemaTables?.trustedDevices??resolved.schemaTables?.trusted_devices;return{sessionsTable:reqTable,col:reqCol,trustedDevicesTable}},routeGetRoot=defineRoute({method:"GET",path:"/",detail:{tags:["Authentication"],summary:"List active sessions",description:"Get all active sessions for the current user"},handler:async(ctx)=>{let{sessionsTable:sessionsTable2,col:col15}=getSessionsForRequest(ctx.request);if(!db)return ctx.set.status=500,{success:!1,message:"Database not configured"};let userId=ctx.request.headers.get("x-user-id");if(!userId)return ctx.set.status=401,{success:!1,message:"Authentication required"};let currentSessionId=ctx.request.headers.get("x-session-id"),sessions=await db.select().from(sessionsTable2).where(and26(eq62(col15("userId"),userId),eq62(col15("isActive"),!0),isNull5(col15("revokedAt")))).orderBy(desc6(col15("lastActivityAt"))),realSessions=sessions.filter((s)=>{let sess=s,fp=(sess.deviceFingerprint||"").toLowerCase(),ip=sess.ipAddress||"";return!((!fp||fp==="--"||fp==="--unknown"||fp.includes("bot/crawler")||fp.includes("headless")||fp.includes("unknown-unknown"))&&(ip==="127.0.0.1"||ip==="::1"||ip==="localhost"||!ip))}),formattedSessions=realSessions.map((s)=>{let session=s,formatted=formatSessionForResponse(session);return formatted.isCurrent=session.id===currentSessionId,formatted});return log4.info("Sessions list retrieved",{userId,totalInDb:sessions.length,filteredCount:realSessions.length,hiddenBotSessions:sessions.length-realSessions.length}),{success:!0,data:{sessions:formattedSessions,currentSessionId,totalCount:realSessions.length}}}}),routeGetCurrent=defineRoute({method:"GET",path:"/current",detail:{tags:["Authentication"],summary:"Get current session",description:"Get details of the current session"},handler:async(ctx)=>{let{sessionsTable:sessionsTable2,col:col15}=getSessionsForRequest(ctx.request);if(!db)return ctx.set.status=500,{success:!1,message:"Database not configured"};let userId=ctx.request.headers.get("x-user-id"),sessionId=ctx.request.headers.get("x-session-id");if(!userId||!sessionId)return ctx.set.status=401,{success:!1,message:"Authentication required"};let sessions=await db.select().from(sessionsTable2).where(eq62(col15("id"),sessionId)).limit(1);if(sessions.length===0)return ctx.set.status=404,{success:!1,message:"Session not found"};let session=sessions[0],formatted=formatSessionForResponse(session);return formatted.isCurrent=!0,{success:!0,data:formatted}}}),routeDeleteSessionid=defineRoute({method:"DELETE",path:"/:sessionId",body:RevokeSessionBodySchema,detail:{tags:["Authentication"],summary:"Revoke session",description:"Revoke a specific session by ID"},handler:async(ctx)=>{let{sessionsTable:sessionsTable2,col:col15}=getSessionsForRequest(ctx.request);if(!db)return ctx.set.status=500,{success:!1,message:"Database not configured"};let userId=ctx.request.headers.get("x-user-id");if(!userId)return ctx.set.status=401,{success:!1,message:"Authentication required"};let{sessionId}=ctx.params,body=ctx.body,currentSessionId=ctx.request.headers.get("x-session-id");if((await db.select().from(sessionsTable2).where(and26(eq62(col15("id"),sessionId),eq62(col15("userId"),userId))).limit(1)).length===0)return ctx.set.status=404,{success:!1,message:"Session not found"};let isCurrentSession=sessionId===currentSessionId;await db.update(sessionsTable2).set({isActive:!1,revokedAt:new Date,revokedReason:isCurrentSession?"user_logout":body.reason||"user_revoked"}).where(eq62(col15("id"),sessionId)),log4.info("Session revoked",{userId,sessionId,isCurrentSession,reason:body.reason||"user_revoked"});let reqUrl=new URL(ctx.request.url);return logger2.audit({entityName:"user_sessions",entityId:sessionId,operation:isCurrentSession?"LOGOUT":"SESSION_REVOKE",userId:userId||void 0,summary:isCurrentSession?"User logged out via session revoke":`Session revoked (${sessionId.substring(0,8)}...)`,ipAddress:ctx.request.headers.get("x-forwarded-for")?.split(",")[0]?.trim()||ctx.request.headers.get("x-real-ip")?.trim()||"unknown",userAgent:ctx.request.headers.get("user-agent")||"unknown",path:reqUrl.pathname,query:reqUrl.search}),{success:!0,message:isCurrentSession?"Logged out successfully":"Session revoked successfully"}}}),routeDeleteAll=defineRoute({method:"DELETE",path:"/all",body:RevokeAllSessionsBodySchema,detail:{tags:["Authentication"],summary:"Revoke all sessions",description:"Revoke all sessions for the current user (optionally exclude current)"},handler:async(ctx)=>{let{sessionsTable:sessionsTable2,col:col15}=getSessionsForRequest(ctx.request);if(!db)return ctx.set.status=500,{success:!1,message:"Database not configured"};let userId=ctx.request.headers.get("x-user-id");if(!userId)return ctx.set.status=401,{success:!1,message:"Authentication required"};let body=ctx.body,currentSessionId=ctx.request.headers.get("x-session-id"),query=db.update(sessionsTable2).set({isActive:!1,revokedAt:new Date,revokedReason:body.reason||"user_revoked"}).where(and26(eq62(col15("userId"),userId),eq62(col15("isActive"),!0)));if(body.excludeCurrent&¤tSessionId){let{ne:ne3}=await import("drizzle-orm");query=db.update(sessionsTable2).set({isActive:!1,revokedAt:new Date,revokedReason:body.reason||"user_revoked"}).where(and26(eq62(col15("userId"),userId),eq62(col15("isActive"),!0),ne3(col15("id"),currentSessionId)))}await query,log4.info("All sessions revoked",{userId,excludeCurrent:body.excludeCurrent,reason:body.reason||"user_revoked"});let reqUrl=new URL(ctx.request.url);return logger2.audit({entityName:"user_sessions",operation:"SESSION_REVOKE_ALL",userId:userId||void 0,summary:body.excludeCurrent?"All other sessions revoked":"All sessions revoked",ipAddress:ctx.request.headers.get("x-forwarded-for")?.split(",")[0]?.trim()||ctx.request.headers.get("x-real-ip")?.trim()||"unknown",userAgent:ctx.request.headers.get("user-agent")||"unknown",path:reqUrl.pathname,query:reqUrl.search}),{success:!0,message:body.excludeCurrent?"All other sessions revoked successfully":"All sessions revoked successfully"}}}),routeGetStats=defineRoute({method:"GET",path:"/stats",detail:{tags:["Authentication"],summary:"Session statistics",description:"Get session statistics for the current user"},handler:async(ctx)=>{let{sessionsTable:sessionsTable2,col:col15}=getSessionsForRequest(ctx.request);if(!db)return ctx.set.status=500,{success:!1,message:"Database not configured"};let userId=ctx.request.headers.get("x-user-id");if(!userId)return ctx.set.status=401,{success:!1,message:"Authentication required"};let{count:count3,countDistinct}=await import("drizzle-orm"),activeSessions=await db.select({count:count3()}).from(sessionsTable2).where(and26(eq62(col15("userId"),userId),eq62(col15("isActive"),!0),isNull5(col15("revokedAt")))),uniqueDevices=await db.select({count:countDistinct(col15("deviceFingerprint"))}).from(sessionsTable2).where(and26(eq62(col15("userId"),userId),eq62(col15("isActive"),!0))),uniqueIps=await db.select({count:countDistinct(col15("ipAddress"))}).from(sessionsTable2).where(and26(eq62(col15("userId"),userId),eq62(col15("isActive"),!0)));return{success:!0,data:{activeSessions:activeSessions[0]?.count||0,uniqueDevices:uniqueDevices[0]?.count||0,uniqueIpAddresses:uniqueIps[0]?.count||0}}}}),APPROVAL_TOKEN_TTL_MS=86400000,buildResultHtml=(action,success,message)=>{let icon=success?action==="approve"?"\u2705":"\uD83D\uDEAB":"\u274C",title=success?action==="approve"?"Device Approved":"Device Rejected":"Action Failed",bName=sessionsConfig.brandName||"Nucleus";return`<!DOCTYPE html>
|
|
453
453
|
<html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1.0">
|
|
454
454
|
<title>${title} \u2014 ${bName}</title>
|
|
455
455
|
<style>*{box-sizing:border-box}body{margin:0;padding:0;background-color:#f4f4f5;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;}</style>
|
|
@@ -522,7 +522,7 @@ ${strMDSAlgs}`)}if(attestationStatementAlg!==void 0&&authenticatorGetInfo?.algor
|
|
|
522
522
|
</table>
|
|
523
523
|
</td></tr>
|
|
524
524
|
</table>
|
|
525
|
-
</body></html>`},routeGetApprovePage=defineRoute({method:"GET",path:"/approve-page",detail:{tags:["Authentication"],summary:"Approval page",description:"Standalone HTML page for approving a device (no frontend auth required)"},handler:async(ctx)=>{let{sessionsTable:sessionsTable2,col:col15}=getSessionsForRequest(ctx.request);if(!db)return new Response("Service unavailable",{status:503});let token=new URL(ctx.request.url).searchParams.get("token");if(!token){let html2=buildApprovalPageHtml({},"approve","","invalid");return new Response(html2,{status:400,headers:{"Content-Type":"text/html; charset=utf-8"}})}let sessions=await db.select().from(sessionsTable2).where(eq62(col15("approvalToken"),token)).limit(1);if(sessions.length===0){let html2=buildApprovalPageHtml({},"approve",token,"invalid");return new Response(html2,{status:404,headers:{"Content-Type":"text/html; charset=utf-8"}})}let session=sessions[0];if(session.approvalStatus!=="pending"&&session.approval_status!=="pending"){let html2=buildApprovalPageHtml(session,"approve",token,"already_processed");return new Response(html2,{status:200,headers:{"Content-Type":"text/html; charset=utf-8"}})}let requestedAt=session.approvalRequestedAt||session.approval_requested_at;if(requestedAt&&Date.now()-new Date(requestedAt).getTime()>APPROVAL_TOKEN_TTL_MS){let html2=buildApprovalPageHtml(session,"approve",token,"expired");return new Response(html2,{status:410,headers:{"Content-Type":"text/html; charset=utf-8"}})}let html=buildApprovalPageHtml(session,"approve",token,"pending");return new Response(html,{status:200,headers:{"Content-Type":"text/html; charset=utf-8"}})}}),routeGetRejectPage=defineRoute({method:"GET",path:"/reject-page",detail:{tags:["Authentication"],summary:"Rejection page",description:"Standalone HTML page for rejecting a device (no frontend auth required)"},handler:async(ctx)=>{let{sessionsTable:sessionsTable2,col:col15}=getSessionsForRequest(ctx.request);if(!db)return new Response("Service unavailable",{status:503});let token=new URL(ctx.request.url).searchParams.get("token");if(!token){let html2=buildApprovalPageHtml({},"reject","","invalid");return new Response(html2,{status:400,headers:{"Content-Type":"text/html; charset=utf-8"}})}let sessions=await db.select().from(sessionsTable2).where(eq62(col15("approvalToken"),token)).limit(1);if(sessions.length===0){let html2=buildApprovalPageHtml({},"reject",token,"invalid");return new Response(html2,{status:404,headers:{"Content-Type":"text/html; charset=utf-8"}})}let session=sessions[0];if(session.approvalStatus!=="pending"&&session.approval_status!=="pending"){let html2=buildApprovalPageHtml(session,"reject",token,"already_processed");return new Response(html2,{status:200,headers:{"Content-Type":"text/html; charset=utf-8"}})}let requestedAt=session.approvalRequestedAt||session.approval_requested_at;if(requestedAt&&Date.now()-new Date(requestedAt).getTime()>APPROVAL_TOKEN_TTL_MS){let html2=buildApprovalPageHtml(session,"reject",token,"expired");return new Response(html2,{status:410,headers:{"Content-Type":"text/html; charset=utf-8"}})}let html=buildApprovalPageHtml(session,"reject",token,"pending");return new Response(html,{status:200,headers:{"Content-Type":"text/html; charset=utf-8"}})}}),routeGetApprovalStatus=defineRoute({method:"GET",path:"/approval-status",detail:{tags:["Authentication"],summary:"Check approval status",description:"Check the approval status of a pending session"},handler:async(ctx)=>{let{sessionsTable:sessionsTable2,col:col15}=getSessionsForRequest(ctx.request);if(!db)return ctx.set.status=500,{success:!1,message:"Database not configured"};let sessionId=new URL(ctx.request.url).searchParams.get("sessionId");if(!sessionId)return ctx.set.status=400,{success:!1,message:"sessionId is required"};let sessions=await db.select().from(sessionsTable2).where(eq62(col15("id"),sessionId)).limit(1);if(sessions.length===0)return ctx.set.status=404,{success:!1,message:"Session not found"};let session=sessions[0];return{success:!0,data:{approvalStatus:session.approvalStatus||session.approval_status||"unknown",isActive:session.isActive||session.is_active}}}}),routeGetDevices=defineRoute({method:"GET",path:"/devices",detail:{tags:["Authentication"],summary:"List remembered devices",description:"List the caller's remembered (device-trust) devices; never returns the token."},handler:async(ctx)=>{let userId=ctx.request.headers.get("x-user-id");if(!userId)return ctx.set.status=401,{success:!1,message:"Authentication required"};let dtTable=getSessionsForRequest(ctx.request).trustedDevicesTable;if(!dtTable||!db)return{success:!0,data:{items:[]}};let{listUserDevices:listUserDevices2}=await Promise.resolve().then(() => (init_store3(),exports_store));return{success:!0,data:{items:await listUserDevices2(db,dtTable,userId)}}}}),routeDeleteDevicesId=defineRoute({method:"DELETE",path:"/devices/:id",detail:{tags:["Authentication"],summary:"Revoke a remembered device",description:"Owner-scoped revoke of one remembered device (untrusts it)."},handler:async(ctx)=>{let userId=ctx.request.headers.get("x-user-id");if(!userId)return ctx.set.status=401,{success:!1,message:"Authentication required"};let id=ctx.params.id,dtTable=getSessionsForRequest(ctx.request).trustedDevicesTable;if(!dtTable||!db)return ctx.set.status=503,{success:!1,message:"Device trust not enabled"};let{revokeDeviceById:revokeDeviceById2}=await Promise.resolve().then(() => (init_store3(),exports_store));if(await revokeDeviceById2(db,dtTable,id,userId,"user_revoked")===0)return ctx.set.status=404,{success:!1,message:"Device not found"};return log4.info("Remembered device revoked",{userId,deviceId:id}),{success:!0,message:"Device revoked"}}}),adminGuard=createGodminGuard({logger:logger2,db,schemaTables:config.schemaTables??{},area:"session management",allowedRoles:sessionsConfig.adminRoles??[]}),routeGetAdminUserid=defineRoute({method:"GET",path:"/admin/:userId",guards:[adminGuard],detail:{tags:["Authentication"],summary:"List another user's active sessions (admin)",description:"Godmin or a role named in authentication.sessions.adminRoles. Used for offboarding and lost-device response."},handler:async(ctx)=>{let{sessionsTable:sessionsTable2,col:col15}=getSessionsForRequest(ctx.request);if(!db)return ctx.set.status=500,{success:!1,message:"Database not configured"};let{userId}=ctx.params,sessions=await db.select().from(sessionsTable2).where(and26(eq62(col15("userId"),userId),eq62(col15("isActive"),!0),isNull5(col15("revokedAt")))).orderBy(desc6(col15("lastActivityAt")));return{success:!0,data:{sessions:sessions.map((session)=>formatSessionForResponse(session)),totalCount:sessions.length}}}}),routeDeleteAdminUserid=defineRoute({method:"DELETE",path:"/admin/:userId",guards:[adminGuard],body:AdminRevokeSessionsBodySchema,detail:{tags:["Authentication"],summary:"End another user's sessions (admin)",description:"Ends every active session of the given user, or just one when `sessionId` is supplied. Godmin or authentication.sessions.adminRoles."},handler:async(ctx)=>{let{sessionsTable:sessionsTable2,col:col15}=getSessionsForRequest(ctx.request);if(!db)return ctx.set.status=500,{success:!1,message:"Database not configured"};let{userId}=ctx.params,body=ctx.body??{},actorId=ctx.request.headers.get("x-user-id")||void 0,target2=body.sessionId?and26(eq62(col15("id"),body.sessionId),eq62(col15("userId"),userId),eq62(col15("isActive"),!0)):and26(eq62(col15("userId"),userId),eq62(col15("isActive"),!0)),affected=await db.update(sessionsTable2).set({isActive:!1,revokedAt:new Date,revokedReason:body.reason||"admin_revoked"}).where(target2).returning({id:col15("id")});log4.warn("Sessions revoked by an administrator",{actorId,subjectUserId:userId,sessionId:body.sessionId,revoked:affected.length,reason:body.reason||"admin_revoked"});let reqUrl=new URL(ctx.request.url);return logger2.audit({entityName:"user_sessions",entityId:body.sessionId,operation:"SESSION_REVOKE_ALL",userId:actorId,summary:body.sessionId?`Admin revoked one session of user ${userId}`:`Admin revoked ${affected.length} session(s) of user ${userId}`,ipAddress:ctx.request.headers.get("x-forwarded-for")?.split(",")[0]?.trim()||ctx.request.headers.get("x-real-ip")?.trim()||"unknown",userAgent:ctx.request.headers.get("user-agent")||"unknown",path:reqUrl.pathname,query:reqUrl.search}),{success:!0,message:`${affected.length} session${affected.length===1?"":"s"} ended`,data:{revoked:affected.length}}}});return collectRoutes([{prefix:baseRoute,routes:[routeGetRoot,routeGetCurrent,routeDeleteSessionid,routeDeleteAll,routeGetStats,routePostApprove,routePostReject,routeGetPending,routeGetApprovePage,routeGetRejectPage,routeGetApprovalStatus,routeGetDevices,routeDeleteDevicesId,routeGetAdminUserid,routeDeleteAdminUserid]}])}var init_sessions=__esm(()=>{init_scopes();init_routeCollector();init_godminGuard();init_types20();init_utils11();init_types20()});function resolveCookies(cookieConfig){return{accessTokenName:cookieConfig?.accessTokenName||"access_token",refreshTokenName:cookieConfig?.refreshTokenName||"refresh_token",sessionTokenName:cookieConfig?.sessionTokenName||"session_token",accessTokenMaxAge:cookieConfig?.accessTokenMaxAge||900,refreshTokenMaxAge:cookieConfig?.refreshTokenMaxAge||604800,sessionTokenMaxAge:cookieConfig?.sessionTokenMaxAge||2592000,secure:cookieConfig?.secure??!0,httpOnly:cookieConfig?.httpOnly??!0,sameSite:cookieConfig?.sameSite||"lax",path:cookieConfig?.path||"/",domain:cookieConfig?.domain}}function buildCookieHeaders(cookies,tokens){let securePart=cookies.secure?"; Secure":"",domainPart=cookies.domain?`; Domain=${cookies.domain}`:"",cookieOptions=`; Path=${cookies.path}; HttpOnly; SameSite=${cookies.sameSite}${securePart}${domainPart}`,headers=new Headers;return headers.set("Content-Type","application/json"),headers.set("x-session-id",tokens.sessionId),headers.append("Set-Cookie",`${cookies.accessTokenName}=${tokens.accessToken}${cookieOptions}; Max-Age=${cookies.accessTokenMaxAge}`),headers.append("Set-Cookie",`${cookies.refreshTokenName}=${tokens.refreshToken}${cookieOptions}; Max-Age=${cookies.refreshTokenMaxAge}`),headers.append("Set-Cookie",`${cookies.sessionTokenName}=${tokens.sessionId}${cookieOptions}; Max-Age=${cookies.sessionTokenMaxAge}`),headers}var WebAuthnRegisterOptionsSchema,WebAuthnRegisterVerifySchema,WebAuthnAuthOptionsSchema,WebAuthnAuthVerifySchema,WebAuthnRenameSchema;var init_types21=__esm(()=>{init_esm3();WebAuthnRegisterOptionsSchema=Type.Object({nickname:Type.Optional(Type.String({maxLength:128}))}),WebAuthnRegisterVerifySchema=Type.Object({response:Type.Any(),challenge:Type.String(),nickname:Type.Optional(Type.String({maxLength:128}))}),WebAuthnAuthOptionsSchema=Type.Object({email:Type.Optional(Type.String({format:"email"}))}),WebAuthnAuthVerifySchema=Type.Object({response:Type.Any(),challenge:Type.String(),rememberMe:Type.Optional(Type.Boolean())}),WebAuthnRenameSchema=Type.Object({nickname:Type.String({minLength:1,maxLength:128})})});import{eq as eq63}from"drizzle-orm";function getSchema(ctx){return ctx.request.headers.get("x-tenant-schema")||void 0}function getUserId2(ctx){return ctx.request.headers.get("x-user-id")}function createWebAuthnRoute(config,routeOptions,webauthnService,helpers3,cookieConfig){let{db,logger:logger2}=config,basePath=routeOptions.basePath||"/auth/webauthn",cookies=resolveCookies(cookieConfig);if(!routeOptions.enabled)return collectRoutes();let routePostRegisterOptions=defineRoute({method:"POST",path:"/register/options",body:WebAuthnRegisterOptionsSchema,handler:async(ctx)=>{let userId=getUserId2(ctx);if(!userId)return ctx.set.status=401,{success:!1,message:"Authentication required"};let{usersTable}=resolveAuthTablesForRequest(ctx.request,config);if(!db||!usersTable)return ctx.set.status=500,{success:!1,message:"Database not configured"};let user=(await db.select().from(usersTable).where(eq63(usersTable.id,userId)).limit(1))[0];if(!user)return ctx.set.status=404,{success:!1,message:"User not found"};return{success:!0,data:await webauthnService.createRegistrationOptions({userId,userName:user.email||userId,userDisplayName:user.email||userId,schemaName:getSchema(ctx)})}}}),routePostRegisterVerify=defineRoute({method:"POST",path:"/register/verify",body:WebAuthnRegisterVerifySchema,handler:async(ctx)=>{let userId=getUserId2(ctx);if(!userId)return ctx.set.status=401,{success:!1,message:"Authentication required"};let body=ctx.body,credential=await webauthnService.verifyRegistration({response:body.response,expectedChallenge:body.challenge,userId,nickname:body.nickname,schemaName:getSchema(ctx)});if(!credential)return logger2.warn("[WEBAUTHN] Registration verification failed",{userId}),ctx.set.status=400,{success:!1,message:"Registration failed"};return{success:!0,data:{id:credential.id,credentialId:credential.credentialId,nickname:credential.nickname,deviceType:credential.deviceType,backedUp:credential.backedUp,authenticatorAttachment:credential.authenticatorAttachment,aaguid:credential.aaguid,transports:credential.transports,lastUsedAt:credential.lastUsedAt,revokedAt:credential.revokedAt,createdAt:credential.createdAt}}}}),routePostAuthenticateOptions=defineRoute({method:"POST",path:"/authenticate/options",body:WebAuthnAuthOptionsSchema,handler:async(ctx)=>{let body=ctx.body,resolvedUserId;if(body.email){let{usersTable}=resolveAuthTablesForRequest(ctx.request,config);if(db&&usersTable){let user=(await db.select().from(usersTable).where(eq63(usersTable.email,body.email.toLowerCase())).limit(1))[0];if(user)resolvedUserId=user.id}}return{success:!0,data:await webauthnService.createAuthenticationOptions({userId:resolvedUserId,schemaName:getSchema(ctx)})}}}),routePostAuthenticateVerify=defineRoute({method:"POST",path:"/authenticate/verify",body:WebAuthnAuthVerifySchema,handler:async(ctx)=>{let body=ctx.body,result=await webauthnService.verifyAuthentication({response:body.response,expectedChallenge:body.challenge,schemaName:getSchema(ctx)});if(!result.verified||!result.userId)return ctx.set.status=401,{success:!1,message:"Authentication failed"};let{usersTable}=resolveAuthTablesForRequest(ctx.request,config);if(!db||!usersTable)return ctx.set.status=500,{success:!1,message:"Database not configured"};let user=(await db.select().from(usersTable).where(eq63(usersTable.id,result.userId)).limit(1))[0];if(!user)return ctx.set.status=401,{success:!1,message:"User not found"};let webauthnState=evaluateAccountState(user);if(!webauthnState.allowed)return ctx.set.status=401,{success:!1,message:webauthnState.message};let roles=[],claims=[],claimScopes={};try{let resolved=resolveAuthTablesForRequest(ctx.request,config),rc=await fetchUserRolesAndClaims(db,result.userId,resolved);roles=rc.roles,claims=rc.claims,claimScopes=rc.claimScopes}catch{}let ipAddress=ctx.request.headers.get("x-forwarded-for")?.split(",")[0]?.trim()||ctx.request.headers.get("x-real-ip")?.trim()||"unknown",userAgent=ctx.request.headers.get("user-agent")||"",sessionParams={userId:result.userId,deviceInfo:ensureDeviceInfo({ipAddress,userAgent},deviceContextFromRequest(ctx.request).clientHints),loginMethod:"webauthn",rememberMe:body.rememberMe??!1},sessionId=await helpers3.createSession(sessionParams),accessToken=helpers3.signAccessToken(result.userId,roles.length>0?roles:void 0,claims.length>0?claims:void 0,getSchema(ctx),claimScopes,sessionId),refreshToken=helpers3.signRefreshToken(result.userId,body.rememberMe??!1,sessionId),deviceTokenCookie;if(helpers3.saveSessionToDb){let dtCfg=config.authentication?.deviceTrust;if(dtCfg?.enabled)sessionParams.deviceToken=readDeviceTokenFromCookieHeader(ctx.request.headers.get("cookie"),dtCfg);let saveResult=await helpers3.saveSessionToDb(sessionId,sessionParams,getSchema(ctx));if(saveResult?.denied)return ctx.set.status=403,{success:!1,message:"This device could not be recognized. Please contact support."};if(deviceTokenCookie=saveResult?.deviceTokenCookie,saveResult?.requiresApproval){if(deviceTokenCookie)ctx.set.headers["Set-Cookie"]=deviceTokenCookie;return ctx.set.status=202,{success:!1,requiresApproval:!0,sessionId:saveResult.sessionId??sessionId,message:"Login requires approval"}}}let headers=buildCookieHeaders(cookies,{accessToken,refreshToken,sessionId});if(deviceTokenCookie)headers.append("Set-Cookie",deviceTokenCookie);return logger2.info("[WEBAUTHN] Authentication successful",{userId:result.userId,credentialId:result.credentialId}),new Response(JSON.stringify({success:!0,data:{user:{id:user.id,email:user.email},accessToken,refreshToken,sessionId}}),{status:200,headers})}}),routeGetCredentials=defineRoute({method:"GET",path:"/credentials",handler:async(ctx)=>{let userId=getUserId2(ctx);if(!userId)return ctx.set.status=401,{success:!1,message:"Authentication required"};return{success:!0,data:{credentials:(await webauthnService.listUserCredentials(userId,getSchema(ctx))).map((cred)=>({id:cred.id,credentialId:cred.credentialId,nickname:cred.nickname,deviceType:cred.deviceType,backedUp:cred.backedUp,authenticatorAttachment:cred.authenticatorAttachment,transports:cred.transports,aaguid:cred.aaguid,lastUsedAt:cred.lastUsedAt,revokedAt:cred.revokedAt,createdAt:cred.createdAt}))}}}}),routeDeleteCredentialsCredentialid=defineRoute({method:"DELETE",path:"/credentials/:credentialId",handler:async(ctx)=>{let userId=getUserId2(ctx);if(!userId)return ctx.set.status=401,{success:!1,message:"Authentication required"};let{credentialId}=ctx.params;if(!await webauthnService.revokeCredential(credentialId,userId,getSchema(ctx)))return ctx.set.status=404,{success:!1,message:"Credential not found"};return{success:!0,message:"Credential revoked"}}}),routePatchCredentialsCredentialid=defineRoute({method:"PATCH",path:"/credentials/:credentialId",body:WebAuthnRenameSchema,handler:async(ctx)=>{let userId=getUserId2(ctx);if(!userId)return ctx.set.status=401,{success:!1,message:"Authentication required"};let{credentialId}=ctx.params,body=ctx.body;if(!await webauthnService.renameCredential(credentialId,userId,body.nickname,getSchema(ctx)))return ctx.set.status=404,{success:!1,message:"Credential not found"};return{success:!0,message:"Credential renamed"}}});return collectRoutes([{prefix:basePath,routes:[routePostRegisterOptions,routePostRegisterVerify,routePostAuthenticateOptions,routePostAuthenticateVerify,routeGetCredentials,routeDeleteCredentialsCredentialid,routePatchCredentialsCredentialid]}])}var init_webauthn=__esm(()=>{init_DeviceTrust();init_routeCollector();init_fetchUserRolesAndClaims();init_deviceInfo();init_types21();init_types21()});var exports_auth={};__export(exports_auth,{createWebAuthnRoute:()=>createWebAuthnRoute,createSessionsRoute:()=>createSessionsRoute,createRegisterRoute:()=>createRegisterRoute,createRefreshRoute:()=>createRefreshRoute,createPasswordSetRoute:()=>createPasswordSetRoute,createPasswordResetRoute:()=>createPasswordResetRoute,createPasswordChangeRoute:()=>createPasswordChangeRoute,createMeRoute:()=>createMeRoute,createMagicLinkRoute:()=>createMagicLinkRoute,createLogoutRoute:()=>createLogoutRoute,createLoginRoute:()=>createLoginRoute,createInviteRoute:()=>createInviteRoute,createImpersonateRoute:()=>createImpersonateRoute,createHardDeleteUserRoute:()=>createHardDeleteUserRoute,createEmailVerificationRoutes:()=>createEmailVerificationRoutes,createCreateUserRoute:()=>createCreateUserRoute,createChangeUserIdRoute:()=>createChangeUserIdRoute,createAuthRoutes:()=>createAuthRoutes,createApiKeyRoutes:()=>createApiKeyRoutes});function createAuthRoutes(app,config){let{authConfig,features,helpers:helpers3}=config;if(config.oauthAccountsTable)authConfig.oauthAccountsTable=config.oauthAccountsTable;if(config.schemaTables)authConfig.schemaTables=config.schemaTables;if(config.tenantRegistry)authConfig.tenantRegistry=config.tenantRegistry;if(config.getTenantRegistry)authConfig.getTenantRegistry=config.getTenantRegistry;let buffer=authConfig.authentication?.cookieMaxAgeBufferSeconds??0,rawCookieDomain=authConfig.authentication?.cookieDomain,resolvedCookieDomainRaw=rawCookieDomain?process.env[rawCookieDomain]??rawCookieDomain:void 0,resolvedCookieDomain=resolvedCookieDomainRaw==="localhost"||resolvedCookieDomainRaw===".localhost"?void 0:resolvedCookieDomainRaw,isLocalDev=!resolvedCookieDomain,cookieConfig={accessTokenName:authConfig.authentication?.accessToken?.name||"access_token",refreshTokenName:authConfig.authentication?.refreshToken?.name||"refresh_token",sessionTokenName:authConfig.authentication?.sessionToken?.name||"session_token",accessTokenMaxAge:Math.max(0,parseTimeToSeconds2(authConfig.authentication?.accessToken?.expiresIn||"15m")-buffer),refreshTokenMaxAge:parseTimeToSeconds2(authConfig.authentication?.refreshToken?.expiresIn||"7d"),sessionTokenMaxAge:parseTimeToSeconds2(authConfig.authentication?.sessionToken?.expiresIn||"30d"),domain:resolvedCookieDomain,secure:!isLocalDev,httpOnly:!0,sameSite:"lax",path:"/"};if(authConfig.logger.info("[AUTH] Cookie config created",{accessTokenMaxAge:cookieConfig.accessTokenMaxAge,refreshTokenMaxAge:cookieConfig.refreshTokenMaxAge,sessionTokenMaxAge:cookieConfig.sessionTokenMaxAge,accessTokenExpiresIn:authConfig.authentication?.accessToken?.expiresIn,sessionTokenExpiresIn:authConfig.authentication?.sessionToken?.expiresIn}),features.login?.enabled){let loginRoutes=createLoginRoute(authConfig,features.login,helpers3.signAccessToken,helpers3.signRefreshToken,helpers3.createSession,helpers3.saveSessionToDb,cookieConfig,config.tokenResponseConfig,config.captchaService??void 0);useRoutes(app,loginRoutes)}if(features.register?.enabled){let registerRoutes=createRegisterRoute(authConfig,features.register,helpers3.sendWelcomeEmail,helpers3.signAccessToken,helpers3.signRefreshToken,helpers3.createSession,cookieConfig,config.tokenResponseConfig,config.getEmailService,config.appName,helpers3.saveSessionToDb);if(useRoutes(app,registerRoutes),features.register.emailVerification?.enabled){let emailVerificationRoutes=createEmailVerificationRoutes({authConfig,registerConfig:features.register,getEmailService:config.getEmailService,appName:config.appName});useRoutes(app,emailVerificationRoutes)}}if(features.logout?.enabled){let logoutRoutes=createLogoutRoute(authConfig,features.logout,helpers3.destroySession,helpers3.revokeSessionInDb,cookieConfig);useRoutes(app,logoutRoutes)}if(features.refresh?.enabled){let refreshRoutes=createRefreshRoute(authConfig,features.refresh,helpers3.verifyRefreshToken,helpers3.signAccessToken,helpers3.signRefreshToken,config.tokenResponseConfig,buffer,cookieConfig);useRoutes(app,refreshRoutes)}if(features.passwordReset?.enabled&&helpers3.storeResetToken&&helpers3.getResetToken&&helpers3.deleteResetToken){let passwordResetRoutes=createPasswordResetRoute(authConfig,features.passwordReset,helpers3.storeResetToken,helpers3.getResetToken,helpers3.deleteResetToken,helpers3.sendResetEmail);useRoutes(app,passwordResetRoutes)}if(features.passwordChange?.enabled){let passwordChangeRoutes=createPasswordChangeRoute(authConfig,features.passwordChange);useRoutes(app,passwordChangeRoutes)}if(features.passwordSet?.enabled){let passwordSetRoutes=createPasswordSetRoute(authConfig,features.passwordSet,helpers3.getMagicToken,helpers3.deleteMagicToken);useRoutes(app,passwordSetRoutes)}if(features.sessions?.enabled&&config.sessionsTable){let sessionsRoutes=createSessionsRoute(authConfig,features.sessions,config.sessionsTable);useRoutes(app,sessionsRoutes)}if(features.magicLink?.enabled&&helpers3.storeMagicToken&&helpers3.getMagicToken&&helpers3.deleteMagicToken){let magicLinkRoutes=createMagicLinkRoute(authConfig,features.magicLink,config.getEmailService,helpers3.signAccessToken,helpers3.signRefreshToken,helpers3.createSession,helpers3.storeMagicToken,helpers3.getMagicToken,helpers3.deleteMagicToken,config.appName,cookieConfig,helpers3.saveSessionToDb);useRoutes(app,magicLinkRoutes)}if(features.me?.enabled){let meRoutes=createMeRoute(authConfig,features.me,config.schemaTables||{},config.schemaRelations||{},config.databaseUrl);useRoutes(app,meRoutes)}if(features.invite?.enabled&&helpers3.storeMagicToken){let inviteRoutes=createInviteRoute(authConfig,features.invite,config.getEmailService,helpers3.storeMagicToken,config.appName,helpers3.getMagicToken);useRoutes(app,inviteRoutes)}if(features.captcha?.enabled&&config.captchaService){let captchaRoutes=createCaptchaRoutes({captchaService:config.captchaService,logger:authConfig.logger,basePath:features.captcha.route||"/auth/captcha"});useRoutes(app,captchaRoutes)}if(features.oauth?.enabled&&features.oauth.providers){let oauthService=new OAuthService(features.oauth,config.oauthStateStore),oauthRoutes=createOAuthRoutes(authConfig,oauthService,helpers3.signAccessToken,helpers3.signRefreshToken,helpers3.createSession,helpers3.saveSessionToDb,cookieConfig,config.tokenResponseConfig,config.getEmailService,helpers3.storeMagicToken,config.appName);useRoutes(app,oauthRoutes)}if(features.apiKeys?.enabled&&config.apiKeysTable)authConfig.apiKeysTable=config.apiKeysTable,createApiKeyRoutes(app,authConfig,features.apiKeys);if(features.webauthn?.enabled&&config.webauthnService){let webauthnRoutes=createWebAuthnRoute(authConfig,{basePath:features.webauthn.route||"/auth/webauthn",isPublic:features.webauthn.isPublic,enabled:!0},config.webauthnService,{signAccessToken:helpers3.signAccessToken,signRefreshToken:helpers3.signRefreshToken,createSession:helpers3.createSession,saveSessionToDb:helpers3.saveSessionToDb},cookieConfig);useRoutes(app,webauthnRoutes)}let checkRoutes=createCheckRoute(authConfig,{route:"/auth/check",isPublic:!1,enabled:!0});if(useRoutes(app,checkRoutes),config.admin?.impersonate?.enabled!==!1){let impersonateRoutes=createImpersonateRoute(authConfig,helpers3.signAccessToken,helpers3.signRefreshToken,helpers3.verifyRefreshToken,helpers3.createSession,helpers3.saveSessionToDb,cookieConfig);useRoutes(app,impersonateRoutes)}if(config.admin?.changeUserId?.enabled!==!1){let changeUserIdRoutes=createChangeUserIdRoute(authConfig,config.schemaName||"public");useRoutes(app,changeUserIdRoutes)}if(config.admin?.createUser?.enabled!==!1){let createUserRoutes=createCreateUserRoute(authConfig,config.admin?.createUser);useRoutes(app,createUserRoutes)}if(useRoutes(app,createPasswordAdminRoutes(authConfig,{passwordTools:config.admin?.passwordTools?.enabled===!0})),useRoutes(app,createUserStateRoutes(authConfig)),config.admin?.hardDelete?.enabled!==!1){let hardDeleteRoutes=createHardDeleteUserRoute(authConfig);useRoutes(app,hardDeleteRoutes)}return app}var init_auth=__esm(()=>{init_OAuth();init_routeMount();init_utils5();init_changeUserId();init_createUser();init_hardDeleteUser();init_impersonate();init_passwordAdmin();init_userState();init_apiKeys();init_captcha();init_check();init_emailVerification();init_invite();init_login();init_logout();init_magicLink();init_me();init_oauth();init_passwordChange();init_passwordReset();init_passwordSet();init_refresh();init_register();init_sessions();init_webauthn();init_changeUserId();init_createUser();init_hardDeleteUser();init_impersonate();init_apiKeys();init_emailVerification();init_invite();init_login();init_logout();init_magicLink();init_me();init_passwordChange();init_passwordReset();init_passwordSet();init_refresh();init_register();init_sessions();init_webauthn()});var exports_backup={};__export(exports_backup,{createBackupRoutes:()=>createBackupRoutes});function createBackupRoutes(routeConfig){let{db,logger:logger2,schemaTables,schemaName,getTenantRegistry,lock}=routeConfig,config={...DEFAULT_BACKUP_CONFIG,...routeConfig.config},basePath=config.basePath,backupLogsTable=schemaTables.backupLogs||schemaTables.backup_logs,backupService=new BackupService({db,logger:logger2,config,schemaTables,schemaName,backupLogsTable,lock});if(config.schedule.enabled)backupService.startScheduler();let guard4=createGodminGuard2({db,schemaTables,logger:logger2},"backup management"),resolveTargetSchema=(targetSchema)=>{if(!targetSchema||targetSchema===schemaName)return{ok:!0,name:schemaName,tables:schemaTables};let tenantCtx=getTenantRegistry?.()?.getSchemaContext(targetSchema);if(!tenantCtx)return{ok:!1,message:`Schema not found: ${targetSchema}`};return{ok:!0,name:targetSchema,tables:tenantCtx.schemaTables}},declared=[defineRoute({method:"POST",path:basePath,handler:async(ctx)=>{let userId=ctx.request.headers.get("x-user-id");if(!userId)return ctx.set.status=401,{success:!1,message:"Authentication required"};let body=ctx.body,target2=resolveTargetSchema(body?.schema);if(!target2.ok)return ctx.set.status=404,{success:!1,message:target2.message};let result=await backupService.createBackup("manual",userId,target2.name,target2.tables);if(result.status==="failed")return ctx.set.status=500,{success:!1,message:result.errorMessage||"Backup failed"};return{success:!0,message:"Backup created successfully",data:{id:result.id,backupName:result.backupName,schemaName:result.schemaName,tableCount:result.tableCount,rowCount:result.rowCount,sizeBytes:result.sizeBytes,status:result.status}}},body:Type.Optional(Type.Object({schema:Type.Optional(Type.String())})),detail:{tags:["Backup"],summary:"Create backup",description:"Create a new database backup. Optionally specify a schema for multi-tenant backups."}}),defineRoute({method:"GET",path:basePath,handler:async(ctx)=>{if(!ctx.request.headers.get("x-user-id"))return ctx.set.status=401,{success:!1,message:"Authentication required",data:null};let backups=await backupService.listBackups();return{success:!0,data:{items:backups,totalCount:backups.length}}},detail:{tags:["Backup"],summary:"List backups",description:"List all backup records."}}),defineRoute({method:"GET",path:`${basePath}/:id/download`,handler:async(ctx)=>{if(!ctx.request.headers.get("x-user-id"))return ctx.set.status=401,{success:!1,message:"Authentication required"};let{id}=ctx.params,filePath=await backupService.getBackupFilePath(id);if(!filePath)return ctx.set.status=404,{success:!1,message:"Backup not found"};try{let{readFile:readFile2}=await import("fs/promises"),content=await readFile2(filePath,"utf-8");return new Response(content,{headers:{"Content-Type":"application/json","Content-Disposition":`attachment; filename="${id}.json"`}})}catch{return ctx.set.status=404,{success:!1,message:"Backup file not found on disk"}}},detail:{tags:["Backup"],summary:"Download backup",description:"Download a backup file by ID."}}),defineRoute({method:"POST",path:`${basePath}/:id/restore`,handler:async(ctx)=>{let userId=ctx.request.headers.get("x-user-id");if(!userId)return ctx.set.status=401,{success:!1,message:"Authentication required"};if(!config.allowRestore)return ctx.set.status=403,{success:!1,message:"Restore is disabled in configuration"};let{id}=ctx.params,restoreBody=ctx.body,target2=resolveTargetSchema(restoreBody?.schema);if(!target2.ok)return ctx.set.status=404,{success:!1,message:target2.message};let result=await backupService.restoreFromBackup(id,userId,target2.name,target2.tables);if(!result.success)return ctx.set.status=400,result;return await logger2.audit({entityName:"backup_logs",entityId:id,operation:"RESTORE",userId,summary:`Database restored from backup ${id}`,ipAddress:ctx.request.headers.get("x-forwarded-for")?.split(",")[0]?.trim()||"unknown",userAgent:ctx.request.headers.get("user-agent")||"unknown",path:new URL(ctx.request.url).pathname,query:""}),result},detail:{tags:["Backup"],summary:"Restore from backup",description:"Restore database from a specific backup. Creates a pre-restore backup automatically."}}),defineRoute({method:"DELETE",path:`${basePath}/:id`,handler:async(ctx)=>{if(!ctx.request.headers.get("x-user-id"))return ctx.set.status=401,{success:!1,message:"Authentication required"};let{id}=ctx.params;if(!await backupService.deleteBackup(id))return ctx.set.status=404,{success:!1,message:"Backup not found"};return{success:!0,message:"Backup deleted"}},detail:{tags:["Backup"],summary:"Delete backup",description:"Delete a specific backup and its file."}})];return{routes:collectRoutes([{guards:[guard4],routes:declared}]),backupService}}var DEFAULT_BACKUP_CONFIG;var init_backup=__esm(()=>{init_esm3();init_Backup();init_routeCollector();init_adminGuard();DEFAULT_BACKUP_CONFIG={enabled:!1,basePath:"/admin/backup",storagePath:"./backups",format:"json",maxBackups:50,allowRestore:!0,excludeTables:["audit_logs","backup_logs"],schedule:{enabled:!1,cron:"0 2 * * *",retentionDays:30}}});var validateAmount=(v,field="amount",opts={})=>{if(typeof v!=="number"||!Number.isFinite(v))return{ok:!1,error:`${field} must be a finite number`};if(opts.allowZero?v<0:v<=0)return{ok:!1,error:`${field} must be ${opts.allowZero?">= 0":"> 0"}`};return{ok:!0,value:v}},isValidCurrency=(v)=>v===void 0||v===null||typeof v==="string"&&/^[A-Za-z]{3}$/.test(v);import{eq as eq64}from"drizzle-orm";var col15=(table,name2)=>table[name2],str4=(v)=>typeof v==="string"?v:null,num3=(v)=>typeof v==="number"?v:null,handleChargeRefunded=async(deps,obj)=>{let candidates=[str4(obj.id),str4(obj.payment_intent)].filter((v)=>v!==null);for(let candidate of candidates){let updated=await deps.db.update(deps.transactionsTable).set({status:"refunded",refundedAmount:num3(obj.amount_refunded)??void 0,refundedAt:new Date,statusMessage:"Refunded via Stripe webhook",updatedAt:new Date}).where(eq64(col15(deps.transactionsTable,"providerPaymentId"),candidate)).returning();if(Array.isArray(updated)&&updated.length>0)return}},handleDisputeCreated=async(deps,obj)=>{let payoutService=deps.getPayoutService?.();if(!payoutService)return;let metadata=obj.metadata??{};await payoutService.openDispute({provider:"stripe",providerDisputeId:str4(obj.id)??void 0,ownerType:str4(metadata.ownerType)==="seller"?"seller":void 0,ownerId:str4(metadata.ownerId)??void 0,amount:num3(obj.amount)??void 0,currency:str4(obj.currency)?.toUpperCase(),reason:str4(obj.reason)??void 0})},handleDisputeClosed=async(deps,obj)=>{let payoutService=deps.getPayoutService?.(),providerId=str4(obj.id);if(!payoutService||!providerId)return;await payoutService.reconcileDisputeByProvider(providerId,str4(obj.status)??"")},handlePayoutOutcome=async(deps,obj,outcome,reason)=>{let payoutService=deps.getPayoutService?.(),providerId=str4(obj.id);if(!payoutService||!providerId)return;await payoutService.reconcilePayoutByProvider(providerId,outcome,reason)},dispatch=async(deps,eventType,obj)=>{switch(eventType){case"charge.refunded":await handleChargeRefunded(deps,obj);break;case"charge.dispute.created":await handleDisputeCreated(deps,obj);break;case"charge.dispute.closed":case"charge.dispute.funds_reinstated":await handleDisputeClosed(deps,obj);break;case"payout.paid":await handlePayoutOutcome(deps,obj,"completed");break;case"payout.failed":case"payout.canceled":await handlePayoutOutcome(deps,obj,"failed",str4(obj.failure_message)??eventType);break;case"transfer.reversed":await handlePayoutOutcome(deps,obj,"failed","transfer reversed");break;default:break}},createStripeWebhookRoute=(deps)=>{return defineRoute({method:"POST",path:"/webhook",handler:async(ctx)=>{if(deps.provider.name!=="stripe")return ctx.set.status=501,{received:!1,error:"Webhooks are only supported for the Stripe provider"};if(!deps.webhookSecret)return deps.logger.error("[Payment] Webhook received but STRIPE_WEBHOOK_SECRET is not configured"),ctx.set.status=500,{received:!1,error:"Webhook secret not configured"};let rawBody=await ctx.request.text(),signature=ctx.request.headers.get("stripe-signature")??"",verified=await verifyStripeWebhook(rawBody,signature,deps.webhookSecret);if(!verified.success||!verified.event)return deps.logger.warn("[Payment] Webhook signature verification failed",{error:verified.error}),ctx.set.status=400,{received:!1,error:"Invalid signature"};let event=verified.event,eventId=str4(event.id)??"",eventType=str4(event.type)??"",obj=event.data?.object??{},idempotencyKey=`stripe:${eventId}`,inserted=await deps.db.insert(deps.webhookLogsTable).values({provider:"stripe",eventType,providerPaymentId:str4(obj.payment_intent)??str4(obj.id),rawPayload:event,processed:!1,idempotencyKey,ipAddress:ctx.request.headers.get("x-forwarded-for")??ctx.request.headers.get("x-real-ip")??null}).onConflictDoNothing().returning();if(!Array.isArray(inserted)||inserted.length===0)return{received:!0,duplicate:!0,eventType};try{return await dispatch(deps,eventType,obj),await deps.db.update(deps.webhookLogsTable).set({processed:!0,processingResult:{ok:!0},updatedAt:new Date}).where(eq64(col15(deps.webhookLogsTable,"idempotencyKey"),idempotencyKey)),deps.logger.info("[Payment] Webhook processed",{eventType,eventId}),{received:!0,eventType}}catch(err){let message=err instanceof Error?err.message:String(err);return deps.logger.error("[Payment] Webhook handler failed",{eventType,error:message}),await deps.db.update(deps.webhookLogsTable).set({errorMessage:message,updatedAt:new Date}).where(eq64(col15(deps.webhookLogsTable,"idempotencyKey"),idempotencyKey)).catch(()=>{}),ctx.set.status=500,{received:!1,error:"Handler error"}}}})};var init_webhook=__esm(()=>{init_Payment()});var exports_payment={};__export(exports_payment,{createPaymentRoutes:()=>createPaymentRoutes});import{and as and27,eq as eq65,inArray as inArray18,ne as ne3,or as or5,sql as sql12}from"drizzle-orm";var escapeHtml2=(value2)=>value2.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""").replace(/'/g,"'"),generateRedirectHtml=(url,title)=>{let safeTitle=escapeHtml2(title),safeUrlAttr=escapeHtml2(url),safeUrlJs=JSON.stringify(url).replace(/</g,"\\u003c").replace(/>/g,"\\u003e");return`
|
|
525
|
+
</body></html>`},routeGetApprovePage=defineRoute({method:"GET",path:"/approve-page",detail:{tags:["Authentication"],summary:"Approval page",description:"Standalone HTML page for approving a device (no frontend auth required)"},handler:async(ctx)=>{let{sessionsTable:sessionsTable2,col:col15}=getSessionsForRequest(ctx.request);if(!db)return new Response("Service unavailable",{status:503});let token=new URL(ctx.request.url).searchParams.get("token");if(!token){let html2=buildApprovalPageHtml({},"approve","","invalid");return new Response(html2,{status:400,headers:{"Content-Type":"text/html; charset=utf-8"}})}let sessions=await db.select().from(sessionsTable2).where(eq62(col15("approvalToken"),token)).limit(1);if(sessions.length===0){let html2=buildApprovalPageHtml({},"approve",token,"invalid");return new Response(html2,{status:404,headers:{"Content-Type":"text/html; charset=utf-8"}})}let session=sessions[0];if(session.approvalStatus!=="pending"&&session.approval_status!=="pending"){let html2=buildApprovalPageHtml(session,"approve",token,"already_processed");return new Response(html2,{status:200,headers:{"Content-Type":"text/html; charset=utf-8"}})}let requestedAt=session.approvalRequestedAt||session.approval_requested_at;if(requestedAt&&Date.now()-new Date(requestedAt).getTime()>APPROVAL_TOKEN_TTL_MS){let html2=buildApprovalPageHtml(session,"approve",token,"expired");return new Response(html2,{status:410,headers:{"Content-Type":"text/html; charset=utf-8"}})}let html=buildApprovalPageHtml(session,"approve",token,"pending");return new Response(html,{status:200,headers:{"Content-Type":"text/html; charset=utf-8"}})}}),routeGetRejectPage=defineRoute({method:"GET",path:"/reject-page",detail:{tags:["Authentication"],summary:"Rejection page",description:"Standalone HTML page for rejecting a device (no frontend auth required)"},handler:async(ctx)=>{let{sessionsTable:sessionsTable2,col:col15}=getSessionsForRequest(ctx.request);if(!db)return new Response("Service unavailable",{status:503});let token=new URL(ctx.request.url).searchParams.get("token");if(!token){let html2=buildApprovalPageHtml({},"reject","","invalid");return new Response(html2,{status:400,headers:{"Content-Type":"text/html; charset=utf-8"}})}let sessions=await db.select().from(sessionsTable2).where(eq62(col15("approvalToken"),token)).limit(1);if(sessions.length===0){let html2=buildApprovalPageHtml({},"reject",token,"invalid");return new Response(html2,{status:404,headers:{"Content-Type":"text/html; charset=utf-8"}})}let session=sessions[0];if(session.approvalStatus!=="pending"&&session.approval_status!=="pending"){let html2=buildApprovalPageHtml(session,"reject",token,"already_processed");return new Response(html2,{status:200,headers:{"Content-Type":"text/html; charset=utf-8"}})}let requestedAt=session.approvalRequestedAt||session.approval_requested_at;if(requestedAt&&Date.now()-new Date(requestedAt).getTime()>APPROVAL_TOKEN_TTL_MS){let html2=buildApprovalPageHtml(session,"reject",token,"expired");return new Response(html2,{status:410,headers:{"Content-Type":"text/html; charset=utf-8"}})}let html=buildApprovalPageHtml(session,"reject",token,"pending");return new Response(html,{status:200,headers:{"Content-Type":"text/html; charset=utf-8"}})}}),routeGetApprovalStatus=defineRoute({method:"GET",path:"/approval-status",detail:{tags:["Authentication"],summary:"Check approval status",description:"Check the approval status of a pending session"},handler:async(ctx)=>{let{sessionsTable:sessionsTable2,col:col15}=getSessionsForRequest(ctx.request);if(!db)return ctx.set.status=500,{success:!1,message:"Database not configured"};let sessionId=new URL(ctx.request.url).searchParams.get("sessionId");if(!sessionId)return ctx.set.status=400,{success:!1,message:"sessionId is required"};let sessions=await db.select().from(sessionsTable2).where(eq62(col15("id"),sessionId)).limit(1);if(sessions.length===0)return ctx.set.status=404,{success:!1,message:"Session not found"};let session=sessions[0];return{success:!0,data:{approvalStatus:session.approvalStatus||session.approval_status||"unknown",isActive:session.isActive||session.is_active}}}}),routeGetDevices=defineRoute({method:"GET",path:"/devices",detail:{tags:["Authentication"],summary:"List remembered devices",description:"List the caller's remembered (device-trust) devices; never returns the token."},handler:async(ctx)=>{let userId=ctx.request.headers.get("x-user-id");if(!userId)return ctx.set.status=401,{success:!1,message:"Authentication required"};let dtTable=getSessionsForRequest(ctx.request).trustedDevicesTable;if(!dtTable||!db)return{success:!0,data:{items:[]}};let{listUserDevices:listUserDevices2}=await Promise.resolve().then(() => (init_store3(),exports_store));return{success:!0,data:{items:await listUserDevices2(db,dtTable,userId)}}}}),routeDeleteDevicesId=defineRoute({method:"DELETE",path:"/devices/:id",detail:{tags:["Authentication"],summary:"Revoke a remembered device",description:"Owner-scoped revoke of one remembered device (untrusts it)."},handler:async(ctx)=>{let userId=ctx.request.headers.get("x-user-id");if(!userId)return ctx.set.status=401,{success:!1,message:"Authentication required"};let id=ctx.params.id,dtTable=getSessionsForRequest(ctx.request).trustedDevicesTable;if(!dtTable||!db)return ctx.set.status=503,{success:!1,message:"Device trust not enabled"};let{revokeDeviceById:revokeDeviceById2}=await Promise.resolve().then(() => (init_store3(),exports_store));if(await revokeDeviceById2(db,dtTable,id,userId,"user_revoked")===0)return ctx.set.status=404,{success:!1,message:"Device not found"};return log4.info("Remembered device revoked",{userId,deviceId:id}),{success:!0,message:"Device revoked"}}}),adminGuard=createGodminGuard({logger:logger2,db,schemaTables:config.schemaTables??{},area:"session management",allowedRoles:sessionsConfig.adminRoles??[]}),routeGetAdminUserid=defineRoute({method:"GET",path:"/admin/:userId",guards:[adminGuard],detail:{tags:["Authentication"],summary:"List another user's active sessions (admin)",description:"Godmin or a role named in authentication.sessions.adminRoles. Used for offboarding and lost-device response."},handler:async(ctx)=>{let{sessionsTable:sessionsTable2,col:col15}=getSessionsForRequest(ctx.request);if(!db)return ctx.set.status=500,{success:!1,message:"Database not configured"};let{userId}=ctx.params,sessions=await db.select().from(sessionsTable2).where(and26(eq62(col15("userId"),userId),eq62(col15("isActive"),!0),isNull5(col15("revokedAt")))).orderBy(desc6(col15("lastActivityAt")));return{success:!0,data:{sessions:sessions.map((session)=>formatSessionForResponse(session)),totalCount:sessions.length}}}}),routeDeleteAdminUserid=defineRoute({method:"DELETE",path:"/admin/:userId",guards:[adminGuard],body:AdminRevokeSessionsBodySchema,detail:{tags:["Authentication"],summary:"End another user's sessions (admin)",description:"Ends every active session of the given user, or just one when `sessionId` is supplied. Godmin or authentication.sessions.adminRoles."},handler:async(ctx)=>{let{sessionsTable:sessionsTable2,col:col15}=getSessionsForRequest(ctx.request);if(!db)return ctx.set.status=500,{success:!1,message:"Database not configured"};let{userId}=ctx.params,body=ctx.body??{},actorId=ctx.request.headers.get("x-user-id")||void 0,target2=body.sessionId?and26(eq62(col15("id"),body.sessionId),eq62(col15("userId"),userId),eq62(col15("isActive"),!0)):and26(eq62(col15("userId"),userId),eq62(col15("isActive"),!0)),affected=await db.update(sessionsTable2).set({isActive:!1,revokedAt:new Date,revokedReason:body.reason||"admin_revoked"}).where(target2).returning({id:col15("id")});log4.warn("Sessions revoked by an administrator",{actorId,subjectUserId:userId,sessionId:body.sessionId,revoked:affected.length,reason:body.reason||"admin_revoked"});let reqUrl=new URL(ctx.request.url);return logger2.audit({entityName:"user_sessions",entityId:body.sessionId,operation:"SESSION_REVOKE_ALL",userId:actorId,summary:body.sessionId?`Admin revoked one session of user ${userId}`:`Admin revoked ${affected.length} session(s) of user ${userId}`,ipAddress:ctx.request.headers.get("x-forwarded-for")?.split(",")[0]?.trim()||ctx.request.headers.get("x-real-ip")?.trim()||"unknown",userAgent:ctx.request.headers.get("user-agent")||"unknown",path:reqUrl.pathname,query:reqUrl.search}),{success:!0,message:`${affected.length} session${affected.length===1?"":"s"} ended`,data:{revoked:affected.length}}}});return collectRoutes([{prefix:baseRoute,routes:[routeGetRoot,routeGetCurrent,routeDeleteSessionid,routeDeleteAll,routeGetStats,routePostApprove,routePostReject,routeGetPending,routeGetApprovePage,routeGetRejectPage,routeGetApprovalStatus,routeGetDevices,routeDeleteDevicesId,routeGetAdminUserid,routeDeleteAdminUserid]}])}var init_sessions=__esm(()=>{init_scopes();init_routeCollector();init_godminGuard();init_types20();init_utils11();init_types20()});function resolveCookies(cookieConfig){return{accessTokenName:cookieConfig?.accessTokenName||"access_token",refreshTokenName:cookieConfig?.refreshTokenName||"refresh_token",sessionTokenName:cookieConfig?.sessionTokenName||"session_token",accessTokenMaxAge:cookieConfig?.accessTokenMaxAge||900,refreshTokenMaxAge:cookieConfig?.refreshTokenMaxAge||604800,sessionTokenMaxAge:cookieConfig?.sessionTokenMaxAge||2592000,secure:cookieConfig?.secure??!0,httpOnly:cookieConfig?.httpOnly??!0,sameSite:cookieConfig?.sameSite||"lax",path:cookieConfig?.path||"/",domain:cookieConfig?.domain}}function buildCookieHeaders(cookies,tokens){let securePart=cookies.secure?"; Secure":"",domainPart=cookies.domain?`; Domain=${cookies.domain}`:"",cookieOptions=`; Path=${cookies.path}; HttpOnly; SameSite=${cookies.sameSite}${securePart}${domainPart}`,headers=new Headers;return headers.set("Content-Type","application/json"),headers.set("x-session-id",tokens.sessionId),headers.append("Set-Cookie",`${cookies.accessTokenName}=${tokens.accessToken}${cookieOptions}; Max-Age=${cookies.accessTokenMaxAge}`),headers.append("Set-Cookie",`${cookies.refreshTokenName}=${tokens.refreshToken}${cookieOptions}; Max-Age=${cookies.refreshTokenMaxAge}`),headers.append("Set-Cookie",`${cookies.sessionTokenName}=${tokens.sessionId}${cookieOptions}; Max-Age=${cookies.sessionTokenMaxAge}`),headers}var WebAuthnRegisterOptionsSchema,WebAuthnRegisterVerifySchema,WebAuthnAuthOptionsSchema,WebAuthnAuthVerifySchema,WebAuthnRenameSchema;var init_types21=__esm(()=>{init_esm3();WebAuthnRegisterOptionsSchema=Type.Object({nickname:Type.Optional(Type.String({maxLength:128}))}),WebAuthnRegisterVerifySchema=Type.Object({response:Type.Any(),challenge:Type.String(),nickname:Type.Optional(Type.String({maxLength:128}))}),WebAuthnAuthOptionsSchema=Type.Object({email:Type.Optional(Type.String({format:"email"}))}),WebAuthnAuthVerifySchema=Type.Object({response:Type.Any(),challenge:Type.String(),rememberMe:Type.Optional(Type.Boolean())}),WebAuthnRenameSchema=Type.Object({nickname:Type.String({minLength:1,maxLength:128})})});import{eq as eq63}from"drizzle-orm";function getSchema(ctx){return ctx.request.headers.get("x-tenant-schema")||void 0}function getUserId2(ctx){return ctx.request.headers.get("x-user-id")}function createWebAuthnRoute(config,routeOptions,webauthnService,helpers3,cookieConfig){let{db,logger:logger2}=config,basePath=routeOptions.basePath||"/auth/webauthn",cookies=resolveCookies(cookieConfig);if(!routeOptions.enabled)return collectRoutes();let routePostRegisterOptions=defineRoute({method:"POST",path:"/register/options",body:WebAuthnRegisterOptionsSchema,handler:async(ctx)=>{let userId=getUserId2(ctx);if(!userId)return ctx.set.status=401,{success:!1,message:"Authentication required"};let{usersTable}=resolveAuthTablesForRequest(ctx.request,config);if(!db||!usersTable)return ctx.set.status=500,{success:!1,message:"Database not configured"};let user=(await db.select().from(usersTable).where(eq63(usersTable.id,userId)).limit(1))[0];if(!user)return ctx.set.status=404,{success:!1,message:"User not found"};return{success:!0,data:await webauthnService.createRegistrationOptions({userId,userName:user.email||userId,userDisplayName:user.email||userId,schemaName:getSchema(ctx)})}}}),routePostRegisterVerify=defineRoute({method:"POST",path:"/register/verify",body:WebAuthnRegisterVerifySchema,handler:async(ctx)=>{let userId=getUserId2(ctx);if(!userId)return ctx.set.status=401,{success:!1,message:"Authentication required"};let body=ctx.body,credential=await webauthnService.verifyRegistration({response:body.response,expectedChallenge:body.challenge,userId,nickname:body.nickname,schemaName:getSchema(ctx)});if(!credential)return logger2.warn("[WEBAUTHN] Registration verification failed",{userId}),ctx.set.status=400,{success:!1,message:"Registration failed"};return{success:!0,data:{id:credential.id,credentialId:credential.credentialId,nickname:credential.nickname,deviceType:credential.deviceType,backedUp:credential.backedUp,authenticatorAttachment:credential.authenticatorAttachment,aaguid:credential.aaguid,transports:credential.transports,lastUsedAt:credential.lastUsedAt,revokedAt:credential.revokedAt,createdAt:credential.createdAt}}}}),routePostAuthenticateOptions=defineRoute({method:"POST",path:"/authenticate/options",body:WebAuthnAuthOptionsSchema,handler:async(ctx)=>{let body=ctx.body,resolvedUserId;if(body.email){let{usersTable}=resolveAuthTablesForRequest(ctx.request,config);if(db&&usersTable){let user=(await db.select().from(usersTable).where(eq63(usersTable.email,body.email.toLowerCase())).limit(1))[0];if(user)resolvedUserId=user.id}}return{success:!0,data:await webauthnService.createAuthenticationOptions({userId:resolvedUserId,schemaName:getSchema(ctx)})}}}),routePostAuthenticateVerify=defineRoute({method:"POST",path:"/authenticate/verify",body:WebAuthnAuthVerifySchema,handler:async(ctx)=>{let body=ctx.body,result=await webauthnService.verifyAuthentication({response:body.response,expectedChallenge:body.challenge,schemaName:getSchema(ctx)});if(!result.verified||!result.userId)return ctx.set.status=401,{success:!1,message:"Authentication failed"};let{usersTable}=resolveAuthTablesForRequest(ctx.request,config);if(!db||!usersTable)return ctx.set.status=500,{success:!1,message:"Database not configured"};let user=(await db.select().from(usersTable).where(eq63(usersTable.id,result.userId)).limit(1))[0];if(!user)return ctx.set.status=401,{success:!1,message:"User not found"};let webauthnState=evaluateAccountState(user);if(!webauthnState.allowed)return ctx.set.status=401,{success:!1,message:webauthnState.message};let roles=[],claims=[],claimScopes={};try{let resolved=resolveAuthTablesForRequest(ctx.request,config),rc=await fetchUserRolesAndClaims(db,result.userId,resolved);roles=rc.roles,claims=rc.claims,claimScopes=rc.claimScopes}catch{}let ipAddress=ctx.request.headers.get("x-forwarded-for")?.split(",")[0]?.trim()||ctx.request.headers.get("x-real-ip")?.trim()||"unknown",userAgent=ctx.request.headers.get("user-agent")||"",sessionParams={userId:result.userId,deviceInfo:ensureDeviceInfo({ipAddress,userAgent},deviceContextFromRequest(ctx.request).clientHints),loginMethod:"webauthn",rememberMe:body.rememberMe??!1},sessionId=await helpers3.createSession(sessionParams),accessToken=helpers3.signAccessToken(result.userId,roles.length>0?roles:void 0,claims.length>0?claims:void 0,getSchema(ctx),claimScopes,sessionId),refreshToken=helpers3.signRefreshToken(result.userId,body.rememberMe??!1,sessionId),deviceTokenCookie;if(helpers3.saveSessionToDb){let dtCfg=config.authentication?.deviceTrust;if(dtCfg?.enabled)sessionParams.deviceToken=readDeviceTokenFromCookieHeader(ctx.request.headers.get("cookie"),dtCfg);let saveResult=await helpers3.saveSessionToDb(sessionId,sessionParams,getSchema(ctx));if(saveResult?.denied)return ctx.set.status=403,{success:!1,message:"This device could not be recognized. Please contact support."};if(deviceTokenCookie=saveResult?.deviceTokenCookie,saveResult?.requiresApproval){if(deviceTokenCookie)ctx.set.headers["Set-Cookie"]=deviceTokenCookie;return ctx.set.status=202,{success:!1,requiresApproval:!0,sessionId:saveResult.sessionId??sessionId,message:"Login requires approval"}}}let headers=buildCookieHeaders(cookies,{accessToken,refreshToken,sessionId});if(deviceTokenCookie)headers.append("Set-Cookie",deviceTokenCookie);return logger2.info("[WEBAUTHN] Authentication successful",{userId:result.userId,credentialId:result.credentialId}),new Response(JSON.stringify({success:!0,data:{user:{id:user.id,email:user.email},accessToken,refreshToken,sessionId}}),{status:200,headers})}}),routeGetCredentials=defineRoute({method:"GET",path:"/credentials",handler:async(ctx)=>{let userId=getUserId2(ctx);if(!userId)return ctx.set.status=401,{success:!1,message:"Authentication required"};return{success:!0,data:{credentials:(await webauthnService.listUserCredentials(userId,getSchema(ctx))).map((cred)=>({id:cred.id,credentialId:cred.credentialId,nickname:cred.nickname,deviceType:cred.deviceType,backedUp:cred.backedUp,authenticatorAttachment:cred.authenticatorAttachment,transports:cred.transports,aaguid:cred.aaguid,lastUsedAt:cred.lastUsedAt,revokedAt:cred.revokedAt,createdAt:cred.createdAt}))}}}}),routeDeleteCredentialsCredentialid=defineRoute({method:"DELETE",path:"/credentials/:credentialId",handler:async(ctx)=>{let userId=getUserId2(ctx);if(!userId)return ctx.set.status=401,{success:!1,message:"Authentication required"};let{credentialId}=ctx.params;if(!await webauthnService.revokeCredential(credentialId,userId,getSchema(ctx)))return ctx.set.status=404,{success:!1,message:"Credential not found"};return{success:!0,message:"Credential revoked"}}}),routePatchCredentialsCredentialid=defineRoute({method:"PATCH",path:"/credentials/:credentialId",body:WebAuthnRenameSchema,handler:async(ctx)=>{let userId=getUserId2(ctx);if(!userId)return ctx.set.status=401,{success:!1,message:"Authentication required"};let{credentialId}=ctx.params,body=ctx.body;if(!await webauthnService.renameCredential(credentialId,userId,body.nickname,getSchema(ctx)))return ctx.set.status=404,{success:!1,message:"Credential not found"};return{success:!0,message:"Credential renamed"}}});return collectRoutes([{prefix:basePath,routes:[routePostRegisterOptions,routePostRegisterVerify,routePostAuthenticateOptions,routePostAuthenticateVerify,routeGetCredentials,routeDeleteCredentialsCredentialid,routePatchCredentialsCredentialid]}])}var init_webauthn=__esm(()=>{init_DeviceTrust();init_routeCollector();init_fetchUserRolesAndClaims();init_deviceInfo();init_types21();init_types21()});var exports_auth={};__export(exports_auth,{createWebAuthnRoute:()=>createWebAuthnRoute,createSessionsRoute:()=>createSessionsRoute,createRegisterRoute:()=>createRegisterRoute,createRefreshRoute:()=>createRefreshRoute,createPasswordSetRoute:()=>createPasswordSetRoute,createPasswordResetRoute:()=>createPasswordResetRoute,createPasswordChangeRoute:()=>createPasswordChangeRoute,createMeRoute:()=>createMeRoute,createMagicLinkRoute:()=>createMagicLinkRoute,createLogoutRoute:()=>createLogoutRoute,createLoginRoute:()=>createLoginRoute,createInviteRoute:()=>createInviteRoute,createImpersonateRoute:()=>createImpersonateRoute,createHardDeleteUserRoute:()=>createHardDeleteUserRoute,createEmailVerificationRoutes:()=>createEmailVerificationRoutes,createCreateUserRoute:()=>createCreateUserRoute,createChangeUserIdRoute:()=>createChangeUserIdRoute,createAuthRoutes:()=>createAuthRoutes,createApiKeyRoutes:()=>createApiKeyRoutes});function createAuthRoutes(app,config){let{authConfig,features,helpers:helpers3}=config;if(config.oauthAccountsTable)authConfig.oauthAccountsTable=config.oauthAccountsTable;if(config.schemaTables)authConfig.schemaTables=config.schemaTables;if(config.tenantRegistry)authConfig.tenantRegistry=config.tenantRegistry;if(config.getTenantRegistry)authConfig.getTenantRegistry=config.getTenantRegistry;let buffer=authConfig.authentication?.cookieMaxAgeBufferSeconds??0,rawCookieDomain=authConfig.authentication?.cookieDomain,resolvedCookieDomainRaw=rawCookieDomain?process.env[rawCookieDomain]??rawCookieDomain:void 0,resolvedCookieDomain=resolvedCookieDomainRaw==="localhost"||resolvedCookieDomainRaw===".localhost"?void 0:resolvedCookieDomainRaw,isLocalDev=!resolvedCookieDomain,cookieConfig={accessTokenName:authConfig.authentication?.accessToken?.name||"access_token",refreshTokenName:authConfig.authentication?.refreshToken?.name||"refresh_token",sessionTokenName:authConfig.authentication?.sessionToken?.name||"session_token",accessTokenMaxAge:Math.max(0,parseTimeToSeconds2(authConfig.authentication?.accessToken?.expiresIn||"15m")-buffer),refreshTokenMaxAge:parseTimeToSeconds2(authConfig.authentication?.refreshToken?.expiresIn||"7d"),sessionTokenMaxAge:parseTimeToSeconds2(authConfig.authentication?.sessionToken?.expiresIn||"30d"),domain:resolvedCookieDomain,secure:!isLocalDev,httpOnly:!0,sameSite:"lax",path:"/"};if(authConfig.logger.info("[AUTH] Cookie config created",{accessTokenMaxAge:cookieConfig.accessTokenMaxAge,refreshTokenMaxAge:cookieConfig.refreshTokenMaxAge,sessionTokenMaxAge:cookieConfig.sessionTokenMaxAge,accessTokenExpiresIn:authConfig.authentication?.accessToken?.expiresIn,sessionTokenExpiresIn:authConfig.authentication?.sessionToken?.expiresIn}),features.login?.enabled){let loginRoutes=createLoginRoute(authConfig,features.login,helpers3.signAccessToken,helpers3.signRefreshToken,helpers3.createSession,helpers3.saveSessionToDb,cookieConfig,config.tokenResponseConfig,config.captchaService??void 0);useRoutes(app,loginRoutes)}if(features.register?.enabled){let registerRoutes=createRegisterRoute(authConfig,features.register,helpers3.sendWelcomeEmail,helpers3.signAccessToken,helpers3.signRefreshToken,helpers3.createSession,cookieConfig,config.tokenResponseConfig,config.getEmailService,config.appName,helpers3.saveSessionToDb);if(useRoutes(app,registerRoutes),features.register.emailVerification?.enabled){let emailVerificationRoutes=createEmailVerificationRoutes({authConfig,registerConfig:features.register,getEmailService:config.getEmailService,appName:config.appName});useRoutes(app,emailVerificationRoutes)}}if(features.logout?.enabled){let logoutRoutes=createLogoutRoute(authConfig,features.logout,helpers3.destroySession,helpers3.revokeSessionInDb,cookieConfig);useRoutes(app,logoutRoutes)}if(features.refresh?.enabled){let refreshRoutes=createRefreshRoute(authConfig,features.refresh,helpers3.verifyRefreshToken,helpers3.signAccessToken,helpers3.signRefreshToken,config.tokenResponseConfig,buffer,cookieConfig);useRoutes(app,refreshRoutes)}if(features.passwordReset?.enabled&&helpers3.storeResetToken&&helpers3.getResetToken&&helpers3.deleteResetToken){let passwordResetRoutes=createPasswordResetRoute(authConfig,features.passwordReset,helpers3.storeResetToken,helpers3.getResetToken,helpers3.deleteResetToken,helpers3.sendResetEmail);useRoutes(app,passwordResetRoutes)}if(features.passwordChange?.enabled){let passwordChangeRoutes=createPasswordChangeRoute(authConfig,features.passwordChange);useRoutes(app,passwordChangeRoutes)}if(features.passwordSet?.enabled){let passwordSetRoutes=createPasswordSetRoute(authConfig,features.passwordSet,helpers3.getMagicToken,helpers3.deleteMagicToken);useRoutes(app,passwordSetRoutes)}if(features.sessions?.enabled&&config.sessionsTable){let sessionsRoutes=createSessionsRoute(authConfig,features.sessions,config.sessionsTable);useRoutes(app,sessionsRoutes)}if(features.magicLink?.enabled&&helpers3.storeMagicToken&&helpers3.getMagicToken&&helpers3.deleteMagicToken){let magicLinkRoutes=createMagicLinkRoute(authConfig,features.magicLink,config.getEmailService,helpers3.signAccessToken,helpers3.signRefreshToken,helpers3.createSession,helpers3.storeMagicToken,helpers3.getMagicToken,helpers3.deleteMagicToken,config.appName,cookieConfig,helpers3.saveSessionToDb);useRoutes(app,magicLinkRoutes)}if(features.me?.enabled){let meRoutes=createMeRoute(authConfig,features.me,config.schemaTables||{},config.schemaRelations||{},config.databaseUrl);useRoutes(app,meRoutes)}if(features.invite?.enabled&&helpers3.storeMagicToken){let inviteRoutes=createInviteRoute(authConfig,features.invite,config.getEmailService,helpers3.storeMagicToken,config.appName,helpers3.getMagicToken);useRoutes(app,inviteRoutes)}if(features.captcha?.enabled&&config.captchaService){let captchaRoutes=createCaptchaRoutes({captchaService:config.captchaService,logger:authConfig.logger,basePath:features.captcha.route||"/auth/captcha"});useRoutes(app,captchaRoutes)}if(features.oauth?.enabled&&features.oauth.providers){let oauthService=new OAuthService(features.oauth,config.oauthStateStore),oauthRoutes=createOAuthRoutes(authConfig,oauthService,helpers3.signAccessToken,helpers3.signRefreshToken,helpers3.createSession,helpers3.saveSessionToDb,cookieConfig,config.tokenResponseConfig,config.getEmailService,helpers3.storeMagicToken,config.appName);useRoutes(app,oauthRoutes)}if(features.apiKeys?.enabled&&config.apiKeysTable)authConfig.apiKeysTable=config.apiKeysTable,createApiKeyRoutes(app,authConfig,features.apiKeys);if(features.webauthn?.enabled&&config.webauthnService){let webauthnRoutes=createWebAuthnRoute(authConfig,{basePath:features.webauthn.route||"/auth/webauthn",isPublic:features.webauthn.isPublic,enabled:!0},config.webauthnService,{signAccessToken:helpers3.signAccessToken,signRefreshToken:helpers3.signRefreshToken,createSession:helpers3.createSession,saveSessionToDb:helpers3.saveSessionToDb},cookieConfig);useRoutes(app,webauthnRoutes)}let checkRoutes=createCheckRoute(authConfig,{route:"/auth/check",isPublic:!1,enabled:!0});if(useRoutes(app,checkRoutes),config.admin?.impersonate?.enabled!==!1){let impersonateRoutes=createImpersonateRoute(authConfig,helpers3.signAccessToken,helpers3.signRefreshToken,helpers3.verifyRefreshToken,helpers3.createSession,helpers3.saveSessionToDb,cookieConfig);useRoutes(app,impersonateRoutes)}if(config.admin?.changeUserId?.enabled!==!1){let changeUserIdRoutes=createChangeUserIdRoute(authConfig,config.schemaName||"public");useRoutes(app,changeUserIdRoutes)}if(config.admin?.createUser?.enabled!==!1){let createUserRoutes=createCreateUserRoute(authConfig,config.admin?.createUser);useRoutes(app,createUserRoutes)}if(useRoutes(app,createPasswordAdminRoutes(authConfig,{passwordTools:config.admin?.passwordTools?.enabled===!0})),useRoutes(app,createUserStateRoutes(authConfig)),config.admin?.hardDelete?.enabled!==!1){let hardDeleteRoutes=createHardDeleteUserRoute(authConfig);useRoutes(app,hardDeleteRoutes)}return app}var init_auth=__esm(()=>{init_OAuth();init_routeMount();init_utils5();init_changeUserId();init_createUser();init_hardDeleteUser();init_impersonate();init_passwordAdmin();init_userState();init_apiKeys();init_captcha();init_check();init_emailVerification();init_invite();init_login();init_logout();init_magicLink();init_me();init_oauth();init_passwordChange();init_passwordReset();init_passwordSet();init_refresh();init_register();init_sessions();init_webauthn();init_changeUserId();init_createUser();init_hardDeleteUser();init_impersonate();init_apiKeys();init_emailVerification();init_invite();init_login();init_logout();init_magicLink();init_me();init_passwordChange();init_passwordReset();init_passwordSet();init_refresh();init_register();init_sessions();init_webauthn()});var exports_backup={};__export(exports_backup,{createBackupRoutes:()=>createBackupRoutes});function createBackupRoutes(routeConfig){let{db,logger:logger2,schemaTables,schemaName,getTenantRegistry,lock}=routeConfig,config={...DEFAULT_BACKUP_CONFIG,...routeConfig.config},basePath=config.basePath,backupLogsTable=schemaTables.backupLogs||schemaTables.backup_logs,backupService=new BackupService({db,logger:logger2,config,schemaTables,schemaName,backupLogsTable,lock});if(config.schedule.enabled)backupService.startScheduler();let guard4=createGodminGuard2({db,schemaTables,logger:logger2},"backup management"),resolveTargetSchema=(targetSchema)=>{if(!targetSchema||targetSchema===schemaName)return{ok:!0,name:schemaName,tables:schemaTables};let tenantCtx=getTenantRegistry?.()?.getSchemaContext(targetSchema);if(!tenantCtx)return{ok:!1,message:`Schema not found: ${targetSchema}`};return{ok:!0,name:targetSchema,tables:tenantCtx.schemaTables}},declared=[defineRoute({method:"POST",path:basePath,handler:async(ctx)=>{let userId=ctx.request.headers.get("x-user-id");if(!userId)return ctx.set.status=401,{success:!1,message:"Authentication required"};let body=ctx.body,target2=resolveTargetSchema(body?.schema);if(!target2.ok)return ctx.set.status=404,{success:!1,message:target2.message};let result=await backupService.createBackup("manual",userId,target2.name,target2.tables);if(result.status==="failed")return ctx.set.status=500,{success:!1,message:result.errorMessage||"Backup failed"};return{success:!0,message:"Backup created successfully",data:{id:result.id,backupName:result.backupName,schemaName:result.schemaName,tableCount:result.tableCount,rowCount:result.rowCount,sizeBytes:result.sizeBytes,status:result.status}}},body:Type.Optional(Type.Object({schema:Type.Optional(Type.String())})),detail:{tags:["Backup"],summary:"Create backup",description:"Create a new database backup. Optionally specify a schema for multi-tenant backups."}}),defineRoute({method:"GET",path:basePath,handler:async(ctx)=>{if(!ctx.request.headers.get("x-user-id"))return ctx.set.status=401,{success:!1,message:"Authentication required",data:null};let backups=await backupService.listBackups();return{success:!0,data:{items:backups,totalCount:backups.length}}},detail:{tags:["Backup"],summary:"List backups",description:"List all backup records."}}),defineRoute({method:"GET",path:`${basePath}/:id/download`,handler:async(ctx)=>{if(!ctx.request.headers.get("x-user-id"))return ctx.set.status=401,{success:!1,message:"Authentication required"};let{id}=ctx.params,filePath=await backupService.getBackupFilePath(id);if(!filePath)return ctx.set.status=404,{success:!1,message:"Backup not found"};try{let{readFile:readFile2}=await import("fs/promises"),content=await readFile2(filePath,"utf-8");return new Response(content,{headers:{"Content-Type":"application/json","Content-Disposition":`attachment; filename="${id}.json"`}})}catch{return ctx.set.status=404,{success:!1,message:"Backup file not found on disk"}}},detail:{tags:["Backup"],summary:"Download backup",description:"Download a backup file by ID."}}),defineRoute({method:"POST",path:`${basePath}/:id/restore`,handler:async(ctx)=>{let userId=ctx.request.headers.get("x-user-id");if(!userId)return ctx.set.status=401,{success:!1,message:"Authentication required"};if(!config.allowRestore)return ctx.set.status=403,{success:!1,message:"Restore is disabled in configuration"};let{id}=ctx.params,restoreBody=ctx.body,target2=resolveTargetSchema(restoreBody?.schema);if(!target2.ok)return ctx.set.status=404,{success:!1,message:target2.message};let result=await backupService.restoreFromBackup(id,userId,target2.name,target2.tables);if(!result.success)return ctx.set.status=400,result;return await logger2.audit({entityName:"backup_logs",entityId:id,operation:"RESTORE",userId,summary:`Database restored from backup ${id}`,ipAddress:ctx.request.headers.get("x-forwarded-for")?.split(",")[0]?.trim()||"unknown",userAgent:ctx.request.headers.get("user-agent")||"unknown",path:new URL(ctx.request.url).pathname,query:""}),result},detail:{tags:["Backup"],summary:"Restore from backup",description:"Restore database from a specific backup. Creates a pre-restore backup automatically."}}),defineRoute({method:"DELETE",path:`${basePath}/:id`,handler:async(ctx)=>{if(!ctx.request.headers.get("x-user-id"))return ctx.set.status=401,{success:!1,message:"Authentication required"};let{id}=ctx.params;if(!await backupService.deleteBackup(id))return ctx.set.status=404,{success:!1,message:"Backup not found"};return{success:!0,message:"Backup deleted"}},detail:{tags:["Backup"],summary:"Delete backup",description:"Delete a specific backup and its file."}})];return{routes:collectRoutes([{guards:[guard4],routes:declared}]),backupService}}var DEFAULT_BACKUP_CONFIG;var init_backup=__esm(()=>{init_esm3();init_Backup();init_routeCollector();init_adminGuard();DEFAULT_BACKUP_CONFIG={enabled:!1,basePath:"/admin/backup",storagePath:"./backups",format:"json",maxBackups:50,allowRestore:!0,excludeTables:["audit_logs","backup_logs"],schedule:{enabled:!1,cron:"0 2 * * *",retentionDays:30}}});var validateAmount=(v,field="amount",opts={})=>{if(typeof v!=="number"||!Number.isFinite(v))return{ok:!1,error:`${field} must be a finite number`};if(opts.allowZero?v<0:v<=0)return{ok:!1,error:`${field} must be ${opts.allowZero?">= 0":"> 0"}`};return{ok:!0,value:v}},isValidCurrency=(v)=>v===void 0||v===null||typeof v==="string"&&/^[A-Za-z]{3}$/.test(v);import{eq as eq64}from"drizzle-orm";var col15=(table,name2)=>table[name2],str4=(v)=>typeof v==="string"?v:null,num3=(v)=>typeof v==="number"?v:null,handleChargeRefunded=async(deps,obj)=>{let candidates=[str4(obj.id),str4(obj.payment_intent)].filter((v)=>v!==null);for(let candidate of candidates){let updated=await deps.db.update(deps.transactionsTable).set({status:"refunded",refundedAmount:num3(obj.amount_refunded)??void 0,refundedAt:new Date,statusMessage:"Refunded via Stripe webhook",updatedAt:new Date}).where(eq64(col15(deps.transactionsTable,"providerPaymentId"),candidate)).returning();if(Array.isArray(updated)&&updated.length>0)return}},handleDisputeCreated=async(deps,obj)=>{let payoutService=deps.getPayoutService?.();if(!payoutService)return;let metadata=obj.metadata??{};await payoutService.openDispute({provider:"stripe",providerDisputeId:str4(obj.id)??void 0,ownerType:str4(metadata.ownerType)==="seller"?"seller":void 0,ownerId:str4(metadata.ownerId)??void 0,amount:num3(obj.amount)??void 0,currency:str4(obj.currency)?.toUpperCase(),reason:str4(obj.reason)??void 0})},handleDisputeClosed=async(deps,obj)=>{let payoutService=deps.getPayoutService?.(),providerId=str4(obj.id);if(!payoutService||!providerId)return;await payoutService.reconcileDisputeByProvider(providerId,str4(obj.status)??"")},handlePayoutOutcome=async(deps,obj,outcome,reason)=>{let payoutService=deps.getPayoutService?.(),providerId=str4(obj.id);if(!payoutService||!providerId)return;await payoutService.reconcilePayoutByProvider(providerId,outcome,reason)},dispatch=async(deps,eventType,obj)=>{switch(eventType){case"charge.refunded":await handleChargeRefunded(deps,obj);break;case"charge.dispute.created":await handleDisputeCreated(deps,obj);break;case"charge.dispute.closed":case"charge.dispute.funds_reinstated":await handleDisputeClosed(deps,obj);break;case"payout.paid":await handlePayoutOutcome(deps,obj,"completed");break;case"payout.failed":case"payout.canceled":await handlePayoutOutcome(deps,obj,"failed",str4(obj.failure_message)??eventType);break;case"transfer.reversed":await handlePayoutOutcome(deps,obj,"failed","transfer reversed");break;default:break}},createStripeWebhookRoute=(deps)=>{return defineRoute({method:"POST",path:"/webhook",handler:async(ctx)=>{if(deps.provider.name!=="stripe")return ctx.set.status=501,{received:!1,error:"Webhooks are only supported for the Stripe provider"};if(!deps.webhookSecret)return deps.logger.error("[Payment] Webhook received but STRIPE_WEBHOOK_SECRET is not configured"),ctx.set.status=500,{received:!1,error:"Webhook secret not configured"};let rawBody=await ctx.request.text(),signature=ctx.request.headers.get("stripe-signature")??"",verified=await verifyStripeWebhook(rawBody,signature,deps.webhookSecret);if(!verified.success||!verified.event)return deps.logger.warn("[Payment] Webhook signature verification failed",{error:verified.error}),ctx.set.status=400,{received:!1,error:"Invalid signature"};let event=verified.event,eventId=str4(event.id)??"",eventType=str4(event.type)??"",obj=event.data?.object??{},idempotencyKey=`stripe:${eventId}`,inserted=await deps.db.insert(deps.webhookLogsTable).values({provider:"stripe",eventType,providerPaymentId:str4(obj.payment_intent)??str4(obj.id),rawPayload:event,processed:!1,idempotencyKey,ipAddress:ctx.request.headers.get("x-forwarded-for")??ctx.request.headers.get("x-real-ip")??null}).onConflictDoNothing().returning();if(!Array.isArray(inserted)||inserted.length===0)return{received:!0,duplicate:!0,eventType};try{return await dispatch(deps,eventType,obj),await deps.db.update(deps.webhookLogsTable).set({processed:!0,processingResult:{ok:!0},updatedAt:new Date}).where(eq64(col15(deps.webhookLogsTable,"idempotencyKey"),idempotencyKey)),deps.logger.info("[Payment] Webhook processed",{eventType,eventId}),{received:!0,eventType}}catch(err){let message=err instanceof Error?err.message:String(err);return deps.logger.error("[Payment] Webhook handler failed",{eventType,error:message}),await deps.db.update(deps.webhookLogsTable).set({errorMessage:message,updatedAt:new Date}).where(eq64(col15(deps.webhookLogsTable,"idempotencyKey"),idempotencyKey)).catch(()=>{}),ctx.set.status=500,{received:!1,error:"Handler error"}}}})};var init_webhook=__esm(()=>{init_Payment()});var exports_payment={};__export(exports_payment,{createPaymentRoutes:()=>createPaymentRoutes});import{and as and27,eq as eq65,inArray as inArray19,ne as ne3,or as or5,sql as sql12}from"drizzle-orm";var escapeHtml2=(value2)=>value2.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""").replace(/'/g,"'"),generateRedirectHtml=(url,title)=>{let safeTitle=escapeHtml2(title),safeUrlAttr=escapeHtml2(url),safeUrlJs=JSON.stringify(url).replace(/</g,"\\u003c").replace(/>/g,"\\u003e");return`
|
|
526
526
|
<!DOCTYPE html>
|
|
527
527
|
<html>
|
|
528
528
|
<head>
|
|
@@ -535,16 +535,16 @@ ${strMDSAlgs}`)}if(attestationStatementAlg!==void 0&&authenticatorGetInfo?.algor
|
|
|
535
535
|
<p>Redirecting...</p>
|
|
536
536
|
</body>
|
|
537
537
|
</html>
|
|
538
|
-
`},UUID_PATTERN,CALLBACK_WRITABLE_STATUSES,CALLBACK_COMPLETABLE_STATUSES,createPaymentRoutes=(config)=>{let{provider,basePath,defaultCurrency,defaultLocale,successRedirectUrl,failedRedirectUrl,errorRedirectUrl,savedMethodsEnabled,threeDSecureEnabled,subMerchantsEnabled,transactionsTable,methodsTable,webhookLogsTable,subMerchantsTable,commissionSplitsTable,productsTable,pricesTable,customersTable,subscriptionsTable,invoicesTable,db,logger:logger2}=config,txTable=transactionsTable,pmTable=methodsTable,whTable=webhookLogsTable,smTable=subMerchantsTable,csTable=commissionSplitsTable,prodTable=productsTable,priceTable=pricesTable,cusTable=customersTable,subTable=subscriptionsTable,invTable=invoicesTable,database=db,declared=[],app=collectRoutes(),customerBelongsToCaller=async(request,customerId)=>{if(hasGodminRole(request))return!0;let uid=request.headers.get("x-user-id");if(!uid||!customerId)return!1;let[c]=await database.select().from(cusTable).where(eq65(cusTable.id,String(customerId)));return!!c&&String(c.userId)===String(uid)},callerCustomerIds=async(uid)=>{return(await database.select().from(cusTable).where(eq65(cusTable.userId,uid))).map((r)=>String(r.id))},routePostInitialize=defineRoute({method:"POST",path:"/initialize",handler:async(ctx)=>{if(!threeDSecureEnabled)return ctx.set.status=400,{success:!1,error:"3D Secure is not enabled"};let userId=ctx.request.headers.get("x-user-id"),body=ctx.body;try{let[transaction]=await database.insert(txTable).values({userId:userId??null,orderId:body.orderId??body.conversationId??null,provider:provider.name,amount:Number(body.paidPrice??body.price??0),currency:body.currency??defaultCurrency,status:"pending",statusCode:1000,statusMessage:"3DS initialization started",isThreeDSecure:!0,installment:body.installment??1,ipAddress:ctx.request.headers.get("x-forwarded-for")??ctx.request.headers.get("x-real-ip")??null,metadata:body.metadata??{}}).returning(),result=await provider.initialize3DS({locale:body.locale??defaultLocale,conversationId:transaction.id,price:body.price,paidPrice:body.paidPrice,currency:body.currency??defaultCurrency,installment:body.installment,paymentChannel:body.paymentChannel,basketId:body.basketId,paymentGroup:body.paymentGroup,paymentCard:body.paymentCard,buyer:body.buyer,shippingAddress:body.shippingAddress,billingAddress:body.billingAddress,basketItems:body.basketItems,callbackUrl:body.callbackUrl??config.callbackUrl??`${basePath}/callback`});if(!result.success)return await database.update(txTable).set({status:"failed",statusCode:1003,statusMessage:result.errorMessage??"Initialization failed",providerResponse:result.rawResponse??{},failedAt:new Date}).where(eq65(txTable.id,transaction.id)),ctx.set.status=400,{success:!1,error:result.errorMessage??"Payment initialization failed",errorCode:result.errorCode};return await database.update(txTable).set({status:"processing",statusCode:1001,statusMessage:"3DS initialized, awaiting bank verification",providerPaymentId:result.paymentId,providerResponse:result.rawResponse??{}}).where(eq65(txTable.id,transaction.id)),{success:!0,transactionId:transaction.id,threeDSHtmlContent:result.threeDSHtmlContent,paymentId:result.paymentId}}catch(error3){return logger2.error("[Payment] initialize3DS error",{error:error3 instanceof Error?error3.message:String(error3)}),ctx.set.status=500,{success:!1,error:"Internal payment error"}}}}),routePostCallback=defineRoute({method:"POST",path:"/callback",handler:async(ctx)=>{let requestOrigin=new URL(ctx.request.url).origin,htmlHeaders={"Content-Type":"text/html; charset=utf-8","Access-Control-Allow-Origin":requestOrigin,"Access-Control-Allow-Methods":"POST, OPTIONS"};try{let parsedBody=ctx.body??{},rawData={};for(let[key2,value2]of Object.entries(parsedBody))rawData[key2]=String(value2);let parsed=provider.parseCallback(rawData);if(!parsed.paymentId)return logger2.warn("[Payment] 3DS callback refused: the body named no payment",{fields:Object.keys(rawData)}),new Response(generateRedirectHtml(`${requestOrigin}${errorRedirectUrl}`,"Payment Error"),{status:400,headers:htmlHeaders});if(await database.insert(whTable).values({provider:provider.name,eventType:"three_ds_callback",providerPaymentId:parsed.paymentId,rawPayload:rawData,processed:!1,idempotencyKey:`${provider.name}:${parsed.paymentId}:${parsed.mdStatus}`,ipAddress:ctx.request.headers.get("x-forwarded-for")??ctx.request.headers.get("x-real-ip")??null}).onConflictDoNothing(),!parsed.success){if(logger2.warn("[Payment] 3DS callback failed",{mdStatus:parsed.mdStatus,paymentId:parsed.paymentId}),parsed.conversationId)await database.update(txTable).set({status:"failed",statusCode:1003,statusMessage:`3DS verification failed: mdStatus=${parsed.mdStatus}`,failedAt:new Date}).where(and27(eq65(txTable.id,parsed.conversationId),inArray18(txTable.status,CALLBACK_WRITABLE_STATUSES))).catch(()=>{});return new Response(generateRedirectHtml(`${requestOrigin}${failedRedirectUrl}`,"Payment Failed"),{headers:htmlHeaders})}let completion=await provider.complete3DS({locale:defaultLocale,conversationId:parsed.conversationId,paymentId:parsed.paymentId});if(!completion.success){if(logger2.error("[Payment] 3DS completion failed",{paymentId:parsed.paymentId,errorCode:completion.errorCode,errorMessage:completion.errorMessage}),parsed.conversationId)await database.update(txTable).set({status:"failed",statusCode:1003,statusMessage:`3DS completion failed: ${completion.errorMessage}`,providerResponse:completion.rawResponse??{},failedAt:new Date}).where(and27(eq65(txTable.id,parsed.conversationId),inArray18(txTable.status,CALLBACK_WRITABLE_STATUSES))).catch(()=>{});return new Response(generateRedirectHtml(`${requestOrigin}${failedRedirectUrl}`,"Payment Failed"),{headers:htmlHeaders})}if(parsed.conversationId)await database.update(txTable).set({status:"completed",statusCode:1001,statusMessage:"Payment completed successfully",providerPaymentId:completion.paymentId??parsed.paymentId,providerTransactionId:completion.signature,amount:completion.paidPrice??0,currency:completion.currency??defaultCurrency,paymentMethod:completion.cardAssociation,cardLastFour:completion.lastFourDigits,cardType:completion.cardType,cardAssociation:completion.cardAssociation,installment:completion.installment??1,providerResponse:completion.rawResponse??{},completedAt:new Date}).where(and27(eq65(txTable.id,parsed.conversationId),inArray18(txTable.status,CALLBACK_COMPLETABLE_STATUSES))).catch(()=>{});await database.update(whTable).set({processed:!0,processingResult:{completion}}).where(eq65(whTable.idempotencyKey,`${provider.name}:${parsed.paymentId}:${parsed.mdStatus}`)).catch(()=>{}),logger2.info("[Payment] 3DS payment completed",{paymentId:completion.paymentId,amount:completion.paidPrice,currency:completion.currency});let queryParams=new URLSearchParams;if(parsed.conversationId)queryParams.append("transactionId",parsed.conversationId);if(completion.paymentId)queryParams.append("paymentId",completion.paymentId);if(completion.paidPrice)queryParams.append("amount",String(completion.paidPrice));let redirectUrl=`${requestOrigin}${successRedirectUrl}?${queryParams.toString()}`;return new Response(generateRedirectHtml(redirectUrl,"Payment Successful"),{headers:htmlHeaders})}catch(error3){return logger2.error("[Payment] callback error",{error:error3 instanceof Error?error3.message:String(error3)}),new Response(generateRedirectHtml(`${requestOrigin}${errorRedirectUrl}`,"Payment Error"),{headers:htmlHeaders})}}}),routeGetTransactions=defineRoute({method:"GET",path:"/transactions",handler:async(ctx)=>{let userId=ctx.request.headers.get("x-user-id");if(!userId)return ctx.set.status=401,{success:!1,error:"Unauthorized"};try{return{success:!0,data:{items:await database.select().from(txTable).where(eq65(txTable.userId,userId)).orderBy(txTable.createdAt)}}}catch(error3){return logger2.error("[Payment] list transactions error",{error:error3 instanceof Error?error3.message:String(error3)}),ctx.set.status=500,{success:!1,error:"Failed to list transactions"}}}}),routeGetTransactionsId=defineRoute({method:"GET",path:"/transactions/:id",handler:async(ctx)=>{let userId=ctx.request.headers.get("x-user-id");if(!userId)return ctx.set.status=401,{success:!1,error:"Unauthorized"};try{let[transaction]=await database.select().from(txTable).where(and27(eq65(txTable.id,ctx.params.id),eq65(txTable.userId,userId))).limit(1);if(!transaction)return ctx.set.status=404,{success:!1,error:"Transaction not found"};return{success:!0,data:transaction}}catch{return ctx.set.status=500,{success:!1,error:"Failed to get transaction"}}}}),routePostBinQuery=defineRoute({method:"POST",path:"/bin-query",handler:async(ctx)=>{let body=ctx.body,binNumber=body.binNumber??body.cardNumber;if(!binNumber||typeof binNumber!=="string")return ctx.set.status=400,{success:!1,error:"binNumber is required"};try{let result=await provider.queryBin({locale:body.locale??defaultLocale,binNumber,price:body.price});if(!result.success)return ctx.set.status=400,{success:!1,error:result.errorMessage??"BIN query failed",errorCode:result.errorCode};return{success:!0,data:result}}catch{return ctx.set.status=500,{success:!1,error:"BIN query failed"}}}}),routePostPaymentDetail=defineRoute({method:"POST",path:"/payment-detail",handler:async(ctx)=>{let userId=ctx.request.headers.get("x-user-id");if(!userId)return ctx.set.status=401,{success:!1,error:"Unauthorized"};let body=ctx.body;if(!body.paymentId)return ctx.set.status=400,{success:!1,error:"paymentId is required"};if(!hasGodminRole(ctx.request)){let keys=[eq65(txTable.providerPaymentId,String(body.paymentId))],conversation=body.conversationId??body.paymentConversationId;if(conversation!=null){let candidate=String(conversation);if(UUID_PATTERN.test(candidate))keys.push(eq65(txTable.id,candidate));keys.push(eq65(txTable.orderId,candidate))}let owned;try{let[transaction]=await database.select().from(txTable).where(and27(or5(...keys),eq65(txTable.userId,userId))).limit(1);owned=transaction}catch(error3){return logger2.error("[Payment] payment-detail ownership lookup failed",{error:error3 instanceof Error?error3.message:String(error3)}),ctx.set.status=500,{success:!1,error:"Payment detail query failed"}}if(!owned)return ctx.set.status=404,{success:!1,error:"Transaction not found"}}try{let result=await provider.getPaymentDetail({locale:body.locale??defaultLocale,paymentId:body.paymentId,conversationId:body.conversationId,paymentConversationId:body.paymentConversationId});return{success:result.success,data:result}}catch{return ctx.set.status=500,{success:!1,error:"Payment detail query failed"}}}}),routePostRefund=defineRoute({method:"POST",path:"/refund",handler:async(ctx)=>{let userId=ctx.request.headers.get("x-user-id");if(!userId)return ctx.set.status=401,{success:!1,error:"Unauthorized"};let body=ctx.body;if(!body.paymentTransactionId||!body.price)return ctx.set.status=400,{success:!1,error:"paymentTransactionId and price are required"};let refundAmt=validateAmount(body.price,"price");if(!refundAmt.ok)return ctx.set.status=400,{success:!1,error:refundAmt.error};if(!hasGodminRole(ctx.request)){if(!body.transactionId)return ctx.set.status=400,{success:!1,error:"transactionId is required"};let[tx]=await database.select().from(txTable).where(eq65(txTable.id,body.transactionId)).limit(1);if(!tx)return ctx.set.status=404,{success:!1,error:"Transaction not found"};if(String(tx.userId)!==String(userId))return ctx.set.status=403,{success:!1,error:"You can only refund your own transactions"};let ownedProviderId=tx.providerTransactionId??tx.providerPaymentId;if(ownedProviderId&&String(body.paymentTransactionId)!==String(ownedProviderId))return ctx.set.status=403,{success:!1,error:"paymentTransactionId does not match this transaction"};let refundable=Number(tx.amount??0)-Number(tx.refundedAmount??0);if(!(Number(body.price)>0)||Number(body.price)>refundable)return ctx.set.status=400,{success:!1,error:`Refund amount exceeds the refundable balance (${refundable})`}}try{let result=await provider.refund({locale:body.locale??defaultLocale,conversationId:body.conversationId,paymentTransactionId:body.paymentTransactionId,price:String(body.price),currency:body.currency??defaultCurrency,ip:ctx.request.headers.get("x-forwarded-for")??ctx.request.headers.get("x-real-ip")??void 0});if(!result.success)return ctx.set.status=400,{success:!1,error:result.errorMessage??"Refund failed",errorCode:result.errorCode};if(body.transactionId){let price=Number(body.price);await database.update(txTable).set({status:sql12`CASE WHEN COALESCE(${txTable.refundedAmount}, 0) + ${price} >= ${txTable.amount} THEN 'refunded' ELSE 'partially_refunded' END`,refundedAmount:sql12`COALESCE(${txTable.refundedAmount}, 0) + ${price}`,refundedAt:new Date,statusMessage:"Payment refunded"}).where(eq65(txTable.id,body.transactionId)).catch(()=>{})}return logger2.info("[Payment] Refund processed",{paymentId:result.paymentId,price:result.price}),{success:!0,data:result}}catch{return ctx.set.status=500,{success:!1,error:"Refund failed"}}}});if(savedMethodsEnabled){let routePostCardsSave=defineRoute({method:"POST",path:"/cards/save",handler:async(ctx)=>{let userId=ctx.request.headers.get("x-user-id");if(!userId)return ctx.set.status=401,{success:!1,error:"Unauthorized"};let body=ctx.body;if(!body.email||!body.card)return ctx.set.status=400,{success:!1,error:"email and card are required"};try{let result=await provider.saveCard({locale:body.locale??defaultLocale,email:body.email,externalId:userId,card:body.card});if(!result.success)return ctx.set.status=400,{success:!1,error:result.errorMessage??"Failed to save card",errorCode:result.errorCode};let[saved]=await database.insert(pmTable).values({userId,provider:provider.name,type:"card",alias:result.cardAlias??body.card.cardAlias,cardLastFour:result.lastFourDigits,cardType:result.cardType,cardAssociation:result.cardAssociation,cardFamily:result.cardFamily,cardBankName:result.cardBankName,providerCardUserKey:result.cardUserKey,providerCardToken:result.cardToken,binNumber:result.binNumber}).returning();return logger2.info("[Payment] Card saved",{userId,cardAlias:result.cardAlias}),{success:!0,data:saved}}catch{return ctx.set.status=500,{success:!1,error:"Failed to save card"}}}});declared.push(routePostCardsSave);let routeGetCards=defineRoute({method:"GET",path:"/cards",handler:async(ctx)=>{let userId=ctx.request.headers.get("x-user-id");if(!userId)return ctx.set.status=401,{success:!1,error:"Unauthorized"};try{return{success:!0,data:{items:await database.select().from(pmTable).where(and27(eq65(pmTable.userId,userId),eq65(pmTable.isActive,!0))).orderBy(pmTable.createdAt)}}}catch{return ctx.set.status=500,{success:!1,error:"Failed to list cards"}}}});declared.push(routeGetCards);let routeDeleteCardsId=defineRoute({method:"DELETE",path:"/cards/:id",handler:async(ctx)=>{let userId=ctx.request.headers.get("x-user-id");if(!userId)return ctx.set.status=401,{success:!1,error:"Unauthorized"};try{let[card]=await database.select().from(pmTable).where(and27(eq65(pmTable.id,ctx.params.id),eq65(pmTable.userId,userId))).limit(1);if(!card)return ctx.set.status=404,{success:!1,error:"Card not found"};if(card.providerCardUserKey&&card.providerCardToken){let deleteResult=await provider.deleteCard({locale:defaultLocale,cardUserKey:card.providerCardUserKey,cardToken:card.providerCardToken});if(!deleteResult.success)logger2.warn("[Payment] Provider card delete failed (proceeding with local delete)",{errorCode:deleteResult.errorCode,errorMessage:deleteResult.errorMessage})}return await database.update(pmTable).set({isActive:!1}).where(eq65(pmTable.id,ctx.params.id)),logger2.info("[Payment] Card deleted",{userId,cardId:ctx.params.id}),{success:!0}}catch{return ctx.set.status=500,{success:!1,error:"Failed to delete card"}}}});declared.push(routeDeleteCardsId);let routePostCardsSync=defineRoute({method:"POST",path:"/cards/sync",handler:async(ctx)=>{if(!ctx.request.headers.get("x-user-id"))return ctx.set.status=401,{success:!1,error:"Unauthorized"};let body=ctx.body,cardUserKey=body.cardUserKey;if(!cardUserKey)return ctx.set.status=400,{success:!1,error:"cardUserKey is required"};try{let result=await provider.listCards({locale:body.locale??defaultLocale,cardUserKey});if(!result.success)return ctx.set.status=400,{success:!1,error:result.errorMessage??"Failed to list cards from provider"};return{success:!0,data:{cardUserKey:result.cardUserKey,cards:result.cards}}}catch{return ctx.set.status=500,{success:!1,error:"Failed to sync cards"}}}});declared.push(routePostCardsSync)}let routePostProducts=defineRoute({method:"POST",path:"/products",handler:async(ctx)=>{if(!ctx.request.headers.get("x-user-id"))return ctx.set.status=401,{success:!1,error:"Unauthorized"};if(!hasGodminRole(ctx.request))return ctx.set.status=403,{success:!1,error:"Forbidden: operator privileges required"};let body=ctx.body;if(!body.name)return ctx.set.status=400,{success:!1,error:"name is required"};try{let providerResult=await provider.createProduct({name:body.name,description:body.description,type:body.type,images:body.images,unitLabel:body.unitLabel,url:body.url,metadata:body.metadata});if(!providerResult.success)return ctx.set.status=400,{success:!1,error:providerResult.errorMessage??"Failed to create product at provider",errorCode:providerResult.errorCode};let[saved]=await database.insert(prodTable).values({provider:provider.name,providerProductId:providerResult.providerProductId,name:body.name,description:body.description??null,type:body.type??"service",status:"active",images:body.images?JSON.stringify(body.images):"[]",unitLabel:body.unitLabel??null,url:body.url??null,metadata:body.metadata?JSON.stringify(body.metadata):"{}"}).returning();return logger2.info("[Payment] Product created",{id:saved.id,providerProductId:providerResult.providerProductId}),{success:!0,data:saved}}catch(error3){return logger2.error("[Payment] create product error",{error:error3 instanceof Error?error3.message:String(error3)}),ctx.set.status=500,{success:!1,error:"Failed to create product"}}}}),routePutProductsId=defineRoute({method:"PUT",path:"/products/:id",handler:async(ctx)=>{if(!ctx.request.headers.get("x-user-id"))return ctx.set.status=401,{success:!1,error:"Unauthorized"};if(!hasGodminRole(ctx.request))return ctx.set.status=403,{success:!1,error:"Forbidden: operator privileges required"};let body=ctx.body;try{let[existing]=await database.select().from(prodTable).where(eq65(prodTable.id,ctx.params.id)).limit(1);if(!existing)return ctx.set.status=404,{success:!1,error:"Product not found"};if(existing.providerProductId){let providerResult=await provider.updateProduct({providerProductId:existing.providerProductId,name:body.name,description:body.description,images:body.images,unitLabel:body.unitLabel,url:body.url,active:body.status==="active"?!0:body.status==="archived"?!1:void 0,metadata:body.metadata});if(!providerResult.success)logger2.warn("[Payment] Provider product update failed",{errorCode:providerResult.errorCode})}let updateData={};if(body.name)updateData.name=body.name;if(body.description!==void 0)updateData.description=body.description;if(body.type)updateData.type=body.type;if(body.status)updateData.status=body.status;if(body.images)updateData.images=JSON.stringify(body.images);if(body.unitLabel!==void 0)updateData.unitLabel=body.unitLabel;if(body.url!==void 0)updateData.url=body.url;if(body.metadata)updateData.metadata=JSON.stringify(body.metadata);updateData.updatedAt=new Date;let[updated]=await database.update(prodTable).set(updateData).where(eq65(prodTable.id,ctx.params.id)).returning();return{success:!0,data:updated}}catch{return ctx.set.status=500,{success:!1,error:"Failed to update product"}}}}),routeGetProducts=defineRoute({method:"GET",path:"/products",handler:async(ctx)=>{if(!ctx.request.headers.get("x-user-id"))return ctx.set.status=401,{success:!1,error:"Unauthorized"};try{let status=new URL(ctx.request.url).searchParams.get("status"),query=database.select().from(prodTable).where(eq65(prodTable.isActive,!0));if(status)query=database.select().from(prodTable).where(and27(eq65(prodTable.isActive,!0),eq65(prodTable.status,status)));return{success:!0,data:{items:await query.orderBy(prodTable.createdAt)}}}catch{return ctx.set.status=500,{success:!1,error:"Failed to list products"}}}}),routeGetProductsId=defineRoute({method:"GET",path:"/products/:id",handler:async(ctx)=>{if(!ctx.request.headers.get("x-user-id"))return ctx.set.status=401,{success:!1,error:"Unauthorized"};try{let[item]=await database.select().from(prodTable).where(eq65(prodTable.id,ctx.params.id)).limit(1);if(!item)return ctx.set.status=404,{success:!1,error:"Product not found"};let prices=await database.select().from(priceTable).where(and27(eq65(priceTable.productId,ctx.params.id),eq65(priceTable.isActive,!0),ne3(priceTable.status,"archived"))).orderBy(priceTable.createdAt);return{success:!0,data:{...item,prices}}}catch{return ctx.set.status=500,{success:!1,error:"Failed to get product"}}}}),routeDeleteProductsId=defineRoute({method:"DELETE",path:"/products/:id",handler:async(ctx)=>{if(!ctx.request.headers.get("x-user-id"))return ctx.set.status=401,{success:!1,error:"Unauthorized"};if(!hasGodminRole(ctx.request))return ctx.set.status=403,{success:!1,error:"Forbidden: operator privileges required"};try{let[existing]=await database.select().from(prodTable).where(eq65(prodTable.id,ctx.params.id)).limit(1);if(!existing)return ctx.set.status=404,{success:!1,error:"Product not found"};if(existing.providerProductId)await provider.archiveProduct({providerProductId:existing.providerProductId});let[updated]=await database.update(prodTable).set({status:"archived",updatedAt:new Date}).where(eq65(prodTable.id,ctx.params.id)).returning();return{success:!0,data:updated}}catch{return ctx.set.status=500,{success:!1,error:"Failed to archive product"}}}}),routePostPrices=defineRoute({method:"POST",path:"/prices",handler:async(ctx)=>{if(!ctx.request.headers.get("x-user-id"))return ctx.set.status=401,{success:!1,error:"Unauthorized"};if(!hasGodminRole(ctx.request))return ctx.set.status=403,{success:!1,error:"Forbidden: operator privileges required"};let body=ctx.body;if(!body.productId||body.unitAmount===void 0||!body.currency)return ctx.set.status=400,{success:!1,error:"productId, unitAmount, and currency are required"};try{let[product]=await database.select().from(prodTable).where(eq65(prodTable.id,body.productId)).limit(1);if(!product)return ctx.set.status=404,{success:!1,error:"Product not found"};let providerResult=await provider.createPrice({providerProductId:product.providerProductId,currency:body.currency,unitAmount:body.unitAmount,type:body.type,billingScheme:body.billingScheme,nickname:body.nickname,recurring:body.recurring,transformQuantity:body.transformQuantity,unitAmountDecimal:body.unitAmountDecimal,metadata:body.metadata});if(!providerResult.success)return ctx.set.status=400,{success:!1,error:providerResult.errorMessage??"Failed to create price at provider",errorCode:providerResult.errorCode};let[saved]=await database.insert(priceTable).values({productId:body.productId,provider:provider.name,providerPriceId:providerResult.providerPriceId,currency:body.currency,unitAmount:body.unitAmount,unitAmountDecimal:body.unitAmountDecimal??null,type:body.type??"one_time",billingScheme:body.billingScheme??"per_unit",nickname:body.nickname??null,recurringInterval:body.recurring?.interval??null,recurringIntervalCount:body.recurring?.intervalCount??1,recurringUsageType:body.recurring?.usageType??"licensed",recurringAggregateUsage:body.recurring?.aggregateUsage??null,transformQuantityDivideBy:body.transformQuantity?.divideBy??null,transformQuantityRound:body.transformQuantity?.round??null,status:"active",metadata:body.metadata?JSON.stringify(body.metadata):"{}"}).returning();return logger2.info("[Payment] Price created",{id:saved.id,providerPriceId:providerResult.providerPriceId}),{success:!0,data:saved}}catch(error3){return logger2.error("[Payment] create price error",{error:error3 instanceof Error?error3.message:String(error3)}),ctx.set.status=500,{success:!1,error:"Failed to create price"}}}}),routePutPricesId=defineRoute({method:"PUT",path:"/prices/:id",handler:async(ctx)=>{if(!ctx.request.headers.get("x-user-id"))return ctx.set.status=401,{success:!1,error:"Unauthorized"};if(!hasGodminRole(ctx.request))return ctx.set.status=403,{success:!1,error:"Forbidden: operator privileges required"};let body=ctx.body;try{let[existing]=await database.select().from(priceTable).where(eq65(priceTable.id,ctx.params.id)).limit(1);if(!existing)return ctx.set.status=404,{success:!1,error:"Price not found"};if(existing.providerPriceId){let providerResult=await provider.updatePrice({providerPriceId:existing.providerPriceId,nickname:body.nickname,active:body.status==="active"?!0:body.status==="archived"?!1:void 0,metadata:body.metadata});if(!providerResult.success)logger2.warn("[Payment] Provider price update failed",{errorCode:providerResult.errorCode})}let updateData={};if(body.nickname!==void 0)updateData.nickname=body.nickname;if(body.status)updateData.status=body.status;if(body.metadata)updateData.metadata=JSON.stringify(body.metadata);updateData.updatedAt=new Date;let[updated]=await database.update(priceTable).set(updateData).where(eq65(priceTable.id,ctx.params.id)).returning();return{success:!0,data:updated}}catch{return ctx.set.status=500,{success:!1,error:"Failed to update price"}}}}),routeGetPrices=defineRoute({method:"GET",path:"/prices",handler:async(ctx)=>{if(!ctx.request.headers.get("x-user-id"))return ctx.set.status=401,{success:!1,error:"Unauthorized"};try{let url=new URL(ctx.request.url),productId=url.searchParams.get("productId"),status=url.searchParams.get("status"),conditions=[eq65(priceTable.isActive,!0)];if(productId)conditions.push(eq65(priceTable.productId,productId));return conditions.push(status?eq65(priceTable.status,status):ne3(priceTable.status,"archived")),{success:!0,data:{items:await database.select().from(priceTable).where(and27(...conditions)).orderBy(priceTable.createdAt)}}}catch{return ctx.set.status=500,{success:!1,error:"Failed to list prices"}}}}),routeGetPricesId=defineRoute({method:"GET",path:"/prices/:id",handler:async(ctx)=>{if(!ctx.request.headers.get("x-user-id"))return ctx.set.status=401,{success:!1,error:"Unauthorized"};try{let[item]=await database.select().from(priceTable).where(eq65(priceTable.id,ctx.params.id)).limit(1);if(!item)return ctx.set.status=404,{success:!1,error:"Price not found"};return{success:!0,data:item}}catch{return ctx.set.status=500,{success:!1,error:"Failed to get price"}}}}),routeDeletePricesId=defineRoute({method:"DELETE",path:"/prices/:id",handler:async(ctx)=>{if(!ctx.request.headers.get("x-user-id"))return ctx.set.status=401,{success:!1,error:"Unauthorized"};if(!hasGodminRole(ctx.request))return ctx.set.status=403,{success:!1,error:"Forbidden: operator privileges required"};try{let[existing]=await database.select().from(priceTable).where(eq65(priceTable.id,ctx.params.id)).limit(1);if(!existing)return ctx.set.status=404,{success:!1,error:"Price not found"};if(existing.providerPriceId)await provider.archivePrice({providerPriceId:existing.providerPriceId});let[updated]=await database.update(priceTable).set({status:"archived",updatedAt:new Date}).where(eq65(priceTable.id,ctx.params.id)).returning();return{success:!0,data:updated}}catch{return ctx.set.status=500,{success:!1,error:"Failed to archive price"}}}}),routePostCustomers=defineRoute({method:"POST",path:"/customers",handler:async(ctx)=>{let userId=ctx.request.headers.get("x-user-id");if(!userId)return ctx.set.status=401,{success:!1,error:"Unauthorized"};try{let body=ctx.body,result=await provider.createCustomer({email:body.email,name:body.name,phone:body.phone,description:body.description,metadata:body.metadata,address:body.address?{line1:body.address?.line1,line2:body.address?.line2,city:body.address?.city,state:body.address?.state,postalCode:body.address?.postalCode,country:body.address?.country}:void 0,taxId:body.taxId});if(!result.success)return ctx.set.status=400,result;let[row]=await database.insert(cusTable).values({userId,provider:provider.name,providerCustomerId:result.providerCustomerId,email:body.email,name:body.name??null,phone:body.phone??null,description:body.description??null,address:body.address??{},taxIdType:body.taxId?.type??null,taxIdValue:body.taxId?.value??null,metadata:body.metadata??{}}).returning();return{success:!0,customer:row,providerCustomerId:result.providerCustomerId}}catch(error3){return logger2.error("[Payment] Create customer error",{error:String(error3)}),ctx.set.status=500,{success:!1,error:"Failed to create customer"}}}}),routePutCustomersId=defineRoute({method:"PUT",path:"/customers/:id",handler:async(ctx)=>{let userId=ctx.request.headers.get("x-user-id");if(!userId)return ctx.set.status=401,{success:!1,error:"Unauthorized"};try{let{id}=ctx.params,body=ctx.body,[existing]=await database.select().from(cusTable).where(eq65(cusTable.id,id));if(!existing)return ctx.set.status=404,{success:!1,error:"Customer not found"};if(String(existing.userId)!==String(userId)&&!hasGodminRole(ctx.request))return ctx.set.status=403,{success:!1,error:"Forbidden"};let result=await provider.updateCustomer({providerCustomerId:existing.providerCustomerId,email:body.email,name:body.name,phone:body.phone,description:body.description,metadata:body.metadata,address:body.address?{line1:body.address?.line1,line2:body.address?.line2,city:body.address?.city,state:body.address?.state,postalCode:body.address?.postalCode,country:body.address?.country}:void 0});if(!result.success)return ctx.set.status=400,result;let updates={};if(body.email)updates.email=body.email;if(body.name!==void 0)updates.name=body.name;if(body.phone!==void 0)updates.phone=body.phone;if(body.description!==void 0)updates.description=body.description;if(body.address)updates.address=body.address;if(body.metadata)updates.metadata=body.metadata;let[updated]=await database.update(cusTable).set(updates).where(eq65(cusTable.id,id)).returning();return{success:!0,customer:updated}}catch(error3){return logger2.error("[Payment] Update customer error",{error:String(error3)}),ctx.set.status=500,{success:!1,error:"Failed to update customer"}}}}),routeGetCustomers=defineRoute({method:"GET",path:"/customers",handler:async(ctx)=>{let userId=ctx.request.headers.get("x-user-id");if(!userId)return ctx.set.status=401,{success:!1,error:"Unauthorized"};try{return{success:!0,customers:await database.select().from(cusTable).where(eq65(cusTable.userId,userId))}}catch(error3){return logger2.error("[Payment] List customers error",{error:String(error3)}),ctx.set.status=500,{success:!1,error:"Failed to list customers"}}}}),routeGetCustomersId=defineRoute({method:"GET",path:"/customers/:id",handler:async(ctx)=>{let userId=ctx.request.headers.get("x-user-id");if(!userId)return ctx.set.status=401,{success:!1,error:"Unauthorized"};try{let{id}=ctx.params,[customer]=await database.select().from(cusTable).where(eq65(cusTable.id,id));if(!customer)return ctx.set.status=404,{success:!1,error:"Customer not found"};if(String(customer.userId)!==String(userId)&&!hasGodminRole(ctx.request))return ctx.set.status=404,{success:!1,error:"Customer not found"};return{success:!0,customer}}catch(error3){return logger2.error("[Payment] Get customer error",{error:String(error3)}),ctx.set.status=500,{success:!1,error:"Failed to get customer"}}}}),routeDeleteCustomersId=defineRoute({method:"DELETE",path:"/customers/:id",handler:async(ctx)=>{let userId=ctx.request.headers.get("x-user-id");if(!userId)return ctx.set.status=401,{success:!1,error:"Unauthorized"};try{let{id}=ctx.params,[existing]=await database.select().from(cusTable).where(eq65(cusTable.id,id));if(!existing)return ctx.set.status=404,{success:!1,error:"Customer not found"};if(String(existing.userId)!==String(userId)&&!hasGodminRole(ctx.request))return ctx.set.status=403,{success:!1,error:"Forbidden"};return await provider.deleteCustomer({providerCustomerId:existing.providerCustomerId}),await database.delete(cusTable).where(eq65(cusTable.id,id)),{success:!0}}catch(error3){return logger2.error("[Payment] Delete customer error",{error:String(error3)}),ctx.set.status=500,{success:!1,error:"Failed to delete customer"}}}}),routePostSubscriptions=defineRoute({method:"POST",path:"/subscriptions",handler:async(ctx)=>{let userId=ctx.request.headers.get("x-user-id");if(!userId)return ctx.set.status=401,{success:!1,error:"Unauthorized"};try{let body=ctx.body,customerId=body.customerId,[customer]=await database.select().from(cusTable).where(eq65(cusTable.id,customerId));if(!customer)return ctx.set.status=404,{success:!1,error:"Customer not found"};if(String(customer.userId)!==String(userId)&&!hasGodminRole(ctx.request))return ctx.set.status=403,{success:!1,error:"Forbidden"};let items=body.items,result=await provider.createSubscription({providerCustomerId:customer.providerCustomerId,items,trialPeriodDays:body.trialPeriodDays,collectionMethod:body.collectionMethod,daysUntilDue:body.daysUntilDue,cancelAtPeriodEnd:body.cancelAtPeriodEnd,metadata:body.metadata,defaultPaymentMethod:body.defaultPaymentMethod});if(!result.success)return ctx.set.status=400,result;let[row]=await database.insert(subTable).values({customerId,provider:provider.name,providerSubscriptionId:result.providerSubscriptionId,status:result.status??"incomplete",collectionMethod:body.collectionMethod??"charge_automatically",currentPeriodStart:result.currentPeriodStart?new Date(result.currentPeriodStart):null,currentPeriodEnd:result.currentPeriodEnd?new Date(result.currentPeriodEnd):null,cancelAtPeriodEnd:body.cancelAtPeriodEnd??!1,items,metadata:body.metadata??{}}).returning();return{success:!0,subscription:row,clientSecret:result.clientSecret}}catch(error3){return logger2.error("[Payment] Create subscription error",{error:String(error3)}),ctx.set.status=500,{success:!1,error:"Failed to create subscription"}}}}),routePutSubscriptionsId=defineRoute({method:"PUT",path:"/subscriptions/:id",handler:async(ctx)=>{if(!ctx.request.headers.get("x-user-id"))return ctx.set.status=401,{success:!1,error:"Unauthorized"};try{let{id}=ctx.params,body=ctx.body,[existing]=await database.select().from(subTable).where(eq65(subTable.id,id));if(!existing)return ctx.set.status=404,{success:!1,error:"Subscription not found"};if(!await customerBelongsToCaller(ctx.request,existing.customerId))return ctx.set.status=403,{success:!1,error:"Forbidden"};let result=await provider.updateSubscription({providerSubscriptionId:existing.providerSubscriptionId,items:body.items,cancelAtPeriodEnd:body.cancelAtPeriodEnd,metadata:body.metadata,collectionMethod:body.collectionMethod,daysUntilDue:body.daysUntilDue,defaultPaymentMethod:body.defaultPaymentMethod,prorationBehavior:body.prorationBehavior});if(!result.success)return ctx.set.status=400,result;let updates={};if(result.status)updates.status=result.status;if(body.cancelAtPeriodEnd!==void 0)updates.cancelAtPeriodEnd=body.cancelAtPeriodEnd;if(body.items)updates.items=body.items;if(body.metadata)updates.metadata=body.metadata;if(body.collectionMethod)updates.collectionMethod=body.collectionMethod;let[updated]=await database.update(subTable).set(updates).where(eq65(subTable.id,id)).returning();return{success:!0,subscription:updated}}catch(error3){return logger2.error("[Payment] Update subscription error",{error:String(error3)}),ctx.set.status=500,{success:!1,error:"Failed to update subscription"}}}}),routePostSubscriptionsIdCancel=defineRoute({method:"POST",path:"/subscriptions/:id/cancel",handler:async(ctx)=>{if(!ctx.request.headers.get("x-user-id"))return ctx.set.status=401,{success:!1,error:"Unauthorized"};try{let{id}=ctx.params,body=ctx.body??{},[existing]=await database.select().from(subTable).where(eq65(subTable.id,id));if(!existing)return ctx.set.status=404,{success:!1,error:"Subscription not found"};if(!await customerBelongsToCaller(ctx.request,existing.customerId))return ctx.set.status=403,{success:!1,error:"Forbidden"};let result=await provider.cancelSubscription({providerSubscriptionId:existing.providerSubscriptionId,cancelAtPeriodEnd:body.cancelAtPeriodEnd,invoiceNow:body.invoiceNow,prorate:body.prorate});if(!result.success)return ctx.set.status=400,result;let updates={status:result.status??"canceled",canceledAt:new Date};if(body.cancelAtPeriodEnd)updates.cancelAtPeriodEnd=!0;let[updated]=await database.update(subTable).set(updates).where(eq65(subTable.id,id)).returning();return{success:!0,subscription:updated}}catch(error3){return logger2.error("[Payment] Cancel subscription error",{error:String(error3)}),ctx.set.status=500,{success:!1,error:"Failed to cancel subscription"}}}}),routeGetSubscriptions=defineRoute({method:"GET",path:"/subscriptions",handler:async(ctx)=>{let userId=ctx.request.headers.get("x-user-id");if(!userId)return ctx.set.status=401,{success:!1,error:"Unauthorized"};try{let query=ctx.query,conditions=[];if(query.customerId)conditions.push(eq65(subTable.customerId,query.customerId));if(query.status)conditions.push(eq65(subTable.status,query.status));if(!hasGodminRole(ctx.request)){let ids=await callerCustomerIds(userId);if(ids.length===0)return{success:!0,subscriptions:[]};conditions.push(inArray18(subTable.customerId,ids))}return{success:!0,subscriptions:conditions.length>0?await database.select().from(subTable).where(and27(...conditions)):await database.select().from(subTable)}}catch(error3){return logger2.error("[Payment] List subscriptions error",{error:String(error3)}),ctx.set.status=500,{success:!1,error:"Failed to list subscriptions"}}}}),routeGetSubscriptionsId=defineRoute({method:"GET",path:"/subscriptions/:id",handler:async(ctx)=>{if(!ctx.request.headers.get("x-user-id"))return ctx.set.status=401,{success:!1,error:"Unauthorized"};try{let{id}=ctx.params,[subscription]=await database.select().from(subTable).where(eq65(subTable.id,id));if(!subscription)return ctx.set.status=404,{success:!1,error:"Subscription not found"};if(!await customerBelongsToCaller(ctx.request,subscription.customerId))return ctx.set.status=403,{success:!1,error:"Forbidden"};return{success:!0,subscription}}catch(error3){return logger2.error("[Payment] Get subscription error",{error:String(error3)}),ctx.set.status=500,{success:!1,error:"Failed to get subscription"}}}}),routePostUsageRecords=defineRoute({method:"POST",path:"/usage-records",handler:async(ctx)=>{if(!ctx.request.headers.get("x-user-id"))return ctx.set.status=401,{success:!1,error:"Unauthorized"};try{let body=ctx.body,result=await provider.reportUsage({subscriptionItemId:body.subscriptionItemId,quantity:body.quantity,timestamp:body.timestamp,action:body.action});if(!result.success)return ctx.set.status=400,result;return{success:!0,usageRecord:result}}catch(error3){return logger2.error("[Payment] Report usage error",{error:String(error3)}),ctx.set.status=500,{success:!1,error:"Failed to report usage"}}}}),routeGetUsageRecordsSubscriptionitemidSummaries=defineRoute({method:"GET",path:"/usage-records/:subscriptionItemId/summaries",handler:async(ctx)=>{if(!ctx.request.headers.get("x-user-id"))return ctx.set.status=401,{success:!1,error:"Unauthorized"};try{let{subscriptionItemId}=ctx.params,query=ctx.query,result=await provider.listUsageRecordSummaries({subscriptionItemId,limit:query.limit?parseInt(query.limit,10):void 0,startingAfter:query.startingAfter});if(!result.success)return ctx.set.status=400,result;return{success:!0,summaries:result.summaries,hasMore:result.hasMore}}catch(error3){return logger2.error("[Payment] List usage summaries error",{error:String(error3)}),ctx.set.status=500,{success:!1,error:"Failed to list usage summaries"}}}}),routePostInvoices=defineRoute({method:"POST",path:"/invoices",handler:async(ctx)=>{let userId=ctx.request.headers.get("x-user-id");if(!userId)return ctx.set.status=401,{success:!1,error:"Unauthorized"};try{let body=ctx.body,customerId=body.customerId,[customer]=await database.select().from(cusTable).where(eq65(cusTable.id,customerId));if(!customer)return ctx.set.status=404,{success:!1,error:"Customer not found"};if(String(customer.userId)!==String(userId)&&!hasGodminRole(ctx.request))return ctx.set.status=403,{success:!1,error:"Forbidden"};let result=await provider.createInvoice({providerCustomerId:customer.providerCustomerId,collectionMethod:body.collectionMethod,daysUntilDue:body.daysUntilDue,description:body.description,metadata:body.metadata,autoAdvance:body.autoAdvance,items:body.items});if(!result.success)return ctx.set.status=400,result;let[row]=await database.insert(invTable).values({customerId,subscriptionId:body.subscriptionId??null,provider:provider.name,providerInvoiceId:result.providerInvoiceId,status:result.status??"draft",collectionMethod:body.collectionMethod??"charge_automatically",currency:body.currency??defaultCurrency,description:body.description??null,hostedInvoiceUrl:result.hostedInvoiceUrl??null,invoicePdf:result.invoicePdf??null,daysUntilDue:body.daysUntilDue??null,lines:body.items??[],metadata:body.metadata??{}}).returning();return{success:!0,invoice:row,providerInvoiceId:result.providerInvoiceId}}catch(error3){return logger2.error("[Payment] Create invoice error",{error:String(error3)}),ctx.set.status=500,{success:!1,error:"Failed to create invoice"}}}}),routePostInvoicesIdFinalize=defineRoute({method:"POST",path:"/invoices/:id/finalize",handler:async(ctx)=>{if(!ctx.request.headers.get("x-user-id"))return ctx.set.status=401,{success:!1,error:"Unauthorized"};try{let{id}=ctx.params,body=ctx.body??{},[existing]=await database.select().from(invTable).where(eq65(invTable.id,id));if(!existing)return ctx.set.status=404,{success:!1,error:"Invoice not found"};if(!await customerBelongsToCaller(ctx.request,existing.customerId))return ctx.set.status=403,{success:!1,error:"Forbidden"};let result=await provider.finalizeInvoice({providerInvoiceId:existing.providerInvoiceId,autoAdvance:body.autoAdvance});if(!result.success)return ctx.set.status=400,result;let[updated]=await database.update(invTable).set({status:result.status??"open",hostedInvoiceUrl:result.hostedInvoiceUrl??existing.hostedInvoiceUrl,invoicePdf:result.invoicePdf??existing.invoicePdf}).where(eq65(invTable.id,id)).returning();return{success:!0,invoice:updated}}catch(error3){return logger2.error("[Payment] Finalize invoice error",{error:String(error3)}),ctx.set.status=500,{success:!1,error:"Failed to finalize invoice"}}}}),routePostInvoicesIdSend=defineRoute({method:"POST",path:"/invoices/:id/send",handler:async(ctx)=>{if(!ctx.request.headers.get("x-user-id"))return ctx.set.status=401,{success:!1,error:"Unauthorized"};try{let{id}=ctx.params,[existing]=await database.select().from(invTable).where(eq65(invTable.id,id));if(!existing)return ctx.set.status=404,{success:!1,error:"Invoice not found"};if(!await customerBelongsToCaller(ctx.request,existing.customerId))return ctx.set.status=403,{success:!1,error:"Forbidden"};let result=await provider.sendInvoice({providerInvoiceId:existing.providerInvoiceId});if(!result.success)return ctx.set.status=400,result;return{success:!0}}catch(error3){return logger2.error("[Payment] Send invoice error",{error:String(error3)}),ctx.set.status=500,{success:!1,error:"Failed to send invoice"}}}}),routePostInvoicesIdVoid=defineRoute({method:"POST",path:"/invoices/:id/void",handler:async(ctx)=>{if(!ctx.request.headers.get("x-user-id"))return ctx.set.status=401,{success:!1,error:"Unauthorized"};try{let{id}=ctx.params,[existing]=await database.select().from(invTable).where(eq65(invTable.id,id));if(!existing)return ctx.set.status=404,{success:!1,error:"Invoice not found"};if(!await customerBelongsToCaller(ctx.request,existing.customerId))return ctx.set.status=403,{success:!1,error:"Forbidden"};let result=await provider.voidInvoice({providerInvoiceId:existing.providerInvoiceId});if(!result.success)return ctx.set.status=400,result;let[updated]=await database.update(invTable).set({status:"void",voidedAt:new Date}).where(eq65(invTable.id,id)).returning();return{success:!0,invoice:updated}}catch(error3){return logger2.error("[Payment] Void invoice error",{error:String(error3)}),ctx.set.status=500,{success:!1,error:"Failed to void invoice"}}}}),routePostInvoicesIdPay=defineRoute({method:"POST",path:"/invoices/:id/pay",handler:async(ctx)=>{if(!ctx.request.headers.get("x-user-id"))return ctx.set.status=401,{success:!1,error:"Unauthorized"};try{let{id}=ctx.params,body=ctx.body??{},[existing]=await database.select().from(invTable).where(eq65(invTable.id,id));if(!existing)return ctx.set.status=404,{success:!1,error:"Invoice not found"};if(!await customerBelongsToCaller(ctx.request,existing.customerId))return ctx.set.status=403,{success:!1,error:"Forbidden"};let result=await provider.payInvoice({providerInvoiceId:existing.providerInvoiceId,paymentMethod:body.paymentMethod});if(!result.success)return ctx.set.status=400,result;let[updated]=await database.update(invTable).set({status:result.status??"paid",amountPaid:result.amountPaid??existing.amountPaid,amountDue:result.amountDue??existing.amountDue,paidAt:new Date}).where(eq65(invTable.id,id)).returning();return{success:!0,invoice:updated}}catch(error3){return logger2.error("[Payment] Pay invoice error",{error:String(error3)}),ctx.set.status=500,{success:!1,error:"Failed to pay invoice"}}}}),routeGetInvoices=defineRoute({method:"GET",path:"/invoices",handler:async(ctx)=>{let userId=ctx.request.headers.get("x-user-id");if(!userId)return ctx.set.status=401,{success:!1,error:"Unauthorized"};try{let query=ctx.query,conditions=[];if(query.customerId)conditions.push(eq65(invTable.customerId,query.customerId));if(query.subscriptionId)conditions.push(eq65(invTable.subscriptionId,query.subscriptionId));if(query.status)conditions.push(eq65(invTable.status,query.status));if(!hasGodminRole(ctx.request)){let ids=await callerCustomerIds(userId);if(ids.length===0)return{success:!0,invoices:[]};conditions.push(inArray18(invTable.customerId,ids))}return{success:!0,invoices:conditions.length>0?await database.select().from(invTable).where(and27(...conditions)):await database.select().from(invTable)}}catch(error3){return logger2.error("[Payment] List invoices error",{error:String(error3)}),ctx.set.status=500,{success:!1,error:"Failed to list invoices"}}}}),routeGetInvoicesId=defineRoute({method:"GET",path:"/invoices/:id",handler:async(ctx)=>{if(!ctx.request.headers.get("x-user-id"))return ctx.set.status=401,{success:!1,error:"Unauthorized"};try{let{id}=ctx.params,[invoice]=await database.select().from(invTable).where(eq65(invTable.id,id));if(!invoice)return ctx.set.status=404,{success:!1,error:"Invoice not found"};if(!await customerBelongsToCaller(ctx.request,invoice.customerId))return ctx.set.status=403,{success:!1,error:"Forbidden"};return{success:!0,invoice}}catch(error3){return logger2.error("[Payment] Get invoice error",{error:String(error3)}),ctx.set.status=500,{success:!1,error:"Failed to get invoice"}}}});if(subMerchantsEnabled){let routePostSubMerchants=defineRoute({method:"POST",path:"/sub-merchants",handler:async(ctx)=>{let userId=ctx.request.headers.get("x-user-id");if(!userId)return ctx.set.status=401,{success:!1,error:"Unauthorized"};let body=ctx.body;if(!body.name||!body.email||!body.subMerchantType)return ctx.set.status=400,{success:!1,error:"name, email, and subMerchantType are required"};try{let externalId=body.externalId??`sm-${Date.now().toString(36)}`,result=await provider.registerSubMerchant({locale:body.locale??defaultLocale,subMerchantExternalId:externalId,subMerchantType:body.subMerchantType,name:body.name,email:body.email,gsmNumber:body.gsmNumber,address:body.address,iban:body.iban,contactName:body.contactName,contactSurname:body.contactSurname,identityNumber:body.identityNumber,taxOffice:body.taxOffice,taxNumber:body.taxNumber,legalCompanyTitle:body.legalCompanyTitle,currency:body.currency??defaultCurrency});if(!result.success)return ctx.set.status=400,{success:!1,error:result.errorMessage??"Failed to register sub-merchant",errorCode:result.errorCode};let[saved]=await database.insert(smTable).values({userId:hasGodminRole(ctx.request)?body.userId??userId:userId,provider:provider.name,providerSubMerchantKey:result.subMerchantKey,externalId,type:body.subMerchantType,status:"active",name:body.name,email:body.email,gsmNumber:body.gsmNumber??null,address:body.address??null,iban:body.iban??null,contactName:body.contactName??null,contactSurname:body.contactSurname??null,identityNumber:body.identityNumber??null,taxOffice:body.taxOffice??null,taxNumber:body.taxNumber??null,legalCompanyTitle:body.legalCompanyTitle??null,currency:body.currency??defaultCurrency,commissionRate:hasGodminRole(ctx.request)?body.commissionRate??0:0}).returning();return logger2.info("[Payment] Sub-merchant registered",{externalId,providerKey:result.subMerchantKey}),{success:!0,data:saved}}catch(error3){return logger2.error("[Payment] register sub-merchant error",{error:error3 instanceof Error?error3.message:String(error3)}),ctx.set.status=500,{success:!1,error:"Failed to register sub-merchant"}}}});declared.push(routePostSubMerchants);let routePutSubMerchantsId=defineRoute({method:"PUT",path:"/sub-merchants/:id",handler:async(ctx)=>{let userId=ctx.request.headers.get("x-user-id");if(!userId)return ctx.set.status=401,{success:!1,error:"Unauthorized"};let body=ctx.body;try{let[existing]=await database.select().from(smTable).where(eq65(smTable.id,ctx.params.id)).limit(1);if(!existing)return ctx.set.status=404,{success:!1,error:"Sub-merchant not found"};let smIsGodmin=hasGodminRole(ctx.request);if(String(existing.userId)!==String(userId)&&!smIsGodmin)return ctx.set.status=403,{success:!1,error:"Forbidden"};if(existing.providerSubMerchantKey){let providerResult=await provider.updateSubMerchant({locale:body.locale??defaultLocale,subMerchantKey:existing.providerSubMerchantKey,name:body.name,email:body.email,gsmNumber:body.gsmNumber,address:body.address,iban:body.iban,contactName:body.contactName,contactSurname:body.contactSurname,identityNumber:body.identityNumber,taxOffice:body.taxOffice,taxNumber:body.taxNumber,legalCompanyTitle:body.legalCompanyTitle,currency:body.currency});if(!providerResult.success)logger2.warn("[Payment] Provider sub-merchant update failed",{errorCode:providerResult.errorCode})}let updateData={};if(body.name)updateData.name=body.name;if(body.email)updateData.email=body.email;if(body.gsmNumber!==void 0)updateData.gsmNumber=body.gsmNumber;if(body.address!==void 0)updateData.address=body.address;if(body.iban!==void 0)updateData.iban=body.iban;if(body.contactName!==void 0)updateData.contactName=body.contactName;if(body.contactSurname!==void 0)updateData.contactSurname=body.contactSurname;if(body.identityNumber!==void 0)updateData.identityNumber=body.identityNumber;if(body.taxOffice!==void 0)updateData.taxOffice=body.taxOffice;if(body.taxNumber!==void 0)updateData.taxNumber=body.taxNumber;if(body.legalCompanyTitle!==void 0)updateData.legalCompanyTitle=body.legalCompanyTitle;if(body.currency!==void 0)updateData.currency=body.currency;if(body.commissionRate!==void 0&&smIsGodmin)updateData.commissionRate=body.commissionRate;if(body.status!==void 0&&smIsGodmin)updateData.status=body.status;let[updated]=await database.update(smTable).set(updateData).where(eq65(smTable.id,ctx.params.id)).returning();return{success:!0,data:updated}}catch{return ctx.set.status=500,{success:!1,error:"Failed to update sub-merchant"}}}});declared.push(routePutSubMerchantsId);let routeGetSubMerchants=defineRoute({method:"GET",path:"/sub-merchants",handler:async(ctx)=>{let userId=ctx.request.headers.get("x-user-id");if(!userId)return ctx.set.status=401,{success:!1,error:"Unauthorized"};try{let smListGodmin=hasGodminRole(ctx.request);return{success:!0,data:{items:await database.select().from(smTable).where(smListGodmin?eq65(smTable.isActive,!0):and27(eq65(smTable.isActive,!0),eq65(smTable.userId,userId))).orderBy(smTable.createdAt)}}}catch{return ctx.set.status=500,{success:!1,error:"Failed to list sub-merchants"}}}});declared.push(routeGetSubMerchants);let routeGetSubMerchantsId=defineRoute({method:"GET",path:"/sub-merchants/:id",handler:async(ctx)=>{let userId=ctx.request.headers.get("x-user-id");if(!userId)return ctx.set.status=401,{success:!1,error:"Unauthorized"};try{let[item]=await database.select().from(smTable).where(eq65(smTable.id,ctx.params.id)).limit(1);if(!item)return ctx.set.status=404,{success:!1,error:"Sub-merchant not found"};if(String(item.userId)!==String(userId)&&!hasGodminRole(ctx.request))return ctx.set.status=403,{success:!1,error:"Forbidden"};return{success:!0,data:item}}catch{return ctx.set.status=500,{success:!1,error:"Failed to get sub-merchant"}}}});declared.push(routeGetSubMerchantsId);let routePostSplitsSplitidApprove=defineRoute({method:"POST",path:"/splits/:splitId/approve",handler:async(ctx)=>{if(!ctx.request.headers.get("x-user-id"))return ctx.set.status=401,{success:!1,error:"Unauthorized"};if(!hasGodminRole(ctx.request))return ctx.set.status=403,{success:!1,error:"Forbidden: operator privileges required"};try{let[split]=await database.select().from(csTable).where(eq65(csTable.id,ctx.params.splitId)).limit(1);if(!split)return ctx.set.status=404,{success:!1,error:"Split not found"};if(split.status!=="pending")return ctx.set.status=400,{success:!1,error:`Split already ${split.status}`};if(split.providerSplitId){let result=await provider.approveSplit({locale:defaultLocale,paymentTransactionId:split.providerSplitId});if(!result.success)return ctx.set.status=400,{success:!1,error:result.errorMessage??"Provider approval failed",errorCode:result.errorCode}}let[updated]=await database.update(csTable).set({status:"approved",approvedAt:new Date}).where(eq65(csTable.id,ctx.params.splitId)).returning();return logger2.info("[Payment] Split approved",{splitId:ctx.params.splitId}),{success:!0,data:updated}}catch{return ctx.set.status=500,{success:!1,error:"Failed to approve split"}}}});declared.push(routePostSplitsSplitidApprove);let routePostSplitsSplitidDisapprove=defineRoute({method:"POST",path:"/splits/:splitId/disapprove",handler:async(ctx)=>{if(!ctx.request.headers.get("x-user-id"))return ctx.set.status=401,{success:!1,error:"Unauthorized"};if(!hasGodminRole(ctx.request))return ctx.set.status=403,{success:!1,error:"Forbidden: operator privileges required"};try{let[split]=await database.select().from(csTable).where(eq65(csTable.id,ctx.params.splitId)).limit(1);if(!split)return ctx.set.status=404,{success:!1,error:"Split not found"};if(split.status!=="pending")return ctx.set.status=400,{success:!1,error:`Split already ${split.status}`};if(split.providerSplitId){let result=await provider.disapproveSplit({locale:defaultLocale,paymentTransactionId:split.providerSplitId});if(!result.success)return ctx.set.status=400,{success:!1,error:result.errorMessage??"Provider disapproval failed",errorCode:result.errorCode}}let[updated]=await database.update(csTable).set({status:"disapproved",disapprovedAt:new Date}).where(eq65(csTable.id,ctx.params.splitId)).returning();return logger2.info("[Payment] Split disapproved",{splitId:ctx.params.splitId}),{success:!0,data:updated}}catch{return ctx.set.status=500,{success:!1,error:"Failed to disapprove split"}}}});declared.push(routePostSplitsSplitidDisapprove);let routeGetSplits=defineRoute({method:"GET",path:"/splits",handler:async(ctx)=>{if(!ctx.request.headers.get("x-user-id"))return ctx.set.status=401,{success:!1,error:"Unauthorized"};if(!hasGodminRole(ctx.request))return ctx.set.status=403,{success:!1,error:"Forbidden: operator privileges required"};try{let url=new URL(ctx.request.url),transactionId=url.searchParams.get("transactionId"),subMerchantId=url.searchParams.get("subMerchantId"),query=database.select().from(csTable);if(transactionId&&subMerchantId)query=query.where(and27(eq65(csTable.transactionId,transactionId),eq65(csTable.subMerchantId,subMerchantId)));else if(transactionId)query=query.where(eq65(csTable.transactionId,transactionId));else if(subMerchantId)query=query.where(eq65(csTable.subMerchantId,subMerchantId));return{success:!0,data:{items:await query.orderBy(csTable.createdAt)}}}catch{return ctx.set.status=500,{success:!1,error:"Failed to list splits"}}}});declared.push(routeGetSplits)}let routeGetBalance=defineRoute({method:"GET",path:"/balance",handler:async(ctx)=>{if(!ctx.request.headers.get("x-user-id"))return ctx.set.status=401,{success:!1,error:"Unauthorized"};try{let result=await provider.getBalance();if(!result.success)return ctx.set.status=400,{success:!1,error:result.errorMessage??"Failed to get balance"};return{success:!0,data:{available:result.available,pending:result.pending,connectReserved:result.connectReserved}}}catch{return ctx.set.status=500,{success:!1,error:"Failed to get balance"}}}}),routePostPayouts=defineRoute({method:"POST",path:"/payouts",handler:async(ctx)=>{if(!ctx.request.headers.get("x-user-id"))return ctx.set.status=401,{success:!1,error:"Unauthorized"};if(!hasGodminRole(ctx.request))return ctx.set.status=403,{success:!1,error:"Forbidden: operator privileges required"};try{let body=ctx.body,payoutAmt=validateAmount(body.amount,"amount");if(!payoutAmt.ok)return ctx.set.status=400,{success:!1,error:payoutAmt.error};if(!isValidCurrency(body.currency))return ctx.set.status=400,{success:!1,error:"currency must be a 3-letter code"};let result=await provider.createPayout({amount:payoutAmt.value,currency:body.currency??defaultCurrency,destination:body.destination,description:body.description,metadata:body.metadata,method:body.method,sourceType:body.sourceType,statementDescriptor:body.statementDescriptor});if(!result.success)return ctx.set.status=400,{success:!1,error:result.errorMessage??"Failed to create payout"};return{success:!0,data:result}}catch{return ctx.set.status=500,{success:!1,error:"Failed to create payout"}}}}),routeGetPayouts=defineRoute({method:"GET",path:"/payouts",handler:async(ctx)=>{if(!ctx.request.headers.get("x-user-id"))return ctx.set.status=401,{success:!1,error:"Unauthorized"};try{let url=new URL(ctx.request.url),result=await provider.listPayouts({status:url.searchParams.get("status")??void 0,destination:url.searchParams.get("destination")??void 0,limit:url.searchParams.get("limit")?Number(url.searchParams.get("limit")):void 0,startingAfter:url.searchParams.get("startingAfter")??void 0});if(!result.success)return ctx.set.status=400,{success:!1,error:result.errorMessage??"Failed to list payouts"};return{success:!0,data:{payouts:result.payouts,hasMore:result.hasMore}}}catch{return ctx.set.status=500,{success:!1,error:"Failed to list payouts"}}}}),routeGetPayoutsId=defineRoute({method:"GET",path:"/payouts/:id",handler:async(ctx)=>{if(!ctx.request.headers.get("x-user-id"))return ctx.set.status=401,{success:!1,error:"Unauthorized"};try{let result=await provider.getPayout({providerPayoutId:ctx.params.id});if(!result.success)return ctx.set.status=400,{success:!1,error:result.errorMessage??"Failed to get payout"};return{success:!0,data:result}}catch{return ctx.set.status=500,{success:!1,error:"Failed to get payout"}}}}),routePostPayoutsIdCancel=defineRoute({method:"POST",path:"/payouts/:id/cancel",handler:async(ctx)=>{if(!ctx.request.headers.get("x-user-id"))return ctx.set.status=401,{success:!1,error:"Unauthorized"};if(!hasGodminRole(ctx.request))return ctx.set.status=403,{success:!1,error:"Forbidden: operator privileges required"};try{let result=await provider.cancelPayout({providerPayoutId:ctx.params.id});if(!result.success)return ctx.set.status=400,{success:!1,error:result.errorMessage??"Failed to cancel payout"};return{success:!0,data:result}}catch{return ctx.set.status=500,{success:!1,error:"Failed to cancel payout"}}}}),routePostTransfers=defineRoute({method:"POST",path:"/transfers",handler:async(ctx)=>{if(!ctx.request.headers.get("x-user-id"))return ctx.set.status=401,{success:!1,error:"Unauthorized"};if(!hasGodminRole(ctx.request))return ctx.set.status=403,{success:!1,error:"Forbidden: operator privileges required"};try{let body=ctx.body,transferAmt=validateAmount(body.amount,"amount");if(!transferAmt.ok)return ctx.set.status=400,{success:!1,error:transferAmt.error};if(!isValidCurrency(body.currency))return ctx.set.status=400,{success:!1,error:"currency must be a 3-letter code"};let result=await provider.createTransfer({amount:transferAmt.value,currency:body.currency??defaultCurrency,destination:body.destination,description:body.description,metadata:body.metadata,sourceTransaction:body.sourceTransaction,transferGroup:body.transferGroup});if(!result.success)return ctx.set.status=400,{success:!1,error:result.errorMessage??"Failed to create transfer"};return{success:!0,data:result}}catch{return ctx.set.status=500,{success:!1,error:"Failed to create transfer"}}}}),routeGetTransfers=defineRoute({method:"GET",path:"/transfers",handler:async(ctx)=>{if(!ctx.request.headers.get("x-user-id"))return ctx.set.status=401,{success:!1,error:"Unauthorized"};try{let url=new URL(ctx.request.url),result=await provider.listTransfers({destination:url.searchParams.get("destination")??void 0,transferGroup:url.searchParams.get("transferGroup")??void 0,limit:url.searchParams.get("limit")?Number(url.searchParams.get("limit")):void 0,startingAfter:url.searchParams.get("startingAfter")??void 0});if(!result.success)return ctx.set.status=400,{success:!1,error:result.errorMessage??"Failed to list transfers"};return{success:!0,data:{transfers:result.transfers,hasMore:result.hasMore}}}catch{return ctx.set.status=500,{success:!1,error:"Failed to list transfers"}}}}),routeOptionsCallback=defineRoute({method:"OPTIONS",path:"/callback",handler:()=>{return new Response(null,{headers:{"Access-Control-Allow-Origin":"*","Access-Control-Allow-Methods":"GET,POST,OPTIONS","Access-Control-Allow-Headers":"Content-Type"}})}}),routePostWebhook=createStripeWebhookRoute({provider,webhookSecret:config.webhookSecret,db:database,webhookLogsTable:whTable,transactionsTable:txTable,getPayoutService:config.getPayoutService,logger:logger2});return declared.push(routePostInitialize,routePostCallback,routeGetTransactions,routeGetTransactionsId,routePostBinQuery,routePostPaymentDetail,routePostRefund,routePostProducts,routePutProductsId,routeGetProducts,routeGetProductsId,routeDeleteProductsId,routePostPrices,routePutPricesId,routeGetPrices,routeGetPricesId,routeDeletePricesId,routePostCustomers,routePutCustomersId,routeGetCustomers,routeGetCustomersId,routeDeleteCustomersId,routePostSubscriptions,routePutSubscriptionsId,routePostSubscriptionsIdCancel,routeGetSubscriptions,routeGetSubscriptionsId,routePostUsageRecords,routeGetUsageRecordsSubscriptionitemidSummaries,routePostInvoices,routePostInvoicesIdFinalize,routePostInvoicesIdSend,routePostInvoicesIdVoid,routePostInvoicesIdPay,routeGetInvoices,routeGetInvoicesId,routeGetBalance,routePostPayouts,routeGetPayouts,routeGetPayoutsId,routePostPayoutsIdCancel,routePostTransfers,routeGetTransfers,routeOptionsCallback),app.use(collectRoutes([{prefix:basePath,routes:[...declared,routePostWebhook]}])),app};var init_payment=__esm(()=>{init_routeCollector();init_adminGuard();init_webhook();UUID_PATTERN=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i,CALLBACK_WRITABLE_STATUSES=["pending","processing","failed"],CALLBACK_COMPLETABLE_STATUSES=["pending","processing"]});var requireUser=(ctx)=>ctx.request.headers.get("x-user-id"),isAdmin=(ctx)=>decodeHeaderList(ctx.request.headers.get("x-user-roles")).some((r)=>isGodminRole(r)),canReadOwnerLedger=(ctx,ownerType,ownerId)=>{if(isAdmin(ctx))return!0;let userId=ctx.request.headers.get("x-user-id");return!!userId&&ownerType==="user"&&!!ownerId&&ownerId===userId},failResponse=(ctx,status,message)=>{return ctx.set.status=status,{success:!1,message,data:null}};var init_types22=__esm(()=>{init_Authorization()});function createPayoutRoutes(config){let base=`${config.basePath}/marketplace`,routePostPayoutRequests=defineRoute({method:"POST",path:"/payout-requests",body:Type.Object({recipientOwnerId:Type.String(),recipientOwnerType:Type.Optional(Type.String()),amount:Type.Number(),currency:Type.Optional(Type.String()),destination:Type.Optional(Type.String()),requestedBy:Type.Optional(Type.String())}),detail:{tags:["Payments Marketplace"],summary:"Request a payout"},handler:async(ctx)=>{let svc=config.getPayoutService();if(!svc)return failResponse(ctx,503,"Marketplace not available");let userId=requireUser(ctx);if(!userId)return failResponse(ctx,401,"Authentication required");let body=ctx.body;if(!canReadOwnerLedger(ctx,body.recipientOwnerType,body.recipientOwnerId))return failResponse(ctx,403,"Forbidden: cannot request a payout for another owner's balance");try{return{success:!0,message:"Payout requested",data:await svc.requestPayout({...body,requestedBy:userId})}}catch(err){return failResponse(ctx,400,err instanceof Error?err.message:"Failed")}}}),routeGetPayoutRequests=defineRoute({method:"GET",path:"/payout-requests",handler:async(ctx)=>{let svc=config.getPayoutService();if(!svc)return failResponse(ctx,503,"Marketplace not available");let userId=requireUser(ctx);if(!userId)return failResponse(ctx,401,"Authentication required");let url=new URL(ctx.request.url),requestedOwnerId=url.searchParams.get("recipientOwnerId")??void 0,recipientOwnerId=isAdmin(ctx)?requestedOwnerId:userId,items=await svc.listPayoutRequests({recipientOwnerId,status:url.searchParams.get("status")??void 0});return{success:!0,message:`Found ${items.length} payout requests`,data:{items}}}}),routeGetPayoutRequestsId=defineRoute({method:"GET",path:"/payout-requests/:id",handler:async(ctx)=>{let svc=config.getPayoutService();if(!svc)return failResponse(ctx,503,"Marketplace not available");if(!requireUser(ctx))return failResponse(ctx,401,"Authentication required");let record3=await svc.getPayoutRequest(ctx.params.id);if(!record3)return failResponse(ctx,404,"Payout request not found");let r=record3;if(!canReadOwnerLedger(ctx,r.recipientOwnerType,r.recipientOwnerId))return failResponse(ctx,404,"Payout request not found");return{success:!0,message:"Payout request",data:record3}}}),routePostPayoutRequestsIdApprove=defineRoute({method:"POST",path:"/payout-requests/:id/approve",handler:async(ctx)=>{let svc=config.getPayoutService();if(!svc)return failResponse(ctx,503,"Marketplace not available");let userId=requireUser(ctx);if(!userId)return failResponse(ctx,401,"Authentication required");if(!isAdmin(ctx))return failResponse(ctx,403,"Forbidden: operator privileges required");try{let record3=await svc.approvePayout(ctx.params.id,{deciderId:userId});if(!record3)return failResponse(ctx,404,"Payout request not found");return{success:!0,message:"Payout approved",data:record3}}catch(err){return failResponse(ctx,400,err instanceof Error?err.message:"Failed")}}}),routePostPayoutRequestsIdReject=defineRoute({method:"POST",path:"/payout-requests/:id/reject",handler:async(ctx)=>{let svc=config.getPayoutService();if(!svc)return failResponse(ctx,503,"Marketplace not available");let userId=requireUser(ctx);if(!userId)return failResponse(ctx,401,"Authentication required");if(!isAdmin(ctx))return failResponse(ctx,403,"Forbidden: operator privileges required");let record3=await svc.rejectPayout(ctx.params.id,userId);if(!record3)return failResponse(ctx,404,"Payout request not found");return{success:!0,message:"Payout rejected",data:record3}}}),routePostDisputes=defineRoute({method:"POST",path:"/disputes",body:Type.Object({provider:Type.String(),transactionId:Type.Optional(Type.String()),providerDisputeId:Type.Optional(Type.String()),ownerType:Type.Optional(Type.String()),ownerId:Type.Optional(Type.String()),amount:Type.Optional(Type.Number()),currency:Type.Optional(Type.String()),reason:Type.Optional(Type.String())}),detail:{tags:["Payments Marketplace"],summary:"Open a dispute"},handler:async(ctx)=>{let svc=config.getPayoutService();if(!svc)return failResponse(ctx,503,"Marketplace not available");if(!requireUser(ctx))return failResponse(ctx,401,"Authentication required");if(!isAdmin(ctx))return failResponse(ctx,403,"Forbidden: operator privileges required");let body=ctx.body;return{success:!0,message:"Dispute opened",data:await svc.openDispute(body)}}}),routeGetDisputes=defineRoute({method:"GET",path:"/disputes",handler:async(ctx)=>{let svc=config.getPayoutService();if(!svc)return failResponse(ctx,503,"Marketplace not available");let userId=requireUser(ctx);if(!userId)return failResponse(ctx,401,"Authentication required");let url=new URL(ctx.request.url),requestedOwnerId=url.searchParams.get("ownerId")??void 0,ownerId=isAdmin(ctx)?requestedOwnerId:userId,items=await svc.listDisputes({ownerId,status:url.searchParams.get("status")??void 0});return{success:!0,message:`Found ${items.length} disputes`,data:{items}}}}),routePostDisputesIdResolve=defineRoute({method:"POST",path:"/disputes/:id/resolve",body:Type.Object({outcome:Type.Union([Type.Literal("won"),Type.Literal("lost"),Type.Literal("accepted"),Type.Literal("cancelled")])}),detail:{tags:["Payments Marketplace"],summary:"Resolve a dispute"},handler:async(ctx)=>{let svc=config.getPayoutService();if(!svc)return failResponse(ctx,503,"Marketplace not available");if(!requireUser(ctx))return failResponse(ctx,401,"Authentication required");if(!isAdmin(ctx))return failResponse(ctx,403,"Forbidden: operator privileges required");let body=ctx.body,record3=await svc.resolveDispute(ctx.params.id,body.outcome);if(!record3)return failResponse(ctx,404,"Dispute not found");return{success:!0,message:"Dispute resolved",data:record3}}});return collectRoutes([{prefix:base,routes:[routePostPayoutRequests,routeGetPayoutRequests,routeGetPayoutRequestsId,routePostPayoutRequestsIdApprove,routePostPayoutRequestsIdReject,routePostDisputes,routeGetDisputes,routePostDisputesIdResolve]}])}var init_payouts=__esm(()=>{init_esm3();init_routeCollector();init_types22()});function createSplitRoutes(config){let base=`${config.basePath}/marketplace`,routePostSplits=defineRoute({method:"POST",path:"/splits",body:Type.Object({recipientOwnerId:Type.String(),recipientOwnerType:Type.Optional(Type.String()),provider:Type.String(),grossAmount:Type.Number(),providerFeeAmount:Type.Optional(Type.Number()),platformFeeAmount:Type.Number(),currency:Type.Optional(Type.String()),transactionId:Type.Optional(Type.String()),sourceType:Type.Optional(Type.String()),sourceId:Type.Optional(Type.String()),settlementDelayDays:Type.Optional(Type.Number()),reserveRate:Type.Optional(Type.Number()),isNewSeller:Type.Optional(Type.Boolean())}),detail:{tags:["Payments Marketplace"],summary:"Record a payment split"},handler:async(ctx)=>{let svc=config.getMarketplaceService();if(!svc)return failResponse(ctx,503,"Marketplace not available");if(!requireUser(ctx))return failResponse(ctx,401,"Authentication required");if(!isAdmin(ctx))return failResponse(ctx,403,"Forbidden: operator privileges required");let body=ctx.body;try{return{success:!0,message:"Split recorded",data:await svc.recordSplit(body)}}catch(err){return failResponse(ctx,400,err instanceof Error?err.message:"Failed")}}}),routeGetSplits=defineRoute({method:"GET",path:"/splits",handler:async(ctx)=>{let svc=config.getMarketplaceService();if(!svc)return failResponse(ctx,503,"Marketplace not available");let userId=requireUser(ctx);if(!userId)return failResponse(ctx,401,"Authentication required");let url=new URL(ctx.request.url),requestedOwnerId=url.searchParams.get("recipientOwnerId")??void 0,recipientOwnerId=isAdmin(ctx)?requestedOwnerId:userId,items=await svc.listSplits({recipientOwnerId,status:url.searchParams.get("status")??void 0});return{success:!0,message:`Found ${items.length} splits`,data:{items}}}}),routeGetBalanceOwnerid=defineRoute({method:"GET",path:"/balance/:ownerId",handler:async(ctx)=>{let svc=config.getMarketplaceService();if(!svc)return failResponse(ctx,503,"Marketplace not available");if(!requireUser(ctx))return failResponse(ctx,401,"Authentication required");let url=new URL(ctx.request.url),ownerType=url.searchParams.get("ownerType")??"seller",ownerId=ctx.params.ownerId;if(!canReadOwnerLedger(ctx,ownerType,ownerId))return failResponse(ctx,403,"Forbidden: cannot read another owner's balance");return{success:!0,message:"Balance",data:await svc.getBalance(ownerType,ownerId,url.searchParams.get("currency")??void 0)}}}),routePostReservesIdRelease=defineRoute({method:"POST",path:"/reserves/:id/release",handler:async(ctx)=>{let svc=config.getMarketplaceService();if(!svc)return failResponse(ctx,503,"Marketplace not available");if(!requireUser(ctx))return failResponse(ctx,401,"Authentication required");if(!isAdmin(ctx))return failResponse(ctx,403,"Forbidden: operator privileges required");if(!await svc.releaseReserve(ctx.params.id))return failResponse(ctx,400,"Reserve not found or not held");return{success:!0,message:"Reserve released",data:{released:!0}}}}),routeGetSettlementPolicy=defineRoute({method:"GET",path:"/settlement-policy",handler:async(ctx)=>{let svc=config.getMarketplaceService();if(!svc)return failResponse(ctx,503,"Marketplace not available");if(!requireUser(ctx))return failResponse(ctx,401,"Authentication required");let url=new URL(ctx.request.url),ownerType=url.searchParams.get("ownerType")??"tenant",ownerId=url.searchParams.get("ownerId")??"";if(!canReadOwnerLedger(ctx,ownerType,ownerId))return failResponse(ctx,403,"Forbidden: cannot read this settlement policy");return{success:!0,message:"Settlement policy",data:await svc.getSettlementPolicy(ownerType,ownerId)}}}),routePostSettlementPolicy=defineRoute({method:"POST",path:"/settlement-policy",body:Type.Object({ownerType:Type.String(),ownerId:Type.String(),defaultDelayDays:Type.Optional(Type.Number()),newSellerDelayDays:Type.Optional(Type.Number()),reserveRate:Type.Optional(Type.Number()),reserveDays:Type.Optional(Type.Number()),minimumPayoutAmount:Type.Optional(Type.Number()),currency:Type.Optional(Type.String()),earlyPayoutEnabled:Type.Optional(Type.Boolean())}),detail:{tags:["Payments Marketplace"],summary:"Upsert settlement policy"},handler:async(ctx)=>{let svc=config.getMarketplaceService();if(!svc)return failResponse(ctx,503,"Marketplace not available");if(!requireUser(ctx))return failResponse(ctx,401,"Authentication required");if(!isAdmin(ctx))return failResponse(ctx,403,"Forbidden: operator privileges required");let body=ctx.body;return{success:!0,message:"Settlement policy saved",data:await svc.upsertSettlementPolicy(body)}}});return collectRoutes([{prefix:base,routes:[routePostSplits,routeGetSplits,routeGetBalanceOwnerid,routePostReservesIdRelease,routeGetSettlementPolicy,routePostSettlementPolicy]}])}var init_splits=__esm(()=>{init_esm3();init_routeCollector();init_types22()});var exports_marketplace={};__export(exports_marketplace,{createMarketplaceRoutes:()=>createMarketplaceRoutes});function createMarketplaceRoutes(app,config){return useRoutes(app,createSplitRoutes(config)),useRoutes(app,createPayoutRoutes(config)),app}var init_marketplace=__esm(()=>{init_routeMount();init_payouts();init_splits()});var exports_schemaReconcile={};__export(exports_schemaReconcile,{undeclaredTables:()=>undeclaredTables,setDefaultStatement:()=>setDefaultStatement,renderDefaultLiteral:()=>renderDefaultLiteral,reconcileMissingColumns:()=>reconcileMissingColumns,readActualSchemaDetailed:()=>readActualSchemaDetailed,readActualSchema:()=>readActualSchema,planRepair:()=>planRepair,findDrift:()=>findDrift,expectedFromTables:()=>expectedFromTables,describeRepair:()=>describeRepair,createMissingTables:()=>createMissingTables,addColumnStatement:()=>addColumnStatement});import{getTableColumns as getTableColumns2,sql as sql13}from"drizzle-orm";function findDrift(expected,actual,actualDefaults){let missingTables=[],missingColumns=[],columnsMissingDefault=[];for(let table of expected){let have=actual[table.name];if(!have){missingTables.push(table.name);continue}let haveDefault=actualDefaults?.[table.name];for(let column of table.columns){if(!have.has(column.name)){missingColumns.push({table:table.name,column});continue}if(haveDefault&&column.hasDefault&&column.defaultLiteral!==void 0&&!haveDefault.has(column.name))columnsMissingDefault.push({table:table.name,column})}}return{missingTables,missingColumns,columnsMissingDefault}}function addColumnStatement(schema3,table,column){if(column.notNull&&!column.hasDefault)return null;return[`alter table "${schema3}"."${table}"`,`add column if not exists "${column.name}" ${column.sqlType}`].join(" ")}function setDefaultStatement(schema3,table,column){if(column.defaultLiteral===void 0)return null;return`alter table "${schema3}"."${table}" alter column "${column.name}" set default ${column.defaultLiteral}`}function planRepair(schema3,drift){let statements=[],unsafe3=[];for(let{table,column}of drift.missingColumns){let statement=addColumnStatement(schema3,table,column);if(statement)statements.push(statement);else unsafe3.push({table,column:column.name,why:"declared NOT NULL with no default, and the table already has rows to fill"})}for(let{table,column}of drift.columnsMissingDefault){let statement=setDefaultStatement(schema3,table,column);if(statement)statements.push(statement)}return{statements,unsafe:unsafe3,missingTables:drift.missingTables,createdTables:[],undeclaredTables:[]}}function undeclaredTables(expected,actual){let declared=new Set(expected.map((t)=>t.name));return Object.keys(actual).filter((name2)=>!declared.has(name2)).sort()}function isAlreadyExists(error3){let code=error3?.code;if(typeof code==="string"&&ALREADY_EXISTS.has(code))return!0;let message=error3 instanceof Error?error3.message:String(error3);return/already exists/i.test(message)}async function createMissingTables(options){let{run,tables,missing,schemaObject,logger:logger2,timeoutMs=60000}=options;if(missing.length===0)return{applied:0,errors:[]};let wanted=new Set(missing),imports={};for(let[key2,table]of Object.entries(tables)){if(!table||typeof table!=="object")continue;let name2=tableNameOf(table);if(name2&&wanted.has(name2))imports[key2]=table}if(schemaObject)imports.__nucleusSchema=schemaObject;let statements;try{let{generateDrizzleJson,generateMigration}=await import("drizzle-kit/api"),realExit=process.exit;try{process.exit=(code)=>{throw Error(`drizzle-kit called process.exit(${code??0}) while generating table DDL`)},statements=await withDeadline(generateMigration(generateDrizzleJson({}),generateDrizzleJson(imports)),timeoutMs,`generating CREATE TABLE statements did not finish within ${timeoutMs}ms`)}finally{process.exit=realExit}}catch(error3){let message=error3 instanceof Error?error3.message:String(error3);return logger2.error(`[Schema] CREATE TABLE statements could not be generated: ${message}`),{applied:0,errors:[message]}}let applied=0,errors2=[];for(let statement of statements)try{await run(statement),applied+=1}catch(error3){if(isAlreadyExists(error3))continue;let message=error3 instanceof Error?error3.message:String(error3);errors2.push(message),logger2.error(`[Schema] could not apply generated DDL: ${statement} \u2014 ${message}`)}return{applied,errors:errors2}}function describeRepair(plan,applied){let lines=[];if(applied>0)lines.push(`[Schema] The push applied nothing, so ${applied} missing column(s) were added directly. `+"This is additive only \u2014 nothing was dropped or altered.");if(plan.createdTables.length>0)lines.push(`[Schema] ${plan.createdTables.length} table(s) the push did not create were created directly: ${plan.createdTables.join(", ")}. Verified present in the database afterwards, not assumed from the statements that ran.`);if(plan.missingTables.length>0){if(lines.push(`[Schema] ${plan.missingTables.length} table(s) the code expects still do not exist: ${plan.missingTables.join(", ")}. Every read of these will fail. The additive repair tried `+"and could not create them \u2014 this needs a human."),plan.undeclaredTables.length>0)lines.push(`[Schema] The push is ambiguous because the database has ${plan.undeclaredTables.length} table(s) the model does not declare: ${plan.undeclaredTables.join(", ")}. drizzle-kit cannot tell a new table from a rename of one of these, and cannot ask without a TTY. Drop the stale one, or declare it, and the push stops aborting.`)}for(let item of plan.unsafe)lines.push(`[Schema] "${item.table}"."${item.column}" could not be added: ${item.why}. Give the column a default, or add it by hand with a backfill.`);return lines}function expectedFromTables(tables){let out=[];for(let table of Object.values(tables)){if(!table||typeof table!=="object")continue;let columns;try{columns=getTableColumns2(table)}catch{continue}let first=Object.values(columns)[0],name2=tableNameOf(table);if(!name2||!first)continue;out.push({name:name2,columns:Object.values(columns).map((column)=>{let c=column;return{name:c.name,sqlType:typeof c.getSQLType==="function"?c.getSQLType():"text",notNull:c.notNull===!0,hasDefault:c.hasDefault===!0,defaultLiteral:renderDefaultLiteral(c.default)}})})}return out}function renderDefaultLiteral(value2){if(typeof value2==="string")return`'${value2.replace(/'/g,"''")}'`;if(typeof value2==="number")return Number.isFinite(value2)?String(value2):void 0;if(typeof value2==="boolean")return value2?"true":"false";return}function tableNameOf(table){for(let key2 of Object.getOwnPropertySymbols(table))if(String(key2).includes("Name")&&!String(key2).includes("Schema")){let value2=table[key2];if(typeof value2==="string"&&value2.length>0)return value2}return null}async function readActualSchema(execute,schema3){return(await readActualSchemaDetailed(execute,schema3)).columns}async function readActualSchemaDetailed(execute,schema3){let result=await execute(`select table_name, column_name, column_default from information_schema.columns where table_schema = '${schema3.replace(/'/g,"''")}'`),rows=Array.isArray(result)?result:result.rows??[],columns={},withDefault={};for(let row of rows){if(!row.table_name||!row.column_name)continue;if(columns[row.table_name]??=new Set,columns[row.table_name]?.add(row.column_name),withDefault[row.table_name]??=new Set,row.column_default!==null&&row.column_default!==void 0)withDefault[row.table_name]?.add(row.column_name)}return{columns,withDefault}}async function reconcileMissingColumns(options){let{db,schema:schema3,tables,logger:logger2,schemaObject}=options,run=(statement)=>db.execute(sql13.raw(statement));try{let expected=expectedFromTables(tables);if(expected.length===0)return null;let{columns:actual,withDefault}=await readActualSchemaDetailed(run,schema3),drift=findDrift(expected,actual,withDefault),plan=planRepair(schema3,drift);if(plan.undeclaredTables=undeclaredTables(expected,actual),drift.missingTables.length>0){await createMissingTables({run,tables,missing:drift.missingTables,schemaObject,logger:logger2});let after=await readActualSchema(run,schema3);plan.createdTables=drift.missingTables.filter((name2)=>after[name2]),plan.missingTables=drift.missingTables.filter((name2)=>!after[name2])}let applied=0;for(let statement of plan.statements)try{await run(statement),applied+=1}catch(error3){logger2.error(`[Schema] could not add a missing column: ${statement} \u2014 ${error3 instanceof Error?error3.message:String(error3)}`)}for(let line of describeRepair(plan,applied))if(plan.missingTables.length>0||plan.unsafe.length>0)logger2.error(line);else logger2.info(line);return{applied,plan}}catch(error3){return logger2.error(`[Schema] the additive repair could not run: ${error3 instanceof Error?error3.message:String(error3)}`),null}}var ALREADY_EXISTS;var init_schemaReconcile=__esm(()=>{ALREADY_EXISTS=new Set(["42P06","42P07","42710","42723","23505"])});var exports_relaxSessionExpiry={};__export(exports_relaxSessionExpiry,{relaxSessionExpiryIfNever:()=>relaxSessionExpiryIfNever});async function relaxSessionExpiryIfNever(params){let{db,sql:sql14,schemaName,lifetimeSeconds:lifetimeSeconds2,logger:logger2}=params,tableName=params.tableName??"user_sessions";if(lifetimeSeconds2!==null)return{action:"skipped",reason:"lifetime-is-bounded"};if(!SAFE_IDENTIFIER.test(schemaName)||!SAFE_IDENTIFIER.test(tableName))return logger2?.warn(`[Schema] Refusing to touch "${schemaName}"."${tableName}" \u2014 the name has characters that cannot be safely quoted.`),{action:"skipped",reason:"unsafe-identifier"};try{let current=await db.execute(sql14`
|
|
538
|
+
`},UUID_PATTERN,CALLBACK_WRITABLE_STATUSES,CALLBACK_COMPLETABLE_STATUSES,createPaymentRoutes=(config)=>{let{provider,basePath,defaultCurrency,defaultLocale,successRedirectUrl,failedRedirectUrl,errorRedirectUrl,savedMethodsEnabled,threeDSecureEnabled,subMerchantsEnabled,transactionsTable,methodsTable,webhookLogsTable,subMerchantsTable,commissionSplitsTable,productsTable,pricesTable,customersTable,subscriptionsTable,invoicesTable,db,logger:logger2}=config,txTable=transactionsTable,pmTable=methodsTable,whTable=webhookLogsTable,smTable=subMerchantsTable,csTable=commissionSplitsTable,prodTable=productsTable,priceTable=pricesTable,cusTable=customersTable,subTable=subscriptionsTable,invTable=invoicesTable,database=db,declared=[],app=collectRoutes(),customerBelongsToCaller=async(request,customerId)=>{if(hasGodminRole(request))return!0;let uid=request.headers.get("x-user-id");if(!uid||!customerId)return!1;let[c]=await database.select().from(cusTable).where(eq65(cusTable.id,String(customerId)));return!!c&&String(c.userId)===String(uid)},callerCustomerIds=async(uid)=>{return(await database.select().from(cusTable).where(eq65(cusTable.userId,uid))).map((r)=>String(r.id))},routePostInitialize=defineRoute({method:"POST",path:"/initialize",handler:async(ctx)=>{if(!threeDSecureEnabled)return ctx.set.status=400,{success:!1,error:"3D Secure is not enabled"};let userId=ctx.request.headers.get("x-user-id"),body=ctx.body;try{let[transaction]=await database.insert(txTable).values({userId:userId??null,orderId:body.orderId??body.conversationId??null,provider:provider.name,amount:Number(body.paidPrice??body.price??0),currency:body.currency??defaultCurrency,status:"pending",statusCode:1000,statusMessage:"3DS initialization started",isThreeDSecure:!0,installment:body.installment??1,ipAddress:ctx.request.headers.get("x-forwarded-for")??ctx.request.headers.get("x-real-ip")??null,metadata:body.metadata??{}}).returning(),result=await provider.initialize3DS({locale:body.locale??defaultLocale,conversationId:transaction.id,price:body.price,paidPrice:body.paidPrice,currency:body.currency??defaultCurrency,installment:body.installment,paymentChannel:body.paymentChannel,basketId:body.basketId,paymentGroup:body.paymentGroup,paymentCard:body.paymentCard,buyer:body.buyer,shippingAddress:body.shippingAddress,billingAddress:body.billingAddress,basketItems:body.basketItems,callbackUrl:body.callbackUrl??config.callbackUrl??`${basePath}/callback`});if(!result.success)return await database.update(txTable).set({status:"failed",statusCode:1003,statusMessage:result.errorMessage??"Initialization failed",providerResponse:result.rawResponse??{},failedAt:new Date}).where(eq65(txTable.id,transaction.id)),ctx.set.status=400,{success:!1,error:result.errorMessage??"Payment initialization failed",errorCode:result.errorCode};return await database.update(txTable).set({status:"processing",statusCode:1001,statusMessage:"3DS initialized, awaiting bank verification",providerPaymentId:result.paymentId,providerResponse:result.rawResponse??{}}).where(eq65(txTable.id,transaction.id)),{success:!0,transactionId:transaction.id,threeDSHtmlContent:result.threeDSHtmlContent,paymentId:result.paymentId}}catch(error3){return logger2.error("[Payment] initialize3DS error",{error:error3 instanceof Error?error3.message:String(error3)}),ctx.set.status=500,{success:!1,error:"Internal payment error"}}}}),routePostCallback=defineRoute({method:"POST",path:"/callback",handler:async(ctx)=>{let requestOrigin=new URL(ctx.request.url).origin,htmlHeaders={"Content-Type":"text/html; charset=utf-8","Access-Control-Allow-Origin":requestOrigin,"Access-Control-Allow-Methods":"POST, OPTIONS"};try{let parsedBody=ctx.body??{},rawData={};for(let[key2,value2]of Object.entries(parsedBody))rawData[key2]=String(value2);let parsed=provider.parseCallback(rawData);if(!parsed.paymentId)return logger2.warn("[Payment] 3DS callback refused: the body named no payment",{fields:Object.keys(rawData)}),new Response(generateRedirectHtml(`${requestOrigin}${errorRedirectUrl}`,"Payment Error"),{status:400,headers:htmlHeaders});if(await database.insert(whTable).values({provider:provider.name,eventType:"three_ds_callback",providerPaymentId:parsed.paymentId,rawPayload:rawData,processed:!1,idempotencyKey:`${provider.name}:${parsed.paymentId}:${parsed.mdStatus}`,ipAddress:ctx.request.headers.get("x-forwarded-for")??ctx.request.headers.get("x-real-ip")??null}).onConflictDoNothing(),!parsed.success){if(logger2.warn("[Payment] 3DS callback failed",{mdStatus:parsed.mdStatus,paymentId:parsed.paymentId}),parsed.conversationId)await database.update(txTable).set({status:"failed",statusCode:1003,statusMessage:`3DS verification failed: mdStatus=${parsed.mdStatus}`,failedAt:new Date}).where(and27(eq65(txTable.id,parsed.conversationId),inArray19(txTable.status,CALLBACK_WRITABLE_STATUSES))).catch(()=>{});return new Response(generateRedirectHtml(`${requestOrigin}${failedRedirectUrl}`,"Payment Failed"),{headers:htmlHeaders})}let completion=await provider.complete3DS({locale:defaultLocale,conversationId:parsed.conversationId,paymentId:parsed.paymentId});if(!completion.success){if(logger2.error("[Payment] 3DS completion failed",{paymentId:parsed.paymentId,errorCode:completion.errorCode,errorMessage:completion.errorMessage}),parsed.conversationId)await database.update(txTable).set({status:"failed",statusCode:1003,statusMessage:`3DS completion failed: ${completion.errorMessage}`,providerResponse:completion.rawResponse??{},failedAt:new Date}).where(and27(eq65(txTable.id,parsed.conversationId),inArray19(txTable.status,CALLBACK_WRITABLE_STATUSES))).catch(()=>{});return new Response(generateRedirectHtml(`${requestOrigin}${failedRedirectUrl}`,"Payment Failed"),{headers:htmlHeaders})}if(parsed.conversationId)await database.update(txTable).set({status:"completed",statusCode:1001,statusMessage:"Payment completed successfully",providerPaymentId:completion.paymentId??parsed.paymentId,providerTransactionId:completion.signature,amount:completion.paidPrice??0,currency:completion.currency??defaultCurrency,paymentMethod:completion.cardAssociation,cardLastFour:completion.lastFourDigits,cardType:completion.cardType,cardAssociation:completion.cardAssociation,installment:completion.installment??1,providerResponse:completion.rawResponse??{},completedAt:new Date}).where(and27(eq65(txTable.id,parsed.conversationId),inArray19(txTable.status,CALLBACK_COMPLETABLE_STATUSES))).catch(()=>{});await database.update(whTable).set({processed:!0,processingResult:{completion}}).where(eq65(whTable.idempotencyKey,`${provider.name}:${parsed.paymentId}:${parsed.mdStatus}`)).catch(()=>{}),logger2.info("[Payment] 3DS payment completed",{paymentId:completion.paymentId,amount:completion.paidPrice,currency:completion.currency});let queryParams=new URLSearchParams;if(parsed.conversationId)queryParams.append("transactionId",parsed.conversationId);if(completion.paymentId)queryParams.append("paymentId",completion.paymentId);if(completion.paidPrice)queryParams.append("amount",String(completion.paidPrice));let redirectUrl=`${requestOrigin}${successRedirectUrl}?${queryParams.toString()}`;return new Response(generateRedirectHtml(redirectUrl,"Payment Successful"),{headers:htmlHeaders})}catch(error3){return logger2.error("[Payment] callback error",{error:error3 instanceof Error?error3.message:String(error3)}),new Response(generateRedirectHtml(`${requestOrigin}${errorRedirectUrl}`,"Payment Error"),{headers:htmlHeaders})}}}),routeGetTransactions=defineRoute({method:"GET",path:"/transactions",handler:async(ctx)=>{let userId=ctx.request.headers.get("x-user-id");if(!userId)return ctx.set.status=401,{success:!1,error:"Unauthorized"};try{return{success:!0,data:{items:await database.select().from(txTable).where(eq65(txTable.userId,userId)).orderBy(txTable.createdAt)}}}catch(error3){return logger2.error("[Payment] list transactions error",{error:error3 instanceof Error?error3.message:String(error3)}),ctx.set.status=500,{success:!1,error:"Failed to list transactions"}}}}),routeGetTransactionsId=defineRoute({method:"GET",path:"/transactions/:id",handler:async(ctx)=>{let userId=ctx.request.headers.get("x-user-id");if(!userId)return ctx.set.status=401,{success:!1,error:"Unauthorized"};try{let[transaction]=await database.select().from(txTable).where(and27(eq65(txTable.id,ctx.params.id),eq65(txTable.userId,userId))).limit(1);if(!transaction)return ctx.set.status=404,{success:!1,error:"Transaction not found"};return{success:!0,data:transaction}}catch{return ctx.set.status=500,{success:!1,error:"Failed to get transaction"}}}}),routePostBinQuery=defineRoute({method:"POST",path:"/bin-query",handler:async(ctx)=>{let body=ctx.body,binNumber=body.binNumber??body.cardNumber;if(!binNumber||typeof binNumber!=="string")return ctx.set.status=400,{success:!1,error:"binNumber is required"};try{let result=await provider.queryBin({locale:body.locale??defaultLocale,binNumber,price:body.price});if(!result.success)return ctx.set.status=400,{success:!1,error:result.errorMessage??"BIN query failed",errorCode:result.errorCode};return{success:!0,data:result}}catch{return ctx.set.status=500,{success:!1,error:"BIN query failed"}}}}),routePostPaymentDetail=defineRoute({method:"POST",path:"/payment-detail",handler:async(ctx)=>{let userId=ctx.request.headers.get("x-user-id");if(!userId)return ctx.set.status=401,{success:!1,error:"Unauthorized"};let body=ctx.body;if(!body.paymentId)return ctx.set.status=400,{success:!1,error:"paymentId is required"};if(!hasGodminRole(ctx.request)){let keys=[eq65(txTable.providerPaymentId,String(body.paymentId))],conversation=body.conversationId??body.paymentConversationId;if(conversation!=null){let candidate=String(conversation);if(UUID_PATTERN.test(candidate))keys.push(eq65(txTable.id,candidate));keys.push(eq65(txTable.orderId,candidate))}let owned;try{let[transaction]=await database.select().from(txTable).where(and27(or5(...keys),eq65(txTable.userId,userId))).limit(1);owned=transaction}catch(error3){return logger2.error("[Payment] payment-detail ownership lookup failed",{error:error3 instanceof Error?error3.message:String(error3)}),ctx.set.status=500,{success:!1,error:"Payment detail query failed"}}if(!owned)return ctx.set.status=404,{success:!1,error:"Transaction not found"}}try{let result=await provider.getPaymentDetail({locale:body.locale??defaultLocale,paymentId:body.paymentId,conversationId:body.conversationId,paymentConversationId:body.paymentConversationId});return{success:result.success,data:result}}catch{return ctx.set.status=500,{success:!1,error:"Payment detail query failed"}}}}),routePostRefund=defineRoute({method:"POST",path:"/refund",handler:async(ctx)=>{let userId=ctx.request.headers.get("x-user-id");if(!userId)return ctx.set.status=401,{success:!1,error:"Unauthorized"};let body=ctx.body;if(!body.paymentTransactionId||!body.price)return ctx.set.status=400,{success:!1,error:"paymentTransactionId and price are required"};let refundAmt=validateAmount(body.price,"price");if(!refundAmt.ok)return ctx.set.status=400,{success:!1,error:refundAmt.error};if(!hasGodminRole(ctx.request)){if(!body.transactionId)return ctx.set.status=400,{success:!1,error:"transactionId is required"};let[tx]=await database.select().from(txTable).where(eq65(txTable.id,body.transactionId)).limit(1);if(!tx)return ctx.set.status=404,{success:!1,error:"Transaction not found"};if(String(tx.userId)!==String(userId))return ctx.set.status=403,{success:!1,error:"You can only refund your own transactions"};let ownedProviderId=tx.providerTransactionId??tx.providerPaymentId;if(ownedProviderId&&String(body.paymentTransactionId)!==String(ownedProviderId))return ctx.set.status=403,{success:!1,error:"paymentTransactionId does not match this transaction"};let refundable=Number(tx.amount??0)-Number(tx.refundedAmount??0);if(!(Number(body.price)>0)||Number(body.price)>refundable)return ctx.set.status=400,{success:!1,error:`Refund amount exceeds the refundable balance (${refundable})`}}try{let result=await provider.refund({locale:body.locale??defaultLocale,conversationId:body.conversationId,paymentTransactionId:body.paymentTransactionId,price:String(body.price),currency:body.currency??defaultCurrency,ip:ctx.request.headers.get("x-forwarded-for")??ctx.request.headers.get("x-real-ip")??void 0});if(!result.success)return ctx.set.status=400,{success:!1,error:result.errorMessage??"Refund failed",errorCode:result.errorCode};if(body.transactionId){let price=Number(body.price);await database.update(txTable).set({status:sql12`CASE WHEN COALESCE(${txTable.refundedAmount}, 0) + ${price} >= ${txTable.amount} THEN 'refunded' ELSE 'partially_refunded' END`,refundedAmount:sql12`COALESCE(${txTable.refundedAmount}, 0) + ${price}`,refundedAt:new Date,statusMessage:"Payment refunded"}).where(eq65(txTable.id,body.transactionId)).catch(()=>{})}return logger2.info("[Payment] Refund processed",{paymentId:result.paymentId,price:result.price}),{success:!0,data:result}}catch{return ctx.set.status=500,{success:!1,error:"Refund failed"}}}});if(savedMethodsEnabled){let routePostCardsSave=defineRoute({method:"POST",path:"/cards/save",handler:async(ctx)=>{let userId=ctx.request.headers.get("x-user-id");if(!userId)return ctx.set.status=401,{success:!1,error:"Unauthorized"};let body=ctx.body;if(!body.email||!body.card)return ctx.set.status=400,{success:!1,error:"email and card are required"};try{let result=await provider.saveCard({locale:body.locale??defaultLocale,email:body.email,externalId:userId,card:body.card});if(!result.success)return ctx.set.status=400,{success:!1,error:result.errorMessage??"Failed to save card",errorCode:result.errorCode};let[saved]=await database.insert(pmTable).values({userId,provider:provider.name,type:"card",alias:result.cardAlias??body.card.cardAlias,cardLastFour:result.lastFourDigits,cardType:result.cardType,cardAssociation:result.cardAssociation,cardFamily:result.cardFamily,cardBankName:result.cardBankName,providerCardUserKey:result.cardUserKey,providerCardToken:result.cardToken,binNumber:result.binNumber}).returning();return logger2.info("[Payment] Card saved",{userId,cardAlias:result.cardAlias}),{success:!0,data:saved}}catch{return ctx.set.status=500,{success:!1,error:"Failed to save card"}}}});declared.push(routePostCardsSave);let routeGetCards=defineRoute({method:"GET",path:"/cards",handler:async(ctx)=>{let userId=ctx.request.headers.get("x-user-id");if(!userId)return ctx.set.status=401,{success:!1,error:"Unauthorized"};try{return{success:!0,data:{items:await database.select().from(pmTable).where(and27(eq65(pmTable.userId,userId),eq65(pmTable.isActive,!0))).orderBy(pmTable.createdAt)}}}catch{return ctx.set.status=500,{success:!1,error:"Failed to list cards"}}}});declared.push(routeGetCards);let routeDeleteCardsId=defineRoute({method:"DELETE",path:"/cards/:id",handler:async(ctx)=>{let userId=ctx.request.headers.get("x-user-id");if(!userId)return ctx.set.status=401,{success:!1,error:"Unauthorized"};try{let[card]=await database.select().from(pmTable).where(and27(eq65(pmTable.id,ctx.params.id),eq65(pmTable.userId,userId))).limit(1);if(!card)return ctx.set.status=404,{success:!1,error:"Card not found"};if(card.providerCardUserKey&&card.providerCardToken){let deleteResult=await provider.deleteCard({locale:defaultLocale,cardUserKey:card.providerCardUserKey,cardToken:card.providerCardToken});if(!deleteResult.success)logger2.warn("[Payment] Provider card delete failed (proceeding with local delete)",{errorCode:deleteResult.errorCode,errorMessage:deleteResult.errorMessage})}return await database.update(pmTable).set({isActive:!1}).where(eq65(pmTable.id,ctx.params.id)),logger2.info("[Payment] Card deleted",{userId,cardId:ctx.params.id}),{success:!0}}catch{return ctx.set.status=500,{success:!1,error:"Failed to delete card"}}}});declared.push(routeDeleteCardsId);let routePostCardsSync=defineRoute({method:"POST",path:"/cards/sync",handler:async(ctx)=>{if(!ctx.request.headers.get("x-user-id"))return ctx.set.status=401,{success:!1,error:"Unauthorized"};let body=ctx.body,cardUserKey=body.cardUserKey;if(!cardUserKey)return ctx.set.status=400,{success:!1,error:"cardUserKey is required"};try{let result=await provider.listCards({locale:body.locale??defaultLocale,cardUserKey});if(!result.success)return ctx.set.status=400,{success:!1,error:result.errorMessage??"Failed to list cards from provider"};return{success:!0,data:{cardUserKey:result.cardUserKey,cards:result.cards}}}catch{return ctx.set.status=500,{success:!1,error:"Failed to sync cards"}}}});declared.push(routePostCardsSync)}let routePostProducts=defineRoute({method:"POST",path:"/products",handler:async(ctx)=>{if(!ctx.request.headers.get("x-user-id"))return ctx.set.status=401,{success:!1,error:"Unauthorized"};if(!hasGodminRole(ctx.request))return ctx.set.status=403,{success:!1,error:"Forbidden: operator privileges required"};let body=ctx.body;if(!body.name)return ctx.set.status=400,{success:!1,error:"name is required"};try{let providerResult=await provider.createProduct({name:body.name,description:body.description,type:body.type,images:body.images,unitLabel:body.unitLabel,url:body.url,metadata:body.metadata});if(!providerResult.success)return ctx.set.status=400,{success:!1,error:providerResult.errorMessage??"Failed to create product at provider",errorCode:providerResult.errorCode};let[saved]=await database.insert(prodTable).values({provider:provider.name,providerProductId:providerResult.providerProductId,name:body.name,description:body.description??null,type:body.type??"service",status:"active",images:body.images?JSON.stringify(body.images):"[]",unitLabel:body.unitLabel??null,url:body.url??null,metadata:body.metadata?JSON.stringify(body.metadata):"{}"}).returning();return logger2.info("[Payment] Product created",{id:saved.id,providerProductId:providerResult.providerProductId}),{success:!0,data:saved}}catch(error3){return logger2.error("[Payment] create product error",{error:error3 instanceof Error?error3.message:String(error3)}),ctx.set.status=500,{success:!1,error:"Failed to create product"}}}}),routePutProductsId=defineRoute({method:"PUT",path:"/products/:id",handler:async(ctx)=>{if(!ctx.request.headers.get("x-user-id"))return ctx.set.status=401,{success:!1,error:"Unauthorized"};if(!hasGodminRole(ctx.request))return ctx.set.status=403,{success:!1,error:"Forbidden: operator privileges required"};let body=ctx.body;try{let[existing]=await database.select().from(prodTable).where(eq65(prodTable.id,ctx.params.id)).limit(1);if(!existing)return ctx.set.status=404,{success:!1,error:"Product not found"};if(existing.providerProductId){let providerResult=await provider.updateProduct({providerProductId:existing.providerProductId,name:body.name,description:body.description,images:body.images,unitLabel:body.unitLabel,url:body.url,active:body.status==="active"?!0:body.status==="archived"?!1:void 0,metadata:body.metadata});if(!providerResult.success)logger2.warn("[Payment] Provider product update failed",{errorCode:providerResult.errorCode})}let updateData={};if(body.name)updateData.name=body.name;if(body.description!==void 0)updateData.description=body.description;if(body.type)updateData.type=body.type;if(body.status)updateData.status=body.status;if(body.images)updateData.images=JSON.stringify(body.images);if(body.unitLabel!==void 0)updateData.unitLabel=body.unitLabel;if(body.url!==void 0)updateData.url=body.url;if(body.metadata)updateData.metadata=JSON.stringify(body.metadata);updateData.updatedAt=new Date;let[updated]=await database.update(prodTable).set(updateData).where(eq65(prodTable.id,ctx.params.id)).returning();return{success:!0,data:updated}}catch{return ctx.set.status=500,{success:!1,error:"Failed to update product"}}}}),routeGetProducts=defineRoute({method:"GET",path:"/products",handler:async(ctx)=>{if(!ctx.request.headers.get("x-user-id"))return ctx.set.status=401,{success:!1,error:"Unauthorized"};try{let status=new URL(ctx.request.url).searchParams.get("status"),query=database.select().from(prodTable).where(eq65(prodTable.isActive,!0));if(status)query=database.select().from(prodTable).where(and27(eq65(prodTable.isActive,!0),eq65(prodTable.status,status)));return{success:!0,data:{items:await query.orderBy(prodTable.createdAt)}}}catch{return ctx.set.status=500,{success:!1,error:"Failed to list products"}}}}),routeGetProductsId=defineRoute({method:"GET",path:"/products/:id",handler:async(ctx)=>{if(!ctx.request.headers.get("x-user-id"))return ctx.set.status=401,{success:!1,error:"Unauthorized"};try{let[item]=await database.select().from(prodTable).where(eq65(prodTable.id,ctx.params.id)).limit(1);if(!item)return ctx.set.status=404,{success:!1,error:"Product not found"};let prices=await database.select().from(priceTable).where(and27(eq65(priceTable.productId,ctx.params.id),eq65(priceTable.isActive,!0),ne3(priceTable.status,"archived"))).orderBy(priceTable.createdAt);return{success:!0,data:{...item,prices}}}catch{return ctx.set.status=500,{success:!1,error:"Failed to get product"}}}}),routeDeleteProductsId=defineRoute({method:"DELETE",path:"/products/:id",handler:async(ctx)=>{if(!ctx.request.headers.get("x-user-id"))return ctx.set.status=401,{success:!1,error:"Unauthorized"};if(!hasGodminRole(ctx.request))return ctx.set.status=403,{success:!1,error:"Forbidden: operator privileges required"};try{let[existing]=await database.select().from(prodTable).where(eq65(prodTable.id,ctx.params.id)).limit(1);if(!existing)return ctx.set.status=404,{success:!1,error:"Product not found"};if(existing.providerProductId)await provider.archiveProduct({providerProductId:existing.providerProductId});let[updated]=await database.update(prodTable).set({status:"archived",updatedAt:new Date}).where(eq65(prodTable.id,ctx.params.id)).returning();return{success:!0,data:updated}}catch{return ctx.set.status=500,{success:!1,error:"Failed to archive product"}}}}),routePostPrices=defineRoute({method:"POST",path:"/prices",handler:async(ctx)=>{if(!ctx.request.headers.get("x-user-id"))return ctx.set.status=401,{success:!1,error:"Unauthorized"};if(!hasGodminRole(ctx.request))return ctx.set.status=403,{success:!1,error:"Forbidden: operator privileges required"};let body=ctx.body;if(!body.productId||body.unitAmount===void 0||!body.currency)return ctx.set.status=400,{success:!1,error:"productId, unitAmount, and currency are required"};try{let[product]=await database.select().from(prodTable).where(eq65(prodTable.id,body.productId)).limit(1);if(!product)return ctx.set.status=404,{success:!1,error:"Product not found"};let providerResult=await provider.createPrice({providerProductId:product.providerProductId,currency:body.currency,unitAmount:body.unitAmount,type:body.type,billingScheme:body.billingScheme,nickname:body.nickname,recurring:body.recurring,transformQuantity:body.transformQuantity,unitAmountDecimal:body.unitAmountDecimal,metadata:body.metadata});if(!providerResult.success)return ctx.set.status=400,{success:!1,error:providerResult.errorMessage??"Failed to create price at provider",errorCode:providerResult.errorCode};let[saved]=await database.insert(priceTable).values({productId:body.productId,provider:provider.name,providerPriceId:providerResult.providerPriceId,currency:body.currency,unitAmount:body.unitAmount,unitAmountDecimal:body.unitAmountDecimal??null,type:body.type??"one_time",billingScheme:body.billingScheme??"per_unit",nickname:body.nickname??null,recurringInterval:body.recurring?.interval??null,recurringIntervalCount:body.recurring?.intervalCount??1,recurringUsageType:body.recurring?.usageType??"licensed",recurringAggregateUsage:body.recurring?.aggregateUsage??null,transformQuantityDivideBy:body.transformQuantity?.divideBy??null,transformQuantityRound:body.transformQuantity?.round??null,status:"active",metadata:body.metadata?JSON.stringify(body.metadata):"{}"}).returning();return logger2.info("[Payment] Price created",{id:saved.id,providerPriceId:providerResult.providerPriceId}),{success:!0,data:saved}}catch(error3){return logger2.error("[Payment] create price error",{error:error3 instanceof Error?error3.message:String(error3)}),ctx.set.status=500,{success:!1,error:"Failed to create price"}}}}),routePutPricesId=defineRoute({method:"PUT",path:"/prices/:id",handler:async(ctx)=>{if(!ctx.request.headers.get("x-user-id"))return ctx.set.status=401,{success:!1,error:"Unauthorized"};if(!hasGodminRole(ctx.request))return ctx.set.status=403,{success:!1,error:"Forbidden: operator privileges required"};let body=ctx.body;try{let[existing]=await database.select().from(priceTable).where(eq65(priceTable.id,ctx.params.id)).limit(1);if(!existing)return ctx.set.status=404,{success:!1,error:"Price not found"};if(existing.providerPriceId){let providerResult=await provider.updatePrice({providerPriceId:existing.providerPriceId,nickname:body.nickname,active:body.status==="active"?!0:body.status==="archived"?!1:void 0,metadata:body.metadata});if(!providerResult.success)logger2.warn("[Payment] Provider price update failed",{errorCode:providerResult.errorCode})}let updateData={};if(body.nickname!==void 0)updateData.nickname=body.nickname;if(body.status)updateData.status=body.status;if(body.metadata)updateData.metadata=JSON.stringify(body.metadata);updateData.updatedAt=new Date;let[updated]=await database.update(priceTable).set(updateData).where(eq65(priceTable.id,ctx.params.id)).returning();return{success:!0,data:updated}}catch{return ctx.set.status=500,{success:!1,error:"Failed to update price"}}}}),routeGetPrices=defineRoute({method:"GET",path:"/prices",handler:async(ctx)=>{if(!ctx.request.headers.get("x-user-id"))return ctx.set.status=401,{success:!1,error:"Unauthorized"};try{let url=new URL(ctx.request.url),productId=url.searchParams.get("productId"),status=url.searchParams.get("status"),conditions=[eq65(priceTable.isActive,!0)];if(productId)conditions.push(eq65(priceTable.productId,productId));return conditions.push(status?eq65(priceTable.status,status):ne3(priceTable.status,"archived")),{success:!0,data:{items:await database.select().from(priceTable).where(and27(...conditions)).orderBy(priceTable.createdAt)}}}catch{return ctx.set.status=500,{success:!1,error:"Failed to list prices"}}}}),routeGetPricesId=defineRoute({method:"GET",path:"/prices/:id",handler:async(ctx)=>{if(!ctx.request.headers.get("x-user-id"))return ctx.set.status=401,{success:!1,error:"Unauthorized"};try{let[item]=await database.select().from(priceTable).where(eq65(priceTable.id,ctx.params.id)).limit(1);if(!item)return ctx.set.status=404,{success:!1,error:"Price not found"};return{success:!0,data:item}}catch{return ctx.set.status=500,{success:!1,error:"Failed to get price"}}}}),routeDeletePricesId=defineRoute({method:"DELETE",path:"/prices/:id",handler:async(ctx)=>{if(!ctx.request.headers.get("x-user-id"))return ctx.set.status=401,{success:!1,error:"Unauthorized"};if(!hasGodminRole(ctx.request))return ctx.set.status=403,{success:!1,error:"Forbidden: operator privileges required"};try{let[existing]=await database.select().from(priceTable).where(eq65(priceTable.id,ctx.params.id)).limit(1);if(!existing)return ctx.set.status=404,{success:!1,error:"Price not found"};if(existing.providerPriceId)await provider.archivePrice({providerPriceId:existing.providerPriceId});let[updated]=await database.update(priceTable).set({status:"archived",updatedAt:new Date}).where(eq65(priceTable.id,ctx.params.id)).returning();return{success:!0,data:updated}}catch{return ctx.set.status=500,{success:!1,error:"Failed to archive price"}}}}),routePostCustomers=defineRoute({method:"POST",path:"/customers",handler:async(ctx)=>{let userId=ctx.request.headers.get("x-user-id");if(!userId)return ctx.set.status=401,{success:!1,error:"Unauthorized"};try{let body=ctx.body,result=await provider.createCustomer({email:body.email,name:body.name,phone:body.phone,description:body.description,metadata:body.metadata,address:body.address?{line1:body.address?.line1,line2:body.address?.line2,city:body.address?.city,state:body.address?.state,postalCode:body.address?.postalCode,country:body.address?.country}:void 0,taxId:body.taxId});if(!result.success)return ctx.set.status=400,result;let[row]=await database.insert(cusTable).values({userId,provider:provider.name,providerCustomerId:result.providerCustomerId,email:body.email,name:body.name??null,phone:body.phone??null,description:body.description??null,address:body.address??{},taxIdType:body.taxId?.type??null,taxIdValue:body.taxId?.value??null,metadata:body.metadata??{}}).returning();return{success:!0,customer:row,providerCustomerId:result.providerCustomerId}}catch(error3){return logger2.error("[Payment] Create customer error",{error:String(error3)}),ctx.set.status=500,{success:!1,error:"Failed to create customer"}}}}),routePutCustomersId=defineRoute({method:"PUT",path:"/customers/:id",handler:async(ctx)=>{let userId=ctx.request.headers.get("x-user-id");if(!userId)return ctx.set.status=401,{success:!1,error:"Unauthorized"};try{let{id}=ctx.params,body=ctx.body,[existing]=await database.select().from(cusTable).where(eq65(cusTable.id,id));if(!existing)return ctx.set.status=404,{success:!1,error:"Customer not found"};if(String(existing.userId)!==String(userId)&&!hasGodminRole(ctx.request))return ctx.set.status=403,{success:!1,error:"Forbidden"};let result=await provider.updateCustomer({providerCustomerId:existing.providerCustomerId,email:body.email,name:body.name,phone:body.phone,description:body.description,metadata:body.metadata,address:body.address?{line1:body.address?.line1,line2:body.address?.line2,city:body.address?.city,state:body.address?.state,postalCode:body.address?.postalCode,country:body.address?.country}:void 0});if(!result.success)return ctx.set.status=400,result;let updates={};if(body.email)updates.email=body.email;if(body.name!==void 0)updates.name=body.name;if(body.phone!==void 0)updates.phone=body.phone;if(body.description!==void 0)updates.description=body.description;if(body.address)updates.address=body.address;if(body.metadata)updates.metadata=body.metadata;let[updated]=await database.update(cusTable).set(updates).where(eq65(cusTable.id,id)).returning();return{success:!0,customer:updated}}catch(error3){return logger2.error("[Payment] Update customer error",{error:String(error3)}),ctx.set.status=500,{success:!1,error:"Failed to update customer"}}}}),routeGetCustomers=defineRoute({method:"GET",path:"/customers",handler:async(ctx)=>{let userId=ctx.request.headers.get("x-user-id");if(!userId)return ctx.set.status=401,{success:!1,error:"Unauthorized"};try{return{success:!0,customers:await database.select().from(cusTable).where(eq65(cusTable.userId,userId))}}catch(error3){return logger2.error("[Payment] List customers error",{error:String(error3)}),ctx.set.status=500,{success:!1,error:"Failed to list customers"}}}}),routeGetCustomersId=defineRoute({method:"GET",path:"/customers/:id",handler:async(ctx)=>{let userId=ctx.request.headers.get("x-user-id");if(!userId)return ctx.set.status=401,{success:!1,error:"Unauthorized"};try{let{id}=ctx.params,[customer]=await database.select().from(cusTable).where(eq65(cusTable.id,id));if(!customer)return ctx.set.status=404,{success:!1,error:"Customer not found"};if(String(customer.userId)!==String(userId)&&!hasGodminRole(ctx.request))return ctx.set.status=404,{success:!1,error:"Customer not found"};return{success:!0,customer}}catch(error3){return logger2.error("[Payment] Get customer error",{error:String(error3)}),ctx.set.status=500,{success:!1,error:"Failed to get customer"}}}}),routeDeleteCustomersId=defineRoute({method:"DELETE",path:"/customers/:id",handler:async(ctx)=>{let userId=ctx.request.headers.get("x-user-id");if(!userId)return ctx.set.status=401,{success:!1,error:"Unauthorized"};try{let{id}=ctx.params,[existing]=await database.select().from(cusTable).where(eq65(cusTable.id,id));if(!existing)return ctx.set.status=404,{success:!1,error:"Customer not found"};if(String(existing.userId)!==String(userId)&&!hasGodminRole(ctx.request))return ctx.set.status=403,{success:!1,error:"Forbidden"};return await provider.deleteCustomer({providerCustomerId:existing.providerCustomerId}),await database.delete(cusTable).where(eq65(cusTable.id,id)),{success:!0}}catch(error3){return logger2.error("[Payment] Delete customer error",{error:String(error3)}),ctx.set.status=500,{success:!1,error:"Failed to delete customer"}}}}),routePostSubscriptions=defineRoute({method:"POST",path:"/subscriptions",handler:async(ctx)=>{let userId=ctx.request.headers.get("x-user-id");if(!userId)return ctx.set.status=401,{success:!1,error:"Unauthorized"};try{let body=ctx.body,customerId=body.customerId,[customer]=await database.select().from(cusTable).where(eq65(cusTable.id,customerId));if(!customer)return ctx.set.status=404,{success:!1,error:"Customer not found"};if(String(customer.userId)!==String(userId)&&!hasGodminRole(ctx.request))return ctx.set.status=403,{success:!1,error:"Forbidden"};let items=body.items,result=await provider.createSubscription({providerCustomerId:customer.providerCustomerId,items,trialPeriodDays:body.trialPeriodDays,collectionMethod:body.collectionMethod,daysUntilDue:body.daysUntilDue,cancelAtPeriodEnd:body.cancelAtPeriodEnd,metadata:body.metadata,defaultPaymentMethod:body.defaultPaymentMethod});if(!result.success)return ctx.set.status=400,result;let[row]=await database.insert(subTable).values({customerId,provider:provider.name,providerSubscriptionId:result.providerSubscriptionId,status:result.status??"incomplete",collectionMethod:body.collectionMethod??"charge_automatically",currentPeriodStart:result.currentPeriodStart?new Date(result.currentPeriodStart):null,currentPeriodEnd:result.currentPeriodEnd?new Date(result.currentPeriodEnd):null,cancelAtPeriodEnd:body.cancelAtPeriodEnd??!1,items,metadata:body.metadata??{}}).returning();return{success:!0,subscription:row,clientSecret:result.clientSecret}}catch(error3){return logger2.error("[Payment] Create subscription error",{error:String(error3)}),ctx.set.status=500,{success:!1,error:"Failed to create subscription"}}}}),routePutSubscriptionsId=defineRoute({method:"PUT",path:"/subscriptions/:id",handler:async(ctx)=>{if(!ctx.request.headers.get("x-user-id"))return ctx.set.status=401,{success:!1,error:"Unauthorized"};try{let{id}=ctx.params,body=ctx.body,[existing]=await database.select().from(subTable).where(eq65(subTable.id,id));if(!existing)return ctx.set.status=404,{success:!1,error:"Subscription not found"};if(!await customerBelongsToCaller(ctx.request,existing.customerId))return ctx.set.status=403,{success:!1,error:"Forbidden"};let result=await provider.updateSubscription({providerSubscriptionId:existing.providerSubscriptionId,items:body.items,cancelAtPeriodEnd:body.cancelAtPeriodEnd,metadata:body.metadata,collectionMethod:body.collectionMethod,daysUntilDue:body.daysUntilDue,defaultPaymentMethod:body.defaultPaymentMethod,prorationBehavior:body.prorationBehavior});if(!result.success)return ctx.set.status=400,result;let updates={};if(result.status)updates.status=result.status;if(body.cancelAtPeriodEnd!==void 0)updates.cancelAtPeriodEnd=body.cancelAtPeriodEnd;if(body.items)updates.items=body.items;if(body.metadata)updates.metadata=body.metadata;if(body.collectionMethod)updates.collectionMethod=body.collectionMethod;let[updated]=await database.update(subTable).set(updates).where(eq65(subTable.id,id)).returning();return{success:!0,subscription:updated}}catch(error3){return logger2.error("[Payment] Update subscription error",{error:String(error3)}),ctx.set.status=500,{success:!1,error:"Failed to update subscription"}}}}),routePostSubscriptionsIdCancel=defineRoute({method:"POST",path:"/subscriptions/:id/cancel",handler:async(ctx)=>{if(!ctx.request.headers.get("x-user-id"))return ctx.set.status=401,{success:!1,error:"Unauthorized"};try{let{id}=ctx.params,body=ctx.body??{},[existing]=await database.select().from(subTable).where(eq65(subTable.id,id));if(!existing)return ctx.set.status=404,{success:!1,error:"Subscription not found"};if(!await customerBelongsToCaller(ctx.request,existing.customerId))return ctx.set.status=403,{success:!1,error:"Forbidden"};let result=await provider.cancelSubscription({providerSubscriptionId:existing.providerSubscriptionId,cancelAtPeriodEnd:body.cancelAtPeriodEnd,invoiceNow:body.invoiceNow,prorate:body.prorate});if(!result.success)return ctx.set.status=400,result;let updates={status:result.status??"canceled",canceledAt:new Date};if(body.cancelAtPeriodEnd)updates.cancelAtPeriodEnd=!0;let[updated]=await database.update(subTable).set(updates).where(eq65(subTable.id,id)).returning();return{success:!0,subscription:updated}}catch(error3){return logger2.error("[Payment] Cancel subscription error",{error:String(error3)}),ctx.set.status=500,{success:!1,error:"Failed to cancel subscription"}}}}),routeGetSubscriptions=defineRoute({method:"GET",path:"/subscriptions",handler:async(ctx)=>{let userId=ctx.request.headers.get("x-user-id");if(!userId)return ctx.set.status=401,{success:!1,error:"Unauthorized"};try{let query=ctx.query,conditions=[];if(query.customerId)conditions.push(eq65(subTable.customerId,query.customerId));if(query.status)conditions.push(eq65(subTable.status,query.status));if(!hasGodminRole(ctx.request)){let ids=await callerCustomerIds(userId);if(ids.length===0)return{success:!0,subscriptions:[]};conditions.push(inArray19(subTable.customerId,ids))}return{success:!0,subscriptions:conditions.length>0?await database.select().from(subTable).where(and27(...conditions)):await database.select().from(subTable)}}catch(error3){return logger2.error("[Payment] List subscriptions error",{error:String(error3)}),ctx.set.status=500,{success:!1,error:"Failed to list subscriptions"}}}}),routeGetSubscriptionsId=defineRoute({method:"GET",path:"/subscriptions/:id",handler:async(ctx)=>{if(!ctx.request.headers.get("x-user-id"))return ctx.set.status=401,{success:!1,error:"Unauthorized"};try{let{id}=ctx.params,[subscription]=await database.select().from(subTable).where(eq65(subTable.id,id));if(!subscription)return ctx.set.status=404,{success:!1,error:"Subscription not found"};if(!await customerBelongsToCaller(ctx.request,subscription.customerId))return ctx.set.status=403,{success:!1,error:"Forbidden"};return{success:!0,subscription}}catch(error3){return logger2.error("[Payment] Get subscription error",{error:String(error3)}),ctx.set.status=500,{success:!1,error:"Failed to get subscription"}}}}),routePostUsageRecords=defineRoute({method:"POST",path:"/usage-records",handler:async(ctx)=>{if(!ctx.request.headers.get("x-user-id"))return ctx.set.status=401,{success:!1,error:"Unauthorized"};try{let body=ctx.body,result=await provider.reportUsage({subscriptionItemId:body.subscriptionItemId,quantity:body.quantity,timestamp:body.timestamp,action:body.action});if(!result.success)return ctx.set.status=400,result;return{success:!0,usageRecord:result}}catch(error3){return logger2.error("[Payment] Report usage error",{error:String(error3)}),ctx.set.status=500,{success:!1,error:"Failed to report usage"}}}}),routeGetUsageRecordsSubscriptionitemidSummaries=defineRoute({method:"GET",path:"/usage-records/:subscriptionItemId/summaries",handler:async(ctx)=>{if(!ctx.request.headers.get("x-user-id"))return ctx.set.status=401,{success:!1,error:"Unauthorized"};try{let{subscriptionItemId}=ctx.params,query=ctx.query,result=await provider.listUsageRecordSummaries({subscriptionItemId,limit:query.limit?parseInt(query.limit,10):void 0,startingAfter:query.startingAfter});if(!result.success)return ctx.set.status=400,result;return{success:!0,summaries:result.summaries,hasMore:result.hasMore}}catch(error3){return logger2.error("[Payment] List usage summaries error",{error:String(error3)}),ctx.set.status=500,{success:!1,error:"Failed to list usage summaries"}}}}),routePostInvoices=defineRoute({method:"POST",path:"/invoices",handler:async(ctx)=>{let userId=ctx.request.headers.get("x-user-id");if(!userId)return ctx.set.status=401,{success:!1,error:"Unauthorized"};try{let body=ctx.body,customerId=body.customerId,[customer]=await database.select().from(cusTable).where(eq65(cusTable.id,customerId));if(!customer)return ctx.set.status=404,{success:!1,error:"Customer not found"};if(String(customer.userId)!==String(userId)&&!hasGodminRole(ctx.request))return ctx.set.status=403,{success:!1,error:"Forbidden"};let result=await provider.createInvoice({providerCustomerId:customer.providerCustomerId,collectionMethod:body.collectionMethod,daysUntilDue:body.daysUntilDue,description:body.description,metadata:body.metadata,autoAdvance:body.autoAdvance,items:body.items});if(!result.success)return ctx.set.status=400,result;let[row]=await database.insert(invTable).values({customerId,subscriptionId:body.subscriptionId??null,provider:provider.name,providerInvoiceId:result.providerInvoiceId,status:result.status??"draft",collectionMethod:body.collectionMethod??"charge_automatically",currency:body.currency??defaultCurrency,description:body.description??null,hostedInvoiceUrl:result.hostedInvoiceUrl??null,invoicePdf:result.invoicePdf??null,daysUntilDue:body.daysUntilDue??null,lines:body.items??[],metadata:body.metadata??{}}).returning();return{success:!0,invoice:row,providerInvoiceId:result.providerInvoiceId}}catch(error3){return logger2.error("[Payment] Create invoice error",{error:String(error3)}),ctx.set.status=500,{success:!1,error:"Failed to create invoice"}}}}),routePostInvoicesIdFinalize=defineRoute({method:"POST",path:"/invoices/:id/finalize",handler:async(ctx)=>{if(!ctx.request.headers.get("x-user-id"))return ctx.set.status=401,{success:!1,error:"Unauthorized"};try{let{id}=ctx.params,body=ctx.body??{},[existing]=await database.select().from(invTable).where(eq65(invTable.id,id));if(!existing)return ctx.set.status=404,{success:!1,error:"Invoice not found"};if(!await customerBelongsToCaller(ctx.request,existing.customerId))return ctx.set.status=403,{success:!1,error:"Forbidden"};let result=await provider.finalizeInvoice({providerInvoiceId:existing.providerInvoiceId,autoAdvance:body.autoAdvance});if(!result.success)return ctx.set.status=400,result;let[updated]=await database.update(invTable).set({status:result.status??"open",hostedInvoiceUrl:result.hostedInvoiceUrl??existing.hostedInvoiceUrl,invoicePdf:result.invoicePdf??existing.invoicePdf}).where(eq65(invTable.id,id)).returning();return{success:!0,invoice:updated}}catch(error3){return logger2.error("[Payment] Finalize invoice error",{error:String(error3)}),ctx.set.status=500,{success:!1,error:"Failed to finalize invoice"}}}}),routePostInvoicesIdSend=defineRoute({method:"POST",path:"/invoices/:id/send",handler:async(ctx)=>{if(!ctx.request.headers.get("x-user-id"))return ctx.set.status=401,{success:!1,error:"Unauthorized"};try{let{id}=ctx.params,[existing]=await database.select().from(invTable).where(eq65(invTable.id,id));if(!existing)return ctx.set.status=404,{success:!1,error:"Invoice not found"};if(!await customerBelongsToCaller(ctx.request,existing.customerId))return ctx.set.status=403,{success:!1,error:"Forbidden"};let result=await provider.sendInvoice({providerInvoiceId:existing.providerInvoiceId});if(!result.success)return ctx.set.status=400,result;return{success:!0}}catch(error3){return logger2.error("[Payment] Send invoice error",{error:String(error3)}),ctx.set.status=500,{success:!1,error:"Failed to send invoice"}}}}),routePostInvoicesIdVoid=defineRoute({method:"POST",path:"/invoices/:id/void",handler:async(ctx)=>{if(!ctx.request.headers.get("x-user-id"))return ctx.set.status=401,{success:!1,error:"Unauthorized"};try{let{id}=ctx.params,[existing]=await database.select().from(invTable).where(eq65(invTable.id,id));if(!existing)return ctx.set.status=404,{success:!1,error:"Invoice not found"};if(!await customerBelongsToCaller(ctx.request,existing.customerId))return ctx.set.status=403,{success:!1,error:"Forbidden"};let result=await provider.voidInvoice({providerInvoiceId:existing.providerInvoiceId});if(!result.success)return ctx.set.status=400,result;let[updated]=await database.update(invTable).set({status:"void",voidedAt:new Date}).where(eq65(invTable.id,id)).returning();return{success:!0,invoice:updated}}catch(error3){return logger2.error("[Payment] Void invoice error",{error:String(error3)}),ctx.set.status=500,{success:!1,error:"Failed to void invoice"}}}}),routePostInvoicesIdPay=defineRoute({method:"POST",path:"/invoices/:id/pay",handler:async(ctx)=>{if(!ctx.request.headers.get("x-user-id"))return ctx.set.status=401,{success:!1,error:"Unauthorized"};try{let{id}=ctx.params,body=ctx.body??{},[existing]=await database.select().from(invTable).where(eq65(invTable.id,id));if(!existing)return ctx.set.status=404,{success:!1,error:"Invoice not found"};if(!await customerBelongsToCaller(ctx.request,existing.customerId))return ctx.set.status=403,{success:!1,error:"Forbidden"};let result=await provider.payInvoice({providerInvoiceId:existing.providerInvoiceId,paymentMethod:body.paymentMethod});if(!result.success)return ctx.set.status=400,result;let[updated]=await database.update(invTable).set({status:result.status??"paid",amountPaid:result.amountPaid??existing.amountPaid,amountDue:result.amountDue??existing.amountDue,paidAt:new Date}).where(eq65(invTable.id,id)).returning();return{success:!0,invoice:updated}}catch(error3){return logger2.error("[Payment] Pay invoice error",{error:String(error3)}),ctx.set.status=500,{success:!1,error:"Failed to pay invoice"}}}}),routeGetInvoices=defineRoute({method:"GET",path:"/invoices",handler:async(ctx)=>{let userId=ctx.request.headers.get("x-user-id");if(!userId)return ctx.set.status=401,{success:!1,error:"Unauthorized"};try{let query=ctx.query,conditions=[];if(query.customerId)conditions.push(eq65(invTable.customerId,query.customerId));if(query.subscriptionId)conditions.push(eq65(invTable.subscriptionId,query.subscriptionId));if(query.status)conditions.push(eq65(invTable.status,query.status));if(!hasGodminRole(ctx.request)){let ids=await callerCustomerIds(userId);if(ids.length===0)return{success:!0,invoices:[]};conditions.push(inArray19(invTable.customerId,ids))}return{success:!0,invoices:conditions.length>0?await database.select().from(invTable).where(and27(...conditions)):await database.select().from(invTable)}}catch(error3){return logger2.error("[Payment] List invoices error",{error:String(error3)}),ctx.set.status=500,{success:!1,error:"Failed to list invoices"}}}}),routeGetInvoicesId=defineRoute({method:"GET",path:"/invoices/:id",handler:async(ctx)=>{if(!ctx.request.headers.get("x-user-id"))return ctx.set.status=401,{success:!1,error:"Unauthorized"};try{let{id}=ctx.params,[invoice]=await database.select().from(invTable).where(eq65(invTable.id,id));if(!invoice)return ctx.set.status=404,{success:!1,error:"Invoice not found"};if(!await customerBelongsToCaller(ctx.request,invoice.customerId))return ctx.set.status=403,{success:!1,error:"Forbidden"};return{success:!0,invoice}}catch(error3){return logger2.error("[Payment] Get invoice error",{error:String(error3)}),ctx.set.status=500,{success:!1,error:"Failed to get invoice"}}}});if(subMerchantsEnabled){let routePostSubMerchants=defineRoute({method:"POST",path:"/sub-merchants",handler:async(ctx)=>{let userId=ctx.request.headers.get("x-user-id");if(!userId)return ctx.set.status=401,{success:!1,error:"Unauthorized"};let body=ctx.body;if(!body.name||!body.email||!body.subMerchantType)return ctx.set.status=400,{success:!1,error:"name, email, and subMerchantType are required"};try{let externalId=body.externalId??`sm-${Date.now().toString(36)}`,result=await provider.registerSubMerchant({locale:body.locale??defaultLocale,subMerchantExternalId:externalId,subMerchantType:body.subMerchantType,name:body.name,email:body.email,gsmNumber:body.gsmNumber,address:body.address,iban:body.iban,contactName:body.contactName,contactSurname:body.contactSurname,identityNumber:body.identityNumber,taxOffice:body.taxOffice,taxNumber:body.taxNumber,legalCompanyTitle:body.legalCompanyTitle,currency:body.currency??defaultCurrency});if(!result.success)return ctx.set.status=400,{success:!1,error:result.errorMessage??"Failed to register sub-merchant",errorCode:result.errorCode};let[saved]=await database.insert(smTable).values({userId:hasGodminRole(ctx.request)?body.userId??userId:userId,provider:provider.name,providerSubMerchantKey:result.subMerchantKey,externalId,type:body.subMerchantType,status:"active",name:body.name,email:body.email,gsmNumber:body.gsmNumber??null,address:body.address??null,iban:body.iban??null,contactName:body.contactName??null,contactSurname:body.contactSurname??null,identityNumber:body.identityNumber??null,taxOffice:body.taxOffice??null,taxNumber:body.taxNumber??null,legalCompanyTitle:body.legalCompanyTitle??null,currency:body.currency??defaultCurrency,commissionRate:hasGodminRole(ctx.request)?body.commissionRate??0:0}).returning();return logger2.info("[Payment] Sub-merchant registered",{externalId,providerKey:result.subMerchantKey}),{success:!0,data:saved}}catch(error3){return logger2.error("[Payment] register sub-merchant error",{error:error3 instanceof Error?error3.message:String(error3)}),ctx.set.status=500,{success:!1,error:"Failed to register sub-merchant"}}}});declared.push(routePostSubMerchants);let routePutSubMerchantsId=defineRoute({method:"PUT",path:"/sub-merchants/:id",handler:async(ctx)=>{let userId=ctx.request.headers.get("x-user-id");if(!userId)return ctx.set.status=401,{success:!1,error:"Unauthorized"};let body=ctx.body;try{let[existing]=await database.select().from(smTable).where(eq65(smTable.id,ctx.params.id)).limit(1);if(!existing)return ctx.set.status=404,{success:!1,error:"Sub-merchant not found"};let smIsGodmin=hasGodminRole(ctx.request);if(String(existing.userId)!==String(userId)&&!smIsGodmin)return ctx.set.status=403,{success:!1,error:"Forbidden"};if(existing.providerSubMerchantKey){let providerResult=await provider.updateSubMerchant({locale:body.locale??defaultLocale,subMerchantKey:existing.providerSubMerchantKey,name:body.name,email:body.email,gsmNumber:body.gsmNumber,address:body.address,iban:body.iban,contactName:body.contactName,contactSurname:body.contactSurname,identityNumber:body.identityNumber,taxOffice:body.taxOffice,taxNumber:body.taxNumber,legalCompanyTitle:body.legalCompanyTitle,currency:body.currency});if(!providerResult.success)logger2.warn("[Payment] Provider sub-merchant update failed",{errorCode:providerResult.errorCode})}let updateData={};if(body.name)updateData.name=body.name;if(body.email)updateData.email=body.email;if(body.gsmNumber!==void 0)updateData.gsmNumber=body.gsmNumber;if(body.address!==void 0)updateData.address=body.address;if(body.iban!==void 0)updateData.iban=body.iban;if(body.contactName!==void 0)updateData.contactName=body.contactName;if(body.contactSurname!==void 0)updateData.contactSurname=body.contactSurname;if(body.identityNumber!==void 0)updateData.identityNumber=body.identityNumber;if(body.taxOffice!==void 0)updateData.taxOffice=body.taxOffice;if(body.taxNumber!==void 0)updateData.taxNumber=body.taxNumber;if(body.legalCompanyTitle!==void 0)updateData.legalCompanyTitle=body.legalCompanyTitle;if(body.currency!==void 0)updateData.currency=body.currency;if(body.commissionRate!==void 0&&smIsGodmin)updateData.commissionRate=body.commissionRate;if(body.status!==void 0&&smIsGodmin)updateData.status=body.status;let[updated]=await database.update(smTable).set(updateData).where(eq65(smTable.id,ctx.params.id)).returning();return{success:!0,data:updated}}catch{return ctx.set.status=500,{success:!1,error:"Failed to update sub-merchant"}}}});declared.push(routePutSubMerchantsId);let routeGetSubMerchants=defineRoute({method:"GET",path:"/sub-merchants",handler:async(ctx)=>{let userId=ctx.request.headers.get("x-user-id");if(!userId)return ctx.set.status=401,{success:!1,error:"Unauthorized"};try{let smListGodmin=hasGodminRole(ctx.request);return{success:!0,data:{items:await database.select().from(smTable).where(smListGodmin?eq65(smTable.isActive,!0):and27(eq65(smTable.isActive,!0),eq65(smTable.userId,userId))).orderBy(smTable.createdAt)}}}catch{return ctx.set.status=500,{success:!1,error:"Failed to list sub-merchants"}}}});declared.push(routeGetSubMerchants);let routeGetSubMerchantsId=defineRoute({method:"GET",path:"/sub-merchants/:id",handler:async(ctx)=>{let userId=ctx.request.headers.get("x-user-id");if(!userId)return ctx.set.status=401,{success:!1,error:"Unauthorized"};try{let[item]=await database.select().from(smTable).where(eq65(smTable.id,ctx.params.id)).limit(1);if(!item)return ctx.set.status=404,{success:!1,error:"Sub-merchant not found"};if(String(item.userId)!==String(userId)&&!hasGodminRole(ctx.request))return ctx.set.status=403,{success:!1,error:"Forbidden"};return{success:!0,data:item}}catch{return ctx.set.status=500,{success:!1,error:"Failed to get sub-merchant"}}}});declared.push(routeGetSubMerchantsId);let routePostSplitsSplitidApprove=defineRoute({method:"POST",path:"/splits/:splitId/approve",handler:async(ctx)=>{if(!ctx.request.headers.get("x-user-id"))return ctx.set.status=401,{success:!1,error:"Unauthorized"};if(!hasGodminRole(ctx.request))return ctx.set.status=403,{success:!1,error:"Forbidden: operator privileges required"};try{let[split]=await database.select().from(csTable).where(eq65(csTable.id,ctx.params.splitId)).limit(1);if(!split)return ctx.set.status=404,{success:!1,error:"Split not found"};if(split.status!=="pending")return ctx.set.status=400,{success:!1,error:`Split already ${split.status}`};if(split.providerSplitId){let result=await provider.approveSplit({locale:defaultLocale,paymentTransactionId:split.providerSplitId});if(!result.success)return ctx.set.status=400,{success:!1,error:result.errorMessage??"Provider approval failed",errorCode:result.errorCode}}let[updated]=await database.update(csTable).set({status:"approved",approvedAt:new Date}).where(eq65(csTable.id,ctx.params.splitId)).returning();return logger2.info("[Payment] Split approved",{splitId:ctx.params.splitId}),{success:!0,data:updated}}catch{return ctx.set.status=500,{success:!1,error:"Failed to approve split"}}}});declared.push(routePostSplitsSplitidApprove);let routePostSplitsSplitidDisapprove=defineRoute({method:"POST",path:"/splits/:splitId/disapprove",handler:async(ctx)=>{if(!ctx.request.headers.get("x-user-id"))return ctx.set.status=401,{success:!1,error:"Unauthorized"};if(!hasGodminRole(ctx.request))return ctx.set.status=403,{success:!1,error:"Forbidden: operator privileges required"};try{let[split]=await database.select().from(csTable).where(eq65(csTable.id,ctx.params.splitId)).limit(1);if(!split)return ctx.set.status=404,{success:!1,error:"Split not found"};if(split.status!=="pending")return ctx.set.status=400,{success:!1,error:`Split already ${split.status}`};if(split.providerSplitId){let result=await provider.disapproveSplit({locale:defaultLocale,paymentTransactionId:split.providerSplitId});if(!result.success)return ctx.set.status=400,{success:!1,error:result.errorMessage??"Provider disapproval failed",errorCode:result.errorCode}}let[updated]=await database.update(csTable).set({status:"disapproved",disapprovedAt:new Date}).where(eq65(csTable.id,ctx.params.splitId)).returning();return logger2.info("[Payment] Split disapproved",{splitId:ctx.params.splitId}),{success:!0,data:updated}}catch{return ctx.set.status=500,{success:!1,error:"Failed to disapprove split"}}}});declared.push(routePostSplitsSplitidDisapprove);let routeGetSplits=defineRoute({method:"GET",path:"/splits",handler:async(ctx)=>{if(!ctx.request.headers.get("x-user-id"))return ctx.set.status=401,{success:!1,error:"Unauthorized"};if(!hasGodminRole(ctx.request))return ctx.set.status=403,{success:!1,error:"Forbidden: operator privileges required"};try{let url=new URL(ctx.request.url),transactionId=url.searchParams.get("transactionId"),subMerchantId=url.searchParams.get("subMerchantId"),query=database.select().from(csTable);if(transactionId&&subMerchantId)query=query.where(and27(eq65(csTable.transactionId,transactionId),eq65(csTable.subMerchantId,subMerchantId)));else if(transactionId)query=query.where(eq65(csTable.transactionId,transactionId));else if(subMerchantId)query=query.where(eq65(csTable.subMerchantId,subMerchantId));return{success:!0,data:{items:await query.orderBy(csTable.createdAt)}}}catch{return ctx.set.status=500,{success:!1,error:"Failed to list splits"}}}});declared.push(routeGetSplits)}let routeGetBalance=defineRoute({method:"GET",path:"/balance",handler:async(ctx)=>{if(!ctx.request.headers.get("x-user-id"))return ctx.set.status=401,{success:!1,error:"Unauthorized"};try{let result=await provider.getBalance();if(!result.success)return ctx.set.status=400,{success:!1,error:result.errorMessage??"Failed to get balance"};return{success:!0,data:{available:result.available,pending:result.pending,connectReserved:result.connectReserved}}}catch{return ctx.set.status=500,{success:!1,error:"Failed to get balance"}}}}),routePostPayouts=defineRoute({method:"POST",path:"/payouts",handler:async(ctx)=>{if(!ctx.request.headers.get("x-user-id"))return ctx.set.status=401,{success:!1,error:"Unauthorized"};if(!hasGodminRole(ctx.request))return ctx.set.status=403,{success:!1,error:"Forbidden: operator privileges required"};try{let body=ctx.body,payoutAmt=validateAmount(body.amount,"amount");if(!payoutAmt.ok)return ctx.set.status=400,{success:!1,error:payoutAmt.error};if(!isValidCurrency(body.currency))return ctx.set.status=400,{success:!1,error:"currency must be a 3-letter code"};let result=await provider.createPayout({amount:payoutAmt.value,currency:body.currency??defaultCurrency,destination:body.destination,description:body.description,metadata:body.metadata,method:body.method,sourceType:body.sourceType,statementDescriptor:body.statementDescriptor});if(!result.success)return ctx.set.status=400,{success:!1,error:result.errorMessage??"Failed to create payout"};return{success:!0,data:result}}catch{return ctx.set.status=500,{success:!1,error:"Failed to create payout"}}}}),routeGetPayouts=defineRoute({method:"GET",path:"/payouts",handler:async(ctx)=>{if(!ctx.request.headers.get("x-user-id"))return ctx.set.status=401,{success:!1,error:"Unauthorized"};try{let url=new URL(ctx.request.url),result=await provider.listPayouts({status:url.searchParams.get("status")??void 0,destination:url.searchParams.get("destination")??void 0,limit:url.searchParams.get("limit")?Number(url.searchParams.get("limit")):void 0,startingAfter:url.searchParams.get("startingAfter")??void 0});if(!result.success)return ctx.set.status=400,{success:!1,error:result.errorMessage??"Failed to list payouts"};return{success:!0,data:{payouts:result.payouts,hasMore:result.hasMore}}}catch{return ctx.set.status=500,{success:!1,error:"Failed to list payouts"}}}}),routeGetPayoutsId=defineRoute({method:"GET",path:"/payouts/:id",handler:async(ctx)=>{if(!ctx.request.headers.get("x-user-id"))return ctx.set.status=401,{success:!1,error:"Unauthorized"};try{let result=await provider.getPayout({providerPayoutId:ctx.params.id});if(!result.success)return ctx.set.status=400,{success:!1,error:result.errorMessage??"Failed to get payout"};return{success:!0,data:result}}catch{return ctx.set.status=500,{success:!1,error:"Failed to get payout"}}}}),routePostPayoutsIdCancel=defineRoute({method:"POST",path:"/payouts/:id/cancel",handler:async(ctx)=>{if(!ctx.request.headers.get("x-user-id"))return ctx.set.status=401,{success:!1,error:"Unauthorized"};if(!hasGodminRole(ctx.request))return ctx.set.status=403,{success:!1,error:"Forbidden: operator privileges required"};try{let result=await provider.cancelPayout({providerPayoutId:ctx.params.id});if(!result.success)return ctx.set.status=400,{success:!1,error:result.errorMessage??"Failed to cancel payout"};return{success:!0,data:result}}catch{return ctx.set.status=500,{success:!1,error:"Failed to cancel payout"}}}}),routePostTransfers=defineRoute({method:"POST",path:"/transfers",handler:async(ctx)=>{if(!ctx.request.headers.get("x-user-id"))return ctx.set.status=401,{success:!1,error:"Unauthorized"};if(!hasGodminRole(ctx.request))return ctx.set.status=403,{success:!1,error:"Forbidden: operator privileges required"};try{let body=ctx.body,transferAmt=validateAmount(body.amount,"amount");if(!transferAmt.ok)return ctx.set.status=400,{success:!1,error:transferAmt.error};if(!isValidCurrency(body.currency))return ctx.set.status=400,{success:!1,error:"currency must be a 3-letter code"};let result=await provider.createTransfer({amount:transferAmt.value,currency:body.currency??defaultCurrency,destination:body.destination,description:body.description,metadata:body.metadata,sourceTransaction:body.sourceTransaction,transferGroup:body.transferGroup});if(!result.success)return ctx.set.status=400,{success:!1,error:result.errorMessage??"Failed to create transfer"};return{success:!0,data:result}}catch{return ctx.set.status=500,{success:!1,error:"Failed to create transfer"}}}}),routeGetTransfers=defineRoute({method:"GET",path:"/transfers",handler:async(ctx)=>{if(!ctx.request.headers.get("x-user-id"))return ctx.set.status=401,{success:!1,error:"Unauthorized"};try{let url=new URL(ctx.request.url),result=await provider.listTransfers({destination:url.searchParams.get("destination")??void 0,transferGroup:url.searchParams.get("transferGroup")??void 0,limit:url.searchParams.get("limit")?Number(url.searchParams.get("limit")):void 0,startingAfter:url.searchParams.get("startingAfter")??void 0});if(!result.success)return ctx.set.status=400,{success:!1,error:result.errorMessage??"Failed to list transfers"};return{success:!0,data:{transfers:result.transfers,hasMore:result.hasMore}}}catch{return ctx.set.status=500,{success:!1,error:"Failed to list transfers"}}}}),routeOptionsCallback=defineRoute({method:"OPTIONS",path:"/callback",handler:()=>{return new Response(null,{headers:{"Access-Control-Allow-Origin":"*","Access-Control-Allow-Methods":"GET,POST,OPTIONS","Access-Control-Allow-Headers":"Content-Type"}})}}),routePostWebhook=createStripeWebhookRoute({provider,webhookSecret:config.webhookSecret,db:database,webhookLogsTable:whTable,transactionsTable:txTable,getPayoutService:config.getPayoutService,logger:logger2});return declared.push(routePostInitialize,routePostCallback,routeGetTransactions,routeGetTransactionsId,routePostBinQuery,routePostPaymentDetail,routePostRefund,routePostProducts,routePutProductsId,routeGetProducts,routeGetProductsId,routeDeleteProductsId,routePostPrices,routePutPricesId,routeGetPrices,routeGetPricesId,routeDeletePricesId,routePostCustomers,routePutCustomersId,routeGetCustomers,routeGetCustomersId,routeDeleteCustomersId,routePostSubscriptions,routePutSubscriptionsId,routePostSubscriptionsIdCancel,routeGetSubscriptions,routeGetSubscriptionsId,routePostUsageRecords,routeGetUsageRecordsSubscriptionitemidSummaries,routePostInvoices,routePostInvoicesIdFinalize,routePostInvoicesIdSend,routePostInvoicesIdVoid,routePostInvoicesIdPay,routeGetInvoices,routeGetInvoicesId,routeGetBalance,routePostPayouts,routeGetPayouts,routeGetPayoutsId,routePostPayoutsIdCancel,routePostTransfers,routeGetTransfers,routeOptionsCallback),app.use(collectRoutes([{prefix:basePath,routes:[...declared,routePostWebhook]}])),app};var init_payment=__esm(()=>{init_routeCollector();init_adminGuard();init_webhook();UUID_PATTERN=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i,CALLBACK_WRITABLE_STATUSES=["pending","processing","failed"],CALLBACK_COMPLETABLE_STATUSES=["pending","processing"]});var requireUser=(ctx)=>ctx.request.headers.get("x-user-id"),isAdmin=(ctx)=>decodeHeaderList(ctx.request.headers.get("x-user-roles")).some((r)=>isGodminRole(r)),canReadOwnerLedger=(ctx,ownerType,ownerId)=>{if(isAdmin(ctx))return!0;let userId=ctx.request.headers.get("x-user-id");return!!userId&&ownerType==="user"&&!!ownerId&&ownerId===userId},failResponse=(ctx,status,message)=>{return ctx.set.status=status,{success:!1,message,data:null}};var init_types22=__esm(()=>{init_Authorization()});function createPayoutRoutes(config){let base=`${config.basePath}/marketplace`,routePostPayoutRequests=defineRoute({method:"POST",path:"/payout-requests",body:Type.Object({recipientOwnerId:Type.String(),recipientOwnerType:Type.Optional(Type.String()),amount:Type.Number(),currency:Type.Optional(Type.String()),destination:Type.Optional(Type.String()),requestedBy:Type.Optional(Type.String())}),detail:{tags:["Payments Marketplace"],summary:"Request a payout"},handler:async(ctx)=>{let svc=config.getPayoutService();if(!svc)return failResponse(ctx,503,"Marketplace not available");let userId=requireUser(ctx);if(!userId)return failResponse(ctx,401,"Authentication required");let body=ctx.body;if(!canReadOwnerLedger(ctx,body.recipientOwnerType,body.recipientOwnerId))return failResponse(ctx,403,"Forbidden: cannot request a payout for another owner's balance");try{return{success:!0,message:"Payout requested",data:await svc.requestPayout({...body,requestedBy:userId})}}catch(err){return failResponse(ctx,400,err instanceof Error?err.message:"Failed")}}}),routeGetPayoutRequests=defineRoute({method:"GET",path:"/payout-requests",handler:async(ctx)=>{let svc=config.getPayoutService();if(!svc)return failResponse(ctx,503,"Marketplace not available");let userId=requireUser(ctx);if(!userId)return failResponse(ctx,401,"Authentication required");let url=new URL(ctx.request.url),requestedOwnerId=url.searchParams.get("recipientOwnerId")??void 0,recipientOwnerId=isAdmin(ctx)?requestedOwnerId:userId,items=await svc.listPayoutRequests({recipientOwnerId,status:url.searchParams.get("status")??void 0});return{success:!0,message:`Found ${items.length} payout requests`,data:{items}}}}),routeGetPayoutRequestsId=defineRoute({method:"GET",path:"/payout-requests/:id",handler:async(ctx)=>{let svc=config.getPayoutService();if(!svc)return failResponse(ctx,503,"Marketplace not available");if(!requireUser(ctx))return failResponse(ctx,401,"Authentication required");let record3=await svc.getPayoutRequest(ctx.params.id);if(!record3)return failResponse(ctx,404,"Payout request not found");let r=record3;if(!canReadOwnerLedger(ctx,r.recipientOwnerType,r.recipientOwnerId))return failResponse(ctx,404,"Payout request not found");return{success:!0,message:"Payout request",data:record3}}}),routePostPayoutRequestsIdApprove=defineRoute({method:"POST",path:"/payout-requests/:id/approve",handler:async(ctx)=>{let svc=config.getPayoutService();if(!svc)return failResponse(ctx,503,"Marketplace not available");let userId=requireUser(ctx);if(!userId)return failResponse(ctx,401,"Authentication required");if(!isAdmin(ctx))return failResponse(ctx,403,"Forbidden: operator privileges required");try{let record3=await svc.approvePayout(ctx.params.id,{deciderId:userId});if(!record3)return failResponse(ctx,404,"Payout request not found");return{success:!0,message:"Payout approved",data:record3}}catch(err){return failResponse(ctx,400,err instanceof Error?err.message:"Failed")}}}),routePostPayoutRequestsIdReject=defineRoute({method:"POST",path:"/payout-requests/:id/reject",handler:async(ctx)=>{let svc=config.getPayoutService();if(!svc)return failResponse(ctx,503,"Marketplace not available");let userId=requireUser(ctx);if(!userId)return failResponse(ctx,401,"Authentication required");if(!isAdmin(ctx))return failResponse(ctx,403,"Forbidden: operator privileges required");let record3=await svc.rejectPayout(ctx.params.id,userId);if(!record3)return failResponse(ctx,404,"Payout request not found");return{success:!0,message:"Payout rejected",data:record3}}}),routePostDisputes=defineRoute({method:"POST",path:"/disputes",body:Type.Object({provider:Type.String(),transactionId:Type.Optional(Type.String()),providerDisputeId:Type.Optional(Type.String()),ownerType:Type.Optional(Type.String()),ownerId:Type.Optional(Type.String()),amount:Type.Optional(Type.Number()),currency:Type.Optional(Type.String()),reason:Type.Optional(Type.String())}),detail:{tags:["Payments Marketplace"],summary:"Open a dispute"},handler:async(ctx)=>{let svc=config.getPayoutService();if(!svc)return failResponse(ctx,503,"Marketplace not available");if(!requireUser(ctx))return failResponse(ctx,401,"Authentication required");if(!isAdmin(ctx))return failResponse(ctx,403,"Forbidden: operator privileges required");let body=ctx.body;return{success:!0,message:"Dispute opened",data:await svc.openDispute(body)}}}),routeGetDisputes=defineRoute({method:"GET",path:"/disputes",handler:async(ctx)=>{let svc=config.getPayoutService();if(!svc)return failResponse(ctx,503,"Marketplace not available");let userId=requireUser(ctx);if(!userId)return failResponse(ctx,401,"Authentication required");let url=new URL(ctx.request.url),requestedOwnerId=url.searchParams.get("ownerId")??void 0,ownerId=isAdmin(ctx)?requestedOwnerId:userId,items=await svc.listDisputes({ownerId,status:url.searchParams.get("status")??void 0});return{success:!0,message:`Found ${items.length} disputes`,data:{items}}}}),routePostDisputesIdResolve=defineRoute({method:"POST",path:"/disputes/:id/resolve",body:Type.Object({outcome:Type.Union([Type.Literal("won"),Type.Literal("lost"),Type.Literal("accepted"),Type.Literal("cancelled")])}),detail:{tags:["Payments Marketplace"],summary:"Resolve a dispute"},handler:async(ctx)=>{let svc=config.getPayoutService();if(!svc)return failResponse(ctx,503,"Marketplace not available");if(!requireUser(ctx))return failResponse(ctx,401,"Authentication required");if(!isAdmin(ctx))return failResponse(ctx,403,"Forbidden: operator privileges required");let body=ctx.body,record3=await svc.resolveDispute(ctx.params.id,body.outcome);if(!record3)return failResponse(ctx,404,"Dispute not found");return{success:!0,message:"Dispute resolved",data:record3}}});return collectRoutes([{prefix:base,routes:[routePostPayoutRequests,routeGetPayoutRequests,routeGetPayoutRequestsId,routePostPayoutRequestsIdApprove,routePostPayoutRequestsIdReject,routePostDisputes,routeGetDisputes,routePostDisputesIdResolve]}])}var init_payouts=__esm(()=>{init_esm3();init_routeCollector();init_types22()});function createSplitRoutes(config){let base=`${config.basePath}/marketplace`,routePostSplits=defineRoute({method:"POST",path:"/splits",body:Type.Object({recipientOwnerId:Type.String(),recipientOwnerType:Type.Optional(Type.String()),provider:Type.String(),grossAmount:Type.Number(),providerFeeAmount:Type.Optional(Type.Number()),platformFeeAmount:Type.Number(),currency:Type.Optional(Type.String()),transactionId:Type.Optional(Type.String()),sourceType:Type.Optional(Type.String()),sourceId:Type.Optional(Type.String()),settlementDelayDays:Type.Optional(Type.Number()),reserveRate:Type.Optional(Type.Number()),isNewSeller:Type.Optional(Type.Boolean())}),detail:{tags:["Payments Marketplace"],summary:"Record a payment split"},handler:async(ctx)=>{let svc=config.getMarketplaceService();if(!svc)return failResponse(ctx,503,"Marketplace not available");if(!requireUser(ctx))return failResponse(ctx,401,"Authentication required");if(!isAdmin(ctx))return failResponse(ctx,403,"Forbidden: operator privileges required");let body=ctx.body;try{return{success:!0,message:"Split recorded",data:await svc.recordSplit(body)}}catch(err){return failResponse(ctx,400,err instanceof Error?err.message:"Failed")}}}),routeGetSplits=defineRoute({method:"GET",path:"/splits",handler:async(ctx)=>{let svc=config.getMarketplaceService();if(!svc)return failResponse(ctx,503,"Marketplace not available");let userId=requireUser(ctx);if(!userId)return failResponse(ctx,401,"Authentication required");let url=new URL(ctx.request.url),requestedOwnerId=url.searchParams.get("recipientOwnerId")??void 0,recipientOwnerId=isAdmin(ctx)?requestedOwnerId:userId,items=await svc.listSplits({recipientOwnerId,status:url.searchParams.get("status")??void 0});return{success:!0,message:`Found ${items.length} splits`,data:{items}}}}),routeGetBalanceOwnerid=defineRoute({method:"GET",path:"/balance/:ownerId",handler:async(ctx)=>{let svc=config.getMarketplaceService();if(!svc)return failResponse(ctx,503,"Marketplace not available");if(!requireUser(ctx))return failResponse(ctx,401,"Authentication required");let url=new URL(ctx.request.url),ownerType=url.searchParams.get("ownerType")??"seller",ownerId=ctx.params.ownerId;if(!canReadOwnerLedger(ctx,ownerType,ownerId))return failResponse(ctx,403,"Forbidden: cannot read another owner's balance");return{success:!0,message:"Balance",data:await svc.getBalance(ownerType,ownerId,url.searchParams.get("currency")??void 0)}}}),routePostReservesIdRelease=defineRoute({method:"POST",path:"/reserves/:id/release",handler:async(ctx)=>{let svc=config.getMarketplaceService();if(!svc)return failResponse(ctx,503,"Marketplace not available");if(!requireUser(ctx))return failResponse(ctx,401,"Authentication required");if(!isAdmin(ctx))return failResponse(ctx,403,"Forbidden: operator privileges required");if(!await svc.releaseReserve(ctx.params.id))return failResponse(ctx,400,"Reserve not found or not held");return{success:!0,message:"Reserve released",data:{released:!0}}}}),routeGetSettlementPolicy=defineRoute({method:"GET",path:"/settlement-policy",handler:async(ctx)=>{let svc=config.getMarketplaceService();if(!svc)return failResponse(ctx,503,"Marketplace not available");if(!requireUser(ctx))return failResponse(ctx,401,"Authentication required");let url=new URL(ctx.request.url),ownerType=url.searchParams.get("ownerType")??"tenant",ownerId=url.searchParams.get("ownerId")??"";if(!canReadOwnerLedger(ctx,ownerType,ownerId))return failResponse(ctx,403,"Forbidden: cannot read this settlement policy");return{success:!0,message:"Settlement policy",data:await svc.getSettlementPolicy(ownerType,ownerId)}}}),routePostSettlementPolicy=defineRoute({method:"POST",path:"/settlement-policy",body:Type.Object({ownerType:Type.String(),ownerId:Type.String(),defaultDelayDays:Type.Optional(Type.Number()),newSellerDelayDays:Type.Optional(Type.Number()),reserveRate:Type.Optional(Type.Number()),reserveDays:Type.Optional(Type.Number()),minimumPayoutAmount:Type.Optional(Type.Number()),currency:Type.Optional(Type.String()),earlyPayoutEnabled:Type.Optional(Type.Boolean())}),detail:{tags:["Payments Marketplace"],summary:"Upsert settlement policy"},handler:async(ctx)=>{let svc=config.getMarketplaceService();if(!svc)return failResponse(ctx,503,"Marketplace not available");if(!requireUser(ctx))return failResponse(ctx,401,"Authentication required");if(!isAdmin(ctx))return failResponse(ctx,403,"Forbidden: operator privileges required");let body=ctx.body;return{success:!0,message:"Settlement policy saved",data:await svc.upsertSettlementPolicy(body)}}});return collectRoutes([{prefix:base,routes:[routePostSplits,routeGetSplits,routeGetBalanceOwnerid,routePostReservesIdRelease,routeGetSettlementPolicy,routePostSettlementPolicy]}])}var init_splits=__esm(()=>{init_esm3();init_routeCollector();init_types22()});var exports_marketplace={};__export(exports_marketplace,{createMarketplaceRoutes:()=>createMarketplaceRoutes});function createMarketplaceRoutes(app,config){return useRoutes(app,createSplitRoutes(config)),useRoutes(app,createPayoutRoutes(config)),app}var init_marketplace=__esm(()=>{init_routeMount();init_payouts();init_splits()});var exports_schemaReconcile={};__export(exports_schemaReconcile,{undeclaredTables:()=>undeclaredTables,setDefaultStatement:()=>setDefaultStatement,renderDefaultLiteral:()=>renderDefaultLiteral,reconcileMissingColumns:()=>reconcileMissingColumns,readActualSchemaDetailed:()=>readActualSchemaDetailed,readActualSchema:()=>readActualSchema,planRepair:()=>planRepair,findDrift:()=>findDrift,expectedFromTables:()=>expectedFromTables,describeRepair:()=>describeRepair,createMissingTables:()=>createMissingTables,addColumnStatement:()=>addColumnStatement});import{getTableColumns as getTableColumns2,sql as sql13}from"drizzle-orm";function findDrift(expected,actual,actualDefaults){let missingTables=[],missingColumns=[],columnsMissingDefault=[];for(let table of expected){let have=actual[table.name];if(!have){missingTables.push(table.name);continue}let haveDefault=actualDefaults?.[table.name];for(let column of table.columns){if(!have.has(column.name)){missingColumns.push({table:table.name,column});continue}if(haveDefault&&column.hasDefault&&column.defaultLiteral!==void 0&&!haveDefault.has(column.name))columnsMissingDefault.push({table:table.name,column})}}return{missingTables,missingColumns,columnsMissingDefault}}function addColumnStatement(schema3,table,column){if(column.notNull&&!column.hasDefault)return null;return[`alter table "${schema3}"."${table}"`,`add column if not exists "${column.name}" ${column.sqlType}`].join(" ")}function setDefaultStatement(schema3,table,column){if(column.defaultLiteral===void 0)return null;return`alter table "${schema3}"."${table}" alter column "${column.name}" set default ${column.defaultLiteral}`}function planRepair(schema3,drift){let statements=[],unsafe3=[];for(let{table,column}of drift.missingColumns){let statement=addColumnStatement(schema3,table,column);if(statement)statements.push(statement);else unsafe3.push({table,column:column.name,why:"declared NOT NULL with no default, and the table already has rows to fill"})}for(let{table,column}of drift.columnsMissingDefault){let statement=setDefaultStatement(schema3,table,column);if(statement)statements.push(statement)}return{statements,unsafe:unsafe3,missingTables:drift.missingTables,createdTables:[],undeclaredTables:[]}}function undeclaredTables(expected,actual){let declared=new Set(expected.map((t)=>t.name));return Object.keys(actual).filter((name2)=>!declared.has(name2)).sort()}function isAlreadyExists(error3){let code=error3?.code;if(typeof code==="string"&&ALREADY_EXISTS.has(code))return!0;let message=error3 instanceof Error?error3.message:String(error3);return/already exists/i.test(message)}async function createMissingTables(options){let{run,tables,missing,schemaObject,logger:logger2,timeoutMs=60000}=options;if(missing.length===0)return{applied:0,errors:[]};let wanted=new Set(missing),imports={};for(let[key2,table]of Object.entries(tables)){if(!table||typeof table!=="object")continue;let name2=tableNameOf(table);if(name2&&wanted.has(name2))imports[key2]=table}if(schemaObject)imports.__nucleusSchema=schemaObject;let statements;try{let{generateDrizzleJson,generateMigration}=await import("drizzle-kit/api"),realExit=process.exit;try{process.exit=(code)=>{throw Error(`drizzle-kit called process.exit(${code??0}) while generating table DDL`)},statements=await withDeadline(generateMigration(generateDrizzleJson({}),generateDrizzleJson(imports)),timeoutMs,`generating CREATE TABLE statements did not finish within ${timeoutMs}ms`)}finally{process.exit=realExit}}catch(error3){let message=error3 instanceof Error?error3.message:String(error3);return logger2.error(`[Schema] CREATE TABLE statements could not be generated: ${message}`),{applied:0,errors:[message]}}let applied=0,errors2=[];for(let statement of statements)try{await run(statement),applied+=1}catch(error3){if(isAlreadyExists(error3))continue;let message=error3 instanceof Error?error3.message:String(error3);errors2.push(message),logger2.error(`[Schema] could not apply generated DDL: ${statement} \u2014 ${message}`)}return{applied,errors:errors2}}function describeRepair(plan,applied){let lines=[];if(applied>0)lines.push(`[Schema] The push applied nothing, so ${applied} missing column(s) were added directly. `+"This is additive only \u2014 nothing was dropped or altered.");if(plan.createdTables.length>0)lines.push(`[Schema] ${plan.createdTables.length} table(s) the push did not create were created directly: ${plan.createdTables.join(", ")}. Verified present in the database afterwards, not assumed from the statements that ran.`);if(plan.missingTables.length>0){if(lines.push(`[Schema] ${plan.missingTables.length} table(s) the code expects still do not exist: ${plan.missingTables.join(", ")}. Every read of these will fail. The additive repair tried `+"and could not create them \u2014 this needs a human."),plan.undeclaredTables.length>0)lines.push(`[Schema] The push is ambiguous because the database has ${plan.undeclaredTables.length} table(s) the model does not declare: ${plan.undeclaredTables.join(", ")}. drizzle-kit cannot tell a new table from a rename of one of these, and cannot ask without a TTY. Drop the stale one, or declare it, and the push stops aborting.`)}for(let item of plan.unsafe)lines.push(`[Schema] "${item.table}"."${item.column}" could not be added: ${item.why}. Give the column a default, or add it by hand with a backfill.`);return lines}function expectedFromTables(tables){let out=[];for(let table of Object.values(tables)){if(!table||typeof table!=="object")continue;let columns;try{columns=getTableColumns2(table)}catch{continue}let first=Object.values(columns)[0],name2=tableNameOf(table);if(!name2||!first)continue;out.push({name:name2,columns:Object.values(columns).map((column)=>{let c=column;return{name:c.name,sqlType:typeof c.getSQLType==="function"?c.getSQLType():"text",notNull:c.notNull===!0,hasDefault:c.hasDefault===!0,defaultLiteral:renderDefaultLiteral(c.default)}})})}return out}function renderDefaultLiteral(value2){if(typeof value2==="string")return`'${value2.replace(/'/g,"''")}'`;if(typeof value2==="number")return Number.isFinite(value2)?String(value2):void 0;if(typeof value2==="boolean")return value2?"true":"false";return}function tableNameOf(table){for(let key2 of Object.getOwnPropertySymbols(table))if(String(key2).includes("Name")&&!String(key2).includes("Schema")){let value2=table[key2];if(typeof value2==="string"&&value2.length>0)return value2}return null}async function readActualSchema(execute,schema3){return(await readActualSchemaDetailed(execute,schema3)).columns}async function readActualSchemaDetailed(execute,schema3){let result=await execute(`select table_name, column_name, column_default from information_schema.columns where table_schema = '${schema3.replace(/'/g,"''")}'`),rows=Array.isArray(result)?result:result.rows??[],columns={},withDefault={};for(let row of rows){if(!row.table_name||!row.column_name)continue;if(columns[row.table_name]??=new Set,columns[row.table_name]?.add(row.column_name),withDefault[row.table_name]??=new Set,row.column_default!==null&&row.column_default!==void 0)withDefault[row.table_name]?.add(row.column_name)}return{columns,withDefault}}async function reconcileMissingColumns(options){let{db,schema:schema3,tables,logger:logger2,schemaObject}=options,run=(statement)=>db.execute(sql13.raw(statement));try{let expected=expectedFromTables(tables);if(expected.length===0)return null;let{columns:actual,withDefault}=await readActualSchemaDetailed(run,schema3),drift=findDrift(expected,actual,withDefault),plan=planRepair(schema3,drift);if(plan.undeclaredTables=undeclaredTables(expected,actual),drift.missingTables.length>0){await createMissingTables({run,tables,missing:drift.missingTables,schemaObject,logger:logger2});let after=await readActualSchema(run,schema3);plan.createdTables=drift.missingTables.filter((name2)=>after[name2]),plan.missingTables=drift.missingTables.filter((name2)=>!after[name2])}let applied=0;for(let statement of plan.statements)try{await run(statement),applied+=1}catch(error3){logger2.error(`[Schema] could not add a missing column: ${statement} \u2014 ${error3 instanceof Error?error3.message:String(error3)}`)}for(let line of describeRepair(plan,applied))if(plan.missingTables.length>0||plan.unsafe.length>0)logger2.error(line);else logger2.info(line);return{applied,plan}}catch(error3){return logger2.error(`[Schema] the additive repair could not run: ${error3 instanceof Error?error3.message:String(error3)}`),null}}var ALREADY_EXISTS;var init_schemaReconcile=__esm(()=>{ALREADY_EXISTS=new Set(["42P06","42P07","42710","42723","23505"])});var exports_relaxSessionExpiry={};__export(exports_relaxSessionExpiry,{relaxSessionExpiryIfNever:()=>relaxSessionExpiryIfNever});async function relaxSessionExpiryIfNever(params){let{db,sql:sql14,schemaName,lifetimeSeconds:lifetimeSeconds2,logger:logger2}=params,tableName=params.tableName??"user_sessions";if(lifetimeSeconds2!==null)return{action:"skipped",reason:"lifetime-is-bounded"};if(!SAFE_IDENTIFIER.test(schemaName)||!SAFE_IDENTIFIER.test(tableName))return logger2?.warn(`[Schema] Refusing to touch "${schemaName}"."${tableName}" \u2014 the name has characters that cannot be safely quoted.`),{action:"skipped",reason:"unsafe-identifier"};try{let current=await db.execute(sql14`
|
|
539
539
|
select is_nullable from information_schema.columns
|
|
540
540
|
where table_schema = ${schemaName}
|
|
541
541
|
and table_name = ${tableName}
|
|
542
542
|
and column_name = 'expires_at'
|
|
543
543
|
`),first=((Array.isArray(current)?current:current?.rows)??[])[0];if(!first)return{action:"already-nullable"};if(first.is_nullable==="YES")return{action:"already-nullable"};return await db.execute(sql14.raw(`ALTER TABLE "${schemaName}"."${tableName}" ALTER COLUMN "expires_at" DROP NOT NULL`)),logger2?.info(`[Schema] "${schemaName}"."${tableName}".expires_at may now be empty \u2014 this install runs sessions that never expire, and a session with no expiry is a row with no expiry.`),{action:"relaxed"}}catch(error3){let reason=error3 instanceof Error?error3.message:String(error3);return logger2?.warn(`[Schema] Could not let "${schemaName}"."${tableName}".expires_at be empty: ${reason}. Sign-in will fail with "Missing required field: expires_at" until this column allows nulls.`),{action:"failed",reason}}}var SAFE_IDENTIFIER;var init_relaxSessionExpiry=__esm(()=>{SAFE_IDENTIFIER=/^[A-Za-z_][A-Za-z0-9_]*$/});function normSegment(input){return input.toLowerCase().replace(/[^a-z0-9]+/g,"_").replace(/_+/g,"_").replace(/^_|_$/g,"")}function normPath(path7){return normSegment(path7.replace(/\{[^}]+\}/g,(m)=>m.slice(1,-1)))}function shortHash(input){let h=5381;for(let i=0;i<input.length;i++)h=(h*33^input.charCodeAt(i))>>>0;return h.toString(36)}function capAction(action,max=100){if(action.length<=max)return action;let suffix=`_${shortHash(action)}`;return action.slice(0,Math.max(1,max-suffix.length))+suffix}function resourceFromPath(path7){let segs=path7.split("/").filter(Boolean).filter((s)=>!s.startsWith("{"));for(let s of segs){if(s==="api"||/^v\d+$/i.test(s))continue;return normSegment(s)}return normSegment(segs[0]??"root")}function matchesExclude(path7,patterns3){if(!patterns3?.length)return!1;for(let raw of patterns3){let p=raw.trim();if(!p)continue;if(p.endsWith("/*")){let prefix=p.slice(0,-2);if(path7===prefix||path7.startsWith(`${prefix}/`))return!0}else if(p.endsWith("*")){if(path7.startsWith(p.slice(0,-1)))return!0}else if(path7===p)return!0}return!1}function parseOpenApiToClaims(serviceId,doc,opts={}){let sid=normSegment(serviceId);if(!sid)throw Error("endpointDiscovery: serviceId is empty after normalization");if(RESERVED_SERVICE_IDS.has(sid))throw Error(`endpointDiscovery: serviceId "${serviceId}" collides with an HTTP-method claim prefix; pick another id`);let specs=[],seen=new Set,paths=doc.paths??{};for(let[path7,ops]of Object.entries(paths)){if(!ops||matchesExclude(path7,opts.exclude))continue;for(let method of HTTP_METHODS2){let op=ops[method];if(!op)continue;let tag=(op.tags?.[0]?normSegment(op.tags[0]):"")||resourceFromPath(path7),opToken=(op.operationId?normSegment(op.operationId):"")||`${method}_${normPath(path7)}`,action=capAction(`${sid}.${tag||"root"}.${opToken||method}`);if(seen.has(action)){action=capAction(`${action}_${shortHash(`${method} ${path7}`)}`);while(seen.has(action))action=capAction(`${action}_${shortHash(action)}`)}seen.add(action),specs.push({action,path:path7,method:method.toUpperCase(),description:op.summary||op.description||`${method.toUpperCase()} ${path7}`})}}return specs}function diffClaims(existing,specs){let specByAction=new Map(specs.map((s)=>[s.action,s])),existByAction=new Map(existing.map((e)=>[e.action,e]));return{toInsert:specs.filter((s)=>!existByAction.has(s.action)),toReactivate:specs.filter((s)=>existByAction.get(s.action)?.isActive===!1),toDeactivate:existing.filter((e)=>e.isActive&&!specByAction.has(e.action)),unchanged:specs.filter((s)=>existByAction.get(s.action)?.isActive===!0)}}async function fetchOpenApi(baseUrl,openapiPath,fetchImpl=fetch){let url=`${baseUrl.replace(/\/$/,"")}${openapiPath.startsWith("/")?"":"/"}${openapiPath}`,res=await fetchImpl(url);if(!res.ok)throw Error(`endpointDiscovery: OpenAPI fetch failed ${res.status} for ${url}`);return await res.json()}var HTTP_METHODS2,RESERVED_SERVICE_IDS;var init_EndpointDiscovery=__esm(()=>{HTTP_METHODS2=["get","post","put","patch","delete"],RESERVED_SERVICE_IDS=new Set(["get","post","put","patch","delete","toggle","bulk"])});var exports_seed={};__export(exports_seed,{seedDiscoveredClaims:()=>seedDiscoveredClaims,runEndpointDiscovery:()=>runEndpointDiscovery,getRouteManifest:()=>getRouteManifest,getRoleClaimsManifest:()=>getRoleClaimsManifest});import{eq as eq66,like as like2}from"drizzle-orm";function col16(table,name2){return table[name2]}async function seedDiscoveredClaims(db,claimsTable,serviceId,specs,logger2){let actionCol=col16(claimsTable,"action"),prefix=specs[0]?.action.split(".")[0]??serviceId.toLowerCase(),existing=(await db.select().from(claimsTable).where(like2(actionCol,`${prefix}.%`))).map((r)=>({action:String(r.action),isActive:r.isActive!==!1,path:String(r.path??""),method:String(r.method??""),description:String(r.description??"")})),plan=diffClaims(existing,specs);for(let s of plan.toInsert)await db.insert(claimsTable).values({action:s.action,path:s.path,method:s.method,description:s.description});for(let s of plan.toReactivate)await db.update(claimsTable).set({isActive:!0,path:s.path,method:s.method,description:s.description}).where(eq66(actionCol,s.action));for(let e of plan.toDeactivate)await db.update(claimsTable).set({isActive:!1}).where(eq66(actionCol,e.action));let summary={added:plan.toInsert.length,reactivated:plan.toReactivate.length,deactivated:plan.toDeactivate.length,unchanged:plan.unchanged.length,total:specs.length};return logger2.info(`[Discovery] Reconciled "${serviceId}"`,summary),summary}async function runEndpointDiscovery(db,schemaTables,config,logger2,fetchImpl=fetch){let claimsTable=schemaTables.claims,services=config.services??[],results=[];if(!claimsTable)return logger2.warn("[Discovery] No claims table in schema; skipping endpoint discovery"),{services:[]};for(let svc of services)try{let doc=await fetchOpenApi(svc.baseUrl,svc.openapi??"/openapi.json",fetchImpl),specs=parseOpenApiToClaims(svc.id,doc,{exclude:svc.exclude}),summary=await seedDiscoveredClaims(db,claimsTable,svc.id,specs,logger2);results.push({id:svc.id,...summary})}catch(err){let message=err instanceof Error?err.message:String(err);logger2.error(`[Discovery] Service "${svc.id}" discovery failed`,{error:message}),results.push({id:svc.id,added:0,reactivated:0,deactivated:0,unchanged:0,total:0,error:message})}return{services:results}}async function getRouteManifest(db,claimsTable,serviceId){return(await db.select().from(claimsTable).where(like2(col16(claimsTable,"action"),`${serviceId.toLowerCase()}.%`))).filter((r)=>r.isActive!==!1).map((r)=>({action:String(r.action),path:String(r.path??""),method:String(r.method??"")}))}async function getRoleClaimsManifest(db,schemaTables,serviceId){let{roles:rolesTable,claims:claimsTable}=schemaTables,roleClaimsTable=schemaTables.roleClaims??schemaTables.role_claims;if(!rolesTable||!claimsTable||!roleClaimsTable)return{};let sid=serviceId.toLowerCase(),claimRows=await db.select().from(claimsTable),claimIdToAction=new Map;for(let c of claimRows){if(c.isActive===!1)continue;let action=String(c.action??"");if(action===sid||action.startsWith(`${sid}.`))claimIdToAction.set(String(c.id),action)}if(claimIdToAction.size===0)return{};let roleRows=await db.select().from(rolesTable),roleIdToName=new Map;for(let r of roleRows)roleIdToName.set(String(r.id),String(r.name??""));let rcRows=await db.select().from(roleClaimsTable),out={};for(let rc of rcRows){if(rc.isActive===!1)continue;let action=claimIdToAction.get(String(rc.claimId??rc.claim_id)),roleName=roleIdToName.get(String(rc.roleId??rc.role_id));if(!action||!roleName)continue;let actions=out[roleName];if(!actions)actions=[],out[roleName]=actions;actions.push(action)}return out}var init_seed=__esm(()=>{init_EndpointDiscovery()});var OPS_BY_TYPE;var init_types23=__esm(()=>{OPS_BY_TYPE={int:["gt","gte","lt","lte","eq","ne"],boolean:["eq","ne"],string:["eq","ne"]}});function isRecord(v){return typeof v==="object"&&v!==null}function guardActionFromName(name2){let brace=name2.indexOf("{");return(brace>=0?name2.slice(0,brace):name2).replace(/:$/,"")}function parseGuardCatalogEntry(entry){if(!isRecord(entry))return null;let name2=typeof entry.name==="string"?entry.name.trim():"";if(!name2)return null;let valueType=entry.value;if(typeof valueType!=="string"||!VALUE_TYPES.includes(valueType))return null;let action=guardActionFromName(name2);if(!action)return null;return{action,policy:{kind:"guard",keyTemplate:name2,valueType}}}function parseGuardPolicy(raw){if(!isRecord(raw)||raw.kind!=="guard")return null;if(typeof raw.keyTemplate!=="string"||!raw.keyTemplate)return null;let vt=raw.valueType;if(typeof vt!=="string"||!VALUE_TYPES.includes(vt))return null;return{kind:"guard",keyTemplate:raw.keyTemplate,valueType:vt}}function resolveGuardKey(keyTemplate,ctx){return keyTemplate.replaceAll("{userId}",ctx.userId).replaceAll("{tenant}",ctx.tenant??"default")}function coerceThreshold(value2,type4){if(type4==="int"){if(value2==null)return null;if(typeof value2==="boolean"||typeof value2==="symbol")return null;if(typeof value2==="string"&&value2.trim()==="")return null;if(typeof value2==="object")return null;let n=Number(value2);return Number.isFinite(n)?n:null}if(type4==="boolean"){if(typeof value2==="boolean")return value2;if(value2==="true"||value2===1||value2==="1")return!0;if(value2==="false"||value2===0||value2==="0")return!1;return null}return value2==null?null:String(value2)}function parseGuardCondition(scope,valueType){let raw=scope;if(typeof scope==="string")try{raw=JSON.parse(scope)}catch{return null}if(!isRecord(raw))return null;let op=raw.op;if(typeof op!=="string"||!OPS_BY_TYPE[valueType].includes(op))return null;let value2=coerceThreshold(raw.value,valueType);if(value2===null)return null;let invalidates=Array.isArray(raw.invalidates)?raw.invalidates.filter((a)=>typeof a==="string"&&a.length>0):[];return{op,value:value2,invalidates}}function coerceStateValue(raw,type4){if(raw==null)return null;let v=isRecord(raw)&&"data"in raw?raw.data:raw;if(v==null)return null;if(typeof v==="object")return null;if(type4==="int"&&typeof v==="string"&&v.trim()==="")return null;return coerceThreshold(v,type4)}function guardConditionHolds(rawValue,valueType,condition){let actual=coerceStateValue(rawValue,valueType);if(actual===null)return!1;let target2=condition.value;switch(condition.op){case"gt":return actual>target2;case"gte":return actual>=target2;case"lt":return actual<target2;case"lte":return actual<=target2;case"eq":return actual===target2;case"ne":return actual!==target2;default:return!1}}function parseClaimGuardCatalog(entries){if(!entries?.length)return[];let out=[],seen=new Set;for(let e of entries){let parsed=parseGuardCatalogEntry(e);if(parsed&&!seen.has(parsed.action))seen.add(parsed.action),out.push(parsed)}return out}var VALUE_TYPES;var init_evaluate=__esm(()=>{init_types23();VALUE_TYPES=["int","boolean","string"]});async function resolveClaimGuards(db,schemaTables,userId,read2,ctx={}){let claimsTable=schemaTables.claims,roleClaimsTable=schemaTables.roleClaims??schemaTables.role_claims,userRolesTable=schemaTables.userRoles??schemaTables.user_roles;if(!claimsTable||!roleClaimsTable||!userRolesTable)return EMPTY;let claimRows=await db.select().from(claimsTable),guardByClaimId=new Map;for(let c of claimRows){if(c.isActive===!1)continue;let policy=parseGuardPolicy(c.policy);if(policy)guardByClaimId.set(String(c.id),{action:String(c.action??""),policy})}if(guardByClaimId.size===0)return EMPTY;let urRows=await db.select().from(userRolesTable),roleIds=new Set(urRows.filter((r)=>r.isActive!==!1&&String(r.userId??r.user_id)===userId).map((r)=>String(r.roleId??r.role_id)));if(roleIds.size===0)return EMPTY;let rcRows=await db.select().from(roleClaimsTable),guards=[],suppressed=new Set,firedGuards=[],stateCache=new Map;for(let rc of rcRows){if(rc.isActive===!1)continue;if(!roleIds.has(String(rc.roleId??rc.role_id)))continue;let guard4=guardByClaimId.get(String(rc.claimId??rc.claim_id));if(!guard4)continue;let condition=parseGuardCondition(rc.scope,guard4.policy.valueType);if(!condition)continue;let key2=resolveGuardKey(guard4.policy.keyTemplate,{userId,tenant:ctx.tenant}),value2;if(stateCache.has(key2))value2=stateCache.get(key2);else value2=await read2(key2).catch(()=>null),stateCache.set(key2,value2);let holds=guardConditionHolds(value2,guard4.policy.valueType,condition),current=normalizeCurrent(value2,guard4.policy.valueType);if(guards.push({action:guard4.action,key:key2,valueType:guard4.policy.valueType,op:condition.op,threshold:condition.value,current,invalidates:condition.invalidates,holds}),holds&&condition.invalidates.length>0){for(let a of condition.invalidates)suppressed.add(a);firedGuards.push({action:guard4.action,key:key2,invalidates:condition.invalidates})}}return{guards,suppressedClaims:[...suppressed],firedGuards}}function normalizeCurrent(raw,type4){if(raw==null)return null;let v=typeof raw==="object"&&raw!==null&&"data"in raw?raw.data:raw;if(v==null)return null;if(type4==="int"){let n=Number(v);return Number.isFinite(n)?n:null}if(type4==="boolean")return v===!0||v==="true"||v===1||v==="1";return String(v)}var EMPTY;var init_resolve=__esm(()=>{init_evaluate();EMPTY={guards:[],suppressedClaims:[],firedGuards:[]}});import{eq as eq67}from"drizzle-orm";async function seedClaimGuards(db,schemaTables,claimGuards,logger2){let claimsTable=schemaTables.claims;if(!claimsTable)return{created:0,updated:0,deactivated:0};let actionCol=claimsTable.action,desired=parseClaimGuardCatalog(claimGuards),desiredByAction=new Map(desired.map((d)=>[d.action,d.policy])),existing=await db.select().from(claimsTable),existingGuardActions=new Set,result={created:0,updated:0,deactivated:0};for(let row of existing){if(!parseGuardPolicy(row.policy))continue;let action=String(row.action??"");existingGuardActions.add(action);let want=desiredByAction.get(action);if(!want){if(row.isActive!==!1)await db.update(claimsTable).set({isActive:!1}).where(eq67(actionCol,action)),result.deactivated++;continue}let current=row.policy;if(JSON.stringify(current)!==JSON.stringify(want)||row.isActive===!1)await db.update(claimsTable).set({policy:want,isActive:!0,path:want.keyTemplate,method:"GUARD"}).where(eq67(actionCol,action)),result.updated++}for(let{action,policy}of desired){if(existingGuardActions.has(action))continue;await db.insert(claimsTable).values({action,description:`ClaimGuard [${policy.valueType}] key=${policy.keyTemplate}`,path:policy.keyTemplate,method:"GUARD",policy,isActive:!0}),result.created++}return logger2?.info?.(`[Authorization] ClaimGuards seeded (created=${result.created}, updated=${result.updated}, deactivated=${result.deactivated})`),result}var init_seed2=__esm(()=>{init_evaluate()});var exports_ClaimGuard={};__export(exports_ClaimGuard,{seedClaimGuards:()=>seedClaimGuards,resolveGuardKey:()=>resolveGuardKey,resolveClaimGuards:()=>resolveClaimGuards,parseGuardPolicy:()=>parseGuardPolicy,parseGuardCondition:()=>parseGuardCondition,parseGuardCatalogEntry:()=>parseGuardCatalogEntry,parseClaimGuardCatalog:()=>parseClaimGuardCatalog,guardConditionHolds:()=>guardConditionHolds,guardActionFromName:()=>guardActionFromName,OPS_BY_TYPE:()=>OPS_BY_TYPE});var init_ClaimGuard=__esm(()=>{init_evaluate();init_resolve();init_seed2();init_types23()});var exports_dbStorage={};__export(exports_dbStorage,{createDbWebAuthnStorage:()=>createDbWebAuthnStorage});import{and as and28,eq as eq68,isNull as isNull6}from"drizzle-orm";function rowToRecord(row){return{id:row.id,userId:row.userId,credentialId:row.credentialId,publicKey:row.publicKey,counter:typeof row.counter==="bigint"?Number(row.counter):Number(row.counter??0),transports:parseTransports(row.transports),deviceType:parseDeviceType(row.deviceType),backedUp:row.backedUp,aaguid:row.aaguid,authenticatorAttachment:row.authenticatorAttachment==="platform"||row.authenticatorAttachment==="cross-platform"?row.authenticatorAttachment:null,nickname:row.nickname,lastUsedAt:row.lastUsedAt,revokedAt:row.revokedAt,createdAt:row.createdAt}}function col17(table,key2){return table[key2]}function createDbWebAuthnStorage(deps){let{db,resolveTable}=deps,credentialsName="webauthnCredentials",challengesName="webauthnChallenges";return{storeChallenge:async(challenge,schemaName)=>{let table=resolveTable("webauthnChallenges",schemaName);if(!table)return;await db.insert(table).values({userId:challenge.userId??null,challenge:challenge.challenge,challengeType:challenge.challengeType,expiresAt:challenge.expiresAt})},consumeChallenge:async(challenge,challengeType,schemaName)=>{let table=resolveTable("webauthnChallenges",schemaName);if(!table)return null;let row=(await db.update(table).set({consumedAt:new Date}).where(and28(eq68(col17(table,"challenge"),challenge),eq68(col17(table,"challengeType"),challengeType),isNull6(col17(table,"consumedAt")))).returning())[0];if(!row)return null;return{challenge:row.challenge,challengeType:row.challengeType,userId:row.userId,expiresAt:row.expiresAt}},listCredentialsByUser:async(userId,schemaName)=>{let table=resolveTable("webauthnCredentials",schemaName);if(!table)return[];return(await db.select().from(table).where(eq68(col17(table,"userId"),userId))).map(rowToRecord)},findCredentialById:async(credentialId,schemaName)=>{let table=resolveTable("webauthnCredentials",schemaName);if(!table)return null;let rows=await db.select().from(table).where(eq68(col17(table,"credentialId"),credentialId)).limit(1);return rows[0]?rowToRecord(rows[0]):null},insertCredential:async(record3,schemaName)=>{let table=resolveTable("webauthnCredentials",schemaName);if(!table)throw Error("webauthn_credentials table not configured");let row=(await db.insert(table).values({userId:record3.userId,credentialId:record3.credentialId,publicKey:record3.publicKey,counter:record3.counter,transports:record3.transports??null,deviceType:record3.deviceType??null,backedUp:record3.backedUp,aaguid:record3.aaguid??null,authenticatorAttachment:record3.authenticatorAttachment??null,nickname:record3.nickname??null}).returning())[0];if(!row)throw Error("Failed to insert webauthn credential");return rowToRecord(row)},updateCredentialCounter:async(credentialId,counter,schemaName)=>{let table=resolveTable("webauthnCredentials",schemaName);if(!table)return;await db.update(table).set({counter,lastUsedAt:new Date}).where(eq68(col17(table,"credentialId"),credentialId))},revokeCredential:async(credentialId,userId,schemaName)=>{let table=resolveTable("webauthnCredentials",schemaName);if(!table)return!1;return(await db.update(table).set({revokedAt:new Date}).where(and28(eq68(col17(table,"credentialId"),credentialId),eq68(col17(table,"userId"),userId))).returning()).length>0},renameCredential:async(credentialId,userId,nickname,schemaName)=>{let table=resolveTable("webauthnCredentials",schemaName);if(!table)return!1;return(await db.update(table).set({nickname}).where(and28(eq68(col17(table,"credentialId"),credentialId),eq68(col17(table,"userId"),userId))).returning()).length>0}}}var init_dbStorage=()=>{};function isValidUploadId(id){return typeof id==="string"&&/^[0-9a-f]{8}(-[0-9a-f]{4}){3}-[0-9a-f]{12}$/i.test(id)}function offsetVerdict(input){let{declaredOffset,actualOffset,chunkBytes,totalSize}=input;if(!Number.isFinite(declaredOffset)||declaredOffset<0)return{ok:!1,reason:"offset_mismatch",expected:actualOffset};if(declaredOffset!==actualOffset)return{ok:!1,reason:"offset_mismatch",expected:actualOffset};if(actualOffset+chunkBytes>totalSize)return{ok:!1,reason:"too_long",expected:actualOffset};return{ok:!0}}function isComplete(actualOffset,totalSize){return actualOffset===totalSize&&totalSize>0}function sessionRefusal(input){let{session,callerId,fingerprint,now}=input;if(!session)return"not_found";if(!callerId||session.owner!==callerId)return"not_yours";if(now-session.createdAt>86400000)return"expired";if(fingerprint!=null&&fingerprint!==session.fingerprint)return"wrong_file";return null}function isExpired(session,now){return now-session.createdAt>86400000}function sessionStatus(session,offset){return{uploadId:session.id,offset,size:session.size,originalName:session.originalName,complete:isComplete(offset,session.size)}}var SESSION_TTL_MS=86400000,CHUNK_SIZE_BYTES=8388608,MAX_CHUNK_BYTES=33554432;import{randomUUID as randomUUID10}from"crypto";import{mkdir as mkdir8,open as open2,readdir as readdir3,readFile as readFile2,rm as rm2,stat as stat8,writeFile as writeFile2}from"fs/promises";import path7 from"path";function createResumableStore(basePath){let root=path7.join(basePath,UPLOAD_DIR);function pathsFor(id){if(!isValidUploadId(id))return null;let part=path7.join(root,`${id}.part`),meta=path7.join(root,`${id}.json`);if(!isPathWithinBase(root,part)||!isPathWithinBase(root,meta))return null;return{part,meta}}async function create4(input,now){await mkdir8(root,{recursive:!0});let session={...input,id:randomUUID10(),createdAt:now},paths=pathsFor(session.id);if(!paths)throw Error("Generated upload id was rejected by its own validator");return await writeFile2(paths.meta,JSON.stringify(session),"utf8"),await writeFile2(paths.part,""),session}async function read2(id){let paths=pathsFor(id);if(!paths)return null;try{let raw=await readFile2(paths.meta,"utf8"),parsed=JSON.parse(raw);if(!parsed||typeof parsed.owner!=="string"||typeof parsed.size!=="number")return null;return parsed}catch{return null}}async function offsetOf(id){let paths=pathsFor(id);if(!paths)return null;try{return(await stat8(paths.part)).size}catch{return null}}async function writeAt(id,offset,chunk){let paths=pathsFor(id);if(!paths)return null;let handle=await open2(paths.part,"r+");try{await handle.write(chunk,0,chunk.byteLength,offset)}finally{await handle.close()}return(await stat8(paths.part)).size}async function readAssembled(id){let paths=pathsFor(id);if(!paths)return null;try{return await readFile2(paths.part)}catch{return null}}function partPath(id){return pathsFor(id)?.part??null}async function discardMeta(id){let paths=pathsFor(id);if(!paths)return;await rm2(paths.meta,{force:!0})}async function discard2(id){let paths=pathsFor(id);if(!paths)return;await rm2(paths.part,{force:!0}),await rm2(paths.meta,{force:!0})}async function sweep(now){let removed=0,entries;try{entries=await readdir3(root)}catch{return 0}for(let entry of entries){if(!entry.endsWith(".json"))continue;let id=entry.slice(0,-5);try{let session=await read2(id);if(session&&!isExpired(session,now))continue;await discard2(id),removed+=1}catch{}}return removed}return{create:create4,read:read2,offsetOf,writeAt,readAssembled,partPath,discard:discard2,discardMeta,sweep,root}}var UPLOAD_DIR=".uploads";var init_store4=__esm(()=>{init_helpers3()});var exports_resumable={};__export(exports_resumable,{createResumableRoutes:()=>createResumableRoutes,SESSION_TTL_MS:()=>SESSION_TTL_MS,CHUNK_SIZE_BYTES:()=>CHUNK_SIZE_BYTES});function toBytes(body){if(body instanceof Uint8Array)return body;if(body instanceof ArrayBuffer)return new Uint8Array(body);if(ArrayBuffer.isView(body))return new Uint8Array(body.buffer,body.byteOffset,body.byteLength);if(typeof body==="string")return new TextEncoder().encode(body);return new Uint8Array(0)}function createResumableRoutes(config){let{basePath,storage,logger:logger2,getUserId:getUserId3,canUpload,register}=config,store=createResumableStore(basePath),openSession=async(id,request,fingerprint)=>{let userId=getUserId3(request);if(!await canUpload(request))return{ok:!1,status:403,message:"You are not allowed to upload files."};let session=isValidUploadId(id)?await store.read(id):null,refusal=sessionRefusal({session,callerId:userId,fingerprint,now:Date.now()});if(refusal)return{ok:!1,status:REFUSAL_STATUS[refusal],message:REFUSAL_MESSAGE[refusal]};return{ok:!0,session,userId}},routePostRoot=defineRoute({method:"POST",path:"/",body:Type.Object({originalName:Type.String({minLength:1,maxLength:500}),size:Type.Number(),mimeType:Type.String({maxLength:200}),type:Type.Optional(Type.String({maxLength:40})),fingerprint:Type.String({minLength:1,maxLength:200})}),detail:{tags:["Storage"],summary:"Start a resumable upload"},handler:async({body,request,set:set2})=>{let userId=getUserId3(request);if(!userId)return set2.status=401,{success:!1,message:"Sign in to upload."};if(!await canUpload(request))return set2.status=403,{success:!1,message:"You are not allowed to upload files."};if(!Number.isFinite(body.size)||body.size<=0)return set2.status=400,{success:!1,message:"A size is required to start an upload."};if(body.size>storage.maxFileSizeBytes)return set2.status=413,{success:!1,message:`This file is larger than the ${Math.round(storage.maxFileSizeBytes/1048576)} MB limit.`};if(isMimeTypeListed(storage.blockedMimeTypes,body.mimeType))return set2.status=415,{success:!1,message:"This kind of file cannot be uploaded."};if(storage.allowedMimeTypes?.length&&!isMimeTypeListed(storage.allowedMimeTypes,body.mimeType))return set2.status=415,{success:!1,message:"This kind of file cannot be uploaded."};let session=await store.create({owner:userId,originalName:body.originalName,size:body.size,mimeType:body.mimeType,type:body.type??"file",fingerprint:body.fingerprint},Date.now());return set2.status=201,{success:!0,data:sessionStatus(session,0)}}}),routeGetId=defineRoute({method:"GET",path:"/:id",handler:async({params,request,set:set2})=>{let opened=await openSession(params.id,request);if(!opened.ok)return set2.status=opened.status,{success:!1,message:opened.message};let offset=await store.offsetOf(params.id)??0;return{success:!0,data:sessionStatus(opened.session,offset)}}}),routePatchId=defineRoute({method:"PATCH",path:"/:id",detail:{tags:["Storage"],summary:"Append a piece"},handler:async({params,request,body,set:set2})=>{let fingerprint=request.headers.get("upload-fingerprint"),opened=await openSession(params.id,request,fingerprint);if(!opened.ok)return set2.status=opened.status,{success:!1,message:opened.message};let declared2=Number(request.headers.get("upload-offset")),actual=await store.offsetOf(params.id)??0,chunk=toBytes(body),sentBytes=request.body!==null||Number(request.headers.get("content-length")??"0")>0;if(chunk.byteLength===0&&sentBytes)return set2.status=415,{success:!1,message:"The piece could not be read. Send it as application/octet-stream.",data:{offset:actual}};if(chunk.byteLength>MAX_CHUNK_BYTES)return set2.status=413,{success:!1,message:`A single piece may not exceed ${MAX_CHUNK_BYTES/1048576} MB.`,data:{offset:actual}};let verdict=offsetVerdict({declaredOffset:declared2,actualOffset:actual,chunkBytes:chunk.byteLength,totalSize:opened.session.size});if(!verdict.ok)return set2.status=409,{success:!1,message:verdict.reason==="too_long"?"This piece would take the file past the size it was started with.":"This piece does not continue where the file ends.",data:{offset:verdict.expected}};let offset=await store.writeAt(params.id,actual,chunk);if(offset===null)return set2.status=500,{success:!1,message:"The piece could not be written."};return{success:!0,data:sessionStatus(opened.session,offset)}}}),routePostIdComplete=defineRoute({method:"POST",path:"/:id/complete",handler:async({params,request,set:set2})=>{let opened=await openSession(params.id,request);if(!opened.ok)return set2.status=opened.status,{success:!1,message:opened.message};let offset=await store.offsetOf(params.id)??0;if(!isComplete(offset,opened.session.size))return set2.status=409,{success:!1,message:"The file is not complete yet.",data:sessionStatus(opened.session,offset)};let partPath=store.partPath(params.id);if(!partPath)return set2.status=500,{success:!1,message:"The assembled file could not be found."};let registered=await register({partPath,size:offset,originalName:opened.session.originalName,mimeType:opened.session.mimeType,type:opened.session.type,userId:opened.userId});if(!registered)return set2.status=500,{success:!1,message:"The file could not be registered. Try completing again."};return await store.discardMeta(params.id),logger2.info(`[Storage] Resumable upload finished: ${opened.session.originalName} (${offset} bytes)`),{success:!0,data:{id:registered.id}}}}),routeDeleteId=defineRoute({method:"DELETE",path:"/:id",handler:async({params,request,set:set2})=>{let opened=await openSession(params.id,request);if(!opened.ok)return set2.status=opened.status,{success:!1,message:opened.message};return await store.discard(params.id),{success:!0,data:{discarded:!0}}}});return{plugin:collectRoutes([{prefix:"/uploads",routes:[routePostRoot,routeGetId,routePatchId,routePostIdComplete,routeDeleteId]}]),sweep:(now)=>store.sweep(now)}}var REFUSAL_STATUS,REFUSAL_MESSAGE;var init_resumable=__esm(()=>{init_esm3();init_routeCollector();init_helpers3();init_store4();REFUSAL_STATUS={not_found:404,not_yours:404,expired:410,wrong_file:409},REFUSAL_MESSAGE={not_found:"No such upload.",not_yours:"No such upload.",expired:"This upload was abandoned and has been cleaned up. Start it again.",wrong_file:"This is a different file from the one this upload started with."}});var exports_authorization={};__export(exports_authorization,{createAuthorizationDiscoveryRoutes:()=>createAuthorizationDiscoveryRoutes});import{timingSafeEqual as timingSafeEqual4}from"crypto";function tokensMatch2(a,b){let ba=Buffer.from(a),bb=Buffer.from(b);if(ba.length!==bb.length)return!1;return timingSafeEqual4(ba,bb)}function createAuthorizationDiscoveryRoutes(cfg){let{db,schemaTables,logger:logger2,discovery,readState}=cfg,token=discovery.token||process.env.NUCLEUS_DISCOVERY_TOKEN,claimsTable=schemaTables.claims,manifestAuthorized=(request)=>{if(hasGodminRole(request))return!0;if(token){let presented=request.headers.get("x-discovery-token")??"";return tokensMatch2(presented,token)}return!1},authorizeGuardRead=(request,query,set2)=>{let callerId=request.headers.get("x-user-id")??"",isGodmin4=hasGodminRole(request),requested=query.userId;if(requested&&requested!==callerId&&!isGodmin4)return{error:fail6(set2,403,"Forbidden: only godmin can read another user's guards")};let targetUserId=requested&&isGodmin4?requested:callerId;if(!targetUserId)return{error:fail6(set2,401,"Unauthorized: no user context")};return{userId:targetUserId}},guardsHandler=async({request,query,set:set2})=>{let auth=authorizeGuardRead(request,query,set2);if("error"in auth)return auth.error;let tenant=request.headers.get("x-tenant-id")??void 0;if(!readState)return{success:!0,data:{userId:auth.userId,guards:[]}};let{guards}=await resolveClaimGuards(db,schemaTables,auth.userId,readState,{tenant});return{success:!0,data:{userId:auth.userId,guards}}},declared=[defineRoute({method:"POST",path:"/authorization/discover",handler:async({request,set:set2})=>{if(!hasGodminRole(request))return fail6(set2,403,"Forbidden: endpoint discovery requires godmin");if(!discovery.enabled)return fail6(set2,400,"endpointDiscovery is not enabled");return{success:!0,message:"Discovery completed",data:await runEndpointDiscovery(db,schemaTables,discovery,logger2)}}}),defineRoute({method:"GET",path:"/authorization/route-manifest",handler:async({request,query,set:set2})=>{if(!manifestAuthorized(request))return fail6(set2,403,"Forbidden: godmin or a valid x-discovery-token required");if(!claimsTable)return fail6(set2,503,"Claims table unavailable");let service=query.service;if(service){let rows=await getRouteManifest(db,claimsTable,service);return{success:!0,data:{service,routes:rows}}}let all={};for(let svc of discovery.services??[])all[svc.id]=await getRouteManifest(db,claimsTable,svc.id);return{success:!0,data:all}}}),defineRoute({method:"GET",path:"/authorization/role-claims-manifest",handler:async({request,query,set:set2})=>{if(!manifestAuthorized(request))return fail6(set2,403,"Forbidden: godmin or a valid x-discovery-token required");let service=query.service;if(!service)return fail6(set2,400,"service query param required");let map3=await getRoleClaimsManifest(db,schemaTables,service);return{success:!0,data:{service,roleClaims:map3}}}}),defineRoute({method:"GET",path:"/authorization/guards",handler:guardsHandler}),defineRoute({method:"GET",path:"/authorization/quotas",handler:guardsHandler}),defineRoute({method:"GET",path:"/authorization/suppressed-claims",handler:async({request,query,set:set2})=>{let auth=authorizeGuardRead(request,query,set2);if("error"in auth)return auth.error;if(!readState)return{success:!0,data:{userId:auth.userId,suppressedClaims:[],firedGuards:[]}};let tenant=request.headers.get("x-tenant-id")??void 0,{suppressedClaims,firedGuards}=await resolveClaimGuards(db,schemaTables,auth.userId,readState,{tenant});return{success:!0,data:{userId:auth.userId,suppressedClaims,firedGuards}}}})];return collectRoutes([{routes:declared}])}var fail6=(set2,status,message)=>{return set2.status=status,{success:!1,message,data:null}};var init_authorization=__esm(()=>{init_ClaimGuard();init_seed();init_routeCollector();init_adminGuard()});var import_reflect_metadata2=__toESM(require_Reflect(),1);import{batch,createStore}from"h-state";import{useEffectEvent}from"react";function createInitialState(endpoints){let state={};for(let key of Object.keys(endpoints))state[key]={isPending:!1,data:null,error:null,code:null};return state}function createActionsFacade(endpoints,buildAction){let cache=new Map;return new Proxy({},{get(_target,property){if(typeof property!=="string")return;if(!Object.hasOwn(endpoints,property))return;if(cache.has(property))return cache.get(property);let action=buildAction(property);return cache.set(property,action),action},has(_target,property){return typeof property==="string"&&Object.hasOwn(endpoints,property)},ownKeys(){return Object.keys(endpoints)},getOwnPropertyDescriptor(){return{enumerable:!0,configurable:!0}}})}function applyResponse(response,fallbackError){if(response.isSuccess&&response.data!==void 0)return{data:response.data,error:null,code:response.code??null};return{data:null,error:response.errors??fallbackError??null,code:response.code??null}}function createApiHook(endpoints,factory){let{useStore}=createStore(createInitialState(endpoints),{_callEndpoint:(store)=>async(endpointKey,options)=>{if(!store[endpointKey])return;batch(()=>{store[endpointKey].isPending=!0,store[endpointKey].error=null});try{let response=await factory(endpointKey,options.payload);if(batch(()=>{store[endpointKey].isPending=!1,store[endpointKey].code=response.code??null;let applied=applyResponse(response);store[endpointKey].data=applied.data,store[endpointKey].error=applied.error}),response.isSuccess)options.onAfterHandle?.(response.data??response);else options.onErrorHandle?.(response.errors??{message:"Request failed"},response.code,response.requestId)}catch(err){batch(()=>{store[endpointKey].isPending=!1,store[endpointKey].data=null,store[endpointKey].error={message:err instanceof Error?err.message:"Unknown error"}}),options.onErrorHandle?.({message:err instanceof Error?err.message:"Unknown error"},null)}}});return function(){let store=useStore(),callEndpoint=useEffectEvent((endpointKey,options)=>{store._callEndpoint(endpointKey,options)});return createActionsFacade(endpoints,(property)=>{let key=property;return{state:store[key],start:(options)=>{callEndpoint(key,options)}}})}}var system_tables_default={$schema:"../schemas/nucleus.tables.schema.json",tables:[{table_name:"users",feature_set:["authentication"],add_base_columns:!0,is_form_data:!1,available_app_ids:["default_be"],available_schemas:["*"],excluded_schemas:[],excluded_methods:[],bulk_endpoints_enabled:!0,columns:[{name:"email",type:"varchar",length:255},{name:"password",type:"varchar",length:255},{name:"verified_at",type:"timestamp"},{name:"email_verification_token",type:"varchar",length:255},{name:"email_verification_token_expires_at",type:"timestamp"},{name:"email_verification_sent_at",type:"timestamp"},{name:"email_verification_attempts",type:"integer",default:0},{name:"last_login_at",type:"timestamp"},{name:"login_count",type:"integer",default:0},{name:"is_locked",type:"boolean",default:!1},{name:"locked_until",type:"timestamp"},{name:"failed_login_attempts",type:"integer",default:0},{name:"is_god",type:"boolean",default:!1},{name:"cohort_id",type:"uuid",references:{table:"user_cohorts",column:"id",onDelete:"set null"}},{name:"email_verified",type:"boolean",default:!1}],indexes:[{columns:["email"],unique:!0},{columns:["email","is_active"]},{columns:["last_login_at"]},{columns:["is_locked","locked_until"]}]},{table_name:"profiles",feature_set:["authentication"],add_base_columns:!0,is_form_data:!1,available_app_ids:["default_be"],available_schemas:["*"],excluded_schemas:[],excluded_methods:[],bulk_endpoints_enabled:!0,columns:[{name:"user_id",type:"uuid",notNull:!0,references:{table:"users",column:"id"}},{name:"first_name",type:"varchar",length:100,notNull:!0},{name:"last_name",type:"varchar",length:100,notNull:!0}],indexes:[{columns:["user_id"],unique:!0},{columns:["first_name","last_name"]}]},{table_name:"roles",feature_set:["authentication","authorization"],add_base_columns:!0,is_form_data:!1,available_app_ids:["default_be"],available_schemas:["*"],excluded_schemas:[],excluded_methods:[],bulk_endpoints_enabled:!0,columns:[{name:"name",type:"varchar",length:100,notNull:!0},{name:"description",type:"varchar",length:500}],indexes:[{columns:["name"],unique:!0}]},{table_name:"claims",feature_set:["authentication","authorization"],add_base_columns:!0,is_form_data:!1,available_app_ids:["default_be"],available_schemas:["*"],excluded_schemas:[],excluded_methods:[],bulk_endpoints_enabled:!0,columns:[{name:"action",type:"varchar",length:100,notNull:!0},{name:"description",type:"varchar",length:500},{name:"path",type:"varchar",length:200,notNull:!0},{name:"method",type:"varchar",length:10,notNull:!0},{name:"policy",type:"jsonb"}],indexes:[{columns:["action"],unique:!0},{columns:["path","method"]}]},{table_name:"user_roles",feature_set:["authentication","authorization"],add_base_columns:!0,is_form_data:!1,available_app_ids:["default_be"],available_schemas:["*"],excluded_schemas:[],excluded_methods:[],bulk_endpoints_enabled:!0,columns:[{name:"user_id",type:"uuid",notNull:!0,references:{table:"users",column:"id",onDelete:"cascade"}},{name:"role_id",type:"uuid",notNull:!0,references:{table:"roles",column:"id",onDelete:"cascade"}}],indexes:[{columns:["user_id"]},{columns:["role_id"]}],constraints:{unique:[{name:"unique_user_role",columns:["user_id","role_id"]}]}},{table_name:"role_claims",feature_set:["authentication","authorization"],add_base_columns:!0,is_form_data:!1,available_app_ids:["default_be"],available_schemas:["*"],excluded_schemas:[],excluded_methods:[],bulk_endpoints_enabled:!0,columns:[{name:"role_id",type:"uuid",notNull:!0,references:{table:"roles",column:"id",onDelete:"cascade"}},{name:"claim_id",type:"uuid",notNull:!0,references:{table:"claims",column:"id",onDelete:"cascade"}},{name:"scope",type:"text"}],indexes:[{columns:["role_id"]},{columns:["claim_id"]},{columns:["role_id","claim_id","scope"]}]},{table_name:"files",feature_set:["storage"],add_base_columns:!0,is_form_data:!0,available_app_ids:["default_be"],available_schemas:["*"],excluded_schemas:[],excluded_methods:[],bulk_endpoints_enabled:!0,columns:[{name:"name",type:"varchar",length:255,notNull:!0},{name:"original_name",type:"varchar",length:255,notNull:!0},{name:"type",type:"varchar",length:50,enumValues:["image","document","video","audio","profile_picture"]},{name:"path",type:"varchar",length:500,notNull:!0},{name:"size",type:"bigint",mode:"number",notNull:!0},{name:"mime_type",type:"varchar",length:100,notNull:!0},{name:"extension",type:"varchar",length:10,notNull:!0},{name:"uploaded_by",type:"uuid",references:{table:"users",column:"id"}}],indexes:[{columns:["type"]},{columns:["uploaded_by"]},{columns:["size"]}]},{table_name:"addresses",feature_set:["authentication"],add_base_columns:!0,is_form_data:!1,available_app_ids:["default_be"],available_schemas:["*"],excluded_schemas:[],excluded_methods:[],bulk_endpoints_enabled:!0,columns:[{name:"owner_type",type:"varchar",length:50,notNull:!0,enumValues:["user","company","contact"]},{name:"owner_id",type:"uuid",notNull:!0},{name:"name",type:"varchar",length:100,notNull:!0},{name:"street",type:"varchar",length:255},{name:"city",type:"varchar",length:100},{name:"state",type:"varchar",length:50},{name:"zip",type:"varchar",length:20},{name:"country",type:"varchar",length:50,default:"US"},{name:"latitude",type:"decimal",precision:10,scale:8},{name:"longitude",type:"decimal",precision:11,scale:8},{name:"neighborhood",type:"varchar",length:100},{name:"apartment",type:"varchar",length:50},{name:"province",type:"varchar",length:100},{name:"district",type:"varchar",length:100},{name:"type",type:"varchar",length:50}],indexes:[{columns:["city","state"]},{columns:["latitude","longitude"]},{columns:["type"]},{columns:["owner_type","owner_id"]}]},{table_name:"phones",feature_set:["authentication"],add_base_columns:!0,is_form_data:!1,available_app_ids:["default_be"],available_schemas:["*"],excluded_schemas:[],excluded_methods:[],bulk_endpoints_enabled:!0,columns:[{name:"owner_type",type:"varchar",length:50,notNull:!0,enumValues:["user","company","contact"]},{name:"owner_id",type:"uuid",notNull:!0},{name:"name",type:"varchar",length:100,notNull:!0},{name:"type",type:"varchar",length:50,enumValues:["mobile","office","fax"]},{name:"number",type:"varchar",length:20,notNull:!0},{name:"country_code",type:"varchar",length:10,notNull:!0,default:"+1"},{name:"extension",type:"varchar",length:10}],indexes:[{columns:["number"]},{columns:["type"]},{columns:["owner_type","owner_id"]}]},{table_name:"notifications",feature_set:["notification"],add_base_columns:!0,is_form_data:!1,available_app_ids:["default_be"],available_schemas:["*"],excluded_schemas:[],excluded_methods:[],columns:[{name:"user_id",type:"uuid",notNull:!0},{name:"title",type:"varchar",length:255,notNull:!0},{name:"body",type:"varchar",length:1000},{name:"entity_name",type:"varchar",length:100},{name:"entity_id",type:"uuid"},{name:"type",type:"varchar",length:50,notNull:!0,default:"system",enumValues:["verification","system","custom"]},{name:"source",type:"varchar",length:100},{name:"is_seen",type:"boolean",notNull:!0,default:!1},{name:"seen_at",type:"timestamptz"}],indexes:[{columns:["user_id","created_at"]},{columns:["is_seen"]},{columns:["type"]},{columns:["user_id","type","is_seen"]}]},{table_name:"tenants",feature_set:["multi-tenant"],add_base_columns:!0,is_form_data:!1,available_app_ids:["default_be"],available_schemas:["main"],excluded_schemas:[],excluded_methods:[],columns:[{name:"subdomain",type:"varchar",length:100,notNull:!0,unique:!0},{name:"schema_name",type:"varchar",length:100,notNull:!0,unique:!0},{name:"company_id",type:"uuid",notNull:!0},{name:"company_name",type:"varchar",length:255},{name:"god_admin_email",type:"varchar",length:255,notNull:!0},{name:"status",type:"varchar",length:20,notNull:!0,default:"provisioning"},{name:"plan",type:"varchar",length:50,default:"free"},{name:"domain",type:"varchar",length:255},{name:"settings",type:"jsonb",default:"{}"},{name:"trusted_sources",type:"jsonb",default:"[]"},{name:"max_users",type:"integer"},{name:"provisioned_at",type:"timestamptz"},{name:"suspended_at",type:"timestamptz"},{name:"suspended_reason",type:"text"}],indexes:[{columns:["status"]}]},{table_name:"tenant_events",feature_set:["multi-tenant"],add_base_columns:!0,is_form_data:!1,available_app_ids:["default_be"],available_schemas:["main"],excluded_schemas:[],excluded_methods:["POST","PUT","PATCH","DELETE"],columns:[{name:"tenant_id",type:"uuid",notNull:!0,references:{table:"tenants",column:"id",onDelete:"cascade"}},{name:"event_type",type:"varchar",length:50,notNull:!0},{name:"event_data",type:"jsonb",default:"{}"},{name:"performed_by",type:"varchar",length:255},{name:"ip_address",type:"varchar",length:45}],indexes:[{columns:["tenant_id"]},{columns:["event_type"]}]},{table_name:"tenant_features",feature_set:["multi-tenant"],add_base_columns:!0,is_form_data:!1,available_app_ids:["default_be"],available_schemas:["main"],excluded_schemas:[],excluded_methods:[],columns:[{name:"tenant_id",type:"uuid",notNull:!0,references:{table:"tenants",column:"id",onDelete:"cascade"}},{name:"feature_name",type:"varchar",length:100,notNull:!0},{name:"enabled",type:"boolean",notNull:!0,default:!0},{name:"config",type:"jsonb",default:"{}"}],indexes:[{columns:["tenant_id","feature_name"],unique:!0},{columns:["feature_name"]}]},{table_name:"domain_hostnames",feature_set:["domains"],add_base_columns:!0,is_form_data:!1,available_app_ids:["default_be"],available_schemas:["main"],excluded_schemas:[],excluded_methods:["PUT"],group_name:"Domains",columns:[{name:"owner_type",type:"varchar",length:20,notNull:!0,default:"tenant",comment:"tenant | organization | user | external"},{name:"owner_id",type:"uuid",notNull:!0,comment:"Product-defined owner reference (e.g. band id, org id)"},{name:"tenant_id",type:"uuid",references:{table:"tenants",column:"id",onDelete:"cascade"},comment:"Tenant this hostname routes to; null for non-tenant owners"},{name:"schema_name",type:"varchar",length:100,comment:"Resolved tenant schema for routing"},{name:"hostname",type:"varchar",length:255,notNull:!0,comment:"Original hostname as entered by the user"},{name:"normalized_hostname",type:"varchar",length:255,notNull:!0,unique:!0,comment:"Lowercased, punycode, port-stripped hostname used for matching"},{name:"hostname_kind",type:"varchar",length:20,notNull:!0,default:"apex",comment:"platform_subdomain | apex | www | subdomain"},{name:"status",type:"varchar",length:20,notNull:!0,default:"pending_verification",comment:"draft | pending_verification | verifying | active | failed | disabled | archived"},{name:"routing_status",type:"varchar",length:20,notNull:!0,default:"inactive",comment:"inactive | pending | active | failed"},{name:"certificate_status",type:"varchar",length:20,notNull:!0,default:"pending",comment:"not_required | pending | active | failed"},{name:"verification_status",type:"varchar",length:20,notNull:!0,default:"pending",comment:"pending | verified | failed | expired"},{name:"provider",type:"varchar",length:30,notNull:!0,default:"manual_dns",comment:"manual_dns | cloudflare_for_saas | cloudflare_registrar"},{name:"provider_hostname_id",type:"varchar",length:255},{name:"dns_target",type:"varchar",length:255,comment:"CNAME / A target the customer must point at"},{name:"is_primary",type:"boolean",notNull:!0,default:!1},{name:"redirect_to_hostname_id",type:"uuid",comment:"When set, this hostname 301-redirects to another hostname (e.g. apex to www)"},{name:"activated_at",type:"timestamptz"},{name:"disabled_at",type:"timestamptz"},{name:"failure_reason",type:"text"},{name:"metadata",type:"jsonb",default:"{}"}],indexes:[{columns:["tenant_id"]},{columns:["schema_name"]},{columns:["status"]},{columns:["owner_type","owner_id"]},{columns:["provider","provider_hostname_id"]}]},{table_name:"domain_registrations",feature_set:["domains"],add_base_columns:!0,is_form_data:!1,available_app_ids:["default_be"],available_schemas:["main"],excluded_schemas:[],excluded_methods:["PUT"],group_name:"Domains",columns:[{name:"owner_type",type:"varchar",length:20,notNull:!0,default:"tenant"},{name:"owner_id",type:"uuid",notNull:!0},{name:"tenant_id",type:"uuid",references:{table:"tenants",column:"id",onDelete:"set null"}},{name:"requested_domain",type:"varchar",length:255,notNull:!0},{name:"registered_domain",type:"varchar",length:255},{name:"provider",type:"varchar",length:30,notNull:!0,default:"cloudflare_registrar"},{name:"provider_registration_id",type:"varchar",length:255},{name:"registrant_owner_type",type:"varchar",length:20,notNull:!0,default:"customer",comment:"customer | platform"},{name:"registrant_contact_snapshot",type:"jsonb",default:"{}"},{name:"status",type:"varchar",length:30,notNull:!0,default:"draft",comment:"draft | availability_checked | awaiting_payment | registering | active | failed | transfer_requested | transferred_out | cancelled | expired"},{name:"price_amount",type:"numeric",precision:12,scale:2},{name:"currency",type:"varchar",length:10},{name:"renewal_date",type:"timestamptz"},{name:"auto_renew",type:"boolean",notNull:!0,default:!0},{name:"terms_version",type:"varchar",length:50},{name:"terms_accepted_at",type:"timestamptz"},{name:"failure_reason",type:"text"},{name:"metadata",type:"jsonb",default:"{}"}],indexes:[{columns:["owner_type","owner_id"]},{columns:["tenant_id"]},{columns:["status"]}]},{table_name:"domain_verification_challenges",feature_set:["domains"],add_base_columns:!0,is_form_data:!1,available_app_ids:["default_be"],available_schemas:["main"],excluded_schemas:[],excluded_methods:["POST","PUT","PATCH","DELETE"],group_name:"Domains",columns:[{name:"domain_hostname_id",type:"uuid",notNull:!0,references:{table:"domainHostnames",column:"id",onDelete:"cascade"}},{name:"challenge_type",type:"varchar",length:30,notNull:!0,default:"ownership_validation",comment:"hostname_validation | certificate_validation | ownership_validation"},{name:"record_type",type:"varchar",length:10,notNull:!0,default:"TXT",comment:"TXT | CNAME | A | AAAA | HTTP"},{name:"record_name",type:"varchar",length:255,notNull:!0},{name:"record_value",type:"text",notNull:!0},{name:"expected_target",type:"varchar",length:255},{name:"status",type:"varchar",length:20,notNull:!0,default:"pending",comment:"pending | verified | failed | expired"},{name:"expires_at",type:"timestamptz"},{name:"verified_at",type:"timestamptz"},{name:"failure_reason",type:"text"},{name:"metadata",type:"jsonb",default:"{}"}],indexes:[{columns:["domain_hostname_id"]},{columns:["status"]}]},{table_name:"domain_provider_records",feature_set:["domains"],add_base_columns:!0,is_form_data:!1,available_app_ids:["default_be"],available_schemas:["main"],excluded_schemas:[],excluded_methods:["POST","PUT","PATCH","DELETE"],group_name:"Domains",columns:[{name:"domain_hostname_id",type:"uuid",notNull:!0,references:{table:"domainHostnames",column:"id",onDelete:"cascade"}},{name:"provider",type:"varchar",length:30,notNull:!0},{name:"provider_resource_type",type:"varchar",length:30,notNull:!0,comment:"custom_hostname | certificate | validation | zone | dns_record | registration"},{name:"provider_resource_id",type:"varchar",length:255},{name:"provider_status",type:"varchar",length:50},{name:"last_synced_at",type:"timestamptz"},{name:"raw_status",type:"jsonb",default:"{}"},{name:"metadata",type:"jsonb",default:"{}"}],indexes:[{columns:["domain_hostname_id"]},{columns:["provider","provider_resource_id"]}]},{table_name:"domain_events",feature_set:["domains"],add_base_columns:!0,is_form_data:!1,available_app_ids:["default_be"],available_schemas:["main"],excluded_schemas:[],excluded_methods:["POST","PUT","PATCH","DELETE"],group_name:"Domains",columns:[{name:"domain_hostname_id",type:"uuid",references:{table:"domainHostnames",column:"id",onDelete:"cascade"}},{name:"domain_registration_id",type:"uuid",references:{table:"domainRegistrations",column:"id",onDelete:"cascade"}},{name:"event_type",type:"varchar",length:50,notNull:!0},{name:"actor_type",type:"varchar",length:20},{name:"actor_id",type:"varchar",length:255},{name:"message",type:"text"},{name:"ip_address",type:"varchar",length:45},{name:"metadata",type:"jsonb",default:"{}"}],indexes:[{columns:["domain_hostname_id"]},{columns:["event_type"]}]},{table_name:"verifications",feature_set:["verification"],add_base_columns:!0,is_form_data:!1,available_app_ids:["default_be"],available_schemas:["*"],excluded_schemas:[],excluded_methods:[],columns:[{name:"instance_id",type:"uuid",notNull:!0,references:{table:"verificationInstances",column:"id",onDelete:"cascade"}},{name:"requirement_id",type:"uuid",notNull:!0,references:{table:"verificationRequirements",column:"id"}},{name:"verifier_id",type:"uuid",notNull:!0,references:{table:"users",column:"id"}},{name:"signature_id",type:"uuid",references:{table:"files",column:"id"}},{name:"entity_name",type:"varchar",length:100,notNull:!0},{name:"entity_id",type:"uuid",notNull:!0},{name:"step_order",type:"integer",notNull:!0,default:1},{name:"decision",type:"varchar",length:50,notNull:!0,default:"pending",enumValues:["approved","rejected","pending"]},{name:"reason",type:"text"},{name:"diff",type:"jsonb"}],indexes:[{columns:["instance_id"]},{columns:["requirement_id"]},{columns:["verifier_id"]},{columns:["entity_name","entity_id"]},{columns:["entity_name","entity_id","step_order"]},{columns:["decision"]}]},{table_name:"verificationRequirements",feature_set:["verification"],add_base_columns:!0,is_form_data:!1,available_app_ids:["default_be"],available_schemas:["*"],excluded_schemas:[],excluded_methods:[],columns:[{name:"instance_id",type:"uuid",notNull:!0,references:{table:"verificationInstances",column:"id",onDelete:"cascade"}},{name:"step_node_id",type:"varchar",length:100,notNull:!0},{name:"verifier_node_id",type:"varchar",length:100,notNull:!0},{name:"entity_name",type:"varchar",length:100,notNull:!0},{name:"entity_id",type:"uuid",notNull:!0},{name:"verifier_type",type:"varchar",length:30,notNull:!0,enumValues:["user","role"]},{name:"verifier_user_id",type:"uuid"},{name:"verifier_role",type:"varchar",length:100},{name:"require_signature",type:"boolean",notNull:!0,default:!1},{name:"all_must_approve",type:"boolean",notNull:!0,default:!1},{name:"step_order",type:"integer",notNull:!0,default:1},{name:"status",type:"varchar",length:30,notNull:!0,default:"pending",enumValues:["pending","approved","rejected"]}],indexes:[{columns:["instance_id"]},{columns:["instance_id","step_order"]},{columns:["entity_name","entity_id"]},{columns:["verifier_user_id"]},{columns:["status"]}]},{table_name:"verificationFlows",feature_set:["authentication","verification"],add_base_columns:!0,is_form_data:!1,available_app_ids:["default_be"],available_schemas:["*"],excluded_schemas:[],excluded_methods:[],columns:[{name:"entity_name",type:"varchar",length:100,notNull:!0},{name:"name",type:"varchar",length:255,notNull:!0},{name:"description",type:"text"},{name:"trigger_on",type:"varchar",length:50,notNull:!0,default:"update",enumValues:["create","update","delete","manual"]},{name:"trigger_fields",type:"jsonb"},{name:"is_draft",type:"boolean",notNull:!0,default:!0},{name:"published_at",type:"timestamptz"},{name:"viewport",type:"jsonb"}],indexes:[{columns:["entity_name"]},{columns:["entity_name","trigger_on"]},{columns:["is_draft"]}]},{table_name:"verificationSteps",feature_set:["authentication","verification"],add_base_columns:!0,is_form_data:!1,available_app_ids:["default_be"],available_schemas:["*"],excluded_schemas:[],excluded_methods:[],columns:[{name:"flow_id",type:"uuid",notNull:!0,references:{table:"verificationFlows",column:"id",onDelete:"cascade"}},{name:"entity_name",type:"varchar",length:100,notNull:!0},{name:"node_id",type:"varchar",length:100,notNull:!0},{name:"node_type",type:"varchar",length:50,notNull:!0,default:"step",enumValues:["step","verifier","notification"]},{name:"step_order",type:"integer",notNull:!0,default:1},{name:"name",type:"varchar",length:255},{name:"description",type:"text"},{name:"position_x",type:"numeric",notNull:!0,default:0},{name:"position_y",type:"numeric",notNull:!0,default:0},{name:"width",type:"numeric"},{name:"height",type:"numeric"},{name:"style",type:"jsonb"},{name:"data",type:"jsonb"}],indexes:[{columns:["flow_id"]},{columns:["entity_name"]},{columns:["flow_id","node_id"],unique:!0},{columns:["entity_name","step_order"]}]},{table_name:"verificationEdges",feature_set:["authentication","verification"],add_base_columns:!0,is_form_data:!1,available_app_ids:["default_be"],available_schemas:["*"],excluded_schemas:[],excluded_methods:[],columns:[{name:"flow_id",type:"uuid",notNull:!0,references:{table:"verificationFlows",column:"id",onDelete:"cascade"}},{name:"edge_id",type:"varchar",length:100,notNull:!0},{name:"source_node_id",type:"varchar",length:100,notNull:!0},{name:"target_node_id",type:"varchar",length:100,notNull:!0},{name:"source_handle",type:"varchar",length:50},{name:"target_handle",type:"varchar",length:50},{name:"edge_type",type:"varchar",length:50,default:"default",enumValues:["default","conditional","success","failure"]},{name:"label",type:"varchar",length:255},{name:"condition",type:"jsonb"},{name:"style",type:"jsonb"},{name:"animated",type:"boolean",default:!1}],indexes:[{columns:["flow_id"]},{columns:["flow_id","edge_id"],unique:!0},{columns:["source_node_id"]},{columns:["target_node_id"]}]},{table_name:"verificationNotificationRules",feature_set:["verification","notification"],add_base_columns:!0,is_form_data:!1,available_app_ids:["default_be"],available_schemas:["*"],excluded_schemas:[],excluded_methods:[],columns:[{name:"flow_id",type:"uuid",notNull:!0,references:{table:"verificationFlows",column:"id",onDelete:"cascade"}},{name:"node_id",type:"varchar",length:100,notNull:!0},{name:"trigger",type:"varchar",length:50,notNull:!0,enumValues:["on_flow_started","on_step_reached","on_approved","on_rejected","on_flow_completed"]},{name:"title_template",type:"varchar",length:255},{name:"body_template",type:"text"},{name:"starts_at",type:"timestamptz"},{name:"expires_at",type:"timestamptz"}],indexes:[{columns:["flow_id"]},{columns:["flow_id","node_id"],unique:!0},{columns:["trigger"]}]},{table_name:"verificationNotificationRecipients",feature_set:["verification","notification"],add_base_columns:!0,is_form_data:!1,available_app_ids:["default_be"],available_schemas:["*"],excluded_schemas:[],excluded_methods:[],columns:[{name:"rule_id",type:"uuid",notNull:!0,references:{table:"verificationNotificationRules",column:"id",onDelete:"cascade"}},{name:"recipient_type",type:"varchar",length:30,notNull:!0,enumValues:["user","role","all_verifiers","step_verifier","entity_creator"]},{name:"recipient_user_id",type:"uuid"},{name:"recipient_role",type:"varchar",length:100}],indexes:[{columns:["rule_id"]},{columns:["recipient_type"]}]},{table_name:"verificationVerifierConfigs",feature_set:["verification"],add_base_columns:!0,is_form_data:!1,available_app_ids:["default_be"],available_schemas:["*"],excluded_schemas:[],excluded_methods:[],columns:[{name:"flow_id",type:"uuid",notNull:!0,references:{table:"verificationFlows",column:"id",onDelete:"cascade"}},{name:"node_id",type:"varchar",length:100,notNull:!0},{name:"verifier_type",type:"varchar",length:30,notNull:!0,enumValues:["user","role"]},{name:"verifier_user_id",type:"uuid"},{name:"verifier_role",type:"varchar",length:100},{name:"require_signature",type:"boolean",notNull:!0,default:!1},{name:"all_must_approve",type:"boolean",notNull:!0,default:!1}],indexes:[{columns:["flow_id"]},{columns:["flow_id","node_id"],unique:!0},{columns:["verifier_type"]}]},{table_name:"verificationInstances",feature_set:["verification"],add_base_columns:!0,is_form_data:!1,available_app_ids:["default_be"],available_schemas:["*"],excluded_schemas:[],excluded_methods:[],columns:[{name:"flow_id",type:"uuid",notNull:!0,references:{table:"verificationFlows",column:"id"}},{name:"entity_name",type:"varchar",length:100,notNull:!0},{name:"entity_id",type:"uuid",notNull:!0},{name:"started_by",type:"uuid",references:{table:"users",column:"id"}},{name:"status",type:"varchar",length:30,notNull:!0,default:"active",enumValues:["active","completed","rejected","cancelled"]},{name:"current_step_order",type:"integer",notNull:!0,default:1},{name:"started_at",type:"timestamptz",notNull:!0,defaultRaw:"now()"},{name:"completed_at",type:"timestamptz"}],indexes:[{columns:["flow_id"]},{columns:["entity_name","entity_id"]},{columns:["entity_name","entity_id","status"]},{columns:["status"]},{columns:["started_by"]}]},{table_name:"verificationNotificationChannels",feature_set:["verification","notification"],add_base_columns:!0,is_form_data:!1,available_app_ids:["default_be"],available_schemas:["*"],excluded_schemas:[],excluded_methods:[],columns:[{name:"rule_id",type:"uuid",notNull:!0,references:{table:"verificationNotificationRules",column:"id",onDelete:"cascade"}},{name:"channel",type:"varchar",length:30,notNull:!0,enumValues:["portal","email","sms","telegram","webhook"]}],indexes:[{columns:["rule_id"]},{columns:["rule_id","channel"],unique:!0},{columns:["channel"]}]},{table_name:"user_cohorts",feature_set:["authentication"],add_base_columns:!0,is_form_data:!1,available_app_ids:["default_be"],available_schemas:["*"],excluded_schemas:[],excluded_methods:[],columns:[{name:"name",type:"varchar",length:255,notNull:!0},{name:"description",type:"text"},{name:"created_by",type:"uuid",references:{table:"users",column:"id",onDelete:"set null"}},{name:"expires_at",type:"timestamptz"},{name:"user_count",type:"integer",notNull:!0,default:0},{name:"metadata",type:"jsonb",default:"{}"}]},{table_name:"user_sessions",feature_set:["authentication"],add_base_columns:!0,is_form_data:!1,group_name:"Authentication",available_app_ids:["default_be"],available_schemas:["*"],excluded_schemas:[],excluded_methods:["POST","PUT","PATCH"],columns:[{name:"user_id",type:"uuid",notNull:!0,references:{table:"users",column:"id",onDelete:"cascade"}},{name:"token_hash",type:"varchar",length:255,notNull:!0},{name:"refresh_token_hash",type:"varchar",length:255},{name:"device_fingerprint",type:"varchar",length:255},{name:"device_name",type:"varchar",length:100},{name:"device_type",type:"varchar",length:50,enumValues:["desktop","mobile","tablet","unknown"]},{name:"browser_name",type:"varchar",length:50},{name:"browser_version",type:"varchar",length:20},{name:"os_name",type:"varchar",length:50},{name:"os_version",type:"varchar",length:20},{name:"ip_address",type:"varchar",length:45,notNull:!0},{name:"location_country",type:"varchar",length:100},{name:"location_city",type:"varchar",length:100},{name:"location_coordinates",type:"varchar",length:50},{name:"last_activity_at",type:"timestamptz",notNull:!0,defaultRaw:"now()"},{name:"expires_at",type:"timestamptz",notNull:!1},{name:"revoked_at",type:"timestamptz"},{name:"revoked_reason",type:"varchar",length:100,enumValues:["user_logout","user_revoked","admin_revoked","security_concern","password_changed","expired","replaced"]},{name:"is_current",type:"boolean",notNull:!0,default:!1},{name:"login_method",type:"varchar",length:50,enumValues:["password","oauth_google","oauth_github","oauth_microsoft","magic_link","sso","api_key"]},{name:"remember_me",type:"boolean",notNull:!0,default:!1},{name:"trust_score",type:"integer",default:100},{name:"approval_status",type:"varchar",length:20,default:"approved",enumValues:["approved","pending","rejected"]},{name:"approval_token",type:"varchar",length:64},{name:"approval_requested_at",type:"timestamptz"},{name:"approval_responded_at",type:"timestamptz"}],indexes:[{columns:["user_id"]},{columns:["token_hash"],unique:!0},{columns:["refresh_token_hash"]},{columns:["user_id","is_active"]},{columns:["expires_at"]},{columns:["device_fingerprint"]},{columns:["ip_address"]},{columns:["last_activity_at"]},{columns:["approval_status"]},{columns:["approval_token"]}]},{table_name:"trusted_devices",feature_set:["authentication"],add_base_columns:!0,is_form_data:!1,group_name:"Authentication",available_app_ids:["default_be"],available_schemas:["*"],excluded_schemas:[],excluded_methods:["POST","PUT","PATCH"],columns:[{name:"user_id",type:"uuid",notNull:!0,references:{table:"users",column:"id",onDelete:"cascade"}},{name:"token_hash",type:"varchar",length:64,notNull:!0},{name:"status",type:"varchar",length:20,notNull:!0,default:"pending",enumValues:["pending","trusted","revoked"]},{name:"origin_session_id",type:"uuid"},{name:"label",type:"varchar",length:120},{name:"device_fingerprint",type:"varchar",length:255},{name:"first_seen_ip",type:"varchar",length:45},{name:"last_seen_ip",type:"varchar",length:45},{name:"last_seen_at",type:"timestamptz"},{name:"expires_at",type:"timestamptz"},{name:"revoked_at",type:"timestamptz"},{name:"revoked_reason",type:"varchar",length:30,enumValues:["user_revoked","password_reset","logout","admin_revoked","expired","replaced"]}],indexes:[{columns:["token_hash"],unique:!0},{columns:["user_id"]},{columns:["user_id","status"]},{columns:["origin_session_id"]},{columns:["expires_at"]}]},{table_name:"password_reset_tokens",feature_set:["authentication"],add_base_columns:!0,is_form_data:!1,group_name:"Authentication",requires:["email"],available_app_ids:["default_be"],available_schemas:["*"],excluded_schemas:[],excluded_methods:["POST","PUT","PATCH","DELETE"],columns:[{name:"user_id",type:"uuid",notNull:!0,references:{table:"users",column:"id",onDelete:"cascade"}},{name:"token_hash",type:"varchar",length:255,notNull:!0},{name:"expires_at",type:"timestamptz",notNull:!0},{name:"used_at",type:"timestamptz"}],indexes:[{columns:["token_hash"],unique:!0},{columns:["user_id"]},{columns:["expires_at"]}]},{table_name:"magic_link_tokens",feature_set:["authentication"],add_base_columns:!0,is_form_data:!1,group_name:"Authentication",requires:["email"],available_app_ids:["default_be"],available_schemas:["*"],excluded_schemas:[],excluded_methods:["POST","PUT","PATCH","DELETE"],columns:[{name:"user_id",type:"uuid",notNull:!0,references:{table:"users",column:"id",onDelete:"cascade"}},{name:"email",type:"varchar",length:255,notNull:!0},{name:"token_hash",type:"varchar",length:255,notNull:!0},{name:"expires_at",type:"timestamptz",notNull:!0},{name:"used_at",type:"timestamptz"}],indexes:[{columns:["token_hash"],unique:!0},{columns:["user_id"]},{columns:["email"]},{columns:["expires_at"]}]},{table_name:"webauthn_credentials",feature_set:["authentication"],add_base_columns:!0,is_form_data:!1,group_name:"Authentication",requires:[],available_app_ids:["default_be"],available_schemas:["*"],excluded_schemas:[],excluded_methods:["POST","PUT","PATCH","DELETE"],columns:[{name:"user_id",type:"uuid",notNull:!0,references:{table:"users",column:"id",onDelete:"cascade"}},{name:"credential_id",type:"text",notNull:!0},{name:"public_key",type:"text",notNull:!0},{name:"counter",type:"bigint",mode:"number",notNull:!0,defaultValue:0},{name:"transports",type:"jsonb"},{name:"device_type",type:"varchar",length:32},{name:"backed_up",type:"boolean",notNull:!0,defaultValue:!1},{name:"aaguid",type:"varchar",length:64},{name:"authenticator_attachment",type:"varchar",length:32},{name:"nickname",type:"varchar",length:128},{name:"last_used_at",type:"timestamptz"},{name:"revoked_at",type:"timestamptz"}],indexes:[{columns:["credential_id"],unique:!0},{columns:["user_id"]}]},{table_name:"webauthn_challenges",feature_set:["authentication"],add_base_columns:!0,is_form_data:!1,group_name:"Authentication",requires:[],available_app_ids:["default_be"],available_schemas:["*"],excluded_schemas:[],excluded_methods:["POST","PUT","PATCH","DELETE","GET"],columns:[{name:"user_id",type:"uuid",references:{table:"users",column:"id",onDelete:"cascade"}},{name:"challenge",type:"text",notNull:!0},{name:"challenge_type",type:"varchar",length:32,notNull:!0},{name:"expires_at",type:"timestamptz",notNull:!0},{name:"consumed_at",type:"timestamptz"}],indexes:[{columns:["challenge"],unique:!0},{columns:["user_id"]},{columns:["expires_at"]}]},{table_name:"audit_logs",feature_set:["audit"],add_base_columns:!1,is_form_data:!1,available_app_ids:["default_be"],available_schemas:["*"],excluded_schemas:[],excluded_methods:["POST","PUT","DELETE","PATCH","TOGGLE","VERIFICATION"],columns:[{name:"id",type:"uuid",primaryKey:!0,defaultRaw:"gen_random_uuid()"},{name:"entity_id",type:"uuid"},{name:"entity_name",type:"text",notNull:!0},{name:"operation_type",type:"text",notNull:!0},{name:"user_id",type:"uuid"},{name:"ip_address",type:"text"},{name:"user_agent",type:"text"},{name:"summary",type:"text"},{name:"severity",type:"text",default:"info"},{name:"category",type:"text"},{name:"occurrence_count",type:"integer",default:1},{name:"last_occurrence_at",type:"timestamp"},{name:"old_values",type:"jsonb"},{name:"new_values",type:"jsonb"},{name:"created_at",type:"timestamp",notNull:!0,defaultRaw:"now()"},{name:"path",type:"text"},{name:"query",type:"text"}],indexes:[{columns:["entity_id"]},{columns:["entity_name"]},{columns:["user_id"]},{columns:["created_at"]},{columns:["severity"]},{columns:["category"]}]},{table_name:"monitoring_metrics",feature_set:["monitoring"],add_base_columns:!1,is_form_data:!1,available_app_ids:["default_be"],available_schemas:["*"],excluded_schemas:[],excluded_methods:["POST","PUT","DELETE","PATCH","TOGGLE","VERIFICATION"],columns:[{name:"id",type:"uuid",primaryKey:!0,defaultRaw:"gen_random_uuid()"},{name:"metric_type",type:"text",notNull:!0},{name:"metric_name",type:"text",notNull:!0},{name:"value",type:"doublePrecision",notNull:!0},{name:"tags",type:"jsonb"},{name:"recorded_at",type:"timestamp",notNull:!0}],indexes:[{columns:["metric_type"]},{columns:["metric_name"]},{columns:["recorded_at"]}]},{table_name:"oauth_accounts",feature_set:["authentication"],add_base_columns:!0,is_form_data:!1,available_app_ids:["default_be"],available_schemas:["*"],excluded_schemas:[],excluded_methods:[],bulk_endpoints_enabled:!1,columns:[{name:"user_id",type:"uuid",notNull:!0,references:{table:"users",column:"id",onDelete:"cascade"}},{name:"provider",type:"varchar",length:50,notNull:!0,enumValues:["google","github","microsoft","discord","facebook","twitter","apple","custom"]},{name:"provider_account_id",type:"varchar",length:255,notNull:!0},{name:"provider_email",type:"varchar",length:255},{name:"provider_name",type:"varchar",length:255},{name:"provider_avatar_url",type:"text"},{name:"access_token",type:"text"},{name:"refresh_token",type:"text"},{name:"token_expires_at",type:"timestamp"},{name:"scope",type:"text"},{name:"raw_profile",type:"jsonb"},{name:"is_primary",type:"boolean",notNull:!0,default:!1},{name:"last_used_at",type:"timestamp"}],indexes:[{columns:["user_id"]},{columns:["provider","provider_account_id"],unique:!0},{columns:["provider_email"]},{columns:["user_id","provider"]}],constraints:{unique:[{name:"unique_provider_account",columns:["provider","provider_account_id"]}]}},{table_name:"api_keys",feature_set:["authentication"],add_base_columns:!0,is_form_data:!1,available_app_ids:["default_be"],available_schemas:["*"],excluded_schemas:[],excluded_methods:["POST","PUT","PATCH"],bulk_endpoints_enabled:!1,columns:[{name:"user_id",type:"uuid",notNull:!0,references:{table:"users",column:"id",onDelete:"cascade"}},{name:"name",type:"varchar",length:255,notNull:!0},{name:"description",type:"text"},{name:"key_hash",type:"varchar",length:255,notNull:!0},{name:"key_preview",type:"varchar",length:20,notNull:!0},{name:"owner_type",type:"varchar",length:30,notNull:!0,default:"personal",enumValues:["personal","application"]},{name:"application_name",type:"varchar",length:255},{name:"allowed_roles",type:"jsonb",notNull:!0,default:"[]"},{name:"allowed_claims",type:"jsonb",notNull:!0,default:"[]"},{name:"allowed_scopes",type:"jsonb",notNull:!0,default:"[]"},{name:"expires_at",type:"timestamptz"},{name:"last_used_at",type:"timestamptz"},{name:"last_used_ip",type:"varchar",length:45},{name:"usage_count",type:"integer",notNull:!0,default:0},{name:"revoked_at",type:"timestamptz"},{name:"revoked_reason",type:"varchar",length:255}],indexes:[{columns:["key_hash"],unique:!0},{columns:["user_id"]},{columns:["user_id","is_active"]},{columns:["owner_type"]},{columns:["expires_at"]},{columns:["last_used_at"]}]},{table_name:"backup_logs",feature_set:["backup"],add_base_columns:!0,is_form_data:!1,available_app_ids:["default_be"],available_schemas:["main","all"],excluded_schemas:[],excluded_methods:["PUT","PATCH"],columns:[{name:"backup_name",type:"varchar",length:255,notNull:!0},{name:"file_name",type:"varchar",length:500,notNull:!0},{name:"schema_name",type:"varchar",length:100,notNull:!0},{name:"format",type:"varchar",length:20,notNull:!0,default:"json"},{name:"status",type:"varchar",length:20,notNull:!0,default:"pending"},{name:"trigger",type:"varchar",length:20,notNull:!0,default:"manual"},{name:"size_bytes",type:"integer"},{name:"table_count",type:"integer"},{name:"row_count",type:"integer"},{name:"included_tables",type:"jsonb",default:"[]"},{name:"excluded_tables",type:"jsonb",default:"[]"},{name:"error_message",type:"text"},{name:"started_at",type:"timestamp"},{name:"completed_at",type:"timestamp"},{name:"performed_by",type:"varchar",length:255},{name:"cron_expression",type:"varchar",length:100},{name:"retention_days",type:"integer"}],indexes:[{columns:["schema_name"]},{columns:["status"]},{columns:["trigger"]},{columns:["created_at"]}]},{table_name:"payment_transactions",feature_set:["payment"],add_base_columns:!0,is_form_data:!1,available_app_ids:["default_be"],available_schemas:["*"],excluded_schemas:[],excluded_methods:["DELETE"],columns:[{name:"user_id",type:"uuid",references:{table:"users",column:"id"}},{name:"order_id",type:"varchar",length:255},{name:"provider",type:"varchar",length:50,notNull:!0},{name:"provider_transaction_id",type:"varchar",length:255},{name:"provider_payment_id",type:"varchar",length:255},{name:"payment_method",type:"varchar",length:50},{name:"amount",type:"numeric",precision:12,scale:2,notNull:!0,default:0},{name:"currency",type:"varchar",length:10,notNull:!0,default:"TRY"},{name:"status",type:"varchar",length:30,notNull:!0,default:"pending",enumValues:["pending","processing","completed","failed","refunded","partially_refunded","cancelled"]},{name:"status_code",type:"integer"},{name:"status_message",type:"text"},{name:"card_last_four",type:"varchar",length:4},{name:"card_type",type:"varchar",length:50},{name:"card_association",type:"varchar",length:50},{name:"installment",type:"integer",default:1},{name:"is_three_d_secure",type:"boolean",default:!1},{name:"provider_response",type:"jsonb",default:"{}"},{name:"metadata",type:"jsonb",default:"{}"},{name:"refunded_amount",type:"numeric",precision:12,scale:2,default:0},{name:"refunded_at",type:"timestamptz"},{name:"completed_at",type:"timestamptz"},{name:"failed_at",type:"timestamptz"},{name:"ip_address",type:"varchar",length:45}],indexes:[{columns:["user_id"]},{columns:["order_id"]},{columns:["provider"]},{columns:["provider_payment_id"]},{columns:["status"]},{columns:["user_id","status"]}]},{table_name:"payment_methods",feature_set:["payment"],add_base_columns:!0,is_form_data:!1,available_app_ids:["default_be"],available_schemas:["*"],excluded_schemas:[],excluded_methods:[],columns:[{name:"user_id",type:"uuid",notNull:!0,references:{table:"users",column:"id",onDelete:"cascade"}},{name:"provider",type:"varchar",length:50,notNull:!0},{name:"type",type:"varchar",length:30,notNull:!0,default:"card",enumValues:["card","bank_account","wallet"]},{name:"alias",type:"varchar",length:100},{name:"card_last_four",type:"varchar",length:4},{name:"card_type",type:"varchar",length:50},{name:"card_association",type:"varchar",length:50},{name:"card_family",type:"varchar",length:100},{name:"card_bank_name",type:"varchar",length:100},{name:"provider_card_user_key",type:"varchar",length:255},{name:"provider_card_token",type:"varchar",length:255},{name:"bin_number",type:"varchar",length:8},{name:"is_default",type:"boolean",default:!1},{name:"metadata",type:"jsonb",default:"{}"}],indexes:[{columns:["user_id"]},{columns:["provider"]},{columns:["user_id","is_default"]},{columns:["provider_card_user_key"]}]},{table_name:"payment_webhook_logs",feature_set:["payment"],add_base_columns:!0,is_form_data:!1,available_app_ids:["default_be"],available_schemas:["*"],excluded_schemas:[],excluded_methods:["PUT","PATCH","DELETE"],columns:[{name:"provider",type:"varchar",length:50,notNull:!0},{name:"event_type",type:"varchar",length:100,notNull:!0},{name:"provider_payment_id",type:"varchar",length:255},{name:"raw_payload",type:"jsonb",notNull:!0},{name:"processed",type:"boolean",default:!1},{name:"processing_result",type:"jsonb"},{name:"error_message",type:"text"},{name:"ip_address",type:"varchar",length:45},{name:"idempotency_key",type:"varchar",length:255}],indexes:[{columns:["provider"]},{columns:["event_type"]},{columns:["provider_payment_id"]},{columns:["processed"]},{columns:["idempotency_key"],unique:!0}]},{table_name:"payment_sub_merchants",feature_set:["payment"],add_base_columns:!0,is_form_data:!1,available_app_ids:["default_be"],available_schemas:["*"],excluded_schemas:[],excluded_methods:[],columns:[{name:"user_id",type:"uuid",references:{table:"users",column:"id"}},{name:"provider",type:"varchar",length:50,notNull:!0},{name:"provider_sub_merchant_key",type:"varchar",length:255},{name:"external_id",type:"varchar",length:255},{name:"type",type:"varchar",length:50,notNull:!0,default:"personal",enumValues:["personal","private_company","limited_or_joint_stock_company"]},{name:"status",type:"varchar",length:30,notNull:!0,default:"pending",enumValues:["pending","active","suspended","rejected"]},{name:"name",type:"varchar",length:255,notNull:!0},{name:"email",type:"varchar",length:255,notNull:!0},{name:"gsm_number",type:"varchar",length:20},{name:"address",type:"text"},{name:"iban",type:"varchar",length:50},{name:"contact_name",type:"varchar",length:100},{name:"contact_surname",type:"varchar",length:100},{name:"identity_number",type:"varchar",length:20},{name:"tax_office",type:"varchar",length:100},{name:"tax_number",type:"varchar",length:20},{name:"legal_company_title",type:"varchar",length:255},{name:"currency",type:"varchar",length:10,default:"TRY"},{name:"commission_rate",type:"numeric",precision:5,scale:2,default:0},{name:"metadata",type:"jsonb",default:"{}"}],indexes:[{columns:["user_id"]},{columns:["provider"]},{columns:["provider_sub_merchant_key"]},{columns:["external_id"]},{columns:["status"]},{columns:["email"]}]},{table_name:"payment_commission_splits",feature_set:["payment"],add_base_columns:!0,is_form_data:!1,available_app_ids:["default_be"],available_schemas:["*"],excluded_schemas:[],excluded_methods:["DELETE"],columns:[{name:"transaction_id",type:"uuid",notNull:!0,references:{table:"payment_transactions",column:"id",onDelete:"cascade"}},{name:"sub_merchant_id",type:"uuid",notNull:!0,references:{table:"payment_sub_merchants",column:"id"}},{name:"basket_item_id",type:"varchar",length:255},{name:"item_price",type:"numeric",precision:12,scale:2,notNull:!0,default:0},{name:"sub_merchant_price",type:"numeric",precision:12,scale:2,notNull:!0,default:0},{name:"platform_commission",type:"numeric",precision:12,scale:2,notNull:!0,default:0},{name:"commission_rate",type:"numeric",precision:5,scale:2,default:0},{name:"provider_split_id",type:"varchar",length:255},{name:"currency",type:"varchar",length:10,notNull:!0,default:"TRY"},{name:"status",type:"varchar",length:30,notNull:!0,default:"pending",enumValues:["pending","approved","disapproved","refunded"]},{name:"approved_at",type:"timestamptz"},{name:"disapproved_at",type:"timestamptz"},{name:"metadata",type:"jsonb",default:"{}"}],indexes:[{columns:["transaction_id"]},{columns:["sub_merchant_id"]},{columns:["status"]},{columns:["transaction_id","sub_merchant_id"]},{columns:["provider_split_id"]}]},{table_name:"payment_products",feature_set:["payment"],add_base_columns:!0,is_form_data:!1,available_app_ids:["default_be"],available_schemas:["*"],excluded_schemas:[],excluded_methods:[],columns:[{name:"provider",type:"varchar",length:50,notNull:!0},{name:"provider_product_id",type:"varchar",length:255},{name:"name",type:"varchar",length:255,notNull:!0},{name:"description",type:"text"},{name:"type",type:"varchar",length:30,default:"service",enumValues:["service","good"]},{name:"status",type:"varchar",length:30,notNull:!0,default:"active",enumValues:["active","archived","draft"]},{name:"images",type:"jsonb",default:"[]"},{name:"unit_label",type:"varchar",length:50},{name:"url",type:"varchar",length:500},{name:"metadata",type:"jsonb",default:"{}"}],indexes:[{columns:["provider"]},{columns:["provider_product_id"]},{columns:["name"]},{columns:["status"]},{columns:["type"]}]},{table_name:"payment_prices",feature_set:["payment"],add_base_columns:!0,is_form_data:!1,available_app_ids:["default_be"],available_schemas:["*"],excluded_schemas:[],excluded_methods:["DELETE"],columns:[{name:"product_id",type:"uuid",notNull:!0,references:{table:"payment_products",column:"id",onDelete:"cascade"}},{name:"provider",type:"varchar",length:50,notNull:!0},{name:"provider_price_id",type:"varchar",length:255},{name:"currency",type:"varchar",length:10,notNull:!0,default:"USD"},{name:"unit_amount",type:"integer",notNull:!0,default:0},{name:"unit_amount_decimal",type:"varchar",length:30},{name:"type",type:"varchar",length:30,notNull:!0,default:"one_time",enumValues:["one_time","recurring"]},{name:"billing_scheme",type:"varchar",length:30,default:"per_unit",enumValues:["per_unit","tiered"]},{name:"nickname",type:"varchar",length:255},{name:"recurring_interval",type:"varchar",length:20,enumValues:["day","week","month","year"]},{name:"recurring_interval_count",type:"integer",default:1},{name:"recurring_usage_type",type:"varchar",length:20,default:"licensed",enumValues:["licensed","metered"]},{name:"recurring_aggregate_usage",type:"varchar",length:30,enumValues:["sum","last_during_period","last_ever","max"]},{name:"transform_quantity_divide_by",type:"integer"},{name:"transform_quantity_round",type:"varchar",length:10,enumValues:["up","down"]},{name:"status",type:"varchar",length:30,notNull:!0,default:"active",enumValues:["active","archived"]},{name:"metadata",type:"jsonb",default:"{}"}],indexes:[{columns:["product_id"]},{columns:["provider"]},{columns:["provider_price_id"]},{columns:["type"]},{columns:["currency"]},{columns:["status"]},{columns:["product_id","status"]}]},{table_name:"payment_customers",feature_set:["payment"],add_base_columns:!0,is_form_data:!1,available_app_ids:["default_be"],available_schemas:["*"],excluded_schemas:[],excluded_methods:[],columns:[{name:"user_id",type:"uuid",references:{table:"users",column:"id"}},{name:"provider",type:"varchar",length:50,notNull:!0},{name:"provider_customer_id",type:"varchar",length:255,notNull:!0},{name:"email",type:"varchar",length:255,notNull:!0},{name:"name",type:"varchar",length:255},{name:"phone",type:"varchar",length:50},{name:"description",type:"text"},{name:"address",type:"jsonb",default:"{}"},{name:"tax_id_type",type:"varchar",length:50},{name:"tax_id_value",type:"varchar",length:100},{name:"metadata",type:"jsonb",default:"{}"}],indexes:[{columns:["user_id"]},{columns:["provider"]},{columns:["provider_customer_id"]},{columns:["email"]},{columns:["user_id","provider"],unique:!0}]},{table_name:"payment_subscriptions",feature_set:["payment"],add_base_columns:!0,is_form_data:!1,available_app_ids:["default_be"],available_schemas:["*"],excluded_schemas:[],excluded_methods:["DELETE"],columns:[{name:"customer_id",type:"uuid",notNull:!0,references:{table:"payment_customers",column:"id"}},{name:"provider",type:"varchar",length:50,notNull:!0},{name:"provider_subscription_id",type:"varchar",length:255},{name:"status",type:"varchar",length:30,notNull:!0,default:"incomplete",enumValues:["active","past_due","unpaid","canceled","incomplete","incomplete_expired","trialing","paused"]},{name:"collection_method",type:"varchar",length:30,default:"charge_automatically",enumValues:["charge_automatically","send_invoice"]},{name:"current_period_start",type:"timestamptz"},{name:"current_period_end",type:"timestamptz"},{name:"cancel_at_period_end",type:"boolean",default:!1},{name:"canceled_at",type:"timestamptz"},{name:"trial_start",type:"timestamptz"},{name:"trial_end",type:"timestamptz"},{name:"items",type:"jsonb",default:"[]"},{name:"metadata",type:"jsonb",default:"{}"}],indexes:[{columns:["customer_id"]},{columns:["provider"]},{columns:["provider_subscription_id"]},{columns:["status"]},{columns:["customer_id","status"]}]},{table_name:"payment_invoices",feature_set:["payment"],add_base_columns:!0,is_form_data:!1,available_app_ids:["default_be"],available_schemas:["*"],excluded_schemas:[],excluded_methods:["DELETE"],columns:[{name:"customer_id",type:"uuid",notNull:!0,references:{table:"payment_customers",column:"id"}},{name:"subscription_id",type:"uuid",references:{table:"payment_subscriptions",column:"id"}},{name:"provider",type:"varchar",length:50,notNull:!0},{name:"provider_invoice_id",type:"varchar",length:255},{name:"status",type:"varchar",length:30,notNull:!0,default:"draft",enumValues:["draft","open","paid","uncollectible","void"]},{name:"collection_method",type:"varchar",length:30,default:"charge_automatically",enumValues:["charge_automatically","send_invoice"]},{name:"currency",type:"varchar",length:10,notNull:!0,default:"USD"},{name:"amount_due",type:"integer",default:0},{name:"amount_paid",type:"integer",default:0},{name:"amount_remaining",type:"integer",default:0},{name:"description",type:"text"},{name:"hosted_invoice_url",type:"varchar",length:1000},{name:"invoice_pdf",type:"varchar",length:1000},{name:"due_date",type:"timestamptz"},{name:"days_until_due",type:"integer"},{name:"period_start",type:"timestamptz"},{name:"period_end",type:"timestamptz"},{name:"paid_at",type:"timestamptz"},{name:"voided_at",type:"timestamptz"},{name:"lines",type:"jsonb",default:"[]"},{name:"metadata",type:"jsonb",default:"{}"}],indexes:[{columns:["customer_id"]},{columns:["subscription_id"]},{columns:["provider"]},{columns:["provider_invoice_id"]},{columns:["status"]},{columns:["customer_id","status"]}]},{table_name:"chat_conversations",feature_set:["authentication","chat"],add_base_columns:!0,is_form_data:!1,available_app_ids:["default_be"],available_schemas:["*"],excluded_schemas:[],excluded_methods:["POST","PUT","PATCH","DELETE","GET"],columns:[{name:"type",type:"varchar",length:20,notNull:!0,default:"direct",enumValues:["direct","group"]},{name:"title",type:"varchar",length:255},{name:"description",type:"text"},{name:"avatar_file_id",type:"uuid"},{name:"direct_key",type:"varchar",length:255},{name:"last_message_at",type:"timestamptz"},{name:"last_message_preview",type:"varchar",length:500},{name:"last_message_sender_id",type:"uuid",references:{table:"users",column:"id",onDelete:"set null"}}],indexes:[{columns:["direct_key"],unique:!0},{columns:["type"]},{columns:["last_message_at"]}]},{table_name:"chat_participants",feature_set:["authentication","chat"],add_base_columns:!0,is_form_data:!1,available_app_ids:["default_be"],available_schemas:["*"],excluded_schemas:[],excluded_methods:["POST","PUT","PATCH","DELETE","GET"],columns:[{name:"conversation_id",type:"uuid",notNull:!0,references:{table:"chat_conversations",column:"id",onDelete:"cascade"}},{name:"user_id",type:"uuid",notNull:!0,references:{table:"users",column:"id",onDelete:"cascade"}},{name:"role",type:"varchar",length:20,notNull:!0,default:"member",enumValues:["member","admin","owner"]},{name:"last_read_message_id",type:"uuid"},{name:"last_read_at",type:"timestamptz"},{name:"unread_count",type:"integer",notNull:!0,default:0},{name:"muted",type:"boolean",notNull:!0,default:!1},{name:"muted_until",type:"timestamptz"},{name:"notifications_enabled",type:"boolean",notNull:!0,default:!0},{name:"left_at",type:"timestamptz"}],indexes:[{columns:["user_id"]},{columns:["conversation_id"]}],constraints:{unique:[{name:"unique_conversation_participant",columns:["conversation_id","user_id"]}]}},{table_name:"chat_messages",feature_set:["authentication","chat"],add_base_columns:!0,is_form_data:!1,available_app_ids:["default_be"],available_schemas:["*"],excluded_schemas:[],excluded_methods:["POST","PUT","PATCH","DELETE","GET"],columns:[{name:"conversation_id",type:"uuid",notNull:!0,references:{table:"chat_conversations",column:"id",onDelete:"cascade"}},{name:"sender_id",type:"uuid",references:{table:"users",column:"id",onDelete:"set null"}},{name:"content",type:"text"},{name:"content_type",type:"varchar",length:20,notNull:!0,default:"text",enumValues:["text","image","file","video","audio","system"]},{name:"reply_to_id",type:"uuid",references:{table:"chat_messages",column:"id",onDelete:"set null"}},{name:"metadata",type:"jsonb",default:"{}"},{name:"client_message_id",type:"varchar",length:100},{name:"edited_at",type:"timestamptz"},{name:"deleted_at",type:"timestamptz"}],indexes:[{columns:["conversation_id","created_at"]},{columns:["sender_id"]},{columns:["reply_to_id"]}]},{table_name:"chat_message_attachments",feature_set:["authentication","chat","storage"],add_base_columns:!0,is_form_data:!1,available_app_ids:["default_be"],available_schemas:["*"],excluded_schemas:[],excluded_methods:["POST","PUT","PATCH","DELETE","GET"],columns:[{name:"message_id",type:"uuid",notNull:!0,references:{table:"chat_messages",column:"id",onDelete:"cascade"}},{name:"conversation_id",type:"uuid",notNull:!0,references:{table:"chat_conversations",column:"id",onDelete:"cascade"}},{name:"file_id",type:"uuid",notNull:!0,references:{table:"files",column:"id",onDelete:"cascade"}},{name:"kind",type:"varchar",length:20,notNull:!0,default:"file",enumValues:["image","video","audio","file"]},{name:"original_name",type:"varchar",length:255},{name:"mime_type",type:"varchar",length:100},{name:"size",type:"bigint",mode:"number"},{name:"width",type:"integer"},{name:"height",type:"integer"},{name:"duration",type:"integer"}],indexes:[{columns:["message_id"]},{columns:["conversation_id"]},{columns:["file_id"]}]},{table_name:"payment_splits",feature_set:["payment"],add_base_columns:!0,is_form_data:!1,available_app_ids:["default_be"],available_schemas:["*"],excluded_schemas:[],excluded_methods:["POST","PUT","PATCH","DELETE"],group_name:"Payments Marketplace",columns:[{name:"transaction_id",type:"uuid",references:{table:"paymentTransactions",column:"id",onDelete:"set null"}},{name:"recipient_owner_type",type:"varchar",length:20,notNull:!0,default:"seller",comment:"seller | platform | tenant | user"},{name:"recipient_owner_id",type:"uuid",notNull:!0},{name:"provider",type:"varchar",length:50,notNull:!0},{name:"gross_amount",type:"bigint",mode:"number",notNull:!0,comment:"Minor units"},{name:"provider_fee_amount",type:"bigint",mode:"number",notNull:!0,default:0},{name:"platform_fee_amount",type:"bigint",mode:"number",notNull:!0,default:0},{name:"reserve_amount",type:"bigint",mode:"number",notNull:!0,default:0},{name:"net_payable_amount",type:"bigint",mode:"number",notNull:!0},{name:"currency",type:"varchar",length:10,notNull:!0},{name:"available_for_payout_at",type:"timestamptz"},{name:"status",type:"varchar",length:20,notNull:!0,default:"pending",comment:"pending | available | payout_requested | paid | held | refunded | disputed | cancelled"},{name:"source_type",type:"varchar",length:50},{name:"source_id",type:"varchar",length:255},{name:"metadata",type:"jsonb",default:"{}"}],indexes:[{columns:["recipient_owner_type","recipient_owner_id"]},{columns:["status"]},{columns:["transaction_id"]}]},{table_name:"payment_ledger_entries",feature_set:["payment"],add_base_columns:!0,is_form_data:!1,available_app_ids:["default_be"],available_schemas:["*"],excluded_schemas:[],excluded_methods:["POST","PUT","PATCH","DELETE"],group_name:"Payments Marketplace",columns:[{name:"account",type:"varchar",length:40,notNull:!0,comment:"platform_revenue | provider_fee | seller_payable | reserve | refund_liability | dispute_liability | payout_in_transit | payout_completed"},{name:"owner_type",type:"varchar",length:20},{name:"owner_id",type:"uuid"},{name:"direction",type:"varchar",length:6,notNull:!0,comment:"debit | credit"},{name:"amount",type:"bigint",mode:"number",notNull:!0,comment:"Minor units"},{name:"currency",type:"varchar",length:10,notNull:!0},{name:"entry_type",type:"varchar",length:40,notNull:!0,comment:"split | refund | payout | reserve_hold | reserve_release | dispute | adjustment"},{name:"reference_type",type:"varchar",length:50},{name:"reference_id",type:"varchar",length:255},{name:"split_id",type:"uuid",references:{table:"paymentSplits",column:"id",onDelete:"set null"}},{name:"description",type:"text"},{name:"metadata",type:"jsonb",default:"{}"}],indexes:[{columns:["account"]},{columns:["owner_type","owner_id"]},{columns:["entry_type"]},{columns:["reference_type","reference_id"]}]},{table_name:"payment_settlement_policies",feature_set:["payment"],add_base_columns:!0,is_form_data:!1,available_app_ids:["default_be"],available_schemas:["*"],excluded_schemas:[],excluded_methods:[],group_name:"Payments Marketplace",columns:[{name:"owner_type",type:"varchar",length:20,notNull:!0,default:"tenant",comment:"tenant | seller"},{name:"owner_id",type:"uuid",notNull:!0},{name:"default_delay_days",type:"integer",notNull:!0,default:7},{name:"new_seller_delay_days",type:"integer",notNull:!0,default:14},{name:"reserve_rate",type:"numeric",precision:5,scale:4,notNull:!0,default:0,comment:"Fraction 0..1 held as reserve"},{name:"reserve_days",type:"integer",notNull:!0,default:0},{name:"minimum_payout_amount",type:"bigint",mode:"number",notNull:!0,default:0},{name:"currency",type:"varchar",length:10},{name:"early_payout_enabled",type:"boolean",notNull:!0,default:!1},{name:"status",type:"varchar",length:20,notNull:!0,default:"active"},{name:"metadata",type:"jsonb",default:"{}"}],indexes:[{columns:["owner_type","owner_id"],unique:!0}]},{table_name:"payment_reserves",feature_set:["payment"],add_base_columns:!0,is_form_data:!1,available_app_ids:["default_be"],available_schemas:["*"],excluded_schemas:[],excluded_methods:["POST","PUT","PATCH","DELETE"],group_name:"Payments Marketplace",columns:[{name:"owner_type",type:"varchar",length:20,notNull:!0,default:"seller"},{name:"owner_id",type:"uuid",notNull:!0},{name:"split_id",type:"uuid",references:{table:"paymentSplits",column:"id",onDelete:"set null"}},{name:"amount",type:"bigint",mode:"number",notNull:!0},{name:"currency",type:"varchar",length:10,notNull:!0},{name:"reason",type:"varchar",length:30,notNull:!0,default:"refund_window",comment:"refund_window | chargeback_risk | new_seller | manual_hold | dispute | compliance_review"},{name:"status",type:"varchar",length:20,notNull:!0,default:"held",comment:"held | released | consumed | cancelled"},{name:"release_at",type:"timestamptz"},{name:"released_at",type:"timestamptz"},{name:"metadata",type:"jsonb",default:"{}"}],indexes:[{columns:["owner_type","owner_id"]},{columns:["status"]},{columns:["release_at"]}]},{table_name:"payment_payout_requests",feature_set:["payment"],add_base_columns:!0,is_form_data:!1,available_app_ids:["default_be"],available_schemas:["*"],excluded_schemas:[],excluded_methods:["POST","PUT","PATCH","DELETE"],group_name:"Payments Marketplace",columns:[{name:"recipient_owner_type",type:"varchar",length:20,notNull:!0,default:"seller"},{name:"recipient_owner_id",type:"uuid",notNull:!0},{name:"amount",type:"bigint",mode:"number",notNull:!0},{name:"currency",type:"varchar",length:10,notNull:!0},{name:"status",type:"varchar",length:20,notNull:!0,default:"pending",comment:"pending | approved | rejected | processing | completed | failed | cancelled"},{name:"provider",type:"varchar",length:50},{name:"provider_payout_id",type:"varchar",length:255},{name:"provider_transfer_id",type:"varchar",length:255},{name:"destination",type:"varchar",length:255},{name:"requested_by",type:"uuid"},{name:"decided_by",type:"uuid"},{name:"decided_at",type:"timestamptz"},{name:"completed_at",type:"timestamptz"},{name:"failure_reason",type:"text"},{name:"metadata",type:"jsonb",default:"{}"}],indexes:[{columns:["recipient_owner_type","recipient_owner_id"]},{columns:["status"]}]},{table_name:"payment_disputes",feature_set:["payment"],add_base_columns:!0,is_form_data:!1,available_app_ids:["default_be"],available_schemas:["*"],excluded_schemas:[],excluded_methods:["PUT"],group_name:"Payments Marketplace",columns:[{name:"transaction_id",type:"uuid",references:{table:"paymentTransactions",column:"id",onDelete:"set null"}},{name:"provider",type:"varchar",length:50,notNull:!0},{name:"provider_dispute_id",type:"varchar",length:255},{name:"owner_type",type:"varchar",length:20},{name:"owner_id",type:"uuid"},{name:"amount",type:"bigint",mode:"number"},{name:"currency",type:"varchar",length:10},{name:"reason",type:"varchar",length:100},{name:"status",type:"varchar",length:30,notNull:!0,default:"open",comment:"open | under_review | won | lost | accepted | cancelled"},{name:"evidence_due_at",type:"timestamptz"},{name:"resolved_at",type:"timestamptz"},{name:"metadata",type:"jsonb",default:"{}"}],indexes:[{columns:["provider","provider_dispute_id"]},{columns:["status"]},{columns:["owner_type","owner_id"]},{columns:["transaction_id"]}]},{table_name:"nucleus_secrets",feature_set:["secrets"],add_base_columns:!0,is_form_data:!1,available_app_ids:["default_be"],available_schemas:["*"],excluded_schemas:[],excluded_methods:["GET","POST","PUT","PATCH","DELETE","TOGGLE"],bulk_endpoints_enabled:!1,group_name:"Secrets",columns:[{name:"scope",type:"varchar",length:200,notNull:!0,comment:"Config dot-path that owns the credential, e.g. authentication.oauth.providers.microsoft"},{name:"key",type:"varchar",length:120,notNull:!0,comment:"Leaf key under that path, e.g. clientSecret"},{name:"value_enc",type:"text",notNull:!0,comment:"AES-256-GCM envelope v1:iv:tag:ciphertext \u2014 never returned by any route"},{name:"previous_enc",type:"text",comment:"Value replaced by the last rotation, retained for one-step rollback"},{name:"preview",type:"varchar",length:80},{name:"kind",type:"varchar",length:30,notNull:!0,default:"text",enumValues:["text","password","json","connection_string","pem"]},{name:"description",type:"varchar",length:500},{name:"version",type:"integer",notNull:!0,default:1},{name:"rotated_at",type:"timestamptz"},{name:"last_used_at",type:"timestamptz"}],indexes:[{columns:["scope","key"],unique:!0},{columns:["scope"]}]},{table_name:"integration_sources",feature_set:["integrations"],add_base_columns:!0,is_form_data:!1,available_app_ids:["default_be"],available_schemas:["*"],excluded_schemas:[],excluded_methods:[],bulk_endpoints_enabled:!1,group_name:"Integrations",columns:[{name:"name",type:"varchar",length:150,notNull:!0},{name:"slug",type:"varchar",length:150,notNull:!0,comment:"Stable machine name; referenced by exports and by operators in logs"},{name:"base_url",type:"varchar",length:500,notNull:!0,comment:"Any scheme, host and port \u2014 https, plain http, in-cluster DNS, IP. HTTPS is NOT required and must never be enforced: reaching a service inside the same cluster over http is a legitimate deployment."},{name:"description",type:"text"},{name:"enabled",type:"boolean",default:!0},{name:"auth_type",type:"varchar",length:20,notNull:!0,default:"none",enumValues:["none","bearer","api_key","basic","oauth2"]},{name:"auth_token_encrypted",type:"text",sensitive:!0,comment:"Static credential (bearer / api key / basic blob), encrypted at rest"},{name:"auth_token_hint",type:"varchar",length:24},{name:"auth_header_name",type:"varchar",length:80,default:"Authorization"},{name:"auth_query_param",type:"varchar",length:80,comment:"When set, an api_key credential travels as this query parameter instead of a header"},{name:"extra_headers",type:"jsonb",default:{}},{name:"token_url",type:"varchar",length:500},{name:"oauth_grant_type",type:"varchar",length:40,default:"password",enumValues:["password","client_credentials"]},{name:"oauth_client_id",type:"varchar",length:200},{name:"oauth_client_secret_encrypted",type:"text",sensitive:!0},{name:"oauth_client_secret_hint",type:"varchar",length:24},{name:"oauth_username",type:"varchar",length:200},{name:"oauth_password_encrypted",type:"text",sensitive:!0},{name:"oauth_password_hint",type:"varchar",length:24},{name:"oauth_scope",type:"varchar",length:300},{name:"oauth_client_auth",type:"varchar",length:20,default:"basic",enumValues:["basic","body"]},{name:"tls_verify",type:"boolean",notNull:!0,default:!0,comment:"Per-source certificate verification. Turning it off applies to THIS source's connections only \u2014 never to the process, which is what a NODE_TLS_REJECT_UNAUTHORIZED toggle would do."},{name:"timeout_ms",type:"integer",notNull:!0,default:30000},{name:"spec_url",type:"varchar",length:500,comment:"Optional API description document (OpenAPI/Swagger/Postman/Bruno). Discovery is a convenience: every endpoint can always be defined by hand instead."},{name:"spec_format",type:"varchar",length:20,default:"manual",enumValues:["manual","openapi","swagger","postman","bruno","har"]}],constraints:{unique:[{name:"uniq_integration_source_slug",columns:["slug"]}]},indexes:[{columns:["enabled"]}]},{table_name:"integration_endpoints",feature_set:["integrations"],add_base_columns:!0,is_form_data:!1,available_app_ids:["default_be"],available_schemas:["*"],excluded_schemas:[],excluded_methods:[],bulk_endpoints_enabled:!1,group_name:"Integrations",columns:[{name:"source_id",type:"uuid",notNull:!0,references:{table:"integrationSources",column:"id",onDelete:"cascade"}},{name:"tag",type:"varchar",length:120,notNull:!0},{name:"name",type:"varchar",length:200},{name:"method",type:"varchar",length:10,notNull:!0,default:"GET",enumValues:["GET","POST","PUT","PATCH","DELETE"]},{name:"path",type:"varchar",length:500,notNull:!0,comment:"Appended to the source base URL. May carry {brace} or :colon path tokens, whose values come from param_bindings."},{name:"description",type:"text"},{name:"query_params",type:"jsonb",default:{}},{name:"request_body",type:"jsonb",default:{}},{name:"param_bindings",type:"jsonb",default:[],comment:"Where each path/query parameter comes from: a static value, another endpoint's records (fan-out), or a column of one of our own tables."},{name:"pagination",type:"jsonb",default:{},comment:"Explicitly chosen paging profile (none/page_number/offset/cursor/link_header). Never guessed \u2014 a wrong guess silently imports one page."},{name:"response_root_path",type:"varchar",length:200},{name:"tree_children_path",type:"varchar",length:200,comment:"Field holding a record's children when the source answers with a shape rather than a list \u2014 each node then becomes its own row and knows its parent"},{name:"sample_response",type:"jsonb",default:{},comment:"A few real records captured by a live test, so the mapping screen still shows fields when the source is unreachable"},{name:"enabled",type:"boolean",default:!0}],indexes:[{columns:["source_id"]},{columns:["source_id","tag"]}]},{table_name:"integration_mappings",feature_set:["integrations"],add_base_columns:!0,is_form_data:!1,available_app_ids:["default_be"],available_schemas:["*"],excluded_schemas:[],excluded_methods:[],bulk_endpoints_enabled:!1,group_name:"Integrations",columns:[{name:"endpoint_id",type:"uuid",notNull:!0,references:{table:"integrationEndpoints",column:"id",onDelete:"cascade"}},{name:"name",type:"varchar",length:200,notNull:!0},{name:"target_entity",type:"varchar",length:120,notNull:!0,comment:"snake_case table name this mapping writes into"},{name:"field_mappings",type:"jsonb",notNull:!0,default:[]},{name:"reference_checks",type:"jsonb",notNull:!0,default:[],comment:"Columns whose value must already exist in another entity, and what to do when it does not."},{name:"dedup_source_field",type:"varchar",length:200},{name:"dedup_target_field",type:"varchar",length:200,comment:"Target column that identifies the same record across runs. Without it nothing can be matched and every run appends."},{name:"write_mode",type:"varchar",length:20,notNull:!0,default:"upsert",enumValues:["upsert","append","replace"],comment:"replace is the destructive wipe-and-rewrite; it is no longer the default and is gated by the completeness check."},{name:"missing_record_policy",type:"varchar",length:20,notNull:!0,default:"ignore",enumValues:["ignore","deactivate","delete"],comment:"What happens to a row that exists here but no longer in the source. deactivate is the right answer for most HR data and could not be expressed before."},{name:"missing_record_column",type:"varchar",length:120},{name:"missing_record_value",type:"varchar",length:200},{name:"origin_column",type:"varchar",length:120,comment:"Column this mapping stamps on rows it owns. Set it when the target table ALSO holds rows people create by hand: without it every hand-made row looks like one the source stopped carrying, and the first run with a removal policy deletes them all."},{name:"origin_value",type:"varchar",length:120,comment:"The value written into origin_column. Rows carrying it are the only removal candidates."},{name:"max_removal_share",type:"numeric",comment:"Share of the target table (0-1) above which an UNATTENDED run refuses to remove. A scheduled run passes through neither the preview nor a confirmation, so this is the only thing between a wrong plan and the table. 0 or null disables."},{name:"password_target_column",type:"varchar",length:100,comment:"Column that receives the shared opening password given at run time. The password itself is never stored."},{name:"account_rule",type:"jsonb",comment:"Creates a login alongside each imported record, in the same transaction, and links it. The password belongs to the run, never to this row."},{name:"schedule_cron",type:"varchar",length:120},{name:"schedule_timezone",type:"varchar",length:64,comment:"IANA zone the cron expression is read in. Without it a nightly job drifts with the container's clock."},{name:"schedule_enabled",type:"boolean",notNull:!0,default:!1},{name:"last_run_at",type:"timestamptz"},{name:"enabled",type:"boolean",default:!0}],indexes:[{columns:["endpoint_id"]},{columns:["target_entity"]},{columns:["schedule_enabled"]}]},{table_name:"integration_runs",feature_set:["integrations"],add_base_columns:!0,is_form_data:!1,available_app_ids:["default_be"],available_schemas:["*"],excluded_schemas:[],excluded_methods:["POST","PUT","PATCH","DELETE"],bulk_endpoints_enabled:!1,group_name:"Integrations",columns:[{name:"mapping_id",type:"uuid",references:{table:"integrationMappings",column:"id",onDelete:"set null"},comment:"Nullable and NOT cascaded: deleting a mapping must not erase the record that it once rewrote a table."},{name:"target_entity",type:"varchar",length:120},{name:"write_mode",type:"varchar",length:20,enumValues:["upsert","append","replace"]},{name:"status",type:"varchar",length:20,notNull:!0,default:"queued",enumValues:["queued","running","succeeded","failed","cancelled","reverted"],comment:"There is deliberately no 'partial': a run either applied in full or not at all."},{name:"trigger",type:"varchar",length:20,notNull:!0,default:"manual",enumValues:["manual","schedule","api"]},{name:"total_fetched",type:"integer",notNull:!0,default:0},{name:"source_reported_total",type:"integer",comment:"What the source said it had. When this and total_fetched disagree, a destructive run refuses to start."},{name:"created_count",type:"integer",notNull:!0,default:0},{name:"references_created_count",type:"integer",notNull:!0,default:0,comment:"Rows this run added to OTHER tables so its records could point at them."},{name:"accounts_created_count",type:"integer",notNull:!0,default:0,comment:"Logins this run created so its records could be signed in to."},{name:"updated_count",type:"integer",notNull:!0,default:0},{name:"skipped_count",type:"integer",notNull:!0,default:0},{name:"deleted_count",type:"integer",notNull:!0,default:0},{name:"deactivated_count",type:"integer",notNull:!0,default:0},{name:"error_count",type:"integer",notNull:!0,default:0},{name:"external_call_count",type:"integer",notNull:!0,default:0},{name:"issues",type:"jsonb",default:[]},{name:"errors",type:"jsonb",default:[]},{name:"cancel_requested",type:"boolean",notNull:!0,default:!1,comment:"Set by the cancel endpoint; the engine checks it at every batch boundary. A dropped HTTP connection is NOT a cancellation."},{name:"started_at",type:"timestamptz"},{name:"finished_at",type:"timestamptz"},{name:"pre_images_expire_at",type:"timestamptz",comment:"After this, the run can no longer be reverted and the UI says so rather than offering a button that quietly does nothing."},{name:"reverted_at",type:"timestamptz"},{name:"reverted_by",type:"uuid",references:{table:"users",column:"id"}},{name:"reverted_run_id",type:"uuid"}],indexes:[{columns:["mapping_id"]},{columns:["status"]},{columns:["target_entity"]},{columns:["created_at"]}]},{table_name:"integration_run_items",feature_set:["integrations"],add_base_columns:!0,is_form_data:!1,available_app_ids:["default_be"],available_schemas:["*"],excluded_schemas:[],excluded_methods:["POST","PUT","PATCH","DELETE"],bulk_endpoints_enabled:!1,group_name:"Integrations",columns:[{name:"run_id",type:"uuid",notNull:!0,references:{table:"integrationRuns",column:"id",onDelete:"cascade"}},{name:"target_entity",type:"varchar",length:120,notNull:!0},{name:"row_id",type:"uuid"},{name:"action",type:"varchar",length:20,notNull:!0,enumValues:["create","update","delete","deactivate"]},{name:"before",type:"jsonb",comment:"The row as it stood before this run touched it. This is what makes a successful run reversible; the bulk delete path already produced these rows and threw them away."},{name:"after",type:"jsonb"}],indexes:[{columns:["run_id"]},{columns:["run_id","action"]},{columns:["target_entity","row_id"]}]}]};function deriveAuthEndpoints(configs){let out={};for(let config of Object.values(configs)){let subs=config.subEndpoints??[{key:config.key,method:config.method,suffix:""}];for(let sub of subs){let base=sub.defaultRoute??config.defaultRoute,entry={method:sub.method??config.method,path:`${base}${sub.suffix??""}`},isPublic=sub.isPublic??config.defaultIsPublic;if(isPublic!==void 0)entry.isPublic=isPublic;entry._payload=sub._payload??config._payload,entry._success=sub._success??config._success,entry._error=sub._error??config._error,out[sub.key]=entry}}return out}var AUTH_ENDPOINT_CONFIGS={login:{key:"LOGIN",method:"POST",defaultRoute:"/auth/login",defaultIsPublic:!0,_payload:void 0,_success:void 0,_error:void 0},register:{key:"REGISTER",method:"POST",defaultRoute:"/auth/register",defaultIsPublic:!0,_payload:void 0,_success:void 0,_error:void 0},logout:{key:"LOGOUT",method:"POST",defaultRoute:"/auth/logout",defaultIsPublic:!1,_payload:void 0,_success:void 0,_error:void 0},refresh:{key:"REFRESH",method:"POST",defaultRoute:"/auth/refresh",defaultIsPublic:!1,_payload:void 0,_success:void 0,_error:void 0},me:{key:"ME",method:"GET",defaultRoute:"/auth/me",defaultIsPublic:!1,_payload:void 0,_success:void 0,_error:void 0},passwordChange:{key:"PASSWORD_CHANGE",method:"POST",defaultRoute:"/auth/password-change",defaultIsPublic:!1,_payload:void 0,_success:void 0,_error:void 0},passwordSet:{key:"PASSWORD_SET",method:"POST",defaultRoute:"/auth/password-set",defaultIsPublic:!1,_payload:void 0,_success:void 0,_error:void 0},passwordReset:{key:"PASSWORD_RESET_REQUEST",method:"POST",defaultRoute:"/auth/password-reset",defaultIsPublic:!0,subEndpoints:[{key:"PASSWORD_RESET_REQUEST",method:"POST",suffix:"/request",_payload:void 0,_success:void 0,_error:void 0},{key:"PASSWORD_RESET_CONFIRM",method:"POST",suffix:"/confirm",_payload:void 0,_success:void 0,_error:void 0}]},sessions:{key:"SESSIONS",method:"GET",defaultRoute:"/auth/sessions",defaultIsPublic:!1,subEndpoints:[{key:"SESSIONS",method:"GET",suffix:"",_payload:void 0,_success:void 0,_error:void 0},{key:"SESSIONS_CURRENT",method:"GET",suffix:"/current",_payload:void 0,_success:void 0,_error:void 0},{key:"SESSIONS_STATS",method:"GET",suffix:"/stats",_payload:void 0,_success:void 0,_error:void 0},{key:"SESSIONS_PENDING",method:"GET",suffix:"/pending",_payload:void 0,_success:void 0,_error:void 0},{key:"SESSIONS_REVOKE",method:"DELETE",suffix:"/:sessionId",_payload:void 0,_success:void 0,_error:void 0},{key:"SESSIONS_REVOKE_ALL",method:"DELETE",suffix:"/all",_payload:void 0,_success:void 0,_error:void 0},{key:"SESSIONS_ADMIN_LIST",method:"GET",suffix:"/admin/:userId",_payload:void 0,_success:void 0,_error:void 0},{key:"SESSIONS_ADMIN_REVOKE",method:"DELETE",suffix:"/admin/:userId",_payload:void 0,_success:void 0,_error:void 0},{key:"SESSIONS_APPROVE",method:"POST",suffix:"/approve",_payload:void 0,_success:void 0,_error:void 0},{key:"SESSIONS_REJECT",method:"POST",suffix:"/reject",_payload:void 0,_success:void 0,_error:void 0}]},magicLink:{key:"MAGIC_LINK",method:"POST",defaultRoute:"/auth/magic-link",defaultIsPublic:!0,subEndpoints:[{key:"MAGIC_LINK",method:"POST",suffix:"",_payload:void 0,_success:void 0,_error:void 0},{key:"MAGIC_LINK_VERIFY",method:"GET",suffix:"/verify",routeKey:"verifyRoute",isPublic:!0,_payload:void 0,_success:void 0,_error:void 0}]},invite:{key:"INVITE",method:"POST",defaultRoute:"/auth/invite",defaultIsPublic:!1,subEndpoints:[{key:"INVITE",method:"POST",suffix:"",_payload:void 0,_success:void 0,_error:void 0},{key:"INVITE_VERIFY",method:"POST",suffix:"/verify",isPublic:!0,_payload:void 0,_success:void 0,_error:void 0}]},emailVerification:{key:"VERIFY_EMAIL",method:"GET",defaultRoute:"/verify-email",defaultIsPublic:!0,subEndpoints:[{key:"VERIFY_EMAIL",method:"GET",suffix:"",_payload:void 0,_success:void 0,_error:void 0},{key:"RESEND_VERIFICATION",method:"POST",suffix:"",routeKey:"resendRoute",defaultRoute:"/resend-verification",_payload:void 0,_success:void 0,_error:void 0}]},captcha:{key:"CAPTCHA",method:"GET",defaultRoute:"/auth/captcha",defaultIsPublic:!0,subEndpoints:[{key:"CAPTCHA_GENERATE",method:"GET",suffix:"/generate",_payload:void 0,_success:void 0,_error:void 0},{key:"CAPTCHA_VALIDATE",method:"POST",suffix:"/validate",_payload:void 0,_success:void 0,_error:void 0}]},oauth:{key:"OAUTH_PROVIDERS",method:"GET",defaultRoute:"/auth/oauth",defaultIsPublic:!0,subEndpoints:[{key:"OAUTH_PROVIDERS",method:"GET",suffix:"/providers",_payload:void 0,_success:void 0,_error:void 0},{key:"OAUTH_REDIRECT",method:"GET",suffix:"/:provider",_payload:void 0,_success:void 0,_error:void 0},{key:"OAUTH_ACCOUNTS",method:"GET",suffix:"/accounts",_payload:void 0,_success:void 0,_error:void 0},{key:"OAUTH_UNLINK",method:"DELETE",suffix:"/unlink/:provider",_payload:void 0,_success:void 0,_error:void 0}]},webauthn:{key:"WEBAUTHN",method:"POST",defaultRoute:"/auth/webauthn",defaultIsPublic:!1,subEndpoints:[{key:"WEBAUTHN_REGISTER_OPTIONS",method:"POST",suffix:"/register/options",_payload:void 0,_success:void 0,_error:void 0},{key:"WEBAUTHN_REGISTER_VERIFY",method:"POST",suffix:"/register/verify",_payload:void 0,_success:void 0,_error:void 0},{key:"WEBAUTHN_AUTH_OPTIONS",method:"POST",suffix:"/authenticate/options",isPublic:!0,_payload:void 0,_success:void 0,_error:void 0},{key:"WEBAUTHN_AUTH_VERIFY",method:"POST",suffix:"/authenticate/verify",isPublic:!0,_payload:void 0,_success:void 0,_error:void 0},{key:"WEBAUTHN_LIST",method:"GET",suffix:"/credentials",_payload:void 0,_success:void 0,_error:void 0},{key:"WEBAUTHN_REVOKE",method:"DELETE",suffix:"/credentials/:credentialId",_payload:void 0,_success:void 0,_error:void 0},{key:"WEBAUTHN_RENAME",method:"PATCH",suffix:"/credentials/:credentialId",_payload:void 0,_success:void 0,_error:void 0}]},apiKeys:{key:"API_KEYS",method:"GET",defaultRoute:"/auth/api-keys",defaultIsPublic:!1,subEndpoints:[{key:"API_KEYS_CREATE",method:"POST",suffix:"",_payload:void 0,_success:void 0,_error:void 0},{key:"API_KEYS_LIST",method:"GET",suffix:"",_payload:void 0,_success:void 0,_error:void 0},{key:"API_KEYS_DETAIL",method:"GET",suffix:"/:id",_payload:void 0,_success:void 0,_error:void 0},{key:"API_KEYS_UPDATE",method:"PATCH",suffix:"/:id",_payload:void 0,_success:void 0,_error:void 0},{key:"API_KEYS_REVOKE",method:"DELETE",suffix:"/:id",_payload:void 0,_success:void 0,_error:void 0}]}},ADMIN_ENDPOINTS={ADMIN_IMPERSONATE:{method:"POST",path:"/auth/admin/impersonate",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},ADMIN_IMPERSONATE_STOP:{method:"POST",path:"/auth/admin/impersonate/stop",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},ADMIN_CHANGE_USER_ID:{method:"POST",path:"/auth/admin/change-user-id",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},ADMIN_CREATE_USER:{method:"POST",path:"/auth/admin/create-user",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},ADMIN_HARD_DELETE_USER:{method:"DELETE",path:"/auth/admin/hard-delete/:userId",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},ADMIN_LOCK_USER:{method:"POST",path:"/auth/admin/lock-user",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},ADMIN_UNLOCK_USER:{method:"POST",path:"/auth/admin/unlock-user",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},ADMIN_MARK_EMAIL_VERIFIED:{method:"POST",path:"/auth/admin/mark-email-verified",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0}},UNPUBLISHED_AUTH_KEYS=[];function publishedAuthEndpoints(){let all=deriveAuthEndpoints(AUTH_ENDPOINT_CONFIGS),excluded=new Set(UNPUBLISHED_AUTH_KEYS);return Object.fromEntries(Object.entries(all).filter(([key])=>!excluded.has(key)))}var AUTH_ENDPOINTS={...publishedAuthEndpoints(),...ADMIN_ENDPOINTS},CONFIG_ENDPOINTS={CONFIG_GET:{method:"GET",path:"/nucleus/config",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},CONFIG_SECTIONS:{method:"GET",path:"/nucleus/config/sections",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},CONFIG_SECTION_GET:{method:"GET",path:"/nucleus/config/:section",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},CONFIG_SECTION_UPDATE:{method:"PATCH",path:"/nucleus/config/:section",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},CONFIG_ENV:{method:"GET",path:"/nucleus/config/env",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},CONFIG_OVERRIDES_GET:{method:"GET",path:"/nucleus/config/overrides",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},CONFIG_OVERRIDES_CLEAR:{method:"DELETE",path:"/nucleus/config/overrides",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},CONFIG_RESTART:{method:"POST",path:"/nucleus/config/restart",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0}},VERIFICATION_ENDPOINTS={VERIFICATION_STATUS:{method:"GET",path:"/verification/status/:entity_name/:entity_id",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},VERIFICATION_DECIDE:{method:"POST",path:"/verification/decide/:entity_name/:entity_id",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},VERIFICATION_PENDING:{method:"GET",path:"/verification/pending",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},VERIFICATION_HISTORY:{method:"GET",path:"/verification/history/:entity_name/:entity_id",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},VERIFICATION_START:{method:"POST",path:"/verification/start",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},VERIFICATION_START_FOR_ENTITY:{method:"POST",path:"/verification/start-for-entity",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},VERIFICATION_ENTITY_STATUSES:{method:"GET",path:"/verification/entity-statuses/:entity_name",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},FLOW_LIST:{method:"GET",path:"/verification/flows",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},FLOW_GET:{method:"GET",path:"/verification/flows/:flow_id",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},FLOW_SAVE:{method:"POST",path:"/verification/flows",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},FLOW_PUBLISH:{method:"POST",path:"/verification/flows/:flow_id/publish",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},FLOW_DELETE:{method:"DELETE",path:"/verification/flows/:flow_id",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},NOTIFICATION_LIST:{method:"GET",path:"/notifications",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},NOTIFICATION_UNSEEN_COUNT:{method:"GET",path:"/notifications/unseen-count",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},NOTIFICATION_MARK_SEEN:{method:"POST",path:"/notifications/:notification_id/seen",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},NOTIFICATION_MARK_ALL_SEEN:{method:"POST",path:"/notifications/seen-all",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0}},CHAT_ENDPOINTS={CHAT_LIST_CONVERSATIONS:{method:"GET",path:"/chat/conversations",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},CHAT_CREATE_CONVERSATION:{method:"POST",path:"/chat/conversations",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},CHAT_GET_CONVERSATION:{method:"GET",path:"/chat/conversations/:id",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},CHAT_GET_MESSAGES:{method:"GET",path:"/chat/conversations/:id/messages",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},CHAT_SEND_MESSAGE:{method:"POST",path:"/chat/conversations/:id/messages",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},CHAT_MARK_READ:{method:"POST",path:"/chat/conversations/:id/read",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},CHAT_TYPING:{method:"POST",path:"/chat/conversations/:id/typing",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},CHAT_ADD_PARTICIPANTS:{method:"POST",path:"/chat/conversations/:id/participants",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},CHAT_REMOVE_PARTICIPANT:{method:"DELETE",path:"/chat/conversations/:id/participants/:userId",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},CHAT_EDIT_MESSAGE:{method:"PATCH",path:"/chat/messages/:id",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},CHAT_DELETE_MESSAGE:{method:"DELETE",path:"/chat/messages/:id",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0}},MONITORING_ENDPOINTS={MONITORING_HEALTH_CHECK:{method:"GET",path:"/monitoring/health",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},MONITORING_GET_SETTINGS:{method:"GET",path:"/monitoring/settings",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},MONITORING_CHANGE_SETTINGS:{method:"PATCH",path:"/monitoring/settings",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},MONITORING_GET_LOGS:{method:"GET",path:"/monitoring/logs",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0}},PAYMENT_ENDPOINTS={PAYMENT_INITIALIZE:{method:"POST",path:"/payments/initialize",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},PAYMENT_TRANSACTIONS:{method:"GET",path:"/payments/transactions",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},PAYMENT_TRANSACTION_DETAIL:{method:"GET",path:"/payments/transactions/:id",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},PAYMENT_BIN_QUERY:{method:"POST",path:"/payments/bin-query",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},PAYMENT_DETAIL:{method:"POST",path:"/payments/payment-detail",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},PAYMENT_REFUND:{method:"POST",path:"/payments/refund",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},PAYMENT_SAVE_CARD:{method:"POST",path:"/payments/cards/save",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},PAYMENT_LIST_CARDS:{method:"GET",path:"/payments/cards",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},PAYMENT_DELETE_CARD:{method:"DELETE",path:"/payments/cards/:id",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},PAYMENT_SYNC_CARDS:{method:"POST",path:"/payments/cards/sync",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},PAYMENT_REGISTER_SUB_MERCHANT:{method:"POST",path:"/payments/sub-merchants",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},PAYMENT_UPDATE_SUB_MERCHANT:{method:"PUT",path:"/payments/sub-merchants/:id",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},PAYMENT_LIST_SUB_MERCHANTS:{method:"GET",path:"/payments/sub-merchants",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},PAYMENT_GET_SUB_MERCHANT:{method:"GET",path:"/payments/sub-merchants/:id",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},PAYMENT_LIST_SPLITS:{method:"GET",path:"/payments/splits",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},PAYMENT_APPROVE_SPLIT:{method:"POST",path:"/payments/splits/:splitId/approve",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},PAYMENT_DISAPPROVE_SPLIT:{method:"POST",path:"/payments/splits/:splitId/disapprove",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},PAYMENT_CREATE_PRODUCT:{method:"POST",path:"/payments/products",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},PAYMENT_UPDATE_PRODUCT:{method:"PUT",path:"/payments/products/:id",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},PAYMENT_LIST_PRODUCTS:{method:"GET",path:"/payments/products",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},PAYMENT_GET_PRODUCT:{method:"GET",path:"/payments/products/:id",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},PAYMENT_ARCHIVE_PRODUCT:{method:"DELETE",path:"/payments/products/:id",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},PAYMENT_CREATE_PRICE:{method:"POST",path:"/payments/prices",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},PAYMENT_UPDATE_PRICE:{method:"PUT",path:"/payments/prices/:id",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},PAYMENT_LIST_PRICES:{method:"GET",path:"/payments/prices",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},PAYMENT_GET_PRICE:{method:"GET",path:"/payments/prices/:id",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},PAYMENT_ARCHIVE_PRICE:{method:"DELETE",path:"/payments/prices/:id",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},PAYMENT_CREATE_CUSTOMER:{method:"POST",path:"/payments/customers",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},PAYMENT_UPDATE_CUSTOMER:{method:"PUT",path:"/payments/customers/:id",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},PAYMENT_LIST_CUSTOMERS:{method:"GET",path:"/payments/customers",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},PAYMENT_GET_CUSTOMER:{method:"GET",path:"/payments/customers/:id",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},PAYMENT_DELETE_CUSTOMER:{method:"DELETE",path:"/payments/customers/:id",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},PAYMENT_CREATE_SUBSCRIPTION:{method:"POST",path:"/payments/subscriptions",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},PAYMENT_UPDATE_SUBSCRIPTION:{method:"PUT",path:"/payments/subscriptions/:id",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},PAYMENT_CANCEL_SUBSCRIPTION:{method:"POST",path:"/payments/subscriptions/:id/cancel",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},PAYMENT_LIST_SUBSCRIPTIONS:{method:"GET",path:"/payments/subscriptions",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},PAYMENT_GET_SUBSCRIPTION:{method:"GET",path:"/payments/subscriptions/:id",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},PAYMENT_REPORT_USAGE:{method:"POST",path:"/payments/usage-records",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},PAYMENT_LIST_USAGE_SUMMARIES:{method:"GET",path:"/payments/usage-records/:subscriptionItemId/summaries",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},PAYMENT_CREATE_INVOICE:{method:"POST",path:"/payments/invoices",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},PAYMENT_FINALIZE_INVOICE:{method:"POST",path:"/payments/invoices/:id/finalize",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},PAYMENT_SEND_INVOICE:{method:"POST",path:"/payments/invoices/:id/send",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},PAYMENT_VOID_INVOICE:{method:"POST",path:"/payments/invoices/:id/void",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},PAYMENT_PAY_INVOICE:{method:"POST",path:"/payments/invoices/:id/pay",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},PAYMENT_LIST_INVOICES:{method:"GET",path:"/payments/invoices",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},PAYMENT_GET_INVOICE:{method:"GET",path:"/payments/invoices/:id",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},PAYMENT_GET_BALANCE:{method:"GET",path:"/payments/balance",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},PAYMENT_CREATE_PAYOUT:{method:"POST",path:"/payments/payouts",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},PAYMENT_LIST_PAYOUTS:{method:"GET",path:"/payments/payouts",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},PAYMENT_GET_PAYOUT:{method:"GET",path:"/payments/payouts/:id",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},PAYMENT_CANCEL_PAYOUT:{method:"POST",path:"/payments/payouts/:id/cancel",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},PAYMENT_CREATE_TRANSFER:{method:"POST",path:"/payments/transfers",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},PAYMENT_LIST_TRANSFERS:{method:"GET",path:"/payments/transfers",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0}},TENANT_ENDPOINTS={TENANT_CHECK_SUBDOMAIN:{method:"GET",path:"/tenants/check-subdomain/:subdomain",isPublic:!0,_payload:void 0,_success:void 0,_error:void 0},TENANT_PROVISION:{method:"POST",path:"/tenants/provision",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},TENANT_LIST:{method:"GET",path:"/tenants",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},TENANT_DETAIL:{method:"GET",path:"/tenants/:id",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},TENANT_SUSPEND:{method:"POST",path:"/tenants/:id/suspend",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},TENANT_REACTIVATE:{method:"POST",path:"/tenants/:id/reactivate",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},TENANT_SELF_SIGNUP:{method:"POST",path:"/tenants/self-signup",isPublic:!0,_payload:void 0,_success:void 0,_error:void 0}},DOMAIN_ENDPOINTS={DOMAIN_RESOLVE:{method:"GET",path:"/domains/resolve",isPublic:!0,_payload:void 0,_success:void 0,_error:void 0},DOMAIN_CREATE_HOSTNAME:{method:"POST",path:"/domains/hostnames",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},DOMAIN_LIST_HOSTNAMES:{method:"GET",path:"/domains/hostnames",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},DOMAIN_GET_HOSTNAME:{method:"GET",path:"/domains/hostnames/:id",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},DOMAIN_HOSTNAME_INSTRUCTIONS:{method:"GET",path:"/domains/hostnames/:id/instructions",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},DOMAIN_VERIFY_HOSTNAME:{method:"POST",path:"/domains/hostnames/:id/verify",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},DOMAIN_ACTIVATE_HOSTNAME:{method:"POST",path:"/domains/hostnames/:id/activate",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},DOMAIN_DISABLE_HOSTNAME:{method:"POST",path:"/domains/hostnames/:id/disable",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},DOMAIN_MAKE_PRIMARY:{method:"POST",path:"/domains/hostnames/:id/make-primary",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},DOMAIN_REFRESH_HOSTNAME:{method:"POST",path:"/domains/hostnames/:id/refresh-status",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},DOMAIN_CREATE_REGISTRATION:{method:"POST",path:"/domains/registrations",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},DOMAIN_LIST_REGISTRATIONS:{method:"GET",path:"/domains/registrations",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},DOMAIN_GET_REGISTRATION:{method:"GET",path:"/domains/registrations/:id",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},DOMAIN_CANCEL_REGISTRATION:{method:"POST",path:"/domains/registrations/:id/cancel",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},DOMAIN_TRANSFER_OUT:{method:"POST",path:"/domains/registrations/:id/transfer-out",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0}},MARKETPLACE_ENDPOINTS={PAYMENT_MARKETPLACE_RECORD_SPLIT:{method:"POST",path:"/payment/marketplace/splits",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},PAYMENT_MARKETPLACE_LIST_SPLITS:{method:"GET",path:"/payment/marketplace/splits",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},PAYMENT_MARKETPLACE_BALANCE:{method:"GET",path:"/payment/marketplace/balance/:ownerId",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},PAYMENT_MARKETPLACE_RELEASE_RESERVE:{method:"POST",path:"/payment/marketplace/reserves/:id/release",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},PAYMENT_MARKETPLACE_GET_SETTLEMENT_POLICY:{method:"GET",path:"/payment/marketplace/settlement-policy",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},PAYMENT_MARKETPLACE_UPSERT_SETTLEMENT_POLICY:{method:"POST",path:"/payment/marketplace/settlement-policy",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},PAYMENT_MARKETPLACE_REQUEST_PAYOUT:{method:"POST",path:"/payment/marketplace/payout-requests",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},PAYMENT_MARKETPLACE_LIST_PAYOUTS:{method:"GET",path:"/payment/marketplace/payout-requests",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},PAYMENT_MARKETPLACE_GET_PAYOUT:{method:"GET",path:"/payment/marketplace/payout-requests/:id",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},PAYMENT_MARKETPLACE_APPROVE_PAYOUT:{method:"POST",path:"/payment/marketplace/payout-requests/:id/approve",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},PAYMENT_MARKETPLACE_REJECT_PAYOUT:{method:"POST",path:"/payment/marketplace/payout-requests/:id/reject",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},PAYMENT_MARKETPLACE_OPEN_DISPUTE:{method:"POST",path:"/payment/marketplace/disputes",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},PAYMENT_MARKETPLACE_LIST_DISPUTES:{method:"GET",path:"/payment/marketplace/disputes",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},PAYMENT_MARKETPLACE_RESOLVE_DISPUTE:{method:"POST",path:"/payment/marketplace/disputes/:id/resolve",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0}},COHORT_ENDPOINTS={ADMIN_LIST_COHORTS:{method:"GET",path:"/auth/admin/cohorts",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},ADMIN_GET_COHORT:{method:"GET",path:"/auth/admin/cohorts/:id",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},ADMIN_CREATE_COHORT:{method:"POST",path:"/auth/admin/cohorts",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},ADMIN_UPDATE_COHORT:{method:"PATCH",path:"/auth/admin/cohorts/:id",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},ADMIN_DELETE_COHORT:{method:"DELETE",path:"/auth/admin/cohorts/:id",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},ADMIN_COHORT_BULK_CREATE:{method:"POST",path:"/auth/admin/cohorts/:id/bulk-create",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},ADMIN_COHORT_DEACTIVATE:{method:"POST",path:"/auth/admin/cohorts/:id/deactivate-users",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},ADMIN_COHORT_ACTIVATE:{method:"POST",path:"/auth/admin/cohorts/:id/activate-users",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},ADMIN_COHORT_DELETE_USERS:{method:"POST",path:"/auth/admin/cohorts/:id/delete-users",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},ADMIN_COHORT_EXPORT:{method:"GET",path:"/auth/admin/cohorts/:id/export",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0}};var SYSTEM_TABLE_NAMES=["profiles","addresses","phones","files","users","roles","claims","user_roles","role_claims","audit_logs","monitoring_metrics","integration_sources","integration_endpoints","integration_mappings","integration_runs","integration_run_items"],systemTables=system_tables_default.tables.filter((t)=>SYSTEM_TABLE_NAMES.includes(t.table_name));function toUpperSnakeCase(str){return str.replace(/([a-z])([A-Z])/g,"$1_$2").replace(/[\s-]+/g,"_").toUpperCase()}function snakeToCamelCase(str){return str.replace(/_([a-z])/g,(_,letter)=>letter.toUpperCase())}function singularize(str){if(str.endsWith("ies"))return`${str.slice(0,-3)}y`;if(str.endsWith("ses"))return`${str.slice(0,-2)}`;if(str.endsWith("s"))return str.slice(0,-1);return str}function generateEntityEndpointKey(tableName,method){let upperName=toUpperSnakeCase(tableName),singularName=toUpperSnakeCase(singularize(tableName));switch(method){case"GET":return`GET_${upperName}`;case"POST":return`ADD_${singularName}`;case"PUT":return`UPDATE_${singularName}`;case"PATCH":return`PATCH_${singularName}`;case"DELETE":return`DELETE_${singularName}`}}function generateBulkEndpointKey(tableName,method){let upperName=toUpperSnakeCase(tableName);switch(method){case"POST":return`BULK_ADD_${upperName}`;case"PUT":return`BULK_UPDATE_${upperName}`;case"DELETE":return`BULK_DELETE_${upperName}`}}function isMethodExcluded(table,method){if(!table.excluded_methods)return!1;let methodMap={GET:"GET",POST:"POST",PUT:"PUT",PATCH:"PATCH",DELETE:"DELETE"};return table.excluded_methods.includes(methodMap[method])}function generateEndpointsFromConfig(config){let endpoints={};for(let table of config.entities){let tableName=table.table_name,basePath=`/${snakeToCamelCase(tableName)}`,sid=table.serviceId;if(!isMethodExcluded(table,"GET")){endpoints[generateEntityEndpointKey(tableName,"GET")]={method:"GET",path:basePath,isPublic:table.is_public?.GET??!1,serviceId:sid,_payload:void 0,_success:void 0,_error:void 0};let singularName=toUpperSnakeCase(singularize(tableName));endpoints[`GET_${singularName}_BY_ID`]={method:"GET",path:`${basePath}/:id`,isPublic:table.is_public?.GET??!1,serviceId:sid,_payload:void 0,_success:void 0,_error:void 0},endpoints[`GET_${toUpperSnakeCase(tableName)}_DISTINCT`]={method:"GET",path:`${basePath}/distinct/:field`,isPublic:table.is_public?.GET??!1,serviceId:sid,_payload:void 0,_success:void 0,_error:void 0}}if(!isMethodExcluded(table,"POST"))endpoints[generateEntityEndpointKey(tableName,"POST")]={method:"POST",path:basePath,isPublic:table.is_public?.POST??!1,isFormData:table.is_form_data,serviceId:sid,_payload:void 0,_success:void 0,_error:void 0};if(!isMethodExcluded(table,"PUT"))endpoints[generateEntityEndpointKey(tableName,"PUT")]={method:"PUT",path:`${basePath}/:id`,isPublic:table.is_public?.PUT??!1,isFormData:table.is_form_data,serviceId:sid,_payload:void 0,_success:void 0,_error:void 0};if(!isMethodExcluded(table,"PATCH"))endpoints[generateEntityEndpointKey(tableName,"PATCH")]={method:"PATCH",path:`${basePath}/:id`,isPublic:table.is_public?.PATCH??!1,serviceId:sid,_payload:void 0,_success:void 0,_error:void 0};if(!isMethodExcluded(table,"DELETE"))endpoints[generateEntityEndpointKey(tableName,"DELETE")]={method:"DELETE",path:`${basePath}/:id`,isPublic:table.is_public?.DELETE??!1,serviceId:sid,_payload:void 0,_success:void 0,_error:void 0};if(table.bulk_endpoints_enabled){if(!isMethodExcluded(table,"POST"))endpoints[generateBulkEndpointKey(tableName,"POST")]={method:"POST",path:`${basePath}/bulk`,isPublic:table.is_public?.POST??!1,serviceId:sid,_payload:void 0,_success:void 0,_error:void 0};if(!isMethodExcluded(table,"PUT"))endpoints[generateBulkEndpointKey(tableName,"PUT")]={method:"PUT",path:`${basePath}/bulk`,isPublic:table.is_public?.PUT??!1,serviceId:sid,_payload:void 0,_success:void 0,_error:void 0};if(!isMethodExcluded(table,"DELETE"))endpoints[generateBulkEndpointKey(tableName,"DELETE")]={method:"DELETE",path:`${basePath}/bulk`,isPublic:table.is_public?.DELETE??!1,serviceId:sid,_payload:void 0,_success:void 0,_error:void 0}}}return endpoints}function generateAuthEndpoints(config){let endpoints={},auth=config.authentication;if(!auth?.enabled)return endpoints;for(let[featureKey,endpointConfig]of Object.entries(AUTH_ENDPOINT_CONFIGS)){let feature=auth[featureKey];if(!feature?.enabled)continue;let feat=feature,baseRoute=feat.route||endpointConfig.defaultRoute,isPublic=feat.isPublic??endpointConfig.defaultIsPublic;if("subEndpoints"in endpointConfig&&endpointConfig.subEndpoints)for(let sub of endpointConfig.subEndpoints){let subRoute="routeKey"in sub&&sub.routeKey&&feature[sub.routeKey]?String(feature[sub.routeKey]):("defaultRoute"in sub)&&sub.defaultRoute?String(sub.defaultRoute):`${baseRoute}${sub.suffix}`,subIsPublic="isPublic"in sub&&typeof sub.isPublic==="boolean"?sub.isPublic:isPublic;endpoints[sub.key]={method:sub.method,path:subRoute,isPublic:subIsPublic,_payload:sub._payload,_success:sub._success,_error:sub._error}}else if("_payload"in endpointConfig)endpoints[endpointConfig.key]={method:endpointConfig.method,path:baseRoute,isPublic,_payload:endpointConfig._payload,_success:endpointConfig._success,_error:endpointConfig._error}}return endpoints}function generateSystemTableEndpoints(){let endpoints={};for(let table of systemTables){let tableName=table.table_name,basePath=`/${snakeToCamelCase(tableName)}`,isFormData=tableName==="files";endpoints[generateEntityEndpointKey(tableName,"GET")]={method:"GET",path:basePath,isPublic:!1,_payload:void 0,_success:void 0,_error:void 0};let singularName=toUpperSnakeCase(singularize(tableName));if(endpoints[`GET_${singularName}_BY_ID`]={method:"GET",path:`${basePath}/:id`,isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},!isMethodExcluded(table,"POST"))endpoints[generateEntityEndpointKey(tableName,"POST")]={method:"POST",path:basePath,isPublic:!1,isFormData,_payload:void 0,_success:void 0,_error:void 0};if(!isMethodExcluded(table,"PUT"))endpoints[generateEntityEndpointKey(tableName,"PUT")]={method:"PUT",path:`${basePath}/:id`,isPublic:!1,isFormData,_payload:void 0,_success:void 0,_error:void 0};if(!isMethodExcluded(table,"PATCH"))endpoints[generateEntityEndpointKey(tableName,"PATCH")]={method:"PATCH",path:`${basePath}/:id`,isPublic:!1,_payload:void 0,_success:void 0,_error:void 0};if(!isMethodExcluded(table,"DELETE"))endpoints[generateEntityEndpointKey(tableName,"DELETE")]={method:"DELETE",path:`${basePath}/:id`,isPublic:!1,_payload:void 0,_success:void 0,_error:void 0};if(table.bulk_endpoints_enabled)endpoints[generateBulkEndpointKey(tableName,"POST")]={method:"POST",path:`${basePath}/bulk`,isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},endpoints[generateBulkEndpointKey(tableName,"PUT")]={method:"PUT",path:`${basePath}/bulk`,isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},endpoints[generateBulkEndpointKey(tableName,"DELETE")]={method:"DELETE",path:`${basePath}/bulk`,isPublic:!1,_payload:void 0,_success:void 0,_error:void 0}}return endpoints}function generateMonitoringEndpoints(config){if(!config.liveMonitoring?.enabled)return{};return{...MONITORING_ENDPOINTS}}function generateAdminEndpoints(config){let endpoints={};if(!config.authentication?.enabled)return endpoints;return endpoints.ADMIN_IMPERSONATE={method:"POST",path:"/auth/admin/impersonate",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},endpoints.ADMIN_IMPERSONATE_STOP={method:"POST",path:"/auth/admin/impersonate/stop",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},endpoints.ADMIN_CHANGE_USER_ID={method:"POST",path:"/auth/admin/change-user-id",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},endpoints.ADMIN_CREATE_USER={method:"POST",path:"/auth/admin/create-user",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},endpoints.ADMIN_HARD_DELETE_USER={method:"DELETE",path:"/auth/admin/hard-delete/:userId",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},endpoints.ADMIN_LOCK_USER={method:"POST",path:"/auth/admin/lock-user",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},endpoints.ADMIN_UNLOCK_USER={method:"POST",path:"/auth/admin/unlock-user",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},endpoints.ADMIN_MARK_EMAIL_VERIFIED={method:"POST",path:"/auth/admin/mark-email-verified",isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},endpoints}function generateCohortEndpoints(config){let auth=config.authentication;if(!auth?.enabled||!auth.cohorts?.enabled)return{};return{...COHORT_ENDPOINTS}}function generateVerificationEndpoints(config){let endpoints={},verification=config.verification;if(!verification?.enabled)return endpoints;let basePath=verification.endpoints?.basePath||"/verifications",flowBasePath=verification.flowEndpoints?.basePath||"/verification-flows";if(endpoints.VERIFICATION_STATUS={method:"GET",path:`${basePath}/status/:entity_name/:entity_id`,isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},endpoints.VERIFICATION_DECIDE={method:"POST",path:`${basePath}/:entity_name/:entity_id/decide`,isPublic:!1,skipCamelCase:!0,_payload:void 0,_success:void 0,_error:void 0},endpoints.VERIFICATION_PENDING={method:"GET",path:`${basePath}/pending`,isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},endpoints.VERIFICATION_HISTORY={method:"GET",path:`${basePath}/history/:entity_name/:entity_id`,isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},endpoints.VERIFICATION_START={method:"POST",path:`${basePath}/start`,isPublic:!1,skipCamelCase:!0,_payload:void 0,_success:void 0,_error:void 0},endpoints.VERIFICATION_START_FOR_ENTITY={method:"POST",path:`${basePath}/start-for-entity`,isPublic:!1,skipCamelCase:!0,_payload:void 0,_success:void 0,_error:void 0},endpoints.VERIFICATION_ENTITY_STATUSES={method:"GET",path:`${basePath}/statuses/:entity_name`,isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},verification.flowEndpoints?.enabled)endpoints.FLOW_LIST={method:"GET",path:flowBasePath,isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},endpoints.FLOW_GET={method:"GET",path:`${flowBasePath}/:flow_id`,isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},endpoints.FLOW_SAVE={method:"POST",path:flowBasePath,isPublic:!1,skipCamelCase:!0,_payload:void 0,_success:void 0,_error:void 0},endpoints.FLOW_PUBLISH={method:"POST",path:`${flowBasePath}/:flow_id/publish`,isPublic:!1,skipCamelCase:!0,_payload:void 0,_success:void 0,_error:void 0},endpoints.FLOW_DELETE={method:"DELETE",path:`${flowBasePath}/:flow_id`,isPublic:!1,_payload:void 0,_success:void 0,_error:void 0};return endpoints}function generateNotificationEndpoints(config){let endpoints={},notification=config.notification;if(!notification?.enabled||notification.endpoints?.enabled===!1)return endpoints;let notifBasePath=notification.endpoints?.basePath||"/notifications";return endpoints.NOTIFICATION_LIST={method:"GET",path:notifBasePath,isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},endpoints.NOTIFICATION_UNSEEN_COUNT={method:"GET",path:`${notifBasePath}/unseen-count`,isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},endpoints.NOTIFICATION_MARK_SEEN={method:"POST",path:`${notifBasePath}/:notification_id/seen`,isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},endpoints.NOTIFICATION_MARK_ALL_SEEN={method:"POST",path:`${notifBasePath}/seen-all`,isPublic:!1,_payload:void 0,_success:void 0,_error:void 0},endpoints}function generatePaymentEndpoints(config){if(!config.payment?.enabled)return{};return{...PAYMENT_ENDPOINTS}}function generateConfigEndpoints(config){if(!config.configManagement?.enabled)return{};return{...CONFIG_ENDPOINTS}}function generateTenantEndpoints(config){if(!config.database?.isMultiTenant)return{};return{...TENANT_ENDPOINTS}}function generateChatEndpoints(config){if(!config.chat?.enabled)return{};return{...CHAT_ENDPOINTS}}function generateDomainEndpoints(config){if(!config.domains?.enabled)return{};return{...DOMAIN_ENDPOINTS}}function generateMarketplaceEndpoints(config){let payment=config.payment;if(!payment?.enabled||!payment.marketplace?.enabled)return{};return{...MARKETPLACE_ENDPOINTS}}function generateAllEndpoints(config,extraEndpoints){let entityEndpoints=generateEndpointsFromConfig(config),authEndpoints=generateAuthEndpoints(config),adminEndpoints=generateAdminEndpoints(config),cohortEndpoints=generateCohortEndpoints(config),systemEndpoints=generateSystemTableEndpoints(),monitoringEndpoints=generateMonitoringEndpoints(config),verificationEndpoints=generateVerificationEndpoints(config),chatEndpoints=generateChatEndpoints(config),tenantEndpoints=generateTenantEndpoints(config),domainEndpoints=generateDomainEndpoints(config),marketplaceEndpoints=generateMarketplaceEndpoints(config),notificationEndpoints=generateNotificationEndpoints(config),paymentEndpoints=generatePaymentEndpoints(config),configEndpoints=generateConfigEndpoints(config);return{...entityEndpoints,...authEndpoints,...adminEndpoints,...cohortEndpoints,...systemEndpoints,...monitoringEndpoints,...verificationEndpoints,...chatEndpoints,...tenantEndpoints,...domainEndpoints,...marketplaceEndpoints,...notificationEndpoints,...paymentEndpoints,...configEndpoints,...extraEndpoints??{}}}init_Logger();var DEFAULT_CONFIG2={timeout:30000,retries:0,retryDelay:1000,debug:!1};class ServerFetch{config;logger;constructor(config={}){this.config={...DEFAULT_CONFIG2,...config},this.logger=new Logger({service:"ServerFetch",prettyPrint:this.config.debug,colorize:this.config.debug,auditEnabled:!1})}buildUrl(url){if(url.startsWith("http://")||url.startsWith("https://"))return url;return this.config.baseUrl?`${this.config.baseUrl}${url}`:url}buildHeaders(customHeaders){let headers=new Headers;if(this.config.defaultHeaders)for(let[key,value]of Object.entries(this.config.defaultHeaders))headers.set(key,value);if(customHeaders)if(customHeaders instanceof Headers)customHeaders.forEach((value,key)=>{headers.set(key,value)});else if(Array.isArray(customHeaders))for(let[key,value]of customHeaders)headers.set(key,value);else for(let[key,value]of Object.entries(customHeaders))headers.set(key,value);return headers}parseResponseHeaders(headers){let result={};if(headers.forEach((value,key)=>{if(key.toLowerCase()==="set-cookie"){let existing=result[key];result[key]=existing?`${existing}, ${value}`:value}else result[key]=value}),typeof headers.getSetCookie==="function"){let setCookies=headers.getSetCookie();if(setCookies.length>0)result["set-cookie"]=setCookies.join(", ")}return result}async executeWithTimeout(promise,timeoutMs,_requestId){let controller=new AbortController,timeoutId=setTimeout(()=>controller.abort(),timeoutMs);try{return await Promise.race([promise,new Promise((_,reject)=>{controller.signal.addEventListener("abort",()=>{reject(Error(`Request timeout after ${timeoutMs}ms`))})})])}finally{clearTimeout(timeoutId)}}async fetch(options){let requestId=randomId(),startTime=performance.now(),url=this.buildUrl(options.url),headers=this.buildHeaders(options.headers),timeout=options.timeout??this.config.timeout??30000,retries=options.retries??this.config.retries??0,retryDelay=options.retryDelay??this.config.retryDelay??1000,body;if(options.body)if(typeof options.body==="object"&&!(options.body instanceof FormData)&&!(options.body instanceof URLSearchParams)&&!(options.body instanceof Blob)&&!(options.body instanceof ArrayBuffer)){if(body=JSON.stringify(options.body),!headers.has("content-type"))headers.set("content-type","application/json")}else body=options.body;this.logger.debug(`[${requestId}] ${options.method} ${url}`,{method:options.method,url,hasBody:!!body});let lastError=null,attempt=0;while(attempt<=retries)try{let response=await this.executeWithTimeout(fetch(url,{method:options.method,headers,body}),timeout,requestId),durationMs2=performance.now()-startTime,responseHeaders=this.parseResponseHeaders(response.headers),rawSetCookies=typeof response.headers.getSetCookie==="function"?response.headers.getSetCookie():[],responseData,errorData,rawText=await response.text();if(rawText)try{let json=JSON.parse(rawText);if(response.ok)responseData=json;else errorData=json}catch{if(!response.ok)errorData={message:rawText||response.statusText}}else if(!response.ok)errorData={message:response.statusText};let result={isSuccess:response.ok,response:responseData,errors:errorData,code:response.status,headers:responseHeaders,rawSetCookies,durationMs:durationMs2,requestId,createdAt:new Date};if(response.ok)this.logger.info(`[${requestId}] ${options.method} ${url} ${response.status}`,{method:options.method,url,statusCode:response.status,durationMs:Math.round(durationMs2)});else this.logger.warn(`[${requestId}] ${options.method} ${url} ${response.status}`,{method:options.method,url,statusCode:response.status,durationMs:Math.round(durationMs2),error:errorData});return result}catch(error){if(lastError=error instanceof Error?error:Error(String(error)),attempt++,attempt<=retries)this.logger.warn(`[${requestId}] Retry ${attempt}/${retries} after error`,{method:options.method,url,error:lastError.message,attempt,retries}),await new Promise((resolve)=>setTimeout(resolve,retryDelay))}let durationMs=performance.now()-startTime;return this.logger.error(`[${requestId}] ${options.method} ${url} failed`,lastError,{method:options.method,url,durationMs:Math.round(durationMs),attempts:attempt}),{isSuccess:!1,response:void 0,errors:{message:lastError?.message||"Unknown error"},code:null,headers:{},rawSetCookies:[],durationMs,requestId,createdAt:new Date}}async get(url,options){return this.fetch({...options,url,method:"GET"})}async post(url,body,options){return this.fetch({...options,url,method:"POST",body})}async put(url,body,options){return this.fetch({...options,url,method:"PUT",body})}async patch(url,body,options){return this.fetch({...options,url,method:"PATCH",body})}async delete(url,options){return this.fetch({...options,url,method:"DELETE"})}}var serverFetch=new ServerFetch;var RESPONSE_CONTROL_FLAGS=["requiresCaptcha","requiresApproval","attemptsRemaining","requestId"];function serverRequestId(response){let fromBody=(value)=>{if(!value||typeof value!=="object")return;let id=value.requestId;return typeof id==="string"&&id.length>0?id:void 0};return fromBody(response.errors)??fromBody(response.response)??response.headers?.["x-request-id"]??void 0}function normalizeResponseShape(response){if(!response||typeof response!=="object"||Array.isArray(response))return response;let raw=response;if(!("success"in raw)||"data"in raw)return response;let{success,isSuccess,message,error,...rest}=raw,controlFlags={};for(let flag of RESPONSE_CONTROL_FLAGS)if(flag in rest)controlFlags[flag]=rest[flag],delete rest[flag];return{success,...isSuccess!==void 0?{isSuccess}:{},...message!==void 0?{message}:{},...error!==void 0?{error}:{},...controlFlags,...Object.keys(rest).length>0?{data:rest}:{}}}var DEFAULT_TOKEN_NAMES={accessToken:"access_token",refreshToken:"refresh_token",sessionToken:"session_token"};function splitSetCookieHeader(header){let cookies=[],current="";for(let i=0;i<header.length;i++){let char=header[i];if(char===","){let next=header.slice(i+1).trimStart();if(/^[a-zA-Z0-9_-]+=/.test(next)){cookies.push(current.trim()),current="";continue}}current+=char}if(current.trim())cookies.push(current.trim());return cookies}function buildRequestHeaders(headersStore,tokenNames){let headers={},forwardHeaders=["x-forwarded-for","x-real-ip","user-agent","accept-language","x-request-id","x-client-ip","cf-connecting-ip","true-client-ip","x-tenant-id","x-app-origin"];for(let header of forwardHeaders){let value=headersStore.get(header);if(value)headers[header]=value}if(!headers["user-agent"])headers["user-agent"]="Nucleus-ServerAction/1.0";let accessToken=headersStore.get(`x-${tokenNames.accessToken}`),refreshToken=headersStore.get(`x-${tokenNames.refreshToken}`),sessionToken=headersStore.get(`x-${tokenNames.sessionToken}`);if(accessToken)headers[`x-${tokenNames.accessToken}`]=accessToken;if(refreshToken)headers[`x-${tokenNames.refreshToken}`]=refreshToken;if(sessionToken)headers[`x-${tokenNames.sessionToken}`]=sessionToken;let cookieHeader=headersStore.get("cookie");if(cookieHeader)headers.cookie=cookieHeader;return headers}function toCamelCase(str){return str.replace(/_([a-z])/g,(_,c)=>c.toUpperCase())}function convertKeysToCamelCase(obj){let result={};for(let[key,value]of Object.entries(obj)){let camelKey=key.startsWith("_")?key:toCamelCase(key);if(value instanceof Date)result[camelKey]=value.toISOString();else if(value&&typeof value==="object"&&!Array.isArray(value))result[camelKey]=convertKeysToCamelCase(value);else result[camelKey]=value}return result}var JSON_STRINGIFY_KEYS=new Set(["filters","sort","with","searchFields","select","distinctOn"]);function appendQueryParams(url,params){let searchParams=new URLSearchParams;for(let[key,value]of Object.entries(params)){if(value===void 0||value===null)continue;if(JSON_STRINGIFY_KEYS.has(key)&&typeof value==="object"){searchParams.append(key,JSON.stringify(value));continue}if(value instanceof Date)searchParams.append(key,value.toISOString());else if(Array.isArray(value))searchParams.append(key,value.filter((v)=>v!=null).map(String).join(","));else if(typeof value==="object")searchParams.append(key,JSON.stringify(value));else searchParams.append(key,String(value))}let queryString=searchParams.toString();if(!queryString)return url;return url.includes("?")?`${url}&${queryString}`:`${url}?${queryString}`}function createServerFactory(endpoints,config,getCookies,getHeaders){let tokenNames={...DEFAULT_TOKEN_NAMES,...config.tokenNames},serverFetch2=new ServerFetch({baseUrl:config.baseUrl,debug:config.debug,timeout:30000,retries:0});return async function(endpointKey,payload){let endpoint=endpoints[endpointKey];if(!endpoint)return{isSuccess:!1,errors:{message:`Endpoint "${endpointKey}" not found`},code:404};let cookieStore=await getCookies(),headersStore=await getHeaders(),allHeaders={};if(headersStore.forEach((value,key)=>{allHeaders[key]=value}),(endpoint.path.includes("/auth/login")||endpoint.path.includes("/auth/oauth"))&&endpoint.method==="POST")try{cookieStore.delete(tokenNames.accessToken),cookieStore.delete(tokenNames.refreshToken),cookieStore.delete(tokenNames.sessionToken)}catch(_){}let headers=buildRequestHeaders(headersStore,tokenNames),url=endpoint.path,body,pathParamNames=new Set,pathParamRegex=/:([a-zA-Z_][a-zA-Z0-9_]*)/g,paramMatch=pathParamRegex.exec(url);while(paramMatch!==null){if(paramMatch[1])pathParamNames.add(paramMatch[1]);paramMatch=pathParamRegex.exec(url)}if(payload&&typeof payload==="object"&&!(payload instanceof FormData)){let payloadObj=payload;for(let[key,value]of Object.entries(payloadObj))if(value!=null){if(pathParamNames.has(key))url=url.replace(`:${key}`,String(value));else if(key.startsWith("_")&&pathParamNames.has(key.substring(1)))url=url.replace(`:${key.substring(1)}`,String(value));else if(key==="id"&&pathParamNames.has("id"))url=url.replace(":id",String(value))}}if(endpoint.method==="GET"&&payload&&typeof payload==="object"){let queryPayload={...payload};for(let paramName of pathParamNames)delete queryPayload[paramName],delete queryPayload[`_${paramName}`];url=appendQueryParams(url,queryPayload)}else if(payload!==void 0){if(endpoint.isFormData&&payload instanceof FormData)body=payload;else if(Array.isArray(payload)){if(body=payload.map((item)=>item&&typeof item==="object"?convertKeysToCamelCase(item):item),!headers["content-type"])headers["content-type"]="application/json"}else if(body=endpoint.skipCamelCase?payload:convertKeysToCamelCase(payload),!headers["content-type"])headers["content-type"]="application/json"}let response=await serverFetch2.fetch({url,method:endpoint.method,headers,body}),setCookiesToProcess=response.rawSetCookies.length>0?response.rawSetCookies:response.headers["set-cookie"]?splitSetCookieHeader(response.headers["set-cookie"]):[];if(setCookiesToProcess.length>0)try{for(let cookie of setCookiesToProcess){let[nameValue,...options]=cookie.split(";");if(!nameValue)continue;let eqIdx=nameValue.indexOf("=");if(eqIdx===-1)continue;let name=nameValue.substring(0,eqIdx).trim(),value=nameValue.substring(eqIdx+1).trim();if(name&&value){let cookieOptions={};for(let opt of options){let trimmed=opt.trim(),optEqIdx=trimmed.indexOf("="),optName=optEqIdx===-1?trimmed:trimmed.substring(0,optEqIdx),optValue=optEqIdx===-1?void 0:trimmed.substring(optEqIdx+1);if(!optName)continue;let optNameLower=optName.toLowerCase();if(optNameLower==="path")cookieOptions.path=optValue;else if(optNameLower==="domain")cookieOptions.domain=optValue;else if(optNameLower==="max-age")cookieOptions.maxAge=Number(optValue);else if(optNameLower==="expires"&&optValue)cookieOptions.expires=new Date(optValue);else if(optNameLower==="httponly")cookieOptions.httpOnly=!0;else if(optNameLower==="secure")cookieOptions.secure=!0;else if(optNameLower==="samesite")cookieOptions.sameSite=optValue}cookieStore.set(name,value,cookieOptions)}}}catch(cookieError){console.warn("[ServerFactory] Failed to process Set-Cookie headers:",cookieError instanceof Error?cookieError.message:String(cookieError))}let normalizedResponse=response.response;if(response.isSuccess)normalizedResponse=normalizeResponseShape(normalizedResponse);return{isSuccess:response.isSuccess,data:normalizedResponse,errors:response.errors,code:response.code,requestId:serverRequestId(response),message:response.isSuccess?void 0:response.errors?.message}}}var SYSTEM_TABLES=[{table_name:"users",feature_set:["authentication"],add_base_columns:!0,is_form_data:!1,available_app_ids:["default_be"],available_schemas:["*"],excluded_schemas:[],excluded_methods:[],bulk_endpoints_enabled:!0,columns:[{name:"email",type:"varchar",length:255},{name:"password",type:"varchar",length:255},{name:"verified_at",type:"timestamp"},{name:"email_verification_token",type:"varchar",length:255},{name:"email_verification_token_expires_at",type:"timestamp"},{name:"email_verification_sent_at",type:"timestamp"},{name:"email_verification_attempts",type:"integer",default:0},{name:"last_login_at",type:"timestamp"},{name:"login_count",type:"integer",default:0},{name:"is_locked",type:"boolean",default:!1},{name:"locked_until",type:"timestamp"},{name:"failed_login_attempts",type:"integer",default:0},{name:"is_god",type:"boolean",default:!1},{name:"cohort_id",type:"uuid",references:{table:"user_cohorts",column:"id",onDelete:"set null"}},{name:"email_verified",type:"boolean",default:!1}],indexes:[{columns:["email"],unique:!0},{columns:["email","is_active"]},{columns:["last_login_at"]},{columns:["is_locked","locked_until"]}]},{table_name:"profiles",feature_set:["authentication"],add_base_columns:!0,is_form_data:!1,available_app_ids:["default_be"],available_schemas:["*"],excluded_schemas:[],excluded_methods:[],bulk_endpoints_enabled:!0,columns:[{name:"user_id",type:"uuid",notNull:!0,references:{table:"users",column:"id"}},{name:"first_name",type:"varchar",length:100,notNull:!0},{name:"last_name",type:"varchar",length:100,notNull:!0}],indexes:[{columns:["user_id"],unique:!0},{columns:["first_name","last_name"]}]},{table_name:"roles",feature_set:["authentication","authorization"],add_base_columns:!0,is_form_data:!1,available_app_ids:["default_be"],available_schemas:["*"],excluded_schemas:[],excluded_methods:[],bulk_endpoints_enabled:!0,columns:[{name:"name",type:"varchar",length:100,notNull:!0},{name:"description",type:"varchar",length:500}],indexes:[{columns:["name"],unique:!0}]},{table_name:"claims",feature_set:["authentication","authorization"],add_base_columns:!0,is_form_data:!1,available_app_ids:["default_be"],available_schemas:["*"],excluded_schemas:[],excluded_methods:[],bulk_endpoints_enabled:!0,columns:[{name:"action",type:"varchar",length:100,notNull:!0},{name:"description",type:"varchar",length:500},{name:"path",type:"varchar",length:200,notNull:!0},{name:"method",type:"varchar",length:10,notNull:!0},{name:"policy",type:"jsonb"}],indexes:[{columns:["action"],unique:!0},{columns:["path","method"]}]},{table_name:"user_roles",feature_set:["authentication","authorization"],add_base_columns:!0,is_form_data:!1,available_app_ids:["default_be"],available_schemas:["*"],excluded_schemas:[],excluded_methods:[],bulk_endpoints_enabled:!0,columns:[{name:"user_id",type:"uuid",notNull:!0,references:{table:"users",column:"id",onDelete:"cascade"}},{name:"role_id",type:"uuid",notNull:!0,references:{table:"roles",column:"id",onDelete:"cascade"}}],indexes:[{columns:["user_id"]},{columns:["role_id"]}],constraints:{unique:[{name:"unique_user_role",columns:["user_id","role_id"]}]}},{table_name:"role_claims",feature_set:["authentication","authorization"],add_base_columns:!0,is_form_data:!1,available_app_ids:["default_be"],available_schemas:["*"],excluded_schemas:[],excluded_methods:[],bulk_endpoints_enabled:!0,columns:[{name:"role_id",type:"uuid",notNull:!0,references:{table:"roles",column:"id",onDelete:"cascade"}},{name:"claim_id",type:"uuid",notNull:!0,references:{table:"claims",column:"id",onDelete:"cascade"}},{name:"scope",type:"text"}],indexes:[{columns:["role_id"]},{columns:["claim_id"]},{columns:["role_id","claim_id","scope"]}]},{table_name:"files",feature_set:["storage"],add_base_columns:!0,is_form_data:!0,available_app_ids:["default_be"],available_schemas:["*"],excluded_schemas:[],excluded_methods:[],bulk_endpoints_enabled:!0,columns:[{name:"name",type:"varchar",length:255,notNull:!0},{name:"original_name",type:"varchar",length:255,notNull:!0},{name:"type",type:"varchar",length:50,enumValues:["image","document","video","audio","profile_picture"]},{name:"path",type:"varchar",length:500,notNull:!0},{name:"size",type:"bigint",mode:"number",notNull:!0},{name:"mime_type",type:"varchar",length:100,notNull:!0},{name:"extension",type:"varchar",length:10,notNull:!0},{name:"uploaded_by",type:"uuid",references:{table:"users",column:"id"}}],indexes:[{columns:["type"]},{columns:["uploaded_by"]},{columns:["size"]}]},{table_name:"addresses",feature_set:["authentication"],add_base_columns:!0,is_form_data:!1,available_app_ids:["default_be"],available_schemas:["*"],excluded_schemas:[],excluded_methods:[],bulk_endpoints_enabled:!0,columns:[{name:"owner_type",type:"varchar",length:50,notNull:!0,enumValues:["user","company","contact"]},{name:"owner_id",type:"uuid",notNull:!0},{name:"name",type:"varchar",length:100,notNull:!0},{name:"street",type:"varchar",length:255},{name:"city",type:"varchar",length:100},{name:"state",type:"varchar",length:50},{name:"zip",type:"varchar",length:20},{name:"country",type:"varchar",length:50,default:"US"},{name:"latitude",type:"decimal",precision:10,scale:8},{name:"longitude",type:"decimal",precision:11,scale:8},{name:"neighborhood",type:"varchar",length:100},{name:"apartment",type:"varchar",length:50},{name:"province",type:"varchar",length:100},{name:"district",type:"varchar",length:100},{name:"type",type:"varchar",length:50}],indexes:[{columns:["city","state"]},{columns:["latitude","longitude"]},{columns:["type"]},{columns:["owner_type","owner_id"]}]},{table_name:"phones",feature_set:["authentication"],add_base_columns:!0,is_form_data:!1,available_app_ids:["default_be"],available_schemas:["*"],excluded_schemas:[],excluded_methods:[],bulk_endpoints_enabled:!0,columns:[{name:"owner_type",type:"varchar",length:50,notNull:!0,enumValues:["user","company","contact"]},{name:"owner_id",type:"uuid",notNull:!0},{name:"name",type:"varchar",length:100,notNull:!0},{name:"type",type:"varchar",length:50,enumValues:["mobile","office","fax"]},{name:"number",type:"varchar",length:20,notNull:!0},{name:"country_code",type:"varchar",length:10,notNull:!0,default:"+1"},{name:"extension",type:"varchar",length:10}],indexes:[{columns:["number"]},{columns:["type"]},{columns:["owner_type","owner_id"]}]},{table_name:"notifications",feature_set:["notification"],add_base_columns:!0,is_form_data:!1,available_app_ids:["default_be"],available_schemas:["*"],excluded_schemas:[],excluded_methods:[],columns:[{name:"user_id",type:"uuid",notNull:!0},{name:"title",type:"varchar",length:255,notNull:!0},{name:"body",type:"varchar",length:1000},{name:"entity_name",type:"varchar",length:100},{name:"entity_id",type:"uuid"},{name:"type",type:"varchar",length:50,notNull:!0,default:"system",enumValues:["verification","system","custom"]},{name:"source",type:"varchar",length:100},{name:"is_seen",type:"boolean",notNull:!0,default:!1},{name:"seen_at",type:"timestamptz"}],indexes:[{columns:["user_id","created_at"]},{columns:["is_seen"]},{columns:["type"]},{columns:["user_id","type","is_seen"]}]},{table_name:"tenants",feature_set:["multi-tenant"],add_base_columns:!0,is_form_data:!1,available_app_ids:["default_be"],available_schemas:["main"],excluded_schemas:[],excluded_methods:[],columns:[{name:"subdomain",type:"varchar",length:100,notNull:!0,unique:!0},{name:"schema_name",type:"varchar",length:100,notNull:!0,unique:!0},{name:"company_id",type:"uuid",notNull:!0},{name:"company_name",type:"varchar",length:255},{name:"god_admin_email",type:"varchar",length:255,notNull:!0},{name:"status",type:"varchar",length:20,notNull:!0,default:"provisioning"},{name:"plan",type:"varchar",length:50,default:"free"},{name:"domain",type:"varchar",length:255},{name:"settings",type:"jsonb",default:"{}"},{name:"trusted_sources",type:"jsonb",default:"[]"},{name:"max_users",type:"integer"},{name:"provisioned_at",type:"timestamptz"},{name:"suspended_at",type:"timestamptz"},{name:"suspended_reason",type:"text"}],indexes:[{columns:["status"]}]},{table_name:"tenant_events",feature_set:["multi-tenant"],add_base_columns:!0,is_form_data:!1,available_app_ids:["default_be"],available_schemas:["main"],excluded_schemas:[],excluded_methods:["POST","PUT","PATCH","DELETE"],columns:[{name:"tenant_id",type:"uuid",notNull:!0,references:{table:"tenants",column:"id",onDelete:"cascade"}},{name:"event_type",type:"varchar",length:50,notNull:!0},{name:"event_data",type:"jsonb",default:"{}"},{name:"performed_by",type:"varchar",length:255},{name:"ip_address",type:"varchar",length:45}],indexes:[{columns:["tenant_id"]},{columns:["event_type"]}]},{table_name:"tenant_features",feature_set:["multi-tenant"],add_base_columns:!0,is_form_data:!1,available_app_ids:["default_be"],available_schemas:["main"],excluded_schemas:[],excluded_methods:[],columns:[{name:"tenant_id",type:"uuid",notNull:!0,references:{table:"tenants",column:"id",onDelete:"cascade"}},{name:"feature_name",type:"varchar",length:100,notNull:!0},{name:"enabled",type:"boolean",notNull:!0,default:!0},{name:"config",type:"jsonb",default:"{}"}],indexes:[{columns:["tenant_id","feature_name"],unique:!0},{columns:["feature_name"]}]},{table_name:"domain_hostnames",feature_set:["domains"],add_base_columns:!0,is_form_data:!1,available_app_ids:["default_be"],available_schemas:["main"],excluded_schemas:[],excluded_methods:["PUT"],group_name:"Domains",columns:[{name:"owner_type",type:"varchar",length:20,notNull:!0,default:"tenant",comment:"tenant | organization | user | external"},{name:"owner_id",type:"uuid",notNull:!0,comment:"Product-defined owner reference (e.g. band id, org id)"},{name:"tenant_id",type:"uuid",references:{table:"tenants",column:"id",onDelete:"cascade"},comment:"Tenant this hostname routes to; null for non-tenant owners"},{name:"schema_name",type:"varchar",length:100,comment:"Resolved tenant schema for routing"},{name:"hostname",type:"varchar",length:255,notNull:!0,comment:"Original hostname as entered by the user"},{name:"normalized_hostname",type:"varchar",length:255,notNull:!0,unique:!0,comment:"Lowercased, punycode, port-stripped hostname used for matching"},{name:"hostname_kind",type:"varchar",length:20,notNull:!0,default:"apex",comment:"platform_subdomain | apex | www | subdomain"},{name:"status",type:"varchar",length:20,notNull:!0,default:"pending_verification",comment:"draft | pending_verification | verifying | active | failed | disabled | archived"},{name:"routing_status",type:"varchar",length:20,notNull:!0,default:"inactive",comment:"inactive | pending | active | failed"},{name:"certificate_status",type:"varchar",length:20,notNull:!0,default:"pending",comment:"not_required | pending | active | failed"},{name:"verification_status",type:"varchar",length:20,notNull:!0,default:"pending",comment:"pending | verified | failed | expired"},{name:"provider",type:"varchar",length:30,notNull:!0,default:"manual_dns",comment:"manual_dns | cloudflare_for_saas | cloudflare_registrar"},{name:"provider_hostname_id",type:"varchar",length:255},{name:"dns_target",type:"varchar",length:255,comment:"CNAME / A target the customer must point at"},{name:"is_primary",type:"boolean",notNull:!0,default:!1},{name:"redirect_to_hostname_id",type:"uuid",comment:"When set, this hostname 301-redirects to another hostname (e.g. apex to www)"},{name:"activated_at",type:"timestamptz"},{name:"disabled_at",type:"timestamptz"},{name:"failure_reason",type:"text"},{name:"metadata",type:"jsonb",default:"{}"}],indexes:[{columns:["tenant_id"]},{columns:["schema_name"]},{columns:["status"]},{columns:["owner_type","owner_id"]},{columns:["provider","provider_hostname_id"]}]},{table_name:"domain_registrations",feature_set:["domains"],add_base_columns:!0,is_form_data:!1,available_app_ids:["default_be"],available_schemas:["main"],excluded_schemas:[],excluded_methods:["PUT"],group_name:"Domains",columns:[{name:"owner_type",type:"varchar",length:20,notNull:!0,default:"tenant"},{name:"owner_id",type:"uuid",notNull:!0},{name:"tenant_id",type:"uuid",references:{table:"tenants",column:"id",onDelete:"set null"}},{name:"requested_domain",type:"varchar",length:255,notNull:!0},{name:"registered_domain",type:"varchar",length:255},{name:"provider",type:"varchar",length:30,notNull:!0,default:"cloudflare_registrar"},{name:"provider_registration_id",type:"varchar",length:255},{name:"registrant_owner_type",type:"varchar",length:20,notNull:!0,default:"customer",comment:"customer | platform"},{name:"registrant_contact_snapshot",type:"jsonb",default:"{}"},{name:"status",type:"varchar",length:30,notNull:!0,default:"draft",comment:"draft | availability_checked | awaiting_payment | registering | active | failed | transfer_requested | transferred_out | cancelled | expired"},{name:"price_amount",type:"numeric",precision:12,scale:2},{name:"currency",type:"varchar",length:10},{name:"renewal_date",type:"timestamptz"},{name:"auto_renew",type:"boolean",notNull:!0,default:!0},{name:"terms_version",type:"varchar",length:50},{name:"terms_accepted_at",type:"timestamptz"},{name:"failure_reason",type:"text"},{name:"metadata",type:"jsonb",default:"{}"}],indexes:[{columns:["owner_type","owner_id"]},{columns:["tenant_id"]},{columns:["status"]}]},{table_name:"domain_verification_challenges",feature_set:["domains"],add_base_columns:!0,is_form_data:!1,available_app_ids:["default_be"],available_schemas:["main"],excluded_schemas:[],excluded_methods:["POST","PUT","PATCH","DELETE"],group_name:"Domains",columns:[{name:"domain_hostname_id",type:"uuid",notNull:!0,references:{table:"domainHostnames",column:"id",onDelete:"cascade"}},{name:"challenge_type",type:"varchar",length:30,notNull:!0,default:"ownership_validation",comment:"hostname_validation | certificate_validation | ownership_validation"},{name:"record_type",type:"varchar",length:10,notNull:!0,default:"TXT",comment:"TXT | CNAME | A | AAAA | HTTP"},{name:"record_name",type:"varchar",length:255,notNull:!0},{name:"record_value",type:"text",notNull:!0},{name:"expected_target",type:"varchar",length:255},{name:"status",type:"varchar",length:20,notNull:!0,default:"pending",comment:"pending | verified | failed | expired"},{name:"expires_at",type:"timestamptz"},{name:"verified_at",type:"timestamptz"},{name:"failure_reason",type:"text"},{name:"metadata",type:"jsonb",default:"{}"}],indexes:[{columns:["domain_hostname_id"]},{columns:["status"]}]},{table_name:"domain_provider_records",feature_set:["domains"],add_base_columns:!0,is_form_data:!1,available_app_ids:["default_be"],available_schemas:["main"],excluded_schemas:[],excluded_methods:["POST","PUT","PATCH","DELETE"],group_name:"Domains",columns:[{name:"domain_hostname_id",type:"uuid",notNull:!0,references:{table:"domainHostnames",column:"id",onDelete:"cascade"}},{name:"provider",type:"varchar",length:30,notNull:!0},{name:"provider_resource_type",type:"varchar",length:30,notNull:!0,comment:"custom_hostname | certificate | validation | zone | dns_record | registration"},{name:"provider_resource_id",type:"varchar",length:255},{name:"provider_status",type:"varchar",length:50},{name:"last_synced_at",type:"timestamptz"},{name:"raw_status",type:"jsonb",default:"{}"},{name:"metadata",type:"jsonb",default:"{}"}],indexes:[{columns:["domain_hostname_id"]},{columns:["provider","provider_resource_id"]}]},{table_name:"domain_events",feature_set:["domains"],add_base_columns:!0,is_form_data:!1,available_app_ids:["default_be"],available_schemas:["main"],excluded_schemas:[],excluded_methods:["POST","PUT","PATCH","DELETE"],group_name:"Domains",columns:[{name:"domain_hostname_id",type:"uuid",references:{table:"domainHostnames",column:"id",onDelete:"cascade"}},{name:"domain_registration_id",type:"uuid",references:{table:"domainRegistrations",column:"id",onDelete:"cascade"}},{name:"event_type",type:"varchar",length:50,notNull:!0},{name:"actor_type",type:"varchar",length:20},{name:"actor_id",type:"varchar",length:255},{name:"message",type:"text"},{name:"ip_address",type:"varchar",length:45},{name:"metadata",type:"jsonb",default:"{}"}],indexes:[{columns:["domain_hostname_id"]},{columns:["event_type"]}]},{table_name:"verifications",feature_set:["verification"],add_base_columns:!0,is_form_data:!1,available_app_ids:["default_be"],available_schemas:["*"],excluded_schemas:[],excluded_methods:[],columns:[{name:"instance_id",type:"uuid",notNull:!0,references:{table:"verificationInstances",column:"id",onDelete:"cascade"}},{name:"requirement_id",type:"uuid",notNull:!0,references:{table:"verificationRequirements",column:"id"}},{name:"verifier_id",type:"uuid",notNull:!0,references:{table:"users",column:"id"}},{name:"signature_id",type:"uuid",references:{table:"files",column:"id"}},{name:"entity_name",type:"varchar",length:100,notNull:!0},{name:"entity_id",type:"uuid",notNull:!0},{name:"step_order",type:"integer",notNull:!0,default:1},{name:"decision",type:"varchar",length:50,notNull:!0,default:"pending",enumValues:["approved","rejected","pending"]},{name:"reason",type:"text"},{name:"diff",type:"jsonb"}],indexes:[{columns:["instance_id"]},{columns:["requirement_id"]},{columns:["verifier_id"]},{columns:["entity_name","entity_id"]},{columns:["entity_name","entity_id","step_order"]},{columns:["decision"]}]},{table_name:"verificationRequirements",feature_set:["verification"],add_base_columns:!0,is_form_data:!1,available_app_ids:["default_be"],available_schemas:["*"],excluded_schemas:[],excluded_methods:[],columns:[{name:"instance_id",type:"uuid",notNull:!0,references:{table:"verificationInstances",column:"id",onDelete:"cascade"}},{name:"step_node_id",type:"varchar",length:100,notNull:!0},{name:"verifier_node_id",type:"varchar",length:100,notNull:!0},{name:"entity_name",type:"varchar",length:100,notNull:!0},{name:"entity_id",type:"uuid",notNull:!0},{name:"verifier_type",type:"varchar",length:30,notNull:!0,enumValues:["user","role"]},{name:"verifier_user_id",type:"uuid"},{name:"verifier_role",type:"varchar",length:100},{name:"require_signature",type:"boolean",notNull:!0,default:!1},{name:"all_must_approve",type:"boolean",notNull:!0,default:!1},{name:"step_order",type:"integer",notNull:!0,default:1},{name:"status",type:"varchar",length:30,notNull:!0,default:"pending",enumValues:["pending","approved","rejected"]}],indexes:[{columns:["instance_id"]},{columns:["instance_id","step_order"]},{columns:["entity_name","entity_id"]},{columns:["verifier_user_id"]},{columns:["status"]}]},{table_name:"verificationFlows",feature_set:["authentication","verification"],add_base_columns:!0,is_form_data:!1,available_app_ids:["default_be"],available_schemas:["*"],excluded_schemas:[],excluded_methods:[],columns:[{name:"entity_name",type:"varchar",length:100,notNull:!0},{name:"name",type:"varchar",length:255,notNull:!0},{name:"description",type:"text"},{name:"trigger_on",type:"varchar",length:50,notNull:!0,default:"update",enumValues:["create","update","delete","manual"]},{name:"trigger_fields",type:"jsonb"},{name:"is_draft",type:"boolean",notNull:!0,default:!0},{name:"published_at",type:"timestamptz"},{name:"viewport",type:"jsonb"}],indexes:[{columns:["entity_name"]},{columns:["entity_name","trigger_on"]},{columns:["is_draft"]}]},{table_name:"verificationSteps",feature_set:["authentication","verification"],add_base_columns:!0,is_form_data:!1,available_app_ids:["default_be"],available_schemas:["*"],excluded_schemas:[],excluded_methods:[],columns:[{name:"flow_id",type:"uuid",notNull:!0,references:{table:"verificationFlows",column:"id",onDelete:"cascade"}},{name:"entity_name",type:"varchar",length:100,notNull:!0},{name:"node_id",type:"varchar",length:100,notNull:!0},{name:"node_type",type:"varchar",length:50,notNull:!0,default:"step",enumValues:["step","verifier","notification"]},{name:"step_order",type:"integer",notNull:!0,default:1},{name:"name",type:"varchar",length:255},{name:"description",type:"text"},{name:"position_x",type:"numeric",notNull:!0,default:0},{name:"position_y",type:"numeric",notNull:!0,default:0},{name:"width",type:"numeric"},{name:"height",type:"numeric"},{name:"style",type:"jsonb"},{name:"data",type:"jsonb"}],indexes:[{columns:["flow_id"]},{columns:["entity_name"]},{columns:["flow_id","node_id"],unique:!0},{columns:["entity_name","step_order"]}]},{table_name:"verificationEdges",feature_set:["authentication","verification"],add_base_columns:!0,is_form_data:!1,available_app_ids:["default_be"],available_schemas:["*"],excluded_schemas:[],excluded_methods:[],columns:[{name:"flow_id",type:"uuid",notNull:!0,references:{table:"verificationFlows",column:"id",onDelete:"cascade"}},{name:"edge_id",type:"varchar",length:100,notNull:!0},{name:"source_node_id",type:"varchar",length:100,notNull:!0},{name:"target_node_id",type:"varchar",length:100,notNull:!0},{name:"source_handle",type:"varchar",length:50},{name:"target_handle",type:"varchar",length:50},{name:"edge_type",type:"varchar",length:50,default:"default",enumValues:["default","conditional","success","failure"]},{name:"label",type:"varchar",length:255},{name:"condition",type:"jsonb"},{name:"style",type:"jsonb"},{name:"animated",type:"boolean",default:!1}],indexes:[{columns:["flow_id"]},{columns:["flow_id","edge_id"],unique:!0},{columns:["source_node_id"]},{columns:["target_node_id"]}]},{table_name:"verificationNotificationRules",feature_set:["verification","notification"],add_base_columns:!0,is_form_data:!1,available_app_ids:["default_be"],available_schemas:["*"],excluded_schemas:[],excluded_methods:[],columns:[{name:"flow_id",type:"uuid",notNull:!0,references:{table:"verificationFlows",column:"id",onDelete:"cascade"}},{name:"node_id",type:"varchar",length:100,notNull:!0},{name:"trigger",type:"varchar",length:50,notNull:!0,enumValues:["on_flow_started","on_step_reached","on_approved","on_rejected","on_flow_completed"]},{name:"title_template",type:"varchar",length:255},{name:"body_template",type:"text"},{name:"starts_at",type:"timestamptz"},{name:"expires_at",type:"timestamptz"}],indexes:[{columns:["flow_id"]},{columns:["flow_id","node_id"],unique:!0},{columns:["trigger"]}]},{table_name:"verificationNotificationRecipients",feature_set:["verification","notification"],add_base_columns:!0,is_form_data:!1,available_app_ids:["default_be"],available_schemas:["*"],excluded_schemas:[],excluded_methods:[],columns:[{name:"rule_id",type:"uuid",notNull:!0,references:{table:"verificationNotificationRules",column:"id",onDelete:"cascade"}},{name:"recipient_type",type:"varchar",length:30,notNull:!0,enumValues:["user","role","all_verifiers","step_verifier","entity_creator"]},{name:"recipient_user_id",type:"uuid"},{name:"recipient_role",type:"varchar",length:100}],indexes:[{columns:["rule_id"]},{columns:["recipient_type"]}]},{table_name:"verificationVerifierConfigs",feature_set:["verification"],add_base_columns:!0,is_form_data:!1,available_app_ids:["default_be"],available_schemas:["*"],excluded_schemas:[],excluded_methods:[],columns:[{name:"flow_id",type:"uuid",notNull:!0,references:{table:"verificationFlows",column:"id",onDelete:"cascade"}},{name:"node_id",type:"varchar",length:100,notNull:!0},{name:"verifier_type",type:"varchar",length:30,notNull:!0,enumValues:["user","role"]},{name:"verifier_user_id",type:"uuid"},{name:"verifier_role",type:"varchar",length:100},{name:"require_signature",type:"boolean",notNull:!0,default:!1},{name:"all_must_approve",type:"boolean",notNull:!0,default:!1}],indexes:[{columns:["flow_id"]},{columns:["flow_id","node_id"],unique:!0},{columns:["verifier_type"]}]},{table_name:"verificationInstances",feature_set:["verification"],add_base_columns:!0,is_form_data:!1,available_app_ids:["default_be"],available_schemas:["*"],excluded_schemas:[],excluded_methods:[],columns:[{name:"flow_id",type:"uuid",notNull:!0,references:{table:"verificationFlows",column:"id"}},{name:"entity_name",type:"varchar",length:100,notNull:!0},{name:"entity_id",type:"uuid",notNull:!0},{name:"started_by",type:"uuid",references:{table:"users",column:"id"}},{name:"status",type:"varchar",length:30,notNull:!0,default:"active",enumValues:["active","completed","rejected","cancelled"]},{name:"current_step_order",type:"integer",notNull:!0,default:1},{name:"started_at",type:"timestamptz",notNull:!0,defaultRaw:"now()"},{name:"completed_at",type:"timestamptz"}],indexes:[{columns:["flow_id"]},{columns:["entity_name","entity_id"]},{columns:["entity_name","entity_id","status"]},{columns:["status"]},{columns:["started_by"]}]},{table_name:"verificationNotificationChannels",feature_set:["verification","notification"],add_base_columns:!0,is_form_data:!1,available_app_ids:["default_be"],available_schemas:["*"],excluded_schemas:[],excluded_methods:[],columns:[{name:"rule_id",type:"uuid",notNull:!0,references:{table:"verificationNotificationRules",column:"id",onDelete:"cascade"}},{name:"channel",type:"varchar",length:30,notNull:!0,enumValues:["portal","email","sms","telegram","webhook"]}],indexes:[{columns:["rule_id"]},{columns:["rule_id","channel"],unique:!0},{columns:["channel"]}]},{table_name:"user_cohorts",feature_set:["authentication"],add_base_columns:!0,is_form_data:!1,available_app_ids:["default_be"],available_schemas:["*"],excluded_schemas:[],excluded_methods:[],columns:[{name:"name",type:"varchar",length:255,notNull:!0},{name:"description",type:"text"},{name:"created_by",type:"uuid",references:{table:"users",column:"id",onDelete:"set null"}},{name:"expires_at",type:"timestamptz"},{name:"user_count",type:"integer",notNull:!0,default:0},{name:"metadata",type:"jsonb",default:"{}"}]},{table_name:"user_sessions",feature_set:["authentication"],add_base_columns:!0,is_form_data:!1,group_name:"Authentication",available_app_ids:["default_be"],available_schemas:["*"],excluded_schemas:[],excluded_methods:["POST","PUT","PATCH"],columns:[{name:"user_id",type:"uuid",notNull:!0,references:{table:"users",column:"id",onDelete:"cascade"}},{name:"token_hash",type:"varchar",length:255,notNull:!0},{name:"refresh_token_hash",type:"varchar",length:255},{name:"device_fingerprint",type:"varchar",length:255},{name:"device_name",type:"varchar",length:100},{name:"device_type",type:"varchar",length:50,enumValues:["desktop","mobile","tablet","unknown"]},{name:"browser_name",type:"varchar",length:50},{name:"browser_version",type:"varchar",length:20},{name:"os_name",type:"varchar",length:50},{name:"os_version",type:"varchar",length:20},{name:"ip_address",type:"varchar",length:45,notNull:!0},{name:"location_country",type:"varchar",length:100},{name:"location_city",type:"varchar",length:100},{name:"location_coordinates",type:"varchar",length:50},{name:"last_activity_at",type:"timestamptz",notNull:!0,defaultRaw:"now()"},{name:"expires_at",type:"timestamptz",notNull:!1},{name:"revoked_at",type:"timestamptz"},{name:"revoked_reason",type:"varchar",length:100,enumValues:["user_logout","user_revoked","admin_revoked","security_concern","password_changed","expired","replaced"]},{name:"is_current",type:"boolean",notNull:!0,default:!1},{name:"login_method",type:"varchar",length:50,enumValues:["password","oauth_google","oauth_github","oauth_microsoft","magic_link","sso","api_key"]},{name:"remember_me",type:"boolean",notNull:!0,default:!1},{name:"trust_score",type:"integer",default:100},{name:"approval_status",type:"varchar",length:20,default:"approved",enumValues:["approved","pending","rejected"]},{name:"approval_token",type:"varchar",length:64},{name:"approval_requested_at",type:"timestamptz"},{name:"approval_responded_at",type:"timestamptz"}],indexes:[{columns:["user_id"]},{columns:["token_hash"],unique:!0},{columns:["refresh_token_hash"]},{columns:["user_id","is_active"]},{columns:["expires_at"]},{columns:["device_fingerprint"]},{columns:["ip_address"]},{columns:["last_activity_at"]},{columns:["approval_status"]},{columns:["approval_token"]}]},{table_name:"trusted_devices",feature_set:["authentication"],add_base_columns:!0,is_form_data:!1,group_name:"Authentication",available_app_ids:["default_be"],available_schemas:["*"],excluded_schemas:[],excluded_methods:["POST","PUT","PATCH"],columns:[{name:"user_id",type:"uuid",notNull:!0,references:{table:"users",column:"id",onDelete:"cascade"}},{name:"token_hash",type:"varchar",length:64,notNull:!0},{name:"status",type:"varchar",length:20,notNull:!0,default:"pending",enumValues:["pending","trusted","revoked"]},{name:"origin_session_id",type:"uuid"},{name:"label",type:"varchar",length:120},{name:"device_fingerprint",type:"varchar",length:255},{name:"first_seen_ip",type:"varchar",length:45},{name:"last_seen_ip",type:"varchar",length:45},{name:"last_seen_at",type:"timestamptz"},{name:"expires_at",type:"timestamptz"},{name:"revoked_at",type:"timestamptz"},{name:"revoked_reason",type:"varchar",length:30,enumValues:["user_revoked","password_reset","logout","admin_revoked","expired","replaced"]}],indexes:[{columns:["token_hash"],unique:!0},{columns:["user_id"]},{columns:["user_id","status"]},{columns:["origin_session_id"]},{columns:["expires_at"]}]},{table_name:"password_reset_tokens",feature_set:["authentication"],add_base_columns:!0,is_form_data:!1,group_name:"Authentication",requires:["email"],available_app_ids:["default_be"],available_schemas:["*"],excluded_schemas:[],excluded_methods:["POST","PUT","PATCH","DELETE"],columns:[{name:"user_id",type:"uuid",notNull:!0,references:{table:"users",column:"id",onDelete:"cascade"}},{name:"token_hash",type:"varchar",length:255,notNull:!0},{name:"expires_at",type:"timestamptz",notNull:!0},{name:"used_at",type:"timestamptz"}],indexes:[{columns:["token_hash"],unique:!0},{columns:["user_id"]},{columns:["expires_at"]}]},{table_name:"magic_link_tokens",feature_set:["authentication"],add_base_columns:!0,is_form_data:!1,group_name:"Authentication",requires:["email"],available_app_ids:["default_be"],available_schemas:["*"],excluded_schemas:[],excluded_methods:["POST","PUT","PATCH","DELETE"],columns:[{name:"user_id",type:"uuid",notNull:!0,references:{table:"users",column:"id",onDelete:"cascade"}},{name:"email",type:"varchar",length:255,notNull:!0},{name:"token_hash",type:"varchar",length:255,notNull:!0},{name:"expires_at",type:"timestamptz",notNull:!0},{name:"used_at",type:"timestamptz"}],indexes:[{columns:["token_hash"],unique:!0},{columns:["user_id"]},{columns:["email"]},{columns:["expires_at"]}]},{table_name:"webauthn_credentials",feature_set:["authentication"],add_base_columns:!0,is_form_data:!1,group_name:"Authentication",requires:[],available_app_ids:["default_be"],available_schemas:["*"],excluded_schemas:[],excluded_methods:["POST","PUT","PATCH","DELETE"],columns:[{name:"user_id",type:"uuid",notNull:!0,references:{table:"users",column:"id",onDelete:"cascade"}},{name:"credential_id",type:"text",notNull:!0},{name:"public_key",type:"text",notNull:!0},{name:"counter",type:"bigint",mode:"number",notNull:!0,defaultValue:0},{name:"transports",type:"jsonb"},{name:"device_type",type:"varchar",length:32},{name:"backed_up",type:"boolean",notNull:!0,defaultValue:!1},{name:"aaguid",type:"varchar",length:64},{name:"authenticator_attachment",type:"varchar",length:32},{name:"nickname",type:"varchar",length:128},{name:"last_used_at",type:"timestamptz"},{name:"revoked_at",type:"timestamptz"}],indexes:[{columns:["credential_id"],unique:!0},{columns:["user_id"]}]},{table_name:"webauthn_challenges",feature_set:["authentication"],add_base_columns:!0,is_form_data:!1,group_name:"Authentication",requires:[],available_app_ids:["default_be"],available_schemas:["*"],excluded_schemas:[],excluded_methods:["POST","PUT","PATCH","DELETE","GET"],columns:[{name:"user_id",type:"uuid",references:{table:"users",column:"id",onDelete:"cascade"}},{name:"challenge",type:"text",notNull:!0},{name:"challenge_type",type:"varchar",length:32,notNull:!0},{name:"expires_at",type:"timestamptz",notNull:!0},{name:"consumed_at",type:"timestamptz"}],indexes:[{columns:["challenge"],unique:!0},{columns:["user_id"]},{columns:["expires_at"]}]},{table_name:"audit_logs",feature_set:["audit"],add_base_columns:!1,is_form_data:!1,available_app_ids:["default_be"],available_schemas:["*"],excluded_schemas:[],excluded_methods:["POST","PUT","DELETE","PATCH","TOGGLE","VERIFICATION"],columns:[{name:"id",type:"uuid",primaryKey:!0,defaultRaw:"gen_random_uuid()"},{name:"entity_id",type:"uuid"},{name:"entity_name",type:"text",notNull:!0},{name:"operation_type",type:"text",notNull:!0},{name:"user_id",type:"uuid"},{name:"ip_address",type:"text"},{name:"user_agent",type:"text"},{name:"summary",type:"text"},{name:"severity",type:"text",default:"info"},{name:"category",type:"text"},{name:"occurrence_count",type:"integer",default:1},{name:"last_occurrence_at",type:"timestamp"},{name:"old_values",type:"jsonb"},{name:"new_values",type:"jsonb"},{name:"created_at",type:"timestamp",notNull:!0,defaultRaw:"now()"},{name:"path",type:"text"},{name:"query",type:"text"}],indexes:[{columns:["entity_id"]},{columns:["entity_name"]},{columns:["user_id"]},{columns:["created_at"]},{columns:["severity"]},{columns:["category"]}]},{table_name:"monitoring_metrics",feature_set:["monitoring"],add_base_columns:!1,is_form_data:!1,available_app_ids:["default_be"],available_schemas:["*"],excluded_schemas:[],excluded_methods:["POST","PUT","DELETE","PATCH","TOGGLE","VERIFICATION"],columns:[{name:"id",type:"uuid",primaryKey:!0,defaultRaw:"gen_random_uuid()"},{name:"metric_type",type:"text",notNull:!0},{name:"metric_name",type:"text",notNull:!0},{name:"value",type:"doublePrecision",notNull:!0},{name:"tags",type:"jsonb"},{name:"recorded_at",type:"timestamp",notNull:!0}],indexes:[{columns:["metric_type"]},{columns:["metric_name"]},{columns:["recorded_at"]}]},{table_name:"oauth_accounts",feature_set:["authentication"],add_base_columns:!0,is_form_data:!1,available_app_ids:["default_be"],available_schemas:["*"],excluded_schemas:[],excluded_methods:[],bulk_endpoints_enabled:!1,columns:[{name:"user_id",type:"uuid",notNull:!0,references:{table:"users",column:"id",onDelete:"cascade"}},{name:"provider",type:"varchar",length:50,notNull:!0,enumValues:["google","github","microsoft","discord","facebook","twitter","apple","custom"]},{name:"provider_account_id",type:"varchar",length:255,notNull:!0},{name:"provider_email",type:"varchar",length:255},{name:"provider_name",type:"varchar",length:255},{name:"provider_avatar_url",type:"text"},{name:"access_token",type:"text"},{name:"refresh_token",type:"text"},{name:"token_expires_at",type:"timestamp"},{name:"scope",type:"text"},{name:"raw_profile",type:"jsonb"},{name:"is_primary",type:"boolean",notNull:!0,default:!1},{name:"last_used_at",type:"timestamp"}],indexes:[{columns:["user_id"]},{columns:["provider","provider_account_id"],unique:!0},{columns:["provider_email"]},{columns:["user_id","provider"]}],constraints:{unique:[{name:"unique_provider_account",columns:["provider","provider_account_id"]}]}},{table_name:"api_keys",feature_set:["authentication"],add_base_columns:!0,is_form_data:!1,available_app_ids:["default_be"],available_schemas:["*"],excluded_schemas:[],excluded_methods:["POST","PUT","PATCH"],bulk_endpoints_enabled:!1,columns:[{name:"user_id",type:"uuid",notNull:!0,references:{table:"users",column:"id",onDelete:"cascade"}},{name:"name",type:"varchar",length:255,notNull:!0},{name:"description",type:"text"},{name:"key_hash",type:"varchar",length:255,notNull:!0},{name:"key_preview",type:"varchar",length:20,notNull:!0},{name:"owner_type",type:"varchar",length:30,notNull:!0,default:"personal",enumValues:["personal","application"]},{name:"application_name",type:"varchar",length:255},{name:"allowed_roles",type:"jsonb",notNull:!0,default:"[]"},{name:"allowed_claims",type:"jsonb",notNull:!0,default:"[]"},{name:"allowed_scopes",type:"jsonb",notNull:!0,default:"[]"},{name:"expires_at",type:"timestamptz"},{name:"last_used_at",type:"timestamptz"},{name:"last_used_ip",type:"varchar",length:45},{name:"usage_count",type:"integer",notNull:!0,default:0},{name:"revoked_at",type:"timestamptz"},{name:"revoked_reason",type:"varchar",length:255}],indexes:[{columns:["key_hash"],unique:!0},{columns:["user_id"]},{columns:["user_id","is_active"]},{columns:["owner_type"]},{columns:["expires_at"]},{columns:["last_used_at"]}]},{table_name:"backup_logs",feature_set:["backup"],add_base_columns:!0,is_form_data:!1,available_app_ids:["default_be"],available_schemas:["main","all"],excluded_schemas:[],excluded_methods:["PUT","PATCH"],columns:[{name:"backup_name",type:"varchar",length:255,notNull:!0},{name:"file_name",type:"varchar",length:500,notNull:!0},{name:"schema_name",type:"varchar",length:100,notNull:!0},{name:"format",type:"varchar",length:20,notNull:!0,default:"json"},{name:"status",type:"varchar",length:20,notNull:!0,default:"pending"},{name:"trigger",type:"varchar",length:20,notNull:!0,default:"manual"},{name:"size_bytes",type:"integer"},{name:"table_count",type:"integer"},{name:"row_count",type:"integer"},{name:"included_tables",type:"jsonb",default:"[]"},{name:"excluded_tables",type:"jsonb",default:"[]"},{name:"error_message",type:"text"},{name:"started_at",type:"timestamp"},{name:"completed_at",type:"timestamp"},{name:"performed_by",type:"varchar",length:255},{name:"cron_expression",type:"varchar",length:100},{name:"retention_days",type:"integer"}],indexes:[{columns:["schema_name"]},{columns:["status"]},{columns:["trigger"]},{columns:["created_at"]}]},{table_name:"payment_transactions",feature_set:["payment"],add_base_columns:!0,is_form_data:!1,available_app_ids:["default_be"],available_schemas:["*"],excluded_schemas:[],excluded_methods:["DELETE"],columns:[{name:"user_id",type:"uuid",references:{table:"users",column:"id"}},{name:"order_id",type:"varchar",length:255},{name:"provider",type:"varchar",length:50,notNull:!0},{name:"provider_transaction_id",type:"varchar",length:255},{name:"provider_payment_id",type:"varchar",length:255},{name:"payment_method",type:"varchar",length:50},{name:"amount",type:"numeric",precision:12,scale:2,notNull:!0,default:0},{name:"currency",type:"varchar",length:10,notNull:!0,default:"TRY"},{name:"status",type:"varchar",length:30,notNull:!0,default:"pending",enumValues:["pending","processing","completed","failed","refunded","partially_refunded","cancelled"]},{name:"status_code",type:"integer"},{name:"status_message",type:"text"},{name:"card_last_four",type:"varchar",length:4},{name:"card_type",type:"varchar",length:50},{name:"card_association",type:"varchar",length:50},{name:"installment",type:"integer",default:1},{name:"is_three_d_secure",type:"boolean",default:!1},{name:"provider_response",type:"jsonb",default:"{}"},{name:"metadata",type:"jsonb",default:"{}"},{name:"refunded_amount",type:"numeric",precision:12,scale:2,default:0},{name:"refunded_at",type:"timestamptz"},{name:"completed_at",type:"timestamptz"},{name:"failed_at",type:"timestamptz"},{name:"ip_address",type:"varchar",length:45}],indexes:[{columns:["user_id"]},{columns:["order_id"]},{columns:["provider"]},{columns:["provider_payment_id"]},{columns:["status"]},{columns:["user_id","status"]}]},{table_name:"payment_methods",feature_set:["payment"],add_base_columns:!0,is_form_data:!1,available_app_ids:["default_be"],available_schemas:["*"],excluded_schemas:[],excluded_methods:[],columns:[{name:"user_id",type:"uuid",notNull:!0,references:{table:"users",column:"id",onDelete:"cascade"}},{name:"provider",type:"varchar",length:50,notNull:!0},{name:"type",type:"varchar",length:30,notNull:!0,default:"card",enumValues:["card","bank_account","wallet"]},{name:"alias",type:"varchar",length:100},{name:"card_last_four",type:"varchar",length:4},{name:"card_type",type:"varchar",length:50},{name:"card_association",type:"varchar",length:50},{name:"card_family",type:"varchar",length:100},{name:"card_bank_name",type:"varchar",length:100},{name:"provider_card_user_key",type:"varchar",length:255},{name:"provider_card_token",type:"varchar",length:255},{name:"bin_number",type:"varchar",length:8},{name:"is_default",type:"boolean",default:!1},{name:"metadata",type:"jsonb",default:"{}"}],indexes:[{columns:["user_id"]},{columns:["provider"]},{columns:["user_id","is_default"]},{columns:["provider_card_user_key"]}]},{table_name:"payment_webhook_logs",feature_set:["payment"],add_base_columns:!0,is_form_data:!1,available_app_ids:["default_be"],available_schemas:["*"],excluded_schemas:[],excluded_methods:["PUT","PATCH","DELETE"],columns:[{name:"provider",type:"varchar",length:50,notNull:!0},{name:"event_type",type:"varchar",length:100,notNull:!0},{name:"provider_payment_id",type:"varchar",length:255},{name:"raw_payload",type:"jsonb",notNull:!0},{name:"processed",type:"boolean",default:!1},{name:"processing_result",type:"jsonb"},{name:"error_message",type:"text"},{name:"ip_address",type:"varchar",length:45},{name:"idempotency_key",type:"varchar",length:255}],indexes:[{columns:["provider"]},{columns:["event_type"]},{columns:["provider_payment_id"]},{columns:["processed"]},{columns:["idempotency_key"],unique:!0}]},{table_name:"payment_sub_merchants",feature_set:["payment"],add_base_columns:!0,is_form_data:!1,available_app_ids:["default_be"],available_schemas:["*"],excluded_schemas:[],excluded_methods:[],columns:[{name:"user_id",type:"uuid",references:{table:"users",column:"id"}},{name:"provider",type:"varchar",length:50,notNull:!0},{name:"provider_sub_merchant_key",type:"varchar",length:255},{name:"external_id",type:"varchar",length:255},{name:"type",type:"varchar",length:50,notNull:!0,default:"personal",enumValues:["personal","private_company","limited_or_joint_stock_company"]},{name:"status",type:"varchar",length:30,notNull:!0,default:"pending",enumValues:["pending","active","suspended","rejected"]},{name:"name",type:"varchar",length:255,notNull:!0},{name:"email",type:"varchar",length:255,notNull:!0},{name:"gsm_number",type:"varchar",length:20},{name:"address",type:"text"},{name:"iban",type:"varchar",length:50},{name:"contact_name",type:"varchar",length:100},{name:"contact_surname",type:"varchar",length:100},{name:"identity_number",type:"varchar",length:20},{name:"tax_office",type:"varchar",length:100},{name:"tax_number",type:"varchar",length:20},{name:"legal_company_title",type:"varchar",length:255},{name:"currency",type:"varchar",length:10,default:"TRY"},{name:"commission_rate",type:"numeric",precision:5,scale:2,default:0},{name:"metadata",type:"jsonb",default:"{}"}],indexes:[{columns:["user_id"]},{columns:["provider"]},{columns:["provider_sub_merchant_key"]},{columns:["external_id"]},{columns:["status"]},{columns:["email"]}]},{table_name:"payment_commission_splits",feature_set:["payment"],add_base_columns:!0,is_form_data:!1,available_app_ids:["default_be"],available_schemas:["*"],excluded_schemas:[],excluded_methods:["DELETE"],columns:[{name:"transaction_id",type:"uuid",notNull:!0,references:{table:"payment_transactions",column:"id",onDelete:"cascade"}},{name:"sub_merchant_id",type:"uuid",notNull:!0,references:{table:"payment_sub_merchants",column:"id"}},{name:"basket_item_id",type:"varchar",length:255},{name:"item_price",type:"numeric",precision:12,scale:2,notNull:!0,default:0},{name:"sub_merchant_price",type:"numeric",precision:12,scale:2,notNull:!0,default:0},{name:"platform_commission",type:"numeric",precision:12,scale:2,notNull:!0,default:0},{name:"commission_rate",type:"numeric",precision:5,scale:2,default:0},{name:"provider_split_id",type:"varchar",length:255},{name:"currency",type:"varchar",length:10,notNull:!0,default:"TRY"},{name:"status",type:"varchar",length:30,notNull:!0,default:"pending",enumValues:["pending","approved","disapproved","refunded"]},{name:"approved_at",type:"timestamptz"},{name:"disapproved_at",type:"timestamptz"},{name:"metadata",type:"jsonb",default:"{}"}],indexes:[{columns:["transaction_id"]},{columns:["sub_merchant_id"]},{columns:["status"]},{columns:["transaction_id","sub_merchant_id"]},{columns:["provider_split_id"]}]},{table_name:"payment_products",feature_set:["payment"],add_base_columns:!0,is_form_data:!1,available_app_ids:["default_be"],available_schemas:["*"],excluded_schemas:[],excluded_methods:[],columns:[{name:"provider",type:"varchar",length:50,notNull:!0},{name:"provider_product_id",type:"varchar",length:255},{name:"name",type:"varchar",length:255,notNull:!0},{name:"description",type:"text"},{name:"type",type:"varchar",length:30,default:"service",enumValues:["service","good"]},{name:"status",type:"varchar",length:30,notNull:!0,default:"active",enumValues:["active","archived","draft"]},{name:"images",type:"jsonb",default:"[]"},{name:"unit_label",type:"varchar",length:50},{name:"url",type:"varchar",length:500},{name:"metadata",type:"jsonb",default:"{}"}],indexes:[{columns:["provider"]},{columns:["provider_product_id"]},{columns:["name"]},{columns:["status"]},{columns:["type"]}]},{table_name:"payment_prices",feature_set:["payment"],add_base_columns:!0,is_form_data:!1,available_app_ids:["default_be"],available_schemas:["*"],excluded_schemas:[],excluded_methods:["DELETE"],columns:[{name:"product_id",type:"uuid",notNull:!0,references:{table:"payment_products",column:"id",onDelete:"cascade"}},{name:"provider",type:"varchar",length:50,notNull:!0},{name:"provider_price_id",type:"varchar",length:255},{name:"currency",type:"varchar",length:10,notNull:!0,default:"USD"},{name:"unit_amount",type:"integer",notNull:!0,default:0},{name:"unit_amount_decimal",type:"varchar",length:30},{name:"type",type:"varchar",length:30,notNull:!0,default:"one_time",enumValues:["one_time","recurring"]},{name:"billing_scheme",type:"varchar",length:30,default:"per_unit",enumValues:["per_unit","tiered"]},{name:"nickname",type:"varchar",length:255},{name:"recurring_interval",type:"varchar",length:20,enumValues:["day","week","month","year"]},{name:"recurring_interval_count",type:"integer",default:1},{name:"recurring_usage_type",type:"varchar",length:20,default:"licensed",enumValues:["licensed","metered"]},{name:"recurring_aggregate_usage",type:"varchar",length:30,enumValues:["sum","last_during_period","last_ever","max"]},{name:"transform_quantity_divide_by",type:"integer"},{name:"transform_quantity_round",type:"varchar",length:10,enumValues:["up","down"]},{name:"status",type:"varchar",length:30,notNull:!0,default:"active",enumValues:["active","archived"]},{name:"metadata",type:"jsonb",default:"{}"}],indexes:[{columns:["product_id"]},{columns:["provider"]},{columns:["provider_price_id"]},{columns:["type"]},{columns:["currency"]},{columns:["status"]},{columns:["product_id","status"]}]},{table_name:"payment_customers",feature_set:["payment"],add_base_columns:!0,is_form_data:!1,available_app_ids:["default_be"],available_schemas:["*"],excluded_schemas:[],excluded_methods:[],columns:[{name:"user_id",type:"uuid",references:{table:"users",column:"id"}},{name:"provider",type:"varchar",length:50,notNull:!0},{name:"provider_customer_id",type:"varchar",length:255,notNull:!0},{name:"email",type:"varchar",length:255,notNull:!0},{name:"name",type:"varchar",length:255},{name:"phone",type:"varchar",length:50},{name:"description",type:"text"},{name:"address",type:"jsonb",default:"{}"},{name:"tax_id_type",type:"varchar",length:50},{name:"tax_id_value",type:"varchar",length:100},{name:"metadata",type:"jsonb",default:"{}"}],indexes:[{columns:["user_id"]},{columns:["provider"]},{columns:["provider_customer_id"]},{columns:["email"]},{columns:["user_id","provider"],unique:!0}]},{table_name:"payment_subscriptions",feature_set:["payment"],add_base_columns:!0,is_form_data:!1,available_app_ids:["default_be"],available_schemas:["*"],excluded_schemas:[],excluded_methods:["DELETE"],columns:[{name:"customer_id",type:"uuid",notNull:!0,references:{table:"payment_customers",column:"id"}},{name:"provider",type:"varchar",length:50,notNull:!0},{name:"provider_subscription_id",type:"varchar",length:255},{name:"status",type:"varchar",length:30,notNull:!0,default:"incomplete",enumValues:["active","past_due","unpaid","canceled","incomplete","incomplete_expired","trialing","paused"]},{name:"collection_method",type:"varchar",length:30,default:"charge_automatically",enumValues:["charge_automatically","send_invoice"]},{name:"current_period_start",type:"timestamptz"},{name:"current_period_end",type:"timestamptz"},{name:"cancel_at_period_end",type:"boolean",default:!1},{name:"canceled_at",type:"timestamptz"},{name:"trial_start",type:"timestamptz"},{name:"trial_end",type:"timestamptz"},{name:"items",type:"jsonb",default:"[]"},{name:"metadata",type:"jsonb",default:"{}"}],indexes:[{columns:["customer_id"]},{columns:["provider"]},{columns:["provider_subscription_id"]},{columns:["status"]},{columns:["customer_id","status"]}]},{table_name:"payment_invoices",feature_set:["payment"],add_base_columns:!0,is_form_data:!1,available_app_ids:["default_be"],available_schemas:["*"],excluded_schemas:[],excluded_methods:["DELETE"],columns:[{name:"customer_id",type:"uuid",notNull:!0,references:{table:"payment_customers",column:"id"}},{name:"subscription_id",type:"uuid",references:{table:"payment_subscriptions",column:"id"}},{name:"provider",type:"varchar",length:50,notNull:!0},{name:"provider_invoice_id",type:"varchar",length:255},{name:"status",type:"varchar",length:30,notNull:!0,default:"draft",enumValues:["draft","open","paid","uncollectible","void"]},{name:"collection_method",type:"varchar",length:30,default:"charge_automatically",enumValues:["charge_automatically","send_invoice"]},{name:"currency",type:"varchar",length:10,notNull:!0,default:"USD"},{name:"amount_due",type:"integer",default:0},{name:"amount_paid",type:"integer",default:0},{name:"amount_remaining",type:"integer",default:0},{name:"description",type:"text"},{name:"hosted_invoice_url",type:"varchar",length:1000},{name:"invoice_pdf",type:"varchar",length:1000},{name:"due_date",type:"timestamptz"},{name:"days_until_due",type:"integer"},{name:"period_start",type:"timestamptz"},{name:"period_end",type:"timestamptz"},{name:"paid_at",type:"timestamptz"},{name:"voided_at",type:"timestamptz"},{name:"lines",type:"jsonb",default:"[]"},{name:"metadata",type:"jsonb",default:"{}"}],indexes:[{columns:["customer_id"]},{columns:["subscription_id"]},{columns:["provider"]},{columns:["provider_invoice_id"]},{columns:["status"]},{columns:["customer_id","status"]}]},{table_name:"chat_conversations",feature_set:["authentication","chat"],add_base_columns:!0,is_form_data:!1,available_app_ids:["default_be"],available_schemas:["*"],excluded_schemas:[],excluded_methods:["POST","PUT","PATCH","DELETE","GET"],columns:[{name:"type",type:"varchar",length:20,notNull:!0,default:"direct",enumValues:["direct","group"]},{name:"title",type:"varchar",length:255},{name:"description",type:"text"},{name:"avatar_file_id",type:"uuid"},{name:"direct_key",type:"varchar",length:255},{name:"last_message_at",type:"timestamptz"},{name:"last_message_preview",type:"varchar",length:500},{name:"last_message_sender_id",type:"uuid",references:{table:"users",column:"id",onDelete:"set null"}}],indexes:[{columns:["direct_key"],unique:!0},{columns:["type"]},{columns:["last_message_at"]}]},{table_name:"chat_participants",feature_set:["authentication","chat"],add_base_columns:!0,is_form_data:!1,available_app_ids:["default_be"],available_schemas:["*"],excluded_schemas:[],excluded_methods:["POST","PUT","PATCH","DELETE","GET"],columns:[{name:"conversation_id",type:"uuid",notNull:!0,references:{table:"chat_conversations",column:"id",onDelete:"cascade"}},{name:"user_id",type:"uuid",notNull:!0,references:{table:"users",column:"id",onDelete:"cascade"}},{name:"role",type:"varchar",length:20,notNull:!0,default:"member",enumValues:["member","admin","owner"]},{name:"last_read_message_id",type:"uuid"},{name:"last_read_at",type:"timestamptz"},{name:"unread_count",type:"integer",notNull:!0,default:0},{name:"muted",type:"boolean",notNull:!0,default:!1},{name:"muted_until",type:"timestamptz"},{name:"notifications_enabled",type:"boolean",notNull:!0,default:!0},{name:"left_at",type:"timestamptz"}],indexes:[{columns:["user_id"]},{columns:["conversation_id"]}],constraints:{unique:[{name:"unique_conversation_participant",columns:["conversation_id","user_id"]}]}},{table_name:"chat_messages",feature_set:["authentication","chat"],add_base_columns:!0,is_form_data:!1,available_app_ids:["default_be"],available_schemas:["*"],excluded_schemas:[],excluded_methods:["POST","PUT","PATCH","DELETE","GET"],columns:[{name:"conversation_id",type:"uuid",notNull:!0,references:{table:"chat_conversations",column:"id",onDelete:"cascade"}},{name:"sender_id",type:"uuid",references:{table:"users",column:"id",onDelete:"set null"}},{name:"content",type:"text"},{name:"content_type",type:"varchar",length:20,notNull:!0,default:"text",enumValues:["text","image","file","video","audio","system"]},{name:"reply_to_id",type:"uuid",references:{table:"chat_messages",column:"id",onDelete:"set null"}},{name:"metadata",type:"jsonb",default:"{}"},{name:"client_message_id",type:"varchar",length:100},{name:"edited_at",type:"timestamptz"},{name:"deleted_at",type:"timestamptz"}],indexes:[{columns:["conversation_id","created_at"]},{columns:["sender_id"]},{columns:["reply_to_id"]}]},{table_name:"chat_message_attachments",feature_set:["authentication","chat","storage"],add_base_columns:!0,is_form_data:!1,available_app_ids:["default_be"],available_schemas:["*"],excluded_schemas:[],excluded_methods:["POST","PUT","PATCH","DELETE","GET"],columns:[{name:"message_id",type:"uuid",notNull:!0,references:{table:"chat_messages",column:"id",onDelete:"cascade"}},{name:"conversation_id",type:"uuid",notNull:!0,references:{table:"chat_conversations",column:"id",onDelete:"cascade"}},{name:"file_id",type:"uuid",notNull:!0,references:{table:"files",column:"id",onDelete:"cascade"}},{name:"kind",type:"varchar",length:20,notNull:!0,default:"file",enumValues:["image","video","audio","file"]},{name:"original_name",type:"varchar",length:255},{name:"mime_type",type:"varchar",length:100},{name:"size",type:"bigint",mode:"number"},{name:"width",type:"integer"},{name:"height",type:"integer"},{name:"duration",type:"integer"}],indexes:[{columns:["message_id"]},{columns:["conversation_id"]},{columns:["file_id"]}]},{table_name:"payment_splits",feature_set:["payment"],add_base_columns:!0,is_form_data:!1,available_app_ids:["default_be"],available_schemas:["*"],excluded_schemas:[],excluded_methods:["POST","PUT","PATCH","DELETE"],group_name:"Payments Marketplace",columns:[{name:"transaction_id",type:"uuid",references:{table:"paymentTransactions",column:"id",onDelete:"set null"}},{name:"recipient_owner_type",type:"varchar",length:20,notNull:!0,default:"seller",comment:"seller | platform | tenant | user"},{name:"recipient_owner_id",type:"uuid",notNull:!0},{name:"provider",type:"varchar",length:50,notNull:!0},{name:"gross_amount",type:"bigint",mode:"number",notNull:!0,comment:"Minor units"},{name:"provider_fee_amount",type:"bigint",mode:"number",notNull:!0,default:0},{name:"platform_fee_amount",type:"bigint",mode:"number",notNull:!0,default:0},{name:"reserve_amount",type:"bigint",mode:"number",notNull:!0,default:0},{name:"net_payable_amount",type:"bigint",mode:"number",notNull:!0},{name:"currency",type:"varchar",length:10,notNull:!0},{name:"available_for_payout_at",type:"timestamptz"},{name:"status",type:"varchar",length:20,notNull:!0,default:"pending",comment:"pending | available | payout_requested | paid | held | refunded | disputed | cancelled"},{name:"source_type",type:"varchar",length:50},{name:"source_id",type:"varchar",length:255},{name:"metadata",type:"jsonb",default:"{}"}],indexes:[{columns:["recipient_owner_type","recipient_owner_id"]},{columns:["status"]},{columns:["transaction_id"]}]},{table_name:"payment_ledger_entries",feature_set:["payment"],add_base_columns:!0,is_form_data:!1,available_app_ids:["default_be"],available_schemas:["*"],excluded_schemas:[],excluded_methods:["POST","PUT","PATCH","DELETE"],group_name:"Payments Marketplace",columns:[{name:"account",type:"varchar",length:40,notNull:!0,comment:"platform_revenue | provider_fee | seller_payable | reserve | refund_liability | dispute_liability | payout_in_transit | payout_completed"},{name:"owner_type",type:"varchar",length:20},{name:"owner_id",type:"uuid"},{name:"direction",type:"varchar",length:6,notNull:!0,comment:"debit | credit"},{name:"amount",type:"bigint",mode:"number",notNull:!0,comment:"Minor units"},{name:"currency",type:"varchar",length:10,notNull:!0},{name:"entry_type",type:"varchar",length:40,notNull:!0,comment:"split | refund | payout | reserve_hold | reserve_release | dispute | adjustment"},{name:"reference_type",type:"varchar",length:50},{name:"reference_id",type:"varchar",length:255},{name:"split_id",type:"uuid",references:{table:"paymentSplits",column:"id",onDelete:"set null"}},{name:"description",type:"text"},{name:"metadata",type:"jsonb",default:"{}"}],indexes:[{columns:["account"]},{columns:["owner_type","owner_id"]},{columns:["entry_type"]},{columns:["reference_type","reference_id"]}]},{table_name:"payment_settlement_policies",feature_set:["payment"],add_base_columns:!0,is_form_data:!1,available_app_ids:["default_be"],available_schemas:["*"],excluded_schemas:[],excluded_methods:[],group_name:"Payments Marketplace",columns:[{name:"owner_type",type:"varchar",length:20,notNull:!0,default:"tenant",comment:"tenant | seller"},{name:"owner_id",type:"uuid",notNull:!0},{name:"default_delay_days",type:"integer",notNull:!0,default:7},{name:"new_seller_delay_days",type:"integer",notNull:!0,default:14},{name:"reserve_rate",type:"numeric",precision:5,scale:4,notNull:!0,default:0,comment:"Fraction 0..1 held as reserve"},{name:"reserve_days",type:"integer",notNull:!0,default:0},{name:"minimum_payout_amount",type:"bigint",mode:"number",notNull:!0,default:0},{name:"currency",type:"varchar",length:10},{name:"early_payout_enabled",type:"boolean",notNull:!0,default:!1},{name:"status",type:"varchar",length:20,notNull:!0,default:"active"},{name:"metadata",type:"jsonb",default:"{}"}],indexes:[{columns:["owner_type","owner_id"],unique:!0}]},{table_name:"payment_reserves",feature_set:["payment"],add_base_columns:!0,is_form_data:!1,available_app_ids:["default_be"],available_schemas:["*"],excluded_schemas:[],excluded_methods:["POST","PUT","PATCH","DELETE"],group_name:"Payments Marketplace",columns:[{name:"owner_type",type:"varchar",length:20,notNull:!0,default:"seller"},{name:"owner_id",type:"uuid",notNull:!0},{name:"split_id",type:"uuid",references:{table:"paymentSplits",column:"id",onDelete:"set null"}},{name:"amount",type:"bigint",mode:"number",notNull:!0},{name:"currency",type:"varchar",length:10,notNull:!0},{name:"reason",type:"varchar",length:30,notNull:!0,default:"refund_window",comment:"refund_window | chargeback_risk | new_seller | manual_hold | dispute | compliance_review"},{name:"status",type:"varchar",length:20,notNull:!0,default:"held",comment:"held | released | consumed | cancelled"},{name:"release_at",type:"timestamptz"},{name:"released_at",type:"timestamptz"},{name:"metadata",type:"jsonb",default:"{}"}],indexes:[{columns:["owner_type","owner_id"]},{columns:["status"]},{columns:["release_at"]}]},{table_name:"payment_payout_requests",feature_set:["payment"],add_base_columns:!0,is_form_data:!1,available_app_ids:["default_be"],available_schemas:["*"],excluded_schemas:[],excluded_methods:["POST","PUT","PATCH","DELETE"],group_name:"Payments Marketplace",columns:[{name:"recipient_owner_type",type:"varchar",length:20,notNull:!0,default:"seller"},{name:"recipient_owner_id",type:"uuid",notNull:!0},{name:"amount",type:"bigint",mode:"number",notNull:!0},{name:"currency",type:"varchar",length:10,notNull:!0},{name:"status",type:"varchar",length:20,notNull:!0,default:"pending",comment:"pending | approved | rejected | processing | completed | failed | cancelled"},{name:"provider",type:"varchar",length:50},{name:"provider_payout_id",type:"varchar",length:255},{name:"provider_transfer_id",type:"varchar",length:255},{name:"destination",type:"varchar",length:255},{name:"requested_by",type:"uuid"},{name:"decided_by",type:"uuid"},{name:"decided_at",type:"timestamptz"},{name:"completed_at",type:"timestamptz"},{name:"failure_reason",type:"text"},{name:"metadata",type:"jsonb",default:"{}"}],indexes:[{columns:["recipient_owner_type","recipient_owner_id"]},{columns:["status"]}]},{table_name:"payment_disputes",feature_set:["payment"],add_base_columns:!0,is_form_data:!1,available_app_ids:["default_be"],available_schemas:["*"],excluded_schemas:[],excluded_methods:["PUT"],group_name:"Payments Marketplace",columns:[{name:"transaction_id",type:"uuid",references:{table:"paymentTransactions",column:"id",onDelete:"set null"}},{name:"provider",type:"varchar",length:50,notNull:!0},{name:"provider_dispute_id",type:"varchar",length:255},{name:"owner_type",type:"varchar",length:20},{name:"owner_id",type:"uuid"},{name:"amount",type:"bigint",mode:"number"},{name:"currency",type:"varchar",length:10},{name:"reason",type:"varchar",length:100},{name:"status",type:"varchar",length:30,notNull:!0,default:"open",comment:"open | under_review | won | lost | accepted | cancelled"},{name:"evidence_due_at",type:"timestamptz"},{name:"resolved_at",type:"timestamptz"},{name:"metadata",type:"jsonb",default:"{}"}],indexes:[{columns:["provider","provider_dispute_id"]},{columns:["status"]},{columns:["owner_type","owner_id"]},{columns:["transaction_id"]}]},{table_name:"nucleus_secrets",feature_set:["secrets"],add_base_columns:!0,is_form_data:!1,available_app_ids:["default_be"],available_schemas:["*"],excluded_schemas:[],excluded_methods:["GET","POST","PUT","PATCH","DELETE","TOGGLE"],bulk_endpoints_enabled:!1,group_name:"Secrets",columns:[{name:"scope",type:"varchar",length:200,notNull:!0,comment:"Config dot-path that owns the credential, e.g. authentication.oauth.providers.microsoft"},{name:"key",type:"varchar",length:120,notNull:!0,comment:"Leaf key under that path, e.g. clientSecret"},{name:"value_enc",type:"text",notNull:!0,comment:"AES-256-GCM envelope v1:iv:tag:ciphertext \u2014 never returned by any route"},{name:"previous_enc",type:"text",comment:"Value replaced by the last rotation, retained for one-step rollback"},{name:"preview",type:"varchar",length:80},{name:"kind",type:"varchar",length:30,notNull:!0,default:"text",enumValues:["text","password","json","connection_string","pem"]},{name:"description",type:"varchar",length:500},{name:"version",type:"integer",notNull:!0,default:1},{name:"rotated_at",type:"timestamptz"},{name:"last_used_at",type:"timestamptz"}],indexes:[{columns:["scope","key"],unique:!0},{columns:["scope"]}]},{table_name:"integration_sources",feature_set:["integrations"],add_base_columns:!0,is_form_data:!1,available_app_ids:["default_be"],available_schemas:["*"],excluded_schemas:[],excluded_methods:[],bulk_endpoints_enabled:!1,group_name:"Integrations",columns:[{name:"name",type:"varchar",length:150,notNull:!0},{name:"slug",type:"varchar",length:150,notNull:!0,comment:"Stable machine name; referenced by exports and by operators in logs"},{name:"base_url",type:"varchar",length:500,notNull:!0,comment:"Any scheme, host and port \u2014 https, plain http, in-cluster DNS, IP. HTTPS is NOT required and must never be enforced: reaching a service inside the same cluster over http is a legitimate deployment."},{name:"description",type:"text"},{name:"enabled",type:"boolean",default:!0},{name:"auth_type",type:"varchar",length:20,notNull:!0,default:"none",enumValues:["none","bearer","api_key","basic","oauth2"]},{name:"auth_token_encrypted",type:"text",sensitive:!0,comment:"Static credential (bearer / api key / basic blob), encrypted at rest"},{name:"auth_token_hint",type:"varchar",length:24},{name:"auth_header_name",type:"varchar",length:80,default:"Authorization"},{name:"auth_query_param",type:"varchar",length:80,comment:"When set, an api_key credential travels as this query parameter instead of a header"},{name:"extra_headers",type:"jsonb",default:{}},{name:"token_url",type:"varchar",length:500},{name:"oauth_grant_type",type:"varchar",length:40,default:"password",enumValues:["password","client_credentials"]},{name:"oauth_client_id",type:"varchar",length:200},{name:"oauth_client_secret_encrypted",type:"text",sensitive:!0},{name:"oauth_client_secret_hint",type:"varchar",length:24},{name:"oauth_username",type:"varchar",length:200},{name:"oauth_password_encrypted",type:"text",sensitive:!0},{name:"oauth_password_hint",type:"varchar",length:24},{name:"oauth_scope",type:"varchar",length:300},{name:"oauth_client_auth",type:"varchar",length:20,default:"basic",enumValues:["basic","body"]},{name:"tls_verify",type:"boolean",notNull:!0,default:!0,comment:"Per-source certificate verification. Turning it off applies to THIS source's connections only \u2014 never to the process, which is what a NODE_TLS_REJECT_UNAUTHORIZED toggle would do."},{name:"timeout_ms",type:"integer",notNull:!0,default:30000},{name:"spec_url",type:"varchar",length:500,comment:"Optional API description document (OpenAPI/Swagger/Postman/Bruno). Discovery is a convenience: every endpoint can always be defined by hand instead."},{name:"spec_format",type:"varchar",length:20,default:"manual",enumValues:["manual","openapi","swagger","postman","bruno","har"]}],constraints:{unique:[{name:"uniq_integration_source_slug",columns:["slug"]}]},indexes:[{columns:["enabled"]}]},{table_name:"integration_endpoints",feature_set:["integrations"],add_base_columns:!0,is_form_data:!1,available_app_ids:["default_be"],available_schemas:["*"],excluded_schemas:[],excluded_methods:[],bulk_endpoints_enabled:!1,group_name:"Integrations",columns:[{name:"source_id",type:"uuid",notNull:!0,references:{table:"integrationSources",column:"id",onDelete:"cascade"}},{name:"tag",type:"varchar",length:120,notNull:!0},{name:"name",type:"varchar",length:200},{name:"method",type:"varchar",length:10,notNull:!0,default:"GET",enumValues:["GET","POST","PUT","PATCH","DELETE"]},{name:"path",type:"varchar",length:500,notNull:!0,comment:"Appended to the source base URL. May carry {brace} or :colon path tokens, whose values come from param_bindings."},{name:"description",type:"text"},{name:"query_params",type:"jsonb",default:{}},{name:"request_body",type:"jsonb",default:{}},{name:"param_bindings",type:"jsonb",default:[],comment:"Where each path/query parameter comes from: a static value, another endpoint's records (fan-out), or a column of one of our own tables."},{name:"pagination",type:"jsonb",default:{},comment:"Explicitly chosen paging profile (none/page_number/offset/cursor/link_header). Never guessed \u2014 a wrong guess silently imports one page."},{name:"response_root_path",type:"varchar",length:200},{name:"tree_children_path",type:"varchar",length:200,comment:"Field holding a record's children when the source answers with a shape rather than a list \u2014 each node then becomes its own row and knows its parent"},{name:"sample_response",type:"jsonb",default:{},comment:"A few real records captured by a live test, so the mapping screen still shows fields when the source is unreachable"},{name:"enabled",type:"boolean",default:!0}],indexes:[{columns:["source_id"]},{columns:["source_id","tag"]}]},{table_name:"integration_mappings",feature_set:["integrations"],add_base_columns:!0,is_form_data:!1,available_app_ids:["default_be"],available_schemas:["*"],excluded_schemas:[],excluded_methods:[],bulk_endpoints_enabled:!1,group_name:"Integrations",columns:[{name:"endpoint_id",type:"uuid",notNull:!0,references:{table:"integrationEndpoints",column:"id",onDelete:"cascade"}},{name:"name",type:"varchar",length:200,notNull:!0},{name:"target_entity",type:"varchar",length:120,notNull:!0,comment:"snake_case table name this mapping writes into"},{name:"field_mappings",type:"jsonb",notNull:!0,default:[]},{name:"reference_checks",type:"jsonb",notNull:!0,default:[],comment:"Columns whose value must already exist in another entity, and what to do when it does not."},{name:"dedup_source_field",type:"varchar",length:200},{name:"dedup_target_field",type:"varchar",length:200,comment:"Target column that identifies the same record across runs. Without it nothing can be matched and every run appends."},{name:"write_mode",type:"varchar",length:20,notNull:!0,default:"upsert",enumValues:["upsert","append","replace"],comment:"replace is the destructive wipe-and-rewrite; it is no longer the default and is gated by the completeness check."},{name:"missing_record_policy",type:"varchar",length:20,notNull:!0,default:"ignore",enumValues:["ignore","deactivate","delete"],comment:"What happens to a row that exists here but no longer in the source. deactivate is the right answer for most HR data and could not be expressed before."},{name:"missing_record_column",type:"varchar",length:120},{name:"missing_record_value",type:"varchar",length:200},{name:"removal_scope_column",type:"varchar",length:120,comment:"Target column carrying whatever the fan-out varies \u2014 a company code, a branch, a tenant. Set it when one mapping reads its source through several calls: removal is then confined to the values actually read, so a call that answers HTTP 500 costs that slice its cleanup instead of costing the whole run. Without it a single failing call refuses every removal, and a source whose one bad endpoint fails every night is never cleaned at all."},{name:"origin_column",type:"varchar",length:120,comment:"Column this mapping stamps on rows it owns. Set it when the target table ALSO holds rows people create by hand: without it every hand-made row looks like one the source stopped carrying, and the first run with a removal policy deletes them all."},{name:"origin_value",type:"varchar",length:120,comment:"The value written into origin_column. Rows carrying it are the only removal candidates."},{name:"max_removal_share",type:"numeric",comment:"Share of the target table (0-1) above which an UNATTENDED run refuses to remove. A scheduled run passes through neither the preview nor a confirmation, so this is the only thing between a wrong plan and the table. 0 or null disables."},{name:"password_target_column",type:"varchar",length:100,comment:"Column that receives the shared opening password given at run time. The password itself is never stored."},{name:"account_rule",type:"jsonb",comment:"Creates a login alongside each imported record, in the same transaction, and links it. The password belongs to the run, never to this row."},{name:"schedule_cron",type:"varchar",length:120},{name:"schedule_timezone",type:"varchar",length:64,comment:"IANA zone the cron expression is read in. Without it a nightly job drifts with the container's clock."},{name:"schedule_enabled",type:"boolean",notNull:!0,default:!1},{name:"last_run_at",type:"timestamptz"},{name:"enabled",type:"boolean",default:!0}],indexes:[{columns:["endpoint_id"]},{columns:["target_entity"]},{columns:["schedule_enabled"]}]},{table_name:"integration_runs",feature_set:["integrations"],add_base_columns:!0,is_form_data:!1,available_app_ids:["default_be"],available_schemas:["*"],excluded_schemas:[],excluded_methods:["POST","PUT","PATCH","DELETE"],bulk_endpoints_enabled:!1,group_name:"Integrations",columns:[{name:"mapping_id",type:"uuid",references:{table:"integrationMappings",column:"id",onDelete:"set null"},comment:"Nullable and NOT cascaded: deleting a mapping must not erase the record that it once rewrote a table."},{name:"target_entity",type:"varchar",length:120},{name:"write_mode",type:"varchar",length:20,enumValues:["upsert","append","replace"]},{name:"status",type:"varchar",length:20,notNull:!0,default:"queued",enumValues:["queued","running","succeeded","failed","cancelled","reverted"],comment:"There is deliberately no 'partial': a run either applied in full or not at all."},{name:"trigger",type:"varchar",length:20,notNull:!0,default:"manual",enumValues:["manual","schedule","api"]},{name:"total_fetched",type:"integer",notNull:!0,default:0},{name:"source_reported_total",type:"integer",comment:"What the source said it had. When this and total_fetched disagree, a destructive run refuses to start."},{name:"created_count",type:"integer",notNull:!0,default:0},{name:"references_created_count",type:"integer",notNull:!0,default:0,comment:"Rows this run added to OTHER tables so its records could point at them."},{name:"accounts_created_count",type:"integer",notNull:!0,default:0,comment:"Logins this run created so its records could be signed in to."},{name:"updated_count",type:"integer",notNull:!0,default:0},{name:"skipped_count",type:"integer",notNull:!0,default:0},{name:"deleted_count",type:"integer",notNull:!0,default:0},{name:"deactivated_count",type:"integer",notNull:!0,default:0},{name:"error_count",type:"integer",notNull:!0,default:0},{name:"external_call_count",type:"integer",notNull:!0,default:0},{name:"issues",type:"jsonb",default:[]},{name:"errors",type:"jsonb",default:[]},{name:"cancel_requested",type:"boolean",notNull:!0,default:!1,comment:"Set by the cancel endpoint; the engine checks it at every batch boundary. A dropped HTTP connection is NOT a cancellation."},{name:"started_at",type:"timestamptz"},{name:"finished_at",type:"timestamptz"},{name:"pre_images_expire_at",type:"timestamptz",comment:"After this, the run can no longer be reverted and the UI says so rather than offering a button that quietly does nothing."},{name:"reverted_at",type:"timestamptz"},{name:"reverted_by",type:"uuid",references:{table:"users",column:"id"}},{name:"reverted_run_id",type:"uuid"}],indexes:[{columns:["mapping_id"]},{columns:["status"]},{columns:["target_entity"]},{columns:["created_at"]}]},{table_name:"integration_run_items",feature_set:["integrations"],add_base_columns:!0,is_form_data:!1,available_app_ids:["default_be"],available_schemas:["*"],excluded_schemas:[],excluded_methods:["POST","PUT","PATCH","DELETE"],bulk_endpoints_enabled:!1,group_name:"Integrations",columns:[{name:"run_id",type:"uuid",notNull:!0,references:{table:"integrationRuns",column:"id",onDelete:"cascade"}},{name:"target_entity",type:"varchar",length:120,notNull:!0},{name:"row_id",type:"uuid"},{name:"action",type:"varchar",length:20,notNull:!0,enumValues:["create","update","delete","deactivate"]},{name:"before",type:"jsonb",comment:"The row as it stood before this run touched it. This is what makes a successful run reversible; the bulk delete path already produced these rows and threw them away."},{name:"after",type:"jsonb"}],indexes:[{columns:["run_id"]},{columns:["run_id","action"]},{columns:["target_entity","row_id"]}]}];import{batch as batch2,createStore as createStore2}from"h-state";var initialState={connection:{status:"disconnected",clientId:null,subscribedTopics:[],error:null,reconnectAttempt:0},events:[],maxEvents:100},{useStore:usePubSubStore}=createStore2(initialState,{setConnectionStatus:(store)=>(status)=>{store.connection.status=status},setClientId:(store)=>(clientId)=>{store.connection.clientId=clientId},setSubscribedTopics:(store)=>(topics)=>{store.connection.subscribedTopics=topics},setError:(store)=>(error)=>{store.connection.error=error},setReconnectAttempt:(store)=>(attempt)=>{store.connection.reconnectAttempt=attempt},addEvent:(store)=>(event)=>{let updated=[event,...store.events];store.events=updated.slice(0,store.maxEvents)},clearEvents:(store)=>()=>{store.events=[]},reset:(store)=>()=>{batch2(()=>{store.connection.status="disconnected",store.connection.clientId=null,store.connection.subscribedTopics=[],store.connection.error=null,store.connection.reconnectAttempt=0,store.events=[]})}});import{useEffect}from"react";var SYNTHESIZED_AUTH_HEADER="x-nucleus-auth-synthesized";var WINDOW_EVENTS=["online","focus","pageshow"];function armReturnListeners(env,onReturn){let fired=!1,timer=null,setTimer=env.setTimeout??((fn,ms)=>{let handle=setTimeout(fn,ms);return handle.unref?.(),handle}),clearTimer=env.clearTimeout??((h)=>clearTimeout(h)),disarm=()=>{for(let type of WINDOW_EVENTS)env.window.removeEventListener(type,check);if(env.document.removeEventListener("visibilitychange",check),timer!==null)clearTimer(timer),timer=null};function fire(){if(fired)return;fired=!0,disarm(),onReturn()}function check(){if(fired)return;if(env.document.visibilityState==="hidden")return;if(env.navigator?.onLine===!1)return;fire()}for(let type of WINDOW_EVENTS)env.window.addEventListener(type,check);return env.document.addEventListener("visibilitychange",check),timer=setTimer(fire,120000),()=>{fired=!0,disarm()}}function buildWsUrl(config){let path=config.wsPath||"/api/events/subscribe",topics=(config.topics||["*"]).join(","),query=`?userId=${encodeURIComponent(config.userId)}&topics=${encodeURIComponent(topics)}`;if(config.wsUrl)return`${config.wsUrl.replace(/\/$/,"")}${path}${query}`;if(typeof window>"u")return"";return`${window.location.protocol==="https:"?"wss:":"ws:"}//${window.location.host}${path}${query}`}var connections=new Map,eventIdCounter=0;function log(conn,...args){if(conn.options.debug)console.log("[usePubSub]",...args)}function clearTimers(conn){if(conn.heartbeat)clearInterval(conn.heartbeat),conn.heartbeat=null;if(conn.reconnectTimeout)clearTimeout(conn.reconnectTimeout),conn.reconnectTimeout=null;if(conn.stopWaitingForReturn)conn.stopWaitingForReturn(),conn.stopWaitingForReturn=null}function detachSocket(conn){let ws=conn.ws;if(!ws)return;if(ws.onopen=null,ws.onmessage=null,ws.onerror=null,ws.onclose=null,ws.readyState===WebSocket.OPEN||ws.readyState===WebSocket.CONNECTING)ws.close();conn.ws=null}function startHeartbeat(conn){if(conn.heartbeat)clearInterval(conn.heartbeat);conn.heartbeat=setInterval(()=>{if(conn.ws?.readyState===WebSocket.OPEN)conn.ws.send(JSON.stringify({type:"ping"}))},conn.options.heartbeatInterval)}var HANDSHAKE_FAILURE_LIMIT=3;async function confirmSessionThenDecide(conn){let path=conn.options.sessionProbePath;if(!path){log(conn,"Handshake failing; no probe configured, continuing to reconnect"),conn.handshakeFailures=0,scheduleReconnect(conn);return}try{let res=await fetch(path,{credentials:"include",method:"GET"});if(res.status===401||res.status===403)if(res.headers.get(SYNTHESIZED_AUTH_HEADER))log(conn,"Probe 401 came from the proxy, not the API; treating as a network problem");else{conn.dispatch.setConnectionStatus("unauthorized"),conn.dispatch.setError(Error("Session is no longer valid")),log(conn,`Session probe says ${res.status}; not retrying`),waitForReturn(conn);return}else log(conn,`Session probe says ${res.status}; the session is fine, retrying`)}catch{log(conn,"Session probe could not be made; treating as a network problem")}conn.handshakeFailures=0,scheduleReconnect(conn)}function waitForReturn(conn){if(typeof window>"u"||typeof document>"u")return;if(connections.get(conn.url)!==conn)return;if(conn.stopWaitingForReturn)return;conn.stopWaitingForReturn=armReturnListeners({window,document,navigator:typeof navigator>"u"?void 0:navigator},()=>{if(connections.get(conn.url)!==conn)return;conn.stopWaitingForReturn=null,conn.reconnectAttempt=0,conn.handshakeFailures=0,log(conn,"Network or tab came back; reconnecting"),openSocket(conn)})}function scheduleReconnect(conn){if(!conn.options.autoReconnect)return;if(connections.get(conn.url)!==conn)return;if(conn.reconnectAttempt>=conn.options.maxReconnectAttempts){conn.dispatch.setConnectionStatus("disconnected"),conn.dispatch.setError(Error("Max reconnection attempts reached")),log(conn,"Max reconnect attempts reached; waiting for network or tab to return"),waitForReturn(conn);return}let delay=Math.min(conn.options.reconnectBaseDelay*2**conn.reconnectAttempt,conn.options.reconnectMaxDelay);if(conn.reconnectAttempt+=1,conn.dispatch.setConnectionStatus("reconnecting"),conn.dispatch.setReconnectAttempt(conn.reconnectAttempt),log(conn,`Reconnecting in ${delay}ms (attempt ${conn.reconnectAttempt})`),conn.reconnectTimeout)clearTimeout(conn.reconnectTimeout);conn.reconnectTimeout=setTimeout(()=>{if(connections.get(conn.url)===conn)openSocket(conn)},delay)}function handleMessage(conn,raw){let message;try{message=JSON.parse(raw)}catch{log(conn,"Failed to parse message");return}switch(message.type){case"connected":conn.reconnectAttempt=0,conn.handshakeFailures=0,conn.dispatch.setConnectionStatus("connected"),conn.dispatch.setClientId(message.clientId),conn.dispatch.setSubscribedTopics(message.subscribedTopics),conn.dispatch.setReconnectAttempt(0),conn.dispatch.setError(null),log(conn,"Connected, clientId:",message.clientId);break;case"subscribed":conn.dispatch.setSubscribedTopics(message.topics),log(conn,"Subscribed to:",message.topics);break;case"event":{if(conn.dispatch.addEvent({id:`evt_${Date.now()}_${eventIdCounter++}`,topic:message.topic,data:message.data,timestamp:message.timestamp,receivedAt:Date.now(),messageId:message.messageId,isRedelivery:message.isRedelivery}),message.messageId&&conn.ws?.readyState===WebSocket.OPEN)conn.ws.send(JSON.stringify({type:"ack",messageId:message.messageId}));break}case"pong":break;case"error":conn.dispatch.setError(Error(message.error)),log(conn,"Server error:",message.error);break}}function openSocket(conn){if(conn.ws?.readyState===WebSocket.OPEN||conn.ws?.readyState===WebSocket.CONNECTING)return;detachSocket(conn),conn.dispatch.setConnectionStatus("connecting"),conn.dispatch.setError(null),log(conn,"Connecting to:",conn.url);let ws=new WebSocket(conn.url);conn.ws=ws;let opened=!1;ws.onopen=()=>{log(conn,"WebSocket opened"),opened=!0,startHeartbeat(conn)},ws.onmessage=(event)=>handleMessage(conn,event.data),ws.onerror=()=>{log(conn,"WebSocket error"),conn.dispatch.setError(Error("WebSocket connection error"))},ws.onclose=(event)=>{if(log(conn,"WebSocket closed",event.code,event.reason),conn.heartbeat)clearInterval(conn.heartbeat),conn.heartbeat=null;if(conn.dispatch.setConnectionStatus("disconnected"),conn.dispatch.setClientId(null),connections.get(conn.url)!==conn)return;if(event.code===4001){log(conn,"Handshake bearer refused (4001); asking whether the session is still valid"),confirmSessionThenDecide(conn);return}if(!opened){if(conn.handshakeFailures+=1,conn.handshakeFailures>=HANDSHAKE_FAILURE_LIMIT){confirmSessionThenDecide(conn);return}}else conn.handshakeFailures=0;scheduleReconnect(conn)}}function acquire(url,dispatch,options){let conn=connections.get(url);if(!conn)conn={url,ws:null,refCount:0,reconnectAttempt:0,handshakeFailures:0,stopWaitingForReturn:null,heartbeat:null,reconnectTimeout:null,dispatch,options},connections.set(url,conn);else conn.dispatch=dispatch,conn.options=options;conn.refCount+=1,openSocket(conn)}function release(url){let conn=connections.get(url);if(!conn)return;if(conn.refCount-=1,conn.refCount>0)return;connections.delete(url),clearTimers(conn),detachSocket(conn),conn.dispatch.setConnectionStatus("disconnected"),conn.dispatch.setClientId(null)}function send(url,payload){let ws=connections.get(url)?.ws;if(ws?.readyState===WebSocket.OPEN)ws.send(JSON.stringify(payload))}function usePubSub(config){let store=usePubSubStore(),url=config.userId?buildWsUrl(config):"";return useEffect(()=>{if(!url)return;let dispatch={setConnectionStatus:store.setConnectionStatus,setClientId:store.setClientId,setSubscribedTopics:store.setSubscribedTopics,setError:store.setError,setReconnectAttempt:store.setReconnectAttempt,addEvent:store.addEvent},options={autoReconnect:config.autoReconnect??!0,maxReconnectAttempts:config.maxReconnectAttempts??10,sessionProbePath:config.sessionProbePath??"/auth/me",reconnectBaseDelay:config.reconnectBaseDelay??1000,reconnectMaxDelay:config.reconnectMaxDelay??30000,heartbeatInterval:config.heartbeatInterval??30000,debug:config.debug??!1};return acquire(url,dispatch,options),()=>release(url)},[url]),{isConnected:store.connection.status==="connected",isConnecting:store.connection.status==="connecting"||store.connection.status==="reconnecting",sessionExpired:store.connection.status==="unauthorized",clientId:store.connection.clientId,subscribedTopics:store.connection.subscribedTopics,events:store.events,error:store.connection.error,reconnectAttempt:store.connection.reconnectAttempt,connect:()=>{let conn=connections.get(url);if(conn)openSocket(conn)},disconnect:()=>release(url),subscribe:(topics)=>send(url,{type:"subscribe",topics}),unsubscribe:(topics)=>send(url,{type:"unsubscribe",topics}),clearEvents:store.clearEvents,getEventsByTopic:(topic)=>store.events.filter((e)=>e.topic===topic)}}function createRateWindow(windowMs=5000){let samples=[];return{push(at,bytes){let last=samples[samples.length-1];if(last&&at<=last.at){last.bytes=Math.max(last.bytes,bytes);return}samples.push({at,bytes});let cutoff=at-windowMs;while(samples.length>2&&samples[0].at<cutoff)samples.shift()},ratePerSecond(){if(samples.length<2)return null;let first=samples[0],last=samples[samples.length-1],spanMs=last.at-first.at;if(spanMs<700)return null;let delta=last.bytes-first.bytes;if(delta<=0)return null;return delta*1000/spanMs},reset(){samples.length=0}}}function uploadFraction(sentBytes,totalBytes){if(totalBytes===null||totalBytes<=0)return null;return Math.min(1,Math.max(0,sentBytes/totalBytes))}function uploadPercent(sentBytes,totalBytes){let fraction=uploadFraction(sentBytes,totalBytes);if(fraction===null)return null;if(fraction>=1)return 100;return Math.min(99,Math.floor(fraction*100))}function secondsRemaining(sentBytes,totalBytes,bytesPerSecond){if(totalBytes===null||totalBytes<=0)return null;let left=totalBytes-sentBytes;if(left<=0)return 0;if(bytesPerSecond===null||bytesPerSecond<=0)return null;return left/bytesPerSecond}function buildProgress(params){let{sentBytes,totalBytes,startedAt,now,window:window2,retrying=!1}=params;window2.push(now,sentBytes);let bytesPerSecond=window2.ratePerSecond();return{sentBytes,totalBytes,fraction:uploadFraction(sentBytes,totalBytes),percent:uploadPercent(sentBytes,totalBytes),bytesPerSecond,secondsRemaining:secondsRemaining(sentBytes,totalBytes,bytesPerSecond),elapsedMs:Math.max(0,now-startedAt),retrying}}var MIN_REPORT_INTERVAL_MS=120;function uploadWithProgress(body,options){let xhr=options.createRequest?options.createRequest():new XMLHttpRequest,window2=createRateWindow(),startedAt=Date.now(),lastReportAt=0,settled=!1,lastSent=0,lastTotal=null,report=(sentBytes,totalBytes,force)=>{if(!options.onProgress)return;let now=Date.now();if(!force&&now-lastReportAt<MIN_REPORT_INTERVAL_MS)return;lastReportAt=now,options.onProgress(buildProgress({sentBytes,totalBytes,startedAt,now,window:window2}))};return{done:new Promise((resolve)=>{let settle=(outcome)=>{if(settled)return;settled=!0,resolve(outcome)};xhr.open("POST",options.url,!0),xhr.withCredentials=options.withCredentials!==!1;for(let[key,value]of Object.entries(options.headers??{}))xhr.setRequestHeader(key,value);if(options.timeoutMs)xhr.timeout=options.timeoutMs;xhr.upload.onprogress=(event)=>{lastSent=event.loaded,lastTotal=event.lengthComputable?event.total:null,report(lastSent,lastTotal,!1)},xhr.upload.onload=()=>{report(lastTotal??lastSent,lastTotal,!0)},xhr.onload=()=>{let parsed=null;try{parsed=xhr.responseText?JSON.parse(xhr.responseText):null}catch{parsed=null}if(xhr.status<200||xhr.status>=300){settle({ok:!1,status:xhr.status,message:serverMessage(parsed)||xhr.statusText||"",cancelled:!1});return}let data=options.parse?options.parse(parsed):parsed;if(data===null||data===void 0){settle({ok:!1,status:xhr.status,message:serverMessage(parsed)||"The server accepted the upload but returned nothing usable",cancelled:!1});return}settle({ok:!0,data})},xhr.onerror=()=>{settle({ok:!1,status:null,message:"The upload could not reach the server",cancelled:!1})},xhr.ontimeout=()=>{settle({ok:!1,status:null,message:"The upload timed out",cancelled:!1})},xhr.onabort=()=>{settle({ok:!1,status:null,message:"",cancelled:!0})},xhr.send(body)}),abort:()=>{xhr.abort()}}}function serverMessage(parsed){if(!parsed||typeof parsed!=="object")return"";let body=parsed;for(let key of["message","error","detail"]){let value=body[key];if(typeof value==="string"&&value)return value}return""}import{useCallback,useRef,useState}from"react";function useUpload(options){let[progress,setProgress]=useState(null),[uploading,setUploading]=useState(!1),[outcome,setOutcome]=useState(null),handleRef=useRef(null),optionsRef=useRef(options);optionsRef.current=options;let runId=useRef(0),cancel=useCallback(()=>{handleRef.current?.abort(),handleRef.current=null},[]),reset=useCallback(()=>{setProgress(null),setOutcome(null)},[]),start=useCallback(async(body,overrides)=>{handleRef.current?.abort();let id=++runId.current;setUploading(!0),setOutcome(null),setProgress(null);let merged={...optionsRef.current,...overrides},handle=uploadWithProgress(body,{...merged,onProgress:(p)=>{if(id!==runId.current)return;setProgress(p),merged.onProgress?.(p)}});handleRef.current=handle;let result=await handle.done;if(id!==runId.current)return result;return handleRef.current=null,setUploading(!1),setOutcome(result),result},[]);return{progress,uploading,outcome,start,cancel,reset}}import{createHash as createHash6,randomUUID as randomUUID11}from"crypto";import{pushSchema}from"drizzle-kit/api";import{and as and29,desc as desc7,eq as eq69,lt as lt3,ne as ne4,sql as sql14}from"drizzle-orm";import{drizzle as drizzle2}from"drizzle-orm/node-postgres";import{pgSchema as pgSchema2}from"drizzle-orm/pg-core";import{join as join3}from"path";class LiveStorageProvider{resolve;constructor(resolve){this.resolve=resolve}get kind(){return this.resolve().kind}get describe(){return this.resolve().describe}isAvailable(){return this.resolve().isAvailable()}exists(relativePath){return this.resolve().exists(relativePath)}stat(relativePath){return this.resolve().stat(relativePath)}read(relativePath){return this.resolve().read(relativePath)}write(relativePath,data){return this.resolve().write(relativePath,data)}delete(relativePath){return this.resolve().delete(relativePath)}ensureDir(relativeDir){return this.resolve().ensureDir(relativeDir)}list(relativeDir){return this.resolve().list(relativeDir)}localPath(relativePath){return this.resolve().localPath(relativePath)}close(){return this.resolve().close()}}import{mkdir,readdir,stat,unlink}from"fs/promises";import{dirname,join}from"path";function normalizeRelativePath(relativePath){let cleaned=relativePath.replace(/\\/g,"/").replace(/^\/+/,""),segments=[];for(let segment of cleaned.split("/")){if(!segment||segment===".")continue;if(segment==="..")throw Error(`Path traversal rejected: ${relativePath}`);segments.push(segment)}return segments.join("/")}class LocalStorageProvider{kind="local";describe;basePath;constructor(basePath,_deps){this.basePath=basePath,this.describe=`local:${basePath}`}resolve(relativePath){return join(this.basePath,normalizeRelativePath(relativePath))}async isAvailable(){try{return await mkdir(this.basePath,{recursive:!0}),!0}catch{return!1}}async exists(relativePath){return Bun.file(this.resolve(relativePath)).exists()}async stat(relativePath){try{let info=await stat(this.resolve(relativePath));return{size:info.size,modifiedAt:info.mtime,isDirectory:info.isDirectory()}}catch{return null}}async read(relativePath){let file=Bun.file(this.resolve(relativePath));if(!await file.exists())return null;return Buffer.from(await file.arrayBuffer())}async write(relativePath,data){let full=this.resolve(relativePath);await mkdir(dirname(full),{recursive:!0}),await Bun.write(full,data)}async delete(relativePath){try{return await unlink(this.resolve(relativePath)),!0}catch{return!1}}async ensureDir(relativeDir){await mkdir(this.resolve(relativeDir),{recursive:!0})}async list(relativeDir){try{return await readdir(this.resolve(relativeDir))}catch{return[]}}async localPath(relativePath){let full=this.resolve(relativePath);return await Bun.file(full).exists()?full:null}async close(){}}import{stat as fsStat,mkdir as mkdir2,rename,unlink as unlink2}from"fs/promises";import{dirname as dirname2,join as join2}from"path";var DEFAULT_TIMEOUT_MS=15000,DEFAULT_CACHE_TTL_SECONDS=300;class SmbStorageProvider{kind="smb";describe;getConfig;logger;client=null;clientFingerprint="";cacheDir;cacheTtlMs;cacheEnabled;inFlight=new Map;constructor(getConfig,deps,defaultCacheDir){this.getConfig=getConfig,this.logger=deps.logger;let config=getConfig();this.cacheEnabled=config.cache?.enabled!==!1,this.cacheDir=config.cache?.path||defaultCacheDir,this.cacheTtlMs=(config.cache?.ttlSeconds??DEFAULT_CACHE_TTL_SECONDS)*1000,this.describe=`smb://${config.host}/${config.share}${config.basePath?`/${config.basePath}`:""}`}async getClient(){let{host,share,domain,username,password,port,timeoutMs}=this.getConfig(),fingerprint=`${host}|${share}|${domain}|${username}|${password}|${port}`;if(this.client&&fingerprint===this.clientFingerprint)return this.client;if(this.client){try{this.client.disconnect()}catch{}this.client=null}let imported=await Promise.resolve().then(() => __toESM(require_smb2(),1)).catch(()=>{throw Error('storage.provider is "smb" but the optional @marsaud/smb2 dependency is not installed \u2014 run: bun add @marsaud/smb2')}),SMB2=imported.default??imported;return this.client=new SMB2({share:`\\\\${host}\\${share}`,domain:domain||"WORKGROUP",username,password,port:port??445,autoCloseTimeout:timeoutMs??DEFAULT_TIMEOUT_MS}),this.clientFingerprint=fingerprint,this.client}async call(fn){let client=await this.getClient(),timeoutMs=this.getConfig().timeoutMs??DEFAULT_TIMEOUT_MS;return new Promise((resolve,reject)=>{let settled=!1,timer=setTimeout(()=>{if(settled)return;settled=!0,reject(Error(`SMB operation timed out after ${timeoutMs}ms (${this.describe})`))},timeoutMs);fn(client,(err,value)=>{if(settled)return;if(settled=!0,clearTimeout(timer),err)reject(err);else resolve(value)})})}remotePath(relativePath){let normalized=normalizeRelativePath(relativePath),configuredBase=this.getConfig().basePath,base=configuredBase?normalizeRelativePath(configuredBase):"";return(base?`${base}/${normalized}`:normalized).replace(/\//g,"\\")}cachePath(relativePath){return join2(this.cacheDir,normalizeRelativePath(relativePath))}async isAvailable(){try{return await this.call((client,cb)=>client.readdir(this.getConfig().basePath?this.remotePath(""):"",cb)),!0}catch(error){return this.logger.warn("[Storage/SMB] Share is not reachable",{target:this.describe,error:error instanceof Error?error.message:String(error)}),!1}}async exists(relativePath){try{return await this.call((client,cb)=>client.exists(this.remotePath(relativePath),cb))}catch{return!1}}async stat(relativePath){try{let info=await this.call((client,cb)=>client.stat(this.remotePath(relativePath),cb));return{size:Number(info.size),modifiedAt:new Date(info.mtime),isDirectory:typeof info.isDirectory==="function"?info.isDirectory():await this.looksLikeDirectory(relativePath)}}catch{return null}}async read(relativePath){try{return await this.call((client,cb)=>client.readFile(this.remotePath(relativePath),cb))}catch(error){return this.logger.warn("[Storage/SMB] Read failed",{path:relativePath,error:error instanceof Error?error.message:String(error)}),null}}async write(relativePath,data){let parent=normalizeRelativePath(relativePath).split("/").slice(0,-1).join("/");if(parent)await this.ensureDir(parent);let payload=typeof data==="string"?Buffer.from(data,"utf-8"):Buffer.from(data);if(await this.call((client,cb)=>client.writeFile(this.remotePath(relativePath),payload,cb)),this.cacheEnabled){let target=this.cachePath(relativePath);await mkdir2(dirname2(target),{recursive:!0}).catch(()=>{}),await Bun.write(target,payload).catch(()=>{})}}async delete(relativePath){try{if(await this.call((client,cb)=>client.unlink(this.remotePath(relativePath),cb)),this.cacheEnabled)await unlink2(this.cachePath(relativePath)).catch(()=>{});return!0}catch{return!1}}async ensureDir(relativeDir){let normalized=normalizeRelativePath(relativeDir);if(!normalized)return;let segments=normalized.split("/");for(let i=1;i<=segments.length;i++){let partial=segments.slice(0,i).join("/");try{await this.call((client,cb)=>client.mkdir(this.remotePath(partial),cb))}catch{}}}async looksLikeDirectory(relativePath){try{return await this.call((client,cb)=>client.readdir(this.remotePath(relativePath),cb)),!0}catch{return!1}}async list(relativeDir){try{return await this.call((client,cb)=>client.readdir(this.remotePath(relativeDir),cb))}catch{return[]}}async localPath(relativePath){if(!this.cacheEnabled)return null;let target=this.cachePath(relativePath),cached=await fsStat(target).catch(()=>null);if(cached&&Date.now()-cached.mtimeMs<this.cacheTtlMs)return target;let pending=this.inFlight.get(target);if(pending)return pending;let download=(async()=>{let data=await this.read(relativePath);if(!data)return null;await mkdir2(dirname2(target),{recursive:!0}).catch(()=>{});let tmp=`${target}.${process.pid}.tmp`;try{return await Bun.write(tmp,data),await rename(tmp,target),target}catch(error){return await unlink2(tmp).catch(()=>{}),this.logger.warn("[Storage/SMB] Could not cache file locally",{path:relativePath,error:error instanceof Error?error.message:String(error)}),null}})().finally(()=>{this.inFlight.delete(target)});return this.inFlight.set(target,download),download}async close(){try{this.client?.disconnect()}catch{}this.client=null}}var DEFAULT_EXCLUDES=[".cache",".tmp","lost+found"];async function migrateStorage(options,deps){let{from,to,onProgress}=options,exclude=new Set([...DEFAULT_EXCLUDES,...options.exclude??[]]),maxFiles=options.maxFiles??50000,report={copied:0,skipped:0,failed:0,bytes:0,failures:[],truncated:!1},walk=async(dir)=>{if(report.truncated)return;let entries=await from.list(dir);for(let entry of entries){if(report.truncated)return;if(!entry||entry==="."||entry==="..")continue;let relative=dir?`${dir}/${entry}`:entry;if(exclude.has(entry))continue;let info=await from.stat(relative);if(!info){if(report.failed+=1,report.failures.length<50)report.failures.push(relative);continue}if(info.isDirectory){await walk(relative);continue}if(report.copied+report.skipped>=maxFiles){report.truncated=!0;return}if(await to.exists(relative)){report.skipped+=1;continue}try{let bytes=await from.read(relative);if(!bytes){if(report.failed+=1,report.failures.length<50)report.failures.push(relative);continue}let parent=relative.includes("/")?relative.slice(0,relative.lastIndexOf("/")):"";if(parent)await to.ensureDir(parent);await to.write(relative,bytes),report.copied+=1,report.bytes+=bytes.length,onProgress?.(report.copied,relative)}catch(error){if(report.failed+=1,report.failures.length<50)report.failures.push(relative);deps.logger.warn("[Storage] Migration copy failed",{path:relative,error:error instanceof Error?error.message:String(error)})}}};return await walk(""),deps.logger.info("[Storage] Migration finished",{from:from.describe,to:to.describe,copied:report.copied,skipped:report.skipped,failed:report.failed,truncated:report.truncated}),report}function parseSmbUrl(value){let raw=(value??"").trim();if(!raw)return{host:"",share:""};let withoutScheme=raw.replace(/^smb:\/\//i,"").replace(/^\\\\/,"").replace(/\\/g,"/"),[host="",share="",...rest]=withoutScheme.split("/").filter(Boolean);return{host,share,basePath:rest.length>0?rest.join("/"):void 0}}function createStorageProvider(options,deps){let{basePath,smb,resolveCredential}=options,setting=(key,configValue)=>resolveCredential?.("storage.smb",key,configValue)??configValue,resolveSmbConfig=()=>{let fromUrl=parseSmbUrl(setting("url"));return{...smb,host:fromUrl.host||setting("host",smb?.host)||"",share:fromUrl.share||setting("share",smb?.share)||"",basePath:fromUrl.basePath??setting("base_path",smb?.basePath),domain:setting("domain",smb?.domain),username:setting("username",smb?.username),password:setting("password",smb?.password)}},resolveKind=()=>{let chosen=resolveCredential?.("storage","provider",options.provider);return chosen==="smb"?"smb":chosen==="local"?"local":options.provider??"local"},local=new LocalStorageProvider(basePath,deps),smbProvider=null,smbSignature="",warnedIncomplete=!1;return new LiveStorageProvider(()=>{if(resolveKind()!=="smb")return local;let config=resolveSmbConfig();if(!config.host||!config.share){if(!warnedIncomplete)warnedIncomplete=!0,deps.logger.error('[Storage] provider is "smb" but the host/share are not set \u2014 using local storage until they are');return local}warnedIncomplete=!1;let signature=JSON.stringify([config.host,config.share,config.basePath,config.domain,config.username,config.password,config.port]);if(!smbProvider||signature!==smbSignature)smbProvider?.close(),smbProvider=new SmbStorageProvider(resolveSmbConfig,deps,join3(basePath,".cache","smb")),smbSignature=signature,deps.logger.info("[Storage] SMB target configured",{target:smbProvider.describe});return smbProvider})}init_Services();init_ApiKey();init_Middleware();init_Captcha();init_Domain();init_ServerLogBuffer();init_transports();init_Secrets();var exceedsBodyLimit=(contentLengthHeader,maxBytes)=>{if(!maxBytes||maxBytes<=0)return!1;if(!contentLengthHeader)return!1;let len=Number(contentLengthHeader);if(!Number.isFinite(len)||len<0)return!1;return len>maxBytes};init_internalHeaders();init_response();function createInboundGuard(options={}){return({request,set})=>{for(let header of INTERNAL_HEADERS)request.headers.delete(header);if(exceedsBodyLimit(request.headers.get("content-length"),options.maxRequestBodyBytes))return set.status=413,Response.json(withEnvelopeFlags({isSuccess:!1,message:"Request body too large",status:413,errors:[{message:"Request body too large"}],data:null}));return}}function columnOfScope(scope){if(!scope)return null;let[field]=scope.split("="),trimmed=field?.trim();return trimmed?trimmed:null}function findUnstampedOwnership(assignments,ownership){let required=new Map;for(let{table,column}of ownership){let columns=required.get(table)??new Set;columns.add(column),required.set(table,columns)}let seen=new Map;for(let assignment of assignments){let scopeColumn=columnOfScope(assignment.scope);for(let claim of assignment.claims??[]){if(!claim.startsWith("post."))continue;let table=claim.slice(5),columns=required.get(table);if(!columns)continue;let perTable=seen.get(table)??new Map;for(let column of columns){let entry=perTable.get(column)??{scoped:new Set,unscoped:new Set},role=assignment.role??"(unnamed role)";if(scopeColumn===column)entry.scoped.add(role);else entry.unscoped.add(role);perTable.set(column,entry)}seen.set(table,perTable)}}let findings=[];for(let[table,perTable]of seen)for(let[column,entry]of perTable){if(entry.scoped.size===0||entry.unscoped.size===0)continue;findings.push({table,column,unscopedRoles:[...entry.unscoped].sort(),scopedRoles:[...entry.scoped].sort()})}return findings.sort((a,b)=>a.table.localeCompare(b.table)||a.column.localeCompare(b.column))}function describeUnstamped(findings){return findings.map((f)=>`[Authorization] ${f.table}.${f.column} is stamped for ${f.scopedRoles.join(", ")} (scoped create) but NOT for ${f.unscopedRoles.join(", ")}. Those roles must send ${f.column} themselves or their writes fail with "${f.column} is required". `+"Give them a scoped claim, or have the client send it \u2014 a scoped caller's value is "+"overridden server-side either way.")}init_response();init_routeCollector();init_routeMount();init_routeRecording();init_lifetime();import{and as and11,eq as eq16,inArray as inArray3,isNull as isNull2}from"drizzle-orm";var DEFAULT_CHAT_CONFIG={enabled:!1,basePath:"/chat",groupsEnabled:!0,maxGroupParticipants:256,maxMessageLength:8000,editingEnabled:!0,deletionEnabled:!0,typingIndicators:!0,readReceipts:!0,presence:!0,realtimeTopicPrefix:"chat",messagePageSize:50,moderatorRoles:[],attachments:{enabled:!0,maxPerMessage:10,maxFileSizeBytes:null,allowedMimeTypes:[]}};function mergeChatConfig(input){let base=DEFAULT_CHAT_CONFIG;if(!input)return base;return{enabled:input.enabled??base.enabled,basePath:input.basePath??base.basePath,groupsEnabled:input.groupsEnabled??base.groupsEnabled,maxGroupParticipants:input.maxGroupParticipants??base.maxGroupParticipants,maxMessageLength:input.maxMessageLength??base.maxMessageLength,editingEnabled:input.editingEnabled??base.editingEnabled,deletionEnabled:input.deletionEnabled??base.deletionEnabled,typingIndicators:input.typingIndicators??base.typingIndicators,readReceipts:input.readReceipts??base.readReceipts,presence:input.presence??base.presence,realtimeTopicPrefix:input.realtimeTopicPrefix??base.realtimeTopicPrefix,messagePageSize:input.messagePageSize??base.messagePageSize,moderatorRoles:input.moderatorRoles??base.moderatorRoles,attachments:{enabled:input.attachments?.enabled??base.attachments.enabled,maxPerMessage:input.attachments?.maxPerMessage??base.attachments.maxPerMessage,maxFileSizeBytes:input.attachments?.maxFileSizeBytes??base.attachments.maxFileSizeBytes,allowedMimeTypes:input.attachments?.allowedMimeTypes??base.attachments.allowedMimeTypes}}}function getCol(table,col7){return table[col7]}function getTable(schemaTables,name2,logger2){return resolveSchemaTable(schemaTables,name2,logger2)}function computeDirectKey(userA,userB){return[userA,userB].sort().join(":")}function kindFromMime(mime){if(!mime)return"file";if(mime.startsWith("image/"))return"image";if(mime.startsWith("video/"))return"video";if(mime.startsWith("audio/"))return"audio";return"file"}function str3(value){return typeof value==="string"?value:null}function num2(value){return typeof value==="number"?value:null}function date(value){if(value instanceof Date)return value;if(typeof value==="string")return new Date(value);return null}function cdnUrl(cdnBasePath,fileId){if(!fileId)return null;return`${cdnBasePath}/${fileId}`}function mapConversation(row,cdnBasePath){let avatarFileId=str3(row.avatarFileId);return{id:String(row.id),type:str3(row.type)??"direct",title:str3(row.title),description:str3(row.description),avatarFileId,avatarUrl:cdnUrl(cdnBasePath,avatarFileId),directKey:str3(row.directKey),lastMessageAt:date(row.lastMessageAt),lastMessagePreview:str3(row.lastMessagePreview),lastMessageSenderId:str3(row.lastMessageSenderId),createdBy:str3(row.createdBy),createdAt:date(row.createdAt)??new Date(0),updatedAt:date(row.updatedAt)??new Date(0),isActive:row.isActive!==!1}}function mapParticipant(row){return{id:String(row.id),conversationId:String(row.conversationId),userId:String(row.userId),role:str3(row.role)??"member",lastReadMessageId:str3(row.lastReadMessageId),lastReadAt:date(row.lastReadAt),unreadCount:num2(row.unreadCount)??0,muted:row.muted===!0,mutedUntil:date(row.mutedUntil),notificationsEnabled:row.notificationsEnabled!==!1,leftAt:date(row.leftAt),joinedAt:date(row.createdAt)??new Date(0)}}function mapAttachment(row,cdnBasePath){let fileId=String(row.fileId);return{id:String(row.id),messageId:String(row.messageId),conversationId:String(row.conversationId),fileId,kind:str3(row.kind)??"file",originalName:str3(row.originalName),mimeType:str3(row.mimeType),size:num2(row.size),width:num2(row.width),height:num2(row.height),duration:num2(row.duration),url:`${cdnBasePath}/${fileId}`}}function mapMessage(row,attachments){let metadata=row.metadata;return{id:String(row.id),conversationId:String(row.conversationId),senderId:str3(row.senderId),content:str3(row.content),contentType:str3(row.contentType)??"text",replyToId:str3(row.replyToId),metadata:metadata&&typeof metadata==="object"?metadata:null,clientMessageId:str3(row.clientMessageId),editedAt:date(row.editedAt),deletedAt:date(row.deletedAt),createdAt:date(row.createdAt)??new Date(0),updatedAt:date(row.updatedAt)??new Date(0),attachments}}function buildPreview(content,contentType,attachmentCount){if(content&&content.trim().length>0){let trimmed=content.trim();return trimmed.length>280?`${trimmed.slice(0,277)}...`:trimmed}if(attachmentCount>0){if(contentType==="image")return attachmentCount>1?`${attachmentCount} photos`:"Photo";if(contentType==="video")return attachmentCount>1?`${attachmentCount} videos`:"Video";if(contentType==="audio")return"Audio";return attachmentCount>1?`${attachmentCount} files`:"File"}if(contentType==="system")return"System message";return""}async function checkAttachmentFileAccess(args){let{db,schemaTables,fileId,userId}=args,attachments=getTable(schemaTables,"chat_message_attachments"),participants=getTable(schemaTables,"chat_participants");if(!attachments||!participants)return"not_attachment";let linked=await db.select().from(attachments).where(eq16(getCol(attachments,"fileId"),fileId));if(linked.length===0)return"not_attachment";if(!userId)return"denied";let conversationIds=[...new Set(linked.map((row)=>String(row.conversationId)))];return(await db.select().from(participants).where(and11(inArray3(getCol(participants,"conversationId"),conversationIds),eq16(getCol(participants,"userId"),userId),isNull2(getCol(participants,"leftAt")))).limit(1)).length>0?"allowed":"denied"}import{and as and13,desc as desc3,eq as eq18,inArray as inArray5}from"drizzle-orm";import{and as and12,eq as eq17,inArray as inArray4}from"drizzle-orm";class ChatError extends Error{code;constructor(code,message){super(message);this.code=code,this.name="ChatError"}}function resolveChatTables(ctx){let conversations=getTable(ctx.schemaTables,"chat_conversations",ctx.logger),participants=getTable(ctx.schemaTables,"chat_participants",ctx.logger),messages=getTable(ctx.schemaTables,"chat_messages",ctx.logger);if(!conversations||!participants||!messages)throw new ChatError("disabled","Chat tables are not available in this schema");let attachments=getTable(ctx.schemaTables,"chat_message_attachments")??null,files=getTable(ctx.schemaTables,"files")??null;return{conversations,participants,messages,attachments,files}}function chatTopic(ctx){return ctx.config.realtimeTopicPrefix||"chat"}async function getConversationRow(ctx,tables,conversationId){return(await ctx.db.select().from(tables.conversations).where(eq17(getCol(tables.conversations,"id"),conversationId)).limit(1))[0]??null}async function getParticipant(ctx,tables,conversationId,userId){let row=(await ctx.db.select().from(tables.participants).where(and12(eq17(getCol(tables.participants,"conversationId"),conversationId),eq17(getCol(tables.participants,"userId"),userId))).limit(1))[0];return row?mapParticipant(row):null}async function requireActiveMembership(ctx,tables,conversationId,userId){let participant=await getParticipant(ctx,tables,conversationId,userId);if(!participant||participant.leftAt)throw new ChatError("forbidden","You are not a participant of this conversation");return participant}async function listParticipants(ctx,tables,conversationId,includeLeft=!1){let all=(await ctx.db.select().from(tables.participants).where(eq17(getCol(tables.participants,"conversationId"),conversationId))).map(mapParticipant);return includeLeft?all:all.filter((p)=>!p.leftAt)}async function assertUsersExist(ctx,userIds){let unique=[...new Set(userIds)];if(unique.length===0)return;let usersTable=getTable(ctx.schemaTables,"users",ctx.logger);if(!usersTable)throw new ChatError("disabled","Users table is not available in this schema");let rows=await ctx.db.select({id:getCol(usersTable,"id")}).from(usersTable).where(inArray4(getCol(usersTable,"id"),unique)),found=new Set(rows.map((r)=>r.id));for(let id of unique)if(!found.has(id))throw new ChatError("not_found",`User ${id} does not exist`)}function deliverToParticipants(ctx,participants,event,exceptUserId){if(!ctx.broadcaster)return;let topic=chatTopic(ctx);for(let participant of participants){if(participant.leftAt)continue;if(exceptUserId&&participant.userId===exceptUserId)continue;ctx.broadcaster.broadcastToUser(participant.userId,topic,event)}}async function buildSummary(ctx,tables,conversationRow,userId,participantsOverride){let conversation=mapConversation(conversationRow,ctx.cdnBasePath),participants=participantsOverride??await listParticipants(ctx,tables,conversation.id),myParticipant=participants.find((p)=>p.userId===userId)??null;return{...conversation,participants,myParticipant,unreadCount:myParticipant?.unreadCount??0}}async function getOrCreateDirect(ctx,params){let tables=resolveChatTables(ctx),{userId,targetUserId}=params;if(!targetUserId||targetUserId===userId)throw new ChatError("validation","A different target user is required");await assertUsersExist(ctx,[targetUserId]);let directKey=computeDirectKey(userId,targetUserId),existing=(await ctx.db.select().from(tables.conversations).where(eq18(getCol(tables.conversations,"directKey"),directKey)).limit(1))[0];if(existing)return await rejoinIfNeeded(ctx,tables,String(existing.id),[userId,targetUserId]),buildSummary(ctx,tables,existing,userId);let now=new Date,conversationRow;try{conversationRow=(await ctx.db.insert(tables.conversations).values({type:"direct",directKey,createdBy:userId,lastMessageAt:now,lastMessagePreview:""}).returning())[0]}catch(_err){let raced=(await ctx.db.select().from(tables.conversations).where(eq18(getCol(tables.conversations,"directKey"),directKey)).limit(1))[0];if(!raced)throw new ChatError("conflict","Failed to create direct conversation");return await rejoinIfNeeded(ctx,tables,String(raced.id),[userId,targetUserId]),buildSummary(ctx,tables,raced,userId)}let conversationId=String(conversationRow.id);await ctx.db.insert(tables.participants).values([{conversationId,userId,role:"member",createdBy:userId},{conversationId,userId:targetUserId,role:"member",createdBy:userId}]);let summary=await buildSummary(ctx,tables,conversationRow,userId);return deliverToParticipants(ctx,summary.participants,{type:"conversation.created",conversationId,conversation:summary},userId),summary}async function createGroup(ctx,params){if(!ctx.config.groupsEnabled)throw new ChatError("disabled","Group conversations are disabled");let tables=resolveChatTables(ctx),{creatorId,title}=params;if(!title||title.trim().length===0)throw new ChatError("validation","A group title is required");let memberIds=[...new Set([creatorId,...params.participantIds])];if(memberIds.length>ctx.config.maxGroupParticipants)throw new ChatError("validation",`A group cannot exceed ${ctx.config.maxGroupParticipants} participants`);if(await assertUsersExist(ctx,memberIds),params.avatarFileId){if(!tables.files)throw new ChatError("disabled","Avatar files require storage to be enabled");let avatarRow=(await ctx.db.select().from(tables.files).where(eq18(getCol(tables.files,"id"),params.avatarFileId)).limit(1))[0],owner=avatarRow?String(avatarRow.uploadedBy??avatarRow.createdBy??""):"";if(!avatarRow||owner!==creatorId)throw new ChatError("forbidden","Avatar file is not owned by the creator")}let now=new Date,conversationRow=(await ctx.db.insert(tables.conversations).values({type:"group",title:title.trim(),description:params.description??null,avatarFileId:params.avatarFileId??null,createdBy:creatorId,lastMessageAt:now,lastMessagePreview:""}).returning())[0],conversationId=String(conversationRow.id);await ctx.db.insert(tables.participants).values(memberIds.map((memberId)=>({conversationId,userId:memberId,role:memberId===creatorId?"owner":"member",createdBy:creatorId})));let summary=await buildSummary(ctx,tables,conversationRow,creatorId);return deliverToParticipants(ctx,summary.participants,{type:"conversation.created",conversationId,conversation:summary},creatorId),summary}async function listConversations(ctx,params){let tables=resolveChatTables(ctx),limit=Math.min(Math.max(params.limit??30,1),100),offset=Math.max(params.offset??0,0),myParticipants=(await ctx.db.select().from(tables.participants).where(eq18(getCol(tables.participants,"userId"),params.userId))).map(mapParticipant).filter((p)=>!p.leftAt);if(myParticipants.length===0)return[];let conversationIds=myParticipants.map((p)=>p.conversationId),pageRows=await ctx.db.select().from(tables.conversations).where(and13(inArray5(getCol(tables.conversations,"id"),conversationIds),eq18(getCol(tables.conversations,"isActive"),!0))).orderBy(desc3(getCol(tables.conversations,"lastMessageAt"))).limit(limit).offset(offset);if(pageRows.length===0)return[];let pageIds=pageRows.map((r)=>String(r.id)),participantRows=await ctx.db.select().from(tables.participants).where(inArray5(getCol(tables.participants,"conversationId"),pageIds)),byConversation=new Map;for(let row of participantRows){let mapped=mapParticipant(row);if(mapped.leftAt)continue;let list2=byConversation.get(mapped.conversationId)??[];list2.push(mapped),byConversation.set(mapped.conversationId,list2)}let summaries=[];for(let row of pageRows){let id=String(row.id);summaries.push(await buildSummary(ctx,tables,row,params.userId,byConversation.get(id)??[]))}return summaries}async function getConversation(ctx,conversationId,userId){let tables=resolveChatTables(ctx);await requireActiveMembership(ctx,tables,conversationId,userId);let row=await getConversationRow(ctx,tables,conversationId);if(!row)throw new ChatError("not_found","Conversation not found");return buildSummary(ctx,tables,row,userId)}async function rejoinIfNeeded(ctx,tables,conversationId,userIds){for(let userId of userIds){let participant=await getParticipant(ctx,tables,conversationId,userId);if(!participant)await ctx.db.insert(tables.participants).values({conversationId,userId,role:"member",createdBy:userId});else if(participant.leftAt)await ctx.db.update(tables.participants).set({leftAt:null}).where(eq18(getCol(tables.participants,"id"),participant.id))}}function assertCanManage(actor){if(actor.role!=="owner"&&actor.role!=="admin")throw new ChatError("forbidden","Only group admins can manage participants")}async function addParticipants(ctx,conversationId,actorId,userIds){let tables=resolveChatTables(ctx),conversation=await getConversationRow(ctx,tables,conversationId);if(!conversation)throw new ChatError("not_found","Conversation not found");if(mapConversation(conversation,ctx.cdnBasePath).type!=="group")throw new ChatError("validation","Participants can only be managed in group conversations");let actor=await requireActiveMembership(ctx,tables,conversationId,actorId);assertCanManage(actor);let toAdd=[...new Set(userIds)];await assertUsersExist(ctx,toAdd);let current=await listParticipants(ctx,tables,conversationId,!0),activeCount=current.filter((p)=>!p.leftAt).length,newOnes=toAdd.filter((id)=>!current.some((p)=>p.userId===id&&!p.leftAt));if(activeCount+newOnes.length>ctx.config.maxGroupParticipants)throw new ChatError("validation","Group participant limit exceeded");for(let userId of toAdd)await rejoinIfNeeded(ctx,tables,conversationId,[userId]);let participants=await listParticipants(ctx,tables,conversationId);return deliverToParticipants(ctx,participants,{type:"participant.added",conversationId,userIds:toAdd,by:actorId}),participants}async function removeParticipant(ctx,conversationId,actorId,targetUserId){let tables=resolveChatTables(ctx),actor=await requireActiveMembership(ctx,tables,conversationId,actorId);if(actorId!==targetUserId)assertCanManage(actor);let target2=await getParticipant(ctx,tables,conversationId,targetUserId);if(!target2||target2.leftAt)throw new ChatError("not_found","Participant not found in conversation");await ctx.db.update(tables.participants).set({leftAt:new Date}).where(eq18(getCol(tables.participants,"id"),target2.id));let participants=await listParticipants(ctx,tables,conversationId),event={type:"participant.removed",conversationId,userId:targetUserId,by:actorId};if(deliverToParticipants(ctx,participants,event),ctx.broadcaster)ctx.broadcaster.broadcastToUser(targetUserId,ctx.config.realtimeTopicPrefix||"chat",event)}import{and as and14,desc as desc4,eq as eq19,inArray as inArray6,isNull as isNull3,lt,ne,sql as sql6}from"drizzle-orm";async function loadAttachments(ctx,tables,messageIds){let grouped=new Map;if(!tables.attachments||messageIds.length===0)return grouped;let rows=await ctx.db.select().from(tables.attachments).where(inArray6(getCol(tables.attachments,"messageId"),messageIds));for(let row of rows){let attachment=mapAttachment(row,ctx.cdnBasePath),list2=grouped.get(attachment.messageId)??[];list2.push(attachment),grouped.set(attachment.messageId,list2)}return grouped}function inferContentType(explicit,attachments){if(explicit)return explicit;if(attachments.length===0)return"text";let kind=attachments[0]?.kind;if(kind==="image")return"image";if(kind==="video")return"video";if(kind==="audio")return"audio";return"file"}async function persistAttachments(ctx,tables,conversationId,messageId,params){let inputs=params.attachments??[];if(inputs.length===0)return[];if(!ctx.attachmentsAvailable||!tables.attachments||!tables.files)throw new ChatError("disabled","File attachments require storage and CDN to be enabled");if(inputs.length>ctx.config.attachments.maxPerMessage)throw new ChatError("validation",`A message cannot have more than ${ctx.config.attachments.maxPerMessage} attachments`);let fileIds=inputs.map((a)=>a.fileId),fileRows=await ctx.db.select().from(tables.files).where(inArray6(getCol(tables.files,"id"),fileIds)),filesById=new Map;for(let row of fileRows)filesById.set(String(row.id),row);let payloads=inputs.map((input)=>{let fileRow=filesById.get(input.fileId);if(!fileRow)throw new ChatError("not_found",`Attachment file ${input.fileId} was not found`);if(String(fileRow.uploadedBy??fileRow.createdBy??"")!==params.senderId)throw new ChatError("forbidden",`Attachment file ${input.fileId} is not owned by the sender`);let mimeType=fileRow.mimeType??input.mimeType??null,size=fileRow.size??input.size??null,allowedTypes=ctx.config.attachments?.allowedMimeTypes??[];if(allowedTypes.length>0&&(!mimeType||!allowedTypes.includes(mimeType)))throw new ChatError("validation",`Attachment file ${input.fileId} is a ${mimeType??"unknown"} file, which this conversation does not accept`);let maxBytes=ctx.config.attachments?.maxFileSizeBytes??null;if(maxBytes!=null&&size!=null&&size>maxBytes)throw new ChatError("validation",`Attachment file ${input.fileId} is ${size} bytes, over the ${maxBytes} byte limit for attachments`);return{conversationId,messageId,fileId:input.fileId,kind:input.kind??kindFromMime(mimeType),originalName:fileRow.originalName??input.originalName??null,mimeType,size,width:input.width??null,height:input.height??null,duration:input.duration??null,createdBy:params.senderId}});return(await ctx.db.insert(tables.attachments).values(payloads).returning()).map((row)=>mapAttachment(row,ctx.cdnBasePath))}async function sendMessage(ctx,params){let tables=resolveChatTables(ctx);await requireActiveMembership(ctx,tables,params.conversationId,params.senderId);let content=params.content?.trim()?params.content:null,hasAttachments=(params.attachments?.length??0)>0;if(!content&&!hasAttachments)throw new ChatError("validation","A message must contain text or at least one attachment");if(content&&content.length>ctx.config.maxMessageLength)throw new ChatError("validation",`Message exceeds the maximum length of ${ctx.config.maxMessageLength} characters`);if(params.metadata!==void 0&¶ms.metadata!==null){if(JSON.stringify(params.metadata).length>4096)throw new ChatError("validation","Message metadata exceeds the maximum size of 4096 bytes")}let{message,participants}=await ctx.db.transaction(async(tx)=>{let txCtx={...ctx,db:tx},messageRow=(await tx.insert(tables.messages).values({conversationId:params.conversationId,senderId:params.senderId,content,contentType:params.contentType??"text",replyToId:params.replyToId??null,clientMessageId:params.clientMessageId??null,metadata:params.metadata??{},createdBy:params.senderId}).returning())[0],messageId=String(messageRow.id),attachments=await persistAttachments(txCtx,tables,params.conversationId,messageId,params),contentType=inferContentType(params.contentType,attachments);if(contentType!==messageRow.contentType)await tx.update(tables.messages).set({contentType}).where(eq19(getCol(tables.messages,"id"),messageId)),messageRow.contentType=contentType;let mappedMessage=mapMessage(messageRow,attachments),preview=buildPreview(mappedMessage.content,mappedMessage.contentType,attachments.length),now=mappedMessage.createdAt;await tx.update(tables.conversations).set({lastMessageAt:now,lastMessagePreview:preview,lastMessageSenderId:params.senderId,updatedAt:new Date}).where(eq19(getCol(tables.conversations,"id"),params.conversationId)),await tx.update(tables.participants).set({unreadCount:sql6`${getCol(tables.participants,"unreadCount")} + 1`}).where(and14(eq19(getCol(tables.participants,"conversationId"),params.conversationId),ne(getCol(tables.participants,"userId"),params.senderId),isNull3(getCol(tables.participants,"leftAt"))));let parts=await listParticipants(txCtx,tables,params.conversationId);return{message:mappedMessage,participants:parts}});return deliverToParticipants(ctx,participants,{type:"message.created",conversationId:params.conversationId,message}),message}async function getMessages(ctx,params){let tables=resolveChatTables(ctx);await requireActiveMembership(ctx,tables,params.conversationId,params.userId);let limit=Math.min(Math.max(params.limit??ctx.config.messagePageSize,1),100),conditions=[eq19(getCol(tables.messages,"conversationId"),params.conversationId)];if(params.before){let cursor=(await ctx.db.select().from(tables.messages).where(eq19(getCol(tables.messages,"id"),params.before)).limit(1))[0];if(cursor?.createdAt)conditions.push(lt(getCol(tables.messages,"createdAt"),cursor.createdAt))}let rows=await ctx.db.select().from(tables.messages).where(and14(...conditions)).orderBy(desc4(getCol(tables.messages,"createdAt"))).limit(limit+1),pageRows=rows.slice(0,limit),hasMore=rows.length>limit,attachmentsByMessage=await loadAttachments(ctx,tables,pageRows.map((r)=>String(r.id)));return{messages:pageRows.map((row)=>mapMessage(row,attachmentsByMessage.get(String(row.id))??[])),hasMore}}async function markRead(ctx,conversationId,userId,messageId){let tables=resolveChatTables(ctx),participant=await requireActiveMembership(ctx,tables,conversationId,userId),targetMessageId=messageId??null;if(targetMessageId){if(!(await ctx.db.select().from(tables.messages).where(and14(eq19(getCol(tables.messages,"id"),targetMessageId),eq19(getCol(tables.messages,"conversationId"),conversationId))).limit(1))[0])throw new ChatError("not_found","Message not found in this conversation")}else{let latestRow=(await ctx.db.select().from(tables.messages).where(eq19(getCol(tables.messages,"conversationId"),conversationId)).orderBy(desc4(getCol(tables.messages,"createdAt"))).limit(1))[0];targetMessageId=latestRow?String(latestRow.id):null}let readAt=new Date;if(await ctx.db.update(tables.participants).set({lastReadMessageId:targetMessageId,lastReadAt:readAt,unreadCount:0}).where(eq19(getCol(tables.participants,"id"),participant.id)),ctx.config.readReceipts){let participants=await listParticipants(ctx,tables,conversationId);deliverToParticipants(ctx,participants,{type:"conversation.read",conversationId,userId,lastReadMessageId:targetMessageId,readAt:readAt.toISOString()},userId)}return{lastReadMessageId:targetMessageId,readAt}}async function editMessage(ctx,messageId,userId,content){if(!ctx.config.editingEnabled)throw new ChatError("disabled","Message editing is disabled");let tables=resolveChatTables(ctx),row=(await ctx.db.select().from(tables.messages).where(eq19(getCol(tables.messages,"id"),messageId)).limit(1))[0];if(!row||row.deletedAt)throw new ChatError("not_found","Message not found");if(String(row.senderId)!==userId)throw new ChatError("forbidden","You can only edit your own messages");let trimmed=content.trim();if(!trimmed)throw new ChatError("validation","Message content cannot be empty");if(trimmed.length>ctx.config.maxMessageLength)throw new ChatError("validation","Message exceeds the maximum length");let editedAt=new Date,updated=await ctx.db.update(tables.messages).set({content:trimmed,editedAt,updatedAt:editedAt}).where(eq19(getCol(tables.messages,"id"),messageId)).returning(),attachments=await loadAttachments(ctx,tables,[messageId]),message=mapMessage(updated[0],attachments.get(messageId)??[]),participants=await listParticipants(ctx,tables,String(row.conversationId));return deliverToParticipants(ctx,participants,{type:"message.updated",conversationId:String(row.conversationId),message}),message}async function deleteMessage(ctx,messageId,userId,asModerator=!1){if(!ctx.config.deletionEnabled&&!asModerator)throw new ChatError("disabled","Message deletion is disabled");let tables=resolveChatTables(ctx),row=(await ctx.db.select().from(tables.messages).where(eq19(getCol(tables.messages,"id"),messageId)).limit(1))[0];if(!row||row.deletedAt)throw new ChatError("not_found","Message not found");if(!asModerator&&String(row.senderId)!==userId)throw new ChatError("forbidden","You can only delete your own messages");let deletedAt=new Date,orphanedFiles=await ctx.db.transaction(async(tx)=>{if(await tx.update(tables.messages).set({content:null,deletedAt,updatedAt:deletedAt}).where(eq19(getCol(tables.messages,"id"),messageId)),!tables.attachments)return[];let attachmentRows=await tx.select().from(tables.attachments).where(eq19(getCol(tables.attachments,"messageId"),messageId));if(attachmentRows.length===0)return[];await tx.delete(tables.attachments).where(eq19(getCol(tables.attachments,"messageId"),messageId));let fileIds=[...new Set(attachmentRows.map((a)=>String(a.fileId)))];if(!tables.files||fileIds.length===0)return[];let stillLinked=await tx.select().from(tables.attachments).where(inArray6(getCol(tables.attachments,"fileId"),fileIds)),keep=new Set(stillLinked.map((a)=>String(a.fileId))),removable=fileIds.filter((id)=>!keep.has(id));if(removable.length===0)return[];let fileRows=await tx.select().from(tables.files).where(inArray6(getCol(tables.files,"id"),removable));return await tx.delete(tables.files).where(inArray6(getCol(tables.files,"id"),removable)),fileRows.filter((f)=>typeof f.path==="string"&&typeof f.name==="string").map((f)=>({path:String(f.path),name:String(f.name)}))});for(let file of orphanedFiles)if(await ctx.deleteStoredFile?.(file)===!1)ctx.logger.warn("[Chat] Attachment blob cleanup failed",{messageId,name:file.name});let participants=await listParticipants(ctx,tables,String(row.conversationId));deliverToParticipants(ctx,participants,{type:"message.deleted",conversationId:String(row.conversationId),messageId})}async function setTyping(ctx,conversationId,userId,isTyping=!0){if(!ctx.config.typingIndicators)return;let tables=resolveChatTables(ctx),participants=await listParticipants(ctx,tables,conversationId);if(!participants.some((p)=>p.userId===userId))throw new ChatError("forbidden","You are not a participant of this conversation");deliverToParticipants(ctx,participants,{type:"typing",conversationId,userId,isTyping},userId)}class ChatService{ctx;constructor(init){this.ctx={db:init.db,schemaTables:init.schemaTables,config:init.config,logger:init.logger,broadcaster:init.broadcaster??null,cdnBasePath:init.cdnBasePath??"/cdn",attachmentsAvailable:init.attachmentsAvailable??!1,deleteStoredFile:init.deleteStoredFile??null}}get config(){return this.ctx.config}getOrCreateDirect(userId,targetUserId){return getOrCreateDirect(this.ctx,{userId,targetUserId})}createGroup(params){return createGroup(this.ctx,params)}listConversations(userId,opts){return listConversations(this.ctx,{userId,limit:opts?.limit,offset:opts?.offset})}getConversation(conversationId,userId){return getConversation(this.ctx,conversationId,userId)}addParticipants(conversationId,actorId,userIds){return addParticipants(this.ctx,conversationId,actorId,userIds)}removeParticipant(conversationId,actorId,targetUserId){return removeParticipant(this.ctx,conversationId,actorId,targetUserId)}sendMessage(params){return sendMessage(this.ctx,params)}getMessages(params){return getMessages(this.ctx,params)}markRead(conversationId,userId,messageId){return markRead(this.ctx,conversationId,userId,messageId)}editMessage(messageId,userId,content){return editMessage(this.ctx,messageId,userId,content)}deleteMessage(messageId,userId,asModerator=!1){return deleteMessage(this.ctx,messageId,userId,asModerator)}setTyping(conversationId,userId,isTyping){return setTyping(this.ctx,conversationId,userId,isTyping)}}init_SystemCollector();init_lifecycle();init_Notification();var EXTEND_ALLOWED_KEYS=new Set(["table_name","extends","columns","indexes"]);function hasDbDefault(column){return column.default!==void 0||column.defaultRaw!==void 0||column.generatedAlwaysAs!==void 0||column.generatedAlwaysAsIdentity===!0||column.generatedByDefaultAsIdentity===!0}var AUTH_FEATURES=new Set(["authentication","authorization"]);function resolveEntities(systemTables2,configEntities,options={}){let errors2=[],warnings=[],consumed=new Set,entities=[];for(let system of systemTables2){let config=configEntities.find((c)=>c.table_name===system.table_name);if(!config){entities.push(system);continue}if(consumed.add(config.table_name),!config.extends){if(!config.replaces)warnings.push(`[Schema] "${config.table_name}" is declared in config.entities AND ships as a system table. Using YOUR definition and skipping the built-in one. Add "extends": true to inherit the built-in columns and route policy, or "replaces": true if replacing it is deliberate.`);entities.push(config);continue}for(let key2 of Object.keys(config))if(!EXTEND_ALLOWED_KEYS.has(key2))warnings.push(`[Schema] "${config.table_name}" sets "${key2}" alongside "extends": true \u2014 ignored. `+"An extending entry inherits every table-level setting from the built-in definition.");if(options.enabledTableNames&&!options.enabledTableNames.has(system.table_name))errors2.push({code:"E5_FEATURE_DISABLED",table:system.table_name,message:`"${system.table_name}" is extended but its feature is disabled, so the table is never created. Enable the feature or remove the extending entry.`});if(options.consumerMode&&(system.feature_set??[]).some((f)=>AUTH_FEATURES.has(f)))errors2.push({code:"E3_CONSUMER_AUTH_TABLE",table:system.table_name,message:`"${system.table_name}" is an auth system table and cannot be extended in consumer mode \u2014 `+"the identity tables live in the IDP. Extending it would create a local copy here."});let builtInNames=new Set((system.columns??[]).map((c)=>c.name));if(system.add_base_columns!==!1)for(let name2 of options.baseColumnNames??[])builtInNames.add(name2);for(let column of config.columns??[]){if(builtInNames.has(column.name))errors2.push({code:"E1_COLUMN_COLLISION",table:system.table_name,column:column.name,message:`"${system.table_name}.${column.name}" already exists in the built-in definition. An extending entry may only ADD columns; rename yours.`});if(column.notNull===!0&&column.nullable!==!0&&!hasDbDefault(column))errors2.push({code:"E2_NOT_NULL_WITHOUT_DEFAULT",table:system.table_name,column:column.name,message:`"${system.table_name}.${column.name}" is notNull without a default. The framework writes this table from fixed-column inserts (register, invite, admin create-user, godmin seed, `+"tenant provisioning) that cannot supply it \u2014 every one of them would fail. Make it "+"nullable or give it a default."})}let merged={...system,columns:[...system.columns??[],...config.columns??[]],indexes:[...system.indexes??[],...config.indexes??[]]};entities.push(merged)}for(let config of configEntities){if(consumed.has(config.table_name))continue;if(config.extends){errors2.push({code:"E4_UNKNOWN_SYSTEM_TABLE",table:config.table_name,message:`"${config.table_name}" sets "extends": true but there is no system table with that name. Remove the flag to declare it as an ordinary entity.`});continue}entities.push(config)}return{entities,errors:errors2,warnings}}function resolveAuthType(pathname,auth){let at=(route)=>pathname===route||pathname.endsWith(route)||pathname.includes(`${route}/`);if(at(auth?.login?.route||"/auth/login"))return"login";if(at(auth?.register?.route||"/auth/register"))return"register";if(at(auth?.passwordReset?.route||"/auth/password-reset"))return"passwordReset";if(at(auth?.magicLink?.route||"/auth/magic-link"))return"magicLink";if(at(auth?.invite?.route||"/auth/invite"))return"other";if(at("/sessions/approve")||at("/sessions/reject"))return"login";return}init_Logger2();var METHOD_MAP={GET:["GET"],POST:["POST"],PUT:["PUT"],DELETE:["DELETE"],PATCH:["PATCH"],TOGGLE:["PATCH"],VERIFICATION:["POST"]};function unhonouredIsPublicDeclarations(config){let auth=config.authentication;if(!auth)return[];let notes=[],alwaysPublic=[["authentication.sessions",auth.sessions],["authentication.passwordSet",auth.passwordSet],["authentication.webauthn",auth.webauthn],["authentication.invite",auth.invite],["authentication.register.emailVerification",auth.register?.emailVerification]];for(let[name2,feature]of alwaysPublic)if(feature?.enabled&&feature.isPublic===!1)notes.push(`${name2}.isPublic is false but these routes stay public: the flow is reached without a session`);if(auth.me?.isPublic===!0)notes.push("authentication.me.isPublic is true but /auth/me is never published as public");let sessionToken=auth.sessionToken;if(sessionToken){let named=["secret","algorithm","issuer","audience"].filter((k)=>sessionToken[k]!==void 0);if(named.length>0)notes.push(`authentication.sessionToken.${named.join("/")} is set, but the session token is an opaque id looked up in the store rather than a JWT: nothing signs or verifies it`)}return notes}function buildAuthPublicRoutes(config,basePath){let routes=[],auth=config.authentication;if(!auth)return routes;if(auth.login?.enabled&&auth.login?.isPublic)routes.push({path:auth.login.route||`${basePath}/auth/login`,method:"POST",source:"auth"});if(auth.register?.enabled&&auth.register?.isPublic)routes.push({path:auth.register.route||`${basePath}/auth/register`,method:"POST",source:"auth"});if(auth.logout?.enabled&&auth.logout?.isPublic)routes.push({path:auth.logout.route||`${basePath}/auth/logout`,method:"POST",source:"auth"});if(auth.refresh?.enabled&&auth.refresh?.isPublic)routes.push({path:auth.refresh.route||`${basePath}/auth/refresh`,method:"POST",source:"auth"});if(auth.admin?.impersonate?.enabled!==!1)routes.push({path:`${basePath}/auth/admin/impersonate/stop`,method:"POST",source:"system"});if(auth.passwordReset?.enabled&&auth.passwordReset?.isPublic){let baseRoute=auth.passwordReset.route||`${basePath}/auth/password-reset`;routes.push({path:`${baseRoute}/request`,method:"POST",source:"auth"},{path:`${baseRoute}/confirm`,method:"POST",source:"auth"})}if(auth.passwordChange?.enabled&&auth.passwordChange?.isPublic)routes.push({path:auth.passwordChange.route||`${basePath}/auth/password-change`,method:"POST",source:"auth"});if(auth.magicLink?.enabled&&auth.magicLink?.isPublic)routes.push({path:auth.magicLink.route||`${basePath}/auth/magic-link`,method:"POST",source:"auth"},{path:auth.magicLink.verifyRoute||`${basePath}/auth/magic-link/verify`,method:"GET",source:"auth"});if(auth.register?.enabled&&auth.register?.emailVerification?.enabled)routes.push({path:`${basePath}/verify-email`,method:"GET",source:"auth"},{path:`${basePath}/resend-verification`,method:"POST",source:"auth"});if(auth.invite?.enabled&&auth.invite?.isPublic)routes.push({path:auth.invite.route||`${basePath}/auth/invite`,method:"POST",source:"auth"});if(auth.invite?.enabled){let inviteRoute=auth.invite.route||`${basePath}/auth/invite`;routes.push({path:`${inviteRoute}/verify`,method:"POST",source:"auth"})}if(auth.passwordSet?.enabled)routes.push({path:auth.passwordSet.route||`${basePath}/auth/password-set`,method:"POST",source:"auth"});if(auth.webauthn?.enabled){let webauthnRoute=auth.webauthn.route||`${basePath}/auth/webauthn`;routes.push({path:`${webauthnRoute}/authenticate/options`,method:"POST",source:"auth"},{path:`${webauthnRoute}/authenticate/verify`,method:"POST",source:"auth"})}if(auth.captcha?.enabled&&auth.captcha?.isPublic){let baseRoute=auth.captcha.route||`${basePath}/auth/captcha`;routes.push({path:`${baseRoute}/generate`,method:"GET",source:"auth"},{path:`${baseRoute}/validate`,method:"POST",source:"auth"})}if(auth.sessions?.enabled){let sessionsRoute=auth.sessions.route||`${basePath}/auth/sessions`;routes.push({path:`${sessionsRoute}/approve`,method:"POST",source:"auth"},{path:`${sessionsRoute}/reject`,method:"POST",source:"auth"},{path:`${sessionsRoute}/approve-page`,method:"GET",source:"auth"},{path:`${sessionsRoute}/reject-page`,method:"GET",source:"auth"},{path:`${sessionsRoute}/approval-status`,method:"GET",source:"auth"})}if(auth.oauth?.enabled){let oauthBase=auth.oauth.basePath||`${basePath}/auth/oauth`,knownProviders=["google","github","microsoft","discord","facebook","twitter","apple","custom"];routes.push({path:`${oauthBase}/providers`,method:"GET",source:"auth"});for(let provider of knownProviders)routes.push({path:`${oauthBase}/${provider}`,method:"GET",source:"auth"},{path:`${oauthBase}/${provider}/callback`,method:"GET",source:"auth"})}return routes}function buildEntityPublicRoutes(entities,basePath,_schema){let routes=[];for(let entity of entities){if(!entity.is_public)continue;let camelName=entity.table_name.replace(/_([a-z])/g,(_,l)=>l.toUpperCase()),entityPath=`${basePath}/${camelName}`;for(let[nucleusMethod,isPublic]of Object.entries(entity.is_public)){if(!isPublic)continue;let httpMethods=METHOD_MAP[nucleusMethod];if(!httpMethods)continue;for(let method of httpMethods)if(method==="GET")routes.push({path:entityPath,method:"GET",source:"entity"}),routes.push({path:`${entityPath}/:id`,method:"GET",source:"entity"});else if(method==="POST")routes.push({path:entityPath,method:"POST",source:"entity"});else if(method==="PUT"||method==="PATCH")routes.push({path:`${entityPath}/:id`,method,source:"entity"});else if(method==="DELETE")routes.push({path:`${entityPath}/:id`,method:"DELETE",source:"entity"})}}return routes}function buildSystemTablePublicRoutes(systemTables2,basePath,schema){return buildEntityPublicRoutes(systemTables2,basePath,schema)}function buildPublicRoutes(config,systemTables2,basePath="",schema="public"){let authRoutes=buildAuthPublicRoutes(config,basePath),entityRoutes=buildEntityPublicRoutes(config.entities||[],basePath,schema),systemRoutes=buildSystemTablePublicRoutes(systemTables2,basePath,schema),pubsubRoutes=[];if(config.pubsub?.enabled){let pubsubBasePath=config.pubsub.basePath||"/subs",wsPath=config.pubsub.wsPath||"/api/events/subscribe";pubsubRoutes.push({path:`${pubsubBasePath}/:topic`,method:"POST",source:"system"},{path:wsPath,method:"GET",source:"system"})}let paymentRoutes=[];if(config.payment?.enabled){let paymentBasePath=config.payment.basePath||"/payment";paymentRoutes.push({path:`${paymentBasePath}/callback`,method:"POST",source:"system"},{path:`${paymentBasePath}/callback`,method:"OPTIONS",source:"system"},{path:`${paymentBasePath}/bin-query`,method:"POST",source:"system"},{path:`${paymentBasePath}/webhook`,method:"POST",source:"system"})}let discoveryRoutes=[];if(config.authorization?.endpointDiscovery?.enabled){let manifestAuth={bypassHeaders:["x-discovery-token"]};discoveryRoutes.push({path:"/authorization/route-manifest",method:"GET",source:"system",authenticateWhenCredentialed:manifestAuth},{path:"/authorization/route-manifest",method:"OPTIONS",source:"system"},{path:"/authorization/role-claims-manifest",method:"GET",source:"system",authenticateWhenCredentialed:manifestAuth},{path:"/authorization/role-claims-manifest",method:"OPTIONS",source:"system"})}let domainRoutes=[];if(config.domains?.enabled){let domainsBasePath=config.domains.basePath||"/domains";domainRoutes.push({path:`${basePath}${domainsBasePath}/resolve`,method:"GET",source:"system"})}let tenantRoutes=[];if(config.database?.isMultiTenant)tenantRoutes.push({path:`${basePath}/tenants/check-subdomain/:subdomain`,method:"GET",source:"system"}),tenantRoutes.push({path:`${basePath}/tenants/self-signup`,method:"POST",source:"system"}),tenantRoutes.push({path:`${basePath}/tenants/refresh`,method:"POST",source:"system"});let customRoutes=[{path:"/nucleus-core",method:"GET",source:"custom"},{path:"/public",method:"GET",source:"custom"},{path:"/docs",method:"GET",source:"custom"},{path:"/docs/json",method:"GET",source:"custom"},{path:"/swagger",method:"GET",source:"custom"},{path:"/swagger/json",method:"GET",source:"custom"}],configPublicPaths=config.authorization?.publicPaths||[],ALL_PUBLIC_METHODS=["GET","HEAD","POST","PUT","PATCH","DELETE","OPTIONS"],withHead=(path,source)=>[{path,method:"HEAD",source}],configPublicRoutes=configPublicPaths.flatMap((entry)=>{let trimmed=entry.trim(),qualified=trimmed.match(/^(GET|HEAD|POST|PUT|PATCH|DELETE|OPTIONS)\s+(.+)$/i);if(qualified?.[1]&&qualified[2]){let method=qualified[1].toUpperCase(),path=qualified[2].trim(),routes=[{path,method,source:"custom"}];if(method!=="OPTIONS")routes.push({path,method:"OPTIONS",source:"custom"});if(method==="GET")routes.push(...withHead(path,"custom"));return routes}if(trimmed.startsWith("/auth/"))return ALL_PUBLIC_METHODS.map((method)=>({path:trimmed,method,source:"custom"}));return logger.warn(`[Security] publicPaths entry "${trimmed}" is unqualified \u2014 exposing GET + HEAD + OPTIONS only. To make a write method public, use the explicit form, e.g. "POST ${trimmed}".`),[{path:trimmed,method:"GET",source:"custom"},...withHead(trimmed,"custom"),{path:trimmed,method:"OPTIONS",source:"custom"}]});return[...authRoutes,...entityRoutes,...systemRoutes,...pubsubRoutes,...paymentRoutes,...discoveryRoutes,...domainRoutes,...tenantRoutes,...customRoutes,...configPublicRoutes]}function isPublicRoute(publicRoutes,path,method,caller){return findPublicRoute(publicRoutes,path,method,caller)!==void 0}function findPublicRoute(publicRoutes,path,method,caller){if(/\/\/|\\|%2f|%2e|%5c/i.test(path))return;let normalizedPath=path.replace(/\/$/,""),normalizedMethod=method.toUpperCase();for(let route of publicRoutes){if(route.method!==normalizedMethod)continue;if(matchPath(route.path,normalizedPath)){let opt=route.authenticateWhenCredentialed;if(opt&&caller){if(!(opt.bypassHeaders??[]).some((name2)=>!!caller.headers.get(name2))&&caller.credentialed())continue}return route}}return}function matchPath(pattern,path){if(pattern===path)return!0;let patternParts=pattern.split("/").filter(Boolean),pathParts=path.split("/").filter(Boolean);if(patternParts.length!==pathParts.length)return!1;for(let i=0;i<patternParts.length;i++){let patternPart=patternParts[i],pathPart=pathParts[i];if(patternPart?.startsWith(":"))continue;if(patternPart!==pathPart)return!1}return!0}init_routeCollector();import{eq as eq22}from"drizzle-orm";init_passwordPolicy();init_requireGodmin();import{eq as eq21}from"drizzle-orm";var col7=(table,name2)=>{let tableRecord=table,snakeCase=name2.replace(/([A-Z])/g,"_$1").toLowerCase();return tableRecord[name2]||tableRecord[snakeCase]};async function requireGodmin(db,usersTable,userId){let user=(await db.select().from(usersTable).where(eq21(col7(usersTable,"id"),userId)).limit(1))[0];if(!user?.isGod)return!1;return accountStateRefusal(user)===null}function validatePassword(pwd,policy,identity){return validateAgainstPolicy(pwd,policy,identity)}var handleActivateUsers=(db,usersTable,logger2)=>async(ctx)=>{let userId=ctx.request.headers.get("x-user-id");if(!userId||!await requireGodmin(db,usersTable,userId))return ctx.set.status=403,{success:!1,message:"Forbidden"};let result=await db.update(usersTable).set({isLocked:!1,lockedUntil:null,failedLoginAttempts:0,updatedAt:new Date}).where(eq22(col7(usersTable,"cohortId"),ctx.params.id)).returning();return logger2.audit({entityName:"user_cohorts",entityId:ctx.params.id,operation:"COHORT_ACTIVATE_USERS",userId,summary:`Activated ${result.length} users in cohort`}),{success:!0,data:{affected:result.length}}};init_assignDefaultRole();init_utils6();import{count,eq as eq24,inArray as inArray7}from"drizzle-orm";var handleBulkCreateUsers=(config,logger2)=>{let{db,usersTable,cohortsTable,rolesTable,userRolesTable,profilesTable}=config;return async(ctx)=>{if(!db||!usersTable||!cohortsTable)return ctx.set.status=500,{success:!1,message:"Server misconfigured"};let callerId=ctx.request.headers.get("x-user-id");if(!callerId||!await requireGodmin(db,usersTable,callerId))return ctx.set.status=403,{success:!1,message:"Forbidden"};let cohortId=ctx.params.id;if((await db.select().from(cohortsTable).where(eq24(col7(cohortsTable,"id"),cohortId)).limit(1)).length===0)return ctx.set.status=404,{success:!1,message:"Cohort not found"};let{users:explicitUsers,emailPrefix,emailDomain,sharedPassword,count:userCount,roleIds}=ctx.body,userList=[];if(explicitUsers&&explicitUsers.length>0)for(let u of explicitUsers)userList.push({email:u.email.toLowerCase().trim(),password:u.password,firstName:u.profile?.firstName,lastName:u.profile?.lastName});else if(emailPrefix&&emailDomain&&sharedPassword&&userCount){let padLen=String(userCount).length;for(let i=1;i<=userCount;i++){let paddedNum=String(i).padStart(padLen,"0");userList.push({email:`${emailPrefix}-${paddedNum}@${emailDomain}`.toLowerCase(),password:sharedPassword})}}else return ctx.set.status=400,{success:!1,message:'Provide either "users" array or "emailPrefix" + "emailDomain" + "sharedPassword" + "count"'};if(userList.length>500)return ctx.set.status=400,{success:!1,message:"Maximum 500 users per batch"};for(let entry of userList){let validation=validatePassword(entry.password,config.passwordPolicy,entry.email);if(!validation.valid)return ctx.set.status=400,{success:!1,message:`Password too weak for ${entry.email}`,errors:validation.errors}}let emails=userList.map((u)=>u.email),existingRows=await db.select({email:col7(usersTable,"email")}).from(usersTable).where(inArray7(col7(usersTable,"email"),emails)),existingEmails=new Set(existingRows.map((r)=>String(r.email).toLowerCase())),created=[],skipped=[],now=new Date;for(let entry of userList){if(existingEmails.has(entry.email)){skipped.push(entry.email);continue}let hashedPw=await hashPassword(entry.password),newUser=(await db.insert(usersTable).values({email:entry.email,password:hashedPw,emailVerified:!0,verifiedAt:now,isLocked:!1,cohortId,createdAt:now,updatedAt:now}).returning())[0];if(!newUser)continue;let newUserId=String(newUser.id);if(created.push({id:newUserId,email:entry.email,password:entry.password}),roleIds&&roleIds.length>0&&rolesTable&&userRolesTable){let validIds=(await db.select({id:col7(rolesTable,"id")}).from(rolesTable).where(inArray7(col7(rolesTable,"id"),roleIds))).map((r)=>String(r.id));if(validIds.length>0)await db.insert(userRolesTable).values(validIds.map((roleId)=>({userId:newUserId,roleId})))}else{let cohortDefaultRoles=resolveDefaultRoleNames(config);if(cohortDefaultRoles.length>0)await assignDefaultRoles({db,userId:newUserId,roleNames:cohortDefaultRoles,rolesTable,userRolesTable,logger:logger2})}if((entry.firstName||entry.lastName)&&profilesTable)await db.insert(profilesTable).values({userId:newUserId,firstName:entry.firstName??null,lastName:entry.lastName??null,createdAt:now,updatedAt:now}).catch((err)=>{logger2.warn("[Cohort] Failed to create profile for user",{userId:newUserId,error:err.message})})}let countResult=await db.select({count:count()}).from(usersTable).where(eq24(col7(usersTable,"cohortId"),cohortId)),totalUsers=Number(countResult[0]?.count)||0;return await db.update(cohortsTable).set({userCount:totalUsers,updatedAt:new Date}).where(eq24(col7(cohortsTable,"id"),cohortId)),logger2.audit({entityName:"user_cohorts",entityId:cohortId,operation:"COHORT_BULK_CREATE",userId:callerId,summary:`Bulk created ${created.length} users in cohort (${skipped.length} skipped)`}),{success:!0,data:{created:created.length,skipped:skipped.length,skippedEmails:skipped,users:created}}}};var handleCreateCohort=(db,usersTable,cohortsTable,logger2)=>async(ctx)=>{let userId=ctx.request.headers.get("x-user-id");if(!userId||!await requireGodmin(db,usersTable,userId))return ctx.set.status=403,{success:!1,message:"Forbidden"};let{name:name2,description,expiresAt,metadata}=ctx.body,now=new Date,inserted=await db.insert(cohortsTable).values({name:name2,description:description??null,createdBy:userId,expiresAt:expiresAt?new Date(expiresAt):null,userCount:0,metadata:metadata??{},createdAt:now,updatedAt:now}).returning();return logger2.audit({entityName:"user_cohorts",entityId:inserted[0]?.id,operation:"COHORT_CREATE",userId,summary:`Created cohort "${name2}"`}),{success:!0,data:inserted[0]}};init_revokeUserSessions();import{eq as eq25}from"drizzle-orm";var handleDeactivateUsers=(db,usersTable,logger2,sessionsTable)=>async(ctx)=>{let userId=ctx.request.headers.get("x-user-id");if(!userId||!await requireGodmin(db,usersTable,userId))return ctx.set.status=403,{success:!1,message:"Forbidden"};let result=await db.update(usersTable).set({isLocked:!0,updatedAt:new Date}).where(eq25(col7(usersTable,"cohortId"),ctx.params.id)).returning(),signedOut=await revokeUserSessions({db,sessionsTable,onError:(error)=>logger2.error("Cohort sessions could not be revoked after deactivating",{error,cohortId:ctx.params.id})},result.map((row)=>String(row.id)));return logger2.audit({entityName:"user_cohorts",entityId:ctx.params.id,operation:"COHORT_DEACTIVATE_USERS",userId,summary:`Deactivated ${result.length} users in cohort, ending ${signedOut} session(s)`}),{success:!0,data:{affected:result.length,signedOut}}};import{eq as eq26,inArray as inArray10}from"drizzle-orm";var handleDeleteUsers=(db,usersTable,cohortsTable,userRolesTable,profilesTable,logger2)=>async(ctx)=>{let userId=ctx.request.headers.get("x-user-id");if(!userId||!await requireGodmin(db,usersTable,userId))return ctx.set.status=403,{success:!1,message:"Forbidden"};let userIds=(await db.select({id:col7(usersTable,"id")}).from(usersTable).where(eq26(col7(usersTable,"cohortId"),ctx.params.id))).map((u)=>String(u.id));if(userIds.length>0){if(userRolesTable)await db.delete(userRolesTable).where(inArray10(col7(userRolesTable,"userId"),userIds));if(profilesTable)await db.delete(profilesTable).where(inArray10(col7(profilesTable,"userId"),userIds));await db.delete(usersTable).where(inArray10(col7(usersTable,"id"),userIds))}return await db.update(cohortsTable).set({userCount:0,updatedAt:new Date}).where(eq26(col7(cohortsTable,"id"),ctx.params.id)),logger2.audit({entityName:"user_cohorts",entityId:ctx.params.id,operation:"COHORT_DELETE_USERS",userId,summary:`Deleted ${userIds.length} users from cohort`}),{success:!0,data:{deleted:userIds.length}}};import{eq as eq27}from"drizzle-orm";var handleGetCohort=(db,usersTable,cohortsTable)=>async(ctx)=>{let userId=ctx.request.headers.get("x-user-id");if(!userId||!await requireGodmin(db,usersTable,userId))return ctx.set.status=403,{success:!1,message:"Forbidden"};let rows=await db.select().from(cohortsTable).where(eq27(col7(cohortsTable,"id"),ctx.params.id)).limit(1);if(rows.length===0)return ctx.set.status=404,{success:!1,message:"Cohort not found"};let userRows=await db.select().from(usersTable).where(eq27(col7(usersTable,"cohortId"),ctx.params.id));return{success:!0,data:{...rows[0],users:userRows}}};import{eq as eq28}from"drizzle-orm";var csvCell=(value)=>{let s=value==null?"":String(value);if(/^[=+\-@\t\r]/.test(s))s=`'${s}`;if(/[",\n\r]/.test(s))s=`"${s.replace(/"/g,'""')}"`;return s},handleExportUsers=(db,usersTable)=>async(ctx)=>{let userId=ctx.request.headers.get("x-user-id");if(!userId||!await requireGodmin(db,usersTable,userId))return ctx.set.status=403,{success:!1,message:"Forbidden"};let userRows=await db.select().from(usersTable).where(eq28(col7(usersTable,"cohortId"),ctx.params.id)),lines=["email,status,locked,created_at"];for(let row of userRows){let u=row;lines.push([csvCell(u.email),csvCell(u.isActive?"active":"inactive"),csvCell(u.isLocked?"yes":"no"),csvCell(u.createdAt)].join(","))}return ctx.set.headers["content-type"]="text/csv",ctx.set.headers["content-disposition"]=`attachment; filename="cohort-${ctx.params.id}-users.csv"`,lines.join(`
|
|
544
|
-
`)};var handleListCohorts=(db,usersTable,cohortsTable)=>async(ctx)=>{let userId=ctx.request.headers.get("x-user-id");if(!userId||!await requireGodmin(db,usersTable,userId))return ctx.set.status=403,{success:!1,message:"Forbidden"};let rows=await db.select().from(cohortsTable);return{success:!0,data:{items:rows,meta:{totalItems:rows.length}}}};import{eq as eq29}from"drizzle-orm";var handleDeleteCohort=(db,usersTable,cohortsTable,logger2)=>async(ctx)=>{let userId=ctx.request.headers.get("x-user-id");if(!userId||!await requireGodmin(db,usersTable,userId))return ctx.set.status=403,{success:!1,message:"Forbidden"};return await db.update(usersTable).set({cohortId:null,updatedAt:new Date}).where(eq29(col7(usersTable,"cohortId"),ctx.params.id)),await db.delete(cohortsTable).where(eq29(col7(cohortsTable,"id"),ctx.params.id)),logger2.audit({entityName:"user_cohorts",entityId:ctx.params.id,operation:"COHORT_DELETE",userId,summary:`Deleted cohort ${ctx.params.id}`}),{success:!0,data:null}};import{eq as eq30}from"drizzle-orm";var handleUpdateCohort=(db,usersTable,cohortsTable)=>async(ctx)=>{let userId=ctx.request.headers.get("x-user-id");if(!userId||!await requireGodmin(db,usersTable,userId))return ctx.set.status=403,{success:!1,message:"Forbidden"};let updates={updatedAt:new Date};if(ctx.body.name!==void 0)updates.name=ctx.body.name;if(ctx.body.description!==void 0)updates.description=ctx.body.description;if(ctx.body.expiresAt!==void 0)updates.expiresAt=ctx.body.expiresAt?new Date(ctx.body.expiresAt):null;if(ctx.body.metadata!==void 0)updates.metadata=ctx.body.metadata;if(ctx.body.isActive!==void 0)updates.isActive=ctx.body.isActive;let updated=await db.update(cohortsTable).set(updates).where(eq30(col7(cohortsTable,"id"),ctx.params.id)).returning();if(updated.length===0)return ctx.set.status=404,{success:!1,message:"Cohort not found"};return{success:!0,data:updated[0]}};init_esm3();var CohortBodySchema=Type.Object({name:Type.String({minLength:1,maxLength:255}),description:Type.Optional(Type.String()),expiresAt:Type.Optional(Type.String()),metadata:Type.Optional(Type.Record(Type.String(),Type.Unknown()))}),CohortUpdateSchema=Type.Object({name:Type.Optional(Type.String({minLength:1,maxLength:255})),description:Type.Optional(Type.String()),expiresAt:Type.Optional(Type.Union([Type.String(),Type.Null()])),metadata:Type.Optional(Type.Record(Type.String(),Type.Unknown())),isActive:Type.Optional(Type.Boolean())}),BulkCreateUserItem=Type.Object({email:Type.String({format:"email"}),password:Type.String({minLength:8}),profile:Type.Optional(Type.Object({firstName:Type.Optional(Type.String()),lastName:Type.Optional(Type.String())}))}),BulkCreateSchema=Type.Object({users:Type.Optional(Type.Array(BulkCreateUserItem)),emailPrefix:Type.Optional(Type.String()),emailDomain:Type.Optional(Type.String()),sharedPassword:Type.Optional(Type.String({minLength:8})),count:Type.Optional(Type.Integer({minimum:1,maximum:500})),roleIds:Type.Optional(Type.Array(Type.String()))});function createCohortRoutes(config){let{db,logger:logger2,cohortsTable,usersTable,userRolesTable,profilesTable,basePath}=config;if(!db||!cohortsTable||!usersTable)return logger2.warn("[Cohort] Skipped \u2014 missing tables",{hasDb:!!db,hasCohortsTable:!!cohortsTable,hasUsersTable:!!usersTable}),collectRoutes();let declared=[defineRoute({method:"GET",path:"/",handler:handleListCohorts(db,usersTable,cohortsTable)}),defineRoute({method:"GET",path:"/:id",handler:handleGetCohort(db,usersTable,cohortsTable)}),defineRoute({method:"POST",path:"/",body:CohortBodySchema,handler:handleCreateCohort(db,usersTable,cohortsTable,logger2)}),defineRoute({method:"PATCH",path:"/:id",body:CohortUpdateSchema,handler:handleUpdateCohort(db,usersTable,cohortsTable)}),defineRoute({method:"DELETE",path:"/:id",handler:handleDeleteCohort(db,usersTable,cohortsTable,logger2)}),defineRoute({method:"POST",path:"/:id/bulk-create",body:BulkCreateSchema,handler:handleBulkCreateUsers(config,logger2)}),defineRoute({method:"POST",path:"/:id/deactivate-users",handler:handleDeactivateUsers(db,usersTable,logger2,config.sessionsTable??null)}),defineRoute({method:"POST",path:"/:id/activate-users",handler:handleActivateUsers(db,usersTable,logger2)}),defineRoute({method:"POST",path:"/:id/delete-users",handler:handleDeleteUsers(db,usersTable,cohortsTable,userRolesTable,profilesTable,logger2)}),defineRoute({method:"GET",path:"/:id/export",handler:handleExportUsers(db,usersTable)})];return collectRoutes([{prefix:basePath,routes:declared}])}init_deviceInfo();function shouldNotifyDevice(question){if(question.isImpersonationLogin)return!1;if(!question.emailAvailable)return!1;if(question.requiresApproval)return!0;if(question.trustScore<question.lowTrustThreshold)return!0;return question.notifyOnNewDevice&&question.isNewDevice}init_routeCollector();init_storage2();init_esm3();init_routeCollector();init_Authorization();function getUserId(request){return request.headers.get("x-user-id")}function isChatModerator(request,moderatorRoles){let roles=decodeHeaderList(request.headers.get("x-user-roles"));if(roles.includes(GODMIN_ROLE_NAME))return!0;if(moderatorRoles.length===0)return!1;let allowed=new Set(moderatorRoles.map((role)=>role.toLowerCase()));return roles.some((role)=>allowed.has(role.toLowerCase()))}function chatErrorStatus(code){switch(code){case"forbidden":return 403;case"not_found":return 404;case"validation":return 400;case"conflict":return 409;case"disabled":return 409;default:return 400}}async function guard3(set2,logger2,fn){try{return{success:!0,data:await fn()}}catch(err){if(err instanceof ChatError)return set2.status=chatErrorStatus(err.code),{success:!1,message:err.message};return logger2.error("[Chat] Unhandled route error",{error:err instanceof Error?err.message:String(err)}),set2.status=500,{success:!1,message:"Internal server error"}}}function unauthorized(set2){return set2.status=401,{success:!1,message:"Authentication required"}}function createConversationRoutes(deps){let{logger:logger2,getService}=deps,declared=[defineRoute({method:"GET",path:"/conversations",handler:({request,query,set:set2})=>{let userId=getUserId(request);if(!userId)return unauthorized(set2);let{service}=getService(request);return guard3(set2,logger2,()=>service.listConversations(userId,{limit:query.limit?Number(query.limit):void 0,offset:query.offset?Number(query.offset):void 0}))},query:Type.Object({limit:Type.Optional(Type.String()),offset:Type.Optional(Type.String())}),detail:{tags:["Chat"],summary:"List my conversations"}}),defineRoute({method:"POST",path:"/conversations",handler:({request,body,set:set2})=>{let userId=getUserId(request);if(!userId)return unauthorized(set2);let{service}=getService(request),input=body;return guard3(set2,logger2,()=>{if(input.type==="group"){if(!input.title)throw new ChatError("validation","A group title is required");return service.createGroup({creatorId:userId,title:input.title,description:input.description,avatarFileId:input.avatarFileId,participantIds:input.participantIds??[]})}if(!input.targetUserId)throw new ChatError("validation","targetUserId is required for a direct conversation");return service.getOrCreateDirect(userId,input.targetUserId)})},body:Type.Object({type:Type.Optional(Type.Union([Type.Literal("direct"),Type.Literal("group")])),targetUserId:Type.Optional(Type.String()),title:Type.Optional(Type.String()),description:Type.Optional(Type.String()),avatarFileId:Type.Optional(Type.String()),participantIds:Type.Optional(Type.Array(Type.String()))}),detail:{tags:["Chat"],summary:"Create or open a conversation"}}),defineRoute({method:"GET",path:"/conversations/:id",handler:({request,params,set:set2})=>{let userId=getUserId(request);if(!userId)return unauthorized(set2);let{service}=getService(request);return guard3(set2,logger2,()=>service.getConversation(params.id,userId))},params:Type.Object({id:Type.String()}),detail:{tags:["Chat"],summary:"Get a conversation"}}),defineRoute({method:"POST",path:"/conversations/:id/participants",handler:({request,params,body,set:set2})=>{let userId=getUserId(request);if(!userId)return unauthorized(set2);let{service}=getService(request),{userIds}=body;return guard3(set2,logger2,()=>service.addParticipants(params.id,userId,userIds))},params:Type.Object({id:Type.String()}),body:Type.Object({userIds:Type.Array(Type.String())}),detail:{tags:["Chat"],summary:"Add group participants"}}),defineRoute({method:"DELETE",path:"/conversations/:id/participants/:userId",handler:({request,params,set:set2})=>{let userId=getUserId(request);if(!userId)return unauthorized(set2);let{service}=getService(request);return guard3(set2,logger2,async()=>{return await service.removeParticipant(params.id,userId,params.userId),{removed:params.userId}})},params:Type.Object({id:Type.String(),userId:Type.String()}),detail:{tags:["Chat"],summary:"Remove a participant / leave group"}})];return collectRoutes([{prefix:deps.config.basePath,routes:declared}])}init_esm3();init_esm3();function FileSchema(){return Type.Object({size:Type.Number(),type:Type.String()},{format:"binary",additionalProperties:!0})}function FilesSchema(){return Type.Union([FileSchema(),Type.Array(FileSchema())])}init_routeCollector();init_storage2();var CONTENT_TYPE_SCHEMA=Type.Optional(Type.Union([Type.Literal("text"),Type.Literal("image"),Type.Literal("file"),Type.Literal("video"),Type.Literal("audio")]));function createMessageRoutes(deps){let{logger:logger2,getService}=deps,declared=[defineRoute({method:"GET",path:"/conversations/:id/messages",handler:({request,params,query,set:set2})=>{let userId=getUserId(request);if(!userId)return unauthorized(set2);let{service}=getService(request);return guard3(set2,logger2,()=>service.getMessages({conversationId:params.id,userId,limit:query.limit?Number(query.limit):void 0,before:query.before||void 0}))},params:Type.Object({id:Type.String()}),query:Type.Object({limit:Type.Optional(Type.String()),before:Type.Optional(Type.String())}),detail:{tags:["Chat"],summary:"Get conversation messages (newest first)"}}),defineRoute({method:"POST",path:"/conversations/:id/messages",handler:({request,params,body,set:set2})=>{let userId=getUserId(request);if(!userId)return unauthorized(set2);let{service}=getService(request),input=body;return guard3(set2,logger2,()=>service.sendMessage({conversationId:params.id,senderId:userId,content:input.content,contentType:input.contentType,replyToId:input.replyToId,clientMessageId:input.clientMessageId,attachments:input.attachmentIds?.map((fileId)=>({fileId})),metadata:input.metadata}))},params:Type.Object({id:Type.String()}),body:Type.Object({content:Type.Optional(Type.String()),contentType:CONTENT_TYPE_SCHEMA,replyToId:Type.Optional(Type.String()),clientMessageId:Type.Optional(Type.String()),attachmentIds:Type.Optional(Type.Array(Type.String())),metadata:Type.Optional(Type.Any())}),detail:{tags:["Chat"],summary:"Send a message (link pre-uploaded attachments)"}}),defineRoute({method:"POST",path:"/conversations/:id/messages/upload",handler:({request,params,body,set:set2})=>{let userId=getUserId(request);if(!userId)return unauthorized(set2);if(!deps.attachmentsAvailable)return set2.status=409,{success:!1,message:"File attachments require storage and CDN to be enabled"};let{service,schemaTables}=getService(request);return guard3(set2,logger2,async()=>{let filesTable=resolveSchemaTable(schemaTables,"files",logger2);if(!filesTable)throw new ChatError("disabled","Files table unavailable in this schema");let{data,files}=parseFormDataBody(body,deps.storageConfig);if(files.length===0)throw new ChatError("validation","At least one file is required");let{records,failed}=await persistUploadedFiles({db:deps.db,filesTable,files,storageConfig:deps.storageConfig,subFolder:"chat",userId});if(records.length===0)throw new ChatError("validation",failed[0]?.error??"File upload failed");if(deps.cdnMedia)scheduleUploadMediaProcessing(records,{storagePath:deps.storageConfig.basePath,media:deps.cdnMedia,logger:logger2});let content=typeof data.content==="string"?data.content:void 0,replyToId=typeof data.replyToId==="string"?data.replyToId:void 0,clientMessageId=typeof data.clientMessageId==="string"?data.clientMessageId:void 0;return service.sendMessage({conversationId:params.id,senderId:userId,content,replyToId,clientMessageId,attachments:records.map((record3)=>({fileId:record3.id,originalName:record3.originalName,mimeType:record3.mimeType,size:record3.size}))})})},type:"formdata",params:Type.Object({id:Type.String()}),body:Type.Object({[deps.storageConfig.formData.dataField]:Type.Optional(Type.Union([Type.String(),Type.Any()])),[deps.storageConfig.formData.filesField]:Type.Optional(FilesSchema())}),detail:{tags:["Chat"],summary:"Send a message with uploaded file attachments"}}),defineRoute({method:"POST",path:"/conversations/:id/read",handler:({request,params,body,set:set2})=>{let userId=getUserId(request);if(!userId)return unauthorized(set2);let{service}=getService(request),{messageId}=body;return guard3(set2,logger2,()=>service.markRead(params.id,userId,messageId))},params:Type.Object({id:Type.String()}),body:Type.Object({messageId:Type.Optional(Type.String())}),detail:{tags:["Chat"],summary:"Mark a conversation as read"}}),defineRoute({method:"POST",path:"/conversations/:id/typing",handler:({request,params,body,set:set2})=>{let userId=getUserId(request);if(!userId)return unauthorized(set2);let{service}=getService(request),{isTyping}=body;return guard3(set2,logger2,async()=>{return await service.setTyping(params.id,userId,isTyping??!0),{ok:!0}})},params:Type.Object({id:Type.String()}),body:Type.Object({isTyping:Type.Optional(Type.Boolean())}),detail:{tags:["Chat"],summary:"Broadcast a typing indicator"}}),defineRoute({method:"PATCH",path:"/messages/:id",handler:({request,params,body,set:set2})=>{let userId=getUserId(request);if(!userId)return unauthorized(set2);let{service}=getService(request),{content}=body;return guard3(set2,logger2,()=>service.editMessage(params.id,userId,content))},params:Type.Object({id:Type.String()}),body:Type.Object({content:Type.String()}),detail:{tags:["Chat"],summary:"Edit a message"}}),defineRoute({method:"DELETE",path:"/messages/:id",handler:({request,params,set:set2})=>{let userId=getUserId(request);if(!userId)return unauthorized(set2);let{service}=getService(request),asModerator=isChatModerator(request,deps.config.moderatorRoles??[]);return guard3(set2,logger2,async()=>{if(await service.deleteMessage(params.id,userId,asModerator),asModerator)logger2.warn("[CHAT] Message deleted by a moderator",{actorId:userId,messageId:params.id});return{deleted:params.id}})},params:Type.Object({id:Type.String()}),detail:{tags:["Chat"],summary:"Delete a message",description:"Sender-only, unless the caller is godmin or holds a role from chat.moderatorRoles \u2014 then any message, and `deletionEnabled` does not apply."}})];return collectRoutes([{prefix:deps.config.basePath,routes:declared}])}function createChatRoutes(routeConfig){let{db,schemaTables,config,logger:logger2,broadcaster,tenantRegistry,storageConfig,attachmentsAvailable,cdnBasePath,cdnMedia}=routeConfig,buildService=(tables)=>new ChatService({db,schemaTables:tables,config,logger:logger2,broadcaster,cdnBasePath,attachmentsAvailable,deleteStoredFile:(file)=>deleteFile(file.path,file.name,storageConfig.basePath)}),defaultService=buildService(schemaTables),deps={db,config,logger:logger2,storageConfig,attachmentsAvailable,getService:(request)=>{if(!tenantRegistry)return{service:defaultService,schemaTables};let schemaName=request.headers.get("x-tenant-schema");if(!schemaName)return{service:defaultService,schemaTables};let ctx=tenantRegistry.getSchemaContext(schemaName);if(!ctx)return{service:defaultService,schemaTables};return{service:buildService(ctx.schemaTables),schemaTables:ctx.schemaTables}},cdnMedia},routes=collectRoutes();return routes.use(createConversationRoutes(deps)),routes.use(createMessageRoutes(deps)),logger2.info(`[Chat] Routes registered at ${config.basePath}`,{attachmentsAvailable,groupsEnabled:config.groupsEnabled}),{routes,chatService:defaultService}}init_esm3();init_routeCollector();init_godminGuard();init_helpers4();function createConfigRoutes(routeConfig){let{logger:logger2,resolvedOptions,configFilePath,basePath,onConfigUpdate,db,schemaTables,getRedis}=routeConfig,guard4=createGodminGuard({logger:logger2,db:db??null,schemaTables,area:"config management"});logger2.info(`[ConfigManagement] Routes enabled at ${basePath}`);let declared=[defineRoute({method:"GET",path:"/",handler:()=>{let configCopy=JSON.parse(JSON.stringify(resolvedOptions));return{isSuccess:!0,message:"Current running configuration",data:{config:maskSensitiveFields(configCopy),configFilePath,sections:buildSectionsMeta(resolvedOptions)}}},detail:{tags:["Config Management"],summary:"Get full running configuration",description:"Returns the full running configuration with sensitive fields masked."}}),defineRoute({method:"GET",path:"/sections",handler:()=>({isSuccess:!0,message:"Config section metadata",data:{sections:buildSectionsMeta(resolvedOptions)}}),detail:{tags:["Config Management"],summary:"List all config sections with metadata",description:"Section list is derived at runtime from the config object \u2014 no hardcoding."}}),defineRoute({method:"GET",path:"/env",handler:()=>{let configCopy=JSON.parse(JSON.stringify(resolvedOptions)),declared2=scanEnvVars(configCopy),declaredNames=new Set(declared2.map((e)=>e.envName)),undeclared=scanUndeclaredEnv(declaredNames),entries=[...declared2,...undeclared],resolvedCount=entries.filter((e)=>e.resolved).length,missingCount=entries.length-resolvedCount,unrecognizedCount=undeclared.length;return{isSuccess:!0,message:`Found ${entries.length} env vars (${resolvedCount} resolved, ${missingCount} missing, ${unrecognizedCount} unrecognized)`,data:{entries,totalCount:entries.length,resolvedCount,missingCount,unrecognizedCount}}},detail:{tags:["Config Management"],summary:"Get environment variables (declared + undeclared)",description:"Surfaces both (a) DECLARED env vars \u2014 UPPER_SNAKE_CASE references found in the config tree (recognized:true) \u2014 and (b) UNDECLARED env vars actually present in process.env but not declared (recognized:false), minus Kubernetes service-link and OS/interpreter infra noise, so orphaned / junk config surfaces for cleanup. Sensitive values are masked; AI-provider *_API_KEY vars are never surfaced."}}),defineRoute({method:"GET",path:"/overrides",handler:async()=>{let redis=getRedis();if(!redis)return{isSuccess:!0,message:"Redis not available \u2014 no overrides stored",data:{overrides:null,sectionCount:0,sections:[]}};let overrides=await readOverridesFromRedis(redis,resolvedOptions.appId||"nucleus"),sections=overrides?Object.keys(overrides).filter((k)=>{let v=overrides[k];return v!==void 0&&v!==null&&(typeof v!=="object"||Object.keys(v).length>0)}):[];return{isSuccess:!0,message:sections.length>0?`${sections.length} section override(s) stored in Redis`:"No overrides in Redis",data:{overrides:overrides?maskSensitiveFields(overrides):null,sectionCount:sections.length,sections}}},detail:{tags:["Config Management"],summary:"View Redis config overrides",description:"Returns overrides stored in Redis that will be merged on next restart."}}),defineRoute({method:"DELETE",path:"/overrides",handler:async(ctx)=>{let redis=getRedis();if(!redis)return ctx.set.status=503,{isSuccess:!1,message:"Redis not available",data:null};let existing=await readOverridesFromRedis(redis,resolvedOptions.appId||"nucleus"),clearedSections=existing?Object.keys(existing):[];return await clearOverridesFromRedis(redis,resolvedOptions.appId||"nucleus"),logger2.info("[ConfigManagement] Redis overrides cleared",{clearedSections}),{isSuccess:!0,message:clearedSections.length>0?`Cleared ${clearedSections.length} override(s) from Redis. Restart to revert to config.json defaults.`:"No overrides to clear.",data:{message:"Redis overrides cleared",clearedSections}}},detail:{tags:["Config Management"],summary:"Clear all Redis config overrides",description:"Removes all stored overrides from Redis. Restart to revert to config.json defaults."}}),defineRoute({method:"GET",path:"/:section",handler:({params,set:set2})=>{let{section}=params;if(!hasSection(resolvedOptions,section))return set2.status=404,{isSuccess:!1,message:`Unknown config section: ${section}`,data:null};let sectionValue=extractSection(resolvedOptions,section),restart=isRestartRequired(section),maskedValue=sectionValue;if(typeof sectionValue==="object"&§ionValue!==null&&!Array.isArray(sectionValue))maskedValue=maskSensitiveFields({[section]:sectionValue})[section];return{isSuccess:!0,message:`Config section: ${section}`,data:{section,config:maskedValue??null,restartRequired:restart}}},params:Type.Object({section:Type.String()}),detail:{tags:["Config Management"],summary:"Get a specific config section",description:"Returns a specific config section with sensitive fields masked."}}),defineRoute({method:"PATCH",path:"/:section",handler:async({params,body,set:set2})=>{let{section}=params,payload=body;if(!hasSection(resolvedOptions,section))return set2.status=404,{isSuccess:!1,message:`Unknown config section: ${section}`,data:null};if(!payload.config||typeof payload.config!=="object")return set2.status=400,{isSuccess:!1,message:'Request body must contain a "config" object',data:null};let restart=isRestartRequired(section),currentValue=extractSection(resolvedOptions,section),submitted=Array.isArray(currentValue)||currentValue===void 0||typeof currentValue==="string"||typeof currentValue==="number"||typeof currentValue==="boolean"?payload.config.value!==void 0?payload.config.value:payload.config:payload.config,incoming=restoreMaskedEchoes(section,currentValue,submitted),newValue;if(typeof currentValue==="object"&¤tValue!==null&&!Array.isArray(currentValue))newValue=deepMerge(currentValue,incoming);else newValue=incoming;if(applySectionUpdate(resolvedOptions,section,newValue),!restart)onConfigUpdate(section,newValue);let persistMethod="memory-only",persistWarning=null;if(configFilePath)try{await persistConfigToDisk(configFilePath,resolvedOptions),persistMethod="disk",logger2.info(`[ConfigManagement] Section "${section}" persisted to disk`)}catch(diskErr){let diskMsg=diskErr instanceof Error?diskErr.message:String(diskErr);logger2.warn(`[ConfigManagement] Disk persist failed: ${diskMsg}`);let redis=getRedis();if(redis)try{await persistSectionToRedis(redis,resolvedOptions.appId||"nucleus",section,newValue),persistMethod="redis",logger2.info(`[ConfigManagement] Section "${section}" persisted to Redis (disk unavailable)`)}catch(redisErr){let redisMsg=redisErr instanceof Error?redisErr.message:String(redisErr);logger2.error(`[ConfigManagement] Redis persist also failed: ${redisMsg}`),persistWarning=`Disk: ${diskMsg}. Redis: ${redisMsg}. Changes are in-memory only and will be lost on restart.`}else persistWarning=`Disk: ${diskMsg}. Redis not available. Changes are in-memory only and will be lost on restart.`}logger2.info(`[ConfigManagement] Section "${section}" updated (persist: ${persistMethod}, restart-required: ${restart})`);let updatedValue=extractSection(resolvedOptions,section),maskedUpdated=updatedValue;if(typeof updatedValue==="object"&&updatedValue!==null&&!Array.isArray(updatedValue))maskedUpdated=maskSensitiveFields({[section]:updatedValue})[section];let baseMsg=restart?`Config section "${section}" saved (${persistMethod}). Restart required for changes to take effect.`:`Config section "${section}" updated and applied (${persistMethod}).`;return{isSuccess:!0,message:persistWarning?`${baseMsg} Warning: ${persistWarning}`:baseMsg,data:{section,applied:!restart,restartRequired:restart,config:maskedUpdated}}},params:Type.Object({section:Type.String()}),body:Type.Object({config:Type.Record(Type.String(),Type.Unknown())}),detail:{tags:["Config Management"],summary:"Update a config section",description:"Deep-merges the payload into the section. Hot-reloadable sections apply immediately. Restart-required sections are saved to disk only."}}),defineRoute({method:"POST",path:"/restart",handler:({body})=>{let delayMs=body.delayMs??1000;logger2.info(`[ConfigManagement] Restart scheduled in ${delayMs}ms`);let scheduledAt=new Date().toISOString();return setTimeout(()=>{logger2.info("[ConfigManagement] Restarting process..."),process.exit(0)},delayMs),{isSuccess:!0,message:`Server restart scheduled in ${delayMs}ms`,data:{message:`Process will exit in ${delayMs}ms. Orchestrator (K8s/PM2/systemd) will restart it.`,scheduledAt}}},body:Type.Optional(Type.Object({delayMs:Type.Optional(Type.Number())})),detail:{tags:["Config Management"],summary:"Restart the server process",description:"Triggers a graceful process exit after a configurable delay. The orchestrator (K8s, PM2, systemd) is expected to restart the process automatically."}})];return collectRoutes([{prefix:basePath,guards:[guard4],routes:declared}])}init_routeMount();init_esm3();init_routeCollector();init_adminGuard();var requireAuth=(ctx)=>ctx.request.headers.get("x-user-id"),fail=(ctx,status,message)=>{return ctx.set.status=status,{success:!1,message,data:null}},callerSchemaOf=(ctx)=>ctx.request.headers.get("x-tenant-schema"),isGodmin=(ctx)=>hasGodminRole(ctx.request),ownsHostname=(ctx,record3)=>{if(isGodmin(ctx))return!0;let caller=callerSchemaOf(ctx);return record3.schemaName!=null&&record3.schemaName===caller};function createHostnameRoutes(config){let base=config.basePath,declared=[defineRoute({method:"POST",path:`${base}/hostnames`,handler:async(ctx)=>{let service=svc(ctx);if(!service)return fail(ctx,503,"Domains not available");if(!requireAuth(ctx))return fail(ctx,401,"Authentication required");let body=ctx.body,callerSchema=callerSchemaOf(ctx),schemaName=body.schemaName??null,tenantId=body.tenantId??null;if(!hasGodminRole(ctx.request)){if(schemaName&&schemaName!==callerSchema)return fail(ctx,403,"Cannot bind a hostname to another tenant's schema");schemaName=callerSchema;let ownTenantId=config.resolveTenantIdForSchema?.(callerSchema)??null;if(tenantId&&tenantId!==ownTenantId)return fail(ctx,403,"Cannot bind a hostname to another tenant");tenantId=ownTenantId}try{let result=await service.createHostname({hostname:body.hostname,ownerId:body.ownerId,ownerType:body.ownerType,tenantId,schemaName,isPrimary:body.isPrimary});return{success:!0,message:"Custom hostname created",data:{hostname:result.hostname,instructions:result.instructions}}}catch(err){return fail(ctx,400,err instanceof Error?err.message:"Failed to create hostname")}},body:Type.Object({hostname:Type.String(),ownerId:Type.String(),ownerType:Type.Optional(Type.Union([Type.Literal("tenant"),Type.Literal("organization"),Type.Literal("user"),Type.Literal("external")])),tenantId:Type.Optional(Type.String()),schemaName:Type.Optional(Type.String()),isPrimary:Type.Optional(Type.Boolean())}),detail:{tags:["Domains"],summary:"Create custom hostname"}}),defineRoute({method:"GET",path:`${base}/hostnames`,handler:async(ctx)=>{let service=svc(ctx);if(!service)return fail(ctx,503,"Domains not available");if(!requireAuth(ctx))return fail(ctx,401,"Authentication required");let url=new URL(ctx.request.url),items;if(isGodmin(ctx))items=await service.listHostnames({ownerId:url.searchParams.get("ownerId")??void 0,tenantId:url.searchParams.get("tenantId")??void 0});else{let ownTenantId=config.resolveTenantIdForSchema?.(callerSchemaOf(ctx))??null;items=ownTenantId?await service.listHostnames({tenantId:ownTenantId}):[]}return{success:!0,message:`Found ${items.length} hostnames`,data:{items}}}}),defineRoute({method:"GET",path:`${base}/hostnames/:id`,handler:async(ctx)=>{let service=svc(ctx);if(!service)return fail(ctx,503,"Domains not available");if(!requireAuth(ctx))return fail(ctx,401,"Authentication required");let record3=await service.getHostname(ctx.params.id);if(!record3||!ownsHostname(ctx,record3))return fail(ctx,404,"Hostname not found");return{success:!0,message:"Hostname found",data:record3}}}),defineRoute({method:"GET",path:`${base}/hostnames/:id/instructions`,handler:async(ctx)=>{let service=svc(ctx);if(!service)return fail(ctx,503,"Domains not available");if(!requireAuth(ctx))return fail(ctx,401,"Authentication required");let id=ctx.params.id,record3=await service.getHostname(id);if(!record3||!ownsHostname(ctx,record3))return fail(ctx,404,"Hostname not found");return{success:!0,message:"DNS instructions",data:{instructions:await service.getInstructions(id)}}}})],svc=(ctx)=>{let s=config.getDomainService();if(!s)return ctx.set.status=503,null;return s},lifecycle2=(suffix,run,summary)=>{declared.push(defineRoute({method:"POST",path:`${base}/hostnames/:id/${suffix}`,handler:async(ctx)=>{let service=svc(ctx);if(!service)return fail(ctx,503,"Domains not available");if(!requireAuth(ctx))return fail(ctx,401,"Authentication required");let id=ctx.params.id,record3=await service.getHostname(id);if(!record3||!ownsHostname(ctx,record3))return fail(ctx,404,"Hostname not found");try{let data=await run(service,id);if(!data)return fail(ctx,404,"Hostname not found");return{success:!0,message:summary,data}}catch(err){return fail(ctx,400,err instanceof Error?err.message:summary)}}}))};return lifecycle2("verify",(s,id)=>s.verifyHostname(id),"Verification processed"),lifecycle2("activate",(s,id)=>s.activateHostname(id),"Hostname activated"),lifecycle2("disable",(s,id)=>s.disableHostname(id),"Hostname disabled"),lifecycle2("make-primary",(s,id)=>s.makePrimary(id),"Primary hostname set"),lifecycle2("refresh-status",(s,id)=>s.refreshStatus(id),"Status refreshed"),collectRoutes([{routes:declared}])}init_esm3();init_routeCollector();init_adminGuard();var requireAuth2=(ctx)=>ctx.request.headers.get("x-user-id"),fail2=(ctx,status,message)=>{return ctx.set.status=status,{success:!1,message,data:null}},callerSchemaOf2=(ctx)=>ctx.request.headers.get("x-tenant-schema"),isGodmin2=(ctx)=>hasGodminRole(ctx.request);function createRegistrationRoutes(config){let base=config.basePath,svc=(ctx)=>{let s=config.getRegistrationService();if(!s)return ctx.set.status=503,null;return s},ownTenantId=(ctx)=>config.resolveTenantIdForSchema?.(callerSchemaOf2(ctx))??null,ownsRegistration=(ctx,record3)=>{if(isGodmin2(ctx))return!0;let own=ownTenantId(ctx);return record3.tenantId!=null&&own!=null&&record3.tenantId===own},declared=[defineRoute({method:"POST",path:`${base}/registrations`,handler:async(ctx)=>{let service=svc(ctx);if(!service)return fail2(ctx,503,"Domains not available");if(!requireAuth2(ctx))return fail2(ctx,401,"Authentication required");let body=ctx.body,tenantId=body.tenantId??null,registrantOwnerType=body.registrantOwnerType;if(!isGodmin2(ctx)){let own=ownTenantId(ctx);if(tenantId&&tenantId!==own)return fail2(ctx,403,"Cannot register a domain for another tenant");if(tenantId=own,registrantOwnerType==="platform")return fail2(ctx,403,"Only a platform admin may register as 'platform'");registrantOwnerType="customer"}try{return{success:!0,message:"Registration requested",data:await service.createRequest({requestedDomain:body.requestedDomain,ownerId:body.ownerId,ownerType:body.ownerType,tenantId,registrantOwnerType,termsVersion:body.termsVersion,termsAccepted:body.termsAccepted,autoRenew:body.autoRenew})}}catch(err){return fail2(ctx,400,err instanceof Error?err.message:"Failed to request registration")}},body:Type.Object({requestedDomain:Type.String(),ownerId:Type.String(),ownerType:Type.Optional(Type.Union([Type.Literal("tenant"),Type.Literal("organization"),Type.Literal("user"),Type.Literal("external")])),tenantId:Type.Optional(Type.String()),registrantOwnerType:Type.Optional(Type.Union([Type.Literal("customer"),Type.Literal("platform")])),termsVersion:Type.String(),termsAccepted:Type.Boolean(),autoRenew:Type.Optional(Type.Boolean())}),detail:{tags:["Domains"],summary:"Request managed domain registration"}}),defineRoute({method:"GET",path:`${base}/registrations`,handler:async(ctx)=>{let service=svc(ctx);if(!service)return fail2(ctx,503,"Domains not available");if(!requireAuth2(ctx))return fail2(ctx,401,"Authentication required");let url=new URL(ctx.request.url),items;if(isGodmin2(ctx))items=await service.listRegistrations({ownerId:url.searchParams.get("ownerId")??void 0,tenantId:url.searchParams.get("tenantId")??void 0});else{let own=ownTenantId(ctx);items=own?await service.listRegistrations({tenantId:own}):[]}return{success:!0,message:`Found ${items.length} registrations`,data:{items}}}}),defineRoute({method:"GET",path:`${base}/registrations/:id`,handler:async(ctx)=>{let service=svc(ctx);if(!service)return fail2(ctx,503,"Domains not available");if(!requireAuth2(ctx))return fail2(ctx,401,"Authentication required");let record3=await service.getRegistration(ctx.params.id);if(!record3||!ownsRegistration(ctx,record3))return fail2(ctx,404,"Registration not found");return{success:!0,message:"Registration found",data:record3}}}),defineRoute({method:"POST",path:`${base}/registrations/:id/cancel`,handler:async(ctx)=>{let service=svc(ctx);if(!service)return fail2(ctx,503,"Domains not available");if(!requireAuth2(ctx))return fail2(ctx,401,"Authentication required");let id=ctx.params.id,record3=await service.getRegistration(id);if(!record3||!ownsRegistration(ctx,record3))return fail2(ctx,404,"Registration not found");let cancelled=await service.cancel(id);if(!cancelled)return fail2(ctx,404,"Registration not found");return{success:!0,message:"Registration cancelled",data:cancelled}}}),defineRoute({method:"POST",path:`${base}/registrations/:id/transfer-out`,handler:async(ctx)=>{let service=svc(ctx);if(!service)return fail2(ctx,503,"Domains not available");if(!requireAuth2(ctx))return fail2(ctx,401,"Authentication required");let id=ctx.params.id,record3=await service.getRegistration(id);if(!record3||!ownsRegistration(ctx,record3))return fail2(ctx,404,"Registration not found");let out=await service.requestTransferOut(id);if(!out)return fail2(ctx,404,"Registration not found");return{success:!0,message:"Transfer-out requested",data:out}}})];return collectRoutes([{routes:declared}])}init_routeCollector();function createResolveRoute(config){let base=config.basePath,declared=[defineRoute({method:"GET",path:`${base}/resolve`,isPublic:!0,handler:async(ctx)=>{let service=config.getDomainService();if(!service)return ctx.set.status=503,{success:!1,message:"Domains not available",data:null};let hostname=new URL(ctx.request.url).searchParams.get("hostname")||ctx.request.headers.get("host")||"";if(!hostname)return ctx.set.status=400,{success:!1,message:"hostname is required",data:null};let match=await service.resolveHostname(hostname);if(!match)return ctx.set.status=404,{success:!1,message:"No active hostname matched",data:null};return{success:!0,message:"Resolved",data:match}},detail:{tags:["Domains"],summary:"Resolve hostname to tenant",description:"Public."}})];return collectRoutes([{routes:declared}])}function createDomainRoutes(app,config){return useRoutes(app,createResolveRoute(config)),useRoutes(app,createHostnameRoutes(config)),useRoutes(app,createRegistrationRoutes(config)),app}init_esm3();import{and as and18,arrayContains,arrayOverlaps,asc,createTableRelationsHelpers,desc as desc5,eq as eq32,getTableName,gt,gte,ilike,inArray as inArray11,isNotNull,isNull as isNull4,like,lt as lt2,lte,ne as ne2,notInArray,or as or4,sql as sql8}from"drizzle-orm";import{drizzle}from"drizzle-orm/node-postgres";init_AccountLink();init_Authorization();init_routeMount();init_utils5();init_revokeUserSessions();function decideRoleAssign(params){let{callerIsGod,callerRoleNames,allowRoles,targetRoleName,subjectIsGodmin,godminRoleName}=params,callerIsGodmin=callerIsGod||callerRoleNames.includes(godminRoleName);if(!(callerIsGodmin||callerRoleNames.some((name2)=>allowRoles.includes(name2))))return{allowed:!1,status:403,reason:"not_privileged"};if(targetRoleName===godminRoleName&&!callerIsGodmin)return{allowed:!1,status:403,reason:"godmin_assignment_forbidden"};if(subjectIsGodmin&&!callerIsGodmin)return{allowed:!1,status:403,reason:"target_is_godmin"};return{allowed:!0,callerIsGodmin}}var ROLE_ASSIGN_REFUSALS={not_privileged:"You are not allowed to assign roles.",godmin_assignment_forbidden:"Only a system administrator can grant this role.",target_is_godmin:"This person\u2019s roles can only be changed by a system administrator."};init_adminGuard();init_sensitiveColumns();var snakeToCamel2=(s)=>s.replace(/_([a-z])/g,(_,l)=>l.toUpperCase()),SENSITIVE_SYSTEM_TABLE_SNAKE=["domain_hostnames","domain_registrations","domain_provider_records","domain_verification_challenges","domain_events","payment_sub_merchants","payment_invoices","payment_subscriptions","payment_customers","payment_transactions","payment_methods","payment_products","payment_prices","payment_commission_splits","payment_splits","payment_reserves","payment_ledger_entries","payment_payout_requests","payment_settlement_policies","payment_disputes","payment_webhook_logs","verifications","verification_requirements","verification_instances","verification_flows","verification_steps","verification_edges","verification_verifier_configs","verification_notification_rules","verification_notification_recipients","verification_notification_channels","api_keys","magic_link_tokens","user_sessions","trusted_devices","integration_sources","integration_endpoints","integration_mappings"],SENSITIVE_SYSTEM_TABLES=new Set(SENSITIVE_SYSTEM_TABLE_SNAKE.flatMap((name2)=>[name2,snakeToCamel2(name2)]));function isSensitiveSystemTable(tableName){return SENSITIVE_SYSTEM_TABLES.has(tableName)}function delegatedWriteRoles(question){if(question.isRbacTable)return new Set;let roles=(question.featureSet??[]).flatMap((feature)=>question.featureAdminRoles?.[feature]??[]);return new Set(roles.map((role)=>role.toLowerCase()))}function isFeatureAdmin(question){let allowed=delegatedWriteRoles(question);if(allowed.size===0)return!1;return question.callerRoles.some((role)=>allowed.has(role.toLowerCase()))}init_storage2();import{and as and16,eq as eq31}from"drizzle-orm";async function activeVerificationExists(db,schemaTables,entityName,entityId){let instancesTable=schemaTables.verificationInstances;if(!instancesTable)return!1;let cols=instancesTable;if(!cols.entityName||!cols.entityId||!cols.status)return!1;try{return(await db.select().from(instancesTable).where(and16(eq31(cols.entityName,entityName),eq31(cols.entityId,entityId),eq31(cols.status,"active"))).limit(1)).length>0}catch{return!1}}function isAnonymousCreate(table,payload){let rule=table.anonymous_create;if(!rule)return!1;if(rule===!0)return!0;let column=rule.column;if(!column)return!1;let value2=payload[column]??payload[toCamelCase3(column)]??payload[toSnakeCase2(column)];return isTruthy(value2)}function isTruthy(value2){if(typeof value2==="boolean")return value2;if(typeof value2==="number")return value2!==0;if(typeof value2==="string"){let normalized=value2.trim().toLowerCase();return normalized==="true"||normalized==="1"||normalized==="yes"||normalized==="on"}return!1}function createAuditIdentity(anonymous,userId,headers){if(anonymous)return{userId:void 0,ipAddress:"anonymous",userAgent:"anonymous"};return{userId:userId||void 0,ipAddress:headers.get("x-client-ip")?.trim()||headers.get("x-forwarded-for")?.split(",")[0]?.trim()||headers.get("x-real-ip")?.trim()||"unknown",userAgent:headers.get("user-agent")||"unknown"}}function toCamelCase3(value2){return value2.replace(/_([a-z0-9])/g,(_m,c)=>c.toUpperCase())}function toSnakeCase2(value2){return value2.replace(/[A-Z]/g,(c)=>`_${c.toLowerCase()}`)}var snakeToCamel3=(s)=>s.replace(/_([a-z])/g,(_,l)=>l.toUpperCase());function forceCreateScope(payload,authResult,resolveColumn){let scopeFilters=authResult?.scopeFilters;if(!scopeFilters)return{ok:!0};for(let[field,value2]of Object.entries(scopeFilters)){if(!resolveColumn(field))return{ok:!1,field};payload[snakeToCamel3(field)]=value2}return{ok:!0}}function decideRelationExpansion(params){let{authEnabled,targetTable,relAuth,isTargetPublicGet}=params;if(!authEnabled||!targetTable)return{expand:!0};if(relAuth===null)return isTargetPublicGet?{expand:!0}:{expand:!1};if(!relAuth.authorized)return{expand:!1};let scope=relAuth.scopeFilters;if(scope&&Object.keys(scope).length>0)return{expand:!0,scope};return{expand:!0}}function resolveRelationScopeColumn(fields,key2,toCamel5){if(Object.hasOwn(fields,key2))return fields[key2];let camel=toCamel5(key2);return Object.hasOwn(fields,camel)?fields[camel]:void 0}import{and as and17,or as or3,sql as sql7}from"drizzle-orm";var MAX_SEARCH_TOKENS=10,TURKISH_LETTERS="\u0130\u0131\u015E\u015F\u011E\u011F\xDC\xFC\xD6\xF6\xC7\xE7",ASCII_EQUIVALENTS="IiSsGgUuOoCc";function searchTokens(term){return term.split(/\s+/).filter(Boolean).slice(0,MAX_SEARCH_TOKENS)}function foldForSearch(value2){let folded="";for(let character of value2){let index=TURKISH_LETTERS.indexOf(character);folded+=index===-1?character:ASCII_EQUIVALENTS[index]}return folded.toLowerCase()}function asRegexLiteral(value2){return value2.replace(/[.*+?^${}()|[\]\\]/g,(character)=>`\\${character}`)}var WORD_START="(^|[^[:alnum:]])";function folded(column){return sql7`lower(translate(cast(${column} as text), ${TURKISH_LETTERS}, ${ASCII_EQUIVALENTS}))`}function buildSearchCondition(columns,term){let tokens=searchTokens(term);if(tokens.length===0||columns.length===0)return null;let perToken=tokens.map((token)=>{let pattern2=`${WORD_START}${asRegexLiteral(foldForSearch(token))}`;return or3(...columns.map((column)=>sql7`${folded(column)} ~ ${pattern2}`))}).filter((condition)=>condition!==void 0);if(perToken.length===0)return null;return and17(...perToken)??null}init_Authorization();init_types3();function targetFromBody(body){let record3=body??{},read2=(...keys)=>{for(let key2 of keys){let value2=record3[key2];if(typeof value2==="string"&&value2.length>0)return value2}return null};return{userId:read2("userId","user_id"),roleId:read2("roleId","role_id")}}async function readRow(db,userRolesTable,rowId,logger2){try{let{eq:eq32}=await import("drizzle-orm"),columns=userRolesTable,row=(await db.select().from(userRolesTable).where(eq32(columns.id,rowId)).limit(1))[0];if(!row)return{userId:null,roleId:null};let text=(value2)=>typeof value2==="string"&&value2.length>0?value2:null;return{userId:text(row.userId??row.user_id),roleId:text(row.roleId??row.role_id)}}catch(err){return logger2.warn("[UserRoleWrite] Failed to read the user_roles row",{error:err instanceof Error?err.message:String(err)}),{userId:null,roleId:null}}}async function roleNameOf(db,rolesTable,roleId,logger2){try{let{eq:eq32}=await import("drizzle-orm"),columns=rolesTable,name2=(await db.select({name:columns.name}).from(rolesTable).where(eq32(columns.id,roleId)).limit(1))[0]?.name;return typeof name2==="string"&&name2.length>0?name2:null}catch(err){return logger2.warn("[UserRoleWrite] Failed to read the role name",{error:err instanceof Error?err.message:String(err)}),null}}async function isGodmin3(db,userRolesTable,rolesTable,userId,logger2){try{let{eq:eq32}=await import("drizzle-orm"),ur=userRolesTable,r=rolesTable;return(await db.select({roleName:r.name}).from(userRolesTable).innerJoin(rolesTable,eq32(ur.roleId,r.id)).where(eq32(ur.userId,userId))).some((row)=>isGodminRole(String(row.roleName??"")))}catch(err){return logger2.warn("[UserRoleWrite] Failed to check whether the subject is a godmin",{error:err instanceof Error?err.message:String(err)}),!0}}async function resolveUserRoleSubject(deps,write){let{db,schemaTables,logger:logger2}=deps,userRolesTable=schemaTables[AUTHORIZATION_TABLE_KEYS.userRoles],rolesTable=schemaTables[AUTHORIZATION_TABLE_KEYS.roles];if(!db||!userRolesTable||!rolesTable)return{targetRoleName:null,subjectIsGodmin:!0};let fromBody=targetFromBody(write.body),fromRow=write.rowId?await readRow(db,userRolesTable,write.rowId,logger2):{userId:null,roleId:null},roleIds=[fromRow.roleId,fromBody.roleId].filter((id)=>Boolean(id)),userIds=[fromRow.userId,fromBody.userId].filter((id)=>Boolean(id));if(roleIds.length===0||userIds.length===0)return{targetRoleName:null,subjectIsGodmin:!0};let names2=await Promise.all(roleIds.map((id)=>roleNameOf(db,rolesTable,id,logger2)));if(names2.some((name2)=>name2===null))return{targetRoleName:null,subjectIsGodmin:!0};let readable=names2.filter((name2)=>name2!==null),targetRoleName=readable.find((name2)=>isGodminRole(name2))??readable[0]??null,godminFlags=await Promise.all(userIds.map((id)=>isGodmin3(db,userRolesTable,rolesTable,id,logger2)));return{targetRoleName,subjectIsGodmin:godminFlags.some(Boolean)}}init_esm3();function buildBodySchemaFromEntityColumns(entityColumns){let properties={};if(!entityColumns||entityColumns.length===0)return Type.Object({},{additionalProperties:!0});for(let col8 of entityColumns)switch(col8.type?.toLowerCase()||"string"){case"integer":case"int":case"serial":case"bigserial":case"numeric":case"decimal":properties[col8.name]=col8.notNull?Type.Number():Type.Optional(Type.Number());break;case"boolean":properties[col8.name]=col8.notNull?Type.Boolean():Type.Optional(Type.Boolean());break;case"timestamp":case"timestamptz":case"date":properties[col8.name]=col8.notNull?Type.String({format:"date-time"}):Type.Optional(Type.String({format:"date-time"}));break;case"json":case"jsonb":properties[col8.name]=Type.Optional(Type.Unknown());break;case"uuid":properties[col8.name]=col8.notNull?Type.String({format:"uuid"}):Type.Optional(Type.String({format:"uuid"}));break;default:properties[col8.name]=col8.notNull?Type.String():Type.Optional(Type.String())}return Type.Object(properties,{additionalProperties:!0})}function createBulkUpdateSchema(entityColumns){return Type.Array(Type.Object({id:Type.String(),data:Type.Partial(buildBodySchemaFromEntityColumns(entityColumns))}))}var MAX_BULK_ITEMS=500;function collectFilterFields(filter,out=[]){if(filter.operator==="or"||filter.operator==="and"){let group=Array.isArray(filter.value)?filter.value:[];for(let f of group)collectFilterFields(f,out)}else if(filter.field)out.push(String(filter.field));return out}function firstUnknownField(fields,resolveCol){for(let field of fields){let trimmed=field.trim();if(trimmed!==""&&!resolveCol(trimmed))return trimmed}return null}var FILTER_OPERATORS=new Set(["eq","neq","gt","gte","lt","lte","like","ilike","in","notIn","isNull","isNotNull","arrayOverlaps","arrayContains","arrayEmpty","or","and"]);function firstUnusableCondition(filter){let operator=filter.operator;if(typeof operator!=="string"||!FILTER_OPERATORS.has(operator))return`Unknown operator in filter: ${String(operator)}`;if(operator==="or"||operator==="and"){let group=Array.isArray(filter.value)?filter.value:[];if(group.length===0)return`Empty "${operator}" group in filter`;for(let sub of group){let unusable=firstUnusableCondition(sub);if(unusable)return unusable}return null}if(typeof filter.field!=="string"||filter.field.trim()==="")return'A filter condition needs a "field"';return null}function buildFilterCondition(filter,resolveCol){if(filter.operator==="or"||filter.operator==="and"){let subs=(Array.isArray(filter.value)?filter.value:[]).map((f)=>buildFilterCondition(f,resolveCol)).filter((c)=>c!==null);if(subs.length===0)return null;return(filter.operator==="or"?or4(...subs):and18(...subs))??null}if(!filter.field)return null;let col8=resolveCol(filter.field);if(!col8)return null;let sqlCol=col8,dataType=col8.dataType,coerce2=(v)=>dataType==="date"&&(typeof v==="string"||typeof v==="number")?new Date(v):v;switch(filter.operator){case"eq":return eq32(sqlCol,coerce2(filter.value));case"neq":return ne2(sqlCol,coerce2(filter.value));case"gt":return gt(sqlCol,coerce2(filter.value));case"gte":return gte(sqlCol,coerce2(filter.value));case"lt":return lt2(sqlCol,coerce2(filter.value));case"lte":return lte(sqlCol,coerce2(filter.value));case"like":return like(sqlCol,filter.value);case"ilike":return ilike(sqlCol,filter.value);case"in":return inArray11(sqlCol,filter.value);case"notIn":return notInArray(sqlCol,filter.value);case"arrayOverlaps":return arrayOverlaps(sqlCol,filter.value??[]);case"arrayContains":return arrayContains(sqlCol,filter.value??[]);case"arrayEmpty":return sql8`cardinality(${sqlCol}) = 0`;case"isNull":return isNull4(sqlCol);case"isNotNull":return isNotNull(sqlCol);default:return null}}function createEntityRoutes(app,config){let{db,schemaTables,schemaRelations,entities,logger:logger2,dbPool,storage,cdnMedia,authorization,authMode,idpUrl,featureAdminRoles}=config,getRegistry=()=>config.getTenantRegistry?config.getTenantRegistry():config.tenantRegistry??null,getReqSchema=(request)=>{let tenantRegistry=getRegistry();if(!tenantRegistry)return{tables:schemaTables,relations:schemaRelations};let schemaName=request.headers.get("x-tenant-schema");if(!schemaName)return{tables:schemaTables,relations:schemaRelations};let ctx=tenantRegistry.getSchemaContext(schemaName);return ctx?{tables:ctx.schemaTables,relations:ctx.schemaRelations}:{tables:schemaTables,relations:schemaRelations}},storageConfig=mergeStorageConfig(storage),warmUploadedMedia=(uploaded)=>{if(!cdnMedia||!uploaded||uploaded.success.length===0)return;scheduleUploadMediaProcessing(uploaded.success,{storagePath:storageConfig.basePath,media:cdnMedia,logger:logger2})},authEnabled=authorization?.enabled??!1,enforceFieldWrites=authEnabled&&authorization?.enforceFieldWriteClaims!==!1,isConsumerMode=authMode==="consumer",filterWritePayload=(payload,authResult)=>{if(!enforceFieldWrites||!authResult?.allowedFields)return payload;return filterResponseFields(payload,authResult.allowedFields)},auditPathContext=(request)=>{let url=new URL(request.url);return{path:url.pathname,query:url.search}},auditIdentityFor=(table,payload,userId,headers)=>createAuditIdentity(isAnonymousCreate(table,payload),userId,headers),UUID_RE=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i,auditEntityId=(id)=>{let s=id==null?"":String(id);return UUID_RE.test(s)?s:null},projectWrittenRow=(row,authResult)=>{let scoped2=authEnabled&&authResult?.allowedFields?filterResponseFields(row,[...authResult.allowedFields,...authResult.allowedRelations??[]]):row;return stripUnclaimedSensitive(redactSensitiveOutput(scoped2),authResult?.allowedFields)};if(!db)return app;function snakeToCamel4(s){return s.replace(/_([a-z])/g,(_,l)=>l.toUpperCase())}let{entities:mergedEntities}=resolveEntities(SYSTEM_TABLES,entities),extendedByName=new Map(mergedEntities.filter((t)=>entities.some((e)=>e.table_name===t.table_name&&e.extends===!0)).map((t)=>[t.table_name,t])),effectiveEntities=entities.map((e)=>extendedByName.get(e.table_name)??e),allTables=Object.keys(schemaTables),entityTableNames=effectiveEntities.map((e)=>snakeToCamel4(e.table_name)),systemTableNames=allTables.filter((tbl)=>!entityTableNames.includes(tbl)),systemTableDefMap=new Map(SYSTEM_TABLES.map((t)=>[snakeToCamel4(t.table_name),t])),asMeta=(def)=>def??void 0,hasUnmetRequirement=(def)=>{let requires=def?.requires;if(!requires||requires.length===0)return null;for(let req of requires)if(req==="email"&&!config.isEmailServiceAvailable?.())return"email";return null},deriveGroupName=(def,fallback)=>{if(def?.group_name)return def.group_name;let primaryFeature=def?.feature_set?.[0];if(!primaryFeature)return fallback;return primaryFeature.charAt(0).toUpperCase()+primaryFeature.slice(1)},systemTables2=systemTableNames.filter((name2)=>{let unmet=hasUnmetRequirement(asMeta(systemTableDefMap.get(name2)));if(unmet)return logger2.info(`Skipping ${name2} routes - missing required dependency: ${unmet}`),!1;return!0}).map((name2)=>{let def=systemTableDefMap.get(name2),meta=asMeta(def);return{table_name:def?.table_name??name2,group_name:deriveGroupName(meta,name2),is_form_data:def?.is_form_data===!0,bulk_endpoints_enabled:def?.bulk_endpoints_enabled??!1,excluded_methods:def?.excluded_methods?[...def.excluded_methods]:[],columns:def?.columns?[...def.columns]:void 0}}),allEntities=[...effectiveEntities,...systemTables2],publicGetEntities=new Set;for(let e of allEntities)if(e.is_public?.GET===!0)publicGetEntities.add(e.table_name),publicGetEntities.add(snakeToCamel4(e.table_name));logger2.info(`All entities: ${allEntities.map((e)=>e.table_name).join(", ")}`);for(let table of allEntities){let rawTableName=table.table_name,tableName=snakeToCamel4(rawTableName),swaggerTag=table.group_name||rawTableName,writeFields=enforceFieldWrites?table.columns?.map((c)=>c.name):void 0,defaultDrizzleTable=schemaTables[tableName];if(!defaultDrizzleTable)continue;let tableRelations=schemaRelations[`${tableName}Relations`];logger2.info(`Creating routes for table: ${tableName}`);let relationTargetTable={};{let rels=tableRelations;if(rels?.table&&typeof rels?.config==="function")try{let built=rels.config(createTableRelationsHelpers(rels.table));for(let[relName,rel]of Object.entries(built))if(rel?.referencedTable)relationTargetTable[relName]=getTableName(rel.referencedTable)}catch(err){logger2.warn(`[Authorization] Could not map relations for ${tableName}`,{error:err instanceof Error?err.message:String(err)})}}let defaultTableColumns=defaultDrizzleTable,defaultIdCol=defaultTableColumns.id,database=db,drizzleTable=defaultDrizzleTable,tableColumns=defaultTableColumns,idCol=defaultIdCol,resolveCol=(field)=>{if(Object.hasOwn(tableColumns,field))return tableColumns[field];let camel=snakeToCamel4(field);return Object.hasOwn(tableColumns,camel)?tableColumns[camel]:void 0},getTableForRequest=(request)=>{if(!getRegistry())return{drizzleTable,tableColumns,idCol,resolveCol,schemaTables,schemaRelations};let reqSchema=getReqSchema(request),reqTable=reqSchema.tables[tableName]||drizzleTable,reqCols=reqTable,reqIdCol=reqCols.id;return{drizzleTable:reqTable,tableColumns:reqCols,idCol:reqIdCol,resolveCol:(field)=>{if(Object.hasOwn(reqCols,field))return reqCols[field];let camel=snakeToCamel4(field);return Object.hasOwn(reqCols,camel)?reqCols[camel]:void 0},schemaTables:reqSchema.tables,schemaRelations:reqSchema.relations}},bulkUpdateSchema=createBulkUpdateSchema(table.columns),bulkDeleteSchema=Type.Array(Type.String()),authzLog=logger2.scoped("authorization.check"),performAuthCheck=async(request,method,reqFields,reqRelations)=>{let result=await runAuthCheck(request,method,reqFields,reqRelations);if(result){let userId=request.headers.get("x-user-id"),requestId=request.headers.get("x-request-id")||void 0;if(!result.authorized)authzLog.warn(`Denied ${method} ${table.table_name}`,{requestId,userId,entity:table.table_name,method,reason:result.reason||"no reason provided",mode:isConsumerMode?idpUrl?"idp":"jwt":"local"});else if(result.scopeFilters||result.allowedFields||result.allowedRelations)authzLog.debug(`Allowed ${method} ${table.table_name} (restricted)`,{requestId,userId,entity:table.table_name,method,scopeFilters:result.scopeFilters,allowedFieldCount:result.allowedFields?.length,allowedRelations:result.allowedRelations})}return result},scopeConditionsFor=(authResult,resolveColumn)=>{if(!authEnabled||!authResult?.scopeFilters)return[];let{conditions,unresolvedFields}=buildScopeConditions(authResult.scopeFilters,resolveColumn);if(unresolvedFields.length>0)authzLog.error(`Scope references unknown column(s) on ${table.table_name}; failing closed (0 rows)`,{entity:table.table_name,unresolvedFields});return conditions},runAuthCheck=async(request,method,reqFields,reqRelations,entityName=table.table_name)=>{let userId=request.headers.get("x-user-id");if(!authEnabled||!userId)return null;if(isConsumerMode){let userClaims=decodeHeaderList(request.headers.get("x-user-claims")),userRoles=decodeHeaderList(request.headers.get("x-user-roles")),userClaimScopes=decodeClaimScopesHeader(request.headers.get("x-user-claim-scopes"));if(idpUrl&&authorization?.jwtClaimsMode==="resolve"){let accessToken=request.headers.get("x-access-token")||(request.headers.get("cookie")||"").match(/access_token=([^;]+)/)?.[1]||"";return checkAuthorizationViaIDP({idpUrl,accessToken,method,entity:entityName,requestedFields:reqFields,requestedRelations:reqRelations,logger:logger2})}return checkAuthorizationFromJWT({userClaims,userRoles,method,entity:entityName,requestedFields:reqFields,requestedRelations:reqRelations,logger:logger2,requireClaimSpecificity:authorization?.requireClaimSpecificity,claimScopes:userClaimScopes})}let reqCtx=getTableForRequest(request);return checkAuthorization({userId,method,entity:entityName,requestedFields:reqFields,requestedRelations:reqRelations,db:database,schemaTables:reqCtx.schemaTables,logger:logger2,requireClaimSpecificity:authorization?.requireClaimSpecificity,getUserData:async()=>{if(!database)return;let usersTbl=reqCtx.schemaTables[AUTHORIZATION_TABLE_KEYS.users];if(!usersTbl)return;let idCol2=usersTbl.id;if(!idCol2)return;return(await database.select().from(usersTbl).where(eq32(idCol2,userId)).limit(1))[0]}})},endSessionsForDeactivated=async(request,changed)=>{if(tableName!==AUTHORIZATION_TABLE_KEYS.users)return;let userIds=changed.filter((one)=>one.after?.isActive===!1&&one.before?.isActive!==!1).map((one)=>String(one.after?.id??"").trim()).filter(Boolean);if(userIds.length===0)return;let authTables=resolveAuthTablesForRequest(request,{usersTable:schemaTables[AUTHORIZATION_TABLE_KEYS.users],sessionsTable:schemaTables.userSessions??schemaTables.sessions,schemaTables,tenantRegistry:config.tenantRegistry,getTenantRegistry:config.getTenantRegistry});await revokeUserSessions({db:database,sessionsTable:authTables.sessionsTable,onError:(error3)=>logger2.error("[Entity] users rows were deactivated but their open sessions were not cleared",{error:error3,userIds})},userIds)},declared=[],entityGuards=[],listLog=logger2.scoped("entity.list"),isRbacTable=tableName===AUTHORIZATION_TABLE_KEYS.roles||tableName===AUTHORIZATION_TABLE_KEYS.claims||tableName===AUTHORIZATION_TABLE_KEYS.roleClaims,isUserRoleTable=tableName===AUTHORIZATION_TABLE_KEYS.userRoles,roleAssignRoles=featureAdminRoles?.authorization??[];if(isRbacTable||isUserRoleTable||isSensitiveSystemTable(tableName)){let denyMessage=isRbacTable?"Forbidden: managing roles and claims requires godmin privileges":isUserRoleTable?"Forbidden: assigning roles requires godmin privileges":`Forbidden: writing to '${tableName}' via the generic entity API requires godmin privileges \u2014 use the dedicated route`;entityGuards.push(async({request,set:set2,body,params})=>{let method=request.method.toUpperCase();if(method==="GET"||method==="HEAD"||method==="OPTIONS")return;if(hasGodminRole(request))return;if(isFeatureAdmin({isRbacTable:isRbacTable||isUserRoleTable,featureSet:table.feature_set,featureAdminRoles,callerRoles:decodeHeaderList(request.headers.get("x-user-roles"))}))return;if(isUserRoleTable&&roleAssignRoles.length>0){let subject=await resolveUserRoleSubject({db,schemaTables:getReqSchema(request).tables,logger:logger2},{rowId:typeof params?.id==="string"?params.id:null,body}),decision=decideRoleAssign({callerIsGod:!1,callerRoleNames:decodeHeaderList(request.headers.get("x-user-roles")),allowRoles:roleAssignRoles,targetRoleName:subject.targetRoleName??GODMIN_ROLE_NAME,subjectIsGodmin:subject.subjectIsGodmin,godminRoleName:GODMIN_ROLE_NAME});if(decision.allowed)return;if(await userHasGodminRoleInDb(db,schemaTables,request.headers.get("x-user-id"),logger2))return;return set2.status=403,{success:!1,reason:decision.reason,message:ROLE_ASSIGN_REFUSALS[decision.reason]}}if(await userHasGodminRoleInDb(db,schemaTables,request.headers.get("x-user-id"),logger2))return;return set2.status=403,{success:!1,message:denyMessage}})}if(!table.excluded_methods?.includes("GET"))declared.push(defineRoute({method:"GET",path:"/",detail:{tags:[swaggerTag],summary:`List ${tableName}`,description:`Get paginated list of ${tableName} records with filtering, sorting, and search`},handler:async(ctx)=>{if(!database)return ctx.set.status=500,{success:!1,message:"DB not initialized"};let{drizzleTable:drizzleTable2,resolveCol:resolveCol2,schemaRelations:schemaRelations2}=getTableForRequest(ctx.request),requestedFields=table.columns?.map((c)=>c.name),requestedRelations=Object.keys(schemaRelations2).filter((k)=>k.startsWith(`${tableName}Relations`)).map((k)=>k.replace("Relations","")),authResult=await performAuthCheck(ctx.request,"GET",requestedFields,requestedRelations);if(authResult&&!authResult.authorized)return ctx.set.status=403,{success:!1,message:authResult.reason||"Forbidden"};let fieldQueryable=(field)=>{if(isSensitiveOutputKey(field))return!1;if(!sensitiveFieldQueryable(field,{authEnabled,allowedFields:authResult?.allowedFields}))return!1;if(authEnabled&&authResult?.allowedFields&&!authResult.allowedFields.includes(field))return!1;return!0},q=parseQueryParams(ctx.query);if(q.malformed)return ctx.set.status=400,{success:!1,message:`Malformed query parameter: ${q.malformed.join(", ")} (expected a JSON array)`};let conditions=[];if(conditions.push(...scopeConditionsFor(authResult,resolveCol2)),q.search&&!q.searchFields)return ctx.set.status=400,{success:!1,message:"A 'search' needs 'searchFields' naming the columns to search, e.g. searchFields=name,email \u2014 without it the term would be ignored and every row returned."};if(q.search&&q.searchFields){for(let f of q.searchFields)if(!fieldQueryable(f.trim()))return ctx.set.status=403,{success:!1,message:`Cannot search on field: ${f.trim()}`};let unknown3=firstUnknownField(q.searchFields,resolveCol2);if(unknown3)return ctx.set.status=400,{success:!1,message:`Unknown field in search: ${unknown3}`};let searchColumns=q.searchFields.map((f)=>resolveCol2(f.trim())).filter((c)=>c!==null),searchCondition=buildSearchCondition(searchColumns,q.search);if(searchCondition)conditions.push(searchCondition)}if(Array.isArray(q.filters))for(let filter of q.filters){let referenced=collectFilterFields(filter);for(let f of referenced)if(!fieldQueryable(f))return ctx.set.status=403,{success:!1,message:`Cannot filter on field: ${f}`};let unknownFilterField=firstUnknownField(referenced,resolveCol2);if(unknownFilterField)return ctx.set.status=400,{success:!1,message:`Unknown field in filter: ${unknownFilterField}`};let unusable=firstUnusableCondition(filter);if(unusable)return ctx.set.status=400,{success:!1,message:unusable};let cond=buildFilterCondition(filter,resolveCol2);if(cond)conditions.push(cond)}let projection;if(q.select&&q.select.length>0){let requested=[...new Set(["id",...q.select.map((f)=>f.trim())])].filter(Boolean),columns={};for(let field of requested){if(!fieldQueryable(field))return ctx.set.status=403,{success:!1,message:`Cannot select field: ${field}`};let col8=resolveCol2(field);if(!col8)return ctx.set.status=400,{success:!1,message:`Unknown field in select: ${field}`};columns[field]=col8}if(Object.keys(columns).length>0)projection=columns}let unprojected=database.select().from(drizzleTable2),baseQuery=projection?database.select(projection).from(drizzleTable2):unprojected;if(conditions.length>0){let combined=and18(...conditions);if(combined)baseQuery=baseQuery.where(combined)}if(q.sort&&q.sort.length>0){let orderClauses=[];for(let s of q.sort){let field=typeof s?.field==="string"?s.field.trim():"";if(field==="")return ctx.set.status=400,{success:!1,message:'Malformed query parameter: sort (every entry needs a "field")'};if(!fieldQueryable(field))return ctx.set.status=403,{success:!1,message:`Cannot sort on field: ${field}`};let col8=resolveCol2(field);if(!col8)return ctx.set.status=400,{success:!1,message:`Unknown field in sort: ${field}`};orderClauses.push(s.direction==="desc"?desc5(col8):asc(col8))}if(orderClauses.length>0)baseQuery=baseQuery.orderBy(...orderClauses)}let page=q.page??1,limit=q.limit??20,offset=q.offset??(page-1)*limit;if(q.groupBy){let field=q.groupBy.trim();if(!fieldQueryable(field))return ctx.set.status=403,{success:!1,message:`Cannot group by field: ${field}`};let groupCol=resolveCol2(field);if(!groupCol)return ctx.set.status=400,{success:!1,message:`Unknown field in groupBy: ${field}`};let where=conditions.length>0?and18(...conditions):void 0,col8=groupCol,distinctCount=await database.select({n:sql8`count(distinct ${col8})::int`}).from(drizzleTable2).where(where),totalGroups=Number(distinctCount[0]?.n??0);return{success:!0,data:{items:(await database.select({value:col8,count:sql8`count(*)::int`}).from(drizzleTable2).where(where).groupBy(col8).orderBy(sql8`count(*) desc`,col8).limit(limit).offset(offset)).map((g)=>({value:g.value,count:Number(g.count)})),meta:buildPaginationMeta(page,limit,offset,totalGroups)}}}let queryStart=performance.now(),combinedCount=conditions.length>0?and18(...conditions):void 0,countResult=await database.select({n:sql8`count(*)::int`}).from(drizzleTable2).where(combinedCount),totalItems=Number(countResult[0]?.n??0);baseQuery=baseQuery.limit(limit).offset(offset);let items=await baseQuery,meta=buildPaginationMeta(page,limit,offset,totalItems);if(listLog.debug(`List ${rawTableName}: ${items.length}/${totalItems} rows`,{requestId:ctx.request.headers.get("x-request-id")||void 0,table:rawTableName,page,limit,totalItems,returned:items.length,conditionCount:conditions.length,search:q.search||void 0,sort:Array.isArray(q.sort)?q.sort.map((s)=>`${s.field}:${s.direction}`):void 0,queryMs:Math.round(performance.now()-queryStart)}),authEnabled&&authResult?.allowedFields)items=filterResponseFields(items,authResult.allowedFields);return{success:!0,data:{items:items.map((r)=>stripUnclaimedSensitive(redactSensitiveOutput(r),authResult?.allowedFields)),meta}}}})),declared.push(defineRoute({method:"GET",path:"/:id",detail:{tags:[swaggerTag],summary:`Get ${tableName} by ID`,description:`Get a single ${tableName} record by its ID with optional relations`},handler:async(ctx)=>{let{drizzleTable:drizzleTable2,idCol:idCol2,schemaTables:schemaTables2,schemaRelations:schemaRelations2,resolveCol:resolveCol2}=getTableForRequest(ctx.request);if(!database||!idCol2)return ctx.set.status=500,{success:!1,message:"No id column or DB"};let params=ctx.params,q=parseQueryParams(ctx.query),requestedFields=table.columns?.map((c)=>c.name),requestedRelations=q.with?.map((w)=>w.name),authResult=await performAuthCheck(ctx.request,"GET",requestedFields,requestedRelations);if(authResult&&!authResult.authorized)return ctx.set.status=403,{success:!1,message:authResult.reason||"Forbidden"};if(q.malformed?.includes("with"))return ctx.set.status=400,{success:!1,message:"Malformed query parameter: with (expected a JSON array)"};let getScope=scopeConditionsFor(authResult,resolveCol2),getWhere=getScope.length>0?and18(eq32(idCol2,params.id),...getScope):eq32(idCol2,params.id);if(q.with&&q.with.length>0&&tableRelations&&dbPool){let allowedWith=authEnabled&&authResult?.allowedRelations?q.with.filter((w)=>authResult.allowedRelations?.includes(w.name)??!1):q.with,dbWithRelations=drizzle(dbPool,{schema:{...schemaTables2,...schemaRelations2}}),withClause={};for(let rel of allowedWith){let targetTable=relationTargetTable[rel.name],relAuth=authEnabled&&targetTable?await runAuthCheck(ctx.request,"GET",void 0,void 0,targetTable):null,decision=decideRelationExpansion({authEnabled,targetTable,relAuth,isTargetPublicGet:targetTable?publicGetEntities.has(targetTable):!1});if(!decision.expand)continue;let relScope=decision.scope;if(relScope&&Object.keys(relScope).length>0)withClause[rel.name]={...rel.limit?{limit:rel.limit}:{},where:(fields,ops)=>{let preds=[],unresolved=!1;for(let[k,v]of Object.entries(relScope)){let col8=resolveRelationScopeColumn(fields,k,snakeToCamel4);if(col8)preds.push(ops.eq(col8,v));else unresolved=!0}if(unresolved)return sql8`1 = 0`;return preds.length>0?ops.and(...preds):void 0}};else withClause[rel.name]=rel.limit?{limit:rel.limit}:!0}let result2=await dbWithRelations.query[tableName]?.findFirst({where:getWhere,with:withClause});if(!result2)return ctx.set.status=404,{success:!1,message:`${tableName} not found`};if(authEnabled&&authResult?.allowedFields)result2=filterResponseFields(result2,[...authResult.allowedFields,...authResult.allowedRelations??[]]);if(authEnabled&&authResult?.allowedRelations)result2=filterResponseRelations(result2,authResult.allowedRelations);return{success:!0,data:stripUnclaimedSensitive(redactSensitiveOutput(result2),authResult?.allowedFields)}}let result=(await database.select().from(drizzleTable2).where(getWhere))[0]||null;if(!result)return ctx.set.status=404,{success:!1,message:`${tableName} not found`};if(authEnabled&&authResult?.allowedFields)result=filterResponseFields(result,authResult.allowedFields);return{success:!0,data:stripUnclaimedSensitive(redactSensitiveOutput(result),authResult?.allowedFields)}}})),declared.push(defineRoute({method:"GET",path:"/distinct/:field",detail:{tags:[swaggerTag],summary:`Get distinct ${tableName} values`,description:`Get distinct values for a specific field in ${tableName}`},handler:async(ctx)=>{let{drizzleTable:drizzleTable2,resolveCol:resolveCol2}=getTableForRequest(ctx.request);if(!database)return ctx.set.status=500,{success:!1,message:"DB not initialized"};let params=ctx.params,authResult=await performAuthCheck(ctx.request,"GET",[params.field],[]);if(authResult&&!authResult.authorized)return ctx.set.status=403,{success:!1,message:authResult.reason||"Forbidden"};if(authEnabled&&authResult?.allowedFields&&!authResult.allowedFields.includes(params.field))return ctx.set.status=403,{success:!1,message:"Forbidden: field not permitted"};if(isSensitiveOutputKey(params.field))return ctx.set.status=403,{success:!1,message:"Forbidden: field not permitted"};if(!sensitiveFieldQueryable(params.field,{authEnabled,allowedFields:authResult?.allowedFields}))return ctx.set.status=403,{success:!1,message:"Forbidden: field not permitted"};let col8=resolveCol2(params.field);if(!col8)return ctx.set.status=404,{success:!1,message:"Field not found"};let distinctScope=scopeConditionsFor(authResult,resolveCol2),distinctQuery=database.selectDistinct({value:col8}).from(drizzleTable2);return{success:!0,data:distinctScope.length>0?await distinctQuery.where(and18(...distinctScope)):await distinctQuery}}}));if(!table.excluded_methods?.includes("POST"))if(table.is_form_data&&storageConfig.enabled)declared.push(defineRoute({method:"POST",path:"/",type:"formdata",body:Type.Object({[storageConfig.formData.dataField]:Type.Optional(Type.Union([Type.String(),Type.Any()])),[storageConfig.formData.filesField]:Type.Optional(FilesSchema())}),detail:{tags:[swaggerTag],summary:`Create ${tableName} with files`,description:`Create a new ${tableName} record with file upload support`},handler:async(ctx)=>{let{drizzleTable:drizzleTable2,resolveCol:resolveCol2}=getTableForRequest(ctx.request);if(!database)return ctx.set.status=500,{success:!1,message:"DB not initialized"};let userId=ctx.request.headers.get("x-user-id"),postFormAuthResult=await performAuthCheck(ctx.request,"POST",writeFields);if(postFormAuthResult&&!postFormAuthResult.authorized)return ctx.set.status=403,{success:!1,message:postFormAuthResult.reason||"Forbidden"};let{data,files}=parseFormDataBody(ctx.body,storageConfig),payload=data,uploadedFiles=null;if(files.length>0){if(uploadedFiles=await uploadFiles(files,storageConfig,table.table_name),uploadedFiles.failed.length>0&&uploadedFiles.success.length===0){let allRefused=uploadedFiles.failed.every((one)=>one.rejected===!0),aboutType=allRefused&&uploadedFiles.failed.some((one)=>one.error.includes("File type"));return ctx.set.status=allRefused?aboutType?415:400:500,{success:!1,message:allRefused?"File refused":"File upload failed",errors:uploadedFiles.failed}}warmUploadedMedia(uploadedFiles)}let mergeUploadedMetadata=()=>{let firstFile=uploadedFiles?.success[0];if(firstFile)payload={...payload,...buildFileRecordPayload(firstFile,userId)}};if(table.columns)payload=sanitizePayload(payload,table.columns,{isFormData:table.is_form_data===!0}),payload=filterWritePayload(payload,postFormAuthResult),mergeUploadedMetadata();else mergeUploadedMetadata();let scopedForm=forceCreateScope(payload,postFormAuthResult,resolveCol2);if(!scopedForm.ok)return ctx.set.status=403,{success:!1,message:`Forbidden: ownership scope references a column '${scopedForm.field}' this entity does not have`};if(table.columns){let validation=validatePayload(payload,table.columns,!1);if(!validation.valid)return ctx.set.status=400,{success:!1,message:"Validation failed",errors:validation.errors}}let anonymous=isAnonymousCreate(table,payload);if(userId&&!anonymous)payload.createdBy=userId;let result=await database.insert(drizzleTable2).values(payload).returning();{let reqUrl=new URL(ctx.request.url);logger2.audit({entityName:table.table_name,entityId:String(result[0]?.id??""),operation:"CREATE",summary:`Created ${table.table_name}`,newValues:result[0],...createAuditIdentity(anonymous,userId,ctx.request.headers),path:reqUrl.pathname,query:reqUrl.search})}return{success:!0,data:stripUnclaimedSensitive(redactSensitiveOutput(result[0]),postFormAuthResult?.allowedFields)}}}));else declared.push(defineRoute({method:"POST",path:"/",body:Type.Object({},{additionalProperties:!0}),detail:{tags:[swaggerTag],summary:`Create ${tableName}`,description:`Create a new ${tableName} record`},handler:async(ctx)=>{let{drizzleTable:drizzleTable2,schemaTables:schemaTables2,resolveCol:resolveCol2}=getTableForRequest(ctx.request);if(!database)return ctx.set.status=500,{success:!1,message:"DB not initialized"};let payload=ctx.body,userId=ctx.request.headers.get("x-user-id"),postAuthResult=await performAuthCheck(ctx.request,"POST",writeFields);if(postAuthResult&&!postAuthResult.authorized)return ctx.set.status=403,{success:!1,message:postAuthResult.reason||"Forbidden"};if(table.columns)payload=sanitizePayload(payload,table.columns,{isFormData:table.is_form_data===!0}),payload=filterWritePayload(payload,postAuthResult);let scoped2=forceCreateScope(payload,postAuthResult,resolveCol2);if(!scoped2.ok)return ctx.set.status=403,{success:!1,message:`Forbidden: ownership scope references a column '${scoped2.field}' this entity does not have`};if(table.columns){let validation=validatePayload(payload,table.columns,!1);if(!validation.valid)return ctx.set.status=400,{success:!1,message:"Validation failed",errors:validation.errors}}let anonymous=isAnonymousCreate(table,payload);if(userId&&!anonymous)payload.createdBy=userId;if(tableName===AUTHORIZATION_TABLE_KEYS.userRoles&&payload.userId&&payload.roleId){let cols=drizzleTable2,existing=await database.select().from(drizzleTable2).where(and18(eq32(cols.userId,payload.userId),eq32(cols.roleId,payload.roleId))).limit(1);if(existing.length>0)return{success:!0,data:existing[0]}}let result=await database.insert(drizzleTable2).values(payload).returning();if(tableName===AUTHORIZATION_TABLE_KEYS.roleClaims&&config.claimsCache)config.claimsCache.invalidate().catch(()=>{});if(tableName===AUTHORIZATION_TABLE_KEYS.userRoles&&payload.roleId&&payload.userId)try{let rolesTable=schemaTables2[AUTHORIZATION_TABLE_KEYS.roles],usersTable=schemaTables2[AUTHORIZATION_TABLE_KEYS.users];if(rolesTable&&usersTable){let roleName=(await database.select().from(rolesTable).where(eq32(rolesTable.id,payload.roleId)).limit(1))[0]?.name;if(roleName===GODMIN_ROLE_NAME)await database.update(usersTable).set({isGod:!0}).where(eq32(usersTable.id,payload.userId));if(roleName)config.onRoleChanged?.({userId:String(payload.userId),roleName,assigned:!0,actorId:userId??void 0})}}catch(_e){logger2.warn("[Entity] Failed to sync is_god flag on userRole create")}{let reqUrl=new URL(ctx.request.url);logger2.audit({entityName:table.table_name,entityId:String(result[0]?.id??""),operation:"CREATE",summary:`Created ${table.table_name}`,newValues:result[0],...createAuditIdentity(anonymous,userId,ctx.request.headers),path:reqUrl.pathname,query:reqUrl.search})}return{success:!0,data:stripUnclaimedSensitive(redactSensitiveOutput(result[0]),postAuthResult?.allowedFields)}}}));if(!table.excluded_methods?.includes("PUT"))if(table.is_form_data&&storageConfig.enabled)declared.push(defineRoute({method:"PUT",path:"/:id",type:"formdata",body:Type.Object({[storageConfig.formData.dataField]:Type.Optional(Type.Union([Type.String(),Type.Any()])),[storageConfig.formData.filesField]:Type.Optional(FilesSchema())}),detail:{tags:[swaggerTag],summary:`Update ${tableName} with files`,description:`Full update of ${tableName} record with file upload support`},handler:async(ctx)=>{let{drizzleTable:drizzleTable2,idCol:idCol2,resolveCol:resolveCol2}=getTableForRequest(ctx.request);if(!database||!idCol2)return ctx.set.status=500,{success:!1,message:"No id column or DB"};let params=ctx.params,userId=ctx.request.headers.get("x-user-id"),putFormAuthResult=await performAuthCheck(ctx.request,"PUT",writeFields);if(putFormAuthResult&&!putFormAuthResult.authorized)return ctx.set.status=403,{success:!1,message:putFormAuthResult.reason||"Forbidden"};let putFormScope=scopeConditionsFor(putFormAuthResult,resolveCol2),putFormWhere=putFormScope.length>0?and18(eq32(idCol2,params.id),...putFormScope):eq32(idCol2,params.id),{data,files}=parseFormDataBody(ctx.body,storageConfig),payload=data,oldRecord=await database.select().from(drizzleTable2).where(putFormWhere).limit(1);if(oldRecord.length===0)return ctx.set.status=404,{success:!1,message:`${tableName} not found`};if(table.columns){payload=sanitizePayload(payload,table.columns,{isFormData:table.is_form_data===!0}),payload=filterWritePayload(payload,putFormAuthResult);let validation=validatePayload(payload,table.columns,!1);if(!validation.valid)return ctx.set.status=400,{success:!1,message:"Validation failed",errors:validation.errors}}let uploadedFiles=null;if(files.length>0)uploadedFiles=await uploadFiles(files,storageConfig,table.table_name),warmUploadedMedia(uploadedFiles);let result=await database.update(drizzleTable2).set(payload).where(putFormWhere).returning();{let reqUrl=new URL(ctx.request.url);logger2.audit({entityName:table.table_name,entityId:params.id,operation:"UPDATE",userId:userId||void 0,summary:`Updated ${table.table_name} (${params.id})`,oldValues:oldRecord[0],newValues:result[0],ipAddress:ctx.request.headers.get("x-forwarded-for")?.split(",")[0]?.trim()||ctx.request.headers.get("x-real-ip")?.trim()||"unknown",userAgent:ctx.request.headers.get("user-agent")||"unknown",path:reqUrl.pathname,query:reqUrl.search})}return{success:!0,data:{record:result[0],files:uploadedFiles?.success||[],fileErrors:uploadedFiles?.failed||[]}}}}));else declared.push(defineRoute({method:"PUT",path:"/:id",body:Type.Object({},{additionalProperties:!0}),detail:{tags:[swaggerTag],summary:`Update ${tableName}`,description:`Full update of ${tableName} record`},handler:async(ctx)=>{let{drizzleTable:drizzleTable2,idCol:idCol2,resolveCol:resolveCol2}=getTableForRequest(ctx.request);if(!database||!idCol2)return ctx.set.status=500,{success:!1,message:"No id column or DB"};let{params,body:payload}=ctx,userId=ctx.request.headers.get("x-user-id"),putAuthResult=await performAuthCheck(ctx.request,"PUT",writeFields);if(putAuthResult&&!putAuthResult.authorized)return ctx.set.status=403,{success:!1,message:putAuthResult.reason||"Forbidden"};let putScope=scopeConditionsFor(putAuthResult,resolveCol2),putWhere=putScope.length>0?and18(eq32(idCol2,params.id),...putScope):eq32(idCol2,params.id),oldRecord=await database.select().from(drizzleTable2).where(putWhere).limit(1);if(oldRecord.length===0)return ctx.set.status=404,{success:!1,message:`${tableName} not found`};if(table.columns){payload=sanitizePayload(payload,table.columns,{isFormData:table.is_form_data===!0}),payload=filterWritePayload(payload,putAuthResult);let validation=validatePayload(payload,table.columns,!1);if(!validation.valid)return ctx.set.status=400,{success:!1,message:"Validation failed",errors:validation.errors}}if(payload.updatedAt=new Date,userId)payload.updatedBy=userId;let result=await database.update(drizzleTable2).set(payload).where(putWhere).returning();await endSessionsForDeactivated(ctx.request,[{before:oldRecord[0],after:result[0]}]);{let reqUrl=new URL(ctx.request.url);logger2.audit({entityName:table.table_name,entityId:params.id,operation:"UPDATE",userId:userId||void 0,summary:`Updated ${table.table_name} (${params.id})`,oldValues:oldRecord[0],newValues:result[0],ipAddress:ctx.request.headers.get("x-forwarded-for")?.split(",")[0]?.trim()||ctx.request.headers.get("x-real-ip")?.trim()||"unknown",userAgent:ctx.request.headers.get("user-agent")||"unknown",path:reqUrl.pathname,query:reqUrl.search})}return{success:!0,data:stripUnclaimedSensitive(redactSensitiveOutput(result[0]),putAuthResult?.allowedFields)}}}));if(!table.excluded_methods?.includes("PATCH"))declared.push(defineRoute({method:"PATCH",path:"/:id",body:Type.Object({},{additionalProperties:!0}),detail:{tags:[swaggerTag],summary:`Patch ${tableName}`,description:`Partial update of ${tableName} record`},handler:async(ctx)=>{let{drizzleTable:drizzleTable2,idCol:idCol2,resolveCol:resolveCol2}=getTableForRequest(ctx.request);if(!database||!idCol2)return ctx.set.status=500,{success:!1,message:"No id column or DB"};let{params,body:payload}=ctx,userId=ctx.request.headers.get("x-user-id"),patchAuthResult=await performAuthCheck(ctx.request,"PATCH",writeFields);if(patchAuthResult&&!patchAuthResult.authorized)return ctx.set.status=403,{success:!1,message:patchAuthResult.reason||"Forbidden"};let patchScope=scopeConditionsFor(patchAuthResult,resolveCol2),patchWhere=patchScope.length>0?and18(eq32(idCol2,params.id),...patchScope):eq32(idCol2,params.id),oldRecord=await database.select().from(drizzleTable2).where(patchWhere).limit(1);if(oldRecord.length===0)return ctx.set.status=404,{success:!1,message:`${tableName} not found`};if(table.columns){payload=sanitizePayload(payload,table.columns,{isFormData:table.is_form_data===!0}),payload=filterWritePayload(payload,patchAuthResult);let validation=validatePayload(payload,table.columns,!0);if(!validation.valid)return ctx.set.status=400,{success:!1,message:"Validation failed",errors:validation.errors}}if(payload.updatedAt=new Date,userId)payload.updatedBy=userId;let result=await database.update(drizzleTable2).set(payload).where(patchWhere).returning();await endSessionsForDeactivated(ctx.request,[{before:oldRecord[0],after:result[0]}]);{let reqUrl=new URL(ctx.request.url);logger2.audit({entityName:table.table_name,entityId:params.id,operation:"PATCH",userId:userId||void 0,summary:`Patched ${table.table_name} (${params.id})`,oldValues:oldRecord[0],newValues:result[0],ipAddress:ctx.request.headers.get("x-forwarded-for")?.split(",")[0]?.trim()||ctx.request.headers.get("x-real-ip")?.trim()||"unknown",userAgent:ctx.request.headers.get("user-agent")||"unknown",path:reqUrl.pathname,query:reqUrl.search})}return{success:!0,data:stripUnclaimedSensitive(redactSensitiveOutput(result[0]),patchAuthResult?.allowedFields)}}}));if(!table.excluded_methods?.includes("DELETE"))declared.push(defineRoute({method:"DELETE",path:"/:id",detail:{tags:[swaggerTag],summary:`Delete ${tableName}`,description:`Delete a ${tableName} record`},handler:async(ctx)=>{let{drizzleTable:drizzleTable2,idCol:idCol2,schemaTables:schemaTables2,resolveCol:resolveCol2}=getTableForRequest(ctx.request);if(!database||!idCol2)return ctx.set.status=500,{success:!1,message:"No id column or DB"};let params=ctx.params,userId=ctx.request.headers.get("x-user-id"),deleteAuthResult=await performAuthCheck(ctx.request,"DELETE");if(deleteAuthResult&&!deleteAuthResult.authorized)return ctx.set.status=403,{success:!1,message:deleteAuthResult.reason||"Forbidden"};let deleteScope=scopeConditionsFor(deleteAuthResult,resolveCol2),deleteWhere=deleteScope.length>0?and18(eq32(idCol2,params.id),...deleteScope):eq32(idCol2,params.id),oldRecord=await database.select().from(drizzleTable2).where(deleteWhere).limit(1);if(oldRecord.length===0)return ctx.set.status=404,{success:!1,message:`${tableName} not found`};if(await activeVerificationExists(database,schemaTables2,tableName,params.id))return ctx.set.status=409,{success:!1,message:`Cannot delete: this ${tableName} is in an active verification. Complete or reject it first.`};if(await database.delete(drizzleTable2).where(deleteWhere),table.is_form_data&&storageConfig.enabled&&oldRecord[0]){let rec=oldRecord[0],filePath=rec.path,fileName=rec.name;if(typeof filePath==="string"&&typeof fileName==="string"){if(!await deleteFile(filePath,fileName,storageConfig.basePath))logger2.warn(`[Entity] Blob cleanup failed for ${table.table_name} (${params.id})`)}}if(oldRecord[0]){let link=accountLinkFor(config.accountLinks,tableName),userIds=link?accountIdsIn([oldRecord[0]],link.userColumn):[];if(userIds.length>0){let authTables=resolveAuthTablesForRequest(ctx.request,{usersTable:schemaTables2[AUTHORIZATION_TABLE_KEYS.users],sessionsTable:schemaTables2.userSessions??schemaTables2.sessions,schemaTables:schemaTables2,tenantRegistry:config.tenantRegistry,getTenantRegistry:config.getTenantRegistry}),result=await closeAccounts({db:database,usersTable:authTables.usersTable,sessionsTable:authTables.sessionsTable,onError:(error3)=>logger2.error(`[Entity] ${tableName} ${params.id} was deleted but its account could not be closed`,{error:error3})},userIds);if(result.closed>0&&!result.signedOut)logger2.warn(`[Entity] Account behind ${tableName} ${params.id} was deactivated but its open sessions were not cleared`)}}if(tableName===AUTHORIZATION_TABLE_KEYS.roleClaims&&config.claimsCache)config.claimsCache.invalidate().catch(()=>{});if(tableName===AUTHORIZATION_TABLE_KEYS.userRoles&&oldRecord[0])try{let deletedUserRole=oldRecord[0],rolesTable=schemaTables2[AUTHORIZATION_TABLE_KEYS.roles],usersTable=schemaTables2[AUTHORIZATION_TABLE_KEYS.users];if(rolesTable&&usersTable&&deletedUserRole.roleId&&deletedUserRole.userId){let roleName=(await database.select().from(rolesTable).where(eq32(rolesTable.id,deletedUserRole.roleId)).limit(1))[0]?.name;if(roleName===GODMIN_ROLE_NAME)await database.update(usersTable).set({isGod:!1}).where(eq32(usersTable.id,deletedUserRole.userId));if(roleName)config.onRoleChanged?.({userId:String(deletedUserRole.userId),roleName,assigned:!1,actorId:ctx.request.headers.get("x-user-id")??void 0})}}catch(_e){logger2.warn("[Entity] Failed to sync is_god flag on userRole delete")}{let reqUrl=new URL(ctx.request.url);logger2.audit({entityName:table.table_name,entityId:params.id,operation:"DELETE",userId:userId||void 0,summary:`Deleted ${table.table_name} (${params.id})`,oldValues:oldRecord[0],ipAddress:ctx.request.headers.get("x-forwarded-for")?.split(",")[0]?.trim()||ctx.request.headers.get("x-real-ip")?.trim()||"unknown",userAgent:ctx.request.headers.get("user-agent")||"unknown",path:reqUrl.pathname,query:reqUrl.search})}return{success:!0,data:null}}}));if(table.bulk_endpoints_enabled){if(!table.excluded_methods?.includes("POST"))declared.push(defineRoute({method:"POST",path:"/bulk",body:Type.Array(Type.Object({},{additionalProperties:!0})),detail:{tags:[swaggerTag],summary:`Bulk create ${tableName}`,description:`Create multiple ${tableName} records`},handler:async(ctx)=>{let{drizzleTable:drizzleTable2,resolveCol:resolveCol2}=getTableForRequest(ctx.request);if(!database)return ctx.set.status=500,{success:!1,message:"DB not initialized"};let items=ctx.body;if(!Array.isArray(items))return ctx.set.status=400,{success:!1,message:"Body must be an array"};if(items.length>MAX_BULK_ITEMS)return ctx.set.status=400,{success:!1,message:`Bulk request exceeds ${MAX_BULK_ITEMS} items`};let userId=ctx.request.headers.get("x-user-id"),bulkPostAuth=await performAuthCheck(ctx.request,"POST",writeFields);if(bulkPostAuth&&!bulkPostAuth.authorized)return ctx.set.status=403,{success:!1,message:bulkPostAuth.reason||"Forbidden"};let sanitizedItems=[];for(let raw of items){let payload=raw;if(table.columns)payload=sanitizePayload(payload,table.columns,{isFormData:table.is_form_data===!0}),payload=filterWritePayload(payload,bulkPostAuth);let scopedItem=forceCreateScope(payload,bulkPostAuth,resolveCol2);if(!scopedItem.ok)return ctx.set.status=403,{success:!1,message:`Forbidden: ownership scope references a column '${scopedItem.field}' this entity does not have`};if(table.columns){let validation=validatePayload(payload,table.columns,!1);if(!validation.valid)return ctx.set.status=400,{success:!1,message:"Validation failed",errors:validation.errors}}if(userId&&!isAnonymousCreate(table,payload))payload.createdBy=userId;sanitizedItems.push(payload)}let txResult=await database.transaction(async(tx)=>{let results=[];for(let payload of sanitizedItems){let result=await tx.insert(drizzleTable2).values(payload).returning();results.push(result[0])}return results}),auditPath=auditPathContext(ctx.request);for(let[i,row]of txResult.entries())await logger2.audit({entityName:table.table_name,entityId:auditEntityId(row.id),operation:"CREATE",summary:`Bulk created ${table.table_name} (${row.id})`,newValues:row,...auditIdentityFor(table,sanitizedItems[i]??{},userId,ctx.request.headers),...auditPath});return{success:!0,data:txResult.map((r)=>projectWrittenRow(r,bulkPostAuth))}}}));if(!table.excluded_methods?.includes("PUT"))declared.push(defineRoute({method:"PUT",path:"/bulk",body:bulkUpdateSchema,detail:{tags:[swaggerTag],summary:`Bulk update ${tableName}`,description:`Update multiple ${tableName} records`},handler:async(ctx)=>{let{drizzleTable:drizzleTable2,idCol:idCol2,resolveCol:resolveCol2}=getTableForRequest(ctx.request);if(!database||!idCol2)return ctx.set.status=500,{success:!1,message:"No id column or DB"};let items=ctx.body;if(!Array.isArray(items))return ctx.set.status=400,{success:!1,message:"Body must be an array"};if(items.length>MAX_BULK_ITEMS)return ctx.set.status=400,{success:!1,message:`Bulk request exceeds ${MAX_BULK_ITEMS} items`};let userId=ctx.request.headers.get("x-user-id"),bulkPutAuth=await performAuthCheck(ctx.request,"PUT",writeFields);if(bulkPutAuth&&!bulkPutAuth.authorized)return ctx.set.status=403,{success:!1,message:bulkPutAuth.reason||"Forbidden"};let bulkPutScope=scopeConditionsFor(bulkPutAuth,resolveCol2),prepared=[];for(let item of items){let payload=item.data;if(table.columns){payload=sanitizePayload(payload,table.columns,{isFormData:table.is_form_data===!0}),payload=filterWritePayload(payload,bulkPutAuth);let validation=validatePayload(payload,table.columns,!0);if(!validation.valid)return ctx.set.status=400,{success:!1,message:"Validation failed",errors:validation.errors}}if(payload.updatedAt=new Date,userId)payload.updatedBy=userId;prepared.push({id:item.id,payload})}let beforeById=new Map,txResult=await database.transaction(async(tx)=>{if(prepared.length>0){let idFilter=inArray11(idCol2,prepared.map((p)=>p.id)),beforeRows=await tx.select().from(drizzleTable2).where(bulkPutScope.length>0?and18(idFilter,...bulkPutScope):idFilter);for(let row of beforeRows)if(row.id!=null)beforeById.set(String(row.id),row)}let results=[];for(let{id,payload}of prepared){let result=await tx.update(drizzleTable2).set(payload).where(bulkPutScope.length>0?and18(eq32(idCol2,id),...bulkPutScope):eq32(idCol2,id)).returning();if(result[0])results.push(result[0])}return results});await endSessionsForDeactivated(ctx.request,txResult.map((row)=>({before:beforeById.get(String(row.id)),after:row})));let auditPath=auditPathContext(ctx.request);for(let row of txResult){let rowId=String(row.id);await logger2.audit({entityName:table.table_name,entityId:auditEntityId(row.id),operation:"UPDATE",userId:userId||void 0,summary:`Bulk updated ${table.table_name} (${rowId})`,oldValues:beforeById.get(rowId),newValues:row,...auditPath})}return{success:!0,data:txResult.map((r)=>projectWrittenRow(r,bulkPutAuth))}}}));if(!table.excluded_methods?.includes("DELETE"))declared.push(defineRoute({method:"DELETE",path:"/bulk",body:bulkDeleteSchema,detail:{tags:[swaggerTag],summary:`Bulk delete ${tableName}`,description:`Delete multiple ${tableName} records`},handler:async(ctx)=>{let{drizzleTable:drizzleTable2,idCol:idCol2,resolveCol:resolveCol2}=getTableForRequest(ctx.request);if(!database||!idCol2)return ctx.set.status=500,{success:!1,message:"No id column or DB"};let ids=ctx.body;if(!Array.isArray(ids))return{success:!1,message:"Body must be an array of ids"};if(ids.length>MAX_BULK_ITEMS)return ctx.set.status=400,{success:!1,message:`Bulk request exceeds ${MAX_BULK_ITEMS} items`};let bulkDeleteAuth=await performAuthCheck(ctx.request,"DELETE");if(bulkDeleteAuth&&!bulkDeleteAuth.authorized)return ctx.set.status=403,{success:!1,message:bulkDeleteAuth.reason||"Forbidden"};let bulkDeleteScope=scopeConditionsFor(bulkDeleteAuth,resolveCol2),userId=ctx.request.headers.get("x-user-id"),removed=[];await database.transaction(async(tx)=>{for(let id of ids){let r=await tx.delete(drizzleTable2).where(bulkDeleteScope.length>0?and18(eq32(idCol2,id),...bulkDeleteScope):eq32(idCol2,id)).returning();removed.push(...r)}});{let link=accountLinkFor(config.accountLinks,tableName),bulkUserIds=link?accountIdsIn(removed,link.userColumn):[];if(bulkUserIds.length>0){let authTables=resolveAuthTablesForRequest(ctx.request,{usersTable:schemaTables[AUTHORIZATION_TABLE_KEYS.users],sessionsTable:schemaTables.userSessions??schemaTables.sessions,schemaTables,tenantRegistry:config.tenantRegistry,getTenantRegistry:config.getTenantRegistry}),result=await closeAccounts({db:database,usersTable:authTables.usersTable,sessionsTable:authTables.sessionsTable,onError:(error3)=>logger2.error(`[Entity] ${removed.length} ${tableName} rows were deleted but their accounts could not be closed`,{error:error3})},bulkUserIds);if(result.closed>0&&!result.signedOut)logger2.warn(`[Entity] ${result.closed} account(s) behind deleted ${tableName} rows were deactivated but their open sessions were not cleared`)}}let auditPath=auditPathContext(ctx.request);for(let row of removed)await logger2.audit({entityName:table.table_name,entityId:auditEntityId(row.id),operation:"DELETE",userId:userId||void 0,summary:`Bulk deleted ${table.table_name} (${row.id})`,oldValues:row,...auditPath});return{success:!0,data:{deleted:removed.length}}}}))}mountRoutes(app,[{prefix:`/${tableName}`,guards:entityGuards,routes:declared}])}return app}init_esm3();init_AccountLink();import{randomUUID as randomUUID9}from"crypto";var LOCK_TTL_SECONDS2=60,RENEW_INTERVAL_MS=20000,lockKeyFor=(mappingId)=>`integrations:run:${mappingId}`;async function acquireRunLock(store,mappingId,deps={}){let key2=lockKeyFor(mappingId),now=deps.now??Date.now,ttl=deps.ttlSeconds??LOCK_TTL_SECONDS2,token=randomUUID9(),existing=await store.read(key2);if(!existing.success)return{ok:!1,reason:"unavailable",error:existing.error};if(existing.data)return{ok:!1,reason:"held",heldSince:existing.data.acquiredAt??null};let created=await store.create(key2,{token,acquiredAt:now()},ttl);if(!created.success)return{ok:!1,reason:"unavailable",error:created.error};let readBack=await store.read(key2);if(!readBack.success)return{ok:!1,reason:"unavailable",error:readBack.error};if(readBack.data?.token!==token)return{ok:!1,reason:"held",heldSince:readBack.data?.acquiredAt??null};let held=!0,timer=setInterval(()=>{(async()=>{let current=await store.read(key2);if(!current.success)return;if(current.data?.token!==token){held=!1,clearInterval(timer),deps.logger?.warn("[Integrations] Run lock was taken over",{mappingId});return}await store.create(key2,current.data,ttl)})()},RENEW_INTERVAL_MS);return timer.unref?.(),{ok:!0,handle:{token,isHeld:()=>held,release:async()=>{if(clearInterval(timer),!held)return;held=!1;let current=await store.read(key2);if(current.success&¤t.data?.token===token)await store.remove(key2)}}}}import{createHash as createHash3}from"crypto";function buildUrl(baseUrl,path7,params){let base=baseUrl.trim().replace(/\/+$/,"");if(!base)return{ok:!1,error:"The source has no base address."};let raw=(path7??"").trim();if(/^[a-z][a-z0-9+.-]*:\/\//i.test(raw)||raw.startsWith("//"))return{ok:!1,error:`The endpoint path points at another host (${raw}). A path must be relative to the source address.`};let suffix=raw===""?"":raw.startsWith("/")?raw:`/${raw}`,url;try{url=new URL(`${base}${suffix}`)}catch{return{ok:!1,error:`The source address is not a valid URL: ${baseUrl}`}}for(let[key2,value2]of Object.entries(params??{})){if(value2==null)continue;url.searchParams.set(key2,String(value2))}return{ok:!0,url}}function applyAuth(source,url){let headers={accept:"application/json",...source.extraHeaders??{}},token=source.token?.trim();if(!token)return{headers};switch(source.authType){case"bearer":case"oauth2":headers[source.authHeaderName||"Authorization"]=`Bearer ${token}`;break;case"api_key":if(source.authQueryParam)url.searchParams.set(source.authQueryParam,token);else headers[source.authHeaderName||"X-API-Key"]=token;break;case"basic":headers.Authorization=`Basic ${token}`;break;default:break}return{headers}}function describeTransportError(error3){let message=error3 instanceof Error?error3.message:String(error3),cause=error3?.cause,code=cause?.code??"",blob=`${message} ${code} ${cause?.message??""}`;if(/UNABLE_TO_GET_ISSUER_CERT|SELF_SIGNED|self.signed|CERT_|unable to verify|ERR_TLS|altnames/i.test(blob))return`Certificate verification failed (${code||"incomplete chain"}). If this server's certificate is knowingly self-signed or incomplete, turn off certificate verification for this source and try again.`;if(/ENOTFOUND|EAI_AGAIN|getaddrinfo|Unable to resolve/i.test(blob))return`The address could not be resolved (DNS${code?`: ${code}`:""}). Check the source address.`;if(/ECONNREFUSED/i.test(blob))return`The connection was refused (${code||"ECONNREFUSED"}). The address and port are reachable but nothing is listening.`;if(/ETIMEDOUT|timeout|timed out|aborted|AbortError|UND_ERR/i.test(blob))return`The connection timed out (${code||"timeout"}). Check network and firewall access to this address.`;if(/ECONNRESET|EPIPE/i.test(blob))return`The connection was closed by the other side (${code||"ECONNRESET"}).`;return`Connection error: ${message}`}async function fetchOnce(source,request,deps={}){let impl=deps.fetchImpl??fetch,headers={...request.headers},method=request.method.toUpperCase(),hasBody=request.body!==void 0&&method!=="GET"&&method!=="HEAD";if(hasBody&&!headers["content-type"]&&!headers["Content-Type"])headers["content-type"]="application/json";let init={method,headers,cache:"no-store",signal:AbortSignal.timeout(source.timeoutMs??30000),tls:{rejectUnauthorized:source.tlsVerify!==!1},...hasBody?{body:typeof request.body==="string"?request.body:JSON.stringify(request.body)}:{}},response;try{response=await impl(request.url.toString(),init)}catch(error3){return deps.logger?.warn("[Integrations] Outbound request failed",{sourceId:source.id,url:request.url.origin+request.url.pathname,error:error3 instanceof Error?error3.message:String(error3)}),{ok:!1,status:0,body:null,linkHeader:null,error:describeTransportError(error3)}}let text=await response.text().catch(()=>""),body=null;if(text)try{body=JSON.parse(text)}catch{body=text}let linkHeader=response.headers.get("link");if(!response.ok){let detail=typeof body==="string"?body:body==null?"":JSON.stringify(body);return{ok:!1,status:response.status,body,linkHeader,error:`The source returned HTTP ${response.status}${detail?` \u2014 ${detail.slice(0,300)}`:""}`}}return{ok:!0,status:response.status,body,linkHeader}}var EXPIRY_SKEW_MS=5000,DEFAULT_TTL_MS=300000,DEFAULT_REFRESH_TTL_MS=1800000,FIELD_SEPARATOR="\x00";function credentialFingerprint(config){let material=[config.tokenUrl,config.grantType,config.clientId??"",config.clientSecret??"",config.username??"",config.password??"",config.scope??"",config.clientAuth??"basic"].join(FIELD_SEPARATOR);return createHash3("sha256").update(material).digest("hex").slice(0,32)}class TokenCache{entries=new Map;get(sourceId,fingerprint){let entry=this.entries.get(sourceId);if(!entry)return null;if(entry.fingerprint!==fingerprint)return this.entries.delete(sourceId),null;return entry}set(sourceId,token){this.entries.set(sourceId,token)}invalidate(sourceId){this.entries.delete(sourceId)}clear(){this.entries.clear()}}var isUsable=(token,now)=>token.expiresAt>now+EXPIRY_SKEW_MS,canRefresh=(token,now)=>Boolean(token.refreshToken)&&(token.refreshExpiresAt??0)>now+EXPIRY_SKEW_MS;function buildTokenRequest(config,form){let headers={"content-type":"application/x-www-form-urlencoded",accept:"application/json"};if(config.scope&&!form.has("scope"))form.set("scope",config.scope);let clientId=config.clientId??"",clientSecret=config.clientSecret??"";if((config.clientAuth??"basic")==="basic")headers.authorization=`Basic ${Buffer.from(`${clientId}:${clientSecret}`).toString("base64")}`;else{if(clientId)form.set("client_id",clientId);if(clientSecret)form.set("client_secret",clientSecret)}return{headers,body:form.toString()}}async function requestToken(config,form,now,fetchImpl){let{headers,body}=buildTokenRequest(config,form),response;try{response=await fetchImpl(config.tokenUrl,{method:"POST",headers,body,cache:"no-store",signal:AbortSignal.timeout(config.timeoutMs??30000),tls:{rejectUnauthorized:config.tlsVerify!==!1}})}catch(error3){return{ok:!1,error:`Token request failed \u2014 ${describeTransportError(error3)}`}}let text=await response.text().catch(()=>""),json={};try{json=text?JSON.parse(text):{}}catch{}if(!response.ok||!json.access_token){let detail=json.error_description||json.error||text.slice(0,200);return{ok:!1,error:`Could not obtain an OAuth2 token (HTTP ${response.status})${detail?`: ${detail}`:""}`}}return{ok:!0,token:{accessToken:json.access_token,expiresAt:now+(json.expires_in?json.expires_in*1000:DEFAULT_TTL_MS),refreshToken:json.refresh_token,refreshExpiresAt:json.refresh_token?now+(json.refresh_expires_in?json.refresh_expires_in*1000:DEFAULT_REFRESH_TTL_MS):void 0,fingerprint:credentialFingerprint(config)}}}async function getAccessToken(config,cache,deps={}){let fetchImpl=deps.fetchImpl??fetch,now=deps.now?.()??Date.now();if(!config.tokenUrl?.trim())return{ok:!1,error:"No token URL is configured for this source."};let fingerprint=credentialFingerprint(config),cached=cache.get(config.sourceId,fingerprint);if(cached&&isUsable(cached,now))return{ok:!0,accessToken:cached.accessToken,fromCache:!0};if(cached&&canRefresh(cached,now)){let refreshed=await requestToken(config,new URLSearchParams({grant_type:"refresh_token",refresh_token:cached.refreshToken}),now,fetchImpl);if(refreshed.ok)return cache.set(config.sourceId,refreshed.token),{ok:!0,accessToken:refreshed.token.accessToken,fromCache:!1}}let form=new URLSearchParams({grant_type:config.grantType});if(config.grantType==="password")form.set("username",config.username??""),form.set("password",config.password??"");let minted=await requestToken(config,form,now,fetchImpl);if(!minted.ok)return{ok:!1,error:minted.error};return cache.set(config.sourceId,minted.token),{ok:!0,accessToken:minted.token.accessToken,fromCache:!1}}import{and as and19,eq as eq33,inArray as inArray12}from"drizzle-orm";var DEFAULT_IGNORED=["updatedAt","updated_at","updatedBy","updated_by"];function planRevert(input){let ignored=new Set([...DEFAULT_IGNORED,...input.ignoreColumns??[]]),ops={deletes:[],restores:[],reinserts:[],conflicts:[]};for(let image of input.preImages)switch(image.action){case"create":{if(!image.rowId)break;let now=input.current.get(image.rowId);if(!now)break;if(!input.force&&changedSince(image.after,now,ignored)){ops.conflicts.push({id:image.rowId,reason:"This record was edited after the import; deleting it would discard that edit."});break}ops.deletes.push(image.rowId);break}case"update":case"deactivate":{if(!image.rowId||!image.before)break;let now=input.current.get(image.rowId);if(!now)break;if(!input.force&&changedSince(image.after,now,ignored)){ops.conflicts.push({id:image.rowId,reason:"This record was edited after the import; restoring it would discard that edit."});break}ops.restores.push({id:image.rowId,row:image.before});break}case"delete":{if(!image.before)break;if(image.rowId&&input.current.has(image.rowId)){ops.conflicts.push({id:image.rowId,reason:"A record with this identifier exists again; it was not replaced."});break}ops.reinserts.push(image.before);break}default:break}return ops}function changedSince(after,now,ignored){if(!after)return!1;for(let[key2,value2]of Object.entries(after)){if(ignored.has(key2))continue;if(!sameValue(value2,now[key2]))return!0}return!1}function sameValue(a,b){if(a===b)return!0;if(a==null&&b==null)return!0;if(a instanceof Date&&b instanceof Date)return a.getTime()===b.getTime();if(a instanceof Date||b instanceof Date){let da=a instanceof Date?a:new Date(String(a)),db=b instanceof Date?b:new Date(String(b));return!Number.isNaN(da.getTime())&&!Number.isNaN(db.getTime())?da.getTime()===db.getTime():!1}if(typeof a==="object"||typeof b==="object")try{return JSON.stringify(a)===JSON.stringify(b)}catch{return!1}return String(a)===String(b)}var CHUNK=500;async function applyRevert(deps,ops,meta){let idCol=deps.resolveCol("id");if(!idCol)return{ok:!1,conflicts:ops.conflicts.length,error:`"${meta.targetEntity}" has no id column; its rows cannot be addressed.`};let now=new Date;try{let work=async(tx)=>{let deleted=0,restored=0,reinserted=0;for(let i=0;i<ops.deletes.length;i+=CHUNK){let chunk=ops.deletes.slice(i,i+CHUNK),removed=await tx.delete(deps.drizzleTable).where(inArray12(idCol,chunk)).returning();deleted+=removed.length}for(let restore of ops.restores){let{id:_id,createdAt:_c,createdBy:_cb,...rest3}=restore.row,written=await tx.update(deps.drizzleTable).set({...rest3,updatedAt:now,updatedBy:meta.userId}).where(and19(eq33(idCol,restore.id))).returning();restored+=written.length}if(ops.reinserts.length>0)for(let i=0;i<ops.reinserts.length;i+=CHUNK){let chunk=ops.reinserts.slice(i,i+CHUNK),written=await tx.insert(deps.drizzleTable).values(chunk).returning();reinserted+=written.length}return{deleted,restored,reinserted}};return{ok:!0,...meta.tx?await work(meta.tx):await deps.db.transaction(work),conflicts:ops.conflicts.length}}catch(error3){return deps.logger.error("[Integrations] Revert transaction failed",{targetEntity:meta.targetEntity,error:error3 instanceof Error?error3.message:String(error3)}),{ok:!1,conflicts:ops.conflicts.length,error:"The database rejected this undo; it was rolled back and nothing changed."}}}function groupPreImagesByEntity(preImages){let byEntity=new Map;for(let image of preImages){let list2=byEntity.get(image.targetEntity);if(list2)list2.push(image);else byEntity.set(image.targetEntity,[image])}return[...byEntity.entries()].map(([targetEntity,images])=>({targetEntity,preImages:images}))}function touchedRowIds(preImages){let ids=new Set;for(let image of preImages)if(image.rowId)ids.add(image.rowId);return[...ids]}var TREE_PARENT_KEY="__treeParent";function flattenTree(records,childrenPath){let out=[],truncated=!1,visit=(node,parent,depth)=>{if(!node||typeof node!=="object")return;if(depth>64){truncated=!0;return}let source=node,children=source[childrenPath],{[childrenPath]:_nested,...own}=source;if(out.push({...own,["__depth"]:depth,["__treeParent"]:parent??null}),!Array.isArray(children))return;for(let child of children)visit(child,own,depth+1)};for(let root of records)visit(root,null,0);return{records:out,truncated}}function treeChildrenPath(value2){let trimmed=(value2??"").trim();return trimmed.length>0?trimmed:null}function getByPath(obj,path7){if(!path7)return;let cur=obj;for(let part of path7.split(".")){if(cur==null||typeof cur!=="object")return;cur=cur[part]}return cur}function readSourceValue(external,parent,path7){return readOne(external,parent,path7)}function readOne(external,parent,path7){return path7.startsWith("__parent.")?getByPath(parent,path7.slice(9)):getByPath(external,path7)}var TEMPLATE_TOKEN=/\{([^{}]+)\}/g;function isTemplateSource(source){return TEMPLATE_TOKEN.lastIndex=0,TEMPLATE_TOKEN.test(source)}function renderTemplate(template,external,parent){let treeParentAbsent=external!=null&&typeof external==="object"&&TREE_PARENT_KEY in external&&external[TREE_PARENT_KEY]==null,readsTreeParent=(text)=>text.includes(`{${TREE_PARENT_KEY}.`),readToken=(token)=>{let[path7="",transform3]=token.split("|"),value2=readOne(external,parent,path7.trim());if(transform3===void 0)return value2;let result=applyTransform(value2,transform3.trim());return result.ok?result.value:value2};for(let alternative of template.split("||")){let candidate=alternative.trim();if(candidate==="")continue;if(treeParentAbsent&&readsTreeParent(candidate))continue;let complete=!0,rendered=candidate.replace(TEMPLATE_TOKEN,(_match,token)=>{let value2=readToken(token);if(value2==null||String(value2).trim()==="")complete=!1;return value2==null?"":String(value2)});if(complete&&rendered.trim()!=="")return rendered}let last=template.split("||").pop()??template;if(treeParentAbsent&&readsTreeParent(last))return"";return last.trim().replace(TEMPLATE_TOKEN,(_match,token)=>{let value2=readToken(token);return value2==null?"":String(value2)})}function extractRecords(body,rootPath){if(rootPath){let at=getByPath(body,rootPath);if(Array.isArray(at))return at;return at&&typeof at==="object"?[at]:[]}let candidate=firstArrayLike(body)??deepFirstArrayOfObjects(body);return Array.isArray(candidate)?candidate:[]}var ENVELOPE_KEYS=["content","items","data","results","records","rows","list","value","responseObject"];function firstArrayLike(body){if(Array.isArray(body))return body;if(!body||typeof body!=="object")return;let b=body;for(let key2 of ENVELOPE_KEYS){let v=b[key2];if(Array.isArray(v))return v;if(v&&typeof v==="object"){let nested=v.items;if(Array.isArray(nested))return nested}}return}function deepFirstArrayOfObjects(body,depth=0){if(depth>5||body==null||typeof body!=="object"||body instanceof Date)return;if(Array.isArray(body))return body.length>0&&typeof body[0]==="object"&&body[0]!==null?body:void 0;let entries=Object.values(body);for(let v of entries)if(Array.isArray(v)&&v.length>0&&typeof v[0]==="object"&&v[0]!==null)return v;for(let v of entries)if(v&&typeof v==="object"&&!Array.isArray(v)){let found=deepFirstArrayOfObjects(v,depth+1);if(found)return found}for(let v of entries)if(Array.isArray(v))return v;return}var FOLD={\u{131}:"i",\u{130}:"I",\u{15f}:"s",\u{15e}:"S",\u{11f}:"g",\u{11e}:"G",\u{f8}:"o",\u{d8}:"O",\u{e6}:"ae",\u{c6}:"AE",\u{df}:"ss",\u{111}:"d",\u{110}:"D",\u{142}:"l",\u{141}:"L"};function foldToAscii(value2){return value2.replace(/[\u0131\u0130\u015F\u015E\u011F\u011E\u00F8\u00D8\u00E6\u00C6\u00DF\u0111\u0110\u0142\u0141]/g,(ch)=>FOLD[ch]??ch).normalize("NFD").replace(/[\u0300-\u036f]/g,"")}var TRUTHY=new Set(["true","1","yes","y","evet","e","on"]),FALSY=new Set(["false","0","no","n","hayir","hay\u0131r","h","off"]);function applyTransform(value2,transform3,locale){if(value2==null)return{ok:!0,value:value2};switch(transform3){case"trim":return{ok:!0,value:String(value2).trim()};case"upper":return{ok:!0,value:String(value2).trim().toLocaleUpperCase(locale)};case"lower":return{ok:!0,value:String(value2).trim().toLocaleLowerCase(locale)};case"toString":return{ok:!0,value:String(value2)};case"toNumber":{if(typeof value2==="boolean")return{ok:!1,reason:"not a number",raw:value2};let s=String(value2).trim();if(s==="")return{ok:!0,value:null};let n=Number(s);return Number.isFinite(n)?{ok:!0,value:n}:{ok:!1,reason:"not a number",raw:value2}}case"toBoolean":{if(typeof value2==="boolean")return{ok:!0,value:value2};let s=String(value2).trim().toLocaleLowerCase("tr-TR");if(s==="")return{ok:!0,value:null};if(TRUTHY.has(s))return{ok:!0,value:!0};if(FALSY.has(s))return{ok:!0,value:!1};return{ok:!1,reason:"not a yes/no value",raw:value2}}case"emailSafe":return{ok:!0,value:foldToAscii(String(value2)).toLowerCase().trim().replace(/[^a-z0-9._@+-]+/g,".").replace(/\.{2,}/g,".").replace(/\.*@\.*/g,"@").replace(/^[.\-_]+|[.\-_]+$/g,"")};case"slug":return{ok:!0,value:foldToAscii(String(value2)).toLowerCase().trim().replace(/[^a-z0-9]+/g,"-").replace(/-{2,}/g,"-").replace(/^-+|-+$/g,"")};case"pathLeaf":{let parts=String(value2).split(/[/\\>\u203A\u00BB]|->/).map((part)=>part.trim()).filter((part)=>part!=="");return{ok:!0,value:parts.length>0?parts[parts.length-1]:""}}case"toList":{if(Array.isArray(value2))return{ok:!0,value:value2};let s=String(value2).trim();if(s==="")return{ok:!0,value:[]};return{ok:!0,value:s.split(",").map((part)=>part.trim()).filter((part)=>part!=="")}}case"dateOnly":{let s=String(value2).trim();if(s==="")return{ok:!0,value:null};let leading=s.match(/^\d{4}-\d{2}-\d{2}/);if(leading)return{ok:!0,value:leading[0]};let d=new Date(s);return Number.isNaN(d.getTime())?{ok:!1,reason:"not a date",raw:value2}:{ok:!0,value:d.toISOString().slice(0,10)}}default:return{ok:!0,value:value2}}}function isEmpty(v){return v==null||typeof v==="string"&&v.trim()===""}function mapRecord(external,mappings,parent){let row={},problems=[];for(let fm of mappings){if(!fm.target)continue;let value2;if(fm.constant!=null&&fm.constant!=="")value2=fm.constant;else{let raw=isTemplateSource(fm.source)?renderTemplate(fm.source,external,parent):readOne(external,parent,fm.source),result=applyTransform(raw,fm.transform);if(!result.ok)problems.push(`${fm.source} \u2192 ${fm.target}: ${result.reason} (${String(result.raw)})`),value2=null;else value2=result.value}if(isEmpty(value2)&&fm.fallback!=null&&fm.fallback!==""){let raw=isTemplateSource(fm.fallback)?renderTemplate(fm.fallback,external,parent):fm.fallback,result=applyTransform(raw,fm.transform);if(result.ok)value2=result.value;else problems.push(`fallback \u2192 ${fm.target}: ${result.reason} (${String(result.raw)})`),value2=null}if(value2!==void 0)row[fm.target]=value2}return{row,problems}}function buildMappedRecords(externalRecords,mappings,dedupTargetField,parents){let records=[],issues=[];return externalRecords.forEach((ext,i)=>{let{row,problems}=mapRecord(ext,mappings,parents?.[i]);for(let message of problems)issues.push({sourceIndex:i,kind:"invalid_value",message});let dedupKey=dedupTargetField&&!isEmpty(row[dedupTargetField])?String(row[dedupTargetField]).trim():null;records.push({data:row,dedupKey,sourceIndex:i,external:ext,parent:parents?.[i]})}),{records,issues}}function flattenKeys(obj,prefix="",out=[],depth=0){if(out.length>200||depth>4||obj==null||typeof obj!=="object")return out;if(Array.isArray(obj)){if(prefix)out.push(prefix);return out}for(let[k,v]of Object.entries(obj)){let path7=prefix?`${prefix}.${k}`:k;if(v!=null&&typeof v==="object"&&!Array.isArray(v))flattenKeys(v,path7,out,depth+1);else out.push(path7)}return out}var MAX_FANOUT_COMBINATIONS=5000;function resolvePath2(rawPath,values){let missing=new Set,used=new Set;return{path:rawPath.replace(/\{([a-zA-Z_][\w-]*)\}|:([a-zA-Z_]\w*)/g,(_full,brace,colon)=>{let key2=brace??colon,v=values[key2];if(v==null||v==="")return missing.add(key2),brace?`{${key2}}`:`:${key2}`;return used.add(key2),encodeURIComponent(String(v))}),missing:[...missing],used}}function planFetchOrder(endpoints,roots){let byId=new Map(endpoints.map((e)=>[e.id,e])),order=[],settled=new Set,onPath=[],onPathSet=new Set,visit=(id,requiredBy)=>{if(settled.has(id))return null;if(onPathSet.has(id))return{ok:!1,reason:"cycle",chain:[...onPath.slice(onPath.indexOf(id)),id]};let node=byId.get(id);if(!node)return{ok:!1,reason:"unknown_endpoint",missing:id,requiredBy:requiredBy??id};onPath.push(id),onPathSet.add(id);for(let binding of node.paramBindings??[]){if(binding.from!=="endpoint")continue;let failure=visit(binding.endpointId,id);if(failure)return failure}return onPath.pop(),onPathSet.delete(id),settled.add(id),order.push(id),null};for(let id of roots??endpoints.map((e)=>e.id)){let failure=visit(id,null);if(failure)return failure}return{ok:!0,order}}function expandBindings(bindings,sources,limit=MAX_FANOUT_COMBINATIONS){if(!bindings||bindings.length===0)return{ok:!0,calls:[{values:{}}]};let axes=[];for(let binding of bindings){if(binding.from==="static"){axes.push({param:binding.param,options:[{value:binding.value}]});continue}if(binding.from==="endpoint"){let options=(sources.parents?.[binding.endpointId]??[]).map((record3)=>({value:getByPath(record3,binding.field),parent:record3})).filter((o)=>o.value!=null&&o.value!=="");axes.push({param:binding.param,options});continue}let values=sources.entityValues?.[`${binding.entity}.${binding.field}`]??[];axes.push({param:binding.param,options:values.filter((v)=>v!=null&&v!=="").map((value2)=>({value:value2}))})}if(axes.some((a)=>a.options.length===0))return{ok:!0,calls:[]};let combinations=axes.reduce((n,a)=>n*a.options.length,1);if(combinations>limit)return{ok:!1,reason:"too_many",combinations,limit};let calls=[{values:{}}];for(let axis of axes){let next=[];for(let call of calls)for(let option of axis.options)next.push({values:{...call.values,[axis.param]:option.value},parent:option.parent??call.parent});calls=next}return{ok:!0,calls}}function parentEndpointIds(bindings){let ids=new Set;for(let b of bindings??[])if(b.from==="endpoint")ids.add(b.endpointId);return[...ids]}function entityValueKeys(bindings){let keys=new Set;for(let b of bindings??[])if(b.from==="entity")keys.add(`${b.entity}.${b.field}`);return[...keys]}var MAX_PAGES=1000,DEFAULT_PAGE_SIZE=200,initialState2=()=>({pagesFetched:0,recordsFetched:0,cursor:null});function firstPageParams(profile){switch(profile.kind){case"page_number":{let params={[profile.pageParam]:String(profile.firstPage??0)};if(profile.sizeParam)params[profile.sizeParam]=String(pageSize(profile));return params}case"offset":return{[profile.offsetParam]:"0",[profile.limitParam]:String(pageSize(profile))};case"cursor":{let params={};if(profile.sizeParam)params[profile.sizeParam]=String(pageSize(profile));return params}case"link_header":{let params={};if(profile.sizeParam)params[profile.sizeParam]=String(pageSize(profile));return params}default:return{}}}function reportedTotal(profile,body){let path7=profile.kind==="page_number"||profile.kind==="offset"?profile.totalPath:void 0;if(!path7)return null;let raw=getByPath(body,path7);if(typeof raw==="number"&&Number.isFinite(raw)&&raw>=0)return Math.floor(raw);if(typeof raw==="string"&&raw.trim()!==""){let n=Number(raw);if(Number.isFinite(n)&&n>=0)return Math.floor(n)}return null}function nextPage(profile,state,observed){if(profile.kind==="none")return{kind:"done",reason:"single request"};if(state.pagesFetched>=MAX_PAGES)return{kind:"failed",reason:`Paging stopped after ${MAX_PAGES} requests without reaching the end. The source may not be honouring the paging parameters; importing this would be a partial read.`};if(observed.recordCount===0)return{kind:"done",reason:"empty page"};switch(profile.kind){case"page_number":{if(isLastPage(profile.lastPagePath,observed.body))return{kind:"done",reason:"source reported the last page"};let total=reportedTotal(profile,observed.body);if(total!==null&&state.recordsFetched>=total)return{kind:"done",reason:"reported total reached"};if(observed.recordCount<pageSize(profile))return{kind:"done",reason:"short page"};let next=(profile.firstPage??0)+state.pagesFetched,params={[profile.pageParam]:String(next)};if(profile.sizeParam)params[profile.sizeParam]=String(pageSize(profile));return{kind:"params",params}}case"offset":{let total=reportedTotal(profile,observed.body);if(total!==null&&state.recordsFetched>=total)return{kind:"done",reason:"reported total reached"};if(observed.recordCount<pageSize(profile))return{kind:"done",reason:"short page"};return{kind:"params",params:{[profile.offsetParam]:String(state.recordsFetched),[profile.limitParam]:String(pageSize(profile))}}}case"cursor":{let raw=getByPath(observed.body,profile.nextCursorPath),next=raw==null?"":String(raw).trim();if(next==="")return{kind:"done",reason:"no further cursor"};if(state.cursor!=null&&next===state.cursor)return{kind:"failed",reason:`The source returned the same cursor twice (${next}); paging would not advance.`};let params={[profile.cursorParam]:next};if(profile.sizeParam)params[profile.sizeParam]=String(pageSize(profile));return{kind:"params",params}}case"link_header":{let url=parseNextLink(observed.linkHeader);if(!url)return{kind:"done",reason:"no rel=next link"};return{kind:"url",url}}default:return{kind:"done",reason:"single request"}}}function pageSize(profile){let declared="pageSize"in profile?profile.pageSize:void 0;return declared&&declared>0?declared:DEFAULT_PAGE_SIZE}function isLastPage(path7,body){if(!path7)return!1;let raw=getByPath(body,path7);if(typeof raw==="boolean")return raw;if(typeof raw==="string")return raw.trim().toLowerCase()==="true";return!1}function parseNextLink(header){if(!header)return null;for(let part of splitLinks(header)){let match=part.match(/<([^>]*)>(.*)/);if(!match)continue;let url=(match[1]??"").trim(),attrs=match[2]??"";if(!url)continue;if(/;\s*rel\s*=\s*["']?\s*next\s*["']?(\s*;|\s*$)/i.test(attrs))return url}return null}function splitLinks(header){let parts=[],depth=0,current="";for(let ch of header){if(ch==="<")depth++;else if(ch===">")depth--;if(ch===","&&depth===0){parts.push(current),current="";continue}current+=ch}if(current.trim()!=="")parts.push(current);return parts}var DEFAULT_MAX_CONCURRENT=4,DEFAULT_MAX_RECORDS=50000;async function collectOne(source,endpoint,params,deps,counters){let profile=endpoint.pagination??{kind:"none"},maxRecords=deps.maxRecords??DEFAULT_MAX_RECORDS,values={...endpoint.queryParams??{},...params.values},resolved=resolvePath2(endpoint.path,values);if(resolved.missing.length>0)return{ok:!1,error:`This endpoint needs path parameter(s) ${resolved.missing.map((m)=>`{${m}}`).join(", ")} and nothing supplies them. Add a parameter binding, or a static value.`};let baseQuery={};for(let[key2,value2]of Object.entries(values)){if(value2==null||resolved.used.has(key2))continue;baseQuery[key2]=value2}let records=[],total=null,state=initialState2(),pageParams=firstPageParams(profile),absoluteUrl=null;for(;;){if(deps.isCancelled?.())return{ok:!1,error:"Cancelled."};let url;if(absoluteUrl)try{url=new URL(absoluteUrl)}catch{return{ok:!1,error:`The source returned an unusable next-page link: ${absoluteUrl}`}}else{let built=buildUrl(source.baseUrl,resolved.path,{...baseQuery,...pageParams});if(!built.ok)return{ok:!1,error:built.error};url=built.url}let headers={accept:"application/json"};if(deps.bearerToken)headers.Authorization=`Bearer ${deps.bearerToken}`;counters.calls++;let outcome=await fetchOnce({...source,token:deps.bearerToken??source.token},{method:endpoint.method,url,headers,body:endpoint.method.toUpperCase()==="GET"?void 0:endpoint.requestBody??{}},{fetchImpl:deps.fetchImpl});if(!outcome.ok)return{ok:!1,error:outcome.error??`HTTP ${outcome.status}`};let raw=extractRecords(outcome.body,endpoint.responseRootPath),childrenPath=treeChildrenPath(endpoint.treeChildrenPath),flattened=childrenPath?flattenTree(raw,childrenPath):null;if(flattened?.truncated)return{ok:!1,error:"This source answered with a structure that nests into itself. Nothing was imported \u2014 a company that contains itself has no order to write it in."};let page=flattened?flattened.records:raw;if(records.push(...page),counters.records+=page.length,total===null)total=reportedTotal(profile,outcome.body);if(counters.records>maxRecords)return{ok:!1,error:`This run would hold more than ${maxRecords} records at once. Narrow the source with a filter, or raise maxRecordsPerRun.`};deps.onProgress?.(counters.records),state={pagesFetched:state.pagesFetched+1,recordsFetched:records.length,cursor:pageParams[cursorParamOf(profile)??""]??state.cursor};let next=nextPage(profile,state,{recordCount:page.length,body:outcome.body,linkHeader:outcome.linkHeader});if(next.kind==="done")break;if(next.kind==="failed")return{ok:!1,error:next.reason};if(next.kind==="url"){absoluteUrl=next.url,pageParams={};continue}absoluteUrl=null,pageParams=next.params}return{ok:!0,records,total}}function cursorParamOf(profile){return profile.kind==="cursor"?profile.cursorParam:null}function describeCallFailure(call,error3){let params=call.values??{},named=Object.entries(params).map(([key2,value2])=>`${key2}=${String(value2)}`).join(", ");return named?`${named}: ${error3}`:error3}async function collectEndpoint(source,endpoint,sources,deps={}){let counters={calls:0,records:0},expansion=expandBindings(endpoint.paramBindings??void 0,sources);if(!expansion.ok)return{ok:!1,callCount:0,error:`This endpoint would be called ${expansion.combinations} times (the ceiling is ${expansion.limit}). Narrow the parent list, or remove one of the parameter bindings.`};if(expansion.calls.length===0)return{ok:!0,records:[],reportedTotal:null,callCount:0};let limit=Math.max(1,deps.maxConcurrent??DEFAULT_MAX_CONCURRENT),collected=[],total=null,failure=null,partial3=[],cursor=0,worker=async()=>{for(;;){if(failure||deps.isCancelled?.())return;let index=cursor++,call=expansion.calls[index];if(!call)return;let result=await collectOne(source,endpoint,call,deps,counters);if(!result.ok){if(expansion.calls.length>1){partial3.push(describeCallFailure(call,result.error));continue}failure??=result.error;return}for(let record3 of result.records)collected.push({record:record3,parent:call.parent});if(expansion.calls.length===1)total=result.total}};if(await Promise.all(Array.from({length:Math.min(limit,expansion.calls.length)},worker)),failure)return{ok:!1,error:failure,callCount:counters.calls};if(deps.isCancelled?.())return{ok:!1,error:"Cancelled.",callCount:counters.calls};if(partial3.length>0&&collected.length===0&&partial3.length===expansion.calls.length)return{ok:!1,error:`Every call to this endpoint failed. First: ${partial3[0]}`,callCount:counters.calls};return{ok:!0,records:collected,reportedTotal:total,callCount:counters.calls,...partial3.length>0?{partialFailures:partial3}:{}}}function isDestructive(writeMode,missingPolicy){return writeMode==="replace"||missingPolicy==="delete"||missingPolicy==="deactivate"}function destructiveGate(input){if(!isDestructive(input.writeMode,input.missingPolicy))return{allowed:!0};if(input.partialFailures>0&&!input.scopedRemoval)return{allowed:!1,kind:"incomplete_source",message:`${input.partialFailures} call(s) in this source never answered, so part of it was not read. This run removes records the source does not send, and those unread records would be removed as if the source had dropped them. Fix the failing call, or switch this mapping to leave missing records alone.`};if(input.fetched===0)return{allowed:!1,kind:"incomplete_source",message:"The source returned no records at all. This run removes what the source does not send, so it would empty the target table. A source that is genuinely empty and one that could "+"not be read look the same from here, so this will not proceed on its own \u2014 preview it, "+"and if the source really is empty, remove the rows deliberately."};if(input.reported!==null&&input.fetched<input.reported)return{allowed:!1,kind:"incomplete_source",message:`The source reports ${input.reported} records but only ${input.fetched} were read. This run deletes what the source does not send, so it will not proceed on a partial read. Check the paging settings for this endpoint.`};return{allowed:!0}}function checkPlanVolume(input){if(input.maxShare<=0)return{allowed:!0};if(input.removing===0||input.existing===0)return{allowed:!0};let share=input.removing/input.existing;if(share<=input.maxShare)return{allowed:!0};let asPercent=(value2)=>`${Math.round(value2*100)}%`;return{allowed:!1,kind:"incomplete_source",message:`This run would remove ${input.removing} of ${input.existing} rows (${asPercent(share)}), which is above the ${asPercent(input.maxShare)} this mapping allows unattended. `+"A source can return perfectly good records and still produce a removal this large \u2014 a "+"changed dedup column, a repointed endpoint, a filter added upstream. Preview the run; if the removal is right, apply it deliberately."}}function orphanLinkNote(input){let{made,unresolved}=input;if(unresolved<=0)return null;let total=made+unresolved,share=total>0?unresolved/total:1,column=input.column?` (${input.column})`:"",consequence=`${unresolved} record${unresolved===1?"":"s"} could not find the parent to attach to${column} \u2014 `+"they are drawn at the TOP of the tree, at the same level as the chief executive.";if(share>=0.2)return`${consequence} Next to the ${made} that did attach, that is a large share: either the source is not sending a parent for these people, or one of the matching fields is written differently in the two places. Look at the source before trusting the chart.`;return`${consequence} ${made} link${made===1?" was":"s were"} made.`}function looksLikeKeyDrift(input){if(input.existing<=0)return!1;if(input.creating<=0||input.unmatched<=0)return!1;return input.creating/input.existing>=0.1&&input.unmatched/input.existing>=0.1}function keyDriftWarning(input){if(!looksLikeKeyDrift(input))return null;return`This run will add ${input.creating} rows, and the source no longer sends ${input.unmatched} of the rows already here. Both being this large points at the matching values in the `+"source having changed \u2014 so these are not new records, they are the same records under "+"new names. Run it with \u201Cmissing rows: leave alone\u201D and both generations survive: the "+`table grows to ${input.existing+input.creating} rows, and where records point at each other (an organisation tree, for instance) the new rows cannot find their old parents and the structure breaks. Either switch the policy to delete/deactivate and clean up `+"once, or correct the matching column to the source\u2019s new value."}var referenceKey=(check)=>`${check.entity}.${check.entityColumn}`,pointsAtOwnTable=(check,targetEntity)=>check.entity===targetEntity;function referenceValuesOf(raw){if(Array.isArray(raw))return{values:raw.map((item)=>String(item??"").trim()).filter((item)=>item!==""),wasList:!0};if(raw===null||raw===void 0)return{values:[],wasList:!1};let single=String(raw).trim();return{values:single===""?[]:[single],wasList:!1}}var isEmptyReference=(raw)=>referenceValuesOf(raw).values.length===0;function resolveReference(raw,ids){let{values,wasList}=referenceValuesOf(raw),found=[],unmatched=[];for(let value2 of values){let id=ids?.[value2];if(id===void 0)unmatched.push(value2);else if(!found.includes(id))found.push(id)}return{ids:found,unmatched,wasList}}function resolvedColumnValue(resolution){return resolution.wasList?resolution.ids:resolution.ids[0]??null}function applyReferenceIds(row,references,ids,targetEntity){let next={...row},deferred=[];for(let check of references){if(!check.writeIdTo)continue;let raw=row[check.column];if(pointsAtOwnTable(check,targetEntity)){if(next[check.writeIdTo]=Array.isArray(raw)?[]:null,!isEmptyReference(raw))deferred.push({check,raw});continue}next[check.writeIdTo]=resolvedColumnValue(resolveReference(raw,ids[referenceKey(check)]))}return{row:next,deferred}}function unambiguousIds(rows){let seen=new Map;for(let row of rows){if(row.value==null||row.id==null)continue;let key2=String(row.value).trim();if(key2==="")continue;seen.set(key2,seen.has(key2)?null:String(row.id))}let ids={};for(let[key2,id]of seen)if(id!==null)ids[key2]=id;return ids}function ambiguousValues(rows){let counts=new Map;for(let row of rows){if(row.value==null||row.id==null)continue;let key2=String(row.value).trim();if(key2==="")continue;counts.set(key2,(counts.get(key2)??0)+1)}return[...counts.entries()].filter(([,n])=>n>1).map(([key2])=>key2)}var UNRESOLVED_TEMPLATE=/^\s*(\$\{[^}]*\}|\{\{[^}]*\}\})\s*$/,isEmpty2=(v)=>v==null||typeof v==="string"&&(v.trim()===""||UNRESOLVED_TEMPLATE.test(v));function displayValue(value2){if(value2!==null&&typeof value2==="object")try{return JSON.stringify(value2)}catch{return"(a value that cannot be shown)"}return String(value2)}function buildFallbackIdentifier(format2,data){let complete=!0,rendered=format2.replace(/\{([^{}]+)\}/g,(_match,path7)=>{let value2=data[path7.trim()];if(value2==null||String(value2).trim()==="")complete=!1;return value2==null?"":String(value2)});if(!complete)return null;let cleaned=foldToAscii(rendered).toLowerCase().trim().replace(/[^a-z0-9._@+-]+/g,".").replace(/\.{2,}/g,".").replace(/\.*@\.*/g,"@").replace(/^[.\-_]+|[.\-_]+$/g,"");return cleaned===""?null:cleaned}function missingReferences(row,references,known){let broken=[];for(let check of references){let raw=row[check.column];if(isEmpty2(raw))continue;let value2=displayValue(raw).trim(),present=known[`${check.entity}.${check.entityColumn}`];if(!present)continue;if(!present.has(value2))broken.push({check,value:value2,raw})}return broken}async function loadReferenceValues(references,read2){let values={};for(let check of references){let key2=`${check.entity}.${check.entityColumn}`;if(values[key2])continue;let rows=await read2(check.entity,check.entityColumn);values[key2]=new Set(rows.filter((v)=>!isEmpty2(v)).map((v)=>String(v).trim()))}return values}async function loadReferenceIds(references,targetEntity,read2){let ids={},ambiguous={};for(let check of references){if(!check.writeIdTo)continue;if(pointsAtOwnTable(check,targetEntity))continue;let key2=referenceKey(check);if(ids[key2])continue;let pairs=await read2(check.entity,check.entityColumn);ids[key2]=unambiguousIds(pairs);let shared=ambiguousValues(pairs);if(shared.length>0)ambiguous[key2]=shared}return{ids,ambiguous}}function ambiguityWarnings(ambiguous){return Object.entries(ambiguous).map(([key2,values])=>{let shown=values.slice(0,5).join(", "),rest3=values.length>5?` and ${values.length-5} more`:"";return`${values.length} value(s) in ${key2} belong to more than one row \u2014 ${shown}${rest3}. Records pointing at them are left unlinked rather than linked to whichever row came first.`})}function planWrites(input){let{records,existing,dedupTargetField,writeMode,missingPolicy,missingColumn,missingValue,removalScope=null,originColumn,originValue,requiredFields=[],uniqueFields=[],createDefaults={},accountRule=null,existingAccounts={},references=[],referenceValues={},referenceIds={},targetEntity=""}=input,issues=[],warnings=[],creates=[],referenceInserts=[],accountInserts=[],accountJoinInserts=[],plannedAccounts=new Set,plannedRefs=new Set,updates=[],deletes=[],deactivates=[],departures=[],deferredLinks=[],dedupConfigured=Boolean(dedupTargetField),accountClaims=new Map,accountIdIsUnique=accountRule?uniqueFields.includes(accountRule.idColumn):!1,fillIdentifier=(data)=>{let format2=accountRule?.emailFormat?.trim();if(!accountRule||!format2)return data;if(!isEmpty2(data[accountRule.matchFrom]))return data;let built=buildFallbackIdentifier(format2,data);return built===null?data:{...data,[accountRule.matchFrom]:built}},withAccount=(record3,input2)=>{if(!accountRule)return input2;let data=fillIdentifier(input2),raw=data[accountRule.matchFrom];if(isEmpty2(raw))return issues.push({sourceIndex:record3.sourceIndex,kind:"account_unmatched",field:accountRule.matchFrom,message:`${accountRule.matchFrom} is empty, so this record gets no account \u2014 it is imported without one.`,dedupKey:record3.dedupKey,row:data}),data;let value2=String(raw).trim(),claimedBy=accountClaims.get(value2);if(claimedBy&&accountIdIsUnique)return issues.push({sourceIndex:record3.sourceIndex,kind:"duplicate_value",field:accountRule.matchFrom,value:String(value2),message:`${accountRule.matchFrom} is \u201C${value2}\u201D, which another record in this import already uses \u2014 and ${accountRule.idColumn} allows one record per account. This one is left out.`,dedupKey:record3.dedupKey,row:data,keptRow:claimedBy.data}),null;accountClaims.set(value2,record3);let existingId=existingAccounts[value2];if(existingId)return{...data,[accountRule.idColumn]:existingId};if(!plannedAccounts.has(value2)){plannedAccounts.add(value2);let accountData={[accountRule.matchColumn]:value2};for(let pair of accountRule.values??[]){let from=data[pair.from];if(!isEmpty2(from))accountData[pair.column]=from}accountInserts.push({entity:accountRule.entity,matchColumn:accountRule.matchColumn,matchValue:value2,data:accountData});for(let join7 of accountRule.joins??[])accountJoinInserts.push({entity:join7.entity,accountColumn:join7.accountColumn,values:join7.values,matchValue:value2})}return data},hasDefaults=Object.keys(createDefaults).length>0,stamp2=originColumn?{[originColumn]:originValue}:{},newRecord=(data)=>hasDefaults?{...createDefaults,...data,...stamp2}:{...data,...stamp2},existingByKey=new Map;if(dedupTargetField)for(let row of existing){let value2=row.row[dedupTargetField];if(!isEmpty2(value2))existingByKey.set(String(value2).trim(),row)}let claimedUnique=new Map;for(let field of uniqueFields){let taken=new Map;for(let existingRow of existing){let value2=existingRow.row[field];if(!isEmpty2(value2))taken.set(String(value2).trim(),existingRow.id)}claimedUnique.set(field,taken)}let ownsRow=(row)=>{if(!originColumn)return!0;let value2=row.row[originColumn];return String(value2??"")===String(originValue??"")},matchedIds=new Set,seenIds=new Set,seenInBatch=new Map;for(let sourceRecord of records){let broken=missingReferences(sourceRecord.data,references,referenceValues),resolvedRow=applyReferenceIds(sourceRecord.data,references,referenceIds,targetEntity),record3={...sourceRecord,data:fillIdentifier(resolvedRow.row)};if(record3.dedupKey!=null){let carried=existingByKey.get(record3.dedupKey);if(carried)seenIds.add(carried.id)}for(let link of resolvedRow.deferred){if(!dedupTargetField)continue;let matchValue=record3.data[dedupTargetField];if(isEmpty2(matchValue))continue;deferredLinks.push({matchColumn:dedupTargetField,matchValue:String(matchValue).trim(),writeIdTo:link.check.writeIdTo,lookupColumn:link.check.entityColumn,raw:link.raw,isList:Array.isArray(link.raw)})}let blocking=broken.find((b)=>b.check.onMissing==="skipRecord");if(blocking){let{check,value:value2}=blocking;issues.push({sourceIndex:record3.sourceIndex,kind:"missing_reference",message:typeof blocking.raw==="object"&&blocking.raw!==null?`${check.column} is not a single value but a whole object \u2014 ${value2}. Point the mapping at one field of it, e.g. \`${check.column}.code\`, and it will have something to match against ${check.entity}.${check.entityColumn}.`:`${check.column} is \u201C${value2}\u201D, which does not exist in ${check.entity}.${check.entityColumn} \u2014 this record is left out.`,dedupKey:record3.dedupKey,row:record3.data,reference:{column:check.column,value:value2,entity:check.entity,entityColumn:check.entityColumn}});continue}let keepReferences=()=>{for(let{check,value:value2}of broken){if(check.onMissing!=="create")continue;let token=`${check.entity}.${check.entityColumn}::${value2}`;if(plannedRefs.has(token))continue;plannedRefs.add(token);let data={};for(let pair of check.createValues??[]){let fromSource=isTemplateSource(pair.from)?renderTemplate(pair.from,record3.external,record3.parent):readSourceValue(record3.external,record3.parent,pair.from),value3=isEmpty2(fromSource)?record3.data[pair.from]:fromSource;if(!isEmpty2(value3))data[pair.column]=value3}referenceInserts.push({entity:check.entity,column:check.entityColumn,value:value2,...Object.keys(data).length>0?{data}:{}})}},uniqueCollision=(data,ownRowId)=>{for(let field of uniqueFields){let raw=data[field];if(isEmpty2(raw))continue;let value2=String(raw).trim(),holder2=claimedUnique.get(field)?.get(value2);if(holder2===void 0)continue;if(holder2!==null&&holder2===ownRowId)continue;return{field,value:value2}}return null},claimUnique=(data,rowId)=>{for(let field of uniqueFields){let raw=data[field];if(isEmpty2(raw))continue;claimedUnique.get(field)?.set(String(raw).trim(),rowId)}},collide=(field,value2)=>{issues.push({sourceIndex:record3.sourceIndex,kind:"duplicate_value",field,value:String(value2),message:`${field} is \u201C${value2}\u201D, which something else already has \u2014 ${field} has to be unique, so this record is left out.`,dedupKey:record3.dedupKey,row:record3.data})},missingRequired=requiredFields.filter((f)=>isEmpty2(record3.data[f])&&isEmpty2(createDefaults[f]));if(missingRequired.length>0){issues.push({sourceIndex:record3.sourceIndex,kind:"missing_required",field:missingRequired.join(", "),message:`Required field(s) empty: ${missingRequired.join(", ")}`,dedupKey:record3.dedupKey,row:record3.data});continue}if(!dedupConfigured){let clash2=uniqueCollision(record3.data,null);if(clash2){collide(clash2.field,clash2.value);continue}keepReferences(),claimUnique(record3.data,null);let withLogin=withAccount(record3,newRecord(record3.data));if(!withLogin)continue;creates.push({data:withLogin,sourceIndex:record3.sourceIndex});continue}if(record3.dedupKey==null){let clash2=uniqueCollision(record3.data,null);if(clash2){collide(clash2.field,clash2.value);continue}issues.push({sourceIndex:record3.sourceIndex,kind:"missing_dedup_key",message:"Deduplication field is empty on this record \u2014 it will be added as new."}),keepReferences(),claimUnique(record3.data,null);let withLogin=withAccount(record3,newRecord(record3.data));if(!withLogin)continue;creates.push({data:withLogin,sourceIndex:record3.sourceIndex});continue}let claimedBy=seenInBatch.get(record3.dedupKey);if(claimedBy){issues.push({sourceIndex:record3.sourceIndex,kind:"duplicate_in_batch",value:String(record3.dedupKey??""),message:`The source carries this key more than once: ${record3.dedupKey} \u2014 this occurrence is skipped.`,dedupKey:record3.dedupKey,row:record3.data,keptRow:claimedBy});continue}if(seenInBatch.set(record3.dedupKey,record3.data),writeMode==="replace"||writeMode==="append"){let clash2=uniqueCollision(record3.data,null);if(clash2&&!(writeMode==="replace"&&claimedUnique.get(clash2.field)?.get(clash2.value))){collide(clash2.field,clash2.value);continue}keepReferences(),claimUnique(record3.data,null);let withLogin=withAccount(record3,newRecord(record3.data));if(!withLogin)continue;creates.push({data:withLogin,sourceIndex:record3.sourceIndex});continue}let match=existingByKey.get(record3.dedupKey),clash=uniqueCollision(record3.data,match?.id??null);if(clash){collide(clash.field,clash.value);continue}if(keepReferences(),claimUnique(record3.data,match?.id??null),match)matchedIds.add(match.id),updates.push({id:match.id,data:{...record3.data,...stamp2},before:match.row,sourceIndex:record3.sourceIndex});else{let withLogin=withAccount(record3,newRecord(record3.data));if(!withLogin)continue;creates.push({data:withLogin,sourceIndex:record3.sourceIndex})}}if(writeMode==="replace")deletes.push(...existing);else if(writeMode==="upsert"&&missingPolicy!=="ignore"){let wasRead=(row)=>{if(!removalScope)return!0;let value2=row.row[removalScope.column];if(isEmpty2(value2))return!1;return removalScope.readValues.includes(String(value2).trim())},gone=existing.filter((row)=>!seenIds.has(row.id)).filter((row)=>ownsRow(row)).filter(wasRead);if(missingPolicy==="delete")deletes.push(...gone);else deactivates.push(...gone);departures.push(...gone)}if(writeMode==="upsert"&&missingPolicy==="ignore"){let owned=existing.filter((row)=>ownsRow(row)),unmatched=owned.filter((row)=>!seenIds.has(row.id)).length,drift=keyDriftWarning({creating:creates.length,unmatched,existing:owned.length});if(drift)warnings.push(drift)}if(!dedupConfigured&&writeMode==="upsert")warnings.push("No deduplication field is set. Nothing can be matched against what is already here, so every record is added as new and each run will duplicate the last.");if(dedupConfigured&&records.length>0&&records.every((r)=>r.dedupKey==null))warnings.push(`The deduplication field is empty on every record \u2014 it is probably not among the mapped fields. As it stands nothing can ever match: all ${records.length} records will be re-added on every run.`);if(writeMode==="append"&&dedupConfigured)warnings.push("Append mode ignores the deduplication field: records already here will be added again.");if(writeMode==="replace"){if(warnings.push(`Replace mode: all ${existing.length} rows currently in the table are deleted and rebuilt from the source. Row identifiers change, so anything referring to them by id breaks.`),dedupConfigured)warnings.push('A deduplication field is set, so "update existing" would keep the current rows and their identifiers. Replace mode is only necessary when records cannot be matched at all.')}if(deactivates.length>0&&(!missingColumn||isEmpty2(missingValue)))warnings.push('The policy for records missing from the source is "deactivate", but no column and value are configured \u2014 those rows will be left untouched.'),deactivates.length=0;return{creates,updates,deletes,deactivates,departures,deferredLinks,issues,warnings,referenceInserts,accountInserts,accountJoinInserts}}var SKIPPING_ISSUE_KINDS=new Set(["missing_required","duplicate_in_batch","duplicate_value","invalid_value","missing_reference"]);function skippedRecordCount(issues){let records=new Set;for(let issue of issues){if(!SKIPPING_ISSUE_KINDS.has(issue.kind))continue;if(issue.sourceIndex<0)continue;records.add(issue.sourceIndex)}return records.size}function summarize(ops,meta){let sampleSize=meta.sampleSize??8,sample=[...ops.creates.map((c)=>c.data),...ops.updates.map((u)=>u.data)].slice(0,sampleSize);return{targetEntity:meta.targetEntity,writeMode:meta.writeMode,totalFetched:meta.totalFetched,reportedTotal:meta.reportedTotal,toCreate:ops.creates.length,toUpdate:ops.updates.length,toSkip:skippedRecordCount(ops.issues),missing:ops.deletes.length+ops.deactivates.length,missingPolicy:meta.missingPolicy,existingCount:meta.existingCount,dedupConfigured:meta.dedupConfigured,issues:ops.issues,warnings:ops.warnings,referenceInserts:ops.referenceInserts,accountsToCreate:ops.accountInserts.length,sample}}function removalScopeFor(column,records){if(!column)return null;let readValues=new Set;for(let record3 of records){let value2=record3.data[column];if(!isEmpty2(value2))readValues.add(String(value2).trim())}return{column,readValues:[...readValues]}}init_AccountLink();import{and as and20,eq as eq34,inArray as inArray13}from"drizzle-orm";var INSERT_CHUNK=500,DELETE_CHUNK=1000;function validateAll(ops,columns,validatePayload2){let issues=[];for(let op of ops.creates){let result=validatePayload2(op.data,columns,!1);if(!result.valid)issues.push({sourceIndex:op.sourceIndex,kind:"invalid_value",message:result.errors.map((e)=>`${e.field}: ${e.message}`).join("; "),row:op.data})}for(let op of ops.updates){let result=validatePayload2(op.data,columns,!0);if(!result.valid)issues.push({sourceIndex:op.sourceIndex,kind:"invalid_value",message:result.errors.map((e)=>`${e.field}: ${e.message}`).join("; "),row:op.data})}return issues}function referenceRow(columns,column,value2,extra){let row={...extra,[column]:value2},unfillable=[];for(let col8 of columns){let name2=camel(col8.name);if(name2===column)continue;if(SYSTEM_COLUMNS.has(name2))continue;if(row[name2]!==void 0)continue;if(!(col8.notNull===!0&&col8.default===void 0&&!col8.defaultRaw&&!col8.nullable))continue;if(TEXTUAL_TYPES.has(String(col8.type)))row[name2]=value2;else unfillable.push(name2)}return unfillable.length>0?{ok:!1,unfillable}:{ok:!0,row}}var SYSTEM_COLUMNS=new Set(["id","createdAt","updatedAt","createdBy","updatedBy"]),TEXTUAL_TYPES=new Set(["text","varchar","char"]),camel=(s)=>s.replace(/_([a-z])/g,(_m,l)=>l.toUpperCase());function linkAccount(data,rule,idByValue){let value2=data[rule.matchFrom];if(value2==null)return{};let id=idByValue.get(String(value2).trim());return id?{[rule.idColumn]:id}:{}}function groupDeferred(links){let groups=new Map;for(let link of links){let key2=`${link.writeIdTo}|${link.lookupColumn}|${link.matchColumn}`,group=groups.get(key2)??{writeIdTo:link.writeIdTo,lookupColumn:link.lookupColumn,matchColumn:link.matchColumn,links:[]};group.links.push(link),groups.set(key2,group)}return[...groups.values()]}async function applyWrites(deps,input){let{ops,targetEntity,userId}=input,scope=input.scopeConditions??[],writeIssues=[],cancelled=()=>input.isCancelled?.()===!0,invalid=validateAll(ops,deps.columns,deps.validatePayload);if(invalid.length>0)return{ok:!1,failure:{kind:"validation_failed",message:`${invalid.length} record(s) did not pass validation; nothing was written.`,issues:invalid}};let refGroups=new Map;for(let insert of ops.referenceInserts){let resolved=deps.resolveEntity?.(insert.entity);if(!resolved)return{ok:!1,failure:{kind:"write_failed",message:`This mapping would add \u201C${insert.value}\u201D to "${insert.entity}", which does not exist.`}};let built=referenceRow(resolved.columns,insert.column,insert.value,insert.data);if(!built.ok)return{ok:!1,failure:{kind:"write_failed",message:`\u201C${insert.value}\u201D cannot be added to "${insert.entity}" automatically: ${built.unfillable.join(", ")} is required and cannot be filled from a ${insert.column}. Add the missing ${insert.entity} by hand, or set this reference to leave those records out.`}};let group=refGroups.get(insert.entity)??{table:resolved.table,rows:[]};group.rows.push(built.row),refGroups.set(insert.entity,group)}let accountRule=input.account??null,accountEntity=ops.accountInserts[0]?.entity,accountTable=accountEntity?deps.resolveEntity?.(accountEntity):null;if(ops.accountInserts.length>0&&(!accountRule||!accountTable))return{ok:!1,failure:{kind:"write_failed",message:accountRule?`This import creates accounts in "${accountEntity}", which does not exist.`:"This import creates accounts but no password was supplied, so nothing was written."}};if(cancelled())return{ok:!1,failure:{kind:"cancelled",message:"Cancelled before writing."}};let idCol=deps.resolveCol("id");if(!idCol)return{ok:!1,failure:{kind:"write_failed",message:`Target "${targetEntity}" has no id column; the engine cannot address its rows.`}};let preImages=[],now=new Date,deactivating=ops.deactivates.length>0&&input.missingColumn&&input.missingValue!=null?{column:input.missingColumn,value:input.missingValue}:null;try{let linksMade=0,linksUnresolved=0;return{ok:!0,...await deps.db.transaction(async(tx)=>{let created=0,accountsCreated=0,referencesCreated=0,updated=0,deleted=0,deactivated=0,accountsClosed=0;if(ops.deletes.length>0){let ids=ops.deletes.map((r)=>r.id);for(let i=0;i<ids.length;i+=DELETE_CHUNK){if(cancelled())throw new CancelledError;let chunk=ids.slice(i,i+DELETE_CHUNK),where=scope.length?and20(inArray13(idCol,chunk),...scope):inArray13(idCol,chunk),removed=await tx.delete(deps.drizzleTable).where(where).returning();deleted+=removed.length;for(let row of removed)preImages.push({targetEntity,rowId:String(row.id),action:"delete",before:row,after:null})}}for(let op of ops.updates){if(cancelled())throw new CancelledError;let where=scope.length?and20(eq34(idCol,op.id),...scope):eq34(idCol,op.id),written=await tx.update(deps.drizzleTable).set({...op.data,updatedAt:now,updatedBy:userId}).where(where).returning();if(written.length===0)throw new ScopeError(op.id);updated+=written.length,preImages.push({targetEntity,rowId:op.id,action:"update",before:op.before,after:written[0]??null})}if(deactivating){let ids=ops.deactivates.map((r)=>r.id);for(let i=0;i<ids.length;i+=DELETE_CHUNK){if(cancelled())throw new CancelledError;let chunk=ids.slice(i,i+DELETE_CHUNK),where=scope.length?and20(inArray13(idCol,chunk),...scope):inArray13(idCol,chunk),written=await tx.update(deps.drizzleTable).set({[deactivating.column]:deactivating.value,updatedAt:now,updatedBy:userId}).where(where).returning();deactivated+=written.length;let beforeById=new Map(ops.deactivates.map((r)=>[r.id,r.row]));for(let row of written){let id=String(row.id);preImages.push({targetEntity,rowId:id,action:"deactivate",before:beforeById.get(id)??null,after:row})}}}if(deps.accountClosure){let written=new Set(preImages.filter((image)=>image.action==="delete"||image.action==="deactivate").map((image)=>image.rowId)),removedRows=ops.departures.filter((row)=>written.has(row.id)).map((row)=>row.row),leaverIds=accountIdsIn(removedRows,deps.accountClosure.userColumn);if(leaverIds.length>0){let closed=await closeAccounts({db:tx,usersTable:deps.accountClosure.usersTable,sessionsTable:deps.accountClosure.sessionsTable,atomic:!0,onError:(error3)=>{deps.logger.error("[Integrations] A departure\u2019s account stayed open",{error:error3})}},leaverIds);accountsClosed=closed.closed;for(let userId2 of closed.closedIds)preImages.push({targetEntity:deps.accountClosure.accountEntity,rowId:userId2,action:"update",before:{isActive:!0},after:{isActive:!1}});if(closed.closed>0&&!closed.signedOut)writeIssues.push({sourceIndex:-1,kind:"account_sessions_not_cleared",value:String(closed.closed),message:`${closed.closed} account(s) behind removed records were deactivated, but their open sessions could not be cleared \u2014 those people stay signed in until the session expires.`})}}for(let[entity,group]of refGroups){let rows=group.rows.map((r)=>({...r,createdAt:now,updatedAt:now,createdBy:userId}));for(let i=0;i<rows.length;i+=INSERT_CHUNK){if(cancelled())throw new CancelledError;let written=await tx.insert(group.table).values(rows.slice(i,i+INSERT_CHUNK)).returning();referencesCreated+=written.length;for(let row of written)preImages.push({targetEntity:entity,rowId:row.id?String(row.id):null,action:"create",before:null,after:row})}}let accountIdByValue=new Map;if(accountRule&&accountTable&&ops.accountInserts.length>0){let rows=ops.accountInserts.map((account)=>({...account.data,[accountRule.passwordColumn]:accountRule.passwordHash,createdAt:now,updatedAt:now,createdBy:userId}));for(let i=0;i<rows.length;i+=INSERT_CHUNK){if(cancelled())throw new CancelledError;let written=await tx.insert(accountTable.table).values(rows.slice(i,i+INSERT_CHUNK)).returning();accountsCreated+=written.length;for(let row of written){let value2=row[accountRule.matchColumn];if(row.id!=null&&value2!=null)accountIdByValue.set(String(value2).trim(),String(row.id));preImages.push({targetEntity:ops.accountInserts[0]?.entity??"",rowId:row.id?String(row.id):null,action:"create",before:null,after:row})}}}if(accountRule&&ops.accountJoinInserts.length>0){let byEntity=new Map;for(let join7 of ops.accountJoinInserts){let accountId=accountIdByValue.get(join7.matchValue);if(!accountId){writeIssues.push({sourceIndex:-1,kind:"account_role_missing",message:`No account was found for \u201C${join7.matchValue}\u201D, so its ${join7.entity} row was not written \u2014 that person can sign in and do nothing.`});continue}let rows=byEntity.get(join7.entity)??[];rows.push({...join7.values,[join7.accountColumn]:accountId,createdAt:now,updatedAt:now,createdBy:userId}),byEntity.set(join7.entity,rows)}for(let[entity,rows]of byEntity){let resolved=deps.resolveEntity?.(entity);if(!resolved){writeIssues.push({sourceIndex:-1,kind:"account_role_missing",message:`The \u201C${entity}\u201D table is not available here, so ${rows.length} role membership(s) were not written. The accounts exist but hold no role.`});continue}for(let i=0;i<rows.length;i+=INSERT_CHUNK){if(cancelled())throw new CancelledError;let written=await tx.insert(resolved.table).values(rows.slice(i,i+INSERT_CHUNK)).returning();for(let row of written)preImages.push({targetEntity:entity,rowId:row.id?String(row.id):null,action:"create",before:null,after:row})}}}if(ops.creates.length>0){let rows=ops.creates.map((c)=>({...c.data,...accountRule&&c.data[accountRule.idColumn]==null?linkAccount(c.data,accountRule,accountIdByValue):{},createdAt:now,updatedAt:now,createdBy:userId}));for(let i=0;i<rows.length;i+=INSERT_CHUNK){if(cancelled())throw new CancelledError;let chunk=rows.slice(i,i+INSERT_CHUNK),written=await tx.insert(deps.drizzleTable).values(chunk).returning();created+=written.length;for(let row of written)preImages.push({targetEntity,rowId:row.id?String(row.id):null,action:"create",before:null,after:row})}}if(ops.deferredLinks.length>0)for(let group of groupDeferred(ops.deferredLinks)){if(cancelled())throw new CancelledError;let lookupCol=deps.resolveCol(group.lookupColumn),matchCol=deps.resolveCol(group.matchColumn),targetCol=deps.resolveCol(group.writeIdTo);if(!lookupCol||!matchCol||!targetCol){deps.logger.error(`[Integrations] Cannot fill "${group.writeIdTo}": ${[!lookupCol?group.lookupColumn:null,!matchCol?group.matchColumn:null,!targetCol?group.writeIdTo:null].filter(Boolean).join(", ")} is not a column of "${targetEntity}".`);continue}let rows=await tx.select({v:lookupCol,id:idCol}).from(deps.drizzleTable),idByValue={};for(let row of rows){if(row.v==null||row.v===""||row.id==null)continue;let key2=String(row.v).trim();if(!idByValue[key2])idByValue[key2]=String(row.id)}let imageByMatch=new Map;for(let image of preImages){let value2=image.after?.[group.matchColumn];if(value2!=null)imageByMatch.set(String(value2).trim(),image)}for(let link of group.links){let resolution=resolveReference(link.raw,idByValue);if(resolution.ids.length===0){linksUnresolved+=1;continue}let value2=link.isList?resolution.ids:resolution.ids[0],where=scope.length?and20(eq34(matchCol,link.matchValue),...scope):eq34(matchCol,link.matchValue);if((await tx.update(deps.drizzleTable).set({[group.writeIdTo]:value2}).where(where).returning()).length===0){linksUnresolved+=1;continue}linksMade+=1;let image=imageByMatch.get(String(link.matchValue).trim());if(image?.after)image.after={...image.after,[group.writeIdTo]:value2}}}return{created,accountsCreated,referencesCreated,updated,deleted,deactivated,accountsClosed,linksMade,linksUnresolved}}),preImages,issues:writeIssues}}catch(error3){if(error3 instanceof CancelledError)return{ok:!1,failure:{kind:"cancelled",message:"Cancelled while writing; the transaction was rolled back and nothing changed."}};if(error3 instanceof ScopeError)return{ok:!1,failure:{kind:"write_failed",message:`Row ${error3.rowId} is outside what this account may write. Nothing was written.`}};return deps.logger.error(`[Integrations] Write transaction failed for "${targetEntity}": ${reasonOf2(error3)}`),{ok:!1,failure:{kind:"write_failed",message:"The database rejected this import; it was rolled back and nothing changed."}}}}function reasonOf2(error3){if(!(error3 instanceof Error))return String(error3);let extra=error3;return[error3.message,extra.detail?`detail: ${String(extra.detail)}`:"",extra.constraint?`constraint: ${String(extra.constraint)}`:"",extra.hint?`hint: ${String(extra.hint)}`:"",extra.code?`code: ${String(extra.code)}`:""].filter(Boolean).join(" \u2014 ")}class CancelledError extends Error{constructor(){super("cancelled");this.name="CancelledError"}}class ScopeError extends Error{rowId;constructor(rowId){super(`row ${rowId} outside scope`);this.rowId=rowId;this.name="ScopeError"}}var DAY_MS=86400000;async function executeRun(context,deps,lock){let{mapping,source}=context,empty={runId:null,status:"failed",failure:null,plan:null,created:0,accountsCreated:0,referencesCreated:0,updated:0,deleted:0,deactivated:0,externalCalls:0,issues:[]},opened=await deps.repo.openRun({mappingId:mapping.id,targetEntity:mapping.targetEntity,writeMode:mapping.writeMode,trigger:context.trigger,userId:context.userId});if(!opened.ok)return{...empty,failure:{kind:"write_failed",message:`Could not open a run record: ${opened.error}`}};let runId=opened.runId;deps.onRunOpened?.(runId);let cancelChecked=0,cancelSeen=!1,isCancelled=()=>{if(cancelSeen)return!0;if(deps.abortSignal?.aborted)return cancelSeen=!0,!0;if(!lock.isHeld())return cancelSeen=!0,!0;return!1},refreshCancelFlag=async()=>{if(cancelChecked++,await deps.repo.isCancelRequested(runId))cancelSeen=!0},progress=(p)=>deps.publishProgress?.({runId,...p}),preImagesStored=!0,finish=async(outcome)=>{let revertible=outcome.status==="succeeded"&&preImagesStored;return await deps.repo.closeRun(runId,{status:outcome.status,totalFetched:outcome.plan?.totalFetched??0,sourceReportedTotal:outcome.plan?.reportedTotal??null,createdCount:outcome.created,accountsCreatedCount:outcome.accountsCreated,referencesCreatedCount:outcome.referencesCreated,updatedCount:outcome.updated,skippedCount:outcome.plan?.toSkip??0,deletedCount:outcome.deleted,deactivatedCount:outcome.deactivated,errorCount:outcome.failure?1:0,externalCallCount:outcome.externalCalls,issues:outcome.issues,errors:outcome.failure?[outcome.failure.message]:[],finishedAt:new Date,preImagesExpireAt:revertible?new Date(Date.now()+(deps.retainPreImagesMs??30*DAY_MS)):null}),progress({phase:"done",message:outcome.failure?.message}),{runId,...outcome}};progress({phase:"fetching"});let nodes=context.endpoints.map((e)=>({id:e.id,paramBindings:e.paramBindings??void 0})),order=planFetchOrder(nodes,[context.rootEndpointId]);if(!order.ok){let failure=order.reason==="cycle"?{kind:"cycle_detected",message:`These endpoints depend on each other in a loop (${order.chain.join(" \u2192 ")}), so there is no correct place to start.`,chain:order.chain}:{kind:"fetch_failed",message:`"${order.requiredBy}" takes a parameter from an endpoint that no longer exists (${order.missing}).`};return finish({...empty,failure,status:"failed"})}let bearerToken=null;if(context.oauth){let token=await getAccessToken(context.oauth,deps.tokenCache,{fetchImpl:deps.fetchImpl});if(!token.ok)return finish({...empty,failure:{kind:"fetch_failed",message:token.error}});bearerToken=token.accessToken}let byId=new Map(context.endpoints.map((e)=>[e.id,e])),parents={},entityValues={},externalCalls=0,rootRecords=[],fetchWarnings=[],reportedTotal2=null;for(let endpointId of order.order){let endpoint=byId.get(endpointId);if(!endpoint)continue;if(await refreshCancelFlag(),isCancelled())return finish({...empty,status:"cancelled",externalCalls,failure:{kind:"cancelled",message:"Cancelled before anything was written."}});for(let key2 of entityValueKeys(endpoint.paramBindings??void 0)){if(entityValues[key2])continue;let[entity,field]=key2.split(".");entityValues[key2]=await deps.repo.readEntityColumn(entity,field)}let collected=await collectEndpoint(source,endpoint,{parents,entityValues},{fetchImpl:deps.fetchImpl,bearerToken,maxConcurrent:deps.maxConcurrentFanout,maxRecords:deps.maxRecordsPerRun,isCancelled,onProgress:(n)=>progress({phase:"fetching",processed:n})});if(externalCalls+=collected.callCount,!collected.ok){let cancelled=isCancelled();return finish({...empty,externalCalls,status:cancelled?"cancelled":"failed",failure:cancelled?{kind:"cancelled",message:"Cancelled before anything was written."}:{kind:"fetch_failed",message:collected.error}})}if(collected.partialFailures?.length)fetchWarnings.push(...collected.partialFailures.map((failure)=>`Could not read part of this source \u2014 ${failure}`));if(parents[endpointId]=collected.records.map((r)=>r.record),endpointId===context.rootEndpointId)rootRecords=collected.records,reportedTotal2=collected.reportedTotal}for(let id of Object.keys(parents))if(!(id===context.rootEndpointId||context.endpoints.some((e)=>parentEndpointIds(e.paramBindings??void 0).includes(id))))delete parents[id];let verdict=destructiveGate({writeMode:mapping.writeMode,missingPolicy:mapping.missingPolicy,fetched:rootRecords.length,reported:reportedTotal2,partialFailures:fetchWarnings.length,scopedRemoval:Boolean(mapping.removalScopeColumn)});if(!verdict.allowed)return finish({...empty,externalCalls,failure:{kind:verdict.kind,message:fetchWarnings.length?`${verdict.message}
|
|
544
|
+
`)};var handleListCohorts=(db,usersTable,cohortsTable)=>async(ctx)=>{let userId=ctx.request.headers.get("x-user-id");if(!userId||!await requireGodmin(db,usersTable,userId))return ctx.set.status=403,{success:!1,message:"Forbidden"};let rows=await db.select().from(cohortsTable);return{success:!0,data:{items:rows,meta:{totalItems:rows.length}}}};import{eq as eq29}from"drizzle-orm";var handleDeleteCohort=(db,usersTable,cohortsTable,logger2)=>async(ctx)=>{let userId=ctx.request.headers.get("x-user-id");if(!userId||!await requireGodmin(db,usersTable,userId))return ctx.set.status=403,{success:!1,message:"Forbidden"};return await db.update(usersTable).set({cohortId:null,updatedAt:new Date}).where(eq29(col7(usersTable,"cohortId"),ctx.params.id)),await db.delete(cohortsTable).where(eq29(col7(cohortsTable,"id"),ctx.params.id)),logger2.audit({entityName:"user_cohorts",entityId:ctx.params.id,operation:"COHORT_DELETE",userId,summary:`Deleted cohort ${ctx.params.id}`}),{success:!0,data:null}};import{eq as eq30}from"drizzle-orm";var handleUpdateCohort=(db,usersTable,cohortsTable)=>async(ctx)=>{let userId=ctx.request.headers.get("x-user-id");if(!userId||!await requireGodmin(db,usersTable,userId))return ctx.set.status=403,{success:!1,message:"Forbidden"};let updates={updatedAt:new Date};if(ctx.body.name!==void 0)updates.name=ctx.body.name;if(ctx.body.description!==void 0)updates.description=ctx.body.description;if(ctx.body.expiresAt!==void 0)updates.expiresAt=ctx.body.expiresAt?new Date(ctx.body.expiresAt):null;if(ctx.body.metadata!==void 0)updates.metadata=ctx.body.metadata;if(ctx.body.isActive!==void 0)updates.isActive=ctx.body.isActive;let updated=await db.update(cohortsTable).set(updates).where(eq30(col7(cohortsTable,"id"),ctx.params.id)).returning();if(updated.length===0)return ctx.set.status=404,{success:!1,message:"Cohort not found"};return{success:!0,data:updated[0]}};init_esm3();var CohortBodySchema=Type.Object({name:Type.String({minLength:1,maxLength:255}),description:Type.Optional(Type.String()),expiresAt:Type.Optional(Type.String()),metadata:Type.Optional(Type.Record(Type.String(),Type.Unknown()))}),CohortUpdateSchema=Type.Object({name:Type.Optional(Type.String({minLength:1,maxLength:255})),description:Type.Optional(Type.String()),expiresAt:Type.Optional(Type.Union([Type.String(),Type.Null()])),metadata:Type.Optional(Type.Record(Type.String(),Type.Unknown())),isActive:Type.Optional(Type.Boolean())}),BulkCreateUserItem=Type.Object({email:Type.String({format:"email"}),password:Type.String({minLength:8}),profile:Type.Optional(Type.Object({firstName:Type.Optional(Type.String()),lastName:Type.Optional(Type.String())}))}),BulkCreateSchema=Type.Object({users:Type.Optional(Type.Array(BulkCreateUserItem)),emailPrefix:Type.Optional(Type.String()),emailDomain:Type.Optional(Type.String()),sharedPassword:Type.Optional(Type.String({minLength:8})),count:Type.Optional(Type.Integer({minimum:1,maximum:500})),roleIds:Type.Optional(Type.Array(Type.String()))});function createCohortRoutes(config){let{db,logger:logger2,cohortsTable,usersTable,userRolesTable,profilesTable,basePath}=config;if(!db||!cohortsTable||!usersTable)return logger2.warn("[Cohort] Skipped \u2014 missing tables",{hasDb:!!db,hasCohortsTable:!!cohortsTable,hasUsersTable:!!usersTable}),collectRoutes();let declared=[defineRoute({method:"GET",path:"/",handler:handleListCohorts(db,usersTable,cohortsTable)}),defineRoute({method:"GET",path:"/:id",handler:handleGetCohort(db,usersTable,cohortsTable)}),defineRoute({method:"POST",path:"/",body:CohortBodySchema,handler:handleCreateCohort(db,usersTable,cohortsTable,logger2)}),defineRoute({method:"PATCH",path:"/:id",body:CohortUpdateSchema,handler:handleUpdateCohort(db,usersTable,cohortsTable)}),defineRoute({method:"DELETE",path:"/:id",handler:handleDeleteCohort(db,usersTable,cohortsTable,logger2)}),defineRoute({method:"POST",path:"/:id/bulk-create",body:BulkCreateSchema,handler:handleBulkCreateUsers(config,logger2)}),defineRoute({method:"POST",path:"/:id/deactivate-users",handler:handleDeactivateUsers(db,usersTable,logger2,config.sessionsTable??null)}),defineRoute({method:"POST",path:"/:id/activate-users",handler:handleActivateUsers(db,usersTable,logger2)}),defineRoute({method:"POST",path:"/:id/delete-users",handler:handleDeleteUsers(db,usersTable,cohortsTable,userRolesTable,profilesTable,logger2)}),defineRoute({method:"GET",path:"/:id/export",handler:handleExportUsers(db,usersTable)})];return collectRoutes([{prefix:basePath,routes:declared}])}init_deviceInfo();function shouldNotifyDevice(question){if(question.isImpersonationLogin)return!1;if(!question.emailAvailable)return!1;if(question.requiresApproval)return!0;if(question.trustScore<question.lowTrustThreshold)return!0;return question.notifyOnNewDevice&&question.isNewDevice}init_routeCollector();init_storage2();init_esm3();init_routeCollector();init_Authorization();function getUserId(request){return request.headers.get("x-user-id")}function isChatModerator(request,moderatorRoles){let roles=decodeHeaderList(request.headers.get("x-user-roles"));if(roles.includes(GODMIN_ROLE_NAME))return!0;if(moderatorRoles.length===0)return!1;let allowed=new Set(moderatorRoles.map((role)=>role.toLowerCase()));return roles.some((role)=>allowed.has(role.toLowerCase()))}function chatErrorStatus(code){switch(code){case"forbidden":return 403;case"not_found":return 404;case"validation":return 400;case"conflict":return 409;case"disabled":return 409;default:return 400}}async function guard3(set2,logger2,fn){try{return{success:!0,data:await fn()}}catch(err){if(err instanceof ChatError)return set2.status=chatErrorStatus(err.code),{success:!1,message:err.message};return logger2.error("[Chat] Unhandled route error",{error:err instanceof Error?err.message:String(err)}),set2.status=500,{success:!1,message:"Internal server error"}}}function unauthorized(set2){return set2.status=401,{success:!1,message:"Authentication required"}}function createConversationRoutes(deps){let{logger:logger2,getService}=deps,declared=[defineRoute({method:"GET",path:"/conversations",handler:({request,query,set:set2})=>{let userId=getUserId(request);if(!userId)return unauthorized(set2);let{service}=getService(request);return guard3(set2,logger2,()=>service.listConversations(userId,{limit:query.limit?Number(query.limit):void 0,offset:query.offset?Number(query.offset):void 0}))},query:Type.Object({limit:Type.Optional(Type.String()),offset:Type.Optional(Type.String())}),detail:{tags:["Chat"],summary:"List my conversations"}}),defineRoute({method:"POST",path:"/conversations",handler:({request,body,set:set2})=>{let userId=getUserId(request);if(!userId)return unauthorized(set2);let{service}=getService(request),input=body;return guard3(set2,logger2,()=>{if(input.type==="group"){if(!input.title)throw new ChatError("validation","A group title is required");return service.createGroup({creatorId:userId,title:input.title,description:input.description,avatarFileId:input.avatarFileId,participantIds:input.participantIds??[]})}if(!input.targetUserId)throw new ChatError("validation","targetUserId is required for a direct conversation");return service.getOrCreateDirect(userId,input.targetUserId)})},body:Type.Object({type:Type.Optional(Type.Union([Type.Literal("direct"),Type.Literal("group")])),targetUserId:Type.Optional(Type.String()),title:Type.Optional(Type.String()),description:Type.Optional(Type.String()),avatarFileId:Type.Optional(Type.String()),participantIds:Type.Optional(Type.Array(Type.String()))}),detail:{tags:["Chat"],summary:"Create or open a conversation"}}),defineRoute({method:"GET",path:"/conversations/:id",handler:({request,params,set:set2})=>{let userId=getUserId(request);if(!userId)return unauthorized(set2);let{service}=getService(request);return guard3(set2,logger2,()=>service.getConversation(params.id,userId))},params:Type.Object({id:Type.String()}),detail:{tags:["Chat"],summary:"Get a conversation"}}),defineRoute({method:"POST",path:"/conversations/:id/participants",handler:({request,params,body,set:set2})=>{let userId=getUserId(request);if(!userId)return unauthorized(set2);let{service}=getService(request),{userIds}=body;return guard3(set2,logger2,()=>service.addParticipants(params.id,userId,userIds))},params:Type.Object({id:Type.String()}),body:Type.Object({userIds:Type.Array(Type.String())}),detail:{tags:["Chat"],summary:"Add group participants"}}),defineRoute({method:"DELETE",path:"/conversations/:id/participants/:userId",handler:({request,params,set:set2})=>{let userId=getUserId(request);if(!userId)return unauthorized(set2);let{service}=getService(request);return guard3(set2,logger2,async()=>{return await service.removeParticipant(params.id,userId,params.userId),{removed:params.userId}})},params:Type.Object({id:Type.String(),userId:Type.String()}),detail:{tags:["Chat"],summary:"Remove a participant / leave group"}})];return collectRoutes([{prefix:deps.config.basePath,routes:declared}])}init_esm3();init_esm3();function FileSchema(){return Type.Object({size:Type.Number(),type:Type.String()},{format:"binary",additionalProperties:!0})}function FilesSchema(){return Type.Union([FileSchema(),Type.Array(FileSchema())])}init_routeCollector();init_storage2();var CONTENT_TYPE_SCHEMA=Type.Optional(Type.Union([Type.Literal("text"),Type.Literal("image"),Type.Literal("file"),Type.Literal("video"),Type.Literal("audio")]));function createMessageRoutes(deps){let{logger:logger2,getService}=deps,declared=[defineRoute({method:"GET",path:"/conversations/:id/messages",handler:({request,params,query,set:set2})=>{let userId=getUserId(request);if(!userId)return unauthorized(set2);let{service}=getService(request);return guard3(set2,logger2,()=>service.getMessages({conversationId:params.id,userId,limit:query.limit?Number(query.limit):void 0,before:query.before||void 0}))},params:Type.Object({id:Type.String()}),query:Type.Object({limit:Type.Optional(Type.String()),before:Type.Optional(Type.String())}),detail:{tags:["Chat"],summary:"Get conversation messages (newest first)"}}),defineRoute({method:"POST",path:"/conversations/:id/messages",handler:({request,params,body,set:set2})=>{let userId=getUserId(request);if(!userId)return unauthorized(set2);let{service}=getService(request),input=body;return guard3(set2,logger2,()=>service.sendMessage({conversationId:params.id,senderId:userId,content:input.content,contentType:input.contentType,replyToId:input.replyToId,clientMessageId:input.clientMessageId,attachments:input.attachmentIds?.map((fileId)=>({fileId})),metadata:input.metadata}))},params:Type.Object({id:Type.String()}),body:Type.Object({content:Type.Optional(Type.String()),contentType:CONTENT_TYPE_SCHEMA,replyToId:Type.Optional(Type.String()),clientMessageId:Type.Optional(Type.String()),attachmentIds:Type.Optional(Type.Array(Type.String())),metadata:Type.Optional(Type.Any())}),detail:{tags:["Chat"],summary:"Send a message (link pre-uploaded attachments)"}}),defineRoute({method:"POST",path:"/conversations/:id/messages/upload",handler:({request,params,body,set:set2})=>{let userId=getUserId(request);if(!userId)return unauthorized(set2);if(!deps.attachmentsAvailable)return set2.status=409,{success:!1,message:"File attachments require storage and CDN to be enabled"};let{service,schemaTables}=getService(request);return guard3(set2,logger2,async()=>{let filesTable=resolveSchemaTable(schemaTables,"files",logger2);if(!filesTable)throw new ChatError("disabled","Files table unavailable in this schema");let{data,files}=parseFormDataBody(body,deps.storageConfig);if(files.length===0)throw new ChatError("validation","At least one file is required");let{records,failed}=await persistUploadedFiles({db:deps.db,filesTable,files,storageConfig:deps.storageConfig,subFolder:"chat",userId});if(records.length===0)throw new ChatError("validation",failed[0]?.error??"File upload failed");if(deps.cdnMedia)scheduleUploadMediaProcessing(records,{storagePath:deps.storageConfig.basePath,media:deps.cdnMedia,logger:logger2});let content=typeof data.content==="string"?data.content:void 0,replyToId=typeof data.replyToId==="string"?data.replyToId:void 0,clientMessageId=typeof data.clientMessageId==="string"?data.clientMessageId:void 0;return service.sendMessage({conversationId:params.id,senderId:userId,content,replyToId,clientMessageId,attachments:records.map((record3)=>({fileId:record3.id,originalName:record3.originalName,mimeType:record3.mimeType,size:record3.size}))})})},type:"formdata",params:Type.Object({id:Type.String()}),body:Type.Object({[deps.storageConfig.formData.dataField]:Type.Optional(Type.Union([Type.String(),Type.Any()])),[deps.storageConfig.formData.filesField]:Type.Optional(FilesSchema())}),detail:{tags:["Chat"],summary:"Send a message with uploaded file attachments"}}),defineRoute({method:"POST",path:"/conversations/:id/read",handler:({request,params,body,set:set2})=>{let userId=getUserId(request);if(!userId)return unauthorized(set2);let{service}=getService(request),{messageId}=body;return guard3(set2,logger2,()=>service.markRead(params.id,userId,messageId))},params:Type.Object({id:Type.String()}),body:Type.Object({messageId:Type.Optional(Type.String())}),detail:{tags:["Chat"],summary:"Mark a conversation as read"}}),defineRoute({method:"POST",path:"/conversations/:id/typing",handler:({request,params,body,set:set2})=>{let userId=getUserId(request);if(!userId)return unauthorized(set2);let{service}=getService(request),{isTyping}=body;return guard3(set2,logger2,async()=>{return await service.setTyping(params.id,userId,isTyping??!0),{ok:!0}})},params:Type.Object({id:Type.String()}),body:Type.Object({isTyping:Type.Optional(Type.Boolean())}),detail:{tags:["Chat"],summary:"Broadcast a typing indicator"}}),defineRoute({method:"PATCH",path:"/messages/:id",handler:({request,params,body,set:set2})=>{let userId=getUserId(request);if(!userId)return unauthorized(set2);let{service}=getService(request),{content}=body;return guard3(set2,logger2,()=>service.editMessage(params.id,userId,content))},params:Type.Object({id:Type.String()}),body:Type.Object({content:Type.String()}),detail:{tags:["Chat"],summary:"Edit a message"}}),defineRoute({method:"DELETE",path:"/messages/:id",handler:({request,params,set:set2})=>{let userId=getUserId(request);if(!userId)return unauthorized(set2);let{service}=getService(request),asModerator=isChatModerator(request,deps.config.moderatorRoles??[]);return guard3(set2,logger2,async()=>{if(await service.deleteMessage(params.id,userId,asModerator),asModerator)logger2.warn("[CHAT] Message deleted by a moderator",{actorId:userId,messageId:params.id});return{deleted:params.id}})},params:Type.Object({id:Type.String()}),detail:{tags:["Chat"],summary:"Delete a message",description:"Sender-only, unless the caller is godmin or holds a role from chat.moderatorRoles \u2014 then any message, and `deletionEnabled` does not apply."}})];return collectRoutes([{prefix:deps.config.basePath,routes:declared}])}function createChatRoutes(routeConfig){let{db,schemaTables,config,logger:logger2,broadcaster,tenantRegistry,storageConfig,attachmentsAvailable,cdnBasePath,cdnMedia}=routeConfig,buildService=(tables)=>new ChatService({db,schemaTables:tables,config,logger:logger2,broadcaster,cdnBasePath,attachmentsAvailable,deleteStoredFile:(file)=>deleteFile(file.path,file.name,storageConfig.basePath)}),defaultService=buildService(schemaTables),deps={db,config,logger:logger2,storageConfig,attachmentsAvailable,getService:(request)=>{if(!tenantRegistry)return{service:defaultService,schemaTables};let schemaName=request.headers.get("x-tenant-schema");if(!schemaName)return{service:defaultService,schemaTables};let ctx=tenantRegistry.getSchemaContext(schemaName);if(!ctx)return{service:defaultService,schemaTables};return{service:buildService(ctx.schemaTables),schemaTables:ctx.schemaTables}},cdnMedia},routes=collectRoutes();return routes.use(createConversationRoutes(deps)),routes.use(createMessageRoutes(deps)),logger2.info(`[Chat] Routes registered at ${config.basePath}`,{attachmentsAvailable,groupsEnabled:config.groupsEnabled}),{routes,chatService:defaultService}}init_esm3();init_routeCollector();init_godminGuard();init_helpers4();function createConfigRoutes(routeConfig){let{logger:logger2,resolvedOptions,configFilePath,basePath,onConfigUpdate,db,schemaTables,getRedis}=routeConfig,guard4=createGodminGuard({logger:logger2,db:db??null,schemaTables,area:"config management"});logger2.info(`[ConfigManagement] Routes enabled at ${basePath}`);let declared=[defineRoute({method:"GET",path:"/",handler:()=>{let configCopy=JSON.parse(JSON.stringify(resolvedOptions));return{isSuccess:!0,message:"Current running configuration",data:{config:maskSensitiveFields(configCopy),configFilePath,sections:buildSectionsMeta(resolvedOptions)}}},detail:{tags:["Config Management"],summary:"Get full running configuration",description:"Returns the full running configuration with sensitive fields masked."}}),defineRoute({method:"GET",path:"/sections",handler:()=>({isSuccess:!0,message:"Config section metadata",data:{sections:buildSectionsMeta(resolvedOptions)}}),detail:{tags:["Config Management"],summary:"List all config sections with metadata",description:"Section list is derived at runtime from the config object \u2014 no hardcoding."}}),defineRoute({method:"GET",path:"/env",handler:()=>{let configCopy=JSON.parse(JSON.stringify(resolvedOptions)),declared2=scanEnvVars(configCopy),declaredNames=new Set(declared2.map((e)=>e.envName)),undeclared=scanUndeclaredEnv(declaredNames),entries=[...declared2,...undeclared],resolvedCount=entries.filter((e)=>e.resolved).length,missingCount=entries.length-resolvedCount,unrecognizedCount=undeclared.length;return{isSuccess:!0,message:`Found ${entries.length} env vars (${resolvedCount} resolved, ${missingCount} missing, ${unrecognizedCount} unrecognized)`,data:{entries,totalCount:entries.length,resolvedCount,missingCount,unrecognizedCount}}},detail:{tags:["Config Management"],summary:"Get environment variables (declared + undeclared)",description:"Surfaces both (a) DECLARED env vars \u2014 UPPER_SNAKE_CASE references found in the config tree (recognized:true) \u2014 and (b) UNDECLARED env vars actually present in process.env but not declared (recognized:false), minus Kubernetes service-link and OS/interpreter infra noise, so orphaned / junk config surfaces for cleanup. Sensitive values are masked; AI-provider *_API_KEY vars are never surfaced."}}),defineRoute({method:"GET",path:"/overrides",handler:async()=>{let redis=getRedis();if(!redis)return{isSuccess:!0,message:"Redis not available \u2014 no overrides stored",data:{overrides:null,sectionCount:0,sections:[]}};let overrides=await readOverridesFromRedis(redis,resolvedOptions.appId||"nucleus"),sections=overrides?Object.keys(overrides).filter((k)=>{let v=overrides[k];return v!==void 0&&v!==null&&(typeof v!=="object"||Object.keys(v).length>0)}):[];return{isSuccess:!0,message:sections.length>0?`${sections.length} section override(s) stored in Redis`:"No overrides in Redis",data:{overrides:overrides?maskSensitiveFields(overrides):null,sectionCount:sections.length,sections}}},detail:{tags:["Config Management"],summary:"View Redis config overrides",description:"Returns overrides stored in Redis that will be merged on next restart."}}),defineRoute({method:"DELETE",path:"/overrides",handler:async(ctx)=>{let redis=getRedis();if(!redis)return ctx.set.status=503,{isSuccess:!1,message:"Redis not available",data:null};let existing=await readOverridesFromRedis(redis,resolvedOptions.appId||"nucleus"),clearedSections=existing?Object.keys(existing):[];return await clearOverridesFromRedis(redis,resolvedOptions.appId||"nucleus"),logger2.info("[ConfigManagement] Redis overrides cleared",{clearedSections}),{isSuccess:!0,message:clearedSections.length>0?`Cleared ${clearedSections.length} override(s) from Redis. Restart to revert to config.json defaults.`:"No overrides to clear.",data:{message:"Redis overrides cleared",clearedSections}}},detail:{tags:["Config Management"],summary:"Clear all Redis config overrides",description:"Removes all stored overrides from Redis. Restart to revert to config.json defaults."}}),defineRoute({method:"GET",path:"/:section",handler:({params,set:set2})=>{let{section}=params;if(!hasSection(resolvedOptions,section))return set2.status=404,{isSuccess:!1,message:`Unknown config section: ${section}`,data:null};let sectionValue=extractSection(resolvedOptions,section),restart=isRestartRequired(section),maskedValue=sectionValue;if(typeof sectionValue==="object"&§ionValue!==null&&!Array.isArray(sectionValue))maskedValue=maskSensitiveFields({[section]:sectionValue})[section];return{isSuccess:!0,message:`Config section: ${section}`,data:{section,config:maskedValue??null,restartRequired:restart}}},params:Type.Object({section:Type.String()}),detail:{tags:["Config Management"],summary:"Get a specific config section",description:"Returns a specific config section with sensitive fields masked."}}),defineRoute({method:"PATCH",path:"/:section",handler:async({params,body,set:set2})=>{let{section}=params,payload=body;if(!hasSection(resolvedOptions,section))return set2.status=404,{isSuccess:!1,message:`Unknown config section: ${section}`,data:null};if(!payload.config||typeof payload.config!=="object")return set2.status=400,{isSuccess:!1,message:'Request body must contain a "config" object',data:null};let restart=isRestartRequired(section),currentValue=extractSection(resolvedOptions,section),submitted=Array.isArray(currentValue)||currentValue===void 0||typeof currentValue==="string"||typeof currentValue==="number"||typeof currentValue==="boolean"?payload.config.value!==void 0?payload.config.value:payload.config:payload.config,incoming=restoreMaskedEchoes(section,currentValue,submitted),newValue;if(typeof currentValue==="object"&¤tValue!==null&&!Array.isArray(currentValue))newValue=deepMerge(currentValue,incoming);else newValue=incoming;if(applySectionUpdate(resolvedOptions,section,newValue),!restart)onConfigUpdate(section,newValue);let persistMethod="memory-only",persistWarning=null;if(configFilePath)try{await persistConfigToDisk(configFilePath,resolvedOptions),persistMethod="disk",logger2.info(`[ConfigManagement] Section "${section}" persisted to disk`)}catch(diskErr){let diskMsg=diskErr instanceof Error?diskErr.message:String(diskErr);logger2.warn(`[ConfigManagement] Disk persist failed: ${diskMsg}`);let redis=getRedis();if(redis)try{await persistSectionToRedis(redis,resolvedOptions.appId||"nucleus",section,newValue),persistMethod="redis",logger2.info(`[ConfigManagement] Section "${section}" persisted to Redis (disk unavailable)`)}catch(redisErr){let redisMsg=redisErr instanceof Error?redisErr.message:String(redisErr);logger2.error(`[ConfigManagement] Redis persist also failed: ${redisMsg}`),persistWarning=`Disk: ${diskMsg}. Redis: ${redisMsg}. Changes are in-memory only and will be lost on restart.`}else persistWarning=`Disk: ${diskMsg}. Redis not available. Changes are in-memory only and will be lost on restart.`}logger2.info(`[ConfigManagement] Section "${section}" updated (persist: ${persistMethod}, restart-required: ${restart})`);let updatedValue=extractSection(resolvedOptions,section),maskedUpdated=updatedValue;if(typeof updatedValue==="object"&&updatedValue!==null&&!Array.isArray(updatedValue))maskedUpdated=maskSensitiveFields({[section]:updatedValue})[section];let baseMsg=restart?`Config section "${section}" saved (${persistMethod}). Restart required for changes to take effect.`:`Config section "${section}" updated and applied (${persistMethod}).`;return{isSuccess:!0,message:persistWarning?`${baseMsg} Warning: ${persistWarning}`:baseMsg,data:{section,applied:!restart,restartRequired:restart,config:maskedUpdated}}},params:Type.Object({section:Type.String()}),body:Type.Object({config:Type.Record(Type.String(),Type.Unknown())}),detail:{tags:["Config Management"],summary:"Update a config section",description:"Deep-merges the payload into the section. Hot-reloadable sections apply immediately. Restart-required sections are saved to disk only."}}),defineRoute({method:"POST",path:"/restart",handler:({body})=>{let delayMs=body.delayMs??1000;logger2.info(`[ConfigManagement] Restart scheduled in ${delayMs}ms`);let scheduledAt=new Date().toISOString();return setTimeout(()=>{logger2.info("[ConfigManagement] Restarting process..."),process.exit(0)},delayMs),{isSuccess:!0,message:`Server restart scheduled in ${delayMs}ms`,data:{message:`Process will exit in ${delayMs}ms. Orchestrator (K8s/PM2/systemd) will restart it.`,scheduledAt}}},body:Type.Optional(Type.Object({delayMs:Type.Optional(Type.Number())})),detail:{tags:["Config Management"],summary:"Restart the server process",description:"Triggers a graceful process exit after a configurable delay. The orchestrator (K8s, PM2, systemd) is expected to restart the process automatically."}})];return collectRoutes([{prefix:basePath,guards:[guard4],routes:declared}])}init_routeMount();init_esm3();init_routeCollector();init_adminGuard();var requireAuth=(ctx)=>ctx.request.headers.get("x-user-id"),fail=(ctx,status,message)=>{return ctx.set.status=status,{success:!1,message,data:null}},callerSchemaOf=(ctx)=>ctx.request.headers.get("x-tenant-schema"),isGodmin=(ctx)=>hasGodminRole(ctx.request),ownsHostname=(ctx,record3)=>{if(isGodmin(ctx))return!0;let caller=callerSchemaOf(ctx);return record3.schemaName!=null&&record3.schemaName===caller};function createHostnameRoutes(config){let base=config.basePath,declared=[defineRoute({method:"POST",path:`${base}/hostnames`,handler:async(ctx)=>{let service=svc(ctx);if(!service)return fail(ctx,503,"Domains not available");if(!requireAuth(ctx))return fail(ctx,401,"Authentication required");let body=ctx.body,callerSchema=callerSchemaOf(ctx),schemaName=body.schemaName??null,tenantId=body.tenantId??null;if(!hasGodminRole(ctx.request)){if(schemaName&&schemaName!==callerSchema)return fail(ctx,403,"Cannot bind a hostname to another tenant's schema");schemaName=callerSchema;let ownTenantId=config.resolveTenantIdForSchema?.(callerSchema)??null;if(tenantId&&tenantId!==ownTenantId)return fail(ctx,403,"Cannot bind a hostname to another tenant");tenantId=ownTenantId}try{let result=await service.createHostname({hostname:body.hostname,ownerId:body.ownerId,ownerType:body.ownerType,tenantId,schemaName,isPrimary:body.isPrimary});return{success:!0,message:"Custom hostname created",data:{hostname:result.hostname,instructions:result.instructions}}}catch(err){return fail(ctx,400,err instanceof Error?err.message:"Failed to create hostname")}},body:Type.Object({hostname:Type.String(),ownerId:Type.String(),ownerType:Type.Optional(Type.Union([Type.Literal("tenant"),Type.Literal("organization"),Type.Literal("user"),Type.Literal("external")])),tenantId:Type.Optional(Type.String()),schemaName:Type.Optional(Type.String()),isPrimary:Type.Optional(Type.Boolean())}),detail:{tags:["Domains"],summary:"Create custom hostname"}}),defineRoute({method:"GET",path:`${base}/hostnames`,handler:async(ctx)=>{let service=svc(ctx);if(!service)return fail(ctx,503,"Domains not available");if(!requireAuth(ctx))return fail(ctx,401,"Authentication required");let url=new URL(ctx.request.url),items;if(isGodmin(ctx))items=await service.listHostnames({ownerId:url.searchParams.get("ownerId")??void 0,tenantId:url.searchParams.get("tenantId")??void 0});else{let ownTenantId=config.resolveTenantIdForSchema?.(callerSchemaOf(ctx))??null;items=ownTenantId?await service.listHostnames({tenantId:ownTenantId}):[]}return{success:!0,message:`Found ${items.length} hostnames`,data:{items}}}}),defineRoute({method:"GET",path:`${base}/hostnames/:id`,handler:async(ctx)=>{let service=svc(ctx);if(!service)return fail(ctx,503,"Domains not available");if(!requireAuth(ctx))return fail(ctx,401,"Authentication required");let record3=await service.getHostname(ctx.params.id);if(!record3||!ownsHostname(ctx,record3))return fail(ctx,404,"Hostname not found");return{success:!0,message:"Hostname found",data:record3}}}),defineRoute({method:"GET",path:`${base}/hostnames/:id/instructions`,handler:async(ctx)=>{let service=svc(ctx);if(!service)return fail(ctx,503,"Domains not available");if(!requireAuth(ctx))return fail(ctx,401,"Authentication required");let id=ctx.params.id,record3=await service.getHostname(id);if(!record3||!ownsHostname(ctx,record3))return fail(ctx,404,"Hostname not found");return{success:!0,message:"DNS instructions",data:{instructions:await service.getInstructions(id)}}}})],svc=(ctx)=>{let s=config.getDomainService();if(!s)return ctx.set.status=503,null;return s},lifecycle2=(suffix,run,summary)=>{declared.push(defineRoute({method:"POST",path:`${base}/hostnames/:id/${suffix}`,handler:async(ctx)=>{let service=svc(ctx);if(!service)return fail(ctx,503,"Domains not available");if(!requireAuth(ctx))return fail(ctx,401,"Authentication required");let id=ctx.params.id,record3=await service.getHostname(id);if(!record3||!ownsHostname(ctx,record3))return fail(ctx,404,"Hostname not found");try{let data=await run(service,id);if(!data)return fail(ctx,404,"Hostname not found");return{success:!0,message:summary,data}}catch(err){return fail(ctx,400,err instanceof Error?err.message:summary)}}}))};return lifecycle2("verify",(s,id)=>s.verifyHostname(id),"Verification processed"),lifecycle2("activate",(s,id)=>s.activateHostname(id),"Hostname activated"),lifecycle2("disable",(s,id)=>s.disableHostname(id),"Hostname disabled"),lifecycle2("make-primary",(s,id)=>s.makePrimary(id),"Primary hostname set"),lifecycle2("refresh-status",(s,id)=>s.refreshStatus(id),"Status refreshed"),collectRoutes([{routes:declared}])}init_esm3();init_routeCollector();init_adminGuard();var requireAuth2=(ctx)=>ctx.request.headers.get("x-user-id"),fail2=(ctx,status,message)=>{return ctx.set.status=status,{success:!1,message,data:null}},callerSchemaOf2=(ctx)=>ctx.request.headers.get("x-tenant-schema"),isGodmin2=(ctx)=>hasGodminRole(ctx.request);function createRegistrationRoutes(config){let base=config.basePath,svc=(ctx)=>{let s=config.getRegistrationService();if(!s)return ctx.set.status=503,null;return s},ownTenantId=(ctx)=>config.resolveTenantIdForSchema?.(callerSchemaOf2(ctx))??null,ownsRegistration=(ctx,record3)=>{if(isGodmin2(ctx))return!0;let own=ownTenantId(ctx);return record3.tenantId!=null&&own!=null&&record3.tenantId===own},declared=[defineRoute({method:"POST",path:`${base}/registrations`,handler:async(ctx)=>{let service=svc(ctx);if(!service)return fail2(ctx,503,"Domains not available");if(!requireAuth2(ctx))return fail2(ctx,401,"Authentication required");let body=ctx.body,tenantId=body.tenantId??null,registrantOwnerType=body.registrantOwnerType;if(!isGodmin2(ctx)){let own=ownTenantId(ctx);if(tenantId&&tenantId!==own)return fail2(ctx,403,"Cannot register a domain for another tenant");if(tenantId=own,registrantOwnerType==="platform")return fail2(ctx,403,"Only a platform admin may register as 'platform'");registrantOwnerType="customer"}try{return{success:!0,message:"Registration requested",data:await service.createRequest({requestedDomain:body.requestedDomain,ownerId:body.ownerId,ownerType:body.ownerType,tenantId,registrantOwnerType,termsVersion:body.termsVersion,termsAccepted:body.termsAccepted,autoRenew:body.autoRenew})}}catch(err){return fail2(ctx,400,err instanceof Error?err.message:"Failed to request registration")}},body:Type.Object({requestedDomain:Type.String(),ownerId:Type.String(),ownerType:Type.Optional(Type.Union([Type.Literal("tenant"),Type.Literal("organization"),Type.Literal("user"),Type.Literal("external")])),tenantId:Type.Optional(Type.String()),registrantOwnerType:Type.Optional(Type.Union([Type.Literal("customer"),Type.Literal("platform")])),termsVersion:Type.String(),termsAccepted:Type.Boolean(),autoRenew:Type.Optional(Type.Boolean())}),detail:{tags:["Domains"],summary:"Request managed domain registration"}}),defineRoute({method:"GET",path:`${base}/registrations`,handler:async(ctx)=>{let service=svc(ctx);if(!service)return fail2(ctx,503,"Domains not available");if(!requireAuth2(ctx))return fail2(ctx,401,"Authentication required");let url=new URL(ctx.request.url),items;if(isGodmin2(ctx))items=await service.listRegistrations({ownerId:url.searchParams.get("ownerId")??void 0,tenantId:url.searchParams.get("tenantId")??void 0});else{let own=ownTenantId(ctx);items=own?await service.listRegistrations({tenantId:own}):[]}return{success:!0,message:`Found ${items.length} registrations`,data:{items}}}}),defineRoute({method:"GET",path:`${base}/registrations/:id`,handler:async(ctx)=>{let service=svc(ctx);if(!service)return fail2(ctx,503,"Domains not available");if(!requireAuth2(ctx))return fail2(ctx,401,"Authentication required");let record3=await service.getRegistration(ctx.params.id);if(!record3||!ownsRegistration(ctx,record3))return fail2(ctx,404,"Registration not found");return{success:!0,message:"Registration found",data:record3}}}),defineRoute({method:"POST",path:`${base}/registrations/:id/cancel`,handler:async(ctx)=>{let service=svc(ctx);if(!service)return fail2(ctx,503,"Domains not available");if(!requireAuth2(ctx))return fail2(ctx,401,"Authentication required");let id=ctx.params.id,record3=await service.getRegistration(id);if(!record3||!ownsRegistration(ctx,record3))return fail2(ctx,404,"Registration not found");let cancelled=await service.cancel(id);if(!cancelled)return fail2(ctx,404,"Registration not found");return{success:!0,message:"Registration cancelled",data:cancelled}}}),defineRoute({method:"POST",path:`${base}/registrations/:id/transfer-out`,handler:async(ctx)=>{let service=svc(ctx);if(!service)return fail2(ctx,503,"Domains not available");if(!requireAuth2(ctx))return fail2(ctx,401,"Authentication required");let id=ctx.params.id,record3=await service.getRegistration(id);if(!record3||!ownsRegistration(ctx,record3))return fail2(ctx,404,"Registration not found");let out=await service.requestTransferOut(id);if(!out)return fail2(ctx,404,"Registration not found");return{success:!0,message:"Transfer-out requested",data:out}}})];return collectRoutes([{routes:declared}])}init_routeCollector();function createResolveRoute(config){let base=config.basePath,declared=[defineRoute({method:"GET",path:`${base}/resolve`,isPublic:!0,handler:async(ctx)=>{let service=config.getDomainService();if(!service)return ctx.set.status=503,{success:!1,message:"Domains not available",data:null};let hostname=new URL(ctx.request.url).searchParams.get("hostname")||ctx.request.headers.get("host")||"";if(!hostname)return ctx.set.status=400,{success:!1,message:"hostname is required",data:null};let match=await service.resolveHostname(hostname);if(!match)return ctx.set.status=404,{success:!1,message:"No active hostname matched",data:null};return{success:!0,message:"Resolved",data:match}},detail:{tags:["Domains"],summary:"Resolve hostname to tenant",description:"Public."}})];return collectRoutes([{routes:declared}])}function createDomainRoutes(app,config){return useRoutes(app,createResolveRoute(config)),useRoutes(app,createHostnameRoutes(config)),useRoutes(app,createRegistrationRoutes(config)),app}init_esm3();import{and as and18,arrayContains,arrayOverlaps,asc,createTableRelationsHelpers,desc as desc5,eq as eq32,getTableName,gt,gte,ilike,inArray as inArray12,isNotNull,isNull as isNull4,like,lt as lt2,lte,ne as ne2,notInArray,or as or4,sql as sql8}from"drizzle-orm";import{drizzle}from"drizzle-orm/node-postgres";init_AccountLink();init_Authorization();init_routeMount();init_utils5();init_revokeUserSessions();function decideRoleAssign(params){let{callerIsGod,callerRoleNames,allowRoles,targetRoleName,subjectIsGodmin,godminRoleName}=params,callerIsGodmin=callerIsGod||callerRoleNames.includes(godminRoleName);if(!(callerIsGodmin||callerRoleNames.some((name2)=>allowRoles.includes(name2))))return{allowed:!1,status:403,reason:"not_privileged"};if(targetRoleName===godminRoleName&&!callerIsGodmin)return{allowed:!1,status:403,reason:"godmin_assignment_forbidden"};if(subjectIsGodmin&&!callerIsGodmin)return{allowed:!1,status:403,reason:"target_is_godmin"};return{allowed:!0,callerIsGodmin}}var ROLE_ASSIGN_REFUSALS={not_privileged:"You are not allowed to assign roles.",godmin_assignment_forbidden:"Only a system administrator can grant this role.",target_is_godmin:"This person\u2019s roles can only be changed by a system administrator."};init_adminGuard();init_sensitiveColumns();var snakeToCamel2=(s)=>s.replace(/_([a-z])/g,(_,l)=>l.toUpperCase()),SENSITIVE_SYSTEM_TABLE_SNAKE=["domain_hostnames","domain_registrations","domain_provider_records","domain_verification_challenges","domain_events","payment_sub_merchants","payment_invoices","payment_subscriptions","payment_customers","payment_transactions","payment_methods","payment_products","payment_prices","payment_commission_splits","payment_splits","payment_reserves","payment_ledger_entries","payment_payout_requests","payment_settlement_policies","payment_disputes","payment_webhook_logs","verifications","verification_requirements","verification_instances","verification_flows","verification_steps","verification_edges","verification_verifier_configs","verification_notification_rules","verification_notification_recipients","verification_notification_channels","api_keys","magic_link_tokens","user_sessions","trusted_devices","integration_sources","integration_endpoints","integration_mappings"],SENSITIVE_SYSTEM_TABLES=new Set(SENSITIVE_SYSTEM_TABLE_SNAKE.flatMap((name2)=>[name2,snakeToCamel2(name2)]));function isSensitiveSystemTable(tableName){return SENSITIVE_SYSTEM_TABLES.has(tableName)}function delegatedWriteRoles(question){if(question.isRbacTable)return new Set;let roles=(question.featureSet??[]).flatMap((feature)=>question.featureAdminRoles?.[feature]??[]);return new Set(roles.map((role)=>role.toLowerCase()))}function isFeatureAdmin(question){let allowed=delegatedWriteRoles(question);if(allowed.size===0)return!1;return question.callerRoles.some((role)=>allowed.has(role.toLowerCase()))}init_storage2();import{and as and16,eq as eq31,inArray as inArray11}from"drizzle-orm";function bothSpellings(name2){let snake=name2.replace(/[A-Z]/g,(c)=>`_${c.toLowerCase()}`),camel=name2.replace(/_([a-z0-9])/g,(_m,c)=>c.toUpperCase());return[...new Set([name2,snake,camel])]}async function activeVerificationExists(db,schemaTables,entityName,entityId){let instancesTable=schemaTables.verificationInstances;if(!instancesTable)return!1;let cols=instancesTable;if(!cols.entityName||!cols.entityId||!cols.status)return!1;try{return(await db.select().from(instancesTable).where(and16(inArray11(cols.entityName,bothSpellings(entityName)),eq31(cols.entityId,entityId),eq31(cols.status,"active"))).limit(1)).length>0}catch{return!1}}function isAnonymousCreate(table,payload){let rule=table.anonymous_create;if(!rule)return!1;if(rule===!0)return!0;let column=rule.column;if(!column)return!1;let value2=payload[column]??payload[toCamelCase3(column)]??payload[toSnakeCase2(column)];return isTruthy(value2)}function isTruthy(value2){if(typeof value2==="boolean")return value2;if(typeof value2==="number")return value2!==0;if(typeof value2==="string"){let normalized=value2.trim().toLowerCase();return normalized==="true"||normalized==="1"||normalized==="yes"||normalized==="on"}return!1}function createAuditIdentity(anonymous,userId,headers){if(anonymous)return{userId:void 0,ipAddress:"anonymous",userAgent:"anonymous"};return{userId:userId||void 0,ipAddress:headers.get("x-client-ip")?.trim()||headers.get("x-forwarded-for")?.split(",")[0]?.trim()||headers.get("x-real-ip")?.trim()||"unknown",userAgent:headers.get("user-agent")||"unknown"}}function toCamelCase3(value2){return value2.replace(/_([a-z0-9])/g,(_m,c)=>c.toUpperCase())}function toSnakeCase2(value2){return value2.replace(/[A-Z]/g,(c)=>`_${c.toLowerCase()}`)}var snakeToCamel3=(s)=>s.replace(/_([a-z])/g,(_,l)=>l.toUpperCase());function forceCreateScope(payload,authResult,resolveColumn){let scopeFilters=authResult?.scopeFilters;if(!scopeFilters)return{ok:!0};for(let[field,value2]of Object.entries(scopeFilters)){if(!resolveColumn(field))return{ok:!1,field};payload[snakeToCamel3(field)]=value2}return{ok:!0}}function decideRelationExpansion(params){let{authEnabled,targetTable,relAuth,isTargetPublicGet}=params;if(!authEnabled||!targetTable)return{expand:!0};if(relAuth===null)return isTargetPublicGet?{expand:!0}:{expand:!1};if(!relAuth.authorized)return{expand:!1};let scope=relAuth.scopeFilters;if(scope&&Object.keys(scope).length>0)return{expand:!0,scope};return{expand:!0}}function resolveRelationScopeColumn(fields,key2,toCamel5){if(Object.hasOwn(fields,key2))return fields[key2];let camel=toCamel5(key2);return Object.hasOwn(fields,camel)?fields[camel]:void 0}import{and as and17,or as or3,sql as sql7}from"drizzle-orm";var MAX_SEARCH_TOKENS=10,TURKISH_LETTERS="\u0130\u0131\u015E\u015F\u011E\u011F\xDC\xFC\xD6\xF6\xC7\xE7",ASCII_EQUIVALENTS="IiSsGgUuOoCc";function searchTokens(term){return term.split(/\s+/).filter(Boolean).slice(0,MAX_SEARCH_TOKENS)}function foldForSearch(value2){let folded="";for(let character of value2){let index=TURKISH_LETTERS.indexOf(character);folded+=index===-1?character:ASCII_EQUIVALENTS[index]}return folded.toLowerCase()}function asRegexLiteral(value2){return value2.replace(/[.*+?^${}()|[\]\\]/g,(character)=>`\\${character}`)}var WORD_START="(^|[^[:alnum:]])";function folded(column){return sql7`lower(translate(cast(${column} as text), ${TURKISH_LETTERS}, ${ASCII_EQUIVALENTS}))`}function buildSearchCondition(columns,term){let tokens=searchTokens(term);if(tokens.length===0||columns.length===0)return null;let perToken=tokens.map((token)=>{let pattern2=`${WORD_START}${asRegexLiteral(foldForSearch(token))}`;return or3(...columns.map((column)=>sql7`${folded(column)} ~ ${pattern2}`))}).filter((condition)=>condition!==void 0);if(perToken.length===0)return null;return and17(...perToken)??null}init_Authorization();init_types3();function targetFromBody(body){let record3=body??{},read2=(...keys)=>{for(let key2 of keys){let value2=record3[key2];if(typeof value2==="string"&&value2.length>0)return value2}return null};return{userId:read2("userId","user_id"),roleId:read2("roleId","role_id")}}async function readRow(db,userRolesTable,rowId,logger2){try{let{eq:eq32}=await import("drizzle-orm"),columns=userRolesTable,row=(await db.select().from(userRolesTable).where(eq32(columns.id,rowId)).limit(1))[0];if(!row)return{userId:null,roleId:null};let text=(value2)=>typeof value2==="string"&&value2.length>0?value2:null;return{userId:text(row.userId??row.user_id),roleId:text(row.roleId??row.role_id)}}catch(err){return logger2.warn("[UserRoleWrite] Failed to read the user_roles row",{error:err instanceof Error?err.message:String(err)}),{userId:null,roleId:null}}}async function roleNameOf(db,rolesTable,roleId,logger2){try{let{eq:eq32}=await import("drizzle-orm"),columns=rolesTable,name2=(await db.select({name:columns.name}).from(rolesTable).where(eq32(columns.id,roleId)).limit(1))[0]?.name;return typeof name2==="string"&&name2.length>0?name2:null}catch(err){return logger2.warn("[UserRoleWrite] Failed to read the role name",{error:err instanceof Error?err.message:String(err)}),null}}async function isGodmin3(db,userRolesTable,rolesTable,userId,logger2){try{let{eq:eq32}=await import("drizzle-orm"),ur=userRolesTable,r=rolesTable;return(await db.select({roleName:r.name}).from(userRolesTable).innerJoin(rolesTable,eq32(ur.roleId,r.id)).where(eq32(ur.userId,userId))).some((row)=>isGodminRole(String(row.roleName??"")))}catch(err){return logger2.warn("[UserRoleWrite] Failed to check whether the subject is a godmin",{error:err instanceof Error?err.message:String(err)}),!0}}async function resolveUserRoleSubject(deps,write){let{db,schemaTables,logger:logger2}=deps,userRolesTable=schemaTables[AUTHORIZATION_TABLE_KEYS.userRoles],rolesTable=schemaTables[AUTHORIZATION_TABLE_KEYS.roles];if(!db||!userRolesTable||!rolesTable)return{targetRoleName:null,subjectIsGodmin:!0};let fromBody=targetFromBody(write.body),fromRow=write.rowId?await readRow(db,userRolesTable,write.rowId,logger2):{userId:null,roleId:null},roleIds=[fromRow.roleId,fromBody.roleId].filter((id)=>Boolean(id)),userIds=[fromRow.userId,fromBody.userId].filter((id)=>Boolean(id));if(roleIds.length===0||userIds.length===0)return{targetRoleName:null,subjectIsGodmin:!0};let names2=await Promise.all(roleIds.map((id)=>roleNameOf(db,rolesTable,id,logger2)));if(names2.some((name2)=>name2===null))return{targetRoleName:null,subjectIsGodmin:!0};let readable=names2.filter((name2)=>name2!==null),targetRoleName=readable.find((name2)=>isGodminRole(name2))??readable[0]??null,godminFlags=await Promise.all(userIds.map((id)=>isGodmin3(db,userRolesTable,rolesTable,id,logger2)));return{targetRoleName,subjectIsGodmin:godminFlags.some(Boolean)}}init_esm3();function buildBodySchemaFromEntityColumns(entityColumns){let properties={};if(!entityColumns||entityColumns.length===0)return Type.Object({},{additionalProperties:!0});for(let col8 of entityColumns)switch(col8.type?.toLowerCase()||"string"){case"integer":case"int":case"serial":case"bigserial":case"numeric":case"decimal":properties[col8.name]=col8.notNull?Type.Number():Type.Optional(Type.Number());break;case"boolean":properties[col8.name]=col8.notNull?Type.Boolean():Type.Optional(Type.Boolean());break;case"timestamp":case"timestamptz":case"date":properties[col8.name]=col8.notNull?Type.String({format:"date-time"}):Type.Optional(Type.String({format:"date-time"}));break;case"json":case"jsonb":properties[col8.name]=Type.Optional(Type.Unknown());break;case"uuid":properties[col8.name]=col8.notNull?Type.String({format:"uuid"}):Type.Optional(Type.String({format:"uuid"}));break;default:properties[col8.name]=col8.notNull?Type.String():Type.Optional(Type.String())}return Type.Object(properties,{additionalProperties:!0})}function createBulkUpdateSchema(entityColumns){return Type.Array(Type.Object({id:Type.String(),data:Type.Partial(buildBodySchemaFromEntityColumns(entityColumns))}))}var MAX_BULK_ITEMS=500;function collectFilterFields(filter,out=[]){if(filter.operator==="or"||filter.operator==="and"){let group=Array.isArray(filter.value)?filter.value:[];for(let f of group)collectFilterFields(f,out)}else if(filter.field)out.push(String(filter.field));return out}function firstUnknownField(fields,resolveCol){for(let field of fields){let trimmed=field.trim();if(trimmed!==""&&!resolveCol(trimmed))return trimmed}return null}var FILTER_OPERATORS=new Set(["eq","neq","gt","gte","lt","lte","like","ilike","in","notIn","isNull","isNotNull","arrayOverlaps","arrayContains","arrayEmpty","or","and"]);function firstUnusableCondition(filter){let operator=filter.operator;if(typeof operator!=="string"||!FILTER_OPERATORS.has(operator))return`Unknown operator in filter: ${String(operator)}`;if(operator==="or"||operator==="and"){let group=Array.isArray(filter.value)?filter.value:[];if(group.length===0)return`Empty "${operator}" group in filter`;for(let sub of group){let unusable=firstUnusableCondition(sub);if(unusable)return unusable}return null}if(typeof filter.field!=="string"||filter.field.trim()==="")return'A filter condition needs a "field"';return null}function buildFilterCondition(filter,resolveCol){if(filter.operator==="or"||filter.operator==="and"){let subs=(Array.isArray(filter.value)?filter.value:[]).map((f)=>buildFilterCondition(f,resolveCol)).filter((c)=>c!==null);if(subs.length===0)return null;return(filter.operator==="or"?or4(...subs):and18(...subs))??null}if(!filter.field)return null;let col8=resolveCol(filter.field);if(!col8)return null;let sqlCol=col8,dataType=col8.dataType,coerce2=(v)=>dataType==="date"&&(typeof v==="string"||typeof v==="number")?new Date(v):v;switch(filter.operator){case"eq":return eq32(sqlCol,coerce2(filter.value));case"neq":return ne2(sqlCol,coerce2(filter.value));case"gt":return gt(sqlCol,coerce2(filter.value));case"gte":return gte(sqlCol,coerce2(filter.value));case"lt":return lt2(sqlCol,coerce2(filter.value));case"lte":return lte(sqlCol,coerce2(filter.value));case"like":return like(sqlCol,filter.value);case"ilike":return ilike(sqlCol,filter.value);case"in":return inArray12(sqlCol,filter.value);case"notIn":return notInArray(sqlCol,filter.value);case"arrayOverlaps":return arrayOverlaps(sqlCol,filter.value??[]);case"arrayContains":return arrayContains(sqlCol,filter.value??[]);case"arrayEmpty":return sql8`cardinality(${sqlCol}) = 0`;case"isNull":return isNull4(sqlCol);case"isNotNull":return isNotNull(sqlCol);default:return null}}function createEntityRoutes(app,config){let{db,schemaTables,schemaRelations,entities,logger:logger2,dbPool,storage,cdnMedia,authorization,authMode,idpUrl,featureAdminRoles}=config,getRegistry=()=>config.getTenantRegistry?config.getTenantRegistry():config.tenantRegistry??null,getReqSchema=(request)=>{let tenantRegistry=getRegistry();if(!tenantRegistry)return{tables:schemaTables,relations:schemaRelations};let schemaName=request.headers.get("x-tenant-schema");if(!schemaName)return{tables:schemaTables,relations:schemaRelations};let ctx=tenantRegistry.getSchemaContext(schemaName);return ctx?{tables:ctx.schemaTables,relations:ctx.schemaRelations}:{tables:schemaTables,relations:schemaRelations}},storageConfig=mergeStorageConfig(storage),warmUploadedMedia=(uploaded)=>{if(!cdnMedia||!uploaded||uploaded.success.length===0)return;scheduleUploadMediaProcessing(uploaded.success,{storagePath:storageConfig.basePath,media:cdnMedia,logger:logger2})},authEnabled=authorization?.enabled??!1,enforceFieldWrites=authEnabled&&authorization?.enforceFieldWriteClaims!==!1,isConsumerMode=authMode==="consumer",filterWritePayload=(payload,authResult)=>{if(!enforceFieldWrites||!authResult?.allowedFields)return payload;return filterResponseFields(payload,authResult.allowedFields)},auditPathContext=(request)=>{let url=new URL(request.url);return{path:url.pathname,query:url.search}},auditIdentityFor=(table,payload,userId,headers)=>createAuditIdentity(isAnonymousCreate(table,payload),userId,headers),UUID_RE=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i,auditEntityId=(id)=>{let s=id==null?"":String(id);return UUID_RE.test(s)?s:null},projectWrittenRow=(row,authResult)=>{let scoped2=authEnabled&&authResult?.allowedFields?filterResponseFields(row,[...authResult.allowedFields,...authResult.allowedRelations??[]]):row;return stripUnclaimedSensitive(redactSensitiveOutput(scoped2),authResult?.allowedFields)};if(!db)return app;function snakeToCamel4(s){return s.replace(/_([a-z])/g,(_,l)=>l.toUpperCase())}let{entities:mergedEntities}=resolveEntities(SYSTEM_TABLES,entities),extendedByName=new Map(mergedEntities.filter((t)=>entities.some((e)=>e.table_name===t.table_name&&e.extends===!0)).map((t)=>[t.table_name,t])),effectiveEntities=entities.map((e)=>extendedByName.get(e.table_name)??e),allTables=Object.keys(schemaTables),entityTableNames=effectiveEntities.map((e)=>snakeToCamel4(e.table_name)),systemTableNames=allTables.filter((tbl)=>!entityTableNames.includes(tbl)),systemTableDefMap=new Map(SYSTEM_TABLES.map((t)=>[snakeToCamel4(t.table_name),t])),asMeta=(def)=>def??void 0,hasUnmetRequirement=(def)=>{let requires=def?.requires;if(!requires||requires.length===0)return null;for(let req of requires)if(req==="email"&&!config.isEmailServiceAvailable?.())return"email";return null},deriveGroupName=(def,fallback)=>{if(def?.group_name)return def.group_name;let primaryFeature=def?.feature_set?.[0];if(!primaryFeature)return fallback;return primaryFeature.charAt(0).toUpperCase()+primaryFeature.slice(1)},systemTables2=systemTableNames.filter((name2)=>{let unmet=hasUnmetRequirement(asMeta(systemTableDefMap.get(name2)));if(unmet)return logger2.info(`Skipping ${name2} routes - missing required dependency: ${unmet}`),!1;return!0}).map((name2)=>{let def=systemTableDefMap.get(name2),meta=asMeta(def);return{table_name:def?.table_name??name2,group_name:deriveGroupName(meta,name2),is_form_data:def?.is_form_data===!0,bulk_endpoints_enabled:def?.bulk_endpoints_enabled??!1,excluded_methods:def?.excluded_methods?[...def.excluded_methods]:[],columns:def?.columns?[...def.columns]:void 0}}),allEntities=[...effectiveEntities,...systemTables2],publicGetEntities=new Set;for(let e of allEntities)if(e.is_public?.GET===!0)publicGetEntities.add(e.table_name),publicGetEntities.add(snakeToCamel4(e.table_name));logger2.info(`All entities: ${allEntities.map((e)=>e.table_name).join(", ")}`);for(let table of allEntities){let rawTableName=table.table_name,tableName=snakeToCamel4(rawTableName),swaggerTag=table.group_name||rawTableName,writeFields=enforceFieldWrites?table.columns?.map((c)=>c.name):void 0,defaultDrizzleTable=schemaTables[tableName];if(!defaultDrizzleTable)continue;let tableRelations=schemaRelations[`${tableName}Relations`];logger2.info(`Creating routes for table: ${tableName}`);let relationTargetTable={};{let rels=tableRelations;if(rels?.table&&typeof rels?.config==="function")try{let built=rels.config(createTableRelationsHelpers(rels.table));for(let[relName,rel]of Object.entries(built))if(rel?.referencedTable)relationTargetTable[relName]=getTableName(rel.referencedTable)}catch(err){logger2.warn(`[Authorization] Could not map relations for ${tableName}`,{error:err instanceof Error?err.message:String(err)})}}let defaultTableColumns=defaultDrizzleTable,defaultIdCol=defaultTableColumns.id,database=db,drizzleTable=defaultDrizzleTable,tableColumns=defaultTableColumns,idCol=defaultIdCol,resolveCol=(field)=>{if(Object.hasOwn(tableColumns,field))return tableColumns[field];let camel=snakeToCamel4(field);return Object.hasOwn(tableColumns,camel)?tableColumns[camel]:void 0},getTableForRequest=(request)=>{if(!getRegistry())return{drizzleTable,tableColumns,idCol,resolveCol,schemaTables,schemaRelations};let reqSchema=getReqSchema(request),reqTable=reqSchema.tables[tableName]||drizzleTable,reqCols=reqTable,reqIdCol=reqCols.id;return{drizzleTable:reqTable,tableColumns:reqCols,idCol:reqIdCol,resolveCol:(field)=>{if(Object.hasOwn(reqCols,field))return reqCols[field];let camel=snakeToCamel4(field);return Object.hasOwn(reqCols,camel)?reqCols[camel]:void 0},schemaTables:reqSchema.tables,schemaRelations:reqSchema.relations}},bulkUpdateSchema=createBulkUpdateSchema(table.columns),bulkDeleteSchema=Type.Array(Type.String()),authzLog=logger2.scoped("authorization.check"),performAuthCheck=async(request,method,reqFields,reqRelations)=>{let result=await runAuthCheck(request,method,reqFields,reqRelations);if(result){let userId=request.headers.get("x-user-id"),requestId=request.headers.get("x-request-id")||void 0;if(!result.authorized)authzLog.warn(`Denied ${method} ${table.table_name}`,{requestId,userId,entity:table.table_name,method,reason:result.reason||"no reason provided",mode:isConsumerMode?idpUrl?"idp":"jwt":"local"});else if(result.scopeFilters||result.allowedFields||result.allowedRelations)authzLog.debug(`Allowed ${method} ${table.table_name} (restricted)`,{requestId,userId,entity:table.table_name,method,scopeFilters:result.scopeFilters,allowedFieldCount:result.allowedFields?.length,allowedRelations:result.allowedRelations})}return result},scopeConditionsFor=(authResult,resolveColumn)=>{if(!authEnabled||!authResult?.scopeFilters)return[];let{conditions,unresolvedFields}=buildScopeConditions(authResult.scopeFilters,resolveColumn);if(unresolvedFields.length>0)authzLog.error(`Scope references unknown column(s) on ${table.table_name}; failing closed (0 rows)`,{entity:table.table_name,unresolvedFields});return conditions},runAuthCheck=async(request,method,reqFields,reqRelations,entityName=table.table_name)=>{let userId=request.headers.get("x-user-id");if(!authEnabled||!userId)return null;if(isConsumerMode){let userClaims=decodeHeaderList(request.headers.get("x-user-claims")),userRoles=decodeHeaderList(request.headers.get("x-user-roles")),userClaimScopes=decodeClaimScopesHeader(request.headers.get("x-user-claim-scopes"));if(idpUrl&&authorization?.jwtClaimsMode==="resolve"){let accessToken=request.headers.get("x-access-token")||(request.headers.get("cookie")||"").match(/access_token=([^;]+)/)?.[1]||"";return checkAuthorizationViaIDP({idpUrl,accessToken,method,entity:entityName,requestedFields:reqFields,requestedRelations:reqRelations,logger:logger2})}return checkAuthorizationFromJWT({userClaims,userRoles,method,entity:entityName,requestedFields:reqFields,requestedRelations:reqRelations,logger:logger2,requireClaimSpecificity:authorization?.requireClaimSpecificity,claimScopes:userClaimScopes})}let reqCtx=getTableForRequest(request);return checkAuthorization({userId,method,entity:entityName,requestedFields:reqFields,requestedRelations:reqRelations,db:database,schemaTables:reqCtx.schemaTables,logger:logger2,requireClaimSpecificity:authorization?.requireClaimSpecificity,getUserData:async()=>{if(!database)return;let usersTbl=reqCtx.schemaTables[AUTHORIZATION_TABLE_KEYS.users];if(!usersTbl)return;let idCol2=usersTbl.id;if(!idCol2)return;return(await database.select().from(usersTbl).where(eq32(idCol2,userId)).limit(1))[0]}})},endSessionsForDeactivated=async(request,changed)=>{if(tableName!==AUTHORIZATION_TABLE_KEYS.users)return;let userIds=changed.filter((one)=>one.after?.isActive===!1&&one.before?.isActive!==!1).map((one)=>String(one.after?.id??"").trim()).filter(Boolean);if(userIds.length===0)return;let authTables=resolveAuthTablesForRequest(request,{usersTable:schemaTables[AUTHORIZATION_TABLE_KEYS.users],sessionsTable:schemaTables.userSessions??schemaTables.sessions,schemaTables,tenantRegistry:config.tenantRegistry,getTenantRegistry:config.getTenantRegistry});await revokeUserSessions({db:database,sessionsTable:authTables.sessionsTable,onError:(error3)=>logger2.error("[Entity] users rows were deactivated but their open sessions were not cleared",{error:error3,userIds})},userIds)},declared=[],entityGuards=[],listLog=logger2.scoped("entity.list"),isRbacTable=tableName===AUTHORIZATION_TABLE_KEYS.roles||tableName===AUTHORIZATION_TABLE_KEYS.claims||tableName===AUTHORIZATION_TABLE_KEYS.roleClaims,isUserRoleTable=tableName===AUTHORIZATION_TABLE_KEYS.userRoles,roleAssignRoles=featureAdminRoles?.authorization??[];if(isRbacTable||isUserRoleTable||isSensitiveSystemTable(tableName)){let denyMessage=isRbacTable?"Forbidden: managing roles and claims requires godmin privileges":isUserRoleTable?"Forbidden: assigning roles requires godmin privileges":`Forbidden: writing to '${tableName}' via the generic entity API requires godmin privileges \u2014 use the dedicated route`;entityGuards.push(async({request,set:set2,body,params})=>{let method=request.method.toUpperCase();if(method==="GET"||method==="HEAD"||method==="OPTIONS")return;if(hasGodminRole(request))return;if(isFeatureAdmin({isRbacTable:isRbacTable||isUserRoleTable,featureSet:table.feature_set,featureAdminRoles,callerRoles:decodeHeaderList(request.headers.get("x-user-roles"))}))return;if(isUserRoleTable&&roleAssignRoles.length>0){let subject=await resolveUserRoleSubject({db,schemaTables:getReqSchema(request).tables,logger:logger2},{rowId:typeof params?.id==="string"?params.id:null,body}),decision=decideRoleAssign({callerIsGod:!1,callerRoleNames:decodeHeaderList(request.headers.get("x-user-roles")),allowRoles:roleAssignRoles,targetRoleName:subject.targetRoleName??GODMIN_ROLE_NAME,subjectIsGodmin:subject.subjectIsGodmin,godminRoleName:GODMIN_ROLE_NAME});if(decision.allowed)return;if(await userHasGodminRoleInDb(db,schemaTables,request.headers.get("x-user-id"),logger2))return;return set2.status=403,{success:!1,reason:decision.reason,message:ROLE_ASSIGN_REFUSALS[decision.reason]}}if(await userHasGodminRoleInDb(db,schemaTables,request.headers.get("x-user-id"),logger2))return;return set2.status=403,{success:!1,message:denyMessage}})}if(!table.excluded_methods?.includes("GET"))declared.push(defineRoute({method:"GET",path:"/",detail:{tags:[swaggerTag],summary:`List ${tableName}`,description:`Get paginated list of ${tableName} records with filtering, sorting, and search`},handler:async(ctx)=>{if(!database)return ctx.set.status=500,{success:!1,message:"DB not initialized"};let{drizzleTable:drizzleTable2,resolveCol:resolveCol2,schemaRelations:schemaRelations2}=getTableForRequest(ctx.request),requestedFields=table.columns?.map((c)=>c.name),requestedRelations=Object.keys(schemaRelations2).filter((k)=>k.startsWith(`${tableName}Relations`)).map((k)=>k.replace("Relations","")),authResult=await performAuthCheck(ctx.request,"GET",requestedFields,requestedRelations);if(authResult&&!authResult.authorized)return ctx.set.status=403,{success:!1,message:authResult.reason||"Forbidden"};let fieldQueryable=(field)=>{if(isSensitiveOutputKey(field))return!1;if(!sensitiveFieldQueryable(field,{authEnabled,allowedFields:authResult?.allowedFields}))return!1;if(authEnabled&&authResult?.allowedFields&&!authResult.allowedFields.includes(field))return!1;return!0},q=parseQueryParams(ctx.query);if(q.malformed)return ctx.set.status=400,{success:!1,message:`Malformed query parameter: ${q.malformed.join(", ")} (expected a JSON array)`};let conditions=[];if(conditions.push(...scopeConditionsFor(authResult,resolveCol2)),q.search&&!q.searchFields)return ctx.set.status=400,{success:!1,message:"A 'search' needs 'searchFields' naming the columns to search, e.g. searchFields=name,email \u2014 without it the term would be ignored and every row returned."};if(q.search&&q.searchFields){for(let f of q.searchFields)if(!fieldQueryable(f.trim()))return ctx.set.status=403,{success:!1,message:`Cannot search on field: ${f.trim()}`};let unknown3=firstUnknownField(q.searchFields,resolveCol2);if(unknown3)return ctx.set.status=400,{success:!1,message:`Unknown field in search: ${unknown3}`};let searchColumns=q.searchFields.map((f)=>resolveCol2(f.trim())).filter((c)=>c!==null),searchCondition=buildSearchCondition(searchColumns,q.search);if(searchCondition)conditions.push(searchCondition)}if(Array.isArray(q.filters))for(let filter of q.filters){let referenced=collectFilterFields(filter);for(let f of referenced)if(!fieldQueryable(f))return ctx.set.status=403,{success:!1,message:`Cannot filter on field: ${f}`};let unknownFilterField=firstUnknownField(referenced,resolveCol2);if(unknownFilterField)return ctx.set.status=400,{success:!1,message:`Unknown field in filter: ${unknownFilterField}`};let unusable=firstUnusableCondition(filter);if(unusable)return ctx.set.status=400,{success:!1,message:unusable};let cond=buildFilterCondition(filter,resolveCol2);if(cond)conditions.push(cond)}let projection;if(q.select&&q.select.length>0){let requested=[...new Set(["id",...q.select.map((f)=>f.trim())])].filter(Boolean),columns={};for(let field of requested){if(!fieldQueryable(field))return ctx.set.status=403,{success:!1,message:`Cannot select field: ${field}`};let col8=resolveCol2(field);if(!col8)return ctx.set.status=400,{success:!1,message:`Unknown field in select: ${field}`};columns[field]=col8}if(Object.keys(columns).length>0)projection=columns}let unprojected=database.select().from(drizzleTable2),baseQuery=projection?database.select(projection).from(drizzleTable2):unprojected;if(conditions.length>0){let combined=and18(...conditions);if(combined)baseQuery=baseQuery.where(combined)}if(q.sort&&q.sort.length>0){let orderClauses=[];for(let s of q.sort){let field=typeof s?.field==="string"?s.field.trim():"";if(field==="")return ctx.set.status=400,{success:!1,message:'Malformed query parameter: sort (every entry needs a "field")'};if(!fieldQueryable(field))return ctx.set.status=403,{success:!1,message:`Cannot sort on field: ${field}`};let col8=resolveCol2(field);if(!col8)return ctx.set.status=400,{success:!1,message:`Unknown field in sort: ${field}`};orderClauses.push(s.direction==="desc"?desc5(col8):asc(col8))}if(orderClauses.length>0)baseQuery=baseQuery.orderBy(...orderClauses)}let page=q.page??1,limit=q.limit??20,offset=q.offset??(page-1)*limit;if(q.groupBy){let field=q.groupBy.trim();if(!fieldQueryable(field))return ctx.set.status=403,{success:!1,message:`Cannot group by field: ${field}`};let groupCol=resolveCol2(field);if(!groupCol)return ctx.set.status=400,{success:!1,message:`Unknown field in groupBy: ${field}`};let where=conditions.length>0?and18(...conditions):void 0,col8=groupCol,distinctCount=await database.select({n:sql8`count(distinct ${col8})::int`}).from(drizzleTable2).where(where),totalGroups=Number(distinctCount[0]?.n??0);return{success:!0,data:{items:(await database.select({value:col8,count:sql8`count(*)::int`}).from(drizzleTable2).where(where).groupBy(col8).orderBy(sql8`count(*) desc`,col8).limit(limit).offset(offset)).map((g)=>({value:g.value,count:Number(g.count)})),meta:buildPaginationMeta(page,limit,offset,totalGroups)}}}let queryStart=performance.now(),combinedCount=conditions.length>0?and18(...conditions):void 0,countResult=await database.select({n:sql8`count(*)::int`}).from(drizzleTable2).where(combinedCount),totalItems=Number(countResult[0]?.n??0);baseQuery=baseQuery.limit(limit).offset(offset);let items=await baseQuery,meta=buildPaginationMeta(page,limit,offset,totalItems);if(listLog.debug(`List ${rawTableName}: ${items.length}/${totalItems} rows`,{requestId:ctx.request.headers.get("x-request-id")||void 0,table:rawTableName,page,limit,totalItems,returned:items.length,conditionCount:conditions.length,search:q.search||void 0,sort:Array.isArray(q.sort)?q.sort.map((s)=>`${s.field}:${s.direction}`):void 0,queryMs:Math.round(performance.now()-queryStart)}),authEnabled&&authResult?.allowedFields)items=filterResponseFields(items,authResult.allowedFields);return{success:!0,data:{items:items.map((r)=>stripUnclaimedSensitive(redactSensitiveOutput(r),authResult?.allowedFields)),meta}}}})),declared.push(defineRoute({method:"GET",path:"/:id",detail:{tags:[swaggerTag],summary:`Get ${tableName} by ID`,description:`Get a single ${tableName} record by its ID with optional relations`},handler:async(ctx)=>{let{drizzleTable:drizzleTable2,idCol:idCol2,schemaTables:schemaTables2,schemaRelations:schemaRelations2,resolveCol:resolveCol2}=getTableForRequest(ctx.request);if(!database||!idCol2)return ctx.set.status=500,{success:!1,message:"No id column or DB"};let params=ctx.params,q=parseQueryParams(ctx.query),requestedFields=table.columns?.map((c)=>c.name),requestedRelations=q.with?.map((w)=>w.name),authResult=await performAuthCheck(ctx.request,"GET",requestedFields,requestedRelations);if(authResult&&!authResult.authorized)return ctx.set.status=403,{success:!1,message:authResult.reason||"Forbidden"};if(q.malformed?.includes("with"))return ctx.set.status=400,{success:!1,message:"Malformed query parameter: with (expected a JSON array)"};let getScope=scopeConditionsFor(authResult,resolveCol2),getWhere=getScope.length>0?and18(eq32(idCol2,params.id),...getScope):eq32(idCol2,params.id);if(q.with&&q.with.length>0&&tableRelations&&dbPool){let allowedWith=authEnabled&&authResult?.allowedRelations?q.with.filter((w)=>authResult.allowedRelations?.includes(w.name)??!1):q.with,dbWithRelations=drizzle(dbPool,{schema:{...schemaTables2,...schemaRelations2}}),withClause={};for(let rel of allowedWith){let targetTable=relationTargetTable[rel.name],relAuth=authEnabled&&targetTable?await runAuthCheck(ctx.request,"GET",void 0,void 0,targetTable):null,decision=decideRelationExpansion({authEnabled,targetTable,relAuth,isTargetPublicGet:targetTable?publicGetEntities.has(targetTable):!1});if(!decision.expand)continue;let relScope=decision.scope;if(relScope&&Object.keys(relScope).length>0)withClause[rel.name]={...rel.limit?{limit:rel.limit}:{},where:(fields,ops)=>{let preds=[],unresolved=!1;for(let[k,v]of Object.entries(relScope)){let col8=resolveRelationScopeColumn(fields,k,snakeToCamel4);if(col8)preds.push(ops.eq(col8,v));else unresolved=!0}if(unresolved)return sql8`1 = 0`;return preds.length>0?ops.and(...preds):void 0}};else withClause[rel.name]=rel.limit?{limit:rel.limit}:!0}let result2=await dbWithRelations.query[tableName]?.findFirst({where:getWhere,with:withClause});if(!result2)return ctx.set.status=404,{success:!1,message:`${tableName} not found`};if(authEnabled&&authResult?.allowedFields)result2=filterResponseFields(result2,[...authResult.allowedFields,...authResult.allowedRelations??[]]);if(authEnabled&&authResult?.allowedRelations)result2=filterResponseRelations(result2,authResult.allowedRelations);return{success:!0,data:stripUnclaimedSensitive(redactSensitiveOutput(result2),authResult?.allowedFields)}}let result=(await database.select().from(drizzleTable2).where(getWhere))[0]||null;if(!result)return ctx.set.status=404,{success:!1,message:`${tableName} not found`};if(authEnabled&&authResult?.allowedFields)result=filterResponseFields(result,authResult.allowedFields);return{success:!0,data:stripUnclaimedSensitive(redactSensitiveOutput(result),authResult?.allowedFields)}}})),declared.push(defineRoute({method:"GET",path:"/distinct/:field",detail:{tags:[swaggerTag],summary:`Get distinct ${tableName} values`,description:`Get distinct values for a specific field in ${tableName}`},handler:async(ctx)=>{let{drizzleTable:drizzleTable2,resolveCol:resolveCol2}=getTableForRequest(ctx.request);if(!database)return ctx.set.status=500,{success:!1,message:"DB not initialized"};let params=ctx.params,authResult=await performAuthCheck(ctx.request,"GET",[params.field],[]);if(authResult&&!authResult.authorized)return ctx.set.status=403,{success:!1,message:authResult.reason||"Forbidden"};if(authEnabled&&authResult?.allowedFields&&!authResult.allowedFields.includes(params.field))return ctx.set.status=403,{success:!1,message:"Forbidden: field not permitted"};if(isSensitiveOutputKey(params.field))return ctx.set.status=403,{success:!1,message:"Forbidden: field not permitted"};if(!sensitiveFieldQueryable(params.field,{authEnabled,allowedFields:authResult?.allowedFields}))return ctx.set.status=403,{success:!1,message:"Forbidden: field not permitted"};let col8=resolveCol2(params.field);if(!col8)return ctx.set.status=404,{success:!1,message:"Field not found"};let distinctScope=scopeConditionsFor(authResult,resolveCol2),distinctQuery=database.selectDistinct({value:col8}).from(drizzleTable2);return{success:!0,data:distinctScope.length>0?await distinctQuery.where(and18(...distinctScope)):await distinctQuery}}}));if(!table.excluded_methods?.includes("POST"))if(table.is_form_data&&storageConfig.enabled)declared.push(defineRoute({method:"POST",path:"/",type:"formdata",body:Type.Object({[storageConfig.formData.dataField]:Type.Optional(Type.Union([Type.String(),Type.Any()])),[storageConfig.formData.filesField]:Type.Optional(FilesSchema())}),detail:{tags:[swaggerTag],summary:`Create ${tableName} with files`,description:`Create a new ${tableName} record with file upload support`},handler:async(ctx)=>{let{drizzleTable:drizzleTable2,resolveCol:resolveCol2}=getTableForRequest(ctx.request);if(!database)return ctx.set.status=500,{success:!1,message:"DB not initialized"};let userId=ctx.request.headers.get("x-user-id"),postFormAuthResult=await performAuthCheck(ctx.request,"POST",writeFields);if(postFormAuthResult&&!postFormAuthResult.authorized)return ctx.set.status=403,{success:!1,message:postFormAuthResult.reason||"Forbidden"};let{data,files}=parseFormDataBody(ctx.body,storageConfig),payload=data,uploadedFiles=null;if(files.length>0){if(uploadedFiles=await uploadFiles(files,storageConfig,table.table_name),uploadedFiles.failed.length>0&&uploadedFiles.success.length===0){let allRefused=uploadedFiles.failed.every((one)=>one.rejected===!0),aboutType=allRefused&&uploadedFiles.failed.some((one)=>one.error.includes("File type"));return ctx.set.status=allRefused?aboutType?415:400:500,{success:!1,message:allRefused?"File refused":"File upload failed",errors:uploadedFiles.failed}}warmUploadedMedia(uploadedFiles)}let mergeUploadedMetadata=()=>{let firstFile=uploadedFiles?.success[0];if(firstFile)payload={...payload,...buildFileRecordPayload(firstFile,userId)}};if(table.columns)payload=sanitizePayload(payload,table.columns,{isFormData:table.is_form_data===!0}),payload=filterWritePayload(payload,postFormAuthResult),mergeUploadedMetadata();else mergeUploadedMetadata();let scopedForm=forceCreateScope(payload,postFormAuthResult,resolveCol2);if(!scopedForm.ok)return ctx.set.status=403,{success:!1,message:`Forbidden: ownership scope references a column '${scopedForm.field}' this entity does not have`};if(table.columns){let validation=validatePayload(payload,table.columns,!1);if(!validation.valid)return ctx.set.status=400,{success:!1,message:"Validation failed",errors:validation.errors}}let anonymous=isAnonymousCreate(table,payload);if(userId&&!anonymous)payload.createdBy=userId;let result=await database.insert(drizzleTable2).values(payload).returning();{let reqUrl=new URL(ctx.request.url);logger2.audit({entityName:table.table_name,entityId:String(result[0]?.id??""),operation:"CREATE",summary:`Created ${table.table_name}`,newValues:result[0],...createAuditIdentity(anonymous,userId,ctx.request.headers),path:reqUrl.pathname,query:reqUrl.search})}return{success:!0,data:stripUnclaimedSensitive(redactSensitiveOutput(result[0]),postFormAuthResult?.allowedFields)}}}));else declared.push(defineRoute({method:"POST",path:"/",body:Type.Object({},{additionalProperties:!0}),detail:{tags:[swaggerTag],summary:`Create ${tableName}`,description:`Create a new ${tableName} record`},handler:async(ctx)=>{let{drizzleTable:drizzleTable2,schemaTables:schemaTables2,resolveCol:resolveCol2}=getTableForRequest(ctx.request);if(!database)return ctx.set.status=500,{success:!1,message:"DB not initialized"};let payload=ctx.body,userId=ctx.request.headers.get("x-user-id"),postAuthResult=await performAuthCheck(ctx.request,"POST",writeFields);if(postAuthResult&&!postAuthResult.authorized)return ctx.set.status=403,{success:!1,message:postAuthResult.reason||"Forbidden"};if(table.columns)payload=sanitizePayload(payload,table.columns,{isFormData:table.is_form_data===!0}),payload=filterWritePayload(payload,postAuthResult);let scoped2=forceCreateScope(payload,postAuthResult,resolveCol2);if(!scoped2.ok)return ctx.set.status=403,{success:!1,message:`Forbidden: ownership scope references a column '${scoped2.field}' this entity does not have`};if(table.columns){let validation=validatePayload(payload,table.columns,!1);if(!validation.valid)return ctx.set.status=400,{success:!1,message:"Validation failed",errors:validation.errors}}let anonymous=isAnonymousCreate(table,payload);if(userId&&!anonymous)payload.createdBy=userId;if(tableName===AUTHORIZATION_TABLE_KEYS.userRoles&&payload.userId&&payload.roleId){let cols=drizzleTable2,existing=await database.select().from(drizzleTable2).where(and18(eq32(cols.userId,payload.userId),eq32(cols.roleId,payload.roleId))).limit(1);if(existing.length>0)return{success:!0,data:existing[0]}}let result=await database.insert(drizzleTable2).values(payload).returning();if(tableName===AUTHORIZATION_TABLE_KEYS.roleClaims&&config.claimsCache)config.claimsCache.invalidate().catch(()=>{});if(tableName===AUTHORIZATION_TABLE_KEYS.userRoles&&payload.roleId&&payload.userId)try{let rolesTable=schemaTables2[AUTHORIZATION_TABLE_KEYS.roles],usersTable=schemaTables2[AUTHORIZATION_TABLE_KEYS.users];if(rolesTable&&usersTable){let roleName=(await database.select().from(rolesTable).where(eq32(rolesTable.id,payload.roleId)).limit(1))[0]?.name;if(roleName===GODMIN_ROLE_NAME)await database.update(usersTable).set({isGod:!0}).where(eq32(usersTable.id,payload.userId));if(roleName)config.onRoleChanged?.({userId:String(payload.userId),roleName,assigned:!0,actorId:userId??void 0})}}catch(_e){logger2.warn("[Entity] Failed to sync is_god flag on userRole create")}{let reqUrl=new URL(ctx.request.url);logger2.audit({entityName:table.table_name,entityId:String(result[0]?.id??""),operation:"CREATE",summary:`Created ${table.table_name}`,newValues:result[0],...createAuditIdentity(anonymous,userId,ctx.request.headers),path:reqUrl.pathname,query:reqUrl.search})}return{success:!0,data:stripUnclaimedSensitive(redactSensitiveOutput(result[0]),postAuthResult?.allowedFields)}}}));if(!table.excluded_methods?.includes("PUT"))if(table.is_form_data&&storageConfig.enabled)declared.push(defineRoute({method:"PUT",path:"/:id",type:"formdata",body:Type.Object({[storageConfig.formData.dataField]:Type.Optional(Type.Union([Type.String(),Type.Any()])),[storageConfig.formData.filesField]:Type.Optional(FilesSchema())}),detail:{tags:[swaggerTag],summary:`Update ${tableName} with files`,description:`Full update of ${tableName} record with file upload support`},handler:async(ctx)=>{let{drizzleTable:drizzleTable2,idCol:idCol2,resolveCol:resolveCol2}=getTableForRequest(ctx.request);if(!database||!idCol2)return ctx.set.status=500,{success:!1,message:"No id column or DB"};let params=ctx.params,userId=ctx.request.headers.get("x-user-id"),putFormAuthResult=await performAuthCheck(ctx.request,"PUT",writeFields);if(putFormAuthResult&&!putFormAuthResult.authorized)return ctx.set.status=403,{success:!1,message:putFormAuthResult.reason||"Forbidden"};let putFormScope=scopeConditionsFor(putFormAuthResult,resolveCol2),putFormWhere=putFormScope.length>0?and18(eq32(idCol2,params.id),...putFormScope):eq32(idCol2,params.id),{data,files}=parseFormDataBody(ctx.body,storageConfig),payload=data,oldRecord=await database.select().from(drizzleTable2).where(putFormWhere).limit(1);if(oldRecord.length===0)return ctx.set.status=404,{success:!1,message:`${tableName} not found`};if(table.columns){payload=sanitizePayload(payload,table.columns,{isFormData:table.is_form_data===!0}),payload=filterWritePayload(payload,putFormAuthResult);let validation=validatePayload(payload,table.columns,!1);if(!validation.valid)return ctx.set.status=400,{success:!1,message:"Validation failed",errors:validation.errors}}let uploadedFiles=null;if(files.length>0)uploadedFiles=await uploadFiles(files,storageConfig,table.table_name),warmUploadedMedia(uploadedFiles);let result=await database.update(drizzleTable2).set(payload).where(putFormWhere).returning();{let reqUrl=new URL(ctx.request.url);logger2.audit({entityName:table.table_name,entityId:params.id,operation:"UPDATE",userId:userId||void 0,summary:`Updated ${table.table_name} (${params.id})`,oldValues:oldRecord[0],newValues:result[0],ipAddress:ctx.request.headers.get("x-forwarded-for")?.split(",")[0]?.trim()||ctx.request.headers.get("x-real-ip")?.trim()||"unknown",userAgent:ctx.request.headers.get("user-agent")||"unknown",path:reqUrl.pathname,query:reqUrl.search})}return{success:!0,data:{record:result[0],files:uploadedFiles?.success||[],fileErrors:uploadedFiles?.failed||[]}}}}));else declared.push(defineRoute({method:"PUT",path:"/:id",body:Type.Object({},{additionalProperties:!0}),detail:{tags:[swaggerTag],summary:`Update ${tableName}`,description:`Full update of ${tableName} record`},handler:async(ctx)=>{let{drizzleTable:drizzleTable2,idCol:idCol2,resolveCol:resolveCol2}=getTableForRequest(ctx.request);if(!database||!idCol2)return ctx.set.status=500,{success:!1,message:"No id column or DB"};let{params,body:payload}=ctx,userId=ctx.request.headers.get("x-user-id"),putAuthResult=await performAuthCheck(ctx.request,"PUT",writeFields);if(putAuthResult&&!putAuthResult.authorized)return ctx.set.status=403,{success:!1,message:putAuthResult.reason||"Forbidden"};let putScope=scopeConditionsFor(putAuthResult,resolveCol2),putWhere=putScope.length>0?and18(eq32(idCol2,params.id),...putScope):eq32(idCol2,params.id),oldRecord=await database.select().from(drizzleTable2).where(putWhere).limit(1);if(oldRecord.length===0)return ctx.set.status=404,{success:!1,message:`${tableName} not found`};if(table.columns){payload=sanitizePayload(payload,table.columns,{isFormData:table.is_form_data===!0}),payload=filterWritePayload(payload,putAuthResult);let validation=validatePayload(payload,table.columns,!1);if(!validation.valid)return ctx.set.status=400,{success:!1,message:"Validation failed",errors:validation.errors}}if(payload.updatedAt=new Date,userId)payload.updatedBy=userId;let result=await database.update(drizzleTable2).set(payload).where(putWhere).returning();await endSessionsForDeactivated(ctx.request,[{before:oldRecord[0],after:result[0]}]);{let reqUrl=new URL(ctx.request.url);logger2.audit({entityName:table.table_name,entityId:params.id,operation:"UPDATE",userId:userId||void 0,summary:`Updated ${table.table_name} (${params.id})`,oldValues:oldRecord[0],newValues:result[0],ipAddress:ctx.request.headers.get("x-forwarded-for")?.split(",")[0]?.trim()||ctx.request.headers.get("x-real-ip")?.trim()||"unknown",userAgent:ctx.request.headers.get("user-agent")||"unknown",path:reqUrl.pathname,query:reqUrl.search})}return{success:!0,data:stripUnclaimedSensitive(redactSensitiveOutput(result[0]),putAuthResult?.allowedFields)}}}));if(!table.excluded_methods?.includes("PATCH"))declared.push(defineRoute({method:"PATCH",path:"/:id",body:Type.Object({},{additionalProperties:!0}),detail:{tags:[swaggerTag],summary:`Patch ${tableName}`,description:`Partial update of ${tableName} record`},handler:async(ctx)=>{let{drizzleTable:drizzleTable2,idCol:idCol2,resolveCol:resolveCol2}=getTableForRequest(ctx.request);if(!database||!idCol2)return ctx.set.status=500,{success:!1,message:"No id column or DB"};let{params,body:payload}=ctx,userId=ctx.request.headers.get("x-user-id"),patchAuthResult=await performAuthCheck(ctx.request,"PATCH",writeFields);if(patchAuthResult&&!patchAuthResult.authorized)return ctx.set.status=403,{success:!1,message:patchAuthResult.reason||"Forbidden"};let patchScope=scopeConditionsFor(patchAuthResult,resolveCol2),patchWhere=patchScope.length>0?and18(eq32(idCol2,params.id),...patchScope):eq32(idCol2,params.id),oldRecord=await database.select().from(drizzleTable2).where(patchWhere).limit(1);if(oldRecord.length===0)return ctx.set.status=404,{success:!1,message:`${tableName} not found`};if(table.columns){payload=sanitizePayload(payload,table.columns,{isFormData:table.is_form_data===!0}),payload=filterWritePayload(payload,patchAuthResult);let validation=validatePayload(payload,table.columns,!0);if(!validation.valid)return ctx.set.status=400,{success:!1,message:"Validation failed",errors:validation.errors}}if(payload.updatedAt=new Date,userId)payload.updatedBy=userId;let result=await database.update(drizzleTable2).set(payload).where(patchWhere).returning();await endSessionsForDeactivated(ctx.request,[{before:oldRecord[0],after:result[0]}]);{let reqUrl=new URL(ctx.request.url);logger2.audit({entityName:table.table_name,entityId:params.id,operation:"PATCH",userId:userId||void 0,summary:`Patched ${table.table_name} (${params.id})`,oldValues:oldRecord[0],newValues:result[0],ipAddress:ctx.request.headers.get("x-forwarded-for")?.split(",")[0]?.trim()||ctx.request.headers.get("x-real-ip")?.trim()||"unknown",userAgent:ctx.request.headers.get("user-agent")||"unknown",path:reqUrl.pathname,query:reqUrl.search})}return{success:!0,data:stripUnclaimedSensitive(redactSensitiveOutput(result[0]),patchAuthResult?.allowedFields)}}}));if(!table.excluded_methods?.includes("DELETE"))declared.push(defineRoute({method:"DELETE",path:"/:id",detail:{tags:[swaggerTag],summary:`Delete ${tableName}`,description:`Delete a ${tableName} record`},handler:async(ctx)=>{let{drizzleTable:drizzleTable2,idCol:idCol2,schemaTables:schemaTables2,resolveCol:resolveCol2}=getTableForRequest(ctx.request);if(!database||!idCol2)return ctx.set.status=500,{success:!1,message:"No id column or DB"};let params=ctx.params,userId=ctx.request.headers.get("x-user-id"),deleteAuthResult=await performAuthCheck(ctx.request,"DELETE");if(deleteAuthResult&&!deleteAuthResult.authorized)return ctx.set.status=403,{success:!1,message:deleteAuthResult.reason||"Forbidden"};let deleteScope=scopeConditionsFor(deleteAuthResult,resolveCol2),deleteWhere=deleteScope.length>0?and18(eq32(idCol2,params.id),...deleteScope):eq32(idCol2,params.id),oldRecord=await database.select().from(drizzleTable2).where(deleteWhere).limit(1);if(oldRecord.length===0)return ctx.set.status=404,{success:!1,message:`${tableName} not found`};if(await activeVerificationExists(database,schemaTables2,tableName,params.id))return ctx.set.status=409,{success:!1,message:`Cannot delete: this ${tableName} is in an active verification. Complete or reject it first.`};if(await database.delete(drizzleTable2).where(deleteWhere),table.is_form_data&&storageConfig.enabled&&oldRecord[0]){let rec=oldRecord[0],filePath=rec.path,fileName=rec.name;if(typeof filePath==="string"&&typeof fileName==="string"){if(!await deleteFile(filePath,fileName,storageConfig.basePath))logger2.warn(`[Entity] Blob cleanup failed for ${table.table_name} (${params.id})`)}}if(oldRecord[0]){let link=accountLinkFor(config.accountLinks,tableName),userIds=link?accountIdsIn([oldRecord[0]],link.userColumn):[];if(userIds.length>0){let authTables=resolveAuthTablesForRequest(ctx.request,{usersTable:schemaTables2[AUTHORIZATION_TABLE_KEYS.users],sessionsTable:schemaTables2.userSessions??schemaTables2.sessions,schemaTables:schemaTables2,tenantRegistry:config.tenantRegistry,getTenantRegistry:config.getTenantRegistry}),result=await closeAccounts({db:database,usersTable:authTables.usersTable,sessionsTable:authTables.sessionsTable,onError:(error3)=>logger2.error(`[Entity] ${tableName} ${params.id} was deleted but its account could not be closed`,{error:error3})},userIds);if(result.closed>0&&!result.signedOut)logger2.warn(`[Entity] Account behind ${tableName} ${params.id} was deactivated but its open sessions were not cleared`)}}if(tableName===AUTHORIZATION_TABLE_KEYS.roleClaims&&config.claimsCache)config.claimsCache.invalidate().catch(()=>{});if(tableName===AUTHORIZATION_TABLE_KEYS.userRoles&&oldRecord[0])try{let deletedUserRole=oldRecord[0],rolesTable=schemaTables2[AUTHORIZATION_TABLE_KEYS.roles],usersTable=schemaTables2[AUTHORIZATION_TABLE_KEYS.users];if(rolesTable&&usersTable&&deletedUserRole.roleId&&deletedUserRole.userId){let roleName=(await database.select().from(rolesTable).where(eq32(rolesTable.id,deletedUserRole.roleId)).limit(1))[0]?.name;if(roleName===GODMIN_ROLE_NAME)await database.update(usersTable).set({isGod:!1}).where(eq32(usersTable.id,deletedUserRole.userId));if(roleName)config.onRoleChanged?.({userId:String(deletedUserRole.userId),roleName,assigned:!1,actorId:ctx.request.headers.get("x-user-id")??void 0})}}catch(_e){logger2.warn("[Entity] Failed to sync is_god flag on userRole delete")}{let reqUrl=new URL(ctx.request.url);logger2.audit({entityName:table.table_name,entityId:params.id,operation:"DELETE",userId:userId||void 0,summary:`Deleted ${table.table_name} (${params.id})`,oldValues:oldRecord[0],ipAddress:ctx.request.headers.get("x-forwarded-for")?.split(",")[0]?.trim()||ctx.request.headers.get("x-real-ip")?.trim()||"unknown",userAgent:ctx.request.headers.get("user-agent")||"unknown",path:reqUrl.pathname,query:reqUrl.search})}return{success:!0,data:null}}}));if(table.bulk_endpoints_enabled){if(!table.excluded_methods?.includes("POST"))declared.push(defineRoute({method:"POST",path:"/bulk",body:Type.Array(Type.Object({},{additionalProperties:!0})),detail:{tags:[swaggerTag],summary:`Bulk create ${tableName}`,description:`Create multiple ${tableName} records`},handler:async(ctx)=>{let{drizzleTable:drizzleTable2,resolveCol:resolveCol2}=getTableForRequest(ctx.request);if(!database)return ctx.set.status=500,{success:!1,message:"DB not initialized"};let items=ctx.body;if(!Array.isArray(items))return ctx.set.status=400,{success:!1,message:"Body must be an array"};if(items.length>MAX_BULK_ITEMS)return ctx.set.status=400,{success:!1,message:`Bulk request exceeds ${MAX_BULK_ITEMS} items`};let userId=ctx.request.headers.get("x-user-id"),bulkPostAuth=await performAuthCheck(ctx.request,"POST",writeFields);if(bulkPostAuth&&!bulkPostAuth.authorized)return ctx.set.status=403,{success:!1,message:bulkPostAuth.reason||"Forbidden"};let sanitizedItems=[];for(let raw of items){let payload=raw;if(table.columns)payload=sanitizePayload(payload,table.columns,{isFormData:table.is_form_data===!0}),payload=filterWritePayload(payload,bulkPostAuth);let scopedItem=forceCreateScope(payload,bulkPostAuth,resolveCol2);if(!scopedItem.ok)return ctx.set.status=403,{success:!1,message:`Forbidden: ownership scope references a column '${scopedItem.field}' this entity does not have`};if(table.columns){let validation=validatePayload(payload,table.columns,!1);if(!validation.valid)return ctx.set.status=400,{success:!1,message:"Validation failed",errors:validation.errors}}if(userId&&!isAnonymousCreate(table,payload))payload.createdBy=userId;sanitizedItems.push(payload)}let txResult=await database.transaction(async(tx)=>{let results=[];for(let payload of sanitizedItems){let result=await tx.insert(drizzleTable2).values(payload).returning();results.push(result[0])}return results}),auditPath=auditPathContext(ctx.request);for(let[i,row]of txResult.entries())await logger2.audit({entityName:table.table_name,entityId:auditEntityId(row.id),operation:"CREATE",summary:`Bulk created ${table.table_name} (${row.id})`,newValues:row,...auditIdentityFor(table,sanitizedItems[i]??{},userId,ctx.request.headers),...auditPath});return{success:!0,data:txResult.map((r)=>projectWrittenRow(r,bulkPostAuth))}}}));if(!table.excluded_methods?.includes("PUT"))declared.push(defineRoute({method:"PUT",path:"/bulk",body:bulkUpdateSchema,detail:{tags:[swaggerTag],summary:`Bulk update ${tableName}`,description:`Update multiple ${tableName} records`},handler:async(ctx)=>{let{drizzleTable:drizzleTable2,idCol:idCol2,resolveCol:resolveCol2}=getTableForRequest(ctx.request);if(!database||!idCol2)return ctx.set.status=500,{success:!1,message:"No id column or DB"};let items=ctx.body;if(!Array.isArray(items))return ctx.set.status=400,{success:!1,message:"Body must be an array"};if(items.length>MAX_BULK_ITEMS)return ctx.set.status=400,{success:!1,message:`Bulk request exceeds ${MAX_BULK_ITEMS} items`};let userId=ctx.request.headers.get("x-user-id"),bulkPutAuth=await performAuthCheck(ctx.request,"PUT",writeFields);if(bulkPutAuth&&!bulkPutAuth.authorized)return ctx.set.status=403,{success:!1,message:bulkPutAuth.reason||"Forbidden"};let bulkPutScope=scopeConditionsFor(bulkPutAuth,resolveCol2),prepared=[];for(let item of items){let payload=item.data;if(table.columns){payload=sanitizePayload(payload,table.columns,{isFormData:table.is_form_data===!0}),payload=filterWritePayload(payload,bulkPutAuth);let validation=validatePayload(payload,table.columns,!0);if(!validation.valid)return ctx.set.status=400,{success:!1,message:"Validation failed",errors:validation.errors}}if(payload.updatedAt=new Date,userId)payload.updatedBy=userId;prepared.push({id:item.id,payload})}let beforeById=new Map,txResult=await database.transaction(async(tx)=>{if(prepared.length>0){let idFilter=inArray12(idCol2,prepared.map((p)=>p.id)),beforeRows=await tx.select().from(drizzleTable2).where(bulkPutScope.length>0?and18(idFilter,...bulkPutScope):idFilter);for(let row of beforeRows)if(row.id!=null)beforeById.set(String(row.id),row)}let results=[];for(let{id,payload}of prepared){let result=await tx.update(drizzleTable2).set(payload).where(bulkPutScope.length>0?and18(eq32(idCol2,id),...bulkPutScope):eq32(idCol2,id)).returning();if(result[0])results.push(result[0])}return results});await endSessionsForDeactivated(ctx.request,txResult.map((row)=>({before:beforeById.get(String(row.id)),after:row})));let auditPath=auditPathContext(ctx.request);for(let row of txResult){let rowId=String(row.id);await logger2.audit({entityName:table.table_name,entityId:auditEntityId(row.id),operation:"UPDATE",userId:userId||void 0,summary:`Bulk updated ${table.table_name} (${rowId})`,oldValues:beforeById.get(rowId),newValues:row,...auditPath})}return{success:!0,data:txResult.map((r)=>projectWrittenRow(r,bulkPutAuth))}}}));if(!table.excluded_methods?.includes("DELETE"))declared.push(defineRoute({method:"DELETE",path:"/bulk",body:bulkDeleteSchema,detail:{tags:[swaggerTag],summary:`Bulk delete ${tableName}`,description:`Delete multiple ${tableName} records`},handler:async(ctx)=>{let{drizzleTable:drizzleTable2,idCol:idCol2,resolveCol:resolveCol2}=getTableForRequest(ctx.request);if(!database||!idCol2)return ctx.set.status=500,{success:!1,message:"No id column or DB"};let ids=ctx.body;if(!Array.isArray(ids))return{success:!1,message:"Body must be an array of ids"};if(ids.length>MAX_BULK_ITEMS)return ctx.set.status=400,{success:!1,message:`Bulk request exceeds ${MAX_BULK_ITEMS} items`};let bulkDeleteAuth=await performAuthCheck(ctx.request,"DELETE");if(bulkDeleteAuth&&!bulkDeleteAuth.authorized)return ctx.set.status=403,{success:!1,message:bulkDeleteAuth.reason||"Forbidden"};let bulkDeleteScope=scopeConditionsFor(bulkDeleteAuth,resolveCol2),userId=ctx.request.headers.get("x-user-id"),removed=[];await database.transaction(async(tx)=>{for(let id of ids){let r=await tx.delete(drizzleTable2).where(bulkDeleteScope.length>0?and18(eq32(idCol2,id),...bulkDeleteScope):eq32(idCol2,id)).returning();removed.push(...r)}});{let link=accountLinkFor(config.accountLinks,tableName),bulkUserIds=link?accountIdsIn(removed,link.userColumn):[];if(bulkUserIds.length>0){let authTables=resolveAuthTablesForRequest(ctx.request,{usersTable:schemaTables[AUTHORIZATION_TABLE_KEYS.users],sessionsTable:schemaTables.userSessions??schemaTables.sessions,schemaTables,tenantRegistry:config.tenantRegistry,getTenantRegistry:config.getTenantRegistry}),result=await closeAccounts({db:database,usersTable:authTables.usersTable,sessionsTable:authTables.sessionsTable,onError:(error3)=>logger2.error(`[Entity] ${removed.length} ${tableName} rows were deleted but their accounts could not be closed`,{error:error3})},bulkUserIds);if(result.closed>0&&!result.signedOut)logger2.warn(`[Entity] ${result.closed} account(s) behind deleted ${tableName} rows were deactivated but their open sessions were not cleared`)}}let auditPath=auditPathContext(ctx.request);for(let row of removed)await logger2.audit({entityName:table.table_name,entityId:auditEntityId(row.id),operation:"DELETE",userId:userId||void 0,summary:`Bulk deleted ${table.table_name} (${row.id})`,oldValues:row,...auditPath});return{success:!0,data:{deleted:removed.length}}}}))}mountRoutes(app,[{prefix:`/${tableName}`,guards:entityGuards,routes:declared}])}return app}init_esm3();init_AccountLink();import{randomUUID as randomUUID9}from"crypto";var LOCK_TTL_SECONDS2=60,RENEW_INTERVAL_MS=20000,lockKeyFor=(mappingId)=>`integrations:run:${mappingId}`;async function acquireRunLock(store,mappingId,deps={}){let key2=lockKeyFor(mappingId),now=deps.now??Date.now,ttl=deps.ttlSeconds??LOCK_TTL_SECONDS2,token=randomUUID9(),existing=await store.read(key2);if(!existing.success)return{ok:!1,reason:"unavailable",error:existing.error};if(existing.data)return{ok:!1,reason:"held",heldSince:existing.data.acquiredAt??null};let created=await store.create(key2,{token,acquiredAt:now()},ttl);if(!created.success)return{ok:!1,reason:"unavailable",error:created.error};let readBack=await store.read(key2);if(!readBack.success)return{ok:!1,reason:"unavailable",error:readBack.error};if(readBack.data?.token!==token)return{ok:!1,reason:"held",heldSince:readBack.data?.acquiredAt??null};let held=!0,timer=setInterval(()=>{(async()=>{let current=await store.read(key2);if(!current.success)return;if(current.data?.token!==token){held=!1,clearInterval(timer),deps.logger?.warn("[Integrations] Run lock was taken over",{mappingId});return}await store.create(key2,current.data,ttl)})()},RENEW_INTERVAL_MS);return timer.unref?.(),{ok:!0,handle:{token,isHeld:()=>held,release:async()=>{if(clearInterval(timer),!held)return;held=!1;let current=await store.read(key2);if(current.success&¤t.data?.token===token)await store.remove(key2)}}}}import{createHash as createHash3}from"crypto";function buildUrl(baseUrl,path7,params){let base=baseUrl.trim().replace(/\/+$/,"");if(!base)return{ok:!1,error:"The source has no base address."};let raw=(path7??"").trim();if(/^[a-z][a-z0-9+.-]*:\/\//i.test(raw)||raw.startsWith("//"))return{ok:!1,error:`The endpoint path points at another host (${raw}). A path must be relative to the source address.`};let suffix=raw===""?"":raw.startsWith("/")?raw:`/${raw}`,url;try{url=new URL(`${base}${suffix}`)}catch{return{ok:!1,error:`The source address is not a valid URL: ${baseUrl}`}}for(let[key2,value2]of Object.entries(params??{})){if(value2==null)continue;url.searchParams.set(key2,String(value2))}return{ok:!0,url}}function applyAuth(source,url){let headers={accept:"application/json",...source.extraHeaders??{}},token=source.token?.trim();if(!token)return{headers};switch(source.authType){case"bearer":case"oauth2":headers[source.authHeaderName||"Authorization"]=`Bearer ${token}`;break;case"api_key":if(source.authQueryParam)url.searchParams.set(source.authQueryParam,token);else headers[source.authHeaderName||"X-API-Key"]=token;break;case"basic":headers.Authorization=`Basic ${token}`;break;default:break}return{headers}}function describeTransportError(error3){let message=error3 instanceof Error?error3.message:String(error3),cause=error3?.cause,code=cause?.code??"",blob=`${message} ${code} ${cause?.message??""}`;if(/UNABLE_TO_GET_ISSUER_CERT|SELF_SIGNED|self.signed|CERT_|unable to verify|ERR_TLS|altnames/i.test(blob))return`Certificate verification failed (${code||"incomplete chain"}). If this server's certificate is knowingly self-signed or incomplete, turn off certificate verification for this source and try again.`;if(/ENOTFOUND|EAI_AGAIN|getaddrinfo|Unable to resolve/i.test(blob))return`The address could not be resolved (DNS${code?`: ${code}`:""}). Check the source address.`;if(/ECONNREFUSED/i.test(blob))return`The connection was refused (${code||"ECONNREFUSED"}). The address and port are reachable but nothing is listening.`;if(/ETIMEDOUT|timeout|timed out|aborted|AbortError|UND_ERR/i.test(blob))return`The connection timed out (${code||"timeout"}). Check network and firewall access to this address.`;if(/ECONNRESET|EPIPE/i.test(blob))return`The connection was closed by the other side (${code||"ECONNRESET"}).`;return`Connection error: ${message}`}async function fetchOnce(source,request,deps={}){let impl=deps.fetchImpl??fetch,headers={...request.headers},method=request.method.toUpperCase(),hasBody=request.body!==void 0&&method!=="GET"&&method!=="HEAD";if(hasBody&&!headers["content-type"]&&!headers["Content-Type"])headers["content-type"]="application/json";let init={method,headers,cache:"no-store",signal:AbortSignal.timeout(source.timeoutMs??30000),tls:{rejectUnauthorized:source.tlsVerify!==!1},...hasBody?{body:typeof request.body==="string"?request.body:JSON.stringify(request.body)}:{}},response;try{response=await impl(request.url.toString(),init)}catch(error3){return deps.logger?.warn("[Integrations] Outbound request failed",{sourceId:source.id,url:request.url.origin+request.url.pathname,error:error3 instanceof Error?error3.message:String(error3)}),{ok:!1,status:0,body:null,linkHeader:null,error:describeTransportError(error3)}}let text=await response.text().catch(()=>""),body=null;if(text)try{body=JSON.parse(text)}catch{body=text}let linkHeader=response.headers.get("link");if(!response.ok){let detail=typeof body==="string"?body:body==null?"":JSON.stringify(body);return{ok:!1,status:response.status,body,linkHeader,error:`The source returned HTTP ${response.status}${detail?` \u2014 ${detail.slice(0,300)}`:""}`}}return{ok:!0,status:response.status,body,linkHeader}}var EXPIRY_SKEW_MS=5000,DEFAULT_TTL_MS=300000,DEFAULT_REFRESH_TTL_MS=1800000,FIELD_SEPARATOR="\x00";function credentialFingerprint(config){let material=[config.tokenUrl,config.grantType,config.clientId??"",config.clientSecret??"",config.username??"",config.password??"",config.scope??"",config.clientAuth??"basic"].join(FIELD_SEPARATOR);return createHash3("sha256").update(material).digest("hex").slice(0,32)}class TokenCache{entries=new Map;get(sourceId,fingerprint){let entry=this.entries.get(sourceId);if(!entry)return null;if(entry.fingerprint!==fingerprint)return this.entries.delete(sourceId),null;return entry}set(sourceId,token){this.entries.set(sourceId,token)}invalidate(sourceId){this.entries.delete(sourceId)}clear(){this.entries.clear()}}var isUsable=(token,now)=>token.expiresAt>now+EXPIRY_SKEW_MS,canRefresh=(token,now)=>Boolean(token.refreshToken)&&(token.refreshExpiresAt??0)>now+EXPIRY_SKEW_MS;function buildTokenRequest(config,form){let headers={"content-type":"application/x-www-form-urlencoded",accept:"application/json"};if(config.scope&&!form.has("scope"))form.set("scope",config.scope);let clientId=config.clientId??"",clientSecret=config.clientSecret??"";if((config.clientAuth??"basic")==="basic")headers.authorization=`Basic ${Buffer.from(`${clientId}:${clientSecret}`).toString("base64")}`;else{if(clientId)form.set("client_id",clientId);if(clientSecret)form.set("client_secret",clientSecret)}return{headers,body:form.toString()}}async function requestToken(config,form,now,fetchImpl){let{headers,body}=buildTokenRequest(config,form),response;try{response=await fetchImpl(config.tokenUrl,{method:"POST",headers,body,cache:"no-store",signal:AbortSignal.timeout(config.timeoutMs??30000),tls:{rejectUnauthorized:config.tlsVerify!==!1}})}catch(error3){return{ok:!1,error:`Token request failed \u2014 ${describeTransportError(error3)}`}}let text=await response.text().catch(()=>""),json={};try{json=text?JSON.parse(text):{}}catch{}if(!response.ok||!json.access_token){let detail=json.error_description||json.error||text.slice(0,200);return{ok:!1,error:`Could not obtain an OAuth2 token (HTTP ${response.status})${detail?`: ${detail}`:""}`}}return{ok:!0,token:{accessToken:json.access_token,expiresAt:now+(json.expires_in?json.expires_in*1000:DEFAULT_TTL_MS),refreshToken:json.refresh_token,refreshExpiresAt:json.refresh_token?now+(json.refresh_expires_in?json.refresh_expires_in*1000:DEFAULT_REFRESH_TTL_MS):void 0,fingerprint:credentialFingerprint(config)}}}async function getAccessToken(config,cache,deps={}){let fetchImpl=deps.fetchImpl??fetch,now=deps.now?.()??Date.now();if(!config.tokenUrl?.trim())return{ok:!1,error:"No token URL is configured for this source."};let fingerprint=credentialFingerprint(config),cached=cache.get(config.sourceId,fingerprint);if(cached&&isUsable(cached,now))return{ok:!0,accessToken:cached.accessToken,fromCache:!0};if(cached&&canRefresh(cached,now)){let refreshed=await requestToken(config,new URLSearchParams({grant_type:"refresh_token",refresh_token:cached.refreshToken}),now,fetchImpl);if(refreshed.ok)return cache.set(config.sourceId,refreshed.token),{ok:!0,accessToken:refreshed.token.accessToken,fromCache:!1}}let form=new URLSearchParams({grant_type:config.grantType});if(config.grantType==="password")form.set("username",config.username??""),form.set("password",config.password??"");let minted=await requestToken(config,form,now,fetchImpl);if(!minted.ok)return{ok:!1,error:minted.error};return cache.set(config.sourceId,minted.token),{ok:!0,accessToken:minted.token.accessToken,fromCache:!1}}import{and as and19,eq as eq33,inArray as inArray13}from"drizzle-orm";var DEFAULT_IGNORED=["updatedAt","updated_at","updatedBy","updated_by"];function planRevert(input){let ignored=new Set([...DEFAULT_IGNORED,...input.ignoreColumns??[]]),ops={deletes:[],restores:[],reinserts:[],conflicts:[]};for(let image of input.preImages)switch(image.action){case"create":{if(!image.rowId)break;let now=input.current.get(image.rowId);if(!now)break;if(!input.force&&changedSince(image.after,now,ignored)){ops.conflicts.push({id:image.rowId,reason:"This record was edited after the import; deleting it would discard that edit."});break}ops.deletes.push(image.rowId);break}case"update":case"deactivate":{if(!image.rowId||!image.before)break;let now=input.current.get(image.rowId);if(!now)break;if(!input.force&&changedSince(image.after,now,ignored)){ops.conflicts.push({id:image.rowId,reason:"This record was edited after the import; restoring it would discard that edit."});break}ops.restores.push({id:image.rowId,row:image.before});break}case"delete":{if(!image.before)break;if(image.rowId&&input.current.has(image.rowId)){ops.conflicts.push({id:image.rowId,reason:"A record with this identifier exists again; it was not replaced."});break}ops.reinserts.push(image.before);break}default:break}return ops}function changedSince(after,now,ignored){if(!after)return!1;for(let[key2,value2]of Object.entries(after)){if(ignored.has(key2))continue;if(!sameValue(value2,now[key2]))return!0}return!1}function sameValue(a,b){if(a===b)return!0;if(a==null&&b==null)return!0;if(a instanceof Date&&b instanceof Date)return a.getTime()===b.getTime();if(a instanceof Date||b instanceof Date){let da=a instanceof Date?a:new Date(String(a)),db=b instanceof Date?b:new Date(String(b));return!Number.isNaN(da.getTime())&&!Number.isNaN(db.getTime())?da.getTime()===db.getTime():!1}if(typeof a==="object"||typeof b==="object")try{return JSON.stringify(a)===JSON.stringify(b)}catch{return!1}return String(a)===String(b)}var CHUNK=500;async function applyRevert(deps,ops,meta){let idCol=deps.resolveCol("id");if(!idCol)return{ok:!1,conflicts:ops.conflicts.length,error:`"${meta.targetEntity}" has no id column; its rows cannot be addressed.`};let now=new Date;try{let work=async(tx)=>{let deleted=0,restored=0,reinserted=0;for(let i=0;i<ops.deletes.length;i+=CHUNK){let chunk=ops.deletes.slice(i,i+CHUNK),removed=await tx.delete(deps.drizzleTable).where(inArray13(idCol,chunk)).returning();deleted+=removed.length}for(let restore of ops.restores){let{id:_id,createdAt:_c,createdBy:_cb,...rest3}=restore.row,written=await tx.update(deps.drizzleTable).set({...rest3,updatedAt:now,updatedBy:meta.userId}).where(and19(eq33(idCol,restore.id))).returning();restored+=written.length}if(ops.reinserts.length>0)for(let i=0;i<ops.reinserts.length;i+=CHUNK){let chunk=ops.reinserts.slice(i,i+CHUNK),written=await tx.insert(deps.drizzleTable).values(chunk).returning();reinserted+=written.length}return{deleted,restored,reinserted}};return{ok:!0,...meta.tx?await work(meta.tx):await deps.db.transaction(work),conflicts:ops.conflicts.length}}catch(error3){return deps.logger.error("[Integrations] Revert transaction failed",{targetEntity:meta.targetEntity,error:error3 instanceof Error?error3.message:String(error3)}),{ok:!1,conflicts:ops.conflicts.length,error:"The database rejected this undo; it was rolled back and nothing changed."}}}function groupPreImagesByEntity(preImages){let byEntity=new Map;for(let image of preImages){let list2=byEntity.get(image.targetEntity);if(list2)list2.push(image);else byEntity.set(image.targetEntity,[image])}return[...byEntity.entries()].map(([targetEntity,images])=>({targetEntity,preImages:images}))}function touchedRowIds(preImages){let ids=new Set;for(let image of preImages)if(image.rowId)ids.add(image.rowId);return[...ids]}var TREE_PARENT_KEY="__treeParent";function flattenTree(records,childrenPath){let out=[],truncated=!1,visit=(node,parent,depth)=>{if(!node||typeof node!=="object")return;if(depth>64){truncated=!0;return}let source=node,children=source[childrenPath],{[childrenPath]:_nested,...own}=source;if(out.push({...own,["__depth"]:depth,["__treeParent"]:parent??null}),!Array.isArray(children))return;for(let child of children)visit(child,own,depth+1)};for(let root of records)visit(root,null,0);return{records:out,truncated}}function treeChildrenPath(value2){let trimmed=(value2??"").trim();return trimmed.length>0?trimmed:null}function getByPath(obj,path7){if(!path7)return;let cur=obj;for(let part of path7.split(".")){if(cur==null||typeof cur!=="object")return;cur=cur[part]}return cur}function readSourceValue(external,parent,path7){return readOne(external,parent,path7)}function readOne(external,parent,path7){return path7.startsWith("__parent.")?getByPath(parent,path7.slice(9)):getByPath(external,path7)}var TEMPLATE_TOKEN=/\{([^{}]+)\}/g;function isTemplateSource(source){return TEMPLATE_TOKEN.lastIndex=0,TEMPLATE_TOKEN.test(source)}function renderTemplate(template,external,parent){let treeParentAbsent=external!=null&&typeof external==="object"&&TREE_PARENT_KEY in external&&external[TREE_PARENT_KEY]==null,readsTreeParent=(text)=>text.includes(`{${TREE_PARENT_KEY}.`),readToken=(token)=>{let[path7="",transform3]=token.split("|"),value2=readOne(external,parent,path7.trim());if(transform3===void 0)return value2;let result=applyTransform(value2,transform3.trim());return result.ok?result.value:value2};for(let alternative of template.split("||")){let candidate=alternative.trim();if(candidate==="")continue;if(treeParentAbsent&&readsTreeParent(candidate))continue;let complete=!0,rendered=candidate.replace(TEMPLATE_TOKEN,(_match,token)=>{let value2=readToken(token);if(value2==null||String(value2).trim()==="")complete=!1;return value2==null?"":String(value2)});if(complete&&rendered.trim()!=="")return rendered}let last=template.split("||").pop()??template;if(treeParentAbsent&&readsTreeParent(last))return"";return last.trim().replace(TEMPLATE_TOKEN,(_match,token)=>{let value2=readToken(token);return value2==null?"":String(value2)})}function extractRecords(body,rootPath){if(rootPath){let at=getByPath(body,rootPath);if(Array.isArray(at))return at;return at&&typeof at==="object"?[at]:[]}let candidate=firstArrayLike(body)??deepFirstArrayOfObjects(body);return Array.isArray(candidate)?candidate:[]}var ENVELOPE_KEYS=["content","items","data","results","records","rows","list","value","responseObject"];function firstArrayLike(body){if(Array.isArray(body))return body;if(!body||typeof body!=="object")return;let b=body;for(let key2 of ENVELOPE_KEYS){let v=b[key2];if(Array.isArray(v))return v;if(v&&typeof v==="object"){let nested=v.items;if(Array.isArray(nested))return nested}}return}function deepFirstArrayOfObjects(body,depth=0){if(depth>5||body==null||typeof body!=="object"||body instanceof Date)return;if(Array.isArray(body))return body.length>0&&typeof body[0]==="object"&&body[0]!==null?body:void 0;let entries=Object.values(body);for(let v of entries)if(Array.isArray(v)&&v.length>0&&typeof v[0]==="object"&&v[0]!==null)return v;for(let v of entries)if(v&&typeof v==="object"&&!Array.isArray(v)){let found=deepFirstArrayOfObjects(v,depth+1);if(found)return found}for(let v of entries)if(Array.isArray(v))return v;return}var FOLD={\u{131}:"i",\u{130}:"I",\u{15f}:"s",\u{15e}:"S",\u{11f}:"g",\u{11e}:"G",\u{f8}:"o",\u{d8}:"O",\u{e6}:"ae",\u{c6}:"AE",\u{df}:"ss",\u{111}:"d",\u{110}:"D",\u{142}:"l",\u{141}:"L"};function foldToAscii(value2){return value2.replace(/[\u0131\u0130\u015F\u015E\u011F\u011E\u00F8\u00D8\u00E6\u00C6\u00DF\u0111\u0110\u0142\u0141]/g,(ch)=>FOLD[ch]??ch).normalize("NFD").replace(/[\u0300-\u036f]/g,"")}var TRUTHY=new Set(["true","1","yes","y","evet","e","on"]),FALSY=new Set(["false","0","no","n","hayir","hay\u0131r","h","off"]);function applyTransform(value2,transform3,locale){if(value2==null)return{ok:!0,value:value2};switch(transform3){case"trim":return{ok:!0,value:String(value2).trim()};case"upper":return{ok:!0,value:String(value2).trim().toLocaleUpperCase(locale)};case"lower":return{ok:!0,value:String(value2).trim().toLocaleLowerCase(locale)};case"toString":return{ok:!0,value:String(value2)};case"toNumber":{if(typeof value2==="boolean")return{ok:!1,reason:"not a number",raw:value2};let s=String(value2).trim();if(s==="")return{ok:!0,value:null};let n=Number(s);return Number.isFinite(n)?{ok:!0,value:n}:{ok:!1,reason:"not a number",raw:value2}}case"toBoolean":{if(typeof value2==="boolean")return{ok:!0,value:value2};let s=String(value2).trim().toLocaleLowerCase("tr-TR");if(s==="")return{ok:!0,value:null};if(TRUTHY.has(s))return{ok:!0,value:!0};if(FALSY.has(s))return{ok:!0,value:!1};return{ok:!1,reason:"not a yes/no value",raw:value2}}case"emailSafe":return{ok:!0,value:foldToAscii(String(value2)).toLowerCase().trim().replace(/[^a-z0-9._@+-]+/g,".").replace(/\.{2,}/g,".").replace(/\.*@\.*/g,"@").replace(/^[.\-_]+|[.\-_]+$/g,"")};case"slug":return{ok:!0,value:foldToAscii(String(value2)).toLowerCase().trim().replace(/[^a-z0-9]+/g,"-").replace(/-{2,}/g,"-").replace(/^-+|-+$/g,"")};case"pathLeaf":{let parts=String(value2).split(/[/\\>\u203A\u00BB]|->/).map((part)=>part.trim()).filter((part)=>part!=="");return{ok:!0,value:parts.length>0?parts[parts.length-1]:""}}case"toList":{if(Array.isArray(value2))return{ok:!0,value:value2};let s=String(value2).trim();if(s==="")return{ok:!0,value:[]};return{ok:!0,value:s.split(",").map((part)=>part.trim()).filter((part)=>part!=="")}}case"dateOnly":{let s=String(value2).trim();if(s==="")return{ok:!0,value:null};let leading=s.match(/^\d{4}-\d{2}-\d{2}/);if(leading)return{ok:!0,value:leading[0]};let d=new Date(s);return Number.isNaN(d.getTime())?{ok:!1,reason:"not a date",raw:value2}:{ok:!0,value:d.toISOString().slice(0,10)}}default:return{ok:!0,value:value2}}}function isEmpty(v){return v==null||typeof v==="string"&&v.trim()===""}function mapRecord(external,mappings,parent){let row={},problems=[];for(let fm of mappings){if(!fm.target)continue;let value2;if(fm.constant!=null&&fm.constant!=="")value2=fm.constant;else{let raw=isTemplateSource(fm.source)?renderTemplate(fm.source,external,parent):readOne(external,parent,fm.source),result=applyTransform(raw,fm.transform);if(!result.ok)problems.push(`${fm.source} \u2192 ${fm.target}: ${result.reason} (${String(result.raw)})`),value2=null;else value2=result.value}if(isEmpty(value2)&&fm.fallback!=null&&fm.fallback!==""){let raw=isTemplateSource(fm.fallback)?renderTemplate(fm.fallback,external,parent):fm.fallback,result=applyTransform(raw,fm.transform);if(result.ok)value2=result.value;else problems.push(`fallback \u2192 ${fm.target}: ${result.reason} (${String(result.raw)})`),value2=null}if(value2!==void 0)row[fm.target]=value2}return{row,problems}}function buildMappedRecords(externalRecords,mappings,dedupTargetField,parents){let records=[],issues=[];return externalRecords.forEach((ext,i)=>{let{row,problems}=mapRecord(ext,mappings,parents?.[i]);for(let message of problems)issues.push({sourceIndex:i,kind:"invalid_value",message});let dedupKey=dedupTargetField&&!isEmpty(row[dedupTargetField])?String(row[dedupTargetField]).trim():null;records.push({data:row,dedupKey,sourceIndex:i,external:ext,parent:parents?.[i]})}),{records,issues}}function flattenKeys(obj,prefix="",out=[],depth=0){if(out.length>200||depth>4||obj==null||typeof obj!=="object")return out;if(Array.isArray(obj)){if(prefix)out.push(prefix);return out}for(let[k,v]of Object.entries(obj)){let path7=prefix?`${prefix}.${k}`:k;if(v!=null&&typeof v==="object"&&!Array.isArray(v))flattenKeys(v,path7,out,depth+1);else out.push(path7)}return out}var MAX_FANOUT_COMBINATIONS=5000;function resolvePath2(rawPath,values){let missing=new Set,used=new Set;return{path:rawPath.replace(/\{([a-zA-Z_][\w-]*)\}|:([a-zA-Z_]\w*)/g,(_full,brace,colon)=>{let key2=brace??colon,v=values[key2];if(v==null||v==="")return missing.add(key2),brace?`{${key2}}`:`:${key2}`;return used.add(key2),encodeURIComponent(String(v))}),missing:[...missing],used}}function planFetchOrder(endpoints,roots){let byId=new Map(endpoints.map((e)=>[e.id,e])),order=[],settled=new Set,onPath=[],onPathSet=new Set,visit=(id,requiredBy)=>{if(settled.has(id))return null;if(onPathSet.has(id))return{ok:!1,reason:"cycle",chain:[...onPath.slice(onPath.indexOf(id)),id]};let node=byId.get(id);if(!node)return{ok:!1,reason:"unknown_endpoint",missing:id,requiredBy:requiredBy??id};onPath.push(id),onPathSet.add(id);for(let binding of node.paramBindings??[]){if(binding.from!=="endpoint")continue;let failure=visit(binding.endpointId,id);if(failure)return failure}return onPath.pop(),onPathSet.delete(id),settled.add(id),order.push(id),null};for(let id of roots??endpoints.map((e)=>e.id)){let failure=visit(id,null);if(failure)return failure}return{ok:!0,order}}function expandBindings(bindings,sources,limit=MAX_FANOUT_COMBINATIONS){if(!bindings||bindings.length===0)return{ok:!0,calls:[{values:{}}]};let axes=[];for(let binding of bindings){if(binding.from==="static"){axes.push({param:binding.param,options:[{value:binding.value}]});continue}if(binding.from==="endpoint"){let options=(sources.parents?.[binding.endpointId]??[]).map((record3)=>({value:getByPath(record3,binding.field),parent:record3})).filter((o)=>o.value!=null&&o.value!=="");axes.push({param:binding.param,options});continue}let values=sources.entityValues?.[`${binding.entity}.${binding.field}`]??[];axes.push({param:binding.param,options:values.filter((v)=>v!=null&&v!=="").map((value2)=>({value:value2}))})}if(axes.some((a)=>a.options.length===0))return{ok:!0,calls:[]};let combinations=axes.reduce((n,a)=>n*a.options.length,1);if(combinations>limit)return{ok:!1,reason:"too_many",combinations,limit};let calls=[{values:{}}];for(let axis of axes){let next=[];for(let call of calls)for(let option of axis.options)next.push({values:{...call.values,[axis.param]:option.value},parent:option.parent??call.parent});calls=next}return{ok:!0,calls}}function parentEndpointIds(bindings){let ids=new Set;for(let b of bindings??[])if(b.from==="endpoint")ids.add(b.endpointId);return[...ids]}function entityValueKeys(bindings){let keys=new Set;for(let b of bindings??[])if(b.from==="entity")keys.add(`${b.entity}.${b.field}`);return[...keys]}var MAX_PAGES=1000,DEFAULT_PAGE_SIZE=200,initialState2=()=>({pagesFetched:0,recordsFetched:0,cursor:null});function firstPageParams(profile){switch(profile.kind){case"page_number":{let params={[profile.pageParam]:String(profile.firstPage??0)};if(profile.sizeParam)params[profile.sizeParam]=String(pageSize(profile));return params}case"offset":return{[profile.offsetParam]:"0",[profile.limitParam]:String(pageSize(profile))};case"cursor":{let params={};if(profile.sizeParam)params[profile.sizeParam]=String(pageSize(profile));return params}case"link_header":{let params={};if(profile.sizeParam)params[profile.sizeParam]=String(pageSize(profile));return params}default:return{}}}function reportedTotal(profile,body){let path7=profile.kind==="page_number"||profile.kind==="offset"?profile.totalPath:void 0;if(!path7)return null;let raw=getByPath(body,path7);if(typeof raw==="number"&&Number.isFinite(raw)&&raw>=0)return Math.floor(raw);if(typeof raw==="string"&&raw.trim()!==""){let n=Number(raw);if(Number.isFinite(n)&&n>=0)return Math.floor(n)}return null}function nextPage(profile,state,observed){if(profile.kind==="none")return{kind:"done",reason:"single request"};if(state.pagesFetched>=MAX_PAGES)return{kind:"failed",reason:`Paging stopped after ${MAX_PAGES} requests without reaching the end. The source may not be honouring the paging parameters; importing this would be a partial read.`};if(observed.recordCount===0)return{kind:"done",reason:"empty page"};switch(profile.kind){case"page_number":{if(isLastPage(profile.lastPagePath,observed.body))return{kind:"done",reason:"source reported the last page"};let total=reportedTotal(profile,observed.body);if(total!==null&&state.recordsFetched>=total)return{kind:"done",reason:"reported total reached"};if(observed.recordCount<pageSize(profile))return{kind:"done",reason:"short page"};let next=(profile.firstPage??0)+state.pagesFetched,params={[profile.pageParam]:String(next)};if(profile.sizeParam)params[profile.sizeParam]=String(pageSize(profile));return{kind:"params",params}}case"offset":{let total=reportedTotal(profile,observed.body);if(total!==null&&state.recordsFetched>=total)return{kind:"done",reason:"reported total reached"};if(observed.recordCount<pageSize(profile))return{kind:"done",reason:"short page"};return{kind:"params",params:{[profile.offsetParam]:String(state.recordsFetched),[profile.limitParam]:String(pageSize(profile))}}}case"cursor":{let raw=getByPath(observed.body,profile.nextCursorPath),next=raw==null?"":String(raw).trim();if(next==="")return{kind:"done",reason:"no further cursor"};if(state.cursor!=null&&next===state.cursor)return{kind:"failed",reason:`The source returned the same cursor twice (${next}); paging would not advance.`};let params={[profile.cursorParam]:next};if(profile.sizeParam)params[profile.sizeParam]=String(pageSize(profile));return{kind:"params",params}}case"link_header":{let url=parseNextLink(observed.linkHeader);if(!url)return{kind:"done",reason:"no rel=next link"};return{kind:"url",url}}default:return{kind:"done",reason:"single request"}}}function pageSize(profile){let declared="pageSize"in profile?profile.pageSize:void 0;return declared&&declared>0?declared:DEFAULT_PAGE_SIZE}function isLastPage(path7,body){if(!path7)return!1;let raw=getByPath(body,path7);if(typeof raw==="boolean")return raw;if(typeof raw==="string")return raw.trim().toLowerCase()==="true";return!1}function parseNextLink(header){if(!header)return null;for(let part of splitLinks(header)){let match=part.match(/<([^>]*)>(.*)/);if(!match)continue;let url=(match[1]??"").trim(),attrs=match[2]??"";if(!url)continue;if(/;\s*rel\s*=\s*["']?\s*next\s*["']?(\s*;|\s*$)/i.test(attrs))return url}return null}function splitLinks(header){let parts=[],depth=0,current="";for(let ch of header){if(ch==="<")depth++;else if(ch===">")depth--;if(ch===","&&depth===0){parts.push(current),current="";continue}current+=ch}if(current.trim()!=="")parts.push(current);return parts}var DEFAULT_MAX_CONCURRENT=4,DEFAULT_MAX_RECORDS=50000;async function collectOne(source,endpoint,params,deps,counters){let profile=endpoint.pagination??{kind:"none"},maxRecords=deps.maxRecords??DEFAULT_MAX_RECORDS,values={...endpoint.queryParams??{},...params.values},resolved=resolvePath2(endpoint.path,values);if(resolved.missing.length>0)return{ok:!1,error:`This endpoint needs path parameter(s) ${resolved.missing.map((m)=>`{${m}}`).join(", ")} and nothing supplies them. Add a parameter binding, or a static value.`};let baseQuery={};for(let[key2,value2]of Object.entries(values)){if(value2==null||resolved.used.has(key2))continue;baseQuery[key2]=value2}let records=[],total=null,state=initialState2(),pageParams=firstPageParams(profile),absoluteUrl=null;for(;;){if(deps.isCancelled?.())return{ok:!1,error:"Cancelled."};let url;if(absoluteUrl)try{url=new URL(absoluteUrl)}catch{return{ok:!1,error:`The source returned an unusable next-page link: ${absoluteUrl}`}}else{let built=buildUrl(source.baseUrl,resolved.path,{...baseQuery,...pageParams});if(!built.ok)return{ok:!1,error:built.error};url=built.url}let headers={accept:"application/json"};if(deps.bearerToken)headers.Authorization=`Bearer ${deps.bearerToken}`;counters.calls++;let outcome=await fetchOnce({...source,token:deps.bearerToken??source.token},{method:endpoint.method,url,headers,body:endpoint.method.toUpperCase()==="GET"?void 0:endpoint.requestBody??{}},{fetchImpl:deps.fetchImpl});if(!outcome.ok)return{ok:!1,error:outcome.error??`HTTP ${outcome.status}`};let raw=extractRecords(outcome.body,endpoint.responseRootPath),childrenPath=treeChildrenPath(endpoint.treeChildrenPath),flattened=childrenPath?flattenTree(raw,childrenPath):null;if(flattened?.truncated)return{ok:!1,error:"This source answered with a structure that nests into itself. Nothing was imported \u2014 a company that contains itself has no order to write it in."};let page=flattened?flattened.records:raw;if(records.push(...page),counters.records+=page.length,total===null)total=reportedTotal(profile,outcome.body);if(counters.records>maxRecords)return{ok:!1,error:`This run would hold more than ${maxRecords} records at once. Narrow the source with a filter, or raise maxRecordsPerRun.`};deps.onProgress?.(counters.records),state={pagesFetched:state.pagesFetched+1,recordsFetched:records.length,cursor:pageParams[cursorParamOf(profile)??""]??state.cursor};let next=nextPage(profile,state,{recordCount:page.length,body:outcome.body,linkHeader:outcome.linkHeader});if(next.kind==="done")break;if(next.kind==="failed")return{ok:!1,error:next.reason};if(next.kind==="url"){absoluteUrl=next.url,pageParams={};continue}absoluteUrl=null,pageParams=next.params}return{ok:!0,records,total}}function cursorParamOf(profile){return profile.kind==="cursor"?profile.cursorParam:null}function describeCallFailure(call,error3){let params=call.values??{},named=Object.entries(params).map(([key2,value2])=>`${key2}=${String(value2)}`).join(", ");return named?`${named}: ${error3}`:error3}async function collectEndpoint(source,endpoint,sources,deps={}){let counters={calls:0,records:0},expansion=expandBindings(endpoint.paramBindings??void 0,sources);if(!expansion.ok)return{ok:!1,callCount:0,error:`This endpoint would be called ${expansion.combinations} times (the ceiling is ${expansion.limit}). Narrow the parent list, or remove one of the parameter bindings.`};if(expansion.calls.length===0)return{ok:!0,records:[],reportedTotal:null,callCount:0};let limit=Math.max(1,deps.maxConcurrent??DEFAULT_MAX_CONCURRENT),collected=[],total=null,failure=null,partial3=[],cursor=0,worker=async()=>{for(;;){if(failure||deps.isCancelled?.())return;let index=cursor++,call=expansion.calls[index];if(!call)return;let result=await collectOne(source,endpoint,call,deps,counters);if(!result.ok){if(expansion.calls.length>1){partial3.push(describeCallFailure(call,result.error));continue}failure??=result.error;return}for(let record3 of result.records)collected.push({record:record3,parent:call.parent});if(expansion.calls.length===1)total=result.total}};if(await Promise.all(Array.from({length:Math.min(limit,expansion.calls.length)},worker)),failure)return{ok:!1,error:failure,callCount:counters.calls};if(deps.isCancelled?.())return{ok:!1,error:"Cancelled.",callCount:counters.calls};if(partial3.length>0&&collected.length===0&&partial3.length===expansion.calls.length)return{ok:!1,error:`Every call to this endpoint failed. First: ${partial3[0]}`,callCount:counters.calls};return{ok:!0,records:collected,reportedTotal:total,callCount:counters.calls,...partial3.length>0?{partialFailures:partial3}:{}}}function isDestructive(writeMode,missingPolicy){return writeMode==="replace"||missingPolicy==="delete"||missingPolicy==="deactivate"}function destructiveGate(input){if(!isDestructive(input.writeMode,input.missingPolicy))return{allowed:!0};if(input.partialFailures>0&&!input.scopedRemoval)return{allowed:!1,kind:"incomplete_source",message:`${input.partialFailures} call(s) in this source never answered, so part of it was not read. This run removes records the source does not send, and those unread records would be removed as if the source had dropped them. Fix the failing call, or switch this mapping to leave missing records alone.`};if(input.fetched===0)return{allowed:!1,kind:"incomplete_source",message:"The source returned no records at all. This run removes what the source does not send, so it would empty the target table. A source that is genuinely empty and one that could "+"not be read look the same from here, so this will not proceed on its own \u2014 preview it, "+"and if the source really is empty, remove the rows deliberately."};if(input.reported!==null&&input.fetched<input.reported)return{allowed:!1,kind:"incomplete_source",message:`The source reports ${input.reported} records but only ${input.fetched} were read. This run deletes what the source does not send, so it will not proceed on a partial read. Check the paging settings for this endpoint.`};return{allowed:!0}}function checkPlanVolume(input){if(input.maxShare<=0)return{allowed:!0};if(input.removing===0||input.existing===0)return{allowed:!0};let share=input.removing/input.existing;if(share<=input.maxShare)return{allowed:!0};let asPercent=(value2)=>`${Math.round(value2*100)}%`;return{allowed:!1,kind:"incomplete_source",message:`This run would remove ${input.removing} of ${input.existing} rows (${asPercent(share)}), which is above the ${asPercent(input.maxShare)} this mapping allows unattended. `+"A source can return perfectly good records and still produce a removal this large \u2014 a "+"changed dedup column, a repointed endpoint, a filter added upstream. Preview the run; if the removal is right, apply it deliberately."}}function orphanLinkNote(input){let{made,unresolved}=input;if(unresolved<=0)return null;let total=made+unresolved,share=total>0?unresolved/total:1,column=input.column?` (${input.column})`:"",consequence=`${unresolved} record${unresolved===1?"":"s"} could not find the parent to attach to${column} \u2014 `+"they are drawn at the TOP of the tree, at the same level as the chief executive.";if(share>=0.2)return`${consequence} Next to the ${made} that did attach, that is a large share: either the source is not sending a parent for these people, or one of the matching fields is written differently in the two places. Look at the source before trusting the chart.`;return`${consequence} ${made} link${made===1?" was":"s were"} made.`}function looksLikeKeyDrift(input){if(input.existing<=0)return!1;if(input.creating<=0||input.unmatched<=0)return!1;return input.creating/input.existing>=0.1&&input.unmatched/input.existing>=0.1}function keyDriftWarning(input){if(!looksLikeKeyDrift(input))return null;return`This run will add ${input.creating} rows, and the source no longer sends ${input.unmatched} of the rows already here. Both being this large points at the matching values in the `+"source having changed \u2014 so these are not new records, they are the same records under "+"new names. Run it with \u201Cmissing rows: leave alone\u201D and both generations survive: the "+`table grows to ${input.existing+input.creating} rows, and where records point at each other (an organisation tree, for instance) the new rows cannot find their old parents and the structure breaks. Either switch the policy to delete/deactivate and clean up `+"once, or correct the matching column to the source\u2019s new value."}var referenceKey=(check)=>`${check.entity}.${check.entityColumn}`,pointsAtOwnTable=(check,targetEntity)=>check.entity===targetEntity;function referenceValuesOf(raw){if(Array.isArray(raw))return{values:raw.map((item)=>String(item??"").trim()).filter((item)=>item!==""),wasList:!0};if(raw===null||raw===void 0)return{values:[],wasList:!1};let single=String(raw).trim();return{values:single===""?[]:[single],wasList:!1}}var isEmptyReference=(raw)=>referenceValuesOf(raw).values.length===0;function resolveReference(raw,ids){let{values,wasList}=referenceValuesOf(raw),found=[],unmatched=[];for(let value2 of values){let id=ids?.[value2];if(id===void 0)unmatched.push(value2);else if(!found.includes(id))found.push(id)}return{ids:found,unmatched,wasList}}function resolvedColumnValue(resolution){return resolution.wasList?resolution.ids:resolution.ids[0]??null}function applyReferenceIds(row,references,ids,targetEntity){let next={...row},deferred=[];for(let check of references){if(!check.writeIdTo)continue;let raw=row[check.column];if(pointsAtOwnTable(check,targetEntity)){if(next[check.writeIdTo]=Array.isArray(raw)?[]:null,!isEmptyReference(raw))deferred.push({check,raw});continue}next[check.writeIdTo]=resolvedColumnValue(resolveReference(raw,ids[referenceKey(check)]))}return{row:next,deferred}}function unambiguousIds(rows){let seen=new Map;for(let row of rows){if(row.value==null||row.id==null)continue;let key2=String(row.value).trim();if(key2==="")continue;seen.set(key2,seen.has(key2)?null:String(row.id))}let ids={};for(let[key2,id]of seen)if(id!==null)ids[key2]=id;return ids}function ambiguousValues(rows){let counts=new Map;for(let row of rows){if(row.value==null||row.id==null)continue;let key2=String(row.value).trim();if(key2==="")continue;counts.set(key2,(counts.get(key2)??0)+1)}return[...counts.entries()].filter(([,n])=>n>1).map(([key2])=>key2)}var UNRESOLVED_TEMPLATE=/^\s*(\$\{[^}]*\}|\{\{[^}]*\}\})\s*$/,isEmpty2=(v)=>v==null||typeof v==="string"&&(v.trim()===""||UNRESOLVED_TEMPLATE.test(v));function displayValue(value2){if(value2!==null&&typeof value2==="object")try{return JSON.stringify(value2)}catch{return"(a value that cannot be shown)"}return String(value2)}function buildFallbackIdentifier(format2,data){let complete=!0,rendered=format2.replace(/\{([^{}]+)\}/g,(_match,path7)=>{let value2=data[path7.trim()];if(value2==null||String(value2).trim()==="")complete=!1;return value2==null?"":String(value2)});if(!complete)return null;let cleaned=foldToAscii(rendered).toLowerCase().trim().replace(/[^a-z0-9._@+-]+/g,".").replace(/\.{2,}/g,".").replace(/\.*@\.*/g,"@").replace(/^[.\-_]+|[.\-_]+$/g,"");return cleaned===""?null:cleaned}function missingReferences(row,references,known){let broken=[];for(let check of references){let raw=row[check.column];if(isEmpty2(raw))continue;let value2=displayValue(raw).trim(),present=known[`${check.entity}.${check.entityColumn}`];if(!present)continue;if(!present.has(value2))broken.push({check,value:value2,raw})}return broken}async function loadReferenceValues(references,read2){let values={};for(let check of references){let key2=`${check.entity}.${check.entityColumn}`;if(values[key2])continue;let rows=await read2(check.entity,check.entityColumn);values[key2]=new Set(rows.filter((v)=>!isEmpty2(v)).map((v)=>String(v).trim()))}return values}async function loadReferenceIds(references,targetEntity,read2){let ids={},ambiguous={};for(let check of references){if(!check.writeIdTo)continue;if(pointsAtOwnTable(check,targetEntity))continue;let key2=referenceKey(check);if(ids[key2])continue;let pairs=await read2(check.entity,check.entityColumn);ids[key2]=unambiguousIds(pairs);let shared=ambiguousValues(pairs);if(shared.length>0)ambiguous[key2]=shared}return{ids,ambiguous}}function ambiguityWarnings(ambiguous){return Object.entries(ambiguous).map(([key2,values])=>{let shown=values.slice(0,5).join(", "),rest3=values.length>5?` and ${values.length-5} more`:"";return`${values.length} value(s) in ${key2} belong to more than one row \u2014 ${shown}${rest3}. Records pointing at them are left unlinked rather than linked to whichever row came first.`})}function planWrites(input){let{records,existing,dedupTargetField,writeMode,missingPolicy,missingColumn,missingValue,removalScope=null,originColumn,originValue,requiredFields=[],uniqueFields=[],createDefaults={},accountRule=null,existingAccounts={},references=[],referenceValues={},referenceIds={},targetEntity=""}=input,issues=[],warnings=[],creates=[],referenceInserts=[],accountInserts=[],accountJoinInserts=[],plannedAccounts=new Set,plannedRefs=new Set,updates=[],deletes=[],deactivates=[],departures=[],deferredLinks=[],dedupConfigured=Boolean(dedupTargetField),accountClaims=new Map,accountIdIsUnique=accountRule?uniqueFields.includes(accountRule.idColumn):!1,fillIdentifier=(data)=>{let format2=accountRule?.emailFormat?.trim();if(!accountRule||!format2)return data;if(!isEmpty2(data[accountRule.matchFrom]))return data;let built=buildFallbackIdentifier(format2,data);return built===null?data:{...data,[accountRule.matchFrom]:built}},withAccount=(record3,input2)=>{if(!accountRule)return input2;let data=fillIdentifier(input2),raw=data[accountRule.matchFrom];if(isEmpty2(raw))return issues.push({sourceIndex:record3.sourceIndex,kind:"account_unmatched",field:accountRule.matchFrom,message:`${accountRule.matchFrom} is empty, so this record gets no account \u2014 it is imported without one.`,dedupKey:record3.dedupKey,row:data}),data;let value2=String(raw).trim(),claimedBy=accountClaims.get(value2);if(claimedBy&&accountIdIsUnique)return issues.push({sourceIndex:record3.sourceIndex,kind:"duplicate_value",field:accountRule.matchFrom,value:String(value2),message:`${accountRule.matchFrom} is \u201C${value2}\u201D, which another record in this import already uses \u2014 and ${accountRule.idColumn} allows one record per account. This one is left out.`,dedupKey:record3.dedupKey,row:data,keptRow:claimedBy.data}),null;accountClaims.set(value2,record3);let existingId=existingAccounts[value2];if(existingId)return{...data,[accountRule.idColumn]:existingId};if(!plannedAccounts.has(value2)){plannedAccounts.add(value2);let accountData={[accountRule.matchColumn]:value2};for(let pair of accountRule.values??[]){let from=data[pair.from];if(!isEmpty2(from))accountData[pair.column]=from}accountInserts.push({entity:accountRule.entity,matchColumn:accountRule.matchColumn,matchValue:value2,data:accountData});for(let join7 of accountRule.joins??[])accountJoinInserts.push({entity:join7.entity,accountColumn:join7.accountColumn,values:join7.values,matchValue:value2})}return data},hasDefaults=Object.keys(createDefaults).length>0,stamp2=originColumn?{[originColumn]:originValue}:{},newRecord=(data)=>hasDefaults?{...createDefaults,...data,...stamp2}:{...data,...stamp2},existingByKey=new Map;if(dedupTargetField)for(let row of existing){let value2=row.row[dedupTargetField];if(!isEmpty2(value2))existingByKey.set(String(value2).trim(),row)}let claimedUnique=new Map;for(let field of uniqueFields){let taken=new Map;for(let existingRow of existing){let value2=existingRow.row[field];if(!isEmpty2(value2))taken.set(String(value2).trim(),existingRow.id)}claimedUnique.set(field,taken)}let ownsRow=(row)=>{if(!originColumn)return!0;let value2=row.row[originColumn];return String(value2??"")===String(originValue??"")},matchedIds=new Set,seenIds=new Set,seenInBatch=new Map;for(let sourceRecord of records){let broken=missingReferences(sourceRecord.data,references,referenceValues),resolvedRow=applyReferenceIds(sourceRecord.data,references,referenceIds,targetEntity),record3={...sourceRecord,data:fillIdentifier(resolvedRow.row)};if(record3.dedupKey!=null){let carried=existingByKey.get(record3.dedupKey);if(carried)seenIds.add(carried.id)}for(let link of resolvedRow.deferred){if(!dedupTargetField)continue;let matchValue=record3.data[dedupTargetField];if(isEmpty2(matchValue))continue;deferredLinks.push({matchColumn:dedupTargetField,matchValue:String(matchValue).trim(),writeIdTo:link.check.writeIdTo,lookupColumn:link.check.entityColumn,raw:link.raw,isList:Array.isArray(link.raw)})}let blocking=broken.find((b)=>b.check.onMissing==="skipRecord");if(blocking){let{check,value:value2}=blocking;issues.push({sourceIndex:record3.sourceIndex,kind:"missing_reference",message:typeof blocking.raw==="object"&&blocking.raw!==null?`${check.column} is not a single value but a whole object \u2014 ${value2}. Point the mapping at one field of it, e.g. \`${check.column}.code\`, and it will have something to match against ${check.entity}.${check.entityColumn}.`:`${check.column} is \u201C${value2}\u201D, which does not exist in ${check.entity}.${check.entityColumn} \u2014 this record is left out.`,dedupKey:record3.dedupKey,row:record3.data,reference:{column:check.column,value:value2,entity:check.entity,entityColumn:check.entityColumn}});continue}let keepReferences=()=>{for(let{check,value:value2}of broken){if(check.onMissing!=="create")continue;let token=`${check.entity}.${check.entityColumn}::${value2}`;if(plannedRefs.has(token))continue;plannedRefs.add(token);let data={};for(let pair of check.createValues??[]){let fromSource=isTemplateSource(pair.from)?renderTemplate(pair.from,record3.external,record3.parent):readSourceValue(record3.external,record3.parent,pair.from),value3=isEmpty2(fromSource)?record3.data[pair.from]:fromSource;if(!isEmpty2(value3))data[pair.column]=value3}referenceInserts.push({entity:check.entity,column:check.entityColumn,value:value2,...Object.keys(data).length>0?{data}:{}})}},uniqueCollision=(data,ownRowId)=>{for(let field of uniqueFields){let raw=data[field];if(isEmpty2(raw))continue;let value2=String(raw).trim(),holder2=claimedUnique.get(field)?.get(value2);if(holder2===void 0)continue;if(holder2!==null&&holder2===ownRowId)continue;return{field,value:value2}}return null},claimUnique=(data,rowId)=>{for(let field of uniqueFields){let raw=data[field];if(isEmpty2(raw))continue;claimedUnique.get(field)?.set(String(raw).trim(),rowId)}},collide=(field,value2)=>{issues.push({sourceIndex:record3.sourceIndex,kind:"duplicate_value",field,value:String(value2),message:`${field} is \u201C${value2}\u201D, which something else already has \u2014 ${field} has to be unique, so this record is left out.`,dedupKey:record3.dedupKey,row:record3.data})},missingRequired=requiredFields.filter((f)=>isEmpty2(record3.data[f])&&isEmpty2(createDefaults[f]));if(missingRequired.length>0){issues.push({sourceIndex:record3.sourceIndex,kind:"missing_required",field:missingRequired.join(", "),message:`Required field(s) empty: ${missingRequired.join(", ")}`,dedupKey:record3.dedupKey,row:record3.data});continue}if(!dedupConfigured){let clash2=uniqueCollision(record3.data,null);if(clash2){collide(clash2.field,clash2.value);continue}keepReferences(),claimUnique(record3.data,null);let withLogin=withAccount(record3,newRecord(record3.data));if(!withLogin)continue;creates.push({data:withLogin,sourceIndex:record3.sourceIndex});continue}if(record3.dedupKey==null){let clash2=uniqueCollision(record3.data,null);if(clash2){collide(clash2.field,clash2.value);continue}issues.push({sourceIndex:record3.sourceIndex,kind:"missing_dedup_key",message:"Deduplication field is empty on this record \u2014 it will be added as new."}),keepReferences(),claimUnique(record3.data,null);let withLogin=withAccount(record3,newRecord(record3.data));if(!withLogin)continue;creates.push({data:withLogin,sourceIndex:record3.sourceIndex});continue}let claimedBy=seenInBatch.get(record3.dedupKey);if(claimedBy){issues.push({sourceIndex:record3.sourceIndex,kind:"duplicate_in_batch",value:String(record3.dedupKey??""),message:`The source carries this key more than once: ${record3.dedupKey} \u2014 this occurrence is skipped.`,dedupKey:record3.dedupKey,row:record3.data,keptRow:claimedBy});continue}if(seenInBatch.set(record3.dedupKey,record3.data),writeMode==="replace"||writeMode==="append"){let clash2=uniqueCollision(record3.data,null);if(clash2&&!(writeMode==="replace"&&claimedUnique.get(clash2.field)?.get(clash2.value))){collide(clash2.field,clash2.value);continue}keepReferences(),claimUnique(record3.data,null);let withLogin=withAccount(record3,newRecord(record3.data));if(!withLogin)continue;creates.push({data:withLogin,sourceIndex:record3.sourceIndex});continue}let match=existingByKey.get(record3.dedupKey),clash=uniqueCollision(record3.data,match?.id??null);if(clash){collide(clash.field,clash.value);continue}if(keepReferences(),claimUnique(record3.data,match?.id??null),match)matchedIds.add(match.id),updates.push({id:match.id,data:{...record3.data,...stamp2},before:match.row,sourceIndex:record3.sourceIndex});else{let withLogin=withAccount(record3,newRecord(record3.data));if(!withLogin)continue;creates.push({data:withLogin,sourceIndex:record3.sourceIndex})}}if(writeMode==="replace")deletes.push(...existing);else if(writeMode==="upsert"&&missingPolicy!=="ignore"){let wasRead=(row)=>{if(!removalScope)return!0;let value2=row.row[removalScope.column];if(isEmpty2(value2))return!1;return removalScope.readValues.includes(String(value2).trim())},gone=existing.filter((row)=>!seenIds.has(row.id)).filter((row)=>ownsRow(row)).filter(wasRead);if(missingPolicy==="delete")deletes.push(...gone);else deactivates.push(...gone);departures.push(...gone)}if(writeMode==="upsert"&&missingPolicy==="ignore"){let owned=existing.filter((row)=>ownsRow(row)),unmatched=owned.filter((row)=>!seenIds.has(row.id)).length,drift=keyDriftWarning({creating:creates.length,unmatched,existing:owned.length});if(drift)warnings.push(drift)}if(!dedupConfigured&&writeMode==="upsert")warnings.push("No deduplication field is set. Nothing can be matched against what is already here, so every record is added as new and each run will duplicate the last.");if(dedupConfigured&&records.length>0&&records.every((r)=>r.dedupKey==null))warnings.push(`The deduplication field is empty on every record \u2014 it is probably not among the mapped fields. As it stands nothing can ever match: all ${records.length} records will be re-added on every run.`);if(writeMode==="append"&&dedupConfigured)warnings.push("Append mode ignores the deduplication field: records already here will be added again.");if(writeMode==="replace"){if(warnings.push(`Replace mode: all ${existing.length} rows currently in the table are deleted and rebuilt from the source. Row identifiers change, so anything referring to them by id breaks.`),dedupConfigured)warnings.push('A deduplication field is set, so "update existing" would keep the current rows and their identifiers. Replace mode is only necessary when records cannot be matched at all.')}if(deactivates.length>0&&(!missingColumn||isEmpty2(missingValue)))warnings.push('The policy for records missing from the source is "deactivate", but no column and value are configured \u2014 those rows will be left untouched.'),deactivates.length=0;return{creates,updates,deletes,deactivates,departures,deferredLinks,issues,warnings,referenceInserts,accountInserts,accountJoinInserts}}var SKIPPING_ISSUE_KINDS=new Set(["missing_required","duplicate_in_batch","duplicate_value","invalid_value","missing_reference"]);function skippedRecordCount(issues){let records=new Set;for(let issue of issues){if(!SKIPPING_ISSUE_KINDS.has(issue.kind))continue;if(issue.sourceIndex<0)continue;records.add(issue.sourceIndex)}return records.size}function summarize(ops,meta){let sampleSize=meta.sampleSize??8,sample=[...ops.creates.map((c)=>c.data),...ops.updates.map((u)=>u.data)].slice(0,sampleSize);return{targetEntity:meta.targetEntity,writeMode:meta.writeMode,totalFetched:meta.totalFetched,reportedTotal:meta.reportedTotal,toCreate:ops.creates.length,toUpdate:ops.updates.length,toSkip:skippedRecordCount(ops.issues),missing:ops.deletes.length+ops.deactivates.length,missingPolicy:meta.missingPolicy,existingCount:meta.existingCount,dedupConfigured:meta.dedupConfigured,issues:ops.issues,warnings:ops.warnings,referenceInserts:ops.referenceInserts,accountsToCreate:ops.accountInserts.length,sample}}function removalScopeFor(column,records){if(!column)return null;let readValues=new Set;for(let record3 of records){let value2=record3.data[column];if(!isEmpty2(value2))readValues.add(String(value2).trim())}return{column,readValues:[...readValues]}}init_AccountLink();import{and as and20,eq as eq34,inArray as inArray14}from"drizzle-orm";var INSERT_CHUNK=500,DELETE_CHUNK=1000;function validateAll(ops,columns,validatePayload2){let issues=[];for(let op of ops.creates){let result=validatePayload2(op.data,columns,!1);if(!result.valid)issues.push({sourceIndex:op.sourceIndex,kind:"invalid_value",message:result.errors.map((e)=>`${e.field}: ${e.message}`).join("; "),row:op.data})}for(let op of ops.updates){let result=validatePayload2(op.data,columns,!0);if(!result.valid)issues.push({sourceIndex:op.sourceIndex,kind:"invalid_value",message:result.errors.map((e)=>`${e.field}: ${e.message}`).join("; "),row:op.data})}return issues}function referenceRow(columns,column,value2,extra){let row={...extra,[column]:value2},unfillable=[];for(let col8 of columns){let name2=camel(col8.name);if(name2===column)continue;if(SYSTEM_COLUMNS.has(name2))continue;if(row[name2]!==void 0)continue;if(!(col8.notNull===!0&&col8.default===void 0&&!col8.defaultRaw&&!col8.nullable))continue;if(TEXTUAL_TYPES.has(String(col8.type)))row[name2]=value2;else unfillable.push(name2)}return unfillable.length>0?{ok:!1,unfillable}:{ok:!0,row}}var SYSTEM_COLUMNS=new Set(["id","createdAt","updatedAt","createdBy","updatedBy"]),TEXTUAL_TYPES=new Set(["text","varchar","char"]),camel=(s)=>s.replace(/_([a-z])/g,(_m,l)=>l.toUpperCase());function linkAccount(data,rule,idByValue){let value2=data[rule.matchFrom];if(value2==null)return{};let id=idByValue.get(String(value2).trim());return id?{[rule.idColumn]:id}:{}}function groupDeferred(links){let groups=new Map;for(let link of links){let key2=`${link.writeIdTo}|${link.lookupColumn}|${link.matchColumn}`,group=groups.get(key2)??{writeIdTo:link.writeIdTo,lookupColumn:link.lookupColumn,matchColumn:link.matchColumn,links:[]};group.links.push(link),groups.set(key2,group)}return[...groups.values()]}async function applyWrites(deps,input){let{ops,targetEntity,userId}=input,scope=input.scopeConditions??[],writeIssues=[],cancelled=()=>input.isCancelled?.()===!0,invalid=validateAll(ops,deps.columns,deps.validatePayload);if(invalid.length>0)return{ok:!1,failure:{kind:"validation_failed",message:`${invalid.length} record(s) did not pass validation; nothing was written.`,issues:invalid}};let refGroups=new Map;for(let insert of ops.referenceInserts){let resolved=deps.resolveEntity?.(insert.entity);if(!resolved)return{ok:!1,failure:{kind:"write_failed",message:`This mapping would add \u201C${insert.value}\u201D to "${insert.entity}", which does not exist.`}};let built=referenceRow(resolved.columns,insert.column,insert.value,insert.data);if(!built.ok)return{ok:!1,failure:{kind:"write_failed",message:`\u201C${insert.value}\u201D cannot be added to "${insert.entity}" automatically: ${built.unfillable.join(", ")} is required and cannot be filled from a ${insert.column}. Add the missing ${insert.entity} by hand, or set this reference to leave those records out.`}};let group=refGroups.get(insert.entity)??{table:resolved.table,rows:[]};group.rows.push(built.row),refGroups.set(insert.entity,group)}let accountRule=input.account??null,accountEntity=ops.accountInserts[0]?.entity,accountTable=accountEntity?deps.resolveEntity?.(accountEntity):null;if(ops.accountInserts.length>0&&(!accountRule||!accountTable))return{ok:!1,failure:{kind:"write_failed",message:accountRule?`This import creates accounts in "${accountEntity}", which does not exist.`:"This import creates accounts but no password was supplied, so nothing was written."}};if(cancelled())return{ok:!1,failure:{kind:"cancelled",message:"Cancelled before writing."}};let idCol=deps.resolveCol("id");if(!idCol)return{ok:!1,failure:{kind:"write_failed",message:`Target "${targetEntity}" has no id column; the engine cannot address its rows.`}};let preImages=[],now=new Date,deactivating=ops.deactivates.length>0&&input.missingColumn&&input.missingValue!=null?{column:input.missingColumn,value:input.missingValue}:null;try{let linksMade=0,linksUnresolved=0;return{ok:!0,...await deps.db.transaction(async(tx)=>{let created=0,accountsCreated=0,referencesCreated=0,updated=0,deleted=0,deactivated=0,accountsClosed=0;if(ops.deletes.length>0){let ids=ops.deletes.map((r)=>r.id);for(let i=0;i<ids.length;i+=DELETE_CHUNK){if(cancelled())throw new CancelledError;let chunk=ids.slice(i,i+DELETE_CHUNK),where=scope.length?and20(inArray14(idCol,chunk),...scope):inArray14(idCol,chunk),removed=await tx.delete(deps.drizzleTable).where(where).returning();deleted+=removed.length;for(let row of removed)preImages.push({targetEntity,rowId:String(row.id),action:"delete",before:row,after:null})}}for(let op of ops.updates){if(cancelled())throw new CancelledError;let where=scope.length?and20(eq34(idCol,op.id),...scope):eq34(idCol,op.id),written=await tx.update(deps.drizzleTable).set({...op.data,updatedAt:now,updatedBy:userId}).where(where).returning();if(written.length===0)throw new ScopeError(op.id);updated+=written.length,preImages.push({targetEntity,rowId:op.id,action:"update",before:op.before,after:written[0]??null})}if(deactivating){let ids=ops.deactivates.map((r)=>r.id);for(let i=0;i<ids.length;i+=DELETE_CHUNK){if(cancelled())throw new CancelledError;let chunk=ids.slice(i,i+DELETE_CHUNK),where=scope.length?and20(inArray14(idCol,chunk),...scope):inArray14(idCol,chunk),written=await tx.update(deps.drizzleTable).set({[deactivating.column]:deactivating.value,updatedAt:now,updatedBy:userId}).where(where).returning();deactivated+=written.length;let beforeById=new Map(ops.deactivates.map((r)=>[r.id,r.row]));for(let row of written){let id=String(row.id);preImages.push({targetEntity,rowId:id,action:"deactivate",before:beforeById.get(id)??null,after:row})}}}if(deps.accountClosure){let written=new Set(preImages.filter((image)=>image.action==="delete"||image.action==="deactivate").map((image)=>image.rowId)),removedRows=ops.departures.filter((row)=>written.has(row.id)).map((row)=>row.row),leaverIds=accountIdsIn(removedRows,deps.accountClosure.userColumn);if(leaverIds.length>0){let closed=await closeAccounts({db:tx,usersTable:deps.accountClosure.usersTable,sessionsTable:deps.accountClosure.sessionsTable,atomic:!0,onError:(error3)=>{deps.logger.error("[Integrations] A departure\u2019s account stayed open",{error:error3})}},leaverIds);accountsClosed=closed.closed;for(let userId2 of closed.closedIds)preImages.push({targetEntity:deps.accountClosure.accountEntity,rowId:userId2,action:"update",before:{isActive:!0},after:{isActive:!1}});if(closed.closed>0&&!closed.signedOut)writeIssues.push({sourceIndex:-1,kind:"account_sessions_not_cleared",value:String(closed.closed),message:`${closed.closed} account(s) behind removed records were deactivated, but their open sessions could not be cleared \u2014 those people stay signed in until the session expires.`})}}for(let[entity,group]of refGroups){let rows=group.rows.map((r)=>({...r,createdAt:now,updatedAt:now,createdBy:userId}));for(let i=0;i<rows.length;i+=INSERT_CHUNK){if(cancelled())throw new CancelledError;let written=await tx.insert(group.table).values(rows.slice(i,i+INSERT_CHUNK)).returning();referencesCreated+=written.length;for(let row of written)preImages.push({targetEntity:entity,rowId:row.id?String(row.id):null,action:"create",before:null,after:row})}}let accountIdByValue=new Map;if(accountRule&&accountTable&&ops.accountInserts.length>0){let rows=ops.accountInserts.map((account)=>({...account.data,[accountRule.passwordColumn]:accountRule.passwordHash,createdAt:now,updatedAt:now,createdBy:userId}));for(let i=0;i<rows.length;i+=INSERT_CHUNK){if(cancelled())throw new CancelledError;let written=await tx.insert(accountTable.table).values(rows.slice(i,i+INSERT_CHUNK)).returning();accountsCreated+=written.length;for(let row of written){let value2=row[accountRule.matchColumn];if(row.id!=null&&value2!=null)accountIdByValue.set(String(value2).trim(),String(row.id));preImages.push({targetEntity:ops.accountInserts[0]?.entity??"",rowId:row.id?String(row.id):null,action:"create",before:null,after:row})}}}if(accountRule&&ops.accountJoinInserts.length>0){let byEntity=new Map;for(let join7 of ops.accountJoinInserts){let accountId=accountIdByValue.get(join7.matchValue);if(!accountId){writeIssues.push({sourceIndex:-1,kind:"account_role_missing",message:`No account was found for \u201C${join7.matchValue}\u201D, so its ${join7.entity} row was not written \u2014 that person can sign in and do nothing.`});continue}let rows=byEntity.get(join7.entity)??[];rows.push({...join7.values,[join7.accountColumn]:accountId,createdAt:now,updatedAt:now,createdBy:userId}),byEntity.set(join7.entity,rows)}for(let[entity,rows]of byEntity){let resolved=deps.resolveEntity?.(entity);if(!resolved){writeIssues.push({sourceIndex:-1,kind:"account_role_missing",message:`The \u201C${entity}\u201D table is not available here, so ${rows.length} role membership(s) were not written. The accounts exist but hold no role.`});continue}for(let i=0;i<rows.length;i+=INSERT_CHUNK){if(cancelled())throw new CancelledError;let written=await tx.insert(resolved.table).values(rows.slice(i,i+INSERT_CHUNK)).returning();for(let row of written)preImages.push({targetEntity:entity,rowId:row.id?String(row.id):null,action:"create",before:null,after:row})}}}if(ops.creates.length>0){let rows=ops.creates.map((c)=>({...c.data,...accountRule&&c.data[accountRule.idColumn]==null?linkAccount(c.data,accountRule,accountIdByValue):{},createdAt:now,updatedAt:now,createdBy:userId}));for(let i=0;i<rows.length;i+=INSERT_CHUNK){if(cancelled())throw new CancelledError;let chunk=rows.slice(i,i+INSERT_CHUNK),written=await tx.insert(deps.drizzleTable).values(chunk).returning();created+=written.length;for(let row of written)preImages.push({targetEntity,rowId:row.id?String(row.id):null,action:"create",before:null,after:row})}}if(ops.deferredLinks.length>0)for(let group of groupDeferred(ops.deferredLinks)){if(cancelled())throw new CancelledError;let lookupCol=deps.resolveCol(group.lookupColumn),matchCol=deps.resolveCol(group.matchColumn),targetCol=deps.resolveCol(group.writeIdTo);if(!lookupCol||!matchCol||!targetCol){deps.logger.error(`[Integrations] Cannot fill "${group.writeIdTo}": ${[!lookupCol?group.lookupColumn:null,!matchCol?group.matchColumn:null,!targetCol?group.writeIdTo:null].filter(Boolean).join(", ")} is not a column of "${targetEntity}".`);continue}let rows=await tx.select({v:lookupCol,id:idCol}).from(deps.drizzleTable),idByValue={};for(let row of rows){if(row.v==null||row.v===""||row.id==null)continue;let key2=String(row.v).trim();if(!idByValue[key2])idByValue[key2]=String(row.id)}let imageByMatch=new Map;for(let image of preImages){let value2=image.after?.[group.matchColumn];if(value2!=null)imageByMatch.set(String(value2).trim(),image)}for(let link of group.links){let resolution=resolveReference(link.raw,idByValue);if(resolution.ids.length===0){linksUnresolved+=1;continue}let value2=link.isList?resolution.ids:resolution.ids[0],where=scope.length?and20(eq34(matchCol,link.matchValue),...scope):eq34(matchCol,link.matchValue);if((await tx.update(deps.drizzleTable).set({[group.writeIdTo]:value2}).where(where).returning()).length===0){linksUnresolved+=1;continue}linksMade+=1;let image=imageByMatch.get(String(link.matchValue).trim());if(image?.after)image.after={...image.after,[group.writeIdTo]:value2}}}return{created,accountsCreated,referencesCreated,updated,deleted,deactivated,accountsClosed,linksMade,linksUnresolved}}),preImages,issues:writeIssues}}catch(error3){if(error3 instanceof CancelledError)return{ok:!1,failure:{kind:"cancelled",message:"Cancelled while writing; the transaction was rolled back and nothing changed."}};if(error3 instanceof ScopeError)return{ok:!1,failure:{kind:"write_failed",message:`Row ${error3.rowId} is outside what this account may write. Nothing was written.`}};return deps.logger.error(`[Integrations] Write transaction failed for "${targetEntity}": ${reasonOf2(error3)}`),{ok:!1,failure:{kind:"write_failed",message:"The database rejected this import; it was rolled back and nothing changed."}}}}function reasonOf2(error3){if(!(error3 instanceof Error))return String(error3);let extra=error3;return[error3.message,extra.detail?`detail: ${String(extra.detail)}`:"",extra.constraint?`constraint: ${String(extra.constraint)}`:"",extra.hint?`hint: ${String(extra.hint)}`:"",extra.code?`code: ${String(extra.code)}`:""].filter(Boolean).join(" \u2014 ")}class CancelledError extends Error{constructor(){super("cancelled");this.name="CancelledError"}}class ScopeError extends Error{rowId;constructor(rowId){super(`row ${rowId} outside scope`);this.rowId=rowId;this.name="ScopeError"}}var DAY_MS=86400000;async function executeRun(context,deps,lock){let{mapping,source}=context,empty={runId:null,status:"failed",failure:null,plan:null,created:0,accountsCreated:0,referencesCreated:0,updated:0,deleted:0,deactivated:0,externalCalls:0,issues:[]},opened=await deps.repo.openRun({mappingId:mapping.id,targetEntity:mapping.targetEntity,writeMode:mapping.writeMode,trigger:context.trigger,userId:context.userId});if(!opened.ok)return{...empty,failure:{kind:"write_failed",message:`Could not open a run record: ${opened.error}`}};let runId=opened.runId;deps.onRunOpened?.(runId);let cancelChecked=0,cancelSeen=!1,isCancelled=()=>{if(cancelSeen)return!0;if(deps.abortSignal?.aborted)return cancelSeen=!0,!0;if(!lock.isHeld())return cancelSeen=!0,!0;return!1},refreshCancelFlag=async()=>{if(cancelChecked++,await deps.repo.isCancelRequested(runId))cancelSeen=!0},progress=(p)=>deps.publishProgress?.({runId,...p}),preImagesStored=!0,finish=async(outcome)=>{let revertible=outcome.status==="succeeded"&&preImagesStored;return await deps.repo.closeRun(runId,{status:outcome.status,totalFetched:outcome.plan?.totalFetched??0,sourceReportedTotal:outcome.plan?.reportedTotal??null,createdCount:outcome.created,accountsCreatedCount:outcome.accountsCreated,referencesCreatedCount:outcome.referencesCreated,updatedCount:outcome.updated,skippedCount:outcome.plan?.toSkip??0,deletedCount:outcome.deleted,deactivatedCount:outcome.deactivated,errorCount:outcome.failure?1:0,externalCallCount:outcome.externalCalls,issues:outcome.issues,errors:outcome.failure?[outcome.failure.message]:[],finishedAt:new Date,preImagesExpireAt:revertible?new Date(Date.now()+(deps.retainPreImagesMs??30*DAY_MS)):null}),progress({phase:"done",message:outcome.failure?.message}),{runId,...outcome}};progress({phase:"fetching"});let nodes=context.endpoints.map((e)=>({id:e.id,paramBindings:e.paramBindings??void 0})),order=planFetchOrder(nodes,[context.rootEndpointId]);if(!order.ok){let failure=order.reason==="cycle"?{kind:"cycle_detected",message:`These endpoints depend on each other in a loop (${order.chain.join(" \u2192 ")}), so there is no correct place to start.`,chain:order.chain}:{kind:"fetch_failed",message:`"${order.requiredBy}" takes a parameter from an endpoint that no longer exists (${order.missing}).`};return finish({...empty,failure,status:"failed"})}let bearerToken=null;if(context.oauth){let token=await getAccessToken(context.oauth,deps.tokenCache,{fetchImpl:deps.fetchImpl});if(!token.ok)return finish({...empty,failure:{kind:"fetch_failed",message:token.error}});bearerToken=token.accessToken}let byId=new Map(context.endpoints.map((e)=>[e.id,e])),parents={},entityValues={},externalCalls=0,rootRecords=[],fetchWarnings=[],reportedTotal2=null;for(let endpointId of order.order){let endpoint=byId.get(endpointId);if(!endpoint)continue;if(await refreshCancelFlag(),isCancelled())return finish({...empty,status:"cancelled",externalCalls,failure:{kind:"cancelled",message:"Cancelled before anything was written."}});for(let key2 of entityValueKeys(endpoint.paramBindings??void 0)){if(entityValues[key2])continue;let[entity,field]=key2.split(".");entityValues[key2]=await deps.repo.readEntityColumn(entity,field)}let collected=await collectEndpoint(source,endpoint,{parents,entityValues},{fetchImpl:deps.fetchImpl,bearerToken,maxConcurrent:deps.maxConcurrentFanout,maxRecords:deps.maxRecordsPerRun,isCancelled,onProgress:(n)=>progress({phase:"fetching",processed:n})});if(externalCalls+=collected.callCount,!collected.ok){let cancelled=isCancelled();return finish({...empty,externalCalls,status:cancelled?"cancelled":"failed",failure:cancelled?{kind:"cancelled",message:"Cancelled before anything was written."}:{kind:"fetch_failed",message:collected.error}})}if(collected.partialFailures?.length)fetchWarnings.push(...collected.partialFailures.map((failure)=>`Could not read part of this source \u2014 ${failure}`));if(parents[endpointId]=collected.records.map((r)=>r.record),endpointId===context.rootEndpointId)rootRecords=collected.records,reportedTotal2=collected.reportedTotal}for(let id of Object.keys(parents))if(!(id===context.rootEndpointId||context.endpoints.some((e)=>parentEndpointIds(e.paramBindings??void 0).includes(id))))delete parents[id];let verdict=destructiveGate({writeMode:mapping.writeMode,missingPolicy:mapping.missingPolicy,fetched:rootRecords.length,reported:reportedTotal2,partialFailures:fetchWarnings.length,scopedRemoval:Boolean(mapping.removalScopeColumn)});if(!verdict.allowed)return finish({...empty,externalCalls,failure:{kind:verdict.kind,message:fetchWarnings.length?`${verdict.message}
|
|
545
545
|
|
|
546
546
|
${fetchWarnings.join(`
|
|
547
|
-
`)}`:verdict.message,fetched:rootRecords.length,reported:reportedTotal2??0}});progress({phase:"mapping",total:rootRecords.length});let mapped3=buildMappedRecords(rootRecords.map((r)=>r.record),mapping.fieldMappings,mapping.dedupTargetField,rootRecords.map((r)=>r.parent));if(await refreshCancelFlag(),isCancelled())return finish({...empty,externalCalls,status:"cancelled",failure:{kind:"cancelled",message:"Cancelled before anything was written."}});let existing=await deps.repo.readExistingRows(mapping.targetEntity),referenceValues=await loadReferenceValues(mapping.references??[],(entity,column)=>deps.repo.readEntityColumn(entity,column)),references=await loadReferenceIds(mapping.references??[],mapping.targetEntity,(entity,column)=>deps.repo.readEntityPairsBy(entity,column)),referenceIds=references.ids,existingAccounts=mapping.accountRule?await deps.repo.readEntityIdsBy(mapping.accountRule.entity,mapping.accountRule.matchColumn):{},ops=planWrites({records:mapped3.records,existing,dedupTargetField:mapping.dedupTargetField,writeMode:mapping.writeMode,missingPolicy:mapping.missingPolicy,missingColumn:mapping.missingColumn,missingValue:mapping.missingValue,removalScope:removalScopeFor(mapping.removalScopeColumn,mapped3.records),originColumn:mapping.originColumn,originValue:mapping.originValue,requiredFields:deps.repo.requiredFields(mapping.targetEntity),uniqueFields:deps.repo.uniqueFields(mapping.targetEntity),createDefaults:context.createDefaults??{},accountRule:mapping.accountRule??null,existingAccounts,references:mapping.references??[],referenceValues,referenceIds,targetEntity:mapping.targetEntity});ops.issues.unshift(...mapped3.issues),ops.warnings.push(...ambiguityWarnings(references.ambiguous)),ops.warnings.unshift(...fetchWarnings);let plan=summarize(ops,{targetEntity:mapping.targetEntity,writeMode:mapping.writeMode,missingPolicy:mapping.missingPolicy,totalFetched:rootRecords.length,reportedTotal:reportedTotal2,existingCount:existing.length,dedupConfigured:Boolean(mapping.dedupTargetField)}),volume=checkPlanVolume({removing:ops.deletes.length+ops.deactivates.length,existing:existing.length,maxShare:mapping.maxRemovalShare??0});if(!volume.allowed)return finish({...empty,externalCalls,failure:{kind:volume.kind,message:volume.message,fetched:rootRecords.length,reported:reportedTotal2??0}});progress({phase:"writing",total:ops.creates.length+ops.updates.length});let applied=await applyWrites(deps.writer,{ops,targetEntity:mapping.targetEntity,userId:context.userId,account:mapping.accountRule&&context.accountPasswordHash?{idColumn:mapping.accountRule.idColumn,matchColumn:mapping.accountRule.matchColumn,matchFrom:mapping.accountRule.matchFrom,passwordColumn:mapping.accountRule.passwordColumn,passwordHash:context.accountPasswordHash}:null,missingColumn:mapping.missingColumn,missingValue:mapping.missingValue,isCancelled});if(!applied.ok){let failureIssues=applied.failure.kind==="validation_failed"?applied.failure.issues:[];return finish({...empty,plan,externalCalls,issues:[...ops.issues,...failureIssues],status:applied.failure.kind==="cancelled"?"cancelled":"failed",failure:applied.failure})}return preImagesStored=await deps.repo.savePreImages(runId,applied.preImages),deps.logger.info("[Integrations] Run finished",{runId,mappingId:mapping.id,targetEntity:mapping.targetEntity,created:applied.created,accountsCreated:applied.accountsCreated,referencesCreated:applied.referencesCreated,updated:applied.updated,deleted:applied.deleted,deactivated:applied.deactivated,externalCalls,cancelChecks:cancelChecked}),finish({...empty,status:"succeeded",plan,failure:null,created:applied.created,accountsCreated:applied.accountsCreated,referencesCreated:applied.referencesCreated,updated:applied.updated,deleted:applied.deleted,deactivated:applied.deactivated,externalCalls,issues:[...ops.issues,...applied.issues,...orphanIssues(applied)]})}function orphanIssues(applied){let note=orphanLinkNote({made:applied.linksMade,unresolved:applied.linksUnresolved});return note?[{sourceIndex:-1,kind:"parent_unresolved",message:note}]:[]}var RANGES=[[0,59],[0,23],[1,31],[1,12],[0,6]],NAMES=["minute","hour","day of month","month","day of week"];function expandField(raw,[min,max]){let values=new Set;for(let part of raw.split(",")){let[spec,stepText]=part.split("/"),step=stepText===void 0?1:Number(stepText);if(!Number.isInteger(step)||step<1)return null;let from,to;if(spec==="*")from=min,to=max;else if(spec?.includes("-")){let[a,b]=spec.split("-").map(Number);if(!Number.isInteger(a)||!Number.isInteger(b))return null;from=a,to=b}else{let one=Number(spec);if(!Number.isInteger(one))return null;from=one,to=stepText===void 0?one:max}if(from<min||to>max||from>to)return null;for(let v=from;v<=to;v+=step)values.add(v)}return values.size>0?values:null}function parseCron2(expression){let parts=expression.trim().split(/\s+/);if(parts.length!==5)return{ok:!1,error:`A schedule has five parts \u2014 minute hour day-of-month month day-of-week \u2014 and this has ${parts.length}. "0 2 * * *" is every night at 02:00.`};let sets2=[];for(let i=0;i<5;i++){let field=expandField(parts[i],RANGES[i]);if(!field)return{ok:!1,error:`The ${NAMES[i]} part ("${parts[i]}") is not something I can read.`};sets2.push(field)}return{ok:!0,fields:{minutes:sets2[0],hours:sets2[1],daysOfMonth:sets2[2],months:sets2[3],daysOfWeek:sets2[4],anyDayOfMonth:parts[2]==="*",anyDayOfWeek:parts[4]==="*"}}}function partsIn2(when,timeZone){let formatter=new Intl.DateTimeFormat("en-US",{timeZone,hour12:!1,year:"numeric",month:"2-digit",day:"2-digit",hour:"2-digit",minute:"2-digit",weekday:"short"}),parts={};for(let part of formatter.formatToParts(when))parts[part.type]=part.value;let weekdays=["Sun","Mon","Tue","Wed","Thu","Fri","Sat"];return{year:Number(parts.year),month:Number(parts.month),day:Number(parts.day),hour:Number(parts.hour)%24,minute:Number(parts.minute),weekday:Math.max(0,weekdays.indexOf(parts.weekday??"Sun"))}}function matchesCron(fields,when,timeZone){let at=partsIn2(when,timeZone);if(!fields.minutes.has(at.minute))return!1;if(!fields.hours.has(at.hour))return!1;if(!fields.months.has(at.month))return!1;let dayOfMonthMatches=fields.daysOfMonth.has(at.day),dayOfWeekMatches=fields.daysOfWeek.has(at.weekday);if(fields.anyDayOfMonth&&fields.anyDayOfWeek)return!0;if(fields.anyDayOfMonth)return dayOfWeekMatches;if(fields.anyDayOfWeek)return dayOfMonthMatches;return dayOfMonthMatches||dayOfWeekMatches}var CATCH_UP_MINUTES=1500,MINUTE_MS=60000;function isDue(input){let parsed=parseCron2(input.expression);if(!parsed.ok)return{due:!1,error:parsed.error};let zone=input.timeZone?.trim()||"UTC",nowMinute=Math.floor(input.now.getTime()/MINUTE_MS),lookBack=0;if(input.lastRunAt){let lastMinute=Math.floor(input.lastRunAt.getTime()/MINUTE_MS);if(lastMinute>=nowMinute)return{due:!1};lookBack=Math.min(nowMinute-lastMinute-1,CATCH_UP_MINUTES)}try{for(let back=0;back<=lookBack;back++){let at=new Date((nowMinute-back)*MINUTE_MS);if(matchesCron(parsed.fields,at,zone))return{due:!0}}}catch{return{due:!1,error:`"${zone}" is not a timezone this system knows.`}}return{due:!1}}function describeCron(expression,timeZone){let parsed=parseCron2(expression);if(!parsed.ok)return parsed.error;let zone=timeZone?.trim()||"UTC",parts=expression.trim().split(/\s+/),[minute,hour,dayOfMonth,month,dayOfWeek]=parts,everyMinutes=minute?.startsWith("*/")?Number(minute.slice(2)):null;if(everyMinutes&&hour==="*"&&dayOfMonth==="*"&&month==="*"&&dayOfWeek==="*")return`Every ${everyMinutes} minutes.`;let time2=minute!==void 0&&hour!==void 0&&!minute.includes("*")&&!hour.includes("*")?`${hour.padStart(2,"0")}:${minute.padStart(2,"0")}`:null,DAYS=["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"];if(time2&&dayOfWeek!=="*"&&dayOfMonth==="*")return`Every ${[...parsed.fields.daysOfWeek??[]].map((d)=>DAYS[d]).join(", ")} at ${time2} (${zone}).`;if(time2&&dayOfMonth!=="*")return`On day ${dayOfMonth} of the month at ${time2} (${zone}).`;if(time2)return`Every day at ${time2} (${zone}).`;return`On the schedule "${expression}" (${zone}).`}function nextFireTimes(input){let parsed=parseCron2(input.expression);if(!parsed.ok)return{times:[],error:parsed.error};let zone=input.timeZone?.trim()||"UTC",count2=Math.max(1,Math.min(input.count??5,50)),horizon=Math.max(1,Math.min(input.horizonDays??400,400)),times=[],startMinute=Math.floor(input.from.getTime()/MINUTE_MS)+1,limit=horizon*24*60;try{for(let offset=0;offset<limit&×.length<count2;offset+=1){let when=new Date((startMinute+offset)*MINUTE_MS);if(matchesCron(parsed.fields,when,zone))times.push(when)}}catch{return{times:[],error:`"${zone}" is not a timezone this system knows.`}}return{times}}var MINUTE2=60000;function dueMappings(mappings,now){let due=[],problems=[];for(let mapping of mappings){if(!mapping.scheduleCron?.trim())continue;let answer=isDue({expression:mapping.scheduleCron,timeZone:mapping.scheduleTimezone,now,lastRunAt:mapping.lastRunAt??null});if(answer.error){problems.push({mapping,error:answer.error});continue}if(answer.due)due.push(mapping)}return{due,problems}}function startScheduler(deps){let now=deps.now??(()=>new Date),reported=new Set,stopped=!1,tick=async()=>{if(stopped)return;let mappings;try{mappings=await deps.listScheduled()}catch(error3){deps.logger.error("[Integrations] Could not read the schedules",{error:error3 instanceof Error?error3.message:String(error3)});return}let{due,problems}=dueMappings(mappings,now());for(let problem of problems){let token=`${problem.mapping.id}:${problem.error}`;if(reported.has(token))continue;reported.add(token),deps.logger.error(`[Integrations] "${problem.mapping.name}" has a schedule that cannot run: ${problem.error}`)}for(let mapping of due)try{if(await deps.startRun(mapping.id))deps.logger.info(`[Integrations] Scheduled import started: ${mapping.name}`)}catch(error3){deps.logger.error(`[Integrations] Scheduled import failed to start: ${mapping.name}`,{error:error3 instanceof Error?error3.message:String(error3)})}},timer=setInterval(()=>{tick()},MINUTE2);if(typeof timer==="object"&&timer&&"unref"in timer)timer.unref();return{stop:()=>{stopped=!0,clearInterval(timer)}}}var asObject=(value2)=>value2&&typeof value2==="object"&&!Array.isArray(value2)?value2:null,asArray=(value2)=>Array.isArray(value2)?value2:[],text=(value2)=>{if(value2==null)return null;let s=String(value2);return s===""?null:s};function buildTransferDocument(input){let{source,endpoints,mappings}=input,nameById=new Map;for(let endpoint of endpoints){let id=text(endpoint.id),name2=text(endpoint.name);if(id&&name2)nameById.set(id,name2)}return{version:1,exportedAt:input.exportedAt,secretsExcluded:!0,connection:{name:String(source.name??""),slug:String(source.slug??""),baseUrl:String(source.baseUrl??""),description:text(source.description),authType:String(source.authType??"none"),authHeaderName:text(source.authHeaderName),authQueryParam:text(source.authQueryParam),extraHeaders:asObject(source.extraHeaders),tokenUrl:text(source.tokenUrl),oauthGrantType:text(source.oauthGrantType),oauthClientId:text(source.oauthClientId),oauthUsername:text(source.oauthUsername),oauthScope:text(source.oauthScope),oauthClientAuth:text(source.oauthClientAuth),tlsVerify:source.tlsVerify!==!1,timeoutMs:typeof source.timeoutMs==="number"?source.timeoutMs:null,enabled:source.enabled!==!1},endpoints:endpoints.map((endpoint)=>({name:String(endpoint.name??""),tag:String(endpoint.tag??""),method:String(endpoint.method??"GET"),path:String(endpoint.path??""),description:text(endpoint.description),queryParams:asObject(endpoint.queryParams),requestBody:asObject(endpoint.requestBody),responseRootPath:text(endpoint.responseRootPath),treeChildrenPath:text(endpoint.treeChildrenPath),pagination:asObject(endpoint.pagination),paramBindings:asArray(endpoint.paramBindings).map((binding)=>{if(binding.from!=="endpoint")return{...binding};let{endpointId,...rest3}=binding;return{...rest3,endpointName:nameById.get(String(endpointId))??null}}),enabled:endpoint.enabled!==!1})),mappings:mappings.map((mapping)=>({name:String(mapping.name??""),endpointName:nameById.get(String(mapping.endpointId))??null,targetEntity:String(mapping.targetEntity??""),fieldMappings:asArray(mapping.fieldMappings),referenceChecks:asArray(mapping.referenceChecks),dedupSourceField:text(mapping.dedupSourceField),dedupTargetField:text(mapping.dedupTargetField),writeMode:String(mapping.writeMode??"upsert")??"upsert",missingRecordPolicy:String(mapping.missingRecordPolicy??"ignore")??"ignore",missingRecordColumn:text(mapping.missingRecordColumn),missingRecordValue:text(mapping.missingRecordValue),removalScopeColumn:text(mapping.removalScopeColumn),passwordTargetColumn:text(mapping.passwordTargetColumn),accountRule:mapping.accountRule??null,scheduleEnabled:mapping.scheduleEnabled===!0,scheduleCron:text(mapping.scheduleCron),scheduleTimezone:text(mapping.scheduleTimezone),enabled:mapping.enabled!==!1}))}}function validateTransferDocument(value2){let problems=[],doc=asObject(value2);if(!doc)return{ok:!1,document:null,problems:[{path:"",message:"This is not an integration export file."}]};if(typeof doc.version!=="number")problems.push({path:"version",message:"The file does not say which format it is in."});else if(doc.version>1)problems.push({path:"version",message:`This file was written by a newer version (${doc.version}); this one understands up to 1.`});let connection=asObject(doc.connection);if(!connection)problems.push({path:"connection",message:"The file has no connection."});else{if(!text(connection.name))problems.push({path:"connection.name",message:"Missing name."});if(!text(connection.slug))problems.push({path:"connection.slug",message:"Missing slug."});if(!text(connection.baseUrl))problems.push({path:"connection.baseUrl",message:"Missing base URL."})}let endpoints=asArray(doc.endpoints),endpointNames=new Set;return endpoints.forEach((endpoint,index)=>{let name2=text(endpoint.name);if(!name2){problems.push({path:`endpoints[${index}].name`,message:"Missing name."});return}if(endpointNames.has(name2))problems.push({path:`endpoints[${index}].name`,message:`Two endpoints are called "${name2}"; names are how the rest of the file refers to them.`});if(endpointNames.add(name2),!text(endpoint.path))problems.push({path:`endpoints[${index}].path`,message:"Missing path."});if(!text(endpoint.tag))problems.push({path:`endpoints[${index}].tag`,message:"Missing tag."})}),endpoints.forEach((endpoint,index)=>{asArray(endpoint.paramBindings).forEach((binding,bindingIndex)=>{if(binding.from!=="endpoint")return;let target2=text(binding.endpointName);if(!target2||!endpointNames.has(target2))problems.push({path:`endpoints[${index}].paramBindings[${bindingIndex}]`,message:`Takes its value from an endpoint called "${target2??"?"}", which is not in this file.`})})}),asArray(doc.mappings).forEach((mapping,index)=>{if(!text(mapping.name))problems.push({path:`mappings[${index}].name`,message:"Missing name."});if(!text(mapping.targetEntity))problems.push({path:`mappings[${index}].targetEntity`,message:"Missing target table."});let endpointName=text(mapping.endpointName);if(!endpointName||!endpointNames.has(endpointName))problems.push({path:`mappings[${index}].endpointName`,message:`Reads from an endpoint called "${endpointName??"?"}", which is not in this file.`})}),{ok:problems.length===0,document:problems.length===0?doc:null,problems}}init_crypto();init_routeCollector();init_utils5();init_utils6();init_godminGuard();init_esm3();init_utils5();async function testConnection(chain,tokenCache,deps={}){let{source,oauth}=chain,fetchImpl=deps.fetchImpl;if(oauth){tokenCache.invalidate(source.id);let token=await getAccessToken(oauth,tokenCache,{fetchImpl});return token.ok?{ok:!0,detail:`A token was issued (${token.accessToken.length} characters). The credentials and token address are valid.`}:{ok:!1,error:token.error}}let built=buildUrl(source.baseUrl,"");if(!built.ok)return{ok:!1,error:built.error};let{headers}=applyAuth(source,built.url),outcome=await fetchOnce(source,{method:"GET",url:built.url,headers},{fetchImpl});if(outcome.status===0)return{ok:!1,error:outcome.error};return{ok:outcome.status<500,detail:`The address answered: HTTP ${outcome.status}.`,error:outcome.status>=500?outcome.error:void 0}}var PREVIEW_LIMIT=1500;async function sampleEndpoint(source,endpoint,pathParams,tokenCache,oauth,deps={}){let fetchImpl=deps.fetchImpl,bearerToken=null;if(oauth){let token=await getAccessToken(oauth,tokenCache,{fetchImpl});if(!token.ok)return blank({error:token.error});bearerToken=token.accessToken}let values={...endpoint.queryParams??{},...pathParams??{}},resolved=resolvePath2(endpoint.path,values);if(resolved.missing.length>0)return blank({error:`This endpoint needs a value for ${resolved.missing.map((m)=>`{${m}}`).join(", ")}. Enter an example value to test it (e.g. ${resolved.missing[0]} = 12345).`});let query={};for(let[key2,value2]of Object.entries(values)){if(value2==null||resolved.used.has(key2))continue;query[key2]=value2}let built=buildUrl(source.baseUrl,resolved.path,query);if(!built.ok)return blank({error:built.error});let{headers}=applyAuth({...source,token:bearerToken??source.token},built.url);if(bearerToken)headers.Authorization=`Bearer ${bearerToken}`;let outcome=await fetchOnce(source,{method:endpoint.method,url:built.url,headers,body:endpoint.method.toUpperCase()==="GET"?void 0:endpoint.requestBody??{}},{fetchImpl}),bodyKeys=outcome.body&&typeof outcome.body==="object"&&!Array.isArray(outcome.body)?Object.keys(outcome.body).slice(0,40):[];if(!outcome.ok)return blank({status:outcome.status,error:outcome.error??`HTTP ${outcome.status}`,bodyKeys,bodyPreview:previewBody(outcome.body)});let childrenPath=treeChildrenPath(endpoint.treeChildrenPath),extracted=extractRecords(outcome.body,endpoint.responseRootPath),flattened=childrenPath?flattenTree(extracted,childrenPath):null;if(flattened?.truncated)return blank({status:outcome.status,error:"This source answered with a structure that nests into itself, so it cannot be walked. Check the field named as the children.",bodyKeys,bodyPreview:previewBody(outcome.body)});let records=flattened?flattened.records:extracted;return{ok:!0,status:outcome.status,error:records.length===0?bodyKeys.length>0?`No record array was found. The response's top-level keys are: ${bodyKeys.join(", ")} \u2014 set the response root path to whichever holds the list.`:"No record array was found in the response.":null,recordCount:records.length,sample:records.slice(0,5),fields:records.length>0?flattenKeys(records[0]):[],bodyKeys,bodyPreview:previewBody(outcome.body)}}function blank(over){return{ok:!1,status:0,error:null,recordCount:0,sample:[],fields:[],bodyKeys:[],bodyPreview:null,...over}}function previewBody(body){if(body==null)return null;try{let text2=typeof body==="string"?body:JSON.stringify(body);return text2.length>PREVIEW_LIMIT?`${text2.slice(0,PREVIEW_LIMIT)}\u2026`:text2}catch{return null}}async function previewRun(chain,tokenCache,lookups,deps={}){let fetchImpl=deps.fetchImpl,{mapping,source,oauth}=chain,bearerToken=null;if(oauth){let token=await getAccessToken(oauth,tokenCache,{fetchImpl});if(!token.ok)return{ok:!1,error:token.error};bearerToken=token.accessToken}let root=chain.endpoints.find((e)=>e.id===chain.rootEndpointId);if(!root)return{ok:!1,error:"The endpoint this mapping reads no longer exists."};let parents={},entityValues={},externalCalls=0;for(let binding of root.paramBindings??[]){if(binding.from==="endpoint"&&!parents[binding.endpointId]){let parentSpec=chain.endpoints.find((e)=>e.id===binding.endpointId);if(!parentSpec)continue;let collected2=await collectEndpoint(source,parentSpec,{},{fetchImpl,bearerToken,maxConcurrent:deps.maxConcurrent,maxRecords:deps.maxRecords});if(externalCalls+=collected2.callCount,!collected2.ok)return{ok:!1,error:collected2.error};parents[binding.endpointId]=collected2.records.map((r)=>r.record)}if(binding.from==="entity"){let key2=`${binding.entity}.${binding.field}`;if(!entityValues[key2])entityValues[key2]=await lookups.readEntityColumn(binding.entity,binding.field)}}let collected=await collectEndpoint(source,root,{parents,entityValues},{fetchImpl,bearerToken,maxConcurrent:deps.maxConcurrent,maxRecords:deps.maxRecords});if(externalCalls+=collected.callCount,!collected.ok)return{ok:!1,error:collected.error};let mapped3=buildMappedRecords(collected.records.map((r)=>r.record),mapping.fieldMappings,mapping.dedupTargetField,collected.records.map((r)=>r.parent)),existing=await lookups.readExistingRows(mapping.targetEntity),referenceValues=await loadReferenceValues(mapping.references??[],(entity,column)=>lookups.readEntityColumn(entity,column)),references=await loadReferenceIds(mapping.references??[],mapping.targetEntity,(entity,column)=>lookups.readEntityPairsBy(entity,column)),referenceIds=references.ids,existingAccounts=mapping.accountRule?await lookups.readEntityIdsBy(mapping.accountRule.entity,mapping.accountRule.matchColumn):{},ops=planWrites({records:mapped3.records,existing,dedupTargetField:mapping.dedupTargetField,writeMode:mapping.writeMode,missingPolicy:mapping.missingPolicy,missingColumn:mapping.missingColumn,missingValue:mapping.missingValue,requiredFields:lookups.requiredFields(mapping.targetEntity),uniqueFields:lookups.uniqueFields(mapping.targetEntity),createDefaults:mapping.passwordColumn?{[mapping.passwordColumn]:"\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022"}:{},accountRule:mapping.accountRule??null,existingAccounts,references:mapping.references??[],referenceValues,referenceIds,targetEntity:mapping.targetEntity});ops.issues.unshift(...mapped3.issues),ops.warnings.push(...ambiguityWarnings(references.ambiguous));let invalid=lookups.validateRows?.(ops.creates)??[];for(let problem of invalid){let create4=ops.creates.find((c)=>c.sourceIndex===problem.sourceIndex);ops.issues.push({sourceIndex:problem.sourceIndex,kind:"invalid_value",message:problem.message,row:create4?.data})}if(invalid.length>0){let refused=new Set(invalid.map((problem)=>problem.sourceIndex));ops.creates=ops.creates.filter((create4)=>!refused.has(create4.sourceIndex))}return{ok:!0,externalCalls,plan:summarize(ops,{targetEntity:mapping.targetEntity,writeMode:mapping.writeMode,missingPolicy:mapping.missingPolicy,totalFetched:collected.records.length,reportedTotal:collected.reportedTotal,existingCount:existing.length,dedupConfigured:Boolean(mapping.dedupTargetField)})}}init_crypto();import{and as and21,eq as eq35,inArray as inArray14}from"drizzle-orm";var camel2=(s)=>s.replace(/_([a-z])/g,(_,l)=>l.toUpperCase());function tableFor(deps,entity){return deps.schemaTables[camel2(entity)]??null}var col8=(table,field)=>table?.[field]??table?.[camel2(field)];function open(deps,envelope,where){if(typeof envelope!=="string"||envelope==="")return"";let key2=deps.getEncryptionKey();if(!key2)return deps.logger.error("[Integrations] A stored credential cannot be read: no encryption key",{column:where}),"";try{return decryptSecret(envelope,key2)}catch{return deps.logger.error("[Integrations] A stored credential could not be decrypted \u2014 the encryption key has changed since it was saved. Re-enter it in the panel.",{column:where}),""}}async function resolveChain(deps,mappingId){let mappings=tableFor(deps,"integration_mappings"),endpoints=tableFor(deps,"integration_endpoints"),sources=tableFor(deps,"integration_sources");if(!mappings||!endpoints||!sources)return{ok:!1,error:"The integration tables are not present in this schema."};let[mappingRow]=await deps.db.select().from(mappings).where(eq35(col8(mappings,"id"),mappingId)).limit(1);if(!mappingRow)return{ok:!1,error:"This mapping no longer exists."};let row=mappingRow,[endpointRow]=await deps.db.select().from(endpoints).where(eq35(col8(endpoints,"id"),String(row.endpointId))).limit(1);if(!endpointRow)return{ok:!1,error:"The endpoint this mapping reads no longer exists."};if(endpointRow.enabled===!1)return{ok:!1,error:"The call this mapping reads is switched off."};if(row.enabled===!1)return{ok:!1,error:"This mapping is switched off."};let sourceId=String(endpointRow.sourceId),siblingRows=await deps.db.select().from(endpoints).where(eq35(col8(endpoints,"sourceId"),sourceId)),[sourceRow]=await deps.db.select().from(sources).where(eq35(col8(sources,"id"),sourceId)).limit(1);if(!sourceRow)return{ok:!1,error:"The source this mapping reads no longer exists."};let s=sourceRow;if(s.enabled===!1)return{ok:!1,error:"This source is switched off."};let authType=String(s.authType??"none"),source={id:String(s.id),baseUrl:String(s.baseUrl??""),authType,token:authType==="oauth2"?null:open(deps,s.authTokenEncrypted,"auth_token_encrypted"),authHeaderName:s.authHeaderName??null,authQueryParam:s.authQueryParam??null,extraHeaders:s.extraHeaders??null,tlsVerify:s.tlsVerify!==!1,timeoutMs:Number(s.timeoutMs??30000)},oauth=authType==="oauth2"?{sourceId:source.id,tokenUrl:String(s.tokenUrl??""),grantType:String(s.oauthGrantType??"password")==="client_credentials"?"client_credentials":"password",clientId:s.oauthClientId??null,clientSecret:open(deps,s.oauthClientSecretEncrypted,"oauth_client_secret_encrypted"),username:s.oauthUsername??null,password:open(deps,s.oauthPasswordEncrypted,"oauth_password_encrypted"),scope:s.oauthScope??null,clientAuth:String(s.oauthClientAuth??"basic")==="body"?"body":"basic",tlsVerify:source.tlsVerify,timeoutMs:source.timeoutMs}:null;return{ok:!0,chain:{mapping:toMappingSpec(row),endpoints:siblingRows.map((e)=>toEndpointSpec(e)),rootEndpointId:String(endpointRow.id),source,oauth}}}async function resolveEndpointContext(deps,by){let sources=tableFor(deps,"integration_sources"),endpoints=tableFor(deps,"integration_endpoints");if(!sources||!endpoints)return{ok:!1,error:"The integration tables are not present in this schema."};let endpointRow=null,sourceId=by.sourceId;if(by.endpointId){let[row]=await deps.db.select().from(endpoints).where(eq35(col8(endpoints,"id"),by.endpointId)).limit(1);if(!row)return{ok:!1,error:"This endpoint no longer exists."};endpointRow=row,sourceId=String(endpointRow.sourceId)}if(!sourceId)return{ok:!1,error:"No source was named."};let[sourceRow]=await deps.db.select().from(sources).where(eq35(col8(sources,"id"),sourceId)).limit(1);if(!sourceRow)return{ok:!1,error:"This source no longer exists."};let{source,oauth}=toConnection(deps,sourceRow);return{ok:!0,context:{source,oauth,endpoint:endpointRow?toEndpointSpec(endpointRow):null}}}async function cacheEndpointSample(deps,endpointId,records){let endpoints=tableFor(deps,"integration_endpoints");if(!endpoints||records.length===0)return;try{await deps.db.update(endpoints).set({sampleResponse:{records:records.slice(0,3)},updatedAt:new Date}).where(eq35(col8(endpoints,"id"),endpointId))}catch(error3){deps.logger.warn("[Integrations] Could not cache the endpoint sample",{endpointId,error:error3 instanceof Error?error3.message:String(error3)})}}function toConnection(deps,s){let authType=String(s.authType??"none"),source={id:String(s.id),baseUrl:String(s.baseUrl??""),authType,token:authType==="oauth2"?null:open(deps,s.authTokenEncrypted,"auth_token_encrypted"),authHeaderName:s.authHeaderName??null,authQueryParam:s.authQueryParam??null,extraHeaders:s.extraHeaders??null,tlsVerify:s.tlsVerify!==!1,timeoutMs:Number(s.timeoutMs??30000)},oauth=authType==="oauth2"?{sourceId:source.id,tokenUrl:String(s.tokenUrl??""),grantType:String(s.oauthGrantType??"password")==="client_credentials"?"client_credentials":"password",clientId:s.oauthClientId??null,clientSecret:open(deps,s.oauthClientSecretEncrypted,"oauth_client_secret_encrypted"),username:s.oauthUsername??null,password:open(deps,s.oauthPasswordEncrypted,"oauth_password_encrypted"),scope:s.oauthScope??null,clientAuth:String(s.oauthClientAuth??"basic")==="body"?"body":"basic",tlsVerify:source.tlsVerify,timeoutMs:source.timeoutMs}:null;return{source,oauth}}function toMappingSpec(row){return{id:String(row.id),name:String(row.name??""),targetEntity:String(row.targetEntity??""),fieldMappings:Array.isArray(row.fieldMappings)?row.fieldMappings:[],references:Array.isArray(row.referenceChecks)?row.referenceChecks:[],dedupTargetField:row.dedupTargetField||null,writeMode:String(row.writeMode??"upsert")??"upsert",missingPolicy:String(row.missingRecordPolicy??"ignore")??"ignore",missingColumn:row.missingRecordColumn??null,removalScopeColumn:row.removalScopeColumn||null,missingValue:row.missingRecordValue??null,originColumn:row.originColumn??null,originValue:row.originValue??null,maxRemovalShare:Number(row.maxRemovalShare??0)||0,passwordColumn:row.passwordTargetColumn||null,accountRule:row.accountRule??null}}function toEndpointSpec(row){let pagination=row.pagination;return{id:String(row.id),method:String(row.method??"GET"),path:String(row.path??""),queryParams:row.queryParams??null,requestBody:row.requestBody??null,responseRootPath:row.responseRootPath||null,treeChildrenPath:row.treeChildrenPath||null,pagination:pagination&&typeof pagination==="object"&&"kind"in pagination?pagination:{kind:"none"},paramBindings:Array.isArray(row.paramBindings)?row.paramBindings:null}}async function readConnectionBundle(deps,sourceId){let sources=tableFor(deps,"integration_sources"),endpoints=tableFor(deps,"integration_endpoints"),mappings=tableFor(deps,"integration_mappings");if(!sources||!endpoints||!mappings)return null;let[sourceRow]=await deps.db.select().from(sources).where(eq35(col8(sources,"id"),sourceId)).limit(1);if(!sourceRow)return null;let endpointRows=await deps.db.select().from(endpoints).where(eq35(col8(endpoints,"sourceId"),sourceId)),ids=new Set(endpointRows.map((row)=>String(row.id))),allMappings=await deps.db.select().from(mappings);return{source:sourceRow,endpoints:endpointRows,mappings:allMappings.filter((row)=>ids.has(String(row.endpointId)))}}async function applyTransferDocument(deps,document2,userId){let sources=tableFor(deps,"integration_sources"),endpoints=tableFor(deps,"integration_endpoints"),mappings=tableFor(deps,"integration_mappings");if(!sources||!endpoints||!mappings)return{ok:!1,error:"The integration tables are not present in this schema."};return deps.db.transaction(async(tx)=>{let now=new Date,c=document2.connection,connectionValues={name:c.name,slug:c.slug,baseUrl:c.baseUrl,description:c.description??null,authType:c.authType,authHeaderName:c.authHeaderName??null,authQueryParam:c.authQueryParam??null,extraHeaders:c.extraHeaders??null,tokenUrl:c.tokenUrl??null,oauthGrantType:c.oauthGrantType??null,oauthClientId:c.oauthClientId??null,oauthUsername:c.oauthUsername??null,oauthScope:c.oauthScope??null,oauthClientAuth:c.oauthClientAuth??null,tlsVerify:c.tlsVerify!==!1,timeoutMs:c.timeoutMs??null,enabled:c.enabled!==!1,updatedAt:now,updatedBy:userId},[existingSource]=await tx.select().from(sources).where(eq35(col8(sources,"slug"),c.slug)).limit(1),sourceId,created=!1;if(existingSource)sourceId=String(existingSource.id),await tx.update(sources).set(connectionValues).where(eq35(col8(sources,"id"),sourceId));else{let[row]=await tx.insert(sources).values({...connectionValues,createdAt:now,createdBy:userId}).returning();if(!row?.id)return{ok:!1,error:"The connection could not be created."};sourceId=String(row.id),created=!0}let existingEndpoints=await tx.select().from(endpoints).where(eq35(col8(endpoints,"sourceId"),sourceId)),endpointIdByName=new Map;for(let row of existingEndpoints)endpointIdByName.set(String(row.name),String(row.id));for(let endpoint of document2.endpoints){let values={sourceId,name:endpoint.name,tag:endpoint.tag,method:endpoint.method,path:endpoint.path,description:endpoint.description??null,queryParams:endpoint.queryParams??null,requestBody:endpoint.requestBody??null,responseRootPath:endpoint.responseRootPath??null,treeChildrenPath:endpoint.treeChildrenPath??null,pagination:endpoint.pagination??null,enabled:endpoint.enabled!==!1,updatedAt:now,updatedBy:userId},existingId=endpointIdByName.get(endpoint.name);if(existingId)await tx.update(endpoints).set(values).where(eq35(col8(endpoints,"id"),existingId));else{let[row]=await tx.insert(endpoints).values({...values,paramBindings:[],createdAt:now,createdBy:userId}).returning();if(row?.id)endpointIdByName.set(endpoint.name,String(row.id))}}for(let endpoint of document2.endpoints){let id=endpointIdByName.get(endpoint.name);if(!id)continue;let bindings=(endpoint.paramBindings??[]).map((binding)=>{if(binding.from!=="endpoint")return binding;let{endpointName,...rest3}=binding;return{...rest3,endpointId:endpointIdByName.get(String(endpointName))??""}});await tx.update(endpoints).set({paramBindings:bindings,updatedAt:now}).where(eq35(col8(endpoints,"id"),id))}let allMappings=await tx.select().from(mappings),ownIds=new Set(endpointIdByName.values()),mappingIdByName=new Map;for(let row of allMappings)if(ownIds.has(String(row.endpointId)))mappingIdByName.set(String(row.name),String(row.id));let mappingCount=0;for(let mapping of document2.mappings){let endpointId=mapping.endpointName?endpointIdByName.get(mapping.endpointName):void 0;if(!endpointId)continue;let values={endpointId,name:mapping.name,targetEntity:mapping.targetEntity,fieldMappings:mapping.fieldMappings??[],referenceChecks:mapping.referenceChecks??[],dedupSourceField:mapping.dedupSourceField??null,dedupTargetField:mapping.dedupTargetField??null,writeMode:mapping.writeMode,missingRecordPolicy:mapping.missingRecordPolicy,missingRecordColumn:mapping.missingRecordColumn??null,missingRecordValue:mapping.missingRecordValue??null,removalScopeColumn:mapping.removalScopeColumn??null,passwordTargetColumn:mapping.passwordTargetColumn??null,accountRule:mapping.accountRule??null,scheduleEnabled:mapping.scheduleEnabled===!0,scheduleCron:mapping.scheduleCron??null,scheduleTimezone:mapping.scheduleTimezone??null,enabled:mapping.enabled!==!1,updatedAt:now,updatedBy:userId},existingId=mappingIdByName.get(mapping.name);if(existingId)await tx.update(mappings).set(values).where(eq35(col8(mappings,"id"),existingId));else await tx.insert(mappings).values({...values,createdAt:now,createdBy:userId});mappingCount++}return{ok:!0,sourceId,created,endpoints:endpointIdByName.size,mappings:mappingCount}})}async function readScheduledMappings(deps){let mappings=tableFor(deps,"integration_mappings");if(!mappings)return[];return(await deps.db.select().from(mappings)).filter((row)=>row.scheduleEnabled===!0&&row.enabled!==!1).filter((row)=>typeof row.scheduleCron==="string"&&row.scheduleCron.trim()!=="").map((row)=>({id:String(row.id),name:String(row.name??""),scheduleCron:String(row.scheduleCron),scheduleTimezone:row.scheduleTimezone??null,lastRunAt:row.lastRunAt?new Date(row.lastRunAt):null,needsPassword:Boolean(row.passwordTargetColumn)||row.accountRule!=null}))}function requiredFieldsOf(entities,targetEntity){return(entities.find((e)=>e.table_name===targetEntity)?.columns??[]).filter((c)=>c.notNull===!0&&c.default===void 0&&!c.defaultRaw&&!c.nullable).map((c)=>camel2(c.name))}function uniqueFieldsOf(entities,targetEntity){let entity=entities.find((e)=>e.table_name===targetEntity),columns=entity?.columns??[],fields=new Set;for(let column of columns)if(column.unique===!0||column.primaryKey===!0)fields.add(camel2(column.name));for(let index of entity?.indexes??[])if(index.unique===!0&&index.columns.length===1&&index.columns[0])fields.add(camel2(index.columns[0]));return fields.delete("id"),[...fields]}function createRunRepository(deps,entities){let runs=()=>tableFor(deps,"integration_runs"),runItems=()=>tableFor(deps,"integration_run_items");return{async openRun(input){let table=runs();if(!table)return{ok:!1,error:"integration_runs is missing from this schema."};try{let id=(await deps.db.insert(table).values({mappingId:input.mappingId,targetEntity:input.targetEntity,writeMode:input.writeMode,trigger:input.trigger,status:"running",startedAt:new Date,createdBy:input.userId??null}).returning())[0]?.id;return id?{ok:!0,runId:String(id)}:{ok:!1,error:"The run record was created but returned no identifier."}}catch(error3){return{ok:!1,error:error3 instanceof Error?error3.message:String(error3)}}},async closeRun(runId,patch){let table=runs();if(!table)return{ok:!1,error:"integration_runs is missing from this schema."};try{let mappingId=(await deps.db.update(table).set({...patch,updatedAt:new Date}).where(eq35(col8(table,"id"),runId)).returning())[0]?.mappingId,finishedAt=patch.finishedAt,mappings=tableFor(deps,"integration_mappings");if(mappings&&mappingId&&finishedAt)await deps.db.update(mappings).set({lastRunAt:finishedAt,updatedAt:new Date}).where(eq35(col8(mappings,"id"),String(mappingId)));return{ok:!0}}catch(error3){return deps.logger.error("[Integrations] Could not close the run record",{runId,error:error3 instanceof Error?error3.message:String(error3)}),{ok:!1,error:"Could not write the run record."}}},async savePreImages(runId,images){let table=runItems();if(!table)return!1;if(images.length===0)return!0;let CHUNK2=500;try{return await deps.db.transaction(async(tx)=>{for(let i=0;i<images.length;i+=CHUNK2)await tx.insert(table).values(images.slice(i,i+CHUNK2).map((image)=>({runId,targetEntity:image.targetEntity,rowId:image.rowId,action:image.action,before:image.before,after:image.after})))}),!0}catch(error3){return deps.logger.error("[Integrations] The run succeeded but its before-images could not be stored; it cannot be undone",{runId,error:error3 instanceof Error?error3.message:String(error3)}),!1}},async isCancelRequested(runId){let table=runs();if(!table)return!1;try{let[row]=await deps.db.select().from(table).where(eq35(col8(table,"id"),runId)).limit(1);return row?.cancelRequested===!0}catch{return!1}},async readExistingRows(targetEntity){let table=tableFor(deps,targetEntity);if(!table)return[];return(await deps.db.select().from(table)).map((row)=>({id:String(row.id),row}))},async readEntityColumn(entity,field){let table=tableFor(deps,entity),column=table?col8(table,field):void 0;if(!table||!column)return[];return(await deps.db.selectDistinct({v:column}).from(table)).map((r)=>r.v).filter((v)=>v!=null&&v!=="")},async readEntityIdsBy(entity,field){let table=tableFor(deps,entity),column=table?col8(table,field):void 0,idColumn=table?col8(table,"id"):void 0;if(!table||!column||!idColumn)return{};let rows=await deps.db.select({v:column,id:idColumn}).from(table),map3={};for(let row of rows){if(row.v==null||row.v===""||row.id==null)continue;let key2=String(row.v).trim();if(!map3[key2])map3[key2]=String(row.id)}return map3},async readEntityPairsBy(entity,field){let table=tableFor(deps,entity),column=table?col8(table,field):void 0,idColumn=table?col8(table,"id"):void 0;if(!table||!column||!idColumn)return[];return await deps.db.select({value:column,id:idColumn}).from(table)},requiredFields:(targetEntity)=>requiredFieldsOf(entities,targetEntity),uniqueFields:(targetEntity)=>uniqueFieldsOf(entities,targetEntity)}}async function readRowsById(deps,targetEntity,ids){let table=tableFor(deps,targetEntity),result=new Map;if(!table||ids.length===0)return result;let CHUNK2=1000;for(let i=0;i<ids.length;i+=CHUNK2){let rows=await deps.db.select().from(table).where(inArray14(col8(table,"id"),ids.slice(i,i+CHUNK2)));for(let row of rows)result.set(String(row.id),row)}return result}async function readPreImages(deps,runId){let table=tableFor(deps,"integration_run_items");if(!table)return[];return(await deps.db.select().from(table).where(eq35(col8(table,"runId"),runId))).map((row)=>({targetEntity:String(row.targetEntity),rowId:row.rowId?String(row.rowId):null,action:String(row.action),before:row.before??null,after:row.after??null}))}async function readRun(deps,runId){let table=tableFor(deps,"integration_runs");if(!table)return null;let[row]=await deps.db.select().from(table).where(eq35(col8(table,"id"),runId)).limit(1);return row??null}async function requestCancel(deps,runId){let table=tableFor(deps,"integration_runs");if(!table)return!1;return(await deps.db.update(table).set({cancelRequested:!0,updatedAt:new Date}).where(and21(eq35(col8(table,"id"),runId),eq35(col8(table,"status"),"running"))).returning()).length>0}var fail3=(set2,status,message)=>{return set2.status=status,{isSuccess:!1,message,data:null}};function createInspectRoutes(routeConfig,deps,tokenCache){let settings=routeConfig.resolvedOptions.integrations??{};return[defineRoute({method:"POST",path:"/sources/:id/test",detail:{tags:["Integrations"],summary:"Check a source is reachable and its credentials are accepted"},handler:async({params,set:set2})=>{let repoDeps=deps();if(!repoDeps)return fail3(set2,503,"The database is not available.");let context=await resolveEndpointContext(repoDeps,{sourceId:params.id});if(!context.ok)return fail3(set2,404,context.error);let result=await testConnection(context.context,tokenCache);return{isSuccess:result.ok,message:result.ok?result.detail??"Connected.":result.error??"Could not connect.",data:result}}}),defineRoute({method:"POST",path:"/endpoints/:id/sample",body:Type.Optional(Type.Object({pathParams:Type.Optional(Type.Record(Type.String(),Type.Unknown()))})),detail:{tags:["Integrations"],summary:"Call one endpoint once and report its real shape"},handler:async({params,body,set:set2})=>{let repoDeps=deps();if(!repoDeps)return fail3(set2,503,"The database is not available.");let context=await resolveEndpointContext(repoDeps,{endpointId:params.id});if(!context.ok)return fail3(set2,404,context.error);let{source,oauth,endpoint}=context.context;if(!endpoint)return fail3(set2,404,"This endpoint no longer exists.");let result=await sampleEndpoint(source,endpoint,body?.pathParams,tokenCache,oauth);if(result.ok&&result.sample.length>0)await cacheEndpointSample(repoDeps,params.id,result.sample);return{isSuccess:result.ok,message:result.error??"Sampled.",data:result}}}),defineRoute({method:"POST",path:"/preview",body:Type.Object({mappingId:Type.String()}),detail:{tags:["Integrations"],summary:"Dry-run an import: counts, warnings and a sample, with no writes"},handler:async({body,set:set2})=>{let repoDeps=deps();if(!repoDeps)return fail3(set2,503,"The database is not available.");let chain=await resolveChain(repoDeps,body.mappingId);if(!chain.ok)return fail3(set2,404,chain.error);let repo=createRunRepository(repoDeps,repoDeps.entities),result=await previewRun(chain.chain,tokenCache,{readExistingRows:repo.readExistingRows,readEntityColumn:repo.readEntityColumn,requiredFields:repo.requiredFields,uniqueFields:repo.uniqueFields,readEntityIdsBy:repo.readEntityIdsBy,readEntityPairsBy:repo.readEntityPairsBy,validateRows:(rows)=>{let columns=repoDeps.entities.find((e)=>e.table_name===chain.chain.mapping.targetEntity)?.columns??[],problems=[];for(let row of rows){let result2=validatePayload(row.data,columns,!1);if(!result2.valid)problems.push({sourceIndex:row.sourceIndex,message:result2.errors.map((e)=>`${e.field}: ${e.message}`).join("; ")})}return problems}},{maxRecords:settings.maxRecordsPerRun,maxConcurrent:settings.maxConcurrentFanout});if(!result.ok)return fail3(set2,422,result.error);return{isSuccess:!0,message:"Nothing was written.",data:{plan:result.plan,externalCalls:result.externalCalls}}}}),defineRoute({method:"POST",path:"/schedule-check",body:Type.Object({mappingId:Type.String(),at:Type.Optional(Type.String())}),detail:{tags:["Integrations"],summary:"Simulate the schedule: the next firings, and whether a given moment is one",description:"Writes nothing and starts nothing. Answers the question a schedule cannot otherwise be asked until the night it runs: does this expression name the minute I meant, in the clock I meant."},handler:async({body,set:set2})=>{let repoDeps=deps();if(!repoDeps)return fail3(set2,503,"The database is not available.");let chain=await resolveChain(repoDeps,body.mappingId);if(!chain.ok)return fail3(set2,404,chain.error);let raw=chain.chain.mapping,expression=String(raw.scheduleCron??"").trim(),timeZone=String(raw.scheduleTimezone??"").trim()||"UTC";if(expression==="")return{isSuccess:!0,message:"Nothing was written.",data:{enabled:raw.scheduleEnabled===!0,expression:"",timeZone,times:[]}};let at=body.at?new Date(body.at):new Date;if(Number.isNaN(at.getTime()))return fail3(set2,400,"That is not a moment I can read.");let upcoming=nextFireTimes({expression,timeZone,from:at,count:5}),verdict=isDue({expression,timeZone,now:at,lastRunAt:null});return{isSuccess:!0,message:"Nothing was written.",data:{enabled:raw.scheduleEnabled===!0,expression,timeZone,describes:describeCron(expression,timeZone),at:at.toISOString(),wouldFireAtThatMoment:verdict.due,error:verdict.error??upcoming.error??null,times:upcoming.times.map((t)=>t.toISOString())}}}})]}var fail4=(set2,status,message)=>{return set2.status=status,{isSuccess:!1,message,data:null}};async function closeAbandonedRuns(repoDeps,mappingId,logger2){try{let runsTable=tableFor(repoDeps,"integration_runs");if(!runsTable)return;let{and:and22,eq:eq36,inArray:inArray15}=await import("drizzle-orm"),closed=await repoDeps.db.update(runsTable).set({status:"failed",finishedAt:new Date,errors:["This import stopped without finishing \u2014 the process running it went away (a restart, a deploy, or a crash). Whatever it had already written is still there; run it again, or check the target table."],updatedAt:new Date}).where(and22(eq36(runsTable.mappingId,mappingId),inArray15(runsTable.status,["running","queued"]))).returning();if(closed.length>0)logger2.warn(`[Integrations] ${closed.length} run(s) of this mapping were left open by a process that went away; marked as failed.`)}catch(error3){logger2.error("[Integrations] Could not close abandoned runs",error3)}}var tokenCache=new TokenCache;function createIntegrationRoutes(routeConfig){let{logger:logger2,basePath,db,schemaTables,resolvedOptions,getEncryptionKey,getLockStore,publish}=routeConfig,entities=resolveEntities(SYSTEM_TABLES,resolvedOptions.entities??[],{consumerMode:resolvedOptions.authentication?.mode==="consumer"}).entities,settings=resolvedOptions.integrations??{},topicPrefix=settings.realtimeTopicPrefix||"integrations",guard4=createGodminGuard({logger:logger2,db,schemaTables,area:"data integrations",allowedRoles:routeConfig.adminRoles??settings.adminRoles}),deps=()=>db?{db,schemaTables,entities,logger:logger2,getEncryptionKey}:null,inspectRoutes=createInspectRoutes(routeConfig,deps,tokenCache),writerFor=(repoDeps,targetEntity)=>{let table=tableFor(repoDeps,targetEntity);if(!table)return null;let entity=entities.find((e)=>e.table_name===targetEntity);return{db:repoDeps.db,drizzleTable:table,resolveCol:(field)=>table[field]??table[field.replace(/_([a-z])/g,(_m,l)=>l.toUpperCase())],columns:entity?.columns??[],logger:logger2,validatePayload,resolveEntity:(name2)=>{let refTable=tableFor(repoDeps,name2);if(!refTable)return null;let refEntity=entities.find((e)=>e.table_name===name2);return{table:refTable,columns:refEntity?.columns??[]}},accountClosure:accountClosureFor(targetEntity)}};function accountClosureFor(targetEntity){let link=accountLinkFor(routeConfig.resolvedOptions.authentication?.accountLinks,targetEntity);if(!link)return null;let usersTable=schemaTables.users;if(!usersTable)return null;let sessionsTable=schemaTables.userSessions??schemaTables.sessions;return{userColumn:link.userColumn,accountEntity:"users",usersTable,sessionsTable:sessionsTable??null}}let routePostRuns=defineRoute({method:"POST",path:"/runs",body:Type.Object({mappingId:Type.String(),confirm:Type.Optional(Type.Boolean()),sharedPassword:Type.Optional(Type.String()),referenceMode:Type.Optional(Type.Union([Type.Literal("create"),Type.Literal("skipRecord")]))}),detail:{tags:["Integrations"],summary:"Start an import; answers 202 with the run identifier"},handler:async({body,set:set2,request})=>{let repoDeps=deps();if(!repoDeps)return fail4(set2,503,"The database is not available.");let chain=await resolveChain(repoDeps,body.mappingId);if(!chain.ok)return fail4(set2,404,chain.error);let declared2=chain.chain.mapping,answer=body.referenceMode,mapping=answer?{...declared2,references:(declared2.references??[]).map((check)=>check.onMissing==="ignore"?check:{...check,onMissing:answer})}:declared2,wantsPassword=Boolean(mapping.passwordColumn||mapping.accountRule),createDefaults,accountPasswordHash=null;if(wantsPassword){let supplied=typeof body.sharedPassword==="string"?body.sharedPassword:"";if(supplied.length<8)return fail4(set2,422,mapping.accountRule?"This import creates a login for every new record, so it needs a password to create them with: send sharedPassword, at least 8 characters. It is used once, for this run, and is stored nowhere.":`This mapping writes a password into "${mapping.passwordColumn}", so it needs one to write: send sharedPassword, at least 8 characters. It is used for the accounts this run creates and is not stored anywhere.`);let hash=await hashPassword(supplied);if(accountPasswordHash=hash,mapping.passwordColumn)createDefaults={[mapping.passwordColumn]:hash}}if(isDestructive(mapping.writeMode,mapping.missingPolicy)&&body.confirm!==!0)return set2.status=428,{isSuccess:!1,message:mapping.writeMode==="replace"?"This mapping rebuilds the whole table. Send confirm: true to proceed.":mapping.missingPolicy==="deactivate"?"This mapping deactivates records the source no longer carries. Send confirm: true to proceed.":"This mapping deletes records the source no longer carries. Send confirm: true to proceed.",data:null};let lockStore=getLockStore();if(!lockStore)return fail4(set2,503,"The lock service is unavailable, so a second run of this mapping could not be prevented. Runs are refused until it is back.");let lock=await acquireRunLock(lockStore,mapping.id,{logger:logger2});if(!lock.ok){if(lock.reason==="unavailable")return fail4(set2,503,`The lock service is unavailable: ${lock.error}`);return set2.status=409,{isSuccess:!1,message:"This mapping is already importing. Wait for it to finish, or cancel it.",data:{heldSince:lock.heldSince}}}await closeAbandonedRuns(repoDeps,mapping.id,logger2);let writer=writerFor(repoDeps,mapping.targetEntity);if(!writer)return await lock.handle.release(),fail4(set2,422,`This mapping targets "${mapping.targetEntity}", which does not exist.`);let userId=request.headers.get("x-user-id")??"unknown",repo=createRunRepository(repoDeps,entities),announce=()=>{},runIdReady=new Promise((resolve2)=>{announce=resolve2}),started=executeRun({...chain.chain,mapping,userId,trigger:"manual",createDefaults,accountPasswordHash},{repo,writer,logger:logger2,tokenCache,publishProgress:(progress)=>publish?.(`${topicPrefix}.run`,progress),onRunOpened:(id)=>announce(id),maxRecordsPerRun:settings.maxRecordsPerRun,maxConcurrentFanout:settings.maxConcurrentFanout,retainPreImagesMs:parseDuration(settings.retainPreImagesFor)},lock.handle),runId=await Promise.race([runIdReady,started.then((outcome)=>outcome.runId)]);return started.catch(async(error3)=>{let reason=error3 instanceof Error?error3.message:String(error3);logger2.error("[Integrations] Run threw outside its own error handling",{mappingId:mapping.id,error:reason});let openedId=await runIdReady.catch(()=>null);if(openedId)await repo.closeRun(openedId,{status:"failed",finishedAt:new Date,errors:[reason]}).catch(()=>{})}).finally(()=>lock.handle.release()),set2.status=202,{isSuccess:!0,message:"The import has started.",data:{runId,mappingId:mapping.id,targetEntity:mapping.targetEntity}}}}),startScheduledRun=async(mappingId)=>{let repoDeps=deps();if(!repoDeps)return!1;let chain=await resolveChain(repoDeps,mappingId);if(!chain.ok)return logger2.error(`[Integrations] Scheduled import cannot run: ${chain.error}`),!1;let lockStore=getLockStore();if(!lockStore)return!1;let lock=await acquireRunLock(lockStore,mappingId,{logger:logger2});if(!lock.ok)return!1;let writer=writerFor(repoDeps,chain.chain.mapping.targetEntity);if(!writer)return await lock.handle.release(),logger2.error(`[Integrations] Scheduled import targets "${chain.chain.mapping.targetEntity}", which does not exist.`),!1;return executeRun({...chain.chain,userId:null,trigger:"schedule"},{repo:createRunRepository(repoDeps,entities),writer,logger:logger2,tokenCache,publishProgress:(progress)=>publish?.(`${topicPrefix}.run`,progress),maxRecordsPerRun:settings.maxRecordsPerRun,maxConcurrentFanout:settings.maxConcurrentFanout,retainPreImagesMs:parseDuration(settings.retainPreImagesFor)},lock.handle).then((outcome)=>{if(outcome.status!=="succeeded")logger2.error(`[Integrations] Scheduled import "${chain.chain.mapping.name}" ${outcome.status}: ${outcome.failure?.message??"no reason recorded"}`)}).catch((error3)=>{logger2.error("[Integrations] Scheduled import threw outside its own error handling",{mappingId,error:error3 instanceof Error?error3.message:String(error3)})}).finally(()=>lock.handle.release()),!0},unattendable=new Set;if(settings.schedulesEnabled!==!1)startScheduler({logger:logger2,listScheduled:async()=>{let repoDeps=deps();if(!repoDeps)return[];return(await readScheduledMappings(repoDeps)).filter((row)=>{if(!row.needsPassword)return!0;if(!unattendable.has(row.id))unattendable.add(row.id),logger2.error(`[Integrations] "${row.name}" is scheduled but creates logins, which need a password nobody can type at that hour. It will not run on its own \u2014 start it by hand, or turn the login off.`);return!1})},startRun:(mappingId)=>startScheduledRun(mappingId)});let routeGetSourcesIdExport=defineRoute({method:"GET",path:"/sources/:id/export",detail:{tags:["Integrations"],summary:"Export a connection, its calls and its mappings \u2014 without any secret"},handler:async({params,set:set2})=>{let repoDeps=deps();if(!repoDeps)return fail4(set2,503,"The database is not available.");let bundle=await readConnectionBundle(repoDeps,params.id);if(!bundle)return fail4(set2,404,"This connection no longer exists.");let document2=buildTransferDocument({...bundle,exportedAt:new Date().toISOString()});return{isSuccess:!0,message:`${document2.endpoints.length} call(s) and ${document2.mappings.length} mapping(s). Credentials are NOT included \u2014 they are asked for again on the other side.`,data:document2}}}),routePostImport=defineRoute({method:"POST",path:"/import",body:Type.Object({document:Type.Unknown()}),detail:{tags:["Integrations"],summary:"Bring in an exported connection; re-running it updates rather than duplicates"},handler:async({body,set:set2,request})=>{let repoDeps=deps();if(!repoDeps)return fail4(set2,503,"The database is not available.");let checked=validateTransferDocument(body?.document);if(!checked.ok||!checked.document)return set2.status=422,{isSuccess:!1,message:"This file cannot be imported as it stands.",data:{problems:checked.problems}};let userId=request.headers.get("x-user-id")??"unknown",result=await applyTransferDocument(repoDeps,checked.document,userId);if(!result.ok)return fail4(set2,422,result.error);return{isSuccess:!0,message:`Connection "${checked.document.connection.name}" is here: ${result.endpoints} call(s) and ${result.mappings} mapping(s). Its credentials are not \u2014 enter them before running anything.`,data:result}}}),routePostRunsIdCancel=defineRoute({method:"POST",path:"/runs/:id/cancel",detail:{tags:["Integrations"],summary:"Ask a running import to stop"},handler:async({params,set:set2})=>{let repoDeps=deps();if(!repoDeps)return fail4(set2,503,"The database is not available.");if(!await requestCancel(repoDeps,params.id))return fail4(set2,409,"This run is not running, so there is nothing to cancel.");return{isSuccess:!0,message:"Cancellation requested. The import stops at its next checkpoint and anything it had written is rolled back.",data:{runId:params.id}}}}),routePostRunsIdRevert=defineRoute({method:"POST",path:"/runs/:id/revert",body:Type.Optional(Type.Object({force:Type.Optional(Type.Boolean())})),detail:{tags:["Integrations"],summary:"Undo a run that succeeded"},handler:async({params,body,set:set2,request})=>{let repoDeps=deps();if(!repoDeps)return fail4(set2,503,"The database is not available.");let run=await readRun(repoDeps,params.id);if(!run)return fail4(set2,404,"This run no longer exists.");if(run.status!=="succeeded")return fail4(set2,409,`Only a run that finished successfully can be undone; this one is "${String(run.status)}".`);let expiry=run.preImagesExpireAt?new Date(String(run.preImagesExpireAt)):null;if(expiry&&expiry.getTime()<Date.now())return fail4(set2,410,"This run is too old to undo: the records of what it replaced have been cleared.");let targetEntity=String(run.targetEntity??""),preImages=await readPreImages(repoDeps,params.id);if(preImages.length===0)return fail4(set2,410,"There is no record of what this run replaced, so it cannot be undone.");let groups=groupPreImagesByEntity(preImages).sort((a,b)=>a.targetEntity===targetEntity?-1:b.targetEntity===targetEntity?1:0),planned=[];for(let group of groups){let current=await readRowsById(repoDeps,group.targetEntity,touchedRowIds(group.preImages));planned.push({targetEntity:group.targetEntity,ops:planRevert({preImages:group.preImages,current,force:body?.force===!0})})}let ops={deletes:planned.flatMap((p)=>p.ops.deletes),restores:planned.flatMap((p)=>p.ops.restores),reinserts:planned.flatMap((p)=>p.ops.reinserts),conflicts:planned.flatMap((p)=>p.ops.conflicts)};if(ops.deletes.length===0&&ops.restores.length===0&&ops.reinserts.length===0&&ops.conflicts.length>0)return set2.status=409,{isSuccess:!1,message:"Every record this run touched has been edited since. Undoing would discard those edits; send force: true to overwrite them anyway.",data:{conflicts:ops.conflicts}};let userId=request.headers.get("x-user-id")??"unknown";for(let group of planned)if(!writerFor(repoDeps,group.targetEntity))return fail4(set2,422,`"${group.targetEntity}" no longer exists.`);let undone={deleted:0,restored:0,reinserted:0},failure=null;try{await repoDeps.db.transaction(async(tx)=>{for(let group of planned){let groupWriter=writerFor(repoDeps,group.targetEntity),result2=await applyRevert(groupWriter,group.ops,{targetEntity:group.targetEntity,userId,tx});if(!result2.ok)throw failure=result2.error??"The undo could not be completed.",Error(failure);undone={deleted:undone.deleted+(result2.deleted??0),restored:undone.restored+(result2.restored??0),reinserted:undone.reinserted+(result2.reinserted??0)}}})}catch(error3){let message=failure??(error3 instanceof Error?error3.message:"The undo could not be completed.");return fail4(set2,500,`${message} Nothing was undone \u2014 every table was rolled back together.`)}let result={ok:!0,...undone,conflicts:ops.conflicts.length},finished=ops.conflicts.length===0,runsTable=tableFor(repoDeps,"integration_runs");if(runsTable&&finished){let{eq:eq36}=await import("drizzle-orm");await repoDeps.db.update(runsTable).set({status:"reverted",revertedAt:new Date,revertedBy:userId}).where(eq36(runsTable.id,params.id))}return{isSuccess:!0,message:finished?"The import was undone.":`Undone except for ${ops.conflicts.length} record(s) edited since the import. They were left as they are \u2014 send force: true to overwrite them too.`,data:{deleted:result.deleted,restored:result.restored,reinserted:result.reinserted,conflicts:ops.conflicts}}}}),routePutSourcesIdCredentials=defineRoute({method:"PUT",path:"/sources/:id/credentials",body:Type.Object({token:Type.Optional(Type.String()),oauthClientSecret:Type.Optional(Type.String()),oauthPassword:Type.Optional(Type.String())}),detail:{tags:["Integrations"],summary:"Store a source credential; only a masked preview is returned"},handler:async({params,body,set:set2})=>{let repoDeps=deps();if(!repoDeps)return fail4(set2,503,"The database is not available.");let key2=getEncryptionKey();if(!key2)return fail4(set2,503,"No encryption key is configured, so a credential stored now could never be read back.");let table=tableFor(repoDeps,"integration_sources");if(!table)return fail4(set2,503,"The integration tables are not present in this schema.");let patch={},hints={},apply=(value2,column,hintColumn,name2)=>{if(value2===void 0)return;let trimmed=value2.trim();if(trimmed===""){patch[column]=null,patch[hintColumn]=null,hints[name2]=null;return}patch[column]=encryptSecret(trimmed,key2),patch[hintColumn]=previewOf(trimmed),hints[name2]=previewOf(trimmed)};if(apply(body.token,"authTokenEncrypted","authTokenHint","token"),apply(body.oauthClientSecret,"oauthClientSecretEncrypted","oauthClientSecretHint","clientSecret"),apply(body.oauthPassword,"oauthPasswordEncrypted","oauthPasswordHint","password"),Object.keys(patch).length===0)return{isSuccess:!0,message:"Nothing to change.",data:{hints}};let{eq:eq36}=await import("drizzle-orm");return await repoDeps.db.update(table).set({...patch,updatedAt:new Date}).where(eq36(table.id,params.id)),tokenCache.invalidate(params.id),{isSuccess:!0,message:"Saved.",data:{hints}}}});logger2.info(`[Integrations] Routes enabled at ${basePath}`);let declared=[routePostRuns,routeGetSourcesIdExport,routePostImport,routePostRunsIdCancel,routePostRunsIdRevert,routePutSourcesIdCredentials,...inspectRoutes];return collectRoutes([{prefix:basePath,guards:[guard4],routes:declared}])}function parseDuration(value2){if(!value2)return;let match=value2.trim().match(/^(\d+)\s*([smhd])$/i);if(!match)return;let amount=Number(match[1]),unit=(match[2]??"d").toLowerCase();return amount*(unit==="s"?1000:unit==="m"?60000:unit==="h"?3600000:86400000)}init_esm3();init_routeCollector();init_adminGuard();var STREAM_HEADERS={"Content-Type":"text/event-stream; charset=utf-8","Cache-Control":"no-cache, no-transform",Connection:"keep-alive"},textEncoder=new TextEncoder,encodeEvent=(event,data)=>{let payload=typeof data==="string"?data:JSON.stringify(data);return textEncoder.encode(`event: ${event}
|
|
547
|
+
`)}`:verdict.message,fetched:rootRecords.length,reported:reportedTotal2??0}});progress({phase:"mapping",total:rootRecords.length});let mapped3=buildMappedRecords(rootRecords.map((r)=>r.record),mapping.fieldMappings,mapping.dedupTargetField,rootRecords.map((r)=>r.parent));if(await refreshCancelFlag(),isCancelled())return finish({...empty,externalCalls,status:"cancelled",failure:{kind:"cancelled",message:"Cancelled before anything was written."}});let existing=await deps.repo.readExistingRows(mapping.targetEntity),referenceValues=await loadReferenceValues(mapping.references??[],(entity,column)=>deps.repo.readEntityColumn(entity,column)),references=await loadReferenceIds(mapping.references??[],mapping.targetEntity,(entity,column)=>deps.repo.readEntityPairsBy(entity,column)),referenceIds=references.ids,existingAccounts=mapping.accountRule?await deps.repo.readEntityIdsBy(mapping.accountRule.entity,mapping.accountRule.matchColumn):{},ops=planWrites({records:mapped3.records,existing,dedupTargetField:mapping.dedupTargetField,writeMode:mapping.writeMode,missingPolicy:mapping.missingPolicy,missingColumn:mapping.missingColumn,missingValue:mapping.missingValue,removalScope:removalScopeFor(mapping.removalScopeColumn,mapped3.records),originColumn:mapping.originColumn,originValue:mapping.originValue,requiredFields:deps.repo.requiredFields(mapping.targetEntity),uniqueFields:deps.repo.uniqueFields(mapping.targetEntity),createDefaults:context.createDefaults??{},accountRule:mapping.accountRule??null,existingAccounts,references:mapping.references??[],referenceValues,referenceIds,targetEntity:mapping.targetEntity});ops.issues.unshift(...mapped3.issues),ops.warnings.push(...ambiguityWarnings(references.ambiguous)),ops.warnings.unshift(...fetchWarnings);let plan=summarize(ops,{targetEntity:mapping.targetEntity,writeMode:mapping.writeMode,missingPolicy:mapping.missingPolicy,totalFetched:rootRecords.length,reportedTotal:reportedTotal2,existingCount:existing.length,dedupConfigured:Boolean(mapping.dedupTargetField)}),volume=checkPlanVolume({removing:ops.deletes.length+ops.deactivates.length,existing:existing.length,maxShare:mapping.maxRemovalShare??0});if(!volume.allowed)return finish({...empty,externalCalls,failure:{kind:volume.kind,message:volume.message,fetched:rootRecords.length,reported:reportedTotal2??0}});progress({phase:"writing",total:ops.creates.length+ops.updates.length});let applied=await applyWrites(deps.writer,{ops,targetEntity:mapping.targetEntity,userId:context.userId,account:mapping.accountRule&&context.accountPasswordHash?{idColumn:mapping.accountRule.idColumn,matchColumn:mapping.accountRule.matchColumn,matchFrom:mapping.accountRule.matchFrom,passwordColumn:mapping.accountRule.passwordColumn,passwordHash:context.accountPasswordHash}:null,missingColumn:mapping.missingColumn,missingValue:mapping.missingValue,isCancelled});if(!applied.ok){let failureIssues=applied.failure.kind==="validation_failed"?applied.failure.issues:[];return finish({...empty,plan,externalCalls,issues:[...ops.issues,...failureIssues],status:applied.failure.kind==="cancelled"?"cancelled":"failed",failure:applied.failure})}return preImagesStored=await deps.repo.savePreImages(runId,applied.preImages),deps.logger.info("[Integrations] Run finished",{runId,mappingId:mapping.id,targetEntity:mapping.targetEntity,created:applied.created,accountsCreated:applied.accountsCreated,referencesCreated:applied.referencesCreated,updated:applied.updated,deleted:applied.deleted,deactivated:applied.deactivated,externalCalls,cancelChecks:cancelChecked}),finish({...empty,status:"succeeded",plan,failure:null,created:applied.created,accountsCreated:applied.accountsCreated,referencesCreated:applied.referencesCreated,updated:applied.updated,deleted:applied.deleted,deactivated:applied.deactivated,externalCalls,issues:[...ops.issues,...applied.issues,...orphanIssues(applied)]})}function orphanIssues(applied){let note=orphanLinkNote({made:applied.linksMade,unresolved:applied.linksUnresolved});return note?[{sourceIndex:-1,kind:"parent_unresolved",message:note}]:[]}var RANGES=[[0,59],[0,23],[1,31],[1,12],[0,6]],NAMES=["minute","hour","day of month","month","day of week"];function expandField(raw,[min,max]){let values=new Set;for(let part of raw.split(",")){let[spec,stepText]=part.split("/"),step=stepText===void 0?1:Number(stepText);if(!Number.isInteger(step)||step<1)return null;let from,to;if(spec==="*")from=min,to=max;else if(spec?.includes("-")){let[a,b]=spec.split("-").map(Number);if(!Number.isInteger(a)||!Number.isInteger(b))return null;from=a,to=b}else{let one=Number(spec);if(!Number.isInteger(one))return null;from=one,to=stepText===void 0?one:max}if(from<min||to>max||from>to)return null;for(let v=from;v<=to;v+=step)values.add(v)}return values.size>0?values:null}function parseCron2(expression){let parts=expression.trim().split(/\s+/);if(parts.length!==5)return{ok:!1,error:`A schedule has five parts \u2014 minute hour day-of-month month day-of-week \u2014 and this has ${parts.length}. "0 2 * * *" is every night at 02:00.`};let sets2=[];for(let i=0;i<5;i++){let field=expandField(parts[i],RANGES[i]);if(!field)return{ok:!1,error:`The ${NAMES[i]} part ("${parts[i]}") is not something I can read.`};sets2.push(field)}return{ok:!0,fields:{minutes:sets2[0],hours:sets2[1],daysOfMonth:sets2[2],months:sets2[3],daysOfWeek:sets2[4],anyDayOfMonth:parts[2]==="*",anyDayOfWeek:parts[4]==="*"}}}function partsIn2(when,timeZone){let formatter=new Intl.DateTimeFormat("en-US",{timeZone,hour12:!1,year:"numeric",month:"2-digit",day:"2-digit",hour:"2-digit",minute:"2-digit",weekday:"short"}),parts={};for(let part of formatter.formatToParts(when))parts[part.type]=part.value;let weekdays=["Sun","Mon","Tue","Wed","Thu","Fri","Sat"];return{year:Number(parts.year),month:Number(parts.month),day:Number(parts.day),hour:Number(parts.hour)%24,minute:Number(parts.minute),weekday:Math.max(0,weekdays.indexOf(parts.weekday??"Sun"))}}function matchesCron(fields,when,timeZone){let at=partsIn2(when,timeZone);if(!fields.minutes.has(at.minute))return!1;if(!fields.hours.has(at.hour))return!1;if(!fields.months.has(at.month))return!1;let dayOfMonthMatches=fields.daysOfMonth.has(at.day),dayOfWeekMatches=fields.daysOfWeek.has(at.weekday);if(fields.anyDayOfMonth&&fields.anyDayOfWeek)return!0;if(fields.anyDayOfMonth)return dayOfWeekMatches;if(fields.anyDayOfWeek)return dayOfMonthMatches;return dayOfMonthMatches||dayOfWeekMatches}var CATCH_UP_MINUTES=1500,MINUTE_MS=60000;function isDue(input){let parsed=parseCron2(input.expression);if(!parsed.ok)return{due:!1,error:parsed.error};let zone=input.timeZone?.trim()||"UTC",nowMinute=Math.floor(input.now.getTime()/MINUTE_MS),lookBack=0;if(input.lastRunAt){let lastMinute=Math.floor(input.lastRunAt.getTime()/MINUTE_MS);if(lastMinute>=nowMinute)return{due:!1};lookBack=Math.min(nowMinute-lastMinute-1,CATCH_UP_MINUTES)}try{for(let back=0;back<=lookBack;back++){let at=new Date((nowMinute-back)*MINUTE_MS);if(matchesCron(parsed.fields,at,zone))return{due:!0}}}catch{return{due:!1,error:`"${zone}" is not a timezone this system knows.`}}return{due:!1}}function describeCron(expression,timeZone){let parsed=parseCron2(expression);if(!parsed.ok)return parsed.error;let zone=timeZone?.trim()||"UTC",parts=expression.trim().split(/\s+/),[minute,hour,dayOfMonth,month,dayOfWeek]=parts,everyMinutes=minute?.startsWith("*/")?Number(minute.slice(2)):null;if(everyMinutes&&hour==="*"&&dayOfMonth==="*"&&month==="*"&&dayOfWeek==="*")return`Every ${everyMinutes} minutes.`;let time2=minute!==void 0&&hour!==void 0&&!minute.includes("*")&&!hour.includes("*")?`${hour.padStart(2,"0")}:${minute.padStart(2,"0")}`:null,DAYS=["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"];if(time2&&dayOfWeek!=="*"&&dayOfMonth==="*")return`Every ${[...parsed.fields.daysOfWeek??[]].map((d)=>DAYS[d]).join(", ")} at ${time2} (${zone}).`;if(time2&&dayOfMonth!=="*")return`On day ${dayOfMonth} of the month at ${time2} (${zone}).`;if(time2)return`Every day at ${time2} (${zone}).`;return`On the schedule "${expression}" (${zone}).`}function nextFireTimes(input){let parsed=parseCron2(input.expression);if(!parsed.ok)return{times:[],error:parsed.error};let zone=input.timeZone?.trim()||"UTC",count2=Math.max(1,Math.min(input.count??5,50)),horizon=Math.max(1,Math.min(input.horizonDays??400,400)),times=[],startMinute=Math.floor(input.from.getTime()/MINUTE_MS)+1,limit=horizon*24*60;try{for(let offset=0;offset<limit&×.length<count2;offset+=1){let when=new Date((startMinute+offset)*MINUTE_MS);if(matchesCron(parsed.fields,when,zone))times.push(when)}}catch{return{times:[],error:`"${zone}" is not a timezone this system knows.`}}return{times}}var MINUTE2=60000;function dueMappings(mappings,now){let due=[],problems=[];for(let mapping of mappings){if(!mapping.scheduleCron?.trim())continue;let answer=isDue({expression:mapping.scheduleCron,timeZone:mapping.scheduleTimezone,now,lastRunAt:mapping.lastRunAt??null});if(answer.error){problems.push({mapping,error:answer.error});continue}if(answer.due)due.push(mapping)}return{due,problems}}function startScheduler(deps){let now=deps.now??(()=>new Date),reported=new Set,stopped=!1,tick=async()=>{if(stopped)return;let mappings;try{mappings=await deps.listScheduled()}catch(error3){deps.logger.error("[Integrations] Could not read the schedules",{error:error3 instanceof Error?error3.message:String(error3)});return}let{due,problems}=dueMappings(mappings,now());for(let problem of problems){let token=`${problem.mapping.id}:${problem.error}`;if(reported.has(token))continue;reported.add(token),deps.logger.error(`[Integrations] "${problem.mapping.name}" has a schedule that cannot run: ${problem.error}`)}for(let mapping of due)try{if(await deps.startRun(mapping.id))deps.logger.info(`[Integrations] Scheduled import started: ${mapping.name}`)}catch(error3){deps.logger.error(`[Integrations] Scheduled import failed to start: ${mapping.name}`,{error:error3 instanceof Error?error3.message:String(error3)})}},timer=setInterval(()=>{tick()},MINUTE2);if(typeof timer==="object"&&timer&&"unref"in timer)timer.unref();return{stop:()=>{stopped=!0,clearInterval(timer)}}}var asObject=(value2)=>value2&&typeof value2==="object"&&!Array.isArray(value2)?value2:null,asArray=(value2)=>Array.isArray(value2)?value2:[],text=(value2)=>{if(value2==null)return null;let s=String(value2);return s===""?null:s};function buildTransferDocument(input){let{source,endpoints,mappings}=input,nameById=new Map;for(let endpoint of endpoints){let id=text(endpoint.id),name2=text(endpoint.name);if(id&&name2)nameById.set(id,name2)}return{version:1,exportedAt:input.exportedAt,secretsExcluded:!0,connection:{name:String(source.name??""),slug:String(source.slug??""),baseUrl:String(source.baseUrl??""),description:text(source.description),authType:String(source.authType??"none"),authHeaderName:text(source.authHeaderName),authQueryParam:text(source.authQueryParam),extraHeaders:asObject(source.extraHeaders),tokenUrl:text(source.tokenUrl),oauthGrantType:text(source.oauthGrantType),oauthClientId:text(source.oauthClientId),oauthUsername:text(source.oauthUsername),oauthScope:text(source.oauthScope),oauthClientAuth:text(source.oauthClientAuth),tlsVerify:source.tlsVerify!==!1,timeoutMs:typeof source.timeoutMs==="number"?source.timeoutMs:null,enabled:source.enabled!==!1},endpoints:endpoints.map((endpoint)=>({name:String(endpoint.name??""),tag:String(endpoint.tag??""),method:String(endpoint.method??"GET"),path:String(endpoint.path??""),description:text(endpoint.description),queryParams:asObject(endpoint.queryParams),requestBody:asObject(endpoint.requestBody),responseRootPath:text(endpoint.responseRootPath),treeChildrenPath:text(endpoint.treeChildrenPath),pagination:asObject(endpoint.pagination),paramBindings:asArray(endpoint.paramBindings).map((binding)=>{if(binding.from!=="endpoint")return{...binding};let{endpointId,...rest3}=binding;return{...rest3,endpointName:nameById.get(String(endpointId))??null}}),enabled:endpoint.enabled!==!1})),mappings:mappings.map((mapping)=>({name:String(mapping.name??""),endpointName:nameById.get(String(mapping.endpointId))??null,targetEntity:String(mapping.targetEntity??""),fieldMappings:asArray(mapping.fieldMappings),referenceChecks:asArray(mapping.referenceChecks),dedupSourceField:text(mapping.dedupSourceField),dedupTargetField:text(mapping.dedupTargetField),writeMode:String(mapping.writeMode??"upsert")??"upsert",missingRecordPolicy:String(mapping.missingRecordPolicy??"ignore")??"ignore",missingRecordColumn:text(mapping.missingRecordColumn),missingRecordValue:text(mapping.missingRecordValue),removalScopeColumn:text(mapping.removalScopeColumn),passwordTargetColumn:text(mapping.passwordTargetColumn),accountRule:mapping.accountRule??null,scheduleEnabled:mapping.scheduleEnabled===!0,scheduleCron:text(mapping.scheduleCron),scheduleTimezone:text(mapping.scheduleTimezone),enabled:mapping.enabled!==!1}))}}function validateTransferDocument(value2){let problems=[],doc=asObject(value2);if(!doc)return{ok:!1,document:null,problems:[{path:"",message:"This is not an integration export file."}]};if(typeof doc.version!=="number")problems.push({path:"version",message:"The file does not say which format it is in."});else if(doc.version>1)problems.push({path:"version",message:`This file was written by a newer version (${doc.version}); this one understands up to 1.`});let connection=asObject(doc.connection);if(!connection)problems.push({path:"connection",message:"The file has no connection."});else{if(!text(connection.name))problems.push({path:"connection.name",message:"Missing name."});if(!text(connection.slug))problems.push({path:"connection.slug",message:"Missing slug."});if(!text(connection.baseUrl))problems.push({path:"connection.baseUrl",message:"Missing base URL."})}let endpoints=asArray(doc.endpoints),endpointNames=new Set;return endpoints.forEach((endpoint,index)=>{let name2=text(endpoint.name);if(!name2){problems.push({path:`endpoints[${index}].name`,message:"Missing name."});return}if(endpointNames.has(name2))problems.push({path:`endpoints[${index}].name`,message:`Two endpoints are called "${name2}"; names are how the rest of the file refers to them.`});if(endpointNames.add(name2),!text(endpoint.path))problems.push({path:`endpoints[${index}].path`,message:"Missing path."});if(!text(endpoint.tag))problems.push({path:`endpoints[${index}].tag`,message:"Missing tag."})}),endpoints.forEach((endpoint,index)=>{asArray(endpoint.paramBindings).forEach((binding,bindingIndex)=>{if(binding.from!=="endpoint")return;let target2=text(binding.endpointName);if(!target2||!endpointNames.has(target2))problems.push({path:`endpoints[${index}].paramBindings[${bindingIndex}]`,message:`Takes its value from an endpoint called "${target2??"?"}", which is not in this file.`})})}),asArray(doc.mappings).forEach((mapping,index)=>{if(!text(mapping.name))problems.push({path:`mappings[${index}].name`,message:"Missing name."});if(!text(mapping.targetEntity))problems.push({path:`mappings[${index}].targetEntity`,message:"Missing target table."});let endpointName=text(mapping.endpointName);if(!endpointName||!endpointNames.has(endpointName))problems.push({path:`mappings[${index}].endpointName`,message:`Reads from an endpoint called "${endpointName??"?"}", which is not in this file.`})}),{ok:problems.length===0,document:problems.length===0?doc:null,problems}}init_crypto();init_routeCollector();init_utils5();init_utils6();init_godminGuard();init_esm3();init_utils5();async function testConnection(chain,tokenCache,deps={}){let{source,oauth}=chain,fetchImpl=deps.fetchImpl;if(oauth){tokenCache.invalidate(source.id);let token=await getAccessToken(oauth,tokenCache,{fetchImpl});return token.ok?{ok:!0,detail:`A token was issued (${token.accessToken.length} characters). The credentials and token address are valid.`}:{ok:!1,error:token.error}}let built=buildUrl(source.baseUrl,"");if(!built.ok)return{ok:!1,error:built.error};let{headers}=applyAuth(source,built.url),outcome=await fetchOnce(source,{method:"GET",url:built.url,headers},{fetchImpl});if(outcome.status===0)return{ok:!1,error:outcome.error};return{ok:outcome.status<500,detail:`The address answered: HTTP ${outcome.status}.`,error:outcome.status>=500?outcome.error:void 0}}var PREVIEW_LIMIT=1500;async function sampleEndpoint(source,endpoint,pathParams,tokenCache,oauth,deps={}){let fetchImpl=deps.fetchImpl,bearerToken=null;if(oauth){let token=await getAccessToken(oauth,tokenCache,{fetchImpl});if(!token.ok)return blank({error:token.error});bearerToken=token.accessToken}let values={...endpoint.queryParams??{},...pathParams??{}},resolved=resolvePath2(endpoint.path,values);if(resolved.missing.length>0)return blank({error:`This endpoint needs a value for ${resolved.missing.map((m)=>`{${m}}`).join(", ")}. Enter an example value to test it (e.g. ${resolved.missing[0]} = 12345).`});let query={};for(let[key2,value2]of Object.entries(values)){if(value2==null||resolved.used.has(key2))continue;query[key2]=value2}let built=buildUrl(source.baseUrl,resolved.path,query);if(!built.ok)return blank({error:built.error});let{headers}=applyAuth({...source,token:bearerToken??source.token},built.url);if(bearerToken)headers.Authorization=`Bearer ${bearerToken}`;let outcome=await fetchOnce(source,{method:endpoint.method,url:built.url,headers,body:endpoint.method.toUpperCase()==="GET"?void 0:endpoint.requestBody??{}},{fetchImpl}),bodyKeys=outcome.body&&typeof outcome.body==="object"&&!Array.isArray(outcome.body)?Object.keys(outcome.body).slice(0,40):[];if(!outcome.ok)return blank({status:outcome.status,error:outcome.error??`HTTP ${outcome.status}`,bodyKeys,bodyPreview:previewBody(outcome.body)});let childrenPath=treeChildrenPath(endpoint.treeChildrenPath),extracted=extractRecords(outcome.body,endpoint.responseRootPath),flattened=childrenPath?flattenTree(extracted,childrenPath):null;if(flattened?.truncated)return blank({status:outcome.status,error:"This source answered with a structure that nests into itself, so it cannot be walked. Check the field named as the children.",bodyKeys,bodyPreview:previewBody(outcome.body)});let records=flattened?flattened.records:extracted;return{ok:!0,status:outcome.status,error:records.length===0?bodyKeys.length>0?`No record array was found. The response's top-level keys are: ${bodyKeys.join(", ")} \u2014 set the response root path to whichever holds the list.`:"No record array was found in the response.":null,recordCount:records.length,sample:records.slice(0,5),fields:records.length>0?flattenKeys(records[0]):[],bodyKeys,bodyPreview:previewBody(outcome.body)}}function blank(over){return{ok:!1,status:0,error:null,recordCount:0,sample:[],fields:[],bodyKeys:[],bodyPreview:null,...over}}function previewBody(body){if(body==null)return null;try{let text2=typeof body==="string"?body:JSON.stringify(body);return text2.length>PREVIEW_LIMIT?`${text2.slice(0,PREVIEW_LIMIT)}\u2026`:text2}catch{return null}}async function previewRun(chain,tokenCache,lookups,deps={}){let fetchImpl=deps.fetchImpl,{mapping,source,oauth}=chain,bearerToken=null;if(oauth){let token=await getAccessToken(oauth,tokenCache,{fetchImpl});if(!token.ok)return{ok:!1,error:token.error};bearerToken=token.accessToken}let root=chain.endpoints.find((e)=>e.id===chain.rootEndpointId);if(!root)return{ok:!1,error:"The endpoint this mapping reads no longer exists."};let parents={},entityValues={},externalCalls=0;for(let binding of root.paramBindings??[]){if(binding.from==="endpoint"&&!parents[binding.endpointId]){let parentSpec=chain.endpoints.find((e)=>e.id===binding.endpointId);if(!parentSpec)continue;let collected2=await collectEndpoint(source,parentSpec,{},{fetchImpl,bearerToken,maxConcurrent:deps.maxConcurrent,maxRecords:deps.maxRecords});if(externalCalls+=collected2.callCount,!collected2.ok)return{ok:!1,error:collected2.error};parents[binding.endpointId]=collected2.records.map((r)=>r.record)}if(binding.from==="entity"){let key2=`${binding.entity}.${binding.field}`;if(!entityValues[key2])entityValues[key2]=await lookups.readEntityColumn(binding.entity,binding.field)}}let collected=await collectEndpoint(source,root,{parents,entityValues},{fetchImpl,bearerToken,maxConcurrent:deps.maxConcurrent,maxRecords:deps.maxRecords});if(externalCalls+=collected.callCount,!collected.ok)return{ok:!1,error:collected.error};let mapped3=buildMappedRecords(collected.records.map((r)=>r.record),mapping.fieldMappings,mapping.dedupTargetField,collected.records.map((r)=>r.parent)),existing=await lookups.readExistingRows(mapping.targetEntity),referenceValues=await loadReferenceValues(mapping.references??[],(entity,column)=>lookups.readEntityColumn(entity,column)),references=await loadReferenceIds(mapping.references??[],mapping.targetEntity,(entity,column)=>lookups.readEntityPairsBy(entity,column)),referenceIds=references.ids,existingAccounts=mapping.accountRule?await lookups.readEntityIdsBy(mapping.accountRule.entity,mapping.accountRule.matchColumn):{},ops=planWrites({records:mapped3.records,existing,dedupTargetField:mapping.dedupTargetField,writeMode:mapping.writeMode,missingPolicy:mapping.missingPolicy,missingColumn:mapping.missingColumn,missingValue:mapping.missingValue,requiredFields:lookups.requiredFields(mapping.targetEntity),uniqueFields:lookups.uniqueFields(mapping.targetEntity),createDefaults:mapping.passwordColumn?{[mapping.passwordColumn]:"\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022"}:{},accountRule:mapping.accountRule??null,existingAccounts,references:mapping.references??[],referenceValues,referenceIds,targetEntity:mapping.targetEntity});ops.issues.unshift(...mapped3.issues),ops.warnings.push(...ambiguityWarnings(references.ambiguous));let invalid=lookups.validateRows?.(ops.creates)??[];for(let problem of invalid){let create4=ops.creates.find((c)=>c.sourceIndex===problem.sourceIndex);ops.issues.push({sourceIndex:problem.sourceIndex,kind:"invalid_value",message:problem.message,row:create4?.data})}if(invalid.length>0){let refused=new Set(invalid.map((problem)=>problem.sourceIndex));ops.creates=ops.creates.filter((create4)=>!refused.has(create4.sourceIndex))}return{ok:!0,externalCalls,plan:summarize(ops,{targetEntity:mapping.targetEntity,writeMode:mapping.writeMode,missingPolicy:mapping.missingPolicy,totalFetched:collected.records.length,reportedTotal:collected.reportedTotal,existingCount:existing.length,dedupConfigured:Boolean(mapping.dedupTargetField)})}}init_crypto();import{and as and21,eq as eq35,inArray as inArray15}from"drizzle-orm";var camel2=(s)=>s.replace(/_([a-z])/g,(_,l)=>l.toUpperCase());function tableFor(deps,entity){return deps.schemaTables[camel2(entity)]??null}var col8=(table,field)=>table?.[field]??table?.[camel2(field)];function open(deps,envelope,where){if(typeof envelope!=="string"||envelope==="")return"";let key2=deps.getEncryptionKey();if(!key2)return deps.logger.error("[Integrations] A stored credential cannot be read: no encryption key",{column:where}),"";try{return decryptSecret(envelope,key2)}catch{return deps.logger.error("[Integrations] A stored credential could not be decrypted \u2014 the encryption key has changed since it was saved. Re-enter it in the panel.",{column:where}),""}}async function resolveChain(deps,mappingId){let mappings=tableFor(deps,"integration_mappings"),endpoints=tableFor(deps,"integration_endpoints"),sources=tableFor(deps,"integration_sources");if(!mappings||!endpoints||!sources)return{ok:!1,error:"The integration tables are not present in this schema."};let[mappingRow]=await deps.db.select().from(mappings).where(eq35(col8(mappings,"id"),mappingId)).limit(1);if(!mappingRow)return{ok:!1,error:"This mapping no longer exists."};let row=mappingRow,[endpointRow]=await deps.db.select().from(endpoints).where(eq35(col8(endpoints,"id"),String(row.endpointId))).limit(1);if(!endpointRow)return{ok:!1,error:"The endpoint this mapping reads no longer exists."};if(endpointRow.enabled===!1)return{ok:!1,error:"The call this mapping reads is switched off."};if(row.enabled===!1)return{ok:!1,error:"This mapping is switched off."};let sourceId=String(endpointRow.sourceId),siblingRows=await deps.db.select().from(endpoints).where(eq35(col8(endpoints,"sourceId"),sourceId)),[sourceRow]=await deps.db.select().from(sources).where(eq35(col8(sources,"id"),sourceId)).limit(1);if(!sourceRow)return{ok:!1,error:"The source this mapping reads no longer exists."};let s=sourceRow;if(s.enabled===!1)return{ok:!1,error:"This source is switched off."};let authType=String(s.authType??"none"),source={id:String(s.id),baseUrl:String(s.baseUrl??""),authType,token:authType==="oauth2"?null:open(deps,s.authTokenEncrypted,"auth_token_encrypted"),authHeaderName:s.authHeaderName??null,authQueryParam:s.authQueryParam??null,extraHeaders:s.extraHeaders??null,tlsVerify:s.tlsVerify!==!1,timeoutMs:Number(s.timeoutMs??30000)},oauth=authType==="oauth2"?{sourceId:source.id,tokenUrl:String(s.tokenUrl??""),grantType:String(s.oauthGrantType??"password")==="client_credentials"?"client_credentials":"password",clientId:s.oauthClientId??null,clientSecret:open(deps,s.oauthClientSecretEncrypted,"oauth_client_secret_encrypted"),username:s.oauthUsername??null,password:open(deps,s.oauthPasswordEncrypted,"oauth_password_encrypted"),scope:s.oauthScope??null,clientAuth:String(s.oauthClientAuth??"basic")==="body"?"body":"basic",tlsVerify:source.tlsVerify,timeoutMs:source.timeoutMs}:null;return{ok:!0,chain:{mapping:toMappingSpec(row),endpoints:siblingRows.map((e)=>toEndpointSpec(e)),rootEndpointId:String(endpointRow.id),source,oauth}}}async function resolveEndpointContext(deps,by){let sources=tableFor(deps,"integration_sources"),endpoints=tableFor(deps,"integration_endpoints");if(!sources||!endpoints)return{ok:!1,error:"The integration tables are not present in this schema."};let endpointRow=null,sourceId=by.sourceId;if(by.endpointId){let[row]=await deps.db.select().from(endpoints).where(eq35(col8(endpoints,"id"),by.endpointId)).limit(1);if(!row)return{ok:!1,error:"This endpoint no longer exists."};endpointRow=row,sourceId=String(endpointRow.sourceId)}if(!sourceId)return{ok:!1,error:"No source was named."};let[sourceRow]=await deps.db.select().from(sources).where(eq35(col8(sources,"id"),sourceId)).limit(1);if(!sourceRow)return{ok:!1,error:"This source no longer exists."};let{source,oauth}=toConnection(deps,sourceRow);return{ok:!0,context:{source,oauth,endpoint:endpointRow?toEndpointSpec(endpointRow):null}}}async function cacheEndpointSample(deps,endpointId,records){let endpoints=tableFor(deps,"integration_endpoints");if(!endpoints||records.length===0)return;try{await deps.db.update(endpoints).set({sampleResponse:{records:records.slice(0,3)},updatedAt:new Date}).where(eq35(col8(endpoints,"id"),endpointId))}catch(error3){deps.logger.warn("[Integrations] Could not cache the endpoint sample",{endpointId,error:error3 instanceof Error?error3.message:String(error3)})}}function toConnection(deps,s){let authType=String(s.authType??"none"),source={id:String(s.id),baseUrl:String(s.baseUrl??""),authType,token:authType==="oauth2"?null:open(deps,s.authTokenEncrypted,"auth_token_encrypted"),authHeaderName:s.authHeaderName??null,authQueryParam:s.authQueryParam??null,extraHeaders:s.extraHeaders??null,tlsVerify:s.tlsVerify!==!1,timeoutMs:Number(s.timeoutMs??30000)},oauth=authType==="oauth2"?{sourceId:source.id,tokenUrl:String(s.tokenUrl??""),grantType:String(s.oauthGrantType??"password")==="client_credentials"?"client_credentials":"password",clientId:s.oauthClientId??null,clientSecret:open(deps,s.oauthClientSecretEncrypted,"oauth_client_secret_encrypted"),username:s.oauthUsername??null,password:open(deps,s.oauthPasswordEncrypted,"oauth_password_encrypted"),scope:s.oauthScope??null,clientAuth:String(s.oauthClientAuth??"basic")==="body"?"body":"basic",tlsVerify:source.tlsVerify,timeoutMs:source.timeoutMs}:null;return{source,oauth}}function toMappingSpec(row){return{id:String(row.id),name:String(row.name??""),targetEntity:String(row.targetEntity??""),fieldMappings:Array.isArray(row.fieldMappings)?row.fieldMappings:[],references:Array.isArray(row.referenceChecks)?row.referenceChecks:[],dedupTargetField:row.dedupTargetField||null,writeMode:String(row.writeMode??"upsert")??"upsert",missingPolicy:String(row.missingRecordPolicy??"ignore")??"ignore",missingColumn:row.missingRecordColumn??null,removalScopeColumn:row.removalScopeColumn||null,missingValue:row.missingRecordValue??null,originColumn:row.originColumn??null,originValue:row.originValue??null,maxRemovalShare:Number(row.maxRemovalShare??0)||0,passwordColumn:row.passwordTargetColumn||null,accountRule:row.accountRule??null}}function toEndpointSpec(row){let pagination=row.pagination;return{id:String(row.id),method:String(row.method??"GET"),path:String(row.path??""),queryParams:row.queryParams??null,requestBody:row.requestBody??null,responseRootPath:row.responseRootPath||null,treeChildrenPath:row.treeChildrenPath||null,pagination:pagination&&typeof pagination==="object"&&"kind"in pagination?pagination:{kind:"none"},paramBindings:Array.isArray(row.paramBindings)?row.paramBindings:null}}async function readConnectionBundle(deps,sourceId){let sources=tableFor(deps,"integration_sources"),endpoints=tableFor(deps,"integration_endpoints"),mappings=tableFor(deps,"integration_mappings");if(!sources||!endpoints||!mappings)return null;let[sourceRow]=await deps.db.select().from(sources).where(eq35(col8(sources,"id"),sourceId)).limit(1);if(!sourceRow)return null;let endpointRows=await deps.db.select().from(endpoints).where(eq35(col8(endpoints,"sourceId"),sourceId)),ids=new Set(endpointRows.map((row)=>String(row.id))),allMappings=await deps.db.select().from(mappings);return{source:sourceRow,endpoints:endpointRows,mappings:allMappings.filter((row)=>ids.has(String(row.endpointId)))}}async function applyTransferDocument(deps,document2,userId){let sources=tableFor(deps,"integration_sources"),endpoints=tableFor(deps,"integration_endpoints"),mappings=tableFor(deps,"integration_mappings");if(!sources||!endpoints||!mappings)return{ok:!1,error:"The integration tables are not present in this schema."};return deps.db.transaction(async(tx)=>{let now=new Date,c=document2.connection,connectionValues={name:c.name,slug:c.slug,baseUrl:c.baseUrl,description:c.description??null,authType:c.authType,authHeaderName:c.authHeaderName??null,authQueryParam:c.authQueryParam??null,extraHeaders:c.extraHeaders??null,tokenUrl:c.tokenUrl??null,oauthGrantType:c.oauthGrantType??null,oauthClientId:c.oauthClientId??null,oauthUsername:c.oauthUsername??null,oauthScope:c.oauthScope??null,oauthClientAuth:c.oauthClientAuth??null,tlsVerify:c.tlsVerify!==!1,timeoutMs:c.timeoutMs??null,enabled:c.enabled!==!1,updatedAt:now,updatedBy:userId},[existingSource]=await tx.select().from(sources).where(eq35(col8(sources,"slug"),c.slug)).limit(1),sourceId,created=!1;if(existingSource)sourceId=String(existingSource.id),await tx.update(sources).set(connectionValues).where(eq35(col8(sources,"id"),sourceId));else{let[row]=await tx.insert(sources).values({...connectionValues,createdAt:now,createdBy:userId}).returning();if(!row?.id)return{ok:!1,error:"The connection could not be created."};sourceId=String(row.id),created=!0}let existingEndpoints=await tx.select().from(endpoints).where(eq35(col8(endpoints,"sourceId"),sourceId)),endpointIdByName=new Map;for(let row of existingEndpoints)endpointIdByName.set(String(row.name),String(row.id));for(let endpoint of document2.endpoints){let values={sourceId,name:endpoint.name,tag:endpoint.tag,method:endpoint.method,path:endpoint.path,description:endpoint.description??null,queryParams:endpoint.queryParams??null,requestBody:endpoint.requestBody??null,responseRootPath:endpoint.responseRootPath??null,treeChildrenPath:endpoint.treeChildrenPath??null,pagination:endpoint.pagination??null,enabled:endpoint.enabled!==!1,updatedAt:now,updatedBy:userId},existingId=endpointIdByName.get(endpoint.name);if(existingId)await tx.update(endpoints).set(values).where(eq35(col8(endpoints,"id"),existingId));else{let[row]=await tx.insert(endpoints).values({...values,paramBindings:[],createdAt:now,createdBy:userId}).returning();if(row?.id)endpointIdByName.set(endpoint.name,String(row.id))}}for(let endpoint of document2.endpoints){let id=endpointIdByName.get(endpoint.name);if(!id)continue;let bindings=(endpoint.paramBindings??[]).map((binding)=>{if(binding.from!=="endpoint")return binding;let{endpointName,...rest3}=binding;return{...rest3,endpointId:endpointIdByName.get(String(endpointName))??""}});await tx.update(endpoints).set({paramBindings:bindings,updatedAt:now}).where(eq35(col8(endpoints,"id"),id))}let allMappings=await tx.select().from(mappings),ownIds=new Set(endpointIdByName.values()),mappingIdByName=new Map;for(let row of allMappings)if(ownIds.has(String(row.endpointId)))mappingIdByName.set(String(row.name),String(row.id));let mappingCount=0;for(let mapping of document2.mappings){let endpointId=mapping.endpointName?endpointIdByName.get(mapping.endpointName):void 0;if(!endpointId)continue;let values={endpointId,name:mapping.name,targetEntity:mapping.targetEntity,fieldMappings:mapping.fieldMappings??[],referenceChecks:mapping.referenceChecks??[],dedupSourceField:mapping.dedupSourceField??null,dedupTargetField:mapping.dedupTargetField??null,writeMode:mapping.writeMode,missingRecordPolicy:mapping.missingRecordPolicy,missingRecordColumn:mapping.missingRecordColumn??null,missingRecordValue:mapping.missingRecordValue??null,removalScopeColumn:mapping.removalScopeColumn??null,passwordTargetColumn:mapping.passwordTargetColumn??null,accountRule:mapping.accountRule??null,scheduleEnabled:mapping.scheduleEnabled===!0,scheduleCron:mapping.scheduleCron??null,scheduleTimezone:mapping.scheduleTimezone??null,enabled:mapping.enabled!==!1,updatedAt:now,updatedBy:userId},existingId=mappingIdByName.get(mapping.name);if(existingId)await tx.update(mappings).set(values).where(eq35(col8(mappings,"id"),existingId));else await tx.insert(mappings).values({...values,createdAt:now,createdBy:userId});mappingCount++}return{ok:!0,sourceId,created,endpoints:endpointIdByName.size,mappings:mappingCount}})}async function readScheduledMappings(deps){let mappings=tableFor(deps,"integration_mappings");if(!mappings)return[];return(await deps.db.select().from(mappings)).filter((row)=>row.scheduleEnabled===!0&&row.enabled!==!1).filter((row)=>typeof row.scheduleCron==="string"&&row.scheduleCron.trim()!=="").map((row)=>({id:String(row.id),name:String(row.name??""),scheduleCron:String(row.scheduleCron),scheduleTimezone:row.scheduleTimezone??null,lastRunAt:row.lastRunAt?new Date(row.lastRunAt):null,needsPassword:Boolean(row.passwordTargetColumn)||row.accountRule!=null}))}function requiredFieldsOf(entities,targetEntity){return(entities.find((e)=>e.table_name===targetEntity)?.columns??[]).filter((c)=>c.notNull===!0&&c.default===void 0&&!c.defaultRaw&&!c.nullable).map((c)=>camel2(c.name))}function uniqueFieldsOf(entities,targetEntity){let entity=entities.find((e)=>e.table_name===targetEntity),columns=entity?.columns??[],fields=new Set;for(let column of columns)if(column.unique===!0||column.primaryKey===!0)fields.add(camel2(column.name));for(let index of entity?.indexes??[])if(index.unique===!0&&index.columns.length===1&&index.columns[0])fields.add(camel2(index.columns[0]));return fields.delete("id"),[...fields]}function createRunRepository(deps,entities){let runs=()=>tableFor(deps,"integration_runs"),runItems=()=>tableFor(deps,"integration_run_items");return{async openRun(input){let table=runs();if(!table)return{ok:!1,error:"integration_runs is missing from this schema."};try{let id=(await deps.db.insert(table).values({mappingId:input.mappingId,targetEntity:input.targetEntity,writeMode:input.writeMode,trigger:input.trigger,status:"running",startedAt:new Date,createdBy:input.userId??null}).returning())[0]?.id;return id?{ok:!0,runId:String(id)}:{ok:!1,error:"The run record was created but returned no identifier."}}catch(error3){return{ok:!1,error:error3 instanceof Error?error3.message:String(error3)}}},async closeRun(runId,patch){let table=runs();if(!table)return{ok:!1,error:"integration_runs is missing from this schema."};try{let mappingId=(await deps.db.update(table).set({...patch,updatedAt:new Date}).where(eq35(col8(table,"id"),runId)).returning())[0]?.mappingId,finishedAt=patch.finishedAt,mappings=tableFor(deps,"integration_mappings");if(mappings&&mappingId&&finishedAt)await deps.db.update(mappings).set({lastRunAt:finishedAt,updatedAt:new Date}).where(eq35(col8(mappings,"id"),String(mappingId)));return{ok:!0}}catch(error3){return deps.logger.error("[Integrations] Could not close the run record",{runId,error:error3 instanceof Error?error3.message:String(error3)}),{ok:!1,error:"Could not write the run record."}}},async savePreImages(runId,images){let table=runItems();if(!table)return!1;if(images.length===0)return!0;let CHUNK2=500;try{return await deps.db.transaction(async(tx)=>{for(let i=0;i<images.length;i+=CHUNK2)await tx.insert(table).values(images.slice(i,i+CHUNK2).map((image)=>({runId,targetEntity:image.targetEntity,rowId:image.rowId,action:image.action,before:image.before,after:image.after})))}),!0}catch(error3){return deps.logger.error("[Integrations] The run succeeded but its before-images could not be stored; it cannot be undone",{runId,error:error3 instanceof Error?error3.message:String(error3)}),!1}},async isCancelRequested(runId){let table=runs();if(!table)return!1;try{let[row]=await deps.db.select().from(table).where(eq35(col8(table,"id"),runId)).limit(1);return row?.cancelRequested===!0}catch{return!1}},async readExistingRows(targetEntity){let table=tableFor(deps,targetEntity);if(!table)return[];return(await deps.db.select().from(table)).map((row)=>({id:String(row.id),row}))},async readEntityColumn(entity,field){let table=tableFor(deps,entity),column=table?col8(table,field):void 0;if(!table||!column)return[];return(await deps.db.selectDistinct({v:column}).from(table)).map((r)=>r.v).filter((v)=>v!=null&&v!=="")},async readEntityIdsBy(entity,field){let table=tableFor(deps,entity),column=table?col8(table,field):void 0,idColumn=table?col8(table,"id"):void 0;if(!table||!column||!idColumn)return{};let rows=await deps.db.select({v:column,id:idColumn}).from(table),map3={};for(let row of rows){if(row.v==null||row.v===""||row.id==null)continue;let key2=String(row.v).trim();if(!map3[key2])map3[key2]=String(row.id)}return map3},async readEntityPairsBy(entity,field){let table=tableFor(deps,entity),column=table?col8(table,field):void 0,idColumn=table?col8(table,"id"):void 0;if(!table||!column||!idColumn)return[];return await deps.db.select({value:column,id:idColumn}).from(table)},requiredFields:(targetEntity)=>requiredFieldsOf(entities,targetEntity),uniqueFields:(targetEntity)=>uniqueFieldsOf(entities,targetEntity)}}async function readRowsById(deps,targetEntity,ids){let table=tableFor(deps,targetEntity),result=new Map;if(!table||ids.length===0)return result;let CHUNK2=1000;for(let i=0;i<ids.length;i+=CHUNK2){let rows=await deps.db.select().from(table).where(inArray15(col8(table,"id"),ids.slice(i,i+CHUNK2)));for(let row of rows)result.set(String(row.id),row)}return result}async function readPreImages(deps,runId){let table=tableFor(deps,"integration_run_items");if(!table)return[];return(await deps.db.select().from(table).where(eq35(col8(table,"runId"),runId))).map((row)=>({targetEntity:String(row.targetEntity),rowId:row.rowId?String(row.rowId):null,action:String(row.action),before:row.before??null,after:row.after??null}))}async function readRun(deps,runId){let table=tableFor(deps,"integration_runs");if(!table)return null;let[row]=await deps.db.select().from(table).where(eq35(col8(table,"id"),runId)).limit(1);return row??null}async function requestCancel(deps,runId){let table=tableFor(deps,"integration_runs");if(!table)return!1;return(await deps.db.update(table).set({cancelRequested:!0,updatedAt:new Date}).where(and21(eq35(col8(table,"id"),runId),eq35(col8(table,"status"),"running"))).returning()).length>0}var fail3=(set2,status,message)=>{return set2.status=status,{isSuccess:!1,message,data:null}};function createInspectRoutes(routeConfig,deps,tokenCache){let settings=routeConfig.resolvedOptions.integrations??{};return[defineRoute({method:"POST",path:"/sources/:id/test",detail:{tags:["Integrations"],summary:"Check a source is reachable and its credentials are accepted"},handler:async({params,set:set2})=>{let repoDeps=deps();if(!repoDeps)return fail3(set2,503,"The database is not available.");let context=await resolveEndpointContext(repoDeps,{sourceId:params.id});if(!context.ok)return fail3(set2,404,context.error);let result=await testConnection(context.context,tokenCache);return{isSuccess:result.ok,message:result.ok?result.detail??"Connected.":result.error??"Could not connect.",data:result}}}),defineRoute({method:"POST",path:"/endpoints/:id/sample",body:Type.Optional(Type.Object({pathParams:Type.Optional(Type.Record(Type.String(),Type.Unknown()))})),detail:{tags:["Integrations"],summary:"Call one endpoint once and report its real shape"},handler:async({params,body,set:set2})=>{let repoDeps=deps();if(!repoDeps)return fail3(set2,503,"The database is not available.");let context=await resolveEndpointContext(repoDeps,{endpointId:params.id});if(!context.ok)return fail3(set2,404,context.error);let{source,oauth,endpoint}=context.context;if(!endpoint)return fail3(set2,404,"This endpoint no longer exists.");let result=await sampleEndpoint(source,endpoint,body?.pathParams,tokenCache,oauth);if(result.ok&&result.sample.length>0)await cacheEndpointSample(repoDeps,params.id,result.sample);return{isSuccess:result.ok,message:result.error??"Sampled.",data:result}}}),defineRoute({method:"POST",path:"/preview",body:Type.Object({mappingId:Type.String()}),detail:{tags:["Integrations"],summary:"Dry-run an import: counts, warnings and a sample, with no writes"},handler:async({body,set:set2})=>{let repoDeps=deps();if(!repoDeps)return fail3(set2,503,"The database is not available.");let chain=await resolveChain(repoDeps,body.mappingId);if(!chain.ok)return fail3(set2,404,chain.error);let repo=createRunRepository(repoDeps,repoDeps.entities),result=await previewRun(chain.chain,tokenCache,{readExistingRows:repo.readExistingRows,readEntityColumn:repo.readEntityColumn,requiredFields:repo.requiredFields,uniqueFields:repo.uniqueFields,readEntityIdsBy:repo.readEntityIdsBy,readEntityPairsBy:repo.readEntityPairsBy,validateRows:(rows)=>{let columns=repoDeps.entities.find((e)=>e.table_name===chain.chain.mapping.targetEntity)?.columns??[],problems=[];for(let row of rows){let result2=validatePayload(row.data,columns,!1);if(!result2.valid)problems.push({sourceIndex:row.sourceIndex,message:result2.errors.map((e)=>`${e.field}: ${e.message}`).join("; ")})}return problems}},{maxRecords:settings.maxRecordsPerRun,maxConcurrent:settings.maxConcurrentFanout});if(!result.ok)return fail3(set2,422,result.error);return{isSuccess:!0,message:"Nothing was written.",data:{plan:result.plan,externalCalls:result.externalCalls}}}}),defineRoute({method:"POST",path:"/schedule-check",body:Type.Object({mappingId:Type.String(),at:Type.Optional(Type.String())}),detail:{tags:["Integrations"],summary:"Simulate the schedule: the next firings, and whether a given moment is one",description:"Writes nothing and starts nothing. Answers the question a schedule cannot otherwise be asked until the night it runs: does this expression name the minute I meant, in the clock I meant."},handler:async({body,set:set2})=>{let repoDeps=deps();if(!repoDeps)return fail3(set2,503,"The database is not available.");let chain=await resolveChain(repoDeps,body.mappingId);if(!chain.ok)return fail3(set2,404,chain.error);let raw=chain.chain.mapping,expression=String(raw.scheduleCron??"").trim(),timeZone=String(raw.scheduleTimezone??"").trim()||"UTC";if(expression==="")return{isSuccess:!0,message:"Nothing was written.",data:{enabled:raw.scheduleEnabled===!0,expression:"",timeZone,times:[]}};let at=body.at?new Date(body.at):new Date;if(Number.isNaN(at.getTime()))return fail3(set2,400,"That is not a moment I can read.");let upcoming=nextFireTimes({expression,timeZone,from:at,count:5}),verdict=isDue({expression,timeZone,now:at,lastRunAt:null});return{isSuccess:!0,message:"Nothing was written.",data:{enabled:raw.scheduleEnabled===!0,expression,timeZone,describes:describeCron(expression,timeZone),at:at.toISOString(),wouldFireAtThatMoment:verdict.due,error:verdict.error??upcoming.error??null,times:upcoming.times.map((t)=>t.toISOString())}}}})]}var fail4=(set2,status,message)=>{return set2.status=status,{isSuccess:!1,message,data:null}};async function closeAbandonedRuns(repoDeps,mappingId,logger2){try{let runsTable=tableFor(repoDeps,"integration_runs");if(!runsTable)return;let{and:and22,eq:eq36,inArray:inArray16}=await import("drizzle-orm"),closed=await repoDeps.db.update(runsTable).set({status:"failed",finishedAt:new Date,errors:["This import stopped without finishing \u2014 the process running it went away (a restart, a deploy, or a crash). Whatever it had already written is still there; run it again, or check the target table."],updatedAt:new Date}).where(and22(eq36(runsTable.mappingId,mappingId),inArray16(runsTable.status,["running","queued"]))).returning();if(closed.length>0)logger2.warn(`[Integrations] ${closed.length} run(s) of this mapping were left open by a process that went away; marked as failed.`)}catch(error3){logger2.error("[Integrations] Could not close abandoned runs",error3)}}var tokenCache=new TokenCache;function createIntegrationRoutes(routeConfig){let{logger:logger2,basePath,db,schemaTables,resolvedOptions,getEncryptionKey,getLockStore,publish}=routeConfig,entities=resolveEntities(SYSTEM_TABLES,resolvedOptions.entities??[],{consumerMode:resolvedOptions.authentication?.mode==="consumer"}).entities,settings=resolvedOptions.integrations??{},topicPrefix=settings.realtimeTopicPrefix||"integrations",guard4=createGodminGuard({logger:logger2,db,schemaTables,area:"data integrations",allowedRoles:routeConfig.adminRoles??settings.adminRoles}),deps=()=>db?{db,schemaTables,entities,logger:logger2,getEncryptionKey}:null,inspectRoutes=createInspectRoutes(routeConfig,deps,tokenCache),writerFor=(repoDeps,targetEntity)=>{let table=tableFor(repoDeps,targetEntity);if(!table)return null;let entity=entities.find((e)=>e.table_name===targetEntity);return{db:repoDeps.db,drizzleTable:table,resolveCol:(field)=>table[field]??table[field.replace(/_([a-z])/g,(_m,l)=>l.toUpperCase())],columns:entity?.columns??[],logger:logger2,validatePayload,resolveEntity:(name2)=>{let refTable=tableFor(repoDeps,name2);if(!refTable)return null;let refEntity=entities.find((e)=>e.table_name===name2);return{table:refTable,columns:refEntity?.columns??[]}},accountClosure:accountClosureFor(targetEntity)}};function accountClosureFor(targetEntity){let link=accountLinkFor(routeConfig.resolvedOptions.authentication?.accountLinks,targetEntity);if(!link)return null;let usersTable=schemaTables.users;if(!usersTable)return null;let sessionsTable=schemaTables.userSessions??schemaTables.sessions;return{userColumn:link.userColumn,accountEntity:"users",usersTable,sessionsTable:sessionsTable??null}}let routePostRuns=defineRoute({method:"POST",path:"/runs",body:Type.Object({mappingId:Type.String(),confirm:Type.Optional(Type.Boolean()),sharedPassword:Type.Optional(Type.String()),referenceMode:Type.Optional(Type.Union([Type.Literal("create"),Type.Literal("skipRecord")]))}),detail:{tags:["Integrations"],summary:"Start an import; answers 202 with the run identifier"},handler:async({body,set:set2,request})=>{let repoDeps=deps();if(!repoDeps)return fail4(set2,503,"The database is not available.");let chain=await resolveChain(repoDeps,body.mappingId);if(!chain.ok)return fail4(set2,404,chain.error);let declared2=chain.chain.mapping,answer=body.referenceMode,mapping=answer?{...declared2,references:(declared2.references??[]).map((check)=>check.onMissing==="ignore"?check:{...check,onMissing:answer})}:declared2,wantsPassword=Boolean(mapping.passwordColumn||mapping.accountRule),createDefaults,accountPasswordHash=null;if(wantsPassword){let supplied=typeof body.sharedPassword==="string"?body.sharedPassword:"";if(supplied.length<8)return fail4(set2,422,mapping.accountRule?"This import creates a login for every new record, so it needs a password to create them with: send sharedPassword, at least 8 characters. It is used once, for this run, and is stored nowhere.":`This mapping writes a password into "${mapping.passwordColumn}", so it needs one to write: send sharedPassword, at least 8 characters. It is used for the accounts this run creates and is not stored anywhere.`);let hash=await hashPassword(supplied);if(accountPasswordHash=hash,mapping.passwordColumn)createDefaults={[mapping.passwordColumn]:hash}}if(isDestructive(mapping.writeMode,mapping.missingPolicy)&&body.confirm!==!0)return set2.status=428,{isSuccess:!1,message:mapping.writeMode==="replace"?"This mapping rebuilds the whole table. Send confirm: true to proceed.":mapping.missingPolicy==="deactivate"?"This mapping deactivates records the source no longer carries. Send confirm: true to proceed.":"This mapping deletes records the source no longer carries. Send confirm: true to proceed.",data:null};let lockStore=getLockStore();if(!lockStore)return fail4(set2,503,"The lock service is unavailable, so a second run of this mapping could not be prevented. Runs are refused until it is back.");let lock=await acquireRunLock(lockStore,mapping.id,{logger:logger2});if(!lock.ok){if(lock.reason==="unavailable")return fail4(set2,503,`The lock service is unavailable: ${lock.error}`);return set2.status=409,{isSuccess:!1,message:"This mapping is already importing. Wait for it to finish, or cancel it.",data:{heldSince:lock.heldSince}}}await closeAbandonedRuns(repoDeps,mapping.id,logger2);let writer=writerFor(repoDeps,mapping.targetEntity);if(!writer)return await lock.handle.release(),fail4(set2,422,`This mapping targets "${mapping.targetEntity}", which does not exist.`);let userId=request.headers.get("x-user-id")??"unknown",repo=createRunRepository(repoDeps,entities),announce=()=>{},runIdReady=new Promise((resolve2)=>{announce=resolve2}),started=executeRun({...chain.chain,mapping,userId,trigger:"manual",createDefaults,accountPasswordHash},{repo,writer,logger:logger2,tokenCache,publishProgress:(progress)=>publish?.(`${topicPrefix}.run`,progress),onRunOpened:(id)=>announce(id),maxRecordsPerRun:settings.maxRecordsPerRun,maxConcurrentFanout:settings.maxConcurrentFanout,retainPreImagesMs:parseDuration(settings.retainPreImagesFor)},lock.handle),runId=await Promise.race([runIdReady,started.then((outcome)=>outcome.runId)]);return started.catch(async(error3)=>{let reason=error3 instanceof Error?error3.message:String(error3);logger2.error("[Integrations] Run threw outside its own error handling",{mappingId:mapping.id,error:reason});let openedId=await runIdReady.catch(()=>null);if(openedId)await repo.closeRun(openedId,{status:"failed",finishedAt:new Date,errors:[reason]}).catch(()=>{})}).finally(()=>lock.handle.release()),set2.status=202,{isSuccess:!0,message:"The import has started.",data:{runId,mappingId:mapping.id,targetEntity:mapping.targetEntity}}}}),startScheduledRun=async(mappingId)=>{let repoDeps=deps();if(!repoDeps)return!1;let chain=await resolveChain(repoDeps,mappingId);if(!chain.ok)return logger2.error(`[Integrations] Scheduled import cannot run: ${chain.error}`),!1;let lockStore=getLockStore();if(!lockStore)return!1;let lock=await acquireRunLock(lockStore,mappingId,{logger:logger2});if(!lock.ok)return!1;let writer=writerFor(repoDeps,chain.chain.mapping.targetEntity);if(!writer)return await lock.handle.release(),logger2.error(`[Integrations] Scheduled import targets "${chain.chain.mapping.targetEntity}", which does not exist.`),!1;return executeRun({...chain.chain,userId:null,trigger:"schedule"},{repo:createRunRepository(repoDeps,entities),writer,logger:logger2,tokenCache,publishProgress:(progress)=>publish?.(`${topicPrefix}.run`,progress),maxRecordsPerRun:settings.maxRecordsPerRun,maxConcurrentFanout:settings.maxConcurrentFanout,retainPreImagesMs:parseDuration(settings.retainPreImagesFor)},lock.handle).then((outcome)=>{if(outcome.status!=="succeeded")logger2.error(`[Integrations] Scheduled import "${chain.chain.mapping.name}" ${outcome.status}: ${outcome.failure?.message??"no reason recorded"}`)}).catch((error3)=>{logger2.error("[Integrations] Scheduled import threw outside its own error handling",{mappingId,error:error3 instanceof Error?error3.message:String(error3)})}).finally(()=>lock.handle.release()),!0},unattendable=new Set;if(settings.schedulesEnabled!==!1)startScheduler({logger:logger2,listScheduled:async()=>{let repoDeps=deps();if(!repoDeps)return[];return(await readScheduledMappings(repoDeps)).filter((row)=>{if(!row.needsPassword)return!0;if(!unattendable.has(row.id))unattendable.add(row.id),logger2.error(`[Integrations] "${row.name}" is scheduled but creates logins, which need a password nobody can type at that hour. It will not run on its own \u2014 start it by hand, or turn the login off.`);return!1})},startRun:(mappingId)=>startScheduledRun(mappingId)});let routeGetSourcesIdExport=defineRoute({method:"GET",path:"/sources/:id/export",detail:{tags:["Integrations"],summary:"Export a connection, its calls and its mappings \u2014 without any secret"},handler:async({params,set:set2})=>{let repoDeps=deps();if(!repoDeps)return fail4(set2,503,"The database is not available.");let bundle=await readConnectionBundle(repoDeps,params.id);if(!bundle)return fail4(set2,404,"This connection no longer exists.");let document2=buildTransferDocument({...bundle,exportedAt:new Date().toISOString()});return{isSuccess:!0,message:`${document2.endpoints.length} call(s) and ${document2.mappings.length} mapping(s). Credentials are NOT included \u2014 they are asked for again on the other side.`,data:document2}}}),routePostImport=defineRoute({method:"POST",path:"/import",body:Type.Object({document:Type.Unknown()}),detail:{tags:["Integrations"],summary:"Bring in an exported connection; re-running it updates rather than duplicates"},handler:async({body,set:set2,request})=>{let repoDeps=deps();if(!repoDeps)return fail4(set2,503,"The database is not available.");let checked=validateTransferDocument(body?.document);if(!checked.ok||!checked.document)return set2.status=422,{isSuccess:!1,message:"This file cannot be imported as it stands.",data:{problems:checked.problems}};let userId=request.headers.get("x-user-id")??"unknown",result=await applyTransferDocument(repoDeps,checked.document,userId);if(!result.ok)return fail4(set2,422,result.error);return{isSuccess:!0,message:`Connection "${checked.document.connection.name}" is here: ${result.endpoints} call(s) and ${result.mappings} mapping(s). Its credentials are not \u2014 enter them before running anything.`,data:result}}}),routePostRunsIdCancel=defineRoute({method:"POST",path:"/runs/:id/cancel",detail:{tags:["Integrations"],summary:"Ask a running import to stop"},handler:async({params,set:set2})=>{let repoDeps=deps();if(!repoDeps)return fail4(set2,503,"The database is not available.");if(!await requestCancel(repoDeps,params.id))return fail4(set2,409,"This run is not running, so there is nothing to cancel.");return{isSuccess:!0,message:"Cancellation requested. The import stops at its next checkpoint and anything it had written is rolled back.",data:{runId:params.id}}}}),routePostRunsIdRevert=defineRoute({method:"POST",path:"/runs/:id/revert",body:Type.Optional(Type.Object({force:Type.Optional(Type.Boolean())})),detail:{tags:["Integrations"],summary:"Undo a run that succeeded"},handler:async({params,body,set:set2,request})=>{let repoDeps=deps();if(!repoDeps)return fail4(set2,503,"The database is not available.");let run=await readRun(repoDeps,params.id);if(!run)return fail4(set2,404,"This run no longer exists.");if(run.status!=="succeeded")return fail4(set2,409,`Only a run that finished successfully can be undone; this one is "${String(run.status)}".`);let expiry=run.preImagesExpireAt?new Date(String(run.preImagesExpireAt)):null;if(expiry&&expiry.getTime()<Date.now())return fail4(set2,410,"This run is too old to undo: the records of what it replaced have been cleared.");let targetEntity=String(run.targetEntity??""),preImages=await readPreImages(repoDeps,params.id);if(preImages.length===0)return fail4(set2,410,"There is no record of what this run replaced, so it cannot be undone.");let groups=groupPreImagesByEntity(preImages).sort((a,b)=>a.targetEntity===targetEntity?-1:b.targetEntity===targetEntity?1:0),planned=[];for(let group of groups){let current=await readRowsById(repoDeps,group.targetEntity,touchedRowIds(group.preImages));planned.push({targetEntity:group.targetEntity,ops:planRevert({preImages:group.preImages,current,force:body?.force===!0})})}let ops={deletes:planned.flatMap((p)=>p.ops.deletes),restores:planned.flatMap((p)=>p.ops.restores),reinserts:planned.flatMap((p)=>p.ops.reinserts),conflicts:planned.flatMap((p)=>p.ops.conflicts)};if(ops.deletes.length===0&&ops.restores.length===0&&ops.reinserts.length===0&&ops.conflicts.length>0)return set2.status=409,{isSuccess:!1,message:"Every record this run touched has been edited since. Undoing would discard those edits; send force: true to overwrite them anyway.",data:{conflicts:ops.conflicts}};let userId=request.headers.get("x-user-id")??"unknown";for(let group of planned)if(!writerFor(repoDeps,group.targetEntity))return fail4(set2,422,`"${group.targetEntity}" no longer exists.`);let undone={deleted:0,restored:0,reinserted:0},failure=null;try{await repoDeps.db.transaction(async(tx)=>{for(let group of planned){let groupWriter=writerFor(repoDeps,group.targetEntity),result2=await applyRevert(groupWriter,group.ops,{targetEntity:group.targetEntity,userId,tx});if(!result2.ok)throw failure=result2.error??"The undo could not be completed.",Error(failure);undone={deleted:undone.deleted+(result2.deleted??0),restored:undone.restored+(result2.restored??0),reinserted:undone.reinserted+(result2.reinserted??0)}}})}catch(error3){let message=failure??(error3 instanceof Error?error3.message:"The undo could not be completed.");return fail4(set2,500,`${message} Nothing was undone \u2014 every table was rolled back together.`)}let result={ok:!0,...undone,conflicts:ops.conflicts.length},finished=ops.conflicts.length===0,runsTable=tableFor(repoDeps,"integration_runs");if(runsTable&&finished){let{eq:eq36}=await import("drizzle-orm");await repoDeps.db.update(runsTable).set({status:"reverted",revertedAt:new Date,revertedBy:userId}).where(eq36(runsTable.id,params.id))}return{isSuccess:!0,message:finished?"The import was undone.":`Undone except for ${ops.conflicts.length} record(s) edited since the import. They were left as they are \u2014 send force: true to overwrite them too.`,data:{deleted:result.deleted,restored:result.restored,reinserted:result.reinserted,conflicts:ops.conflicts}}}}),routePutSourcesIdCredentials=defineRoute({method:"PUT",path:"/sources/:id/credentials",body:Type.Object({token:Type.Optional(Type.String()),oauthClientSecret:Type.Optional(Type.String()),oauthPassword:Type.Optional(Type.String())}),detail:{tags:["Integrations"],summary:"Store a source credential; only a masked preview is returned"},handler:async({params,body,set:set2})=>{let repoDeps=deps();if(!repoDeps)return fail4(set2,503,"The database is not available.");let key2=getEncryptionKey();if(!key2)return fail4(set2,503,"No encryption key is configured, so a credential stored now could never be read back.");let table=tableFor(repoDeps,"integration_sources");if(!table)return fail4(set2,503,"The integration tables are not present in this schema.");let patch={},hints={},apply=(value2,column,hintColumn,name2)=>{if(value2===void 0)return;let trimmed=value2.trim();if(trimmed===""){patch[column]=null,patch[hintColumn]=null,hints[name2]=null;return}patch[column]=encryptSecret(trimmed,key2),patch[hintColumn]=previewOf(trimmed),hints[name2]=previewOf(trimmed)};if(apply(body.token,"authTokenEncrypted","authTokenHint","token"),apply(body.oauthClientSecret,"oauthClientSecretEncrypted","oauthClientSecretHint","clientSecret"),apply(body.oauthPassword,"oauthPasswordEncrypted","oauthPasswordHint","password"),Object.keys(patch).length===0)return{isSuccess:!0,message:"Nothing to change.",data:{hints}};let{eq:eq36}=await import("drizzle-orm");return await repoDeps.db.update(table).set({...patch,updatedAt:new Date}).where(eq36(table.id,params.id)),tokenCache.invalidate(params.id),{isSuccess:!0,message:"Saved.",data:{hints}}}});logger2.info(`[Integrations] Routes enabled at ${basePath}`);let declared=[routePostRuns,routeGetSourcesIdExport,routePostImport,routePostRunsIdCancel,routePostRunsIdRevert,routePutSourcesIdCredentials,...inspectRoutes];return collectRoutes([{prefix:basePath,guards:[guard4],routes:declared}])}function parseDuration(value2){if(!value2)return;let match=value2.trim().match(/^(\d+)\s*([smhd])$/i);if(!match)return;let amount=Number(match[1]),unit=(match[2]??"d").toLowerCase();return amount*(unit==="s"?1000:unit==="m"?60000:unit==="h"?3600000:86400000)}init_esm3();init_routeCollector();init_adminGuard();var STREAM_HEADERS={"Content-Type":"text/event-stream; charset=utf-8","Cache-Control":"no-cache, no-transform",Connection:"keep-alive"},textEncoder=new TextEncoder,encodeEvent=(event,data)=>{let payload=typeof data==="string"?data:JSON.stringify(data);return textEncoder.encode(`event: ${event}
|
|
548
548
|
data: ${payload}
|
|
549
549
|
|
|
550
550
|
`)};function parseTimeToMs2(time2){let match=time2.match(/^(\d+)(ms|s|m|h)$/);if(!match?.[1]||!match[2])return 5000;let value2=parseInt(match[1],10);switch(match[2]){case"ms":return value2;case"s":return value2*1000;case"m":return value2*60*1000;case"h":return value2*60*60*1000;default:return 5000}}function createMonitoringRoutes(config){let{getService,logger:logger2,endpoints,db,schemaTables}=config,snapshotOrNull=async()=>await getService()?.getLatestSnapshot()??null,alertsOrEmpty=()=>getService()?.getActiveAlerts()??[],guard4=createPrivilegedGuard({db:db??null,schemaTables:schemaTables??{},logger:logger2},"monitoring",{allowRoles:endpoints.allowRoles}),mount=(routes2)=>collectRoutes([{prefix:endpoints.basePath,guards:[guard4],routes:routes2}]);if(!endpoints.enabled)return mount([]);logger2.info(`[Monitoring] Routes enabled at ${endpoints.basePath} (stream: ${endpoints.stream.enabled}, snapshot: ${endpoints.snapshot.enabled}, history: ${endpoints.history.enabled}, alerts: ${endpoints.alerts.enabled})`);let routes=[];if(endpoints.stream.enabled)routes.push(defineRoute({method:"GET",path:endpoints.stream.path,handler:({request})=>{let intervalMs=parseTimeToMs2(endpoints.stream.interval),abortSignal=request.signal;logger2.info("[Monitoring] SSE stream connected");let cleanup,stream=new ReadableStream({start(controller){let closed=!1,send2=(event,data)=>{if(closed)return;controller.enqueue(encodeEvent(event,data))},cleanupHandler=()=>{if(closed)return;closed=!0,clearInterval(timer),abortSignal?.removeEventListener("abort",cleanupHandler),logger2.info("[Monitoring] SSE stream disconnected");try{controller.close()}catch{}};cleanup=cleanupHandler;let tick=async()=>{if(closed)return;let snapshot=await snapshotOrNull();if(snapshot)send2("snapshot",snapshot);let alerts=alertsOrEmpty();if(alerts.length>0)send2("alerts",alerts)},timer=setInterval(tick,intervalMs);abortSignal?.addEventListener("abort",cleanupHandler),send2("open",{interval:intervalMs}),tick()},cancel(){cleanup?.()}});return new Response(stream,{headers:STREAM_HEADERS})},detail:{tags:["Monitoring"],summary:"Stream real-time monitoring data",description:"Server-Sent Events stream for real-time monitoring metrics"}}));if(endpoints.snapshot.enabled)routes.push(defineRoute({method:"GET",path:endpoints.snapshot.path,handler:async()=>{let snapshot=await snapshotOrNull();if(!snapshot)return{isSuccess:!0,message:"No monitoring data available yet",data:null};return{isSuccess:!0,message:"Current monitoring snapshot",data:snapshot}},detail:{tags:["Monitoring"],summary:"Get current monitoring snapshot",description:"Returns the latest monitoring metrics snapshot"}}));if(endpoints.history.enabled)routes.push(defineRoute({method:"GET",path:endpoints.history.path,handler:async({query})=>{let minutes=Math.min(query.minutes?parseInt(String(query.minutes),10):60,endpoints.history.maxMinutes),history=await getService()?.getHistory(minutes)??[];return{isSuccess:!0,message:`Monitoring history for last ${minutes} minutes`,data:{minutes,count:history.length,snapshots:history}}},query:Type.Object({minutes:Type.Optional(Type.Number())}),detail:{tags:["Monitoring"],summary:"Get monitoring history",description:"Returns historical monitoring data for the specified time range"}}));if(endpoints.alerts.enabled)routes.push(defineRoute({method:"GET",path:endpoints.alerts.path,handler:()=>{let alerts=alertsOrEmpty();return{isSuccess:!0,message:"Active alerts",data:{count:alerts.length,alerts}}},detail:{tags:["Monitoring"],summary:"Get active alerts",description:"Returns all currently active monitoring alerts"}})),routes.push(defineRoute({method:"POST",path:`${endpoints.alerts.path}/:alertId/acknowledge`,handler:({params,set:set2})=>{let service=getService();if(!service)return set2.status=503,{isSuccess:!1,message:"Monitoring service is not available",data:null};if(!service.acknowledgeAlert(params.alertId))return set2.status=404,{isSuccess:!1,message:"Alert not found",data:null};return{isSuccess:!0,message:"Alert acknowledged",data:{alertId:params.alertId}}},params:Type.Object({alertId:Type.String()}),detail:{tags:["Monitoring"],summary:"Acknowledge an alert",description:"Mark an alert as acknowledged"}}));return mount(routes)}init_esm3();init_routeCollector();init_adminGuard();function createLiveMonitoringRoutes(config){let{getService,getLifecycleRecorder,logger:logger2,basePath,streamInterval,db,schemaTables}=config,guard4=createGodminGuard2({db:db??null,schemaTables:schemaTables??{},logger:logger2},"live monitoring"),requireService=()=>{let svc=getService();if(!svc)throw Error("Live monitoring service not initialized");return svc};logger2.info(`[LiveMonitoring] Routes enabled at ${basePath} (stream interval: ${streamInterval}ms)`);let routes=[defineRoute({method:"GET",path:"/health",handler:()=>{let svc=getService();return{status:svc?"ok":"initializing",timestamp:Date.now(),monitoring:svc?.isEnabled()??!1}},detail:{tags:["Live Monitoring"],summary:"Health check for live monitoring"}}),defineRoute({method:"GET",path:"/settings",handler:()=>{return requireService().getSettings()},detail:{tags:["Live Monitoring"],summary:"Get live monitoring settings"}}),defineRoute({method:"PATCH",path:"/settings",handler:({body})=>{return requireService().changeSettings(body)},body:Type.Partial(Type.Object({logMemory:Type.Boolean(),logCpu:Type.Boolean(),logDapr:Type.Boolean(),logWebSocket:Type.Boolean(),memoryLogInterval:Type.Number(),cpuLogInterval:Type.Number(),memoryLogLimit:Type.Number(),cpuLogLimit:Type.Number(),daprLogLimit:Type.Number(),wsLogLimit:Type.Number(),requestLogLimit:Type.Number()})),detail:{tags:["Live Monitoring"],summary:"Change live monitoring settings"}}),defineRoute({method:"GET",path:"/incidents",handler:async({query})=>{let raw=Number(query?.limit),limit=Number.isFinite(raw)&&raw>0?Math.min(raw,50):20,recorder=getLifecycleRecorder?.();if(!recorder)return{available:!1,incidents:[]};return{available:!0,incidents:await recorder.incidents(limit)}},query:Type.Optional(Type.Object({limit:Type.Optional(Type.String())})),detail:{tags:["Live Monitoring"],summary:"Why previous runs of this process ended"}}),defineRoute({method:"GET",path:"/realtime",handler:({query})=>{let raw=Number(query?.windowMinutes),minutes=Number.isFinite(raw)&&raw>0?Math.min(raw,1440):void 0;return requireService().getRealtimeHealth(minutes?minutes*60000:void 0)},query:Type.Optional(Type.Object({windowMinutes:Type.Optional(Type.String())})),detail:{tags:["Live Monitoring"],summary:"Realtime (WebSocket) health digest"}}),defineRoute({method:"GET",path:"/logs",handler:()=>{return requireService().getLogs()},detail:{tags:["Live Monitoring"],summary:"Get all live monitoring logs"}}),defineRoute({method:"GET",path:"/logs/stream",handler:({request})=>{let abortSignal=request.signal,cleanup,svc=requireService(),snapshot=svc.getSnapshot(),lastTimestamps={memory:snapshot.memory.length?snapshot.memory[snapshot.memory.length-1]?.timestamp??0:0,cpu:snapshot.cpu.length?snapshot.cpu[snapshot.cpu.length-1]?.timestamp??0:0,request:snapshot.requests.length?snapshot.requests[snapshot.requests.length-1]?.timestamp??0:0,dapr:snapshot.dapr.length?snapshot.dapr[snapshot.dapr.length-1]?.timestamp??0:0,ws:snapshot.ws.length?snapshot.ws[snapshot.ws.length-1]?.timestamp??0:0},stream=new ReadableStream({start(controller){let closed=!1;controller.enqueue(encodeEvent("snapshot",snapshot));let interval=setInterval(()=>{if(closed)return;let update=svc.getUpdatesSince(lastTimestamps);if(!update){controller.enqueue(encodeEvent("heartbeat",{timestamp:Date.now()}));return}if(update.memory.length)lastTimestamps.memory=update.memory[update.memory.length-1]?.timestamp??lastTimestamps.memory;if(update.cpu.length)lastTimestamps.cpu=update.cpu[update.cpu.length-1]?.timestamp??lastTimestamps.cpu;if(update.requests.length)lastTimestamps.request=update.requests[update.requests.length-1]?.timestamp??lastTimestamps.request;if(update.dapr.length)lastTimestamps.dapr=update.dapr[update.dapr.length-1]?.timestamp??lastTimestamps.dapr;if(update.ws.length)lastTimestamps.ws=update.ws[update.ws.length-1]?.timestamp??lastTimestamps.ws;controller.enqueue(encodeEvent("update",update))},streamInterval),cleanupHandler=()=>{if(closed)return;closed=!0,clearInterval(interval),abortSignal?.removeEventListener("abort",cleanupHandler);try{controller.close()}catch{}};cleanup=cleanupHandler,abortSignal?.addEventListener("abort",cleanupHandler)},cancel(){cleanup?.()}});return new Response(stream,{headers:STREAM_HEADERS})},detail:{tags:["Live Monitoring"],summary:"Stream real-time live monitoring data via SSE"}})];return collectRoutes([{prefix:basePath,guards:[guard4],routes}])}init_esm3();init_routeCollector();init_adminGuard();var LOG_LEVELS=["debug","info","warn","error","fatal"],SOURCES=["logger","stdout","stderr"];function parseLevels(raw){if(!raw)return;let parsed=raw.split(",").map((value2)=>value2.trim().toLowerCase()).filter((value2)=>LOG_LEVELS.includes(value2));return parsed.length>0?parsed:void 0}function parseSource(raw){if(!raw)return;let value2=raw.trim().toLowerCase();return SOURCES.includes(value2)?value2:void 0}function parseMinLevel(raw){if(!raw)return;let value2=raw.trim().toLowerCase();return LOG_LEVELS.includes(value2)?value2:void 0}function parseCursor(raw){if(raw===void 0||raw===null||raw==="")return;let value2=Number(raw);return Number.isFinite(value2)&&value2>=0?value2:void 0}function buildQuery(query){return{levels:parseLevels(query.levels),minLevel:parseMinLevel(query.minLevel),scope:query.scope?.trim()||void 0,search:query.search||void 0,source:parseSource(query.source),after:parseCursor(query.after),before:parseCursor(query.before),limit:parseCursor(query.limit)}}var STREAM_KEEPALIVE_MS=25000;function createServerLogRoutes(config){let{buffer,logger:logger2,basePath,path:path7,allowRoles,streamEnabled=!0,clearEnabled=!0,db,schemaTables}=config,guard4=createPrivilegedGuard({db:db??null,schemaTables:schemaTables??{},logger:logger2},"server logs",{allowRoles}),routes=[defineRoute({method:"GET",path:path7,handler:({query})=>{return{isSuccess:!0,message:"Server logs",data:{...buffer.query(buildQuery(query)),scopes:buffer.scopes(),levelCounts:buffer.levelCounts()}}},query:Type.Object({levels:Type.Optional(Type.String()),minLevel:Type.Optional(Type.String()),scope:Type.Optional(Type.String()),search:Type.Optional(Type.String()),source:Type.Optional(Type.String()),after:Type.Optional(Type.Number()),before:Type.Optional(Type.Number()),limit:Type.Optional(Type.Number())}),detail:{tags:["Monitoring"],summary:"Read buffered server logs",description:"Filtered, cursor-paged view of the in-memory server log buffer. Use `after` to tail forward and `before` to page backwards."}})];if(streamEnabled)routes.push(defineRoute({method:"GET",path:`${path7}/stream`,handler:({request,query})=>{let tailQuery=buildQuery(query),{after,before:_before,limit:_limit,...filter}=tailQuery,abortSignal=request.signal,cleanup,stream=new ReadableStream({start(controller){let closed=!1,backlog=buffer.query({...filter,...after!==void 0?{after}:{},limit:200});controller.enqueue(encodeEvent("backlog",backlog));let unsubscribe=buffer.subscribe((record3)=>{if(closed)return;if(!buffer.query({...filter,after:record3.seq-1,limit:1}).records.some((r)=>r.seq===record3.seq))return;try{controller.enqueue(encodeEvent("logs",[record3]))}catch{cleanup?.()}}),heartbeat=setInterval(()=>{if(closed)return;try{controller.enqueue(encodeEvent("ping",{timestamp:Date.now()}))}catch{cleanup?.()}},STREAM_KEEPALIVE_MS),cleanupHandler=()=>{if(closed)return;closed=!0,clearInterval(heartbeat),unsubscribe(),abortSignal?.removeEventListener("abort",cleanupHandler);try{controller.close()}catch{}};cleanup=cleanupHandler,abortSignal?.addEventListener("abort",cleanupHandler)},cancel(){cleanup?.()}});return new Response(stream,{headers:STREAM_HEADERS})},query:Type.Object({levels:Type.Optional(Type.String()),minLevel:Type.Optional(Type.String()),scope:Type.Optional(Type.String()),search:Type.Optional(Type.String()),source:Type.Optional(Type.String()),after:Type.Optional(Type.Number())}),detail:{tags:["Monitoring"],summary:"Tail server logs",description:"Server-Sent Events tail of the server log buffer, filtered server-side."}}));if(clearEnabled)routes.push(defineRoute({method:"DELETE",path:path7,handler:()=>{return buffer.clear(),logger2.info("[ServerLogs] Buffer cleared from the admin panel"),{isSuccess:!0,message:"Server log buffer cleared",data:null}},detail:{tags:["Monitoring"],summary:"Clear the server log buffer"}}));return logger2.info(`[ServerLogs] Routes enabled at ${basePath}${path7} (capacity: ${buffer.getCapacity()}, stream: ${streamEnabled})`),collectRoutes([{prefix:basePath,guards:[guard4],routes}])}init_esm3();init_Notification();init_routeCollector();init_adminGuard();var CHANNELS=["portal","email","telegram","webhook"];function createNotificationRoutes(routeConfig){let{db,schemaTables,config,logger:logger2,tenantRegistry,getEmailService}=routeConfig,basePath=routeConfig.basePath||config.endpoints?.basePath||"/notifications",buildService=(tables)=>new NotificationService({db,schemaTables:tables,config,logger:logger2,getEmailService}),defaultService=buildService(schemaTables),serviceFor=(request)=>{if(!tenantRegistry)return defaultService;let schemaName=request.headers.get("x-tenant-schema");if(!schemaName)return defaultService;let ctx=tenantRegistry.getSchemaContext(schemaName);return ctx?buildService(ctx.schemaTables):defaultService},allowedSendRoles=new Set((config.endpoints?.sendRoles??[]).map((role)=>role.toLowerCase())),canSend=async(request)=>{if(allowedSendRoles.size>0){if(decodeHeaderList(request.headers.get("x-user-roles")).some((role)=>allowedSendRoles.has(role.toLowerCase())))return!0}return isGodminRequest(request,{db,schemaTables,logger:logger2})},requireUser=(request,set2)=>{let userId=request.headers.get("x-user-id");if(!userId)return set2.status=401,null;return userId},declared=[defineRoute({method:"GET",path:"/",handler:async({request,query,set:set2})=>{let userId=requireUser(request,set2);if(!userId)return{success:!1,message:"User ID required"};return{success:!0,data:await serviceFor(request).getNotifications(userId,{limit:query.limit?Number(query.limit):void 0,offset:query.offset?Number(query.offset):void 0,type:query.type||void 0})}},query:Type.Object({limit:Type.Optional(Type.String()),offset:Type.Optional(Type.String()),type:Type.Optional(Type.String())}),detail:{tags:["Notifications"],summary:"List the caller\u2019s notifications"}}),defineRoute({method:"GET",path:"/unseen-count",handler:async({request,set:set2})=>{let userId=requireUser(request,set2);if(!userId)return{success:!1,message:"User ID required"};return{success:!0,data:{count:await serviceFor(request).getUnseenCount(userId)}}},detail:{tags:["Notifications"],summary:"Unseen notification count"}}),defineRoute({method:"POST",path:"/:notification_id/seen",handler:async({params,request,set:set2})=>{let userId=requireUser(request,set2);if(!userId)return{success:!1,message:"User ID required"};if(!await serviceFor(request).markAsSeen(params.notification_id,userId))return set2.status=404,{success:!1,message:"Notification not found"};return{success:!0,message:"Marked as seen"}},params:Type.Object({notification_id:Type.String()}),detail:{tags:["Notifications"],summary:"Mark one notification as seen"}}),defineRoute({method:"POST",path:"/seen-all",handler:async({request,set:set2})=>{let userId=requireUser(request,set2);if(!userId)return{success:!1,message:"User ID required"};return{success:!0,data:{count:await serviceFor(request).markAllAsSeen(userId)}}},detail:{tags:["Notifications"],summary:"Mark every notification as seen"}}),defineRoute({method:"POST",path:"/send",handler:async({body,request,set:set2})=>{if(!requireUser(request,set2))return{success:!1,message:"Authentication required"};if(!await canSend(request))return set2.status=403,{success:!1,message:"Sending notifications requires godmin or an allowed role"};let recipients=body.userIds??(body.userId?[body.userId]:[]);if(recipients.length===0)return set2.status=400,{success:!1,message:"userId or userIds is required"};let channels=body.channels??[config.defaultChannel??"portal"],service=serviceFor(request),failed=(await Promise.allSettled(recipients.map((user_id)=>service.send({user_id,title:body.title,body:body.body,entity_name:body.entityName,entity_id:body.entityId,type:body.type??"custom",source:body.source,channels})))).map((result,index)=>{let userId=recipients[index];if(result.status==="rejected"){let reason=result.reason;return{userId,sent:!1,reason:reason instanceof Error?reason.message:String(reason)}}if(result.value.delivered.length>0)return{userId,sent:!0,reason:""};return{userId,sent:!1,reason:result.value.undelivered.map((one)=>`${one.channel}: ${one.reason}`).join("; ")||"no channel was requested"}}).filter((one)=>!one.sent),sentCount=recipients.length-failed.length,reasons=[...new Set(failed.map((one)=>one.reason))].join("; ");if(sentCount===0)set2.status=503;return{success:failed.length===0,message:failed.length===0?`Sent to ${recipients.length} recipient(s)`:`Sent to ${sentCount}, failed for ${failed.length}: ${reasons}`,data:{sent:sentCount,failed:failed.map((one)=>one.userId)}}},body:Type.Object({userId:Type.Optional(Type.String()),userIds:Type.Optional(Type.Array(Type.String())),title:Type.String(),body:Type.Optional(Type.String()),entityName:Type.Optional(Type.String()),entityId:Type.Optional(Type.String()),type:Type.Optional(Type.Union([Type.Literal("verification"),Type.Literal("system"),Type.Literal("custom")])),source:Type.Optional(Type.String()),channels:Type.Optional(Type.Array(Type.Union(CHANNELS.map((c)=>Type.Literal(c)))))}),detail:{tags:["Notifications"],summary:"Send a notification to one or more users",description:"Privileged. Delivers over the requested channels (portal, email, telegram, webhook). Email uses the configured provider \u2014 credentials resolve through the secrets store, so a rotation applies immediately."}}),defineRoute({method:"POST",path:"/email",handler:async({body,request,set:set2})=>{if(!requireUser(request,set2))return{success:!1,message:"Authentication required"};if(!await canSend(request))return set2.status=403,{success:!1,message:"Sending email requires godmin or an allowed role"};let emailService=getEmailService?.()??null;if(!emailService?.isAvailable())return set2.status=503,{success:!1,message:"No email provider is configured or available"};let result=await emailService.sendEmail({to:body.to,subject:body.subject,html:body.html,text:body.text,replyTo:body.replyTo});if(!result.success)return set2.status=502,{success:!1,message:result.error??"Email delivery failed"};return logger2.info("[Notification] Transactional email sent",{recipients:Array.isArray(body.to)?body.to.length:1,subject:body.subject}),{success:!0,message:"Email sent",data:null}},body:Type.Object({to:Type.Union([Type.String(),Type.Array(Type.String())]),subject:Type.String(),html:Type.Optional(Type.String()),text:Type.Optional(Type.String()),replyTo:Type.Optional(Type.String())}),detail:{tags:["Notifications"],summary:"Send a transactional email to explicit recipients",description:"Backend-to-backend send through the configured provider \u2014 an app never needs an email SDK or the provider credentials of its own. Privileged, same gate as /send."}})];return{routes:collectRoutes([{prefix:basePath,routes:declared}]),notificationService:defaultService}}init_routeCollector();init_routeRecording();init_websocket();init_ack_manager();import{timingSafeEqual as timingSafeEqual3}from"crypto";init_ack_manager();var DEFAULT_MAX_TOPICS_PER_CLIENT=512;function createClientManager(config){let clients=new Map,presenceBroadcastDebounce=new Map,{redis,logger:logger2}=config,ackTtl=config.ack.ttlSeconds,ackEnabled=config.ack.enabled,maxRetries=config.ack.maxRetries,presenceEnabled=config.presence.enabled,presenceDebounceMs=config.presence.debounceMs,maxClientsPerUser=config.maxClientsPerUser,maxTopicsPerClient=config.maxTopicsPerClient??DEFAULT_MAX_TOPICS_PER_CLIENT,MAX_TOPIC_LENGTH=256;function generateClientId(){return`client_${Date.now()}_${Math.random().toString(36).substring(2,9)}`}function capTopics(topics){let accepted=[],dropped=[],seen=new Set;for(let t of topics){if(typeof t!=="string")continue;let trimmed=t.length>256?t.slice(0,256):t;if(seen.has(trimmed))continue;if(seen.add(trimmed),accepted.length>=maxTopicsPerClient){dropped.push(trimmed);continue}accepted.push(trimmed)}return{accepted,dropped}}function reportDropped(context,clientId,userId,dropped){if(dropped.length===0)return;logger2.warn("[PubSub] Topic subscription capped \u2014 events for these will NOT be delivered",{context,clientId,userId,cap:maxTopicsPerClient,droppedCount:dropped.length,dropped})}function registerClient(clientId,ws,userId,topics){let existingClients=getClientsByUser(userId);if(existingClients.length>=maxClientsPerUser){let oldestClientId=existingClients[0];if(oldestClientId)logger2.info("[PubSub] Max clients reached, closing oldest",{userId,oldestClientId}),unregisterClient(oldestClientId)}let{accepted,dropped}=capTopics(topics);return clients.set(clientId,{ws,userId,subscribedTopics:new Set(accepted),connectedAt:Date.now(),pendingAcks:new Map}),logger2.info("[PubSub] Client registered",{clientId,userId,topics:accepted,requestedCount:topics.length,subscribedCount:accepted.length,totalClients:clients.size}),reportDropped("registerClient",clientId,userId,dropped),{accepted,dropped}}function unregisterClient(clientId){let client=clients.get(clientId);if(!client)return;let userId=client.userId;if(clients.delete(clientId),logger2.info("[PubSub] Client unregistered",{clientId,userId,totalClients:clients.size}),presenceEnabled&&userId){if(getClientsByUser(userId).length===0)broadcastPresenceToOthers(userId,"offline")}}function updateClientTopics(clientId,topics){let client=clients.get(clientId);if(!client)return{accepted:[],dropped:[]};let{accepted,dropped}=capTopics(topics);return client.subscribedTopics=new Set(accepted),reportDropped("updateClientTopics",clientId,client.userId,dropped),{accepted,dropped}}function getClientsByUser(userId){let result=[];for(let[clientId,client]of clients)if(client.userId===userId)result.push(clientId);return result}function getConnectedUserIds(){let userIds=new Set;for(let[,client]of clients)if(client.userId)userIds.add(client.userId);return userIds}function getClientCount(){return clients.size}function sendToClient(clientId,message){let client=clients.get(clientId);if(!client)return!1;try{return client.ws.send(JSON.stringify(message)),!0}catch{return clients.delete(clientId),!1}}function broadcastEvent(topic,event){let messageId=generateMessageId(),timestamp=Date.now(),payload=JSON.stringify({type:"event",messageId:ackEnabled?messageId:void 0,topic,data:event,timestamp}),sentCount=0;for(let[clientId,client]of clients)if(client.subscribedTopics.has("*")||client.subscribedTopics.has(topic))try{if(client.ws.send(payload),sentCount++,ackEnabled){if(client.pendingAcks.set(messageId,{sentAt:timestamp,retryCount:0}),recordSent(),client.userId)storePendingMessage(redis,{messageId,topic,clientId,userId:client.userId,data:event,sentAt:timestamp,retryCount:0,maxRetries},ackTtl).catch(()=>{})}}catch{clients.delete(clientId)}logger2.info("[PubSub] Broadcasted event",{topic,messageId,sentCount})}function broadcastToUser(userId,topic,event){let messageId=generateMessageId(),timestamp=Date.now(),payload=JSON.stringify({type:"event",messageId:ackEnabled?messageId:void 0,topic,data:event,timestamp}),sentCount=0,sentToAnyClient=!1;for(let[clientId,client]of clients){if(client.userId!==userId)continue;if(!client.subscribedTopics.has("*")&&!client.subscribedTopics.has(topic))continue;try{if(client.ws.send(payload),sentCount++,sentToAnyClient=!0,ackEnabled)client.pendingAcks.set(messageId,{sentAt:timestamp,retryCount:0}),recordSent()}catch{clients.delete(clientId)}}if(ackEnabled)storePendingMessage(redis,{messageId,topic,clientId:sentToAnyClient?"delivered":"pending",userId,data:event,sentAt:timestamp,retryCount:0,maxRetries},ackTtl).catch(()=>{});logger2.info("[PubSub] Broadcasted to user",{userId,topic,messageId,sentCount,storedForOffline:!sentToAnyClient})}function broadcastToUserNoAck(userId,topic,event){let timestamp=Date.now(),payload=JSON.stringify({type:"event",topic,data:event,timestamp});for(let[clientId,client]of clients){if(client.userId!==userId)continue;if(!client.subscribedTopics.has("*")&&!client.subscribedTopics.has(topic))continue;try{client.ws.send(payload)}catch{clients.delete(clientId)}}}function broadcastPresenceToOthers(userId,status){let debounceKey=`${userId}:${status}`,now=Date.now(),lastBroadcast=presenceBroadcastDebounce.get(debounceKey);if(lastBroadcast&&now-lastBroadcast<presenceDebounceMs)return;if(presenceBroadcastDebounce.set(debounceKey,now),presenceBroadcastDebounce.size>1000){let cutoff=now-presenceDebounceMs*2;for(let[key2,timestamp]of presenceBroadcastDebounce)if(timestamp<cutoff)presenceBroadcastDebounce.delete(key2)}let connectedUserIds=getConnectedUserIds();for(let targetUserId of connectedUserIds)if(targetUserId!==userId)broadcastToUserNoAck(targetUserId,"user-presence",{type:"user-presence",data:{userId,status}})}async function handleClientAck(clientId,messageId){if(!ackEnabled)return!0;let client=clients.get(clientId);if(!client)return!1;if(client.pendingAcks.get(messageId))client.pendingAcks.delete(messageId);if(client.userId)return acknowledgeMessage(redis,client.userId,messageId,ackTtl);return!0}async function deliverPendingMessages(userId,clientId){if(!ackEnabled)return 0;let client=clients.get(clientId);if(!client||client.userId!==userId)return 0;let pendingMessages=await getPendingMessagesForUser(redis,userId);if(pendingMessages.length===0)return 0;logger2.info("[PubSub] Delivering pending messages",{userId,count:pendingMessages.length});let deliveredCount=0;for(let message of pendingMessages){if(!client.subscribedTopics.has("*")&&!client.subscribedTopics.has(message.topic))continue;let payload=JSON.stringify({type:"event",messageId:message.messageId,topic:message.topic,data:message.data,timestamp:message.sentAt,isRedelivery:!0});try{client.ws.send(payload),client.pendingAcks.set(message.messageId,{sentAt:Date.now(),retryCount:message.retryCount+1}),await incrementRetryCount(redis,userId,message.messageId,ackTtl,maxRetries),deliveredCount++}catch{break}}return deliveredCount}function sweepStaleClients(){let payload=JSON.stringify({type:"ping",data:{at:Date.now()}}),dropped=0,checked=clients.size;for(let[clientId,client]of[...clients])try{client.ws.send(payload)}catch{clients.delete(clientId),dropped++}return{checked,dropped}}return{generateClientId,registerClient,unregisterClient,updateClientTopics,getClientsByUser,getConnectedUserIds,getClientCount,sweepStaleClients,sendToClient,broadcastEvent,broadcastToUser,broadcastToUserNoAck,broadcastPresenceToOthers,handleClientAck,deliverPendingMessages,getPendingMessageCount:(userId)=>getPendingMessageCount(redis,userId)}}var OPAQUE_RUN=/[A-Za-z0-9_-]{24,}(?:\.[A-Za-z0-9_-]{8,}){0,2}/g;function previewOf2(frame){return frame.slice(0,120).replace(OPAQUE_RUN,(match)=>`[redacted ${match.length} chars]`)}function classifyClientFrame(message){if(typeof message==="string"){if(message.trim()==="")return{kind:"ignore"};let parsed;try{parsed=JSON.parse(message)}catch{return{kind:"malformed",preview:previewOf2(message)}}return asMessage(parsed,message.length)}if(message&&typeof message==="object")return asMessage(message,approximateSize(message));return{kind:"ignore"}}function approximateSize(value2){if(ArrayBuffer.isView(value2))return value2.byteLength;if(value2 instanceof ArrayBuffer)return value2.byteLength;try{return JSON.stringify(value2)?.length??0}catch{return 0}}function asMessage(value2,size){if(!value2||typeof value2!=="object"||Array.isArray(value2))return{kind:"ignore"};let record3=value2,type4=record3.type;if(typeof type4!=="string"||type4==="")return{kind:"ignore"};return{kind:"message",parsed:record3,type:type4,size}}function tokensMatch(a,b){let ba=Buffer.from(a),bb=Buffer.from(b);if(ba.length!==bb.length)return!1;return timingSafeEqual3(ba,bb)}var HEARTBEAT_WS_TYPES=new Set(["ping","pong","ack"]);function buildHandshakeHeaders(rawHeaders,queryToken){let headers=new Headers;if(rawHeaders&&typeof rawHeaders==="object"){for(let[key2,value2]of Object.entries(rawHeaders))if(typeof value2==="string")headers.set(key2,value2)}if(queryToken)headers.set("authorization",`Bearer ${queryToken}`),headers.delete("cookie");return headers}function createPubSubRoutes(config){let{logger:logger2,getLiveMonitoringService}=config,clientManager=createClientManager(config);if(config.ack.enabled)initAckCleanup();let cleanupTimer=null,daprApiToken=config.daprApiToken||process.env.APP_API_TOKEN||process.env.DAPR_API_TOKEN,warnedNoDaprToken=!1;recordBootStep(()=>{if(config.cleanupIntervalMs>0)cleanupTimer=setInterval(()=>{let{checked,dropped}=clientManager.sweepStaleClients();if(dropped>0)logger2.info("[PubSub] Dropped clients whose socket no longer accepts a write",{checked,dropped,remaining:clientManager.getClientCount()})},config.cleanupIntervalMs);logger2.info("[PubSub] Routes initialized",{basePath:config.basePath,wsPath:config.wsPath,ackEnabled:config.ack.enabled,presenceEnabled:config.presence.enabled})}),recordShutdownStep(()=>{if(cleanupTimer)clearInterval(cleanupTimer),cleanupTimer=null});let routePostTopic=defineRoute({method:"POST",path:"/:topic",handler:async({params,body,request,set:set2})=>{let topic=params.topic;if(daprApiToken){let presented=request.headers.get("dapr-api-token")??"";if(!tokensMatch(presented,daprApiToken))return logger2.warn("[PubSub] Rejected Dapr event: missing/invalid dapr-api-token",{topic}),set2.status=403,{status:"DROP"}}else if(!warnedNoDaprToken)warnedNoDaprToken=!0,logger2.warn("[PubSub] Dapr subscription endpoint is UNAUTHENTICATED \u2014 set config.pubsub.daprApiToken or the APP_API_TOKEN/DAPR_API_TOKEN env so only the Dapr sidecar can inject events");let eventData;try{if(!body){let text2=await request.text();eventData=JSON.parse(text2)}else eventData=body}catch{return set2.status=200,{status:"DROP"}}logger2.info("[PubSub] Dapr event received",{topic,eventId:eventData.id,source:eventData.source});let userId=eventData.data?.user_id;if(userId)clientManager.broadcastToUser(userId,topic,eventData);else clientManager.broadcastEvent(topic,eventData);return getLiveMonitoringService?.()?.recordDaprEvent("pubsub_subscribe",{topic,success:!0,metadata:{eventId:eventData.id,source:eventData.source,userId:userId||null}}),set2.status=200,{status:"SUCCESS"}}}),routes=collectRoutes([{prefix:config.basePath,routes:[routePostTopic]}]),socketRoute=defineWebSocketRoute({path:config.wsPath,idleTimeout:config.wsIdleTimeout,async open(ws){let data=ws.data,query=data.query,userId;if(config.authenticate){let handshakeHeaders=buildHandshakeHeaders(data.headers,query.token),authResult=await config.authenticate(handshakeHeaders);if(!authResult||!("userId"in authResult)){let reason=authResult?.reason??"unspecified";ws.send(JSON.stringify({type:"error",error:"Unauthorized: a valid session is required for this connection",reason,timestamp:Date.now()})),ws.close(4001,`Unauthorized: ${reason}`),getLiveMonitoringService?.()?.recordWsEvent("error",{success:!1,error:`Unauthorized WebSocket handshake: ${reason}`,metadata:{reason}});return}userId=authResult.userId}else userId=query.userId;if(!userId){ws.send(JSON.stringify({type:"error",error:"userId is required for WebSocket connection",timestamp:Date.now()})),ws.close(4001,"userId is required"),getLiveMonitoringService?.()?.recordWsEvent("error",{success:!1,error:"userId is required"});return}let clientId=clientManager.generateClientId(),topics=query.topics?.split(",").map((t)=>t.trim())||["*"];ws.data.clientId=clientId,ws.data.openedAt=Date.now();let registration=clientManager.registerClient(clientId,ws,userId,topics);if(ws.send(JSON.stringify({type:"connected",clientId,subscribedTopics:registration.accepted,...registration.dropped.length>0?{droppedTopics:registration.dropped,topicCap:config.maxTopicsPerClient}:{},timestamp:Date.now()})),getLiveMonitoringService?.()?.recordWsEvent("connection",{clientId,userId,topic:topics.join(","),success:!0}),config.presence.enabled)clientManager.broadcastPresenceToOthers(userId,"online");let connectedUserId=userId;if(config.ack.enabled&&connectedUserId)clientManager.getPendingMessageCount(connectedUserId).then((count2)=>{if(count2>0)logger2.info("[PubSub] Delivering pending messages",{userId:connectedUserId,count:count2}),clientManager.deliverPendingMessages(connectedUserId,clientId).catch(()=>{})}).catch(()=>{})},message(ws,message){let clientId=ws.data.clientId,frame=classifyClientFrame(message);if(frame.kind==="ignore")return;if(frame.kind==="malformed"){logger2.debug("[PubSub] Failed to parse client message",{clientId,frame:frame.preview});return}let{parsed}=frame;try{if(!HEARTBEAT_WS_TYPES.has(frame.type))getLiveMonitoringService?.()?.recordWsEvent("message",{clientId,messageType:frame.type,success:!0,dataSize:frame.size});switch(frame.type){case"subscribe":if(Array.isArray(parsed.topics)){let update=clientManager.updateClientTopics(clientId,parsed.topics);clientManager.sendToClient(clientId,{type:"subscribed",topics:update.accepted,...update.dropped.length>0?{droppedTopics:update.dropped,topicCap:config.maxTopicsPerClient}:{},timestamp:Date.now()})}break;case"unsubscribe":logger2.info("[PubSub] Unsubscribe request",{clientId,topics:parsed.topics});break;case"ping":clientManager.sendToClient(clientId,{type:"pong",timestamp:Date.now()});break;case"ack":if(parsed.messageId)clientManager.handleClientAck(clientId,parsed.messageId).catch(()=>{});break}}catch(error3){logger2.error("[PubSub] Client message handler failed",error3,{clientId,messageType:frame.type})}},close(ws,code,reason){let state=ws.data,clientId=state.clientId,openedAt=state.openedAt,livedMs=typeof openedAt==="number"?Date.now()-openedAt:null;if(!clientId)return;clientManager.unregisterClient(clientId),getLiveMonitoringService?.()?.recordWsEvent("close",{clientId,success:!0,metadata:{code,reason:reason||"",livedMs,shortLived:livedMs!==null&&livedMs<60000}}),logger2.info("[PubSub] Client disconnected",{clientId,code,reason,livedMs})}});return{routes,socket:socketRoute,clientManager}}init_JWT();init_utils5();init_sessionBinding();function classifyRefusal(error3){if(!error3)return"unspecified";switch(error3){case"Token expired":return"expired";case"Invalid signature":return"invalid-signature";case"Invalid token format: expected 3 parts":case"Invalid token format: missing parts":case"Invalid header: failed to decode":case"Invalid header: typ must be JWT":case"Invalid payload: failed to decode":return"malformed";case"Invalid issuer":case"Invalid audience":return"wrong-audience";case"Token issued in the future":return"not-yet-valid";default:if(error3.startsWith("Unsupported algorithm:"))return"malformed";return"unspecified"}}function createHandshakeAuthenticator(deps){return async(headers)=>{let wsTokens=parseTokenValuesFromHeaders(headers,deps.tokenNames);if(!wsTokens.access_token)return deps.logger.debug("[PubSub] WS handshake rejected: no access token presented"),{reason:"no-token"};let jwtResult=verifyJWT(wsTokens.access_token,deps.accessTokenSecret,{issuer:deps.issuer,audience:deps.audience});if(!jwtResult.valid){let reason=classifyRefusal(jwtResult.error);return deps.logger.warn("[PubSub] WS handshake rejected: JWT verification failed",{reason:jwtResult.error,classified:reason}),{reason}}let sub=jwtResult.payload?.sub;if(typeof sub!=="string"||sub.length===0)return{reason:"token-without-subject"};if(!deps.requireLiveSession)return{userId:sub};let boundSessionId=typeof jwtResult.payload.sessionId==="string"?jwtResult.payload.sessionId:"";if(!boundSessionId)return deps.logger.warn("[PubSub] WS handshake rejected: token carries no session",{userId:sub}),{reason:"session-unbound"};let tenant=typeof jwtResult.payload.tenant==="string"?jwtResult.payload.tenant:void 0,sessionsTable=deps.resolveSessionsTable?.(tenant)??null,session;try{session=await refreshSessionIsAlive({sessionId:boundSessionId,db:deps.db,sessionsTable})}catch(error3){return deps.logger.warn("[PubSub] WS handshake rejected: session store unreadable",{userId:sub,sessionId:boundSessionId,error:error3 instanceof Error?error3.message:String(error3)}),{reason:"session-unreadable"}}if(!session.alive)return deps.logger.warn("[PubSub] WS handshake rejected: session is no longer active",{userId:sub,sessionId:boundSessionId,reason:session.reason}),{reason:"session-revoked"};if(session.userId&&session.userId!==sub)return deps.logger.warn("[PubSub] WS handshake rejected: token subject is not the session owner",{userId:sub,sessionId:boundSessionId,sessionOwner:session.userId}),{reason:"session-subject-mismatch"};return{userId:sub}}}init_esm3();init_Secrets();var timed=async(fn)=>{let started=performance.now();return{result:await fn(),durationMs:Math.round(performance.now()-started)}},postForm=async(url,body)=>{let response=await fetch(url,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded",Accept:"application/json"},body:new URLSearchParams(body).toString(),signal:AbortSignal.timeout(12000)}),text2=await response.text(),json={};try{json=JSON.parse(text2)}catch{json=Object.fromEntries(new URLSearchParams(text2))}return{status:response.status,json,text:text2}},describeError=(error3)=>{if(error3 instanceof Error){if(error3.name==="TimeoutError")return"Provider did not respond within 12s";return error3.message}return String(error3)},MICROSOFT_TOKEN_URL=(tenant)=>`https://login.microsoftonline.com/${tenant}/oauth2/v2.0/token`,DEFAULT_TOKEN_URLS={google:"https://oauth2.googleapis.com/token",github:"https://github.com/login/oauth/access_token"},describeRedirectUri=(value2)=>{let trimmed=value2?.trim();if(!trimmed)return"The reply URL (Redirect URI) is empty \u2014 sign-in cannot complete";if(/^[A-Z][A-Z0-9_]{2,}$/.test(trimmed))return`The reply URL looks like an environment variable name ("${trimmed}") and that variable is not set`;let parsed;try{parsed=new URL(trimmed)}catch{return`The reply URL is not a valid URL: "${trimmed}"`}if(parsed.protocol!=="https:"&&parsed.hostname!=="localhost")return`The reply URL must be https (it is "${parsed.protocol}//")`;return null};async function testOAuthProvider(provider,config){if(!config.clientId||!config.clientSecret)return{ok:!1,status:"failed",message:"Client ID and Client Secret must both be filled in",durationMs:0};let redirectProblem=describeRedirectUri(config.redirectUri);if(redirectProblem)return{ok:!1,status:"failed",message:redirectProblem,durationMs:0};try{if(provider==="microsoft"){let tenant=config.tenantId||"common";if(tenant==="common"||tenant==="organizations"||tenant==="consumers")return await probeWithBogusCode(MICROSOFT_TOKEN_URL(tenant),config);let{result,durationMs}=await timed(()=>postForm(MICROSOFT_TOKEN_URL(tenant),{grant_type:"client_credentials",client_id:config.clientId,client_secret:config.clientSecret,scope:"https://graph.microsoft.com/.default"}));if(result.status===200&&result.json.access_token)return{ok:!0,status:"ok",message:`Entra credentials verified (tenant ${tenant})`,durationMs};let code=String(result.json.error??result.status);return{ok:!1,status:"failed",message:String(result.json.error_description??result.text).slice(0,400),code,durationMs}}let tokenUrl=config.tokenUrl||DEFAULT_TOKEN_URLS[provider];if(!tokenUrl)return{ok:!1,status:"unknown",message:`No token URL is known for "${provider}" \u2014 enter a tokenUrl`,durationMs:0};return await probeWithBogusCode(tokenUrl,config)}catch(error3){return{ok:!1,status:"failed",message:describeError(error3),durationMs:0}}}async function probeWithBogusCode(tokenUrl,config){let{result,durationMs}=await timed(()=>postForm(tokenUrl,{grant_type:"authorization_code",code:"nucleus-credential-probe",client_id:config.clientId,client_secret:config.clientSecret,redirect_uri:config.redirectUri||"https://localhost/callback"})),code=String(result.json.error??""),description=String(result.json.error_description??result.text).slice(0,400);if(code==="invalid_grant"||code==="bad_verification_code")return{ok:!0,status:"ok",message:"The provider accepted these credentials",code,durationMs};if(code==="invalid_client"||code==="unauthorized_client"||result.status===401)return{ok:!1,status:"failed",message:description||"The provider refused this Client ID/Secret pair",code:code||"invalid_client",durationMs};if(description.includes("AADSTS7000215")||description.includes("AADSTS700016"))return{ok:!1,status:"failed",message:description,code,durationMs};return{ok:!1,status:"unknown",message:description||"The provider returned an unexpected answer",code:code||String(result.status),durationMs}}async function testStorageProvider(provider){if(!provider)return{ok:!1,status:"failed",message:"No storage provider is configured",durationMs:0};try{let{result,durationMs}=await timed(()=>provider.isAvailable());return result?{ok:!0,status:"ok",message:`Connection verified: ${provider.describe}`,durationMs}:{ok:!1,status:"failed",message:`Unreachable: ${provider.describe} \u2014 check the server, the share name and the credentials`,durationMs}}catch(error3){return{ok:!1,status:"failed",message:describeError(error3),durationMs:0}}}async function testEmailProvider(service,recipient){if(!service)return{ok:!1,status:"failed",message:"No email provider is configured",durationMs:0};if(!service.isAvailable())return{ok:!1,status:"failed",message:"The provider could not be started with these credentials",durationMs:0};if(!recipient)return{ok:!0,status:"ok",message:"The provider started \u2014 enter a recipient address to send for real",durationMs:0};try{let{result,durationMs}=await timed(()=>service.sendEmail({to:recipient,subject:"Nucleus connection test",html:"<p>This email was sent by a connection test run from the admin panel.</p>"}));return result.success?{ok:!0,status:"ok",message:`Test email sent to ${recipient}`,durationMs}:{ok:!1,status:"failed",message:result.error??"Sending failed",durationMs}}catch(error3){return{ok:!1,status:"failed",message:describeError(error3),durationMs:0}}}init_routeCollector();init_godminGuard();var SECRET_KINDS=["text","password","json","connection_string","pem","boolean","list"];var SCOPE_PATTERN=/^[A-Za-z0-9_.-]{1,200}$/,KEY_PATTERN=/^[A-Za-z0-9_-]{1,120}$/,fail5=(set2,status,message)=>{return set2.status=status,{isSuccess:!1,message,data:null}};function createSecretsRoutes(routeConfig){let{logger:logger2,basePath,db,schemaTables,getService,resolvedOptions,getEmailService,getStorageProvider:getStorageProvider2}=routeConfig,guard4=createGodminGuard({logger:logger2,db,schemaTables,area:"secrets management",allowedRoles:routeConfig.adminRoles}),requireService=(set2)=>{let service=getService();if(!service)return set2.status=503,null;return service},actorOf=(request)=>request.headers.get("x-user-id")??void 0,declared=[defineRoute({method:"GET",path:"/slots",handler:async({set:set2})=>{let service=requireService(set2);if(!service)return{isSuccess:!1,message:"Secrets management is disabled \u2014 set secrets.enabled and provide an encryption key",data:null};let slots=await service.describe(resolvedOptions),groups=[...new Set(slots.map((s)=>s.group))].sort();return{isSuccess:!0,message:"Credential slots",data:{slots,groups,counts:{total:slots.length,fromDb:slots.filter((s)=>s.source==="db").length,fromEnv:slots.filter((s)=>s.source==="env").length,missing:slots.filter((s)=>s.source==="missing").length,missingRequired:slots.filter((s)=>s.source==="missing"&&s.required).length}}}},detail:{tags:["Secrets"],summary:"List credential slots discovered from the running config",description:"Slots are derived by walking the config tree, so a new integration appears without a code change. Values are never returned \u2014 only a masked preview and the resolution source."}}),defineRoute({method:"GET",path:"/",handler:async({set:set2})=>{let service=requireService(set2);if(!service)return{isSuccess:!1,message:"Secrets management is disabled",data:null};return{isSuccess:!0,message:"Stored credentials",data:{secrets:await service.list()}}},detail:{tags:["Secrets"],summary:"List stored credentials",description:"Metadata only \u2014 the encrypted value is never returned."}}),defineRoute({method:"PUT",path:"/:scope/:key",handler:async({params,body,set:set2,request})=>{let service=requireService(set2);if(!service)return{isSuccess:!1,message:"Secrets management is disabled",data:null};let{scope,key:key2}=params;if(!SCOPE_PATTERN.test(scope))return fail5(set2,400,"Invalid scope");if(!KEY_PATTERN.test(key2))return fail5(set2,400,"Invalid key");let value2=body.value;if(typeof value2!=="string"||value2.length===0)return fail5(set2,400,"value is required");if(value2.length>65536)return fail5(set2,400,"value exceeds 64KB");if(body.kind==="json")try{JSON.parse(value2)}catch{return fail5(set2,400,"value is not valid JSON")}let saved=await service.set({scope,key:key2,value:value2,kind:body.kind,description:body.description,actorId:actorOf(request)});if(!saved)return fail5(set2,500,"Could not store the credential");return{isSuccess:!0,message:"Credential stored",data:{secret:saved}}},params:Type.Object({scope:Type.String(),key:Type.String()}),body:Type.Object({value:Type.String(),kind:Type.Optional(Type.Union(SECRET_KINDS.map((kind)=>Type.Literal(kind)))),description:Type.Optional(Type.String())}),detail:{tags:["Secrets"],summary:"Set or rotate a credential",description:"Stores the value encrypted (AES-256-GCM). Rotating retains the previous value for a one-step rollback. Takes effect within seconds on every pod \u2014 no restart."}}),defineRoute({method:"POST",path:"/:scope/:key/rollback",handler:async({params,set:set2,request})=>{let service=requireService(set2);if(!service)return{isSuccess:!1,message:"Secrets management is disabled",data:null};let restored=await service.rollback(params.scope,params.key,actorOf(request));if(!restored)return fail5(set2,404,"No previous value to roll back to");return{isSuccess:!0,message:"Previous credential restored",data:{secret:restored}}},params:Type.Object({scope:Type.String(),key:Type.String()}),detail:{tags:["Secrets"],summary:"Roll back to the value replaced by the last rotation"}}),defineRoute({method:"DELETE",path:"/:scope/:key",handler:async({params,set:set2,request})=>{let service=requireService(set2);if(!service)return{isSuccess:!1,message:"Secrets management is disabled",data:null};if(!await service.remove(params.scope,params.key,actorOf(request)))return fail5(set2,404,"Credential not found");return{isSuccess:!0,message:"Credential removed \u2014 the config falls back to env or literal",data:null}},params:Type.Object({scope:Type.String(),key:Type.String()}),detail:{tags:["Secrets"],summary:"Delete a stored credential",description:"Resolution falls back to the env var or literal declared in config."}}),defineRoute({method:"POST",path:"/test",handler:async({body,set:set2})=>{let service=requireService(set2);if(!service)return{isSuccess:!1,message:"Secrets management is disabled",data:null};let{target:target2}=body,result;if(target2==="email")result=await testEmailProvider(getEmailService?.()??null,body.recipient);else if(target2==="oauth"){let provider=body.provider;if(!provider)return fail5(set2,400,"provider is required for an oauth test");let scope=`authentication.oauth.providers.${provider}`,providerConfig=(resolvedOptions.authentication?.oauth?.providers??{})[provider]??{},declaredString=(key2)=>{let raw=providerConfig[key2];return typeof raw==="string"?raw:void 0};result=await testOAuthProvider(provider,{clientId:service.value(scope,"clientId",declaredString("clientId")),clientSecret:service.value(scope,"clientSecret",declaredString("clientSecret")),tenantId:service.value(scope,"tenantId",declaredString("tenantId")),tokenUrl:declaredString("tokenUrl"),redirectUri:service.value(scope,"redirectUri",declaredString("redirectUri"))})}else if(target2==="storage")result=await testStorageProvider(getStorageProvider2?.()??null);else return fail5(set2,400,`Unsupported test target "${target2}"`);return logger2.info("[Secrets] Connection test",{target:target2,provider:body.provider,status:result.status,durationMs:result.durationMs}),{isSuccess:result.ok,message:result.message,data:{result}}},body:Type.Object({target:Type.Union([Type.Literal("oauth"),Type.Literal("email"),Type.Literal("storage")]),provider:Type.Optional(Type.String()),recipient:Type.Optional(Type.String())}),detail:{tags:["Secrets"],summary:"Test a credential against the real provider",description:"Backend-to-backend only. OAuth uses client_credentials where the provider supports it and a client-authentication probe otherwise; email optionally sends a real test message."}}),defineRoute({method:"POST",path:"/reencrypt",handler:async({body,set:set2})=>{let service=requireService(set2);if(!service)return{isSuccess:!1,message:"Secrets management is disabled",data:null};if(!body.newMasterKey||body.newMasterKey.length<16)return fail5(set2,400,"newMasterKey must be at least 16 characters");let result=await service.reencryptAll(body.newMasterKey),uncovered="Credentials stored outside this store \u2014 an integration connection\u2019s token, client secret and service password \u2014 are NOT re-encrypted by this. Re-enter them from the integrations panel after the new key is deployed.";return{isSuccess:result.failed.length===0,message:result.failed.length===0?`Re-encrypted ${result.migrated} credentials \u2014 deploy the new key before the next restart. ${uncovered}`:`Nothing was re-encrypted: ${result.failed.length} credential(s) could not be read with the current key, so none were rewritten.`,data:{...result,uncovered}}},body:Type.Object({newMasterKey:Type.String()}),detail:{tags:["Secrets"],summary:"Re-encrypt every credential under a new master key",description:"Rotates the key protecting the store. The new key must be deployed to the environment before the next restart, or nothing will decrypt."}}),defineRoute({method:"GET",path:"/discover",handler:()=>({isSuccess:!0,message:"Slots discovered from the running config",data:{slots:discoverSlots(resolvedOptions).map(({configValue:_configValue,...slot})=>slot)}}),detail:{tags:["Secrets"],summary:"Show which credential slots the config declares",description:"Diagnostics \u2014 resolution state is not consulted and no value is read."}}),defineRoute({method:"POST",path:"/storage/migrate",handler:async(ctx)=>{let body=ctx.body,build=routeConfig.buildStorageProvider;if(!build)return fail5(ctx.set,503,"Storage is not configured");let destination=build(body.to),source=build(body.to==="smb"?"local":"smb");if(!source||!destination)return fail5(ctx.set,503,"Storage is not configured");if(!await destination.isAvailable())return fail5(ctx.set,422,`The destination store is unreachable (${destination.describe}) \u2014 test the connection first`);if(body.dryRun){let preview=await migrateStorage({from:source,to:Object.assign(Object.create(Object.getPrototypeOf(destination)),destination,{exists:async()=>!1,write:async()=>{},ensureDir:async()=>{}}),maxFiles:1e5},{logger:logger2});return{isSuccess:!0,message:`${preview.copied} file${preview.copied===1?"":"s"} would be copied`,data:{preview}}}let report=await migrateStorage({from:source,to:destination},{logger:logger2}),clean=report.failed===0&&!report.truncated;return logger2.warn("[Storage] Migration run from the admin panel",{to:body.to,copied:report.copied,skipped:report.skipped,failed:report.failed,truncated:report.truncated}),{isSuccess:clean,message:clean?`${report.copied} file${report.copied===1?"":"s"} copied, ${report.skipped} already present`:`${report.copied} copied, ${report.failed} failed${report.truncated?", list truncated":""}`,data:{report}}},body:Type.Object({to:Type.Union([Type.Literal("local"),Type.Literal("smb")]),dryRun:Type.Optional(Type.Boolean())}),detail:{tags:["Secrets"],summary:"Copy every stored file to the other backend",description:"Copy-only: the source is never deleted, and a file already on the target is skipped so a re-run resumes. Run BEFORE switching storage.provider."}})];return collectRoutes([{prefix:basePath,guards:[guard4],routes:declared}])}function readAttemptedIdentity(parsedBody,rawBodyText,contentType){let fromJson=parsedBody.email;if(typeof fromJson==="string"&&fromJson.trim())return fromJson;if(!rawBodyText)return;if((contentType||"").toLowerCase().includes("application/x-www-form-urlencoded"))try{let value2=new URLSearchParams(rawBodyText).get("email");return value2?.trim()?value2:void 0}catch{return}return}var snakeToCamel4=(s)=>s.replace(/_([a-z])/g,(_,l)=>l.toUpperCase());function warnOnEntityBasePathCollision(feature,basePath,entities,logger2){let segment=basePath.replace(/^\/+|\/+$/g,"");if(!segment||segment.includes("/"))return!1;let clash=(entities??[]).find((entity)=>snakeToCamel4(entity.table_name)===segment);if(!clash)return!1;return logger2.error(`[${feature}] basePath "${basePath}" is also the generated entity route for "${clash.table_name}". Both are registered and one will shadow the other. Move the feature (e.g. "/nucleus${basePath}") or rename the entity.`),!0}function requiredBodyCeiling(input){if(!input.storageEnabled)return null;let perFile=input.maxFileSizeBytes;if(!perFile||!Number.isFinite(perFile)||perFile<=0)return null;let files=Math.max(1,Math.floor(input.maxFiles??1));return perFile*files+8388608}function bodyCeilingComplaint(input){if(input.required===null)return null;if((input.configured??134217728)>=input.required)return null;let mb2=(bytes)=>`${Math.round(bytes/1048576)} MB`,source=input.configured==null?`the host passed no \`serve.maxRequestBodySize\`, so Bun's default of ${mb2(134217728)} applies`:`the host passed ${mb2(input.configured)}`;return`Uploads are configured up to ${mb2(input.required)}, but ${source}. `+"Anything larger is refused by the server before this application sees it \u2014 "+`the upload appears to run and then fails with nothing to show. Fix: raise \`storage.maxFileSizeBytes\` / \`security.maxRequestBodyBytes\` in the config, or pass \`maxRequestBodyBytes\` to \`createNucleusServer\`. The server needs ${input.required} bytes of headroom.`}function bunBodyCeilingFor(input){let wanted=Math.max(input.storageRequired??0,input.guardCeiling??0);return wanted>134217728?wanted:void 0}var APP_BASELINE_BYTES=268435456,COPIES_IN_FLIGHT=2;function memoryCeilingComplaint(input){if(input.required===null||input.cgroupLimit===null||input.cgroupLimit<=0)return null;let needed=input.required*COPIES_IN_FLIGHT+APP_BASELINE_BYTES;if(input.cgroupLimit>=needed)return null;let mb2=(bytes)=>`${Math.round(bytes/1048576)} MB`,affordable=Math.max(0,Math.floor((input.cgroupLimit-APP_BASELINE_BYTES)/COPIES_IN_FLIGHT));return`Uploads are configured up to ${mb2(input.required)}, but this container is capped at ${mb2(input.cgroupLimit)} and an upload is held in memory twice on top of the ~${mb2(APP_BASELINE_BYTES)} `+`the app already holds \u2014 it needs ~${mb2(needed)}. The kernel will kill the process mid-upload. Fix: raise the container's memory limit to at least ${mb2(needed)}, or lower storage.maxFileSizeBytes to about ${mb2(affordable)}.`}var ipMatches=(ip,entry)=>entry.includes("/")?isIpInCidr(ip,entry):ip===entry;function resolveClientIp(socketIp,forwardedFor,trustedProxies=[]){let peer=socketIp?.trim()||"",chain=(forwardedFor||"").split(",").map((s)=>s.trim()).filter(Boolean);if(!peer)return chain[0]||"unknown";if(trustedProxies.length===0)return peer;let trusted=(ip)=>trustedProxies.some((p)=>ipMatches(ip,p));if(!trusted(peer))return peer;for(let i=chain.length-1;i>=0;i--){let hop=chain[i];if(hop&&!trusted(hop))return hop}return chain[0]||peer}var isNonDiscriminatingPeer=(ip)=>ip==="::1"||ip==="unknown"||/^(::ffff:)?127\./.test(ip);function createPeerDiversityWatcher(sampleSize=200){let seen=new Set,seenCount=0,settled=!1;return{observe(ip){if(settled)return null;if(seenCount++,seen.add(ip),seen.size>1)return settled=!0,null;if(seenCount>=sampleSize)return settled=!0,ip;return null}}}var DEFAULTS={cookieName:"csrf_token",headerName:"x-csrf-token"},resolveCsrfConfig=(raw)=>{if(raw===!0)return{enabled:!0,...DEFAULTS};if(!raw)return{enabled:!1,...DEFAULTS};return{enabled:raw.enabled!==!1,cookieName:raw.cookieName||DEFAULTS.cookieName,headerName:(raw.headerName||DEFAULTS.headerName).toLowerCase()}},parseCookies=(cookieHeader)=>{let out={};if(!cookieHeader)return out;for(let part of cookieHeader.split(";")){let trimmed=part.trim(),eq37=trimmed.indexOf("=");if(eq37>0)out[trimmed.slice(0,eq37)]=trimmed.slice(eq37+1)}return out},SAFE_METHODS=new Set(["GET","HEAD","OPTIONS"]),isStateChanging=(method)=>!SAFE_METHODS.has(method.toUpperCase()),requiresCsrf=(params)=>{if(!isStateChanging(params.method))return!1;if(params.hasAuthorizationHeader)return!1;return params.authCookieNames.some((n)=>!!params.cookies[n])},csrfTokenMatches=(csrfCookie,csrfHeader)=>{if(!csrfCookie||!csrfHeader)return!1;return csrfCookie===csrfHeader};var SENSITIVE_HEADER_NAMES=new Set(["authorization","proxy-authorization","cookie","set-cookie","x-access-token","x-refresh-token","x-session-id","x-user-id","x-user-roles","x-user-claims","x-user-email","x-api-key","x-api-key-id","x-api-key-owner-type","x-auth-type"]);function redactSensitiveHeaders(headers){let out={};return headers.forEach((value2,key2)=>{out[key2]=SENSITIVE_HEADER_NAMES.has(key2.toLowerCase())?"[REDACTED]":value2}),out}init_sensitiveColumns();var TENANT_CLAIM="tenant",verifyTenantBinding=(tokenTenant,resolvedSchema,mode="lenient")=>{if(mode==="off")return{ok:!0};if(!resolvedSchema)return{ok:!0};let claim=typeof tokenTenant==="string"&&tokenTenant.length>0?tokenTenant:void 0;if(!claim)return mode==="strict"?{ok:!1,reason:"token is not bound to a tenant"}:{ok:!0};return claim===resolvedSchema?{ok:!0}:{ok:!1,reason:"token tenant does not match the requested tenant"}};init_storage2();init_routeMount();init_esm3();init_routeCollector();import{eq as eq37}from"drizzle-orm";var RESERVED_SUBDOMAINS=["www","api","app","admin","dashboard","mail","ftp","cdn","static","assets","docs","help","support","status","blog","dev","staging","test","demo","localhost"],DEFAULT_SELF_SIGNUP_LIMITS={maxSignupsPerIpPerWindow:3,ipWindowMs:3600000,maxTenantsPerEmail:1};var col9=(table,name2)=>table[name2];function createCheckSubdomainRoute(config){let declared=[defineRoute({method:"GET",path:"/tenants/check-subdomain/:subdomain",handler:async(ctx)=>{let{subdomain}=ctx.params,normalized=subdomain.toLowerCase().trim();if(!/^[a-z][a-z0-9-]*$/.test(normalized)||normalized.length<2||normalized.length>63)return ctx.set.status=400,{isSuccess:!1,message:"Invalid subdomain format. Must start with a letter, contain only lowercase letters, numbers, and hyphens, and be 2-63 characters.",data:{available:!1,subdomain:normalized}};if(RESERVED_SUBDOMAINS.includes(normalized))return{isSuccess:!0,message:"Subdomain is reserved",data:{available:!1,subdomain:normalized}};let tenantRegistry=config.getTenantRegistry(),db=config.getDb();if(tenantRegistry){if(tenantRegistry.getActiveTenants().find((t)=>t.subdomain===normalized))return{isSuccess:!0,message:"Subdomain is already taken",data:{available:!1,subdomain:normalized}}}if(db&&tenantRegistry){let tenantsTable=tenantRegistry.getMainContext().schemaTables.tenants;if(tenantsTable)try{if((await db.select().from(tenantsTable).where(eq37(col9(tenantsTable,"subdomain"),normalized)).limit(1)).length>0)return{isSuccess:!0,message:"Subdomain is already taken",data:{available:!1,subdomain:normalized}}}catch{}}return{isSuccess:!0,message:"Subdomain is available",data:{available:!0,subdomain:normalized}}},params:Type.Object({subdomain:Type.String({minLength:2,maxLength:63})}),detail:{tags:["Tenants"],summary:"Check subdomain availability",description:"Public endpoint to check if a subdomain is available for registration. No authentication required."}})];return collectRoutes([{routes:declared}])}init_esm3();init_Logger2();init_routeCollector();import{eq as eq38}from"drizzle-orm";var col10=(table,name2)=>table[name2];function createManageRoutes(config){let log4=config.logger.scoped(TENANT_SUSPEND),declared=[defineRoute({method:"GET",path:"/tenants",handler:async(ctx)=>{let db=config.getDb(),tenantRegistry=config.getTenantRegistry();if(!db||!tenantRegistry)return ctx.set.status=503,{success:!1,message:"Tenant management not available",data:null};let requestingUserId=ctx.request.headers.get("x-user-id");if(!requestingUserId)return ctx.set.status=401,{success:!1,message:"Authentication required",data:null};if(!await verifyGodmin(db,tenantRegistry,requestingUserId))return ctx.set.status=403,{success:!1,message:"Godmin privileges required",data:null};let allTenants=[...tenantRegistry.getActiveTenants(),...Array.from(getAllTenants(tenantRegistry)).filter((t)=>t.status!=="active")];return{success:!0,message:`Found ${allTenants.length} tenants`,data:{items:allTenants.map((t)=>({id:t.id,subdomain:t.subdomain,schemaName:t.schemaName,companyId:t.companyId,companyName:t.companyName,godAdminEmail:t.godAdminEmail,status:t.status,plan:t.plan,domain:t.domain,maxUsers:t.maxUsers,provisionedAt:t.provisionedAt,suspendedAt:t.suspendedAt})),totalCount:allTenants.length}}},detail:{tags:["Tenants"],summary:"List tenants",description:"List all tenants in the platform. Requires godmin privileges."}}),defineRoute({method:"GET",path:"/tenants/:id",handler:async(ctx)=>{let db=config.getDb(),tenantRegistry=config.getTenantRegistry();if(!db||!tenantRegistry)return ctx.set.status=503,{success:!1,message:"Tenant management not available",data:null};let requestingUserId=ctx.request.headers.get("x-user-id");if(!requestingUserId)return ctx.set.status=401,{success:!1,message:"Authentication required",data:null};if(!await verifyGodmin(db,tenantRegistry,requestingUserId))return ctx.set.status=403,{success:!1,message:"Godmin privileges required",data:null};let{id}=ctx.params,tenant=tenantRegistry.getTenantById(id);if(!tenant)return ctx.set.status=404,{success:!1,message:"Tenant not found",data:null};let features=tenantRegistry.getTenantFeatures(id);return{success:!0,message:"Tenant found",data:{...tenant,features:features.map((f)=>({featureName:f.featureName,enabled:f.enabled}))}}},detail:{tags:["Tenants"],summary:"Get tenant details",description:"Get detailed information about a specific tenant. Requires godmin privileges."}}),defineRoute({method:"POST",path:"/tenants/:id/suspend",handler:async(ctx)=>{let db=config.getDb(),tenantRegistry=config.getTenantRegistry();if(!db||!tenantRegistry)return ctx.set.status=503,{success:!1,message:"Tenant management not available"};let requestingUserId=ctx.request.headers.get("x-user-id");if(!requestingUserId)return ctx.set.status=401,{success:!1,message:"Authentication required"};if(!await verifyGodmin(db,tenantRegistry,requestingUserId))return ctx.set.status=403,{success:!1,message:"Godmin privileges required"};let{id}=ctx.params,body=ctx.body,tenant=tenantRegistry.getTenantById(id);if(!tenant)return ctx.set.status=404,{success:!1,message:"Tenant not found"};if(tenant.status==="suspended")return ctx.set.status=400,{success:!1,message:"Tenant is already suspended"};let mainContext=tenantRegistry.getMainContext(),tenantsTable=mainContext.schemaTables.tenants;if(!tenantsTable)return ctx.set.status=503,{success:!1,message:"Tenants table not available"};let now=new Date;await db.update(tenantsTable).set({status:"suspended",suspendedAt:now,suspendedReason:body.reason||"Suspended by godmin",updatedAt:now}).where(eq38(col10(tenantsTable,"id"),id));let tenantEventsTable=mainContext.schemaTables.tenantEvents;if(tenantEventsTable)try{await db.insert(tenantEventsTable).values({id:crypto.randomUUID(),tenantId:id,eventType:"suspended",eventData:JSON.stringify({reason:body.reason||"Suspended by godmin"}),performedBy:requestingUserId,ipAddress:ctx.request.headers.get("x-client-ip")?.trim()||ctx.request.headers.get("x-forwarded-for")?.split(",")[0]?.trim()||"unknown",createdAt:now,updatedAt:now})}catch{log4.warn("Failed to record suspend event")}return await tenantRegistry.refreshTenant(id),log4.info("Tenant suspended",{tenantId:id,reason:body.reason}),{success:!0,message:`Tenant "${tenant.subdomain}" suspended`}},body:Type.Object({reason:Type.Optional(Type.String())}),detail:{tags:["Tenants"],summary:"Suspend tenant",description:"Suspend a tenant, preventing all access. Requires godmin privileges."}}),defineRoute({method:"POST",path:"/tenants/:id/reactivate",handler:async(ctx)=>{let db=config.getDb(),tenantRegistry=config.getTenantRegistry();if(!db||!tenantRegistry)return ctx.set.status=503,{success:!1,message:"Tenant management not available"};let requestingUserId=ctx.request.headers.get("x-user-id");if(!requestingUserId)return ctx.set.status=401,{success:!1,message:"Authentication required"};if(!await verifyGodmin(db,tenantRegistry,requestingUserId))return ctx.set.status=403,{success:!1,message:"Godmin privileges required"};let{id}=ctx.params,tenant=tenantRegistry.getTenantById(id);if(!tenant)return ctx.set.status=404,{success:!1,message:"Tenant not found"};if(tenant.status!=="suspended")return ctx.set.status=400,{success:!1,message:`Tenant is not suspended (current: ${tenant.status})`};let mainContext=tenantRegistry.getMainContext(),tenantsTable=mainContext.schemaTables.tenants;if(!tenantsTable)return ctx.set.status=503,{success:!1,message:"Tenants table not available"};let now=new Date;await db.update(tenantsTable).set({status:"active",suspendedAt:null,suspendedReason:null,updatedAt:now}).where(eq38(col10(tenantsTable,"id"),id));let tenantEventsTable=mainContext.schemaTables.tenantEvents;if(tenantEventsTable)try{await db.insert(tenantEventsTable).values({id:crypto.randomUUID(),tenantId:id,eventType:"reactivated",eventData:JSON.stringify({previousReason:tenant.suspendedReason}),performedBy:requestingUserId,ipAddress:ctx.request.headers.get("x-client-ip")?.trim()||ctx.request.headers.get("x-forwarded-for")?.split(",")[0]?.trim()||"unknown",createdAt:now,updatedAt:now})}catch{log4.warn("Failed to record reactivate event")}return await tenantRegistry.refreshTenant(id),log4.info("Tenant reactivated",{tenantId:id}),{success:!0,message:`Tenant "${tenant.subdomain}" reactivated`}},detail:{tags:["Tenants"],summary:"Reactivate tenant",description:"Reactivate a suspended tenant. Requires godmin privileges."}})];return collectRoutes([{routes:declared}])}var verifyGodmin=async(db,tenantRegistry,userId)=>{let usersTable=tenantRegistry.getMainContext().schemaTables.users;if(!usersTable)return!1;return(await db.select().from(usersTable).where(eq38(col10(usersTable,"id"),userId)).limit(1))[0]?.isGod===!0},getAllTenants=(registry3)=>{let schemaNames=registry3.getAllSchemaNames(),tenants=[];for(let name2 of schemaNames){let ctx=registry3.getSchemaContext(name2);if(ctx?.tenant)tenants.push(ctx.tenant)}return tenants};init_esm3();init_Logger2();init_routeCollector();init_utils6();import{eq as eq39}from"drizzle-orm";var col11=(table,name2)=>table[name2],ProvisionBodySchema=Type.Object({subdomain:Type.String({minLength:2,maxLength:63,pattern:"^[a-z][a-z0-9-]*$"}),companyId:Type.String({format:"uuid"}),companyName:Type.Optional(Type.String()),godAdminEmail:Type.String({format:"email"}),plan:Type.Optional(Type.String()),domain:Type.Optional(Type.String())});function createProvisionRoute(config){let log4=config.logger.scoped(TENANT_PROVISION),declared=[defineRoute({method:"POST",path:"/tenants/provision",handler:async(ctx)=>{let db=config.getDb(),tenantRegistry=config.getTenantRegistry();if(!db||!tenantRegistry)return ctx.set.status=503,{success:!1,message:"Tenant provisioning not available"};let requestingUserId=ctx.request.headers.get("x-user-id");if(!requestingUserId)return ctx.set.status=401,{success:!1,message:"Authentication required"};let mainContext=tenantRegistry.getMainContext(),usersTable=mainContext.schemaTables.users;if(!usersTable)return ctx.set.status=503,{success:!1,message:"Users table not available"};if(!(await db.select().from(usersTable).where(eq39(col11(usersTable,"id"),requestingUserId)).limit(1))[0]?.isGod)return ctx.set.status=403,{success:!1,message:"Godmin privileges required"};let body=ctx.body,tenantSchemaName=`tenant_${body.subdomain.replace(/-/g,"_")}`;if(tenantRegistry.getActiveTenants().find((t)=>t.subdomain===body.subdomain))return ctx.set.status=409,{success:!1,message:`Subdomain "${body.subdomain}" is already taken`};let tenantsTable=mainContext.schemaTables.tenants;if(!tenantsTable)return ctx.set.status=503,{success:!1,message:"Tenants table not available in main schema"};let tenantId=crypto.randomUUID(),now=new Date;try{await db.insert(tenantsTable).values({id:tenantId,subdomain:body.subdomain,schemaName:tenantSchemaName,companyId:body.companyId,companyName:body.companyName||null,godAdminEmail:body.godAdminEmail,status:"provisioning",plan:body.plan||null,domain:body.domain||null,createdAt:now,updatedAt:now}),log4.info("Tenant record created",{tenantId,subdomain:body.subdomain,schemaName:tenantSchemaName})}catch(err){let msg=err instanceof Error?err.message:String(err);return log4.error("Failed to insert tenant record",{error:msg}),ctx.set.status=500,{success:!1,message:`Failed to create tenant record: ${msg}`}}try{let tenantRecord={id:tenantId,subdomain:body.subdomain,schemaName:tenantSchemaName,companyId:body.companyId,companyName:body.companyName||null,godAdminEmail:body.godAdminEmail,status:"provisioning",plan:body.plan||null,domain:body.domain||null,settings:{},trustedSources:[],maxUsers:null,provisionedAt:null,suspendedAt:null,suspendedReason:null},tenantContext=await tenantRegistry.provisionTenant(tenantRecord);log4.info("Schema provisioned",{tenantId,schemaName:tenantSchemaName,tableCount:Object.keys(tenantContext.schemaTables).length});let tenantUsersTable=tenantContext.schemaTables.users;if(tenantUsersTable){let godminId=crypto.randomUUID(),tempPassword=crypto.randomUUID().slice(0,16),hashedPassword=await hashPassword(tempPassword);await db.insert(tenantUsersTable).values({id:godminId,email:body.godAdminEmail,password:hashedPassword,isGod:!0,isActive:!0,createdAt:now,updatedAt:now}),log4.info("Godmin user seeded",{tenantId,godminEmail:body.godAdminEmail,godminId})}await db.update(tenantsTable).set({status:"active",provisionedAt:now,updatedAt:now}).where(eq39(col11(tenantsTable,"id"),tenantId));let tenantEventsTable=mainContext.schemaTables.tenantEvents;if(tenantEventsTable)try{await db.insert(tenantEventsTable).values({id:crypto.randomUUID(),tenantId,eventType:"provisioned",eventData:JSON.stringify({schemaName:tenantSchemaName,godAdminEmail:body.godAdminEmail,plan:body.plan||null}),performedBy:requestingUserId,ipAddress:ctx.request.headers.get("x-forwarded-for")?.split(",")[0]?.trim()||"unknown",createdAt:now,updatedAt:now})}catch{log4.warn("Failed to record tenant_event, continuing")}return await tenantRegistry.refreshTenant(tenantId),log4.info("Provisioning complete",{tenantId,subdomain:body.subdomain,schemaName:tenantSchemaName}),await config.logger.audit({entityName:"tenants",entityId:tenantId,operation:"PROVISION",userId:requestingUserId,summary:`Tenant "${body.subdomain}" provisioned by godmin`,ipAddress:ctx.request.headers.get("x-forwarded-for")?.split(",")[0]?.trim()||"unknown",userAgent:ctx.request.headers.get("user-agent")||"unknown",path:new URL(ctx.request.url).pathname,query:""}),{success:!0,message:"Tenant provisioned successfully",data:{tenantId,schemaName:tenantSchemaName,subdomain:body.subdomain,godAdminEmail:body.godAdminEmail,status:"active"}}}catch(err){let msg=err instanceof Error?err.message:String(err);log4.error("Provisioning failed",{error:msg,tenantId});try{await db.update(tenantsTable).set({status:"provisioning",updatedAt:now}).where(eq39(col11(tenantsTable,"id"),tenantId))}catch{}return ctx.set.status=500,{success:!1,message:`Provisioning failed: ${msg}`}}},body:ProvisionBodySchema,detail:{tags:["Tenants"],summary:"Provision new tenant",description:"Create a new tenant with its own schema, tables, and godmin user. Requires godmin privileges."}})];return collectRoutes([{routes:declared}])}init_esm3();init_Authorization();init_Logger2();init_routeCollector();init_passwordPolicy();init_utils6();import{eq as eq40}from"drizzle-orm";var col12=(table,name2)=>table[name2],ipRateMap=new Map,lastCleanup=Date.now();function cleanupRateMap(windowMs){let now=Date.now();if(now-lastCleanup<600000)return;lastCleanup=now;for(let[key2,entry]of ipRateMap.entries())if(now-entry.windowStart>windowMs)ipRateMap.delete(key2)}function isIpRateLimited(ip,maxPerWindow,windowMs){let now=Date.now(),entry=ipRateMap.get(ip);if(!entry||now-entry.windowStart>windowMs)return ipRateMap.set(ip,{count:1,windowStart:now}),!1;return entry.count++,entry.count>maxPerWindow}var SelfSignupBodySchema=Type.Object({email:Type.String({format:"email"}),password:Type.String({minLength:8}),subdomain:Type.String({minLength:2,maxLength:63,pattern:"^[a-z][a-z0-9-]*$"}),plan:Type.Optional(Type.String()),companyName:Type.Optional(Type.String())});function createSelfSignupRoute(config){let log4=config.logger.scoped(TENANT_SELF_SIGNUP),limits={...DEFAULT_SELF_SIGNUP_LIMITS,...config.selfSignupLimits},declared=[defineRoute({method:"POST",path:"/tenants/self-signup",handler:async(ctx)=>{cleanupRateMap(limits.ipWindowMs);let db=config.getDb(),tenantRegistry=config.getTenantRegistry();if(!db||!tenantRegistry)return ctx.set.status=503,{success:!1,message:"Tenant provisioning not available"};let ip=ctx.request.headers.get("x-forwarded-for")?.split(",")[0]?.trim()||ctx.request.headers.get("x-real-ip")?.trim()||"unknown";if(isIpRateLimited(ip,limits.maxSignupsPerIpPerWindow,limits.ipWindowMs))return log4.warn("Self-signup rate limited",{ip}),ctx.set.status=429,{success:!1,message:"Too many signup attempts. Please try again later."};let body=ctx.body,email=body.email.toLowerCase().trim(),subdomain=body.subdomain.toLowerCase().trim();if(RESERVED_SUBDOMAINS.includes(subdomain))return ctx.set.status=400,{success:!1,message:"This subdomain is reserved."};let mainContext=tenantRegistry.getMainContext(),usersTable=mainContext.schemaTables.users,tenantsTable=mainContext.schemaTables.tenants;if(!usersTable||!tenantsTable)return ctx.set.status=503,{success:!1,message:"Required tables not available."};if(tenantRegistry.getActiveTenants().find((t)=>t.subdomain===subdomain))return ctx.set.status=409,{success:!1,message:"This subdomain is already taken."};try{if((await db.select().from(tenantsTable).where(eq40(col12(tenantsTable,"subdomain"),subdomain)).limit(1)).length>0)return ctx.set.status=409,{success:!1,message:"This subdomain is already taken."}}catch{}try{if((await db.select().from(tenantsTable).where(eq40(col12(tenantsTable,"godAdminEmail"),email))).length>=limits.maxTenantsPerEmail)return ctx.set.status=409,{success:!1,message:`Tenant limit reached. Maximum ${limits.maxTenantsPerEmail} tenant(s) per account.`}}catch{}let strength=validateAgainstPolicy(body.password,void 0,body.email);if(!strength.valid)return ctx.set.status=400,{success:!1,message:"Password too weak",errors:strength.errors};let userId,hashedPassword=await hashPassword(body.password),now=new Date,existingUsers=await db.select().from(usersTable).where(eq40(col12(usersTable,"email"),email)).limit(1);if(existingUsers.length>0)userId=existingUsers[0].id,log4.info("Self-signup: existing user resolved",{userId,email});else{userId=crypto.randomUUID();try{await db.insert(usersTable).values({id:userId,email,password:hashedPassword,isActive:!0,createdAt:now,updatedAt:now}),log4.info("Self-signup: user created",{userId,email})}catch(err){let msg=err instanceof Error?err.message:String(err);return log4.error("Self-signup: user creation failed",{error:msg}),ctx.set.status=500,{success:!1,message:"Failed to create account."}}}let tenantId=crypto.randomUUID(),tenantSchemaName=`tenant_${subdomain.replace(/-/g,"_")}`;try{await db.insert(tenantsTable).values({id:tenantId,subdomain,schemaName:tenantSchemaName,companyId:userId,companyName:body.companyName||subdomain,godAdminEmail:email,status:"provisioning",plan:body.plan||null,createdAt:now,updatedAt:now}),log4.info("Tenant record created",{tenantId,subdomain})}catch(err){let msg=err instanceof Error?err.message:String(err);return log4.error("Tenant record creation failed",{error:msg}),ctx.set.status=500,{success:!1,message:"Failed to create tenant."}}try{let tenantRecord={id:tenantId,subdomain,schemaName:tenantSchemaName,companyId:userId,companyName:body.companyName||subdomain,godAdminEmail:email,status:"provisioning",plan:body.plan||null,domain:null,settings:{},trustedSources:[],maxUsers:null,provisionedAt:null,suspendedAt:null,suspendedReason:null},tenantContext=await tenantRegistry.provisionTenant(tenantRecord);log4.info("Schema provisioned",{tenantId,schemaName:tenantSchemaName,tableCount:Object.keys(tenantContext.schemaTables).length});let tenantUsersTable=tenantContext.schemaTables.users;if(tenantUsersTable)await db.insert(tenantUsersTable).values({id:userId,email,password:hashedPassword,isGod:!0,isActive:!0,createdAt:now,updatedAt:now}),log4.info("Godmin seeded in tenant schema",{tenantId,email,userId});let tenantRolesTable=tenantContext.schemaTables.roles,tenantUserRolesTable=tenantContext.schemaTables.userRoles;if(tenantRolesTable&&tenantUserRolesTable)try{let godminRoles=await db.select().from(tenantRolesTable).where(eq40(tenantRolesTable.name,GODMIN_ROLE_NAME)).limit(1),godminRoleId;if(godminRoles.length>0)godminRoleId=godminRoles[0].id;else{let[newRole]=await db.insert(tenantRolesTable).values({name:GODMIN_ROLE_NAME,description:"God mode administrator - bypasses all authorization checks"}).returning();godminRoleId=newRole.id,log4.info("Created godmin role in tenant schema",{tenantId,roleId:godminRoleId})}if(godminRoleId)await db.insert(tenantUserRolesTable).values({userId,roleId:godminRoleId}),log4.info("Assigned godmin role to signup user",{tenantId,userId,roleId:godminRoleId})}catch(roleErr){log4.warn("Failed to assign godmin role to signup user",{tenantId,userId,error:roleErr instanceof Error?roleErr.message:String(roleErr)})}await db.update(tenantsTable).set({status:"active",provisionedAt:now,updatedAt:now}).where(eq40(col12(tenantsTable,"id"),tenantId));let tenantEventsTable=mainContext.schemaTables.tenantEvents;if(tenantEventsTable)try{await db.insert(tenantEventsTable).values({id:crypto.randomUUID(),tenantId,eventType:"self_signup",eventData:JSON.stringify({email,subdomain,plan:body.plan||null}),performedBy:userId,ipAddress:ip,createdAt:now,updatedAt:now})}catch{}return await tenantRegistry.refreshTenant(tenantId),log4.info("Self-signup complete",{tenantId,subdomain,email}),await config.logger.audit({entityName:"tenants",entityId:tenantId,operation:"SELF_SIGNUP",userId,summary:`Self-signup: tenant "${subdomain}" provisioned by ${email}`,ipAddress:ip,userAgent:ctx.request.headers.get("user-agent")||"unknown",path:new URL(ctx.request.url).pathname,query:""}),{success:!0,message:"Tenant created successfully",data:{tenantId,subdomain,schemaName:tenantSchemaName,userId,email,status:"active"}}}catch(err){let msg=err instanceof Error?err.message:String(err);log4.error("Self-signup provisioning failed",{error:msg,tenantId});try{await db.update(tenantsTable).set({status:"provisioning",updatedAt:now}).where(eq40(col12(tenantsTable,"id"),tenantId))}catch{}return ctx.set.status=500,{success:!1,message:"Provisioning failed. Please try again."}}},body:SelfSignupBodySchema,detail:{tags:["Tenants"],summary:"Self-service tenant signup",description:"Public endpoint for self-service signup. Creates user account, provisions tenant schema, and seeds godmin. Rate-limited by IP with configurable max tenants per email."}})];return collectRoutes([{routes:declared}])}function createTenantRoutes(app,config){return useRoutes(app,createCheckSubdomainRoute(config)),useRoutes(app,createProvisionRoute(config)),useRoutes(app,createSelfSignupRoute(config)),useRoutes(app,createManageRoutes(config)),app}init_esm3();init_Notification();init_Verification();init_routeCollector();init_adminGuard();function createVerificationRoutes(routeConfig){let{db,schemaTables,config,logger:logger2,tenantRegistry}=routeConfig,defaultVerificationService=new VerificationService({db,schemaTables,config,logger:logger2}),defaultNotificationService=new NotificationService({db,schemaTables,config:routeConfig.notificationConfig||{enabled:!1},logger:logger2,getEmailService:routeConfig.getEmailService}),wireNotificationHandler=(verSvc,notifSvc)=>{verSvc.setNotificationHandler(async(params)=>{if(!routeConfig.notificationConfig?.enabled)return;await notifSvc.triggerNotifications({trigger:params.trigger,flow_id:params.flow_id,entity_name:params.entity_name,entity_id:params.entity_id,node_id:params.node_id,verifier_id:params.verifier_id,decision:params.decision,context:params.context})})};wireNotificationHandler(defaultVerificationService,defaultNotificationService);let getServicesForRequest=(request)=>{if(!tenantRegistry)return{verificationService:defaultVerificationService,notificationService:defaultNotificationService};let schemaName=request.headers.get("x-tenant-schema");if(!schemaName)return{verificationService:defaultVerificationService,notificationService:defaultNotificationService};let ctx=tenantRegistry.getSchemaContext(schemaName);if(!ctx)return{verificationService:defaultVerificationService,notificationService:defaultNotificationService};let reqVerSvc=new VerificationService({db,schemaTables:ctx.schemaTables,config,logger:logger2}),reqNotifSvc=new NotificationService({db,schemaTables:ctx.schemaTables,config:routeConfig.notificationConfig||{enabled:!1},logger:logger2,getEmailService:routeConfig.getEmailService});return wireNotificationHandler(reqVerSvc,reqNotifSvc),{verificationService:reqVerSvc,notificationService:reqNotifSvc}},basePath=config.endpoints?.basePath||"/verifications",flowBasePath=config.flowEndpoints?.basePath||"/verification-flows",requireUser=({request,set:set2})=>{if(!request.headers.get("x-user-id"))return set2.status=401,{success:!1,message:"Authentication required"}},verificationDeclared=[defineRoute({method:"GET",path:"/status/:entity_name/:entity_id",handler:async({params,request,set:set2})=>{let{verificationService}=getServicesForRequest(request),status=await verificationService.getStatus(params.entity_name,params.entity_id),startedBy=status.instance?.started_by;if(!hasGodminRole(request)&&startedBy!=null&&String(startedBy)!==String(request.headers.get("x-user-id")))return set2.status=403,{success:!1,message:"Forbidden"};return{success:!0,data:status}},params:Type.Object({entity_name:Type.String(),entity_id:Type.String()})}),defineRoute({method:"POST",path:"/:entity_name/:entity_id/decide",handler:async({params,body,request})=>{let{verificationService}=getServicesForRequest(request),userId=request.headers.get("x-user-id");if(!userId)return{success:!1,message:"User ID required",status:401};return await verificationService.decide({entity_name:params.entity_name,entity_id:params.entity_id,user_id:userId,decision:body.decision,reason:body.reason,signature_id:body.signature_id,diff:body.diff})},params:Type.Object({entity_name:Type.String(),entity_id:Type.String()}),body:Type.Object({decision:Type.Union([Type.Literal("approved"),Type.Literal("rejected")]),reason:Type.Optional(Type.String()),signature_id:Type.Optional(Type.String()),diff:Type.Optional(Type.Record(Type.String(),Type.Unknown()))})}),defineRoute({method:"GET",path:"/pending",handler:async({request})=>{let{verificationService}=getServicesForRequest(request),userId=request.headers.get("x-user-id");if(!userId)return{success:!1,message:"User ID required",status:401};return{success:!0,data:await verificationService.getPending(userId)}}}),defineRoute({method:"GET",path:"/history/:entity_name/:entity_id",handler:async({params,request,set:set2})=>{let{verificationService}=getServicesForRequest(request),status=await verificationService.getStatus(params.entity_name,params.entity_id),startedBy=status.instance?.started_by;if(!hasGodminRole(request)&&startedBy!=null&&String(startedBy)!==String(request.headers.get("x-user-id")))return set2.status=403,{success:!1,message:"Forbidden"};return{success:!0,data:{verifications:status.verifications,current_step:status.current_step,total_steps:status.total_steps,is_completed:status.is_completed,is_rejected:status.is_rejected}}},params:Type.Object({entity_name:Type.String(),entity_id:Type.String()})}),defineRoute({method:"POST",path:"/start",handler:async({body,request})=>{let{verificationService}=getServicesForRequest(request),userId=request.headers.get("x-user-id");return await verificationService.startFlow({flow_id:body.flow_id,entity_name:body.entity_name,entity_id:body.entity_id,started_by:userId||void 0})},body:Type.Object({flow_id:Type.String(),entity_name:Type.String(),entity_id:Type.String()})}),defineRoute({method:"POST",path:"/start-for-entity",handler:async({body,request})=>{let{verificationService}=getServicesForRequest(request),userId=request.headers.get("x-user-id");return await verificationService.startFlowForEntity({entity_name:body.entity_name,entity_id:body.entity_id,started_by:userId||void 0})},body:Type.Object({entity_name:Type.String(),entity_id:Type.String()})}),defineRoute({method:"GET",path:"/statuses/:entity_name",handler:async({params,query,request,set:set2})=>{if(!hasGodminRole(request))return set2.status=403,{success:!1,message:"Forbidden: operator privileges required"};let{verificationService}=getServicesForRequest(request);return{success:!0,data:await verificationService.listEntityStatuses({entity_name:params.entity_name,status:query.status||void 0,page:query.page?Number(query.page):void 0,limit:query.limit?Number(query.limit):void 0})}},params:Type.Object({entity_name:Type.String()}),query:Type.Object({status:Type.Optional(Type.String()),page:Type.Optional(Type.String()),limit:Type.Optional(Type.String())})})],groups=[{prefix:basePath,guards:[requireUser],routes:verificationDeclared}];if(logger2.info(`[Verification] Routes registered at ${basePath}`),config.flowEndpoints?.enabled!==!1){let flowDeclared=[defineRoute({method:"GET",path:"/",handler:async({query,request})=>{let{verificationService:vs}=getServicesForRequest(request);return{success:!0,data:await vs.listFlows(query.entity_name||void 0)}},query:Type.Object({entity_name:Type.Optional(Type.String())})}),defineRoute({method:"GET",path:"/:flow_id",handler:async({params,request,set:set2})=>{let{verificationService:vs}=getServicesForRequest(request),result=await vs.getFlow(params.flow_id);if(!result)return set2.status=404,{success:!1,message:"Flow not found"};return{success:!0,data:result}},params:Type.Object({flow_id:Type.String()})}),defineRoute({method:"POST",path:"/",handler:async({body,request})=>{let{verificationService:vs}=getServicesForRequest(request);return await vs.saveFlow(body)},body:Type.Object({flow_id:Type.String(),entity_name:Type.String(),name:Type.String(),description:Type.Optional(Type.String()),trigger_on:Type.Union([Type.Literal("create"),Type.Literal("update"),Type.Literal("delete"),Type.Literal("manual")]),trigger_fields:Type.Optional(Type.Array(Type.String())),is_draft:Type.Boolean(),viewport:Type.Optional(Type.Object({x:Type.Number(),y:Type.Number(),zoom:Type.Number()})),graph:Type.Object({steps:Type.Array(Type.Any()),edges:Type.Array(Type.Any()),verifier_configs:Type.Array(Type.Any()),notification_rules:Type.Array(Type.Any()),notification_recipients:Type.Array(Type.Any()),notification_channels:Type.Array(Type.Any())})})}),defineRoute({method:"POST",path:"/:flow_id/publish",handler:async({params,request})=>{let{verificationService:vs}=getServicesForRequest(request);return await vs.publishFlow(params.flow_id)},params:Type.Object({flow_id:Type.String()})}),defineRoute({method:"DELETE",path:"/:flow_id",handler:async({params,request})=>{let{verificationService:vs}=getServicesForRequest(request);return await vs.deleteFlow(params.flow_id)},params:Type.Object({flow_id:Type.String()})})];groups.push({prefix:flowBasePath,guards:[createGodminGuard2({db,schemaTables,logger:logger2},"verification flow management")],routes:flowDeclared}),logger2.info(`[Verification] Flow routes registered at ${flowBasePath}`)}return{routes:collectRoutes(groups),verificationService:defaultVerificationService,notificationService:defaultNotificationService}}function createNucleusRuntime(deps){return{sendEmail:async(payload)=>{let mailer=deps.getEmailService();if(!mailer?.isAvailable())return{success:!1,error:"No email provider is configured or available"};return mailer.sendEmail(payload)},emailProvider:()=>deps.activeEmailProvider(),isEmailAvailable:()=>!!deps.getEmailService()?.isAvailable(),emailSenderAddress:()=>deps.getEmailService()?.getSenderAddress?.()??""}}function unappliedSettings(config,swagger){let notes=[],say=(option,reality)=>notes.push(`${option} \u2014 ${reality}`);if(swagger?.provider&&swagger.provider!=="scalar")say(`swagger.provider: '${swagger.provider}'`,"only the Scalar viewer is shipped; the document itself is unaffected");if(swagger?.excludeStaticFile!==void 0)say("swagger.excludeStaticFile","nothing distinguishes a static-asset route from any other, so no route is excluded by it. Use `swagger.exclude` to name paths");let pubsub=config.pubsub;if(pubsub?.pubsubName)say("pubsub.pubsubName","names a Dapr component for a publish call this build never makes; realtime delivery goes straight to the connected sockets");if(pubsub?.ack?.retryIntervalMs!==void 0)say("pubsub.ack.retryIntervalMs","there is no interval-based retry loop; unacknowledged messages are redelivered when a client reconnects");if(config.domains?.cloudflare?.fallbackOrigin)say("domains.cloudflare.fallbackOrigin","never sent to Cloudflare; set the fallback origin on the zone itself");if(config.domains?.verification?.defaultMethod&&config.domains.verification.defaultMethod!=="txt")say(`domains.verification.defaultMethod: '${config.domains.verification.defaultMethod}'`,"ownership is always proved with a TXT record; cname and http are not implemented");if(config.database?.type)say("database.type","Postgres is the only engine this build talks to");let schemas=config.database?.schemas;if(Array.isArray(schemas)&&schemas.length>1)say(`database.schemas (${schemas.length} named)`,`only the first is used (\`${schemas[0]}\`); the rest are ignored`);let verification=config.authentication?.register?.emailVerification;if(verification?.provider)say(`authentication.register.emailVerification.provider: '${verification.provider}'`,"the sender is chosen globally from the `email` config and the secrets store, not per mail");let templates=verification?.templates,withTemplatePath=Object.entries(templates??{}).filter(([,t])=>t?.htmlTemplatePath).map(([name2])=>name2);if(withTemplatePath.length>0)say(`authentication.register.emailVerification.templates.${withTemplatePath.join("/")}.htmlTemplatePath`,"the mail bodies are built in code; only `subject` is taken from this config");return notes}init_utils5();function asText(value2){return typeof value2==="string"&&value2.trim()?value2.trim():void 0}function fieldOf(path7){let text2=asText(path7);if(!text2)return;return text2.replace(/^\//,"").replace(/\//g,".")||void 0}function fromValidator(error3){let candidate=error3;if(typeof candidate?.validator?.Errors!=="function")return;try{let fields=[],reason;for(let item of candidate.validator.Errors(candidate.value)){let field=fieldOf(item?.path);if(field&&!fields.includes(field))fields.push(field);if(reason??=asText(item?.message),fields.length>=4)break}return fields.length||reason?{fields,reason}:void 0}catch{return}}function compose(where,reason){if(where&&reason)return`Invalid request: ${where} \u2014 ${reason}`;if(where)return`Invalid request: ${where}`;if(reason)return`Invalid request: ${reason}`;return"Invalid request"}function fromMessage(raw){let text2=asText(raw);if(!text2)return"Invalid request";let payload;try{payload=JSON.parse(text2)}catch{return}if(!payload||typeof payload!=="object"||payload.type!=="validation")return;let on=asText(payload.on),property=fieldOf(payload.property),reason=asText(payload.summary)??asText(payload.message),where=property&&property!=="root"?`${on?`${on}.`:""}${property}`:on?`the request ${on}`:void 0;return compose(where,reason)}function describeValidationError(error3){if(typeof error3!=="string"&&error3){let detail=fromValidator(error3);if(detail){let on=asText(error3.type),where=detail.fields.length?detail.fields.map((field)=>on?`${on}.${field}`:field).join(", "):on?`the request ${on}`:void 0;return compose(where,detail.reason)}return fromMessage(error3.message)}return fromMessage(error3)}function statusOfError(code,error3){if(typeof code==="number")return code;let carried=error3?.status;if(typeof carried==="number"&&carried>=100&&carried<=599)return carried;return 500}init_auth();init_backup();init_payment();init_marketplace();init_storage2();var mergeEntitiesByName=(entities)=>{let entityMap=new Map;for(let entity of entities){let existing=entityMap.get(entity.table_name),mergedColumns=entity.columns??existing?.columns;entityMap.set(entity.table_name,{...existing||{},...entity,columns:mergedColumns})}return Array.from(entityMap.values())},effectiveConfigEntities=(configEntities)=>{if(!configEntities.some((e)=>e.extends))return configEntities;let{entities:resolved}=resolveEntities(SYSTEM_TABLES,configEntities),byName=new Map(resolved.map((t)=>[t.table_name,t]));return configEntities.map((e)=>e.extends?byName.get(e.table_name)??e:e)},normalizeSystemTable=(table)=>({table_name:table.table_name,excluded_methods:table.excluded_methods?[...table.excluded_methods]:void 0,columns:table.columns?table.columns.map((column)=>({name:column.name,type:column.type})):void 0}),extractSchemaTableEntities=(schemaTables)=>{let entities=[];for(let[_key,tableValue]of Object.entries(schemaTables)){if(!tableValue||typeof tableValue!=="object")continue;let tableObj=tableValue,underscoreMeta=tableObj._;if(underscoreMeta?.name){entities.push({table_name:underscoreMeta.name});continue}let symbols3=Object.getOwnPropertySymbols(tableObj);for(let sym of symbols3){let symValue=tableObj[sym];if(symValue&&typeof symValue==="object"){let symMeta=symValue;if(symMeta.name&&typeof symMeta.name==="string"){entities.push({table_name:symMeta.name});break}}}}return entities},lastMountedGroups=[];function getMountedRuntime2(){return getMountedRuntime()}function getResolvedOptions2(){return getResolvedOptions()}function getMountedRouteGroups(){return lastMountedGroups}var lastMountedSocket=null;function getMountedSocket(){return lastMountedSocket}var lastStaticAssets=null;function getMountedStaticAssets(){return lastStaticAssets}var lastBootSteps=null,lastShutdownSteps=null,lastAuthMiddleware=null,lastErrorHandler=null,lastAfterHandler=null;function getAfterHandler(){return lastAfterHandler}function getErrorHandler(){if(!lastErrorHandler)throw Error("getErrorHandler() before NucleusElysiaPlugin() \u2014 nothing has been built yet");return lastErrorHandler}function getAuthMiddleware(){if(!lastAuthMiddleware)throw Error("getAuthMiddleware() before NucleusElysiaPlugin() \u2014 nothing has been built yet");return lastAuthMiddleware}function getBootSteps(){if(!lastBootSteps)throw Error("getBootSteps() before NucleusElysiaPlugin() \u2014 nothing has been built yet");return lastBootSteps}function getShutdownSteps(){return lastShutdownSteps??[]}function resolveOverrides(groups,logger2){let claim=new Map,dropped=[],kept=[];for(let index=groups.length-1;index>=0;index--){let group=groups[index];if(!group)continue;let routes=[];for(let position=group.routes.length-1;position>=0;position--){let route=group.routes[position];if(!route)continue;let declared=route.path==="/"?"":route.path,key2=`${route.method} ${`${group.prefix??""}${declared}`||"/"}`,winner=claim.get(key2);if(winner){let summary=route.detail?.summary??"(no summary)";dropped.push(`${key2} \u2014 kept "${winner.summary}", dropped "${summary}"`);continue}claim.set(key2,{summary:route.detail?.summary??"(no summary)"}),routes.unshift(route)}if(routes.length>0)kept.unshift({...group,routes})}if(dropped.length>0)logger2.warn(`[Routes] ${dropped.length} route(s) replaced by a later declaration of the same path`,{replaced:dropped});return kept}async function buildNucleus(config){beginRouteRecording();let plugin=collectRoutes(),routeGetHealth=defineRoute({method:"GET",path:"/health",handler:()=>({status:"ok",timestamp:Date.now()})});if(mountRoutes(plugin,[{routes:[routeGetHealth]}]),config.staticAssets!==!1){let path8=__require("path"),fs3=__require("fs"),assetsPath;if(typeof config.staticAssets==="string")assetsPath=config.staticAssets;else{let localPath=path8.join(process.cwd(),"public"),resolvedPkgPath="";for(let pkgName of["nucleus-core-ts","nucleus-core"])try{let pkgJson=__require.resolve(`${pkgName}/package.json`),candidate=path8.join(path8.dirname(pkgJson),"public");if(fs3.existsSync(candidate)){resolvedPkgPath=candidate;break}}catch{}if(resolvedPkgPath)assetsPath=resolvedPkgPath;else if(fs3.existsSync(localPath))assetsPath=localPath;else assetsPath=localPath}recordStaticAssets(assetsPath)}let publicRoutes=[],resolvedOptions,configDir=process.cwd(),configFilePath=null;if(typeof config.options==="string"){let fs3=__require("fs"),path8=__require("path"),configPath=path8.isAbsolute(config.options)?config.options:path8.resolve(process.cwd(),config.options);configDir=path8.dirname(configPath),configFilePath=configPath;let configContent=fs3.readFileSync(configPath,"utf-8");resolvedOptions=JSON.parse(configContent)}else resolvedOptions=config.options;if(resolvedOptions.email?.gmail?.json_file_path){let path8=__require("path"),gmailPath=resolvedOptions.email.gmail.json_file_path;if(!path8.isAbsolute(gmailPath))resolvedOptions.email.gmail.json_file_path=path8.resolve(configDir,gmailPath)}recordResolvedOptions(resolvedOptions);let{authentication,audit,entities,database}=resolvedOptions,isDev=resolvedOptions.mode==="development",loggingConfig=resolvedOptions.logging,logger2=new Logger({service:resolvedOptions.appId||"nucleus",level:loggingConfig?.level||(isDev?"debug":"info"),prettyPrint:loggingConfig?.prettyPrint??isDev,colorize:loggingConfig?.colorize??isDev,includeCallerInfo:loggingConfig?.includeCallerInfo??isDev,redactKeys:loggingConfig?.redactKeys||[],auditEnabled:audit?.enabled??!1,...audit?.suppressReasons!==void 0?{auditSuppressReasons:audit.suppressReasons}:{},...audit?.minSeverity!==void 0?{auditMinSeverity:audit.minSeverity}:{},enabledScopes:loggingConfig?.scopes||["*"]});Logger.getInstance().configure({service:resolvedOptions.appId||"nucleus",level:loggingConfig?.level||(isDev?"debug":"info"),prettyPrint:loggingConfig?.prettyPrint??isDev,colorize:loggingConfig?.colorize??isDev,includeCallerInfo:loggingConfig?.includeCallerInfo??isDev,redactKeys:loggingConfig?.redactKeys||[],enabledScopes:loggingConfig?.scopes||["*"]});let serverLogsConfig=resolvedOptions.monitoring?.serverLogs,serverLogBuffer=serverLogsConfig?.enabled===!0?getServerLogBuffer({capacity:serverLogsConfig.capacity??2000,maxLineLength:serverLogsConfig.maxLineLength}):null;if(serverLogBuffer){let transport=new ServerLogTransport(serverLogBuffer);if(logger2.addTransport(transport),Logger.getInstance().addTransport(transport),serverLogsConfig?.captureStdout!==!1)attachStreamCapture(serverLogBuffer);logger2.info(`[ServerLogs] Buffer active (capacity: ${serverLogBuffer.getCapacity()}, stdout capture: ${serverLogsConfig?.captureStdout!==!1})`)}{let{entities:resolvedForBoot,errors:extendErrors,warnings:extendWarnings}=resolveEntities(SYSTEM_TABLES,entities??[],{consumerMode:authentication?.mode==="consumer"});for(let warning of extendWarnings)logger2.warn(warning);let syncErrors=[],syncMappings=resolvedOptions.authentication?.oauth?.profileSync?.mappings??[];if(syncMappings.length>0){let columnsFor=(name2)=>{return(resolvedForBoot.find((t)=>t.table_name===name2)?.columns??[]).map((c)=>c.name)};syncErrors.push(...validateProfileSyncTargets(syncMappings,{users:columnsFor("users"),profiles:columnsFor("profiles")}))}if(extendErrors.length>0||syncErrors.length>0){let detail=[...extendErrors.map((e)=>` ${e.code}: ${e.message}`),...syncErrors.map((m)=>` PROFILE_SYNC_TARGET: ${m}`)].join(`
|