nucleus-core-ts 0.9.974 → 0.9.975
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/.build-ok +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
package/dist/.build-ok
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.9.
|
|
1
|
+
0.9.975
|
package/dist/index.js
CHANGED
|
@@ -543,7 +543,7 @@ data: ${payload}
|
|
|
543
543
|
`)};function createLiveMonitoringRoutes(config){let{getService,logger:logger2,basePath,streamInterval,db,schemaTables}=config,plugin=new Elysia15({prefix:basePath});plugin.onBeforeHandle(createGodminGuard({db:db??null,schemaTables:schemaTables??{},logger:logger2},"live monitoring"));let requireService=()=>{let svc=getService();if(!svc)throw Error("Live monitoring service not initialized");return svc};return plugin.get("/health",()=>{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"}}),plugin.get("/settings",()=>{return requireService().getSettings()},{detail:{tags:["Live Monitoring"],summary:"Get live monitoring settings"}}),plugin.patch("/settings",({body})=>{return requireService().changeSettings(body)},{body:t13.Partial(t13.Object({logMemory:t13.Boolean(),logCpu:t13.Boolean(),logDapr:t13.Boolean(),logWebSocket:t13.Boolean(),memoryLogInterval:t13.Number(),cpuLogInterval:t13.Number(),memoryLogLimit:t13.Number(),cpuLogLimit:t13.Number(),daprLogLimit:t13.Number(),wsLogLimit:t13.Number(),requestLogLimit:t13.Number()})),detail:{tags:["Live Monitoring"],summary:"Change live monitoring settings"}}),plugin.get("/realtime",({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:t13.Optional(t13.Object({windowMinutes:t13.Optional(t13.String())})),detail:{tags:["Live Monitoring"],summary:"Realtime (WebSocket) health digest"}}),plugin.get("/logs",()=>{return requireService().getLogs()},{detail:{tags:["Live Monitoring"],summary:"Get all live monitoring logs"}}),plugin.get("/logs/stream",({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"}}),logger2.info(`[LiveMonitoring] Routes enabled at ${basePath} (stream interval: ${streamInterval}ms)`),plugin}init_adminGuard();import Elysia16,{t as t14}from"elysia";var STREAM_HEADERS2={"Content-Type":"text/event-stream; charset=utf-8","Cache-Control":"no-cache, no-transform",Connection:"keep-alive"},textEncoder2=new TextEncoder,encodeEvent2=(event,data)=>textEncoder2.encode(`event: ${event}
|
|
544
544
|
data: ${JSON.stringify(data)}
|
|
545
545
|
|
|
546
|
-
`),LOG_LEVELS=["debug","info","warn","error","fatal"],SOURCES=["logger","stdout","stderr"];function parseLevels(raw){if(!raw)return;let parsed=raw.split(",").map((value)=>value.trim().toLowerCase()).filter((value)=>LOG_LEVELS.includes(value));return parsed.length>0?parsed:void 0}function parseSource(raw){if(!raw)return;let value=raw.trim().toLowerCase();return SOURCES.includes(value)?value:void 0}function parseMinLevel(raw){if(!raw)return;let value=raw.trim().toLowerCase();return LOG_LEVELS.includes(value)?value:void 0}function parseCursor(raw){if(raw===void 0||raw===null||raw==="")return;let value=Number(raw);return Number.isFinite(value)&&value>=0?value: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:path8,allowRoles,streamEnabled=!0,clearEnabled=!0,db,schemaTables}=config,plugin=new Elysia16({prefix:basePath});if(plugin.onBeforeHandle(createPrivilegedGuard({db:db??null,schemaTables:schemaTables??{},logger:logger2},"server logs",{allowRoles})),plugin.get(path8,({query})=>{return{isSuccess:!0,message:"Server logs",data:{...buffer.query(buildQuery(query)),scopes:buffer.scopes(),levelCounts:buffer.levelCounts()}}},{query:t14.Object({levels:t14.Optional(t14.String()),minLevel:t14.Optional(t14.String()),scope:t14.Optional(t14.String()),search:t14.Optional(t14.String()),source:t14.Optional(t14.String()),after:t14.Optional(t14.Numeric()),before:t14.Optional(t14.Numeric()),limit:t14.Optional(t14.Numeric())}),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."}}),streamEnabled)plugin.get(`${path8}/stream`,({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(encodeEvent2("backlog",backlog));let unsubscribe=buffer.subscribe((record)=>{if(closed)return;if(!buffer.query({...filter,after:record.seq-1,limit:1}).records.some((r)=>r.seq===record.seq))return;try{controller.enqueue(encodeEvent2("logs",[record]))}catch{cleanup?.()}}),heartbeat=setInterval(()=>{if(closed)return;try{controller.enqueue(encodeEvent2("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_HEADERS2})},{query:t14.Object({levels:t14.Optional(t14.String()),minLevel:t14.Optional(t14.String()),scope:t14.Optional(t14.String()),search:t14.Optional(t14.String()),source:t14.Optional(t14.String()),after:t14.Optional(t14.Numeric())}),detail:{tags:["Monitoring"],summary:"Tail server logs",description:"Server-Sent Events tail of the server log buffer, filtered server-side."}});if(clearEnabled)plugin.delete(path8,()=>{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}${path8} (capacity: ${buffer.getCapacity()}, stream: ${streamEnabled})`),plugin}init_Notification();init_adminGuard();import{Elysia as Elysia17,t as t15}from"elysia";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})},routes=new Elysia17({prefix:basePath}),requireUser=(request,set)=>{let userId=request.headers.get("x-user-id");if(!userId)return set.status=401,null;return userId};return routes.get("/",async({request,query,set})=>{let userId=requireUser(request,set);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:t15.Object({limit:t15.Optional(t15.String()),offset:t15.Optional(t15.String()),type:t15.Optional(t15.String())}),detail:{tags:["Notifications"],summary:"List the caller\u2019s notifications"}}),routes.get("/unseen-count",async({request,set})=>{let userId=requireUser(request,set);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"}}),routes.post("/:notification_id/seen",async({params,request,set})=>{let userId=requireUser(request,set);if(!userId)return{success:!1,message:"User ID required"};let marked=await serviceFor(request).markAsSeen(params.notification_id,userId);return{success:marked,message:marked?"Marked as seen":"Failed"}},{params:t15.Object({notification_id:t15.String()}),detail:{tags:["Notifications"],summary:"Mark one notification as seen"}}),routes.post("/seen-all",async({request,set})=>{let userId=requireUser(request,set);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"}}),routes.post("/send",async({body,request,set})=>{if(!requireUser(request,set))return{success:!1,message:"Authentication required"};if(!await canSend(request))return set.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 set.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)=>({result,userId:recipients[index]})).filter((entry)=>entry.result.status==="rejected").map((entry)=>entry.userId);return{success:failed.length===0,message:failed.length===0?`Sent to ${recipients.length} recipient(s)`:`Sent to ${recipients.length-failed.length}, failed for ${failed.length}`,data:{sent:recipients.length-failed.length,failed}}},{body:t15.Object({userId:t15.Optional(t15.String()),userIds:t15.Optional(t15.Array(t15.String())),title:t15.String(),body:t15.Optional(t15.String()),entityName:t15.Optional(t15.String()),entityId:t15.Optional(t15.String()),type:t15.Optional(t15.Union([t15.Literal("verification"),t15.Literal("system"),t15.Literal("custom")])),source:t15.Optional(t15.String()),channels:t15.Optional(t15.Array(t15.Union(CHANNELS.map((c)=>t15.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."}}),routes.post("/email",async({body,request,set})=>{if(!requireUser(request,set))return{success:!1,message:"Authentication required"};if(!await canSend(request))return set.status=403,{success:!1,message:"Sending email requires godmin or an allowed role"};let emailService=getEmailService?.()??null;if(!emailService?.isAvailable())return set.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 set.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:t15.Object({to:t15.Union([t15.String(),t15.Array(t15.String())]),subject:t15.String(),html:t15.Optional(t15.String()),text:t15.Optional(t15.String()),replyTo:t15.Optional(t15.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."}}),{routes,notificationService:defaultService}}init_ack_manager();import{timingSafeEqual as timingSafeEqual3}from"crypto";import Elysia18 from"elysia";init_ack_manager();var clients=new Map,presenceBroadcastDebounce=new Map;function createClientManager(config){let{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??64,MAX_TOPIC_LENGTH=256;function generateClientId(){return`client_${Date.now()}_${Math.random().toString(36).substring(2,9)}`}function capTopics(topics){let capped=[],seen=new Set;for(let t16 of topics){if(typeof t16!=="string")continue;let trimmed=t16.length>256?t16.slice(0,256):t16;if(seen.has(trimmed))continue;if(seen.add(trimmed),capped.push(trimmed),capped.length>=maxTopicsPerClient)break}return capped}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)}clients.set(clientId,{ws,userId,subscribedTopics:new Set(capTopics(topics)),connectedAt:Date.now(),pendingAcks:new Map}),logger2.info("[PubSub] Client registered",{clientId,userId,topics,totalClients:clients.size})}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)client.subscribedTopics=new Set(capTopics(topics))}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}return{generateClientId,registerClient,unregisterClient,updateClientTopics,getClientsByUser,getConnectedUserIds,getClientCount,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(value){if(ArrayBuffer.isView(value))return value.byteLength;if(value instanceof ArrayBuffer)return value.byteLength;try{return JSON.stringify(value)?.length??0}catch{return 0}}function asMessage(value,size){if(!value||typeof value!=="object"||Array.isArray(value))return{kind:"ignore"};let record=value,type=record.type;if(typeof type!=="string"||type==="")return{kind:"ignore"};return{kind:"message",parsed:record,type,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,value]of Object.entries(rawHeaders))if(typeof value==="string")headers.set(key2,value)}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,plugin=new Elysia18;return plugin.onStart(()=>{if(config.cleanupIntervalMs>0)cleanupTimer=setInterval(()=>{logger2.info("[PubSub] Periodic cleanup running",{totalClients:clientManager.getClientCount()})},config.cleanupIntervalMs);logger2.info("[PubSub] Routes initialized",{basePath:config.basePath,wsPath:config.wsPath,ackEnabled:config.ack.enabled,presenceEnabled:config.presence.enabled})}),plugin.onStop(()=>{if(cleanupTimer)clearInterval(cleanupTimer),cleanupTimer=null}),plugin.post(`${config.basePath}/:topic`,async({params,body,request,set})=>{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}),set.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 set.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}}),set.status=200,{status:"SUCCESS"}}),plugin.ws(config.wsPath,{idleTimeout:config.wsIdleTimeout,open(ws){let data=ws.data||{},query=data.query||{},userId;if(config.authenticate){let handshakeHeaders=buildHandshakeHeaders(data.headers,query.token),authResult=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((t16)=>t16.trim())||["*"];if(ws.data.clientId=clientId,ws.data.openedAt=Date.now(),clientManager.registerClient(clientId,ws,userId,topics),ws.send(JSON.stringify({type:"connected",clientId,subscribedTopics:topics,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))clientManager.updateClientTopics(clientId,parsed.topics),clientManager.sendToClient(clientId,{type:"subscribed",topics:parsed.topics,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(error){logger2.error("[PubSub] Client message handler failed",error,{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)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})}}),{plugin,clientManager}}function classifyRefusal(error){if(!error)return"unspecified";switch(error){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(error.startsWith("Unsupported algorithm:"))return"malformed";return"unspecified"}}import Elysia19,{t as t16}from"elysia";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=(error)=>{if(error instanceof Error){if(error.name==="TimeoutError")return"Provider did not respond within 12s";return error.message}return String(error)},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=(value)=>{let trimmed=value?.trim();if(!trimmed)return"Y\xF6nlendirme adresi (Redirect URI) bo\u015F \u2014 giri\u015F tamamlanamaz";if(/^[A-Z][A-Z0-9_]{2,}$/.test(trimmed))return`Y\xF6nlendirme adresi bir ortam de\u011Fi\u015Fkeni ad\u0131 olarak duruyor ("${trimmed}") ve o de\u011Fi\u015Fken tan\u0131ml\u0131 de\u011Fil`;let parsed;try{parsed=new URL(trimmed)}catch{return`Y\xF6nlendirme adresi ge\xE7erli bir URL de\u011Fil: "${trimmed}"`}if(parsed.protocol!=="https:"&&parsed.hostname!=="localhost")return`Y\xF6nlendirme adresi https olmal\u0131 (\u015Fu an "${parsed.protocol}//")`;return null};async function testOAuthProvider(provider,config){if(!config.clientId||!config.clientSecret)return{ok:!1,status:"failed",message:"Client ID ve Client Secret dolu olmal\u0131",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 kimlik bilgileri do\u011Fruland\u0131 (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:`"${provider}" i\xE7in token adresi tan\u0131ml\u0131 de\u011Fil \u2014 tokenUrl girin`,durationMs:0};return await probeWithBogusCode(tokenUrl,config)}catch(error){return{ok:!1,status:"failed",message:describeError(error),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:"Kimlik bilgileri sa\u011Flay\u0131c\u0131 taraf\u0131ndan kabul edildi",code,durationMs};if(code==="invalid_client"||code==="unauthorized_client"||result.status===401)return{ok:!1,status:"failed",message:description||"Sa\u011Flay\u0131c\u0131 Client ID/Secret \xE7iftini reddetti",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||"Sa\u011Flay\u0131c\u0131 beklenmeyen bir yan\u0131t d\xF6nd\xFC",code:code||String(result.status),durationMs}}async function testStorageProvider(provider){if(!provider)return{ok:!1,status:"failed",message:"Depolama sa\u011Flay\u0131c\u0131 yap\u0131land\u0131r\u0131lmam\u0131\u015F",durationMs:0};try{let{result,durationMs}=await timed(()=>provider.isAvailable());return result?{ok:!0,status:"ok",message:`Ba\u011Flant\u0131 do\u011Fruland\u0131: ${provider.describe}`,durationMs}:{ok:!1,status:"failed",message:`Eri\u015Filemedi: ${provider.describe} \u2014 sunucu, payla\u015F\u0131m ad\u0131 ve kimlik bilgilerini kontrol edin`,durationMs}}catch(error){return{ok:!1,status:"failed",message:describeError(error),durationMs:0}}}async function testEmailProvider(service,recipient){if(!service)return{ok:!1,status:"failed",message:"E-posta sa\u011Flay\u0131c\u0131 yap\u0131land\u0131r\u0131lmam\u0131\u015F",durationMs:0};if(!service.isAvailable())return{ok:!1,status:"failed",message:"Sa\u011Flay\u0131c\u0131 kimlik bilgileriyle ba\u015Flat\u0131lamad\u0131",durationMs:0};if(!recipient)return{ok:!0,status:"ok",message:"Sa\u011Flay\u0131c\u0131 ba\u015Flat\u0131ld\u0131 \u2014 ger\xE7ek g\xF6nderim i\xE7in bir al\u0131c\u0131 adresi girin",durationMs:0};try{let{result,durationMs}=await timed(()=>service.sendEmail({to:recipient,subject:"Nucleus ba\u011Flant\u0131 testi",html:"<p>Bu e-posta, y\xF6netim panelinden yap\u0131lan ba\u011Flant\u0131 testiyle g\xF6nderildi.</p>"}));return result.success?{ok:!0,status:"ok",message:`Test e-postas\u0131 ${recipient} adresine g\xF6nderildi`,durationMs}:{ok:!1,status:"failed",message:result.error??"G\xF6nderim ba\u015Far\u0131s\u0131z",durationMs}}catch(error){return{ok:!1,status:"failed",message:describeError(error),durationMs:0}}}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=(set,status,message)=>{return set.status=status,{isSuccess:!1,message,data:null}};function createSecretsRoutes(routeConfig){let{logger:logger2,basePath,db,schemaTables,getService,resolvedOptions,getEmailService,getStorageProvider:getStorageProvider2}=routeConfig,plugin=new Elysia19({prefix:basePath});plugin.onBeforeHandle(createGodminGuard2({logger:logger2,db,schemaTables,area:"secrets management",allowedRoles:routeConfig.adminRoles}));let requireService=(set)=>{let service=getService();if(!service)return set.status=503,null;return service},actorOf=(request)=>request.headers.get("x-user-id")??void 0;return plugin.get("/slots",async({set})=>{let service=requireService(set);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."}}),plugin.get("/",async({set})=>{let service=requireService(set);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."}}),plugin.put("/:scope/:key",async({params,body,set,request})=>{let service=requireService(set);if(!service)return{isSuccess:!1,message:"Secrets management is disabled",data:null};let{scope,key:key2}=params;if(!SCOPE_PATTERN.test(scope))return fail5(set,400,"Invalid scope");if(!KEY_PATTERN.test(key2))return fail5(set,400,"Invalid key");let value=body.value;if(typeof value!=="string"||value.length===0)return fail5(set,400,"value is required");if(value.length>65536)return fail5(set,400,"value exceeds 64KB");if(body.kind==="json")try{JSON.parse(value)}catch{return fail5(set,400,"value is not valid JSON")}let saved=await service.set({scope,key:key2,value,kind:body.kind,description:body.description,actorId:actorOf(request)});if(!saved)return fail5(set,500,"Could not store the credential");return{isSuccess:!0,message:"Credential stored",data:{secret:saved}}},{params:t16.Object({scope:t16.String(),key:t16.String()}),body:t16.Object({value:t16.String(),kind:t16.Optional(t16.Union(SECRET_KINDS.map((kind)=>t16.Literal(kind)))),description:t16.Optional(t16.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."}}),plugin.post("/:scope/:key/rollback",async({params,set,request})=>{let service=requireService(set);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(set,404,"No previous value to roll back to");return{isSuccess:!0,message:"Previous credential restored",data:{secret:restored}}},{params:t16.Object({scope:t16.String(),key:t16.String()}),detail:{tags:["Secrets"],summary:"Roll back to the value replaced by the last rotation"}}),plugin.delete("/:scope/:key",async({params,set,request})=>{let service=requireService(set);if(!service)return{isSuccess:!1,message:"Secrets management is disabled",data:null};if(!await service.remove(params.scope,params.key,actorOf(request)))return fail5(set,404,"Credential not found");return{isSuccess:!0,message:"Credential removed \u2014 the config falls back to env or literal",data:null}},{params:t16.Object({scope:t16.String(),key:t16.String()}),detail:{tags:["Secrets"],summary:"Delete a stored credential",description:"Resolution falls back to the env var or literal declared in config."}}),plugin.post("/test",async({body,set})=>{let service=requireService(set);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(set,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(set,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:t16.Object({target:t16.Union([t16.Literal("oauth"),t16.Literal("email"),t16.Literal("storage")]),provider:t16.Optional(t16.String()),recipient:t16.Optional(t16.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."}}),plugin.post("/reencrypt",async({body,set})=>{let service=requireService(set);if(!service)return{isSuccess:!1,message:"Secrets management is disabled",data:null};if(!body.newMasterKey||body.newMasterKey.length<16)return fail5(set,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:t16.Object({newMasterKey:t16.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."}}),plugin.get("/discover",()=>({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."}}),plugin.post("/storage/migrate",async(ctx)=>{let body=ctx.body,build=routeConfig.buildStorageProvider;if(!build)return fail5(ctx.set,503,"Depolama yap\u0131land\u0131r\u0131lmam\u0131\u015F");let destination=build(body.to),source=build(body.to==="smb"?"local":"smb");if(!source||!destination)return fail5(ctx.set,503,"Depolama yap\u0131land\u0131r\u0131lmam\u0131\u015F");if(!await destination.isAvailable())return fail5(ctx.set,422,`Hedef depo eri\u015Filebilir de\u011Fil (${destination.describe}) \u2014 \xF6nce ba\u011Flant\u0131y\u0131 test edin`);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} dosya ta\u015F\u0131nacak`,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} dosya ta\u015F\u0131nd\u0131, ${report.skipped} zaten mevcuttu`:`${report.copied} ta\u015F\u0131nd\u0131, ${report.failed} ba\u015Far\u0131s\u0131z${report.truncated?", liste kesildi":""}`,data:{report}}},{body:t16.Object({to:t16.Union([t16.Literal("local"),t16.Literal("smb")]),dryRun:t16.Optional(t16.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."}}),plugin}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 mb=(bytes)=>`${Math.round(bytes/1048576)} MB`,source=input.configured==null?`the host passed no \`serve.maxRequestBodySize\`, so Bun's default of ${mb(134217728)} applies`:`the host passed ${mb(input.configured)}`;return`Uploads are configured up to ${mb(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: new Elysia({ serve: { maxRequestBodySize: ${input.required} } }).`}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 mb=(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 ${mb(input.required)}, but this container is capped at ${mb(input.cgroupLimit)} and an upload is held in memory twice on top of the ~${mb(APP_BASELINE_BYTES)} `+`the app already holds \u2014 it needs ~${mb(needed)}. The kernel will kill the process mid-upload. Fix: raise the container's memory limit to at least ${mb(needed)}, or lower storage.maxFileSizeBytes to about ${mb(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 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(),eq32=trimmed.indexOf("=");if(eq32>0)out[trimmed.slice(0,eq32)]=trimmed.slice(eq32+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((value,key2)=>{out[key2]=SENSITIVE_HEADER_NAMES.has(key2.toLowerCase())?"[REDACTED]":value}),out}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_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();import{eq as eq32}from"drizzle-orm";import{Elysia as Elysia20,t as t17}from"elysia";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 checkRoute=new Elysia20;return checkRoute.get("/tenants/check-subdomain/:subdomain",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((t18)=>t18.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(eq32(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:t17.Object({subdomain:t17.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."}}),checkRoute}init_Logger2();import{eq as eq33}from"drizzle-orm";import{Elysia as Elysia21,t as t18}from"elysia";var col10=(table,name2)=>table[name2];function createManageRoutes(config){let log4=config.logger.scoped(TENANT_SUSPEND),manageRoutes=new Elysia21;return manageRoutes.get("/tenants",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((t19)=>t19.status!=="active")];return{success:!0,message:`Found ${allTenants.length} tenants`,data:{items:allTenants.map((t19)=>({id:t19.id,subdomain:t19.subdomain,schemaName:t19.schemaName,companyId:t19.companyId,companyName:t19.companyName,godAdminEmail:t19.godAdminEmail,status:t19.status,plan:t19.plan,domain:t19.domain,maxUsers:t19.maxUsers,provisionedAt:t19.provisionedAt,suspendedAt:t19.suspendedAt})),totalCount:allTenants.length}}},{detail:{tags:["Tenants"],summary:"List tenants",description:"List all tenants in the platform. Requires godmin privileges."}}),manageRoutes.get("/tenants/:id",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."}}),manageRoutes.post("/tenants/:id/suspend",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(eq33(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-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:t18.Object({reason:t18.Optional(t18.String())}),detail:{tags:["Tenants"],summary:"Suspend tenant",description:"Suspend a tenant, preventing all access. Requires godmin privileges."}}),manageRoutes.post("/tenants/:id/reactivate",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(eq33(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-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."}}),manageRoutes}var verifyGodmin=async(db,tenantRegistry,userId)=>{let usersTable=tenantRegistry.getMainContext().schemaTables.users;if(!usersTable)return!1;return(await db.select().from(usersTable).where(eq33(col10(usersTable,"id"),userId)).limit(1))[0]?.isGod===!0},getAllTenants=(registry2)=>{let schemaNames=registry2.getAllSchemaNames(),tenants=[];for(let name2 of schemaNames){let ctx=registry2.getSchemaContext(name2);if(ctx?.tenant)tenants.push(ctx.tenant)}return tenants};init_Logger2();init_utils6();import{eq as eq34}from"drizzle-orm";import{Elysia as Elysia22,t as t19}from"elysia";var col11=(table,name2)=>table[name2],ProvisionBodySchema=t19.Object({subdomain:t19.String({minLength:2,maxLength:63,pattern:"^[a-z][a-z0-9-]*$"}),companyId:t19.String({minLength:1}),companyName:t19.Optional(t19.String()),godAdminEmail:t19.String({format:"email"}),plan:t19.Optional(t19.String()),domain:t19.Optional(t19.String())});function createProvisionRoute(config){let log4=config.logger.scoped(TENANT_PROVISION),provisionRoutes=new Elysia22;return provisionRoutes.post("/tenants/provision",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"};let requestingUser=(await db.select().from(usersTable).where(eq34(col11(usersTable,"id"),requestingUserId)).limit(1))[0];if(!requestingUser||!requestingUser.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((t20)=>t20.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(eq34(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(eq34(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."}}),provisionRoutes}init_Authorization();init_Logger2();init_utils6();import{eq as eq35}from"drizzle-orm";import{Elysia as Elysia23,t as t20}from"elysia";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=t20.Object({email:t20.String({format:"email"}),password:t20.String({minLength:8}),subdomain:t20.String({minLength:2,maxLength:63,pattern:"^[a-z][a-z0-9-]*$"}),plan:t20.Optional(t20.String()),companyName:t20.Optional(t20.String())});function createSelfSignupRoute(config){let log4=config.logger.scoped(TENANT_SELF_SIGNUP),limits={...DEFAULT_SELF_SIGNUP_LIMITS,...config.selfSignupLimits},selfSignupRoutes=new Elysia23;return selfSignupRoutes.post("/tenants/self-signup",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((t21)=>t21.subdomain===subdomain))return ctx.set.status=409,{success:!1,message:"This subdomain is already taken."};try{if((await db.select().from(tenantsTable).where(eq35(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(eq35(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 userId,hashedPassword=await hashPassword(body.password),now=new Date,existingUsers=await db.select().from(usersTable).where(eq35(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(eq35(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(eq35(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(eq35(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."}}),selfSignupRoutes}function createTenantRoutes(app,config){return app.use(createCheckSubdomainRoute(config)),app.use(createProvisionRoute(config)),app.use(createSelfSignupRoute(config)),app.use(createManageRoutes(config)),app}init_Notification();init_Verification();init_adminGuard();import{Elysia as Elysia24,t as t21}from"elysia";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",routes=new Elysia24,verificationRoutes=new Elysia24({prefix:basePath});if(verificationRoutes.onBeforeHandle(({request,set})=>{if(!request.headers.get("x-user-id"))return set.status=401,{success:!1,message:"Authentication required"}}),verificationRoutes.get("/status/:entity_name/:entity_id",async({params,request,set})=>{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 set.status=403,{success:!1,message:"Forbidden"};return{success:!0,data:status}},{params:t21.Object({entity_name:t21.String(),entity_id:t21.String()})}),verificationRoutes.post("/:entity_name/:entity_id/decide",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:t21.Object({entity_name:t21.String(),entity_id:t21.String()}),body:t21.Object({decision:t21.Union([t21.Literal("approved"),t21.Literal("rejected")]),reason:t21.Optional(t21.String()),signature_id:t21.Optional(t21.String()),diff:t21.Optional(t21.Record(t21.String(),t21.Unknown()))})}),verificationRoutes.get("/pending",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)}}),verificationRoutes.get("/history/:entity_name/:entity_id",async({params,request,set})=>{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 set.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:t21.Object({entity_name:t21.String(),entity_id:t21.String()})}),verificationRoutes.post("/start",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:t21.Object({flow_id:t21.String(),entity_name:t21.String(),entity_id:t21.String()})}),verificationRoutes.post("/start-for-entity",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:t21.Object({entity_name:t21.String(),entity_id:t21.String()})}),verificationRoutes.get("/statuses/:entity_name",async({params,query,request,set})=>{if(!hasGodminRole(request))return set.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:t21.Object({entity_name:t21.String()}),query:t21.Object({status:t21.Optional(t21.String()),page:t21.Optional(t21.String()),limit:t21.Optional(t21.String())})}),routes.use(verificationRoutes),logger2.info(`[Verification] Routes registered at ${basePath}`),config.flowEndpoints?.enabled!==!1){let flowRoutes=new Elysia24({prefix:flowBasePath});flowRoutes.onBeforeHandle(createGodminGuard({db,schemaTables,logger:logger2},"verification flow management")),flowRoutes.get("/",async({query,request})=>{let{verificationService:vs}=getServicesForRequest(request);return{success:!0,data:await vs.listFlows(query.entity_name||void 0)}},{query:t21.Object({entity_name:t21.Optional(t21.String())})}),flowRoutes.get("/:flow_id",async({params,request})=>{let{verificationService:vs}=getServicesForRequest(request),result=await vs.getFlow(params.flow_id);if(!result)return{success:!1,message:"Flow not found"};return{success:!0,data:result}},{params:t21.Object({flow_id:t21.String()})}),flowRoutes.post("/",async({body,request})=>{let{verificationService:vs}=getServicesForRequest(request);return await vs.saveFlow(body)},{body:t21.Object({flow_id:t21.String(),entity_name:t21.String(),name:t21.String(),description:t21.Optional(t21.String()),trigger_on:t21.Union([t21.Literal("create"),t21.Literal("update"),t21.Literal("delete"),t21.Literal("manual")]),trigger_fields:t21.Optional(t21.Array(t21.String())),is_draft:t21.Boolean(),viewport:t21.Optional(t21.Object({x:t21.Number(),y:t21.Number(),zoom:t21.Number()})),graph:t21.Object({steps:t21.Array(t21.Any()),edges:t21.Array(t21.Any()),verifier_configs:t21.Array(t21.Any()),notification_rules:t21.Array(t21.Any()),notification_recipients:t21.Array(t21.Any()),notification_channels:t21.Array(t21.Any())})})}),flowRoutes.post("/:flow_id/publish",async({params,request})=>{let{verificationService:vs}=getServicesForRequest(request);return await vs.publishFlow(params.flow_id)},{params:t21.Object({flow_id:t21.String()})}),flowRoutes.delete("/:flow_id",async({params,request})=>{let{verificationService:vs}=getServicesForRequest(request);return await vs.deleteFlow(params.flow_id)},{params:t21.Object({flow_id:t21.String()})}),routes.use(flowRoutes),logger2.info(`[Verification] Flow routes registered at ${flowBasePath}`)}return{routes,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()}}import{Elysia as Elysia25}from"elysia";var a=`/* basic theme */
|
|
546
|
+
`),LOG_LEVELS=["debug","info","warn","error","fatal"],SOURCES=["logger","stdout","stderr"];function parseLevels(raw){if(!raw)return;let parsed=raw.split(",").map((value)=>value.trim().toLowerCase()).filter((value)=>LOG_LEVELS.includes(value));return parsed.length>0?parsed:void 0}function parseSource(raw){if(!raw)return;let value=raw.trim().toLowerCase();return SOURCES.includes(value)?value:void 0}function parseMinLevel(raw){if(!raw)return;let value=raw.trim().toLowerCase();return LOG_LEVELS.includes(value)?value:void 0}function parseCursor(raw){if(raw===void 0||raw===null||raw==="")return;let value=Number(raw);return Number.isFinite(value)&&value>=0?value: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:path8,allowRoles,streamEnabled=!0,clearEnabled=!0,db,schemaTables}=config,plugin=new Elysia16({prefix:basePath});if(plugin.onBeforeHandle(createPrivilegedGuard({db:db??null,schemaTables:schemaTables??{},logger:logger2},"server logs",{allowRoles})),plugin.get(path8,({query})=>{return{isSuccess:!0,message:"Server logs",data:{...buffer.query(buildQuery(query)),scopes:buffer.scopes(),levelCounts:buffer.levelCounts()}}},{query:t14.Object({levels:t14.Optional(t14.String()),minLevel:t14.Optional(t14.String()),scope:t14.Optional(t14.String()),search:t14.Optional(t14.String()),source:t14.Optional(t14.String()),after:t14.Optional(t14.Numeric()),before:t14.Optional(t14.Numeric()),limit:t14.Optional(t14.Numeric())}),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."}}),streamEnabled)plugin.get(`${path8}/stream`,({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(encodeEvent2("backlog",backlog));let unsubscribe=buffer.subscribe((record)=>{if(closed)return;if(!buffer.query({...filter,after:record.seq-1,limit:1}).records.some((r)=>r.seq===record.seq))return;try{controller.enqueue(encodeEvent2("logs",[record]))}catch{cleanup?.()}}),heartbeat=setInterval(()=>{if(closed)return;try{controller.enqueue(encodeEvent2("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_HEADERS2})},{query:t14.Object({levels:t14.Optional(t14.String()),minLevel:t14.Optional(t14.String()),scope:t14.Optional(t14.String()),search:t14.Optional(t14.String()),source:t14.Optional(t14.String()),after:t14.Optional(t14.Numeric())}),detail:{tags:["Monitoring"],summary:"Tail server logs",description:"Server-Sent Events tail of the server log buffer, filtered server-side."}});if(clearEnabled)plugin.delete(path8,()=>{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}${path8} (capacity: ${buffer.getCapacity()}, stream: ${streamEnabled})`),plugin}init_Notification();init_adminGuard();import{Elysia as Elysia17,t as t15}from"elysia";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})},routes=new Elysia17({prefix:basePath}),requireUser=(request,set)=>{let userId=request.headers.get("x-user-id");if(!userId)return set.status=401,null;return userId};return routes.get("/",async({request,query,set})=>{let userId=requireUser(request,set);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:t15.Object({limit:t15.Optional(t15.String()),offset:t15.Optional(t15.String()),type:t15.Optional(t15.String())}),detail:{tags:["Notifications"],summary:"List the caller\u2019s notifications"}}),routes.get("/unseen-count",async({request,set})=>{let userId=requireUser(request,set);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"}}),routes.post("/:notification_id/seen",async({params,request,set})=>{let userId=requireUser(request,set);if(!userId)return{success:!1,message:"User ID required"};let marked=await serviceFor(request).markAsSeen(params.notification_id,userId);return{success:marked,message:marked?"Marked as seen":"Failed"}},{params:t15.Object({notification_id:t15.String()}),detail:{tags:["Notifications"],summary:"Mark one notification as seen"}}),routes.post("/seen-all",async({request,set})=>{let userId=requireUser(request,set);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"}}),routes.post("/send",async({body,request,set})=>{if(!requireUser(request,set))return{success:!1,message:"Authentication required"};if(!await canSend(request))return set.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 set.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)=>({result,userId:recipients[index]})).filter((entry)=>entry.result.status==="rejected").map((entry)=>entry.userId);return{success:failed.length===0,message:failed.length===0?`Sent to ${recipients.length} recipient(s)`:`Sent to ${recipients.length-failed.length}, failed for ${failed.length}`,data:{sent:recipients.length-failed.length,failed}}},{body:t15.Object({userId:t15.Optional(t15.String()),userIds:t15.Optional(t15.Array(t15.String())),title:t15.String(),body:t15.Optional(t15.String()),entityName:t15.Optional(t15.String()),entityId:t15.Optional(t15.String()),type:t15.Optional(t15.Union([t15.Literal("verification"),t15.Literal("system"),t15.Literal("custom")])),source:t15.Optional(t15.String()),channels:t15.Optional(t15.Array(t15.Union(CHANNELS.map((c)=>t15.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."}}),routes.post("/email",async({body,request,set})=>{if(!requireUser(request,set))return{success:!1,message:"Authentication required"};if(!await canSend(request))return set.status=403,{success:!1,message:"Sending email requires godmin or an allowed role"};let emailService=getEmailService?.()??null;if(!emailService?.isAvailable())return set.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 set.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:t15.Object({to:t15.Union([t15.String(),t15.Array(t15.String())]),subject:t15.String(),html:t15.Optional(t15.String()),text:t15.Optional(t15.String()),replyTo:t15.Optional(t15.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."}}),{routes,notificationService:defaultService}}init_ack_manager();import{timingSafeEqual as timingSafeEqual3}from"crypto";import Elysia18 from"elysia";init_ack_manager();var clients=new Map,presenceBroadcastDebounce=new Map;function createClientManager(config){let{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??64,MAX_TOPIC_LENGTH=256;function generateClientId(){return`client_${Date.now()}_${Math.random().toString(36).substring(2,9)}`}function capTopics(topics){let capped=[],seen=new Set;for(let t16 of topics){if(typeof t16!=="string")continue;let trimmed=t16.length>256?t16.slice(0,256):t16;if(seen.has(trimmed))continue;if(seen.add(trimmed),capped.push(trimmed),capped.length>=maxTopicsPerClient)break}return capped}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)}clients.set(clientId,{ws,userId,subscribedTopics:new Set(capTopics(topics)),connectedAt:Date.now(),pendingAcks:new Map}),logger2.info("[PubSub] Client registered",{clientId,userId,topics,totalClients:clients.size})}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)client.subscribedTopics=new Set(capTopics(topics))}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}return{generateClientId,registerClient,unregisterClient,updateClientTopics,getClientsByUser,getConnectedUserIds,getClientCount,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(value){if(ArrayBuffer.isView(value))return value.byteLength;if(value instanceof ArrayBuffer)return value.byteLength;try{return JSON.stringify(value)?.length??0}catch{return 0}}function asMessage(value,size){if(!value||typeof value!=="object"||Array.isArray(value))return{kind:"ignore"};let record=value,type=record.type;if(typeof type!=="string"||type==="")return{kind:"ignore"};return{kind:"message",parsed:record,type,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,value]of Object.entries(rawHeaders))if(typeof value==="string")headers.set(key2,value)}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,plugin=new Elysia18;return plugin.onStart(()=>{if(config.cleanupIntervalMs>0)cleanupTimer=setInterval(()=>{logger2.info("[PubSub] Periodic cleanup running",{totalClients:clientManager.getClientCount()})},config.cleanupIntervalMs);logger2.info("[PubSub] Routes initialized",{basePath:config.basePath,wsPath:config.wsPath,ackEnabled:config.ack.enabled,presenceEnabled:config.presence.enabled})}),plugin.onStop(()=>{if(cleanupTimer)clearInterval(cleanupTimer),cleanupTimer=null}),plugin.post(`${config.basePath}/:topic`,async({params,body,request,set})=>{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}),set.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 set.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}}),set.status=200,{status:"SUCCESS"}}),plugin.ws(config.wsPath,{idleTimeout:config.wsIdleTimeout,open(ws){let data=ws.data||{},query=data.query||{},userId;if(config.authenticate){let handshakeHeaders=buildHandshakeHeaders(data.headers,query.token),authResult=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((t16)=>t16.trim())||["*"];if(ws.data.clientId=clientId,ws.data.openedAt=Date.now(),clientManager.registerClient(clientId,ws,userId,topics),ws.send(JSON.stringify({type:"connected",clientId,subscribedTopics:topics,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))clientManager.updateClientTopics(clientId,parsed.topics),clientManager.sendToClient(clientId,{type:"subscribed",topics:parsed.topics,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(error){logger2.error("[PubSub] Client message handler failed",error,{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})}}),{plugin,clientManager}}function classifyRefusal(error){if(!error)return"unspecified";switch(error){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(error.startsWith("Unsupported algorithm:"))return"malformed";return"unspecified"}}import Elysia19,{t as t16}from"elysia";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=(error)=>{if(error instanceof Error){if(error.name==="TimeoutError")return"Provider did not respond within 12s";return error.message}return String(error)},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=(value)=>{let trimmed=value?.trim();if(!trimmed)return"Y\xF6nlendirme adresi (Redirect URI) bo\u015F \u2014 giri\u015F tamamlanamaz";if(/^[A-Z][A-Z0-9_]{2,}$/.test(trimmed))return`Y\xF6nlendirme adresi bir ortam de\u011Fi\u015Fkeni ad\u0131 olarak duruyor ("${trimmed}") ve o de\u011Fi\u015Fken tan\u0131ml\u0131 de\u011Fil`;let parsed;try{parsed=new URL(trimmed)}catch{return`Y\xF6nlendirme adresi ge\xE7erli bir URL de\u011Fil: "${trimmed}"`}if(parsed.protocol!=="https:"&&parsed.hostname!=="localhost")return`Y\xF6nlendirme adresi https olmal\u0131 (\u015Fu an "${parsed.protocol}//")`;return null};async function testOAuthProvider(provider,config){if(!config.clientId||!config.clientSecret)return{ok:!1,status:"failed",message:"Client ID ve Client Secret dolu olmal\u0131",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 kimlik bilgileri do\u011Fruland\u0131 (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:`"${provider}" i\xE7in token adresi tan\u0131ml\u0131 de\u011Fil \u2014 tokenUrl girin`,durationMs:0};return await probeWithBogusCode(tokenUrl,config)}catch(error){return{ok:!1,status:"failed",message:describeError(error),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:"Kimlik bilgileri sa\u011Flay\u0131c\u0131 taraf\u0131ndan kabul edildi",code,durationMs};if(code==="invalid_client"||code==="unauthorized_client"||result.status===401)return{ok:!1,status:"failed",message:description||"Sa\u011Flay\u0131c\u0131 Client ID/Secret \xE7iftini reddetti",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||"Sa\u011Flay\u0131c\u0131 beklenmeyen bir yan\u0131t d\xF6nd\xFC",code:code||String(result.status),durationMs}}async function testStorageProvider(provider){if(!provider)return{ok:!1,status:"failed",message:"Depolama sa\u011Flay\u0131c\u0131 yap\u0131land\u0131r\u0131lmam\u0131\u015F",durationMs:0};try{let{result,durationMs}=await timed(()=>provider.isAvailable());return result?{ok:!0,status:"ok",message:`Ba\u011Flant\u0131 do\u011Fruland\u0131: ${provider.describe}`,durationMs}:{ok:!1,status:"failed",message:`Eri\u015Filemedi: ${provider.describe} \u2014 sunucu, payla\u015F\u0131m ad\u0131 ve kimlik bilgilerini kontrol edin`,durationMs}}catch(error){return{ok:!1,status:"failed",message:describeError(error),durationMs:0}}}async function testEmailProvider(service,recipient){if(!service)return{ok:!1,status:"failed",message:"E-posta sa\u011Flay\u0131c\u0131 yap\u0131land\u0131r\u0131lmam\u0131\u015F",durationMs:0};if(!service.isAvailable())return{ok:!1,status:"failed",message:"Sa\u011Flay\u0131c\u0131 kimlik bilgileriyle ba\u015Flat\u0131lamad\u0131",durationMs:0};if(!recipient)return{ok:!0,status:"ok",message:"Sa\u011Flay\u0131c\u0131 ba\u015Flat\u0131ld\u0131 \u2014 ger\xE7ek g\xF6nderim i\xE7in bir al\u0131c\u0131 adresi girin",durationMs:0};try{let{result,durationMs}=await timed(()=>service.sendEmail({to:recipient,subject:"Nucleus ba\u011Flant\u0131 testi",html:"<p>Bu e-posta, y\xF6netim panelinden yap\u0131lan ba\u011Flant\u0131 testiyle g\xF6nderildi.</p>"}));return result.success?{ok:!0,status:"ok",message:`Test e-postas\u0131 ${recipient} adresine g\xF6nderildi`,durationMs}:{ok:!1,status:"failed",message:result.error??"G\xF6nderim ba\u015Far\u0131s\u0131z",durationMs}}catch(error){return{ok:!1,status:"failed",message:describeError(error),durationMs:0}}}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=(set,status,message)=>{return set.status=status,{isSuccess:!1,message,data:null}};function createSecretsRoutes(routeConfig){let{logger:logger2,basePath,db,schemaTables,getService,resolvedOptions,getEmailService,getStorageProvider:getStorageProvider2}=routeConfig,plugin=new Elysia19({prefix:basePath});plugin.onBeforeHandle(createGodminGuard2({logger:logger2,db,schemaTables,area:"secrets management",allowedRoles:routeConfig.adminRoles}));let requireService=(set)=>{let service=getService();if(!service)return set.status=503,null;return service},actorOf=(request)=>request.headers.get("x-user-id")??void 0;return plugin.get("/slots",async({set})=>{let service=requireService(set);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."}}),plugin.get("/",async({set})=>{let service=requireService(set);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."}}),plugin.put("/:scope/:key",async({params,body,set,request})=>{let service=requireService(set);if(!service)return{isSuccess:!1,message:"Secrets management is disabled",data:null};let{scope,key:key2}=params;if(!SCOPE_PATTERN.test(scope))return fail5(set,400,"Invalid scope");if(!KEY_PATTERN.test(key2))return fail5(set,400,"Invalid key");let value=body.value;if(typeof value!=="string"||value.length===0)return fail5(set,400,"value is required");if(value.length>65536)return fail5(set,400,"value exceeds 64KB");if(body.kind==="json")try{JSON.parse(value)}catch{return fail5(set,400,"value is not valid JSON")}let saved=await service.set({scope,key:key2,value,kind:body.kind,description:body.description,actorId:actorOf(request)});if(!saved)return fail5(set,500,"Could not store the credential");return{isSuccess:!0,message:"Credential stored",data:{secret:saved}}},{params:t16.Object({scope:t16.String(),key:t16.String()}),body:t16.Object({value:t16.String(),kind:t16.Optional(t16.Union(SECRET_KINDS.map((kind)=>t16.Literal(kind)))),description:t16.Optional(t16.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."}}),plugin.post("/:scope/:key/rollback",async({params,set,request})=>{let service=requireService(set);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(set,404,"No previous value to roll back to");return{isSuccess:!0,message:"Previous credential restored",data:{secret:restored}}},{params:t16.Object({scope:t16.String(),key:t16.String()}),detail:{tags:["Secrets"],summary:"Roll back to the value replaced by the last rotation"}}),plugin.delete("/:scope/:key",async({params,set,request})=>{let service=requireService(set);if(!service)return{isSuccess:!1,message:"Secrets management is disabled",data:null};if(!await service.remove(params.scope,params.key,actorOf(request)))return fail5(set,404,"Credential not found");return{isSuccess:!0,message:"Credential removed \u2014 the config falls back to env or literal",data:null}},{params:t16.Object({scope:t16.String(),key:t16.String()}),detail:{tags:["Secrets"],summary:"Delete a stored credential",description:"Resolution falls back to the env var or literal declared in config."}}),plugin.post("/test",async({body,set})=>{let service=requireService(set);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(set,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(set,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:t16.Object({target:t16.Union([t16.Literal("oauth"),t16.Literal("email"),t16.Literal("storage")]),provider:t16.Optional(t16.String()),recipient:t16.Optional(t16.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."}}),plugin.post("/reencrypt",async({body,set})=>{let service=requireService(set);if(!service)return{isSuccess:!1,message:"Secrets management is disabled",data:null};if(!body.newMasterKey||body.newMasterKey.length<16)return fail5(set,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:t16.Object({newMasterKey:t16.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."}}),plugin.get("/discover",()=>({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."}}),plugin.post("/storage/migrate",async(ctx)=>{let body=ctx.body,build=routeConfig.buildStorageProvider;if(!build)return fail5(ctx.set,503,"Depolama yap\u0131land\u0131r\u0131lmam\u0131\u015F");let destination=build(body.to),source=build(body.to==="smb"?"local":"smb");if(!source||!destination)return fail5(ctx.set,503,"Depolama yap\u0131land\u0131r\u0131lmam\u0131\u015F");if(!await destination.isAvailable())return fail5(ctx.set,422,`Hedef depo eri\u015Filebilir de\u011Fil (${destination.describe}) \u2014 \xF6nce ba\u011Flant\u0131y\u0131 test edin`);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} dosya ta\u015F\u0131nacak`,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} dosya ta\u015F\u0131nd\u0131, ${report.skipped} zaten mevcuttu`:`${report.copied} ta\u015F\u0131nd\u0131, ${report.failed} ba\u015Far\u0131s\u0131z${report.truncated?", liste kesildi":""}`,data:{report}}},{body:t16.Object({to:t16.Union([t16.Literal("local"),t16.Literal("smb")]),dryRun:t16.Optional(t16.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."}}),plugin}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 mb=(bytes)=>`${Math.round(bytes/1048576)} MB`,source=input.configured==null?`the host passed no \`serve.maxRequestBodySize\`, so Bun's default of ${mb(134217728)} applies`:`the host passed ${mb(input.configured)}`;return`Uploads are configured up to ${mb(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: new Elysia({ serve: { maxRequestBodySize: ${input.required} } }).`}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 mb=(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 ${mb(input.required)}, but this container is capped at ${mb(input.cgroupLimit)} and an upload is held in memory twice on top of the ~${mb(APP_BASELINE_BYTES)} `+`the app already holds \u2014 it needs ~${mb(needed)}. The kernel will kill the process mid-upload. Fix: raise the container's memory limit to at least ${mb(needed)}, or lower storage.maxFileSizeBytes to about ${mb(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 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(),eq32=trimmed.indexOf("=");if(eq32>0)out[trimmed.slice(0,eq32)]=trimmed.slice(eq32+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((value,key2)=>{out[key2]=SENSITIVE_HEADER_NAMES.has(key2.toLowerCase())?"[REDACTED]":value}),out}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_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();import{eq as eq32}from"drizzle-orm";import{Elysia as Elysia20,t as t17}from"elysia";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 checkRoute=new Elysia20;return checkRoute.get("/tenants/check-subdomain/:subdomain",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((t18)=>t18.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(eq32(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:t17.Object({subdomain:t17.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."}}),checkRoute}init_Logger2();import{eq as eq33}from"drizzle-orm";import{Elysia as Elysia21,t as t18}from"elysia";var col10=(table,name2)=>table[name2];function createManageRoutes(config){let log4=config.logger.scoped(TENANT_SUSPEND),manageRoutes=new Elysia21;return manageRoutes.get("/tenants",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((t19)=>t19.status!=="active")];return{success:!0,message:`Found ${allTenants.length} tenants`,data:{items:allTenants.map((t19)=>({id:t19.id,subdomain:t19.subdomain,schemaName:t19.schemaName,companyId:t19.companyId,companyName:t19.companyName,godAdminEmail:t19.godAdminEmail,status:t19.status,plan:t19.plan,domain:t19.domain,maxUsers:t19.maxUsers,provisionedAt:t19.provisionedAt,suspendedAt:t19.suspendedAt})),totalCount:allTenants.length}}},{detail:{tags:["Tenants"],summary:"List tenants",description:"List all tenants in the platform. Requires godmin privileges."}}),manageRoutes.get("/tenants/:id",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."}}),manageRoutes.post("/tenants/:id/suspend",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(eq33(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-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:t18.Object({reason:t18.Optional(t18.String())}),detail:{tags:["Tenants"],summary:"Suspend tenant",description:"Suspend a tenant, preventing all access. Requires godmin privileges."}}),manageRoutes.post("/tenants/:id/reactivate",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(eq33(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-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."}}),manageRoutes}var verifyGodmin=async(db,tenantRegistry,userId)=>{let usersTable=tenantRegistry.getMainContext().schemaTables.users;if(!usersTable)return!1;return(await db.select().from(usersTable).where(eq33(col10(usersTable,"id"),userId)).limit(1))[0]?.isGod===!0},getAllTenants=(registry2)=>{let schemaNames=registry2.getAllSchemaNames(),tenants=[];for(let name2 of schemaNames){let ctx=registry2.getSchemaContext(name2);if(ctx?.tenant)tenants.push(ctx.tenant)}return tenants};init_Logger2();init_utils6();import{eq as eq34}from"drizzle-orm";import{Elysia as Elysia22,t as t19}from"elysia";var col11=(table,name2)=>table[name2],ProvisionBodySchema=t19.Object({subdomain:t19.String({minLength:2,maxLength:63,pattern:"^[a-z][a-z0-9-]*$"}),companyId:t19.String({minLength:1}),companyName:t19.Optional(t19.String()),godAdminEmail:t19.String({format:"email"}),plan:t19.Optional(t19.String()),domain:t19.Optional(t19.String())});function createProvisionRoute(config){let log4=config.logger.scoped(TENANT_PROVISION),provisionRoutes=new Elysia22;return provisionRoutes.post("/tenants/provision",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"};let requestingUser=(await db.select().from(usersTable).where(eq34(col11(usersTable,"id"),requestingUserId)).limit(1))[0];if(!requestingUser||!requestingUser.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((t20)=>t20.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(eq34(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(eq34(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."}}),provisionRoutes}init_Authorization();init_Logger2();init_utils6();import{eq as eq35}from"drizzle-orm";import{Elysia as Elysia23,t as t20}from"elysia";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=t20.Object({email:t20.String({format:"email"}),password:t20.String({minLength:8}),subdomain:t20.String({minLength:2,maxLength:63,pattern:"^[a-z][a-z0-9-]*$"}),plan:t20.Optional(t20.String()),companyName:t20.Optional(t20.String())});function createSelfSignupRoute(config){let log4=config.logger.scoped(TENANT_SELF_SIGNUP),limits={...DEFAULT_SELF_SIGNUP_LIMITS,...config.selfSignupLimits},selfSignupRoutes=new Elysia23;return selfSignupRoutes.post("/tenants/self-signup",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((t21)=>t21.subdomain===subdomain))return ctx.set.status=409,{success:!1,message:"This subdomain is already taken."};try{if((await db.select().from(tenantsTable).where(eq35(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(eq35(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 userId,hashedPassword=await hashPassword(body.password),now=new Date,existingUsers=await db.select().from(usersTable).where(eq35(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(eq35(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(eq35(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(eq35(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."}}),selfSignupRoutes}function createTenantRoutes(app,config){return app.use(createCheckSubdomainRoute(config)),app.use(createProvisionRoute(config)),app.use(createSelfSignupRoute(config)),app.use(createManageRoutes(config)),app}init_Notification();init_Verification();init_adminGuard();import{Elysia as Elysia24,t as t21}from"elysia";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",routes=new Elysia24,verificationRoutes=new Elysia24({prefix:basePath});if(verificationRoutes.onBeforeHandle(({request,set})=>{if(!request.headers.get("x-user-id"))return set.status=401,{success:!1,message:"Authentication required"}}),verificationRoutes.get("/status/:entity_name/:entity_id",async({params,request,set})=>{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 set.status=403,{success:!1,message:"Forbidden"};return{success:!0,data:status}},{params:t21.Object({entity_name:t21.String(),entity_id:t21.String()})}),verificationRoutes.post("/:entity_name/:entity_id/decide",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:t21.Object({entity_name:t21.String(),entity_id:t21.String()}),body:t21.Object({decision:t21.Union([t21.Literal("approved"),t21.Literal("rejected")]),reason:t21.Optional(t21.String()),signature_id:t21.Optional(t21.String()),diff:t21.Optional(t21.Record(t21.String(),t21.Unknown()))})}),verificationRoutes.get("/pending",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)}}),verificationRoutes.get("/history/:entity_name/:entity_id",async({params,request,set})=>{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 set.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:t21.Object({entity_name:t21.String(),entity_id:t21.String()})}),verificationRoutes.post("/start",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:t21.Object({flow_id:t21.String(),entity_name:t21.String(),entity_id:t21.String()})}),verificationRoutes.post("/start-for-entity",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:t21.Object({entity_name:t21.String(),entity_id:t21.String()})}),verificationRoutes.get("/statuses/:entity_name",async({params,query,request,set})=>{if(!hasGodminRole(request))return set.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:t21.Object({entity_name:t21.String()}),query:t21.Object({status:t21.Optional(t21.String()),page:t21.Optional(t21.String()),limit:t21.Optional(t21.String())})}),routes.use(verificationRoutes),logger2.info(`[Verification] Routes registered at ${basePath}`),config.flowEndpoints?.enabled!==!1){let flowRoutes=new Elysia24({prefix:flowBasePath});flowRoutes.onBeforeHandle(createGodminGuard({db,schemaTables,logger:logger2},"verification flow management")),flowRoutes.get("/",async({query,request})=>{let{verificationService:vs}=getServicesForRequest(request);return{success:!0,data:await vs.listFlows(query.entity_name||void 0)}},{query:t21.Object({entity_name:t21.Optional(t21.String())})}),flowRoutes.get("/:flow_id",async({params,request})=>{let{verificationService:vs}=getServicesForRequest(request),result=await vs.getFlow(params.flow_id);if(!result)return{success:!1,message:"Flow not found"};return{success:!0,data:result}},{params:t21.Object({flow_id:t21.String()})}),flowRoutes.post("/",async({body,request})=>{let{verificationService:vs}=getServicesForRequest(request);return await vs.saveFlow(body)},{body:t21.Object({flow_id:t21.String(),entity_name:t21.String(),name:t21.String(),description:t21.Optional(t21.String()),trigger_on:t21.Union([t21.Literal("create"),t21.Literal("update"),t21.Literal("delete"),t21.Literal("manual")]),trigger_fields:t21.Optional(t21.Array(t21.String())),is_draft:t21.Boolean(),viewport:t21.Optional(t21.Object({x:t21.Number(),y:t21.Number(),zoom:t21.Number()})),graph:t21.Object({steps:t21.Array(t21.Any()),edges:t21.Array(t21.Any()),verifier_configs:t21.Array(t21.Any()),notification_rules:t21.Array(t21.Any()),notification_recipients:t21.Array(t21.Any()),notification_channels:t21.Array(t21.Any())})})}),flowRoutes.post("/:flow_id/publish",async({params,request})=>{let{verificationService:vs}=getServicesForRequest(request);return await vs.publishFlow(params.flow_id)},{params:t21.Object({flow_id:t21.String()})}),flowRoutes.delete("/:flow_id",async({params,request})=>{let{verificationService:vs}=getServicesForRequest(request);return await vs.deleteFlow(params.flow_id)},{params:t21.Object({flow_id:t21.String()})}),routes.use(flowRoutes),logger2.info(`[Verification] Flow routes registered at ${flowBasePath}`)}return{routes,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()}}import{Elysia as Elysia25}from"elysia";var a=`/* basic theme */
|
|
547
547
|
:root {
|
|
548
548
|
--scalar-text-decoration: underline;
|
|
549
549
|
--scalar-text-decoration-hover: underline;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nucleus-core-ts",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.975",
|
|
4
4
|
"description": "Production-ready, enterprise-grade TypeScript framework for building multi-tenant APIs",
|
|
5
5
|
"author": "Hidayet Can Özcan <hidayetcan@gmail.com>",
|
|
6
6
|
"license": "SEE LICENSE IN LICENSE",
|