libre-webui 0.24.1 → 0.26.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +36 -15
- package/backend/dist/index.d.ts.map +1 -1
- package/backend/dist/index.js +35 -4
- package/backend/dist/index.js.map +1 -1
- package/backend/dist/middleware/auth.d.ts +7 -0
- package/backend/dist/middleware/auth.d.ts.map +1 -1
- package/backend/dist/middleware/auth.js +26 -0
- package/backend/dist/middleware/auth.js.map +1 -1
- package/backend/dist/middleware/index.d.ts +20 -0
- package/backend/dist/middleware/index.d.ts.map +1 -1
- package/backend/dist/middleware/index.js +105 -2
- package/backend/dist/middleware/index.js.map +1 -1
- package/backend/dist/models/personaModel.d.ts.map +1 -1
- package/backend/dist/models/personaModel.js +2 -0
- package/backend/dist/models/personaModel.js.map +1 -1
- package/backend/dist/observability/otel.d.ts +40 -0
- package/backend/dist/observability/otel.d.ts.map +1 -0
- package/backend/dist/observability/otel.js +337 -0
- package/backend/dist/observability/otel.js.map +1 -0
- package/backend/dist/observability/requestContext.d.ts +14 -0
- package/backend/dist/observability/requestContext.d.ts.map +1 -0
- package/backend/dist/observability/requestContext.js +21 -0
- package/backend/dist/observability/requestContext.js.map +1 -0
- package/backend/dist/persistence/chatGenerationTypes.d.ts +9 -0
- package/backend/dist/persistence/chatGenerationTypes.d.ts.map +1 -1
- package/backend/dist/persistence/extensionTypes.d.ts +13 -0
- package/backend/dist/persistence/extensionTypes.d.ts.map +1 -1
- package/backend/dist/persistence/extensionTypes.js.map +1 -1
- package/backend/dist/persistence/index.d.ts +1 -1
- package/backend/dist/persistence/index.d.ts.map +1 -1
- package/backend/dist/persistence/index.js.map +1 -1
- package/backend/dist/persistence/postgresAgentFoundationMigration.d.ts +9 -0
- package/backend/dist/persistence/postgresAgentFoundationMigration.d.ts.map +1 -0
- package/backend/dist/persistence/postgresAgentFoundationMigration.js +164 -0
- package/backend/dist/persistence/postgresAgentFoundationMigration.js.map +1 -0
- package/backend/dist/persistence/postgresExtensionRepositories.d.ts.map +1 -1
- package/backend/dist/persistence/postgresExtensionRepositories.js +46 -2
- package/backend/dist/persistence/postgresExtensionRepositories.js.map +1 -1
- package/backend/dist/persistence/postgresMediaEnterpriseOpsMigration.d.ts +10 -0
- package/backend/dist/persistence/postgresMediaEnterpriseOpsMigration.d.ts.map +1 -0
- package/backend/dist/persistence/postgresMediaEnterpriseOpsMigration.js +148 -0
- package/backend/dist/persistence/postgresMediaEnterpriseOpsMigration.js.map +1 -0
- package/backend/dist/persistence/postgresMigrationRegistry.d.ts.map +1 -1
- package/backend/dist/persistence/postgresMigrationRegistry.js +12 -0
- package/backend/dist/persistence/postgresMigrationRegistry.js.map +1 -1
- package/backend/dist/persistence/postgresNotesV2Migration.d.ts +4 -0
- package/backend/dist/persistence/postgresNotesV2Migration.d.ts.map +1 -0
- package/backend/dist/persistence/postgresNotesV2Migration.js +57 -0
- package/backend/dist/persistence/postgresNotesV2Migration.js.map +1 -0
- package/backend/dist/persistence/postgresPersonalAutomationsMigration.d.ts +8 -0
- package/backend/dist/persistence/postgresPersonalAutomationsMigration.d.ts.map +1 -0
- package/backend/dist/persistence/postgresPersonalAutomationsMigration.js +98 -0
- package/backend/dist/persistence/postgresPersonalAutomationsMigration.js.map +1 -0
- package/backend/dist/persistence/postgresResourceRepositories.d.ts.map +1 -1
- package/backend/dist/persistence/postgresResourceRepositories.js +1972 -63
- package/backend/dist/persistence/postgresResourceRepositories.js.map +1 -1
- package/backend/dist/persistence/postgresSkillFilesMigration.d.ts +9 -0
- package/backend/dist/persistence/postgresSkillFilesMigration.d.ts.map +1 -0
- package/backend/dist/persistence/postgresSkillFilesMigration.js +50 -0
- package/backend/dist/persistence/postgresSkillFilesMigration.js.map +1 -0
- package/backend/dist/persistence/postgresTeamCollaborationMigration.d.ts +10 -0
- package/backend/dist/persistence/postgresTeamCollaborationMigration.d.ts.map +1 -0
- package/backend/dist/persistence/postgresTeamCollaborationMigration.js +161 -0
- package/backend/dist/persistence/postgresTeamCollaborationMigration.js.map +1 -0
- package/backend/dist/persistence/resourceTypes.d.ts +622 -3
- package/backend/dist/persistence/resourceTypes.d.ts.map +1 -1
- package/backend/dist/persistence/resourceTypes.js.map +1 -1
- package/backend/dist/persistence/sqliteExtensionRepositories.d.ts.map +1 -1
- package/backend/dist/persistence/sqliteExtensionRepositories.js +26 -4
- package/backend/dist/persistence/sqliteExtensionRepositories.js.map +1 -1
- package/backend/dist/persistence/sqliteMigrations.d.ts +10 -0
- package/backend/dist/persistence/sqliteMigrations.d.ts.map +1 -1
- package/backend/dist/persistence/sqliteMigrations.js +1396 -48
- package/backend/dist/persistence/sqliteMigrations.js.map +1 -1
- package/backend/dist/persistence/sqliteResourceRepositories.d.ts.map +1 -1
- package/backend/dist/persistence/sqliteResourceRepositories.js +1680 -58
- package/backend/dist/persistence/sqliteResourceRepositories.js.map +1 -1
- package/backend/dist/persistence/sqliteToPostgresMigration.d.ts.map +1 -1
- package/backend/dist/persistence/sqliteToPostgresMigration.js +297 -2
- package/backend/dist/persistence/sqliteToPostgresMigration.js.map +1 -1
- package/backend/dist/platform/jobs/domainJobContracts.d.ts +11 -0
- package/backend/dist/platform/jobs/domainJobContracts.d.ts.map +1 -1
- package/backend/dist/platform/jobs/domainJobContracts.js +11 -0
- package/backend/dist/platform/jobs/domainJobContracts.js.map +1 -1
- package/backend/dist/platform/jobs/domainJobHandlers.d.ts.map +1 -1
- package/backend/dist/platform/jobs/domainJobHandlers.js +409 -2
- package/backend/dist/platform/jobs/domainJobHandlers.js.map +1 -1
- package/backend/dist/platform/jobs/embeddedDurableJobWorker.d.ts.map +1 -1
- package/backend/dist/platform/jobs/embeddedDurableJobWorker.js +4 -2
- package/backend/dist/platform/jobs/embeddedDurableJobWorker.js.map +1 -1
- package/backend/dist/platform/jobs/postgresDurableJobWorker.d.ts.map +1 -1
- package/backend/dist/platform/jobs/postgresDurableJobWorker.js +4 -2
- package/backend/dist/platform/jobs/postgresDurableJobWorker.js.map +1 -1
- package/backend/dist/platform/storage/pgVectorStore.d.ts +2 -1
- package/backend/dist/platform/storage/pgVectorStore.d.ts.map +1 -1
- package/backend/dist/platform/storage/pgVectorStore.js +51 -0
- package/backend/dist/platform/storage/pgVectorStore.js.map +1 -1
- package/backend/dist/platform/storage/platformDomainRepositories.d.ts +8 -2
- package/backend/dist/platform/storage/platformDomainRepositories.d.ts.map +1 -1
- package/backend/dist/platform/storage/postgresPlatformDomainRepositories.d.ts.map +1 -1
- package/backend/dist/platform/storage/postgresPlatformDomainRepositories.js +25 -4
- package/backend/dist/platform/storage/postgresPlatformDomainRepositories.js.map +1 -1
- package/backend/dist/platform/storage/sqliteEncryptedVectorStore.d.ts +2 -1
- package/backend/dist/platform/storage/sqliteEncryptedVectorStore.d.ts.map +1 -1
- package/backend/dist/platform/storage/sqliteEncryptedVectorStore.js +35 -0
- package/backend/dist/platform/storage/sqliteEncryptedVectorStore.js.map +1 -1
- package/backend/dist/platform/storage/sqlitePlatformDomainRepositories.d.ts.map +1 -1
- package/backend/dist/platform/storage/sqlitePlatformDomainRepositories.js +26 -3
- package/backend/dist/platform/storage/sqlitePlatformDomainRepositories.js.map +1 -1
- package/backend/dist/platform/storage/vectorStore.d.ts +14 -0
- package/backend/dist/platform/storage/vectorStore.d.ts.map +1 -1
- package/backend/dist/platform/storage/vectorStore.js.map +1 -1
- package/backend/dist/routes/access.d.ts.map +1 -1
- package/backend/dist/routes/access.js +84 -3
- package/backend/dist/routes/access.js.map +1 -1
- package/backend/dist/routes/automations.d.ts +3 -0
- package/backend/dist/routes/automations.d.ts.map +1 -0
- package/backend/dist/routes/automations.js +232 -0
- package/backend/dist/routes/automations.js.map +1 -0
- package/backend/dist/routes/calendar.d.ts +3 -0
- package/backend/dist/routes/calendar.d.ts.map +1 -0
- package/backend/dist/routes/calendar.js +309 -0
- package/backend/dist/routes/calendar.js.map +1 -0
- package/backend/dist/routes/channels.d.ts +3 -0
- package/backend/dist/routes/channels.d.ts.map +1 -0
- package/backend/dist/routes/channels.js +447 -0
- package/backend/dist/routes/channels.js.map +1 -0
- package/backend/dist/routes/chat.d.ts.map +1 -1
- package/backend/dist/routes/chat.js +237 -10
- package/backend/dist/routes/chat.js.map +1 -1
- package/backend/dist/routes/costs.d.ts +3 -0
- package/backend/dist/routes/costs.d.ts.map +1 -0
- package/backend/dist/routes/costs.js +191 -0
- package/backend/dist/routes/costs.js.map +1 -0
- package/backend/dist/routes/documents.d.ts.map +1 -1
- package/backend/dist/routes/documents.js +64 -21
- package/backend/dist/routes/documents.js.map +1 -1
- package/backend/dist/routes/evaluations.d.ts +3 -0
- package/backend/dist/routes/evaluations.d.ts.map +1 -0
- package/backend/dist/routes/evaluations.js +352 -0
- package/backend/dist/routes/evaluations.js.map +1 -0
- package/backend/dist/routes/imageGen.d.ts.map +1 -1
- package/backend/dist/routes/imageGen.js +2 -1
- package/backend/dist/routes/imageGen.js.map +1 -1
- package/backend/dist/routes/media.d.ts.map +1 -1
- package/backend/dist/routes/media.js +228 -7
- package/backend/dist/routes/media.js.map +1 -1
- package/backend/dist/routes/notes.d.ts.map +1 -1
- package/backend/dist/routes/notes.js +149 -36
- package/backend/dist/routes/notes.js.map +1 -1
- package/backend/dist/routes/notifications.d.ts +3 -0
- package/backend/dist/routes/notifications.d.ts.map +1 -0
- package/backend/dist/routes/notifications.js +213 -0
- package/backend/dist/routes/notifications.js.map +1 -0
- package/backend/dist/routes/ollama.d.ts.map +1 -1
- package/backend/dist/routes/ollama.js +4 -1
- package/backend/dist/routes/ollama.js.map +1 -1
- package/backend/dist/routes/openaiCompat.d.ts +3 -0
- package/backend/dist/routes/openaiCompat.d.ts.map +1 -0
- package/backend/dist/routes/openaiCompat.js +314 -0
- package/backend/dist/routes/openaiCompat.js.map +1 -0
- package/backend/dist/routes/personas.js +1 -1
- package/backend/dist/routes/personas.js.map +1 -1
- package/backend/dist/routes/prompts.d.ts +3 -0
- package/backend/dist/routes/prompts.d.ts.map +1 -0
- package/backend/dist/routes/prompts.js +173 -0
- package/backend/dist/routes/prompts.js.map +1 -0
- package/backend/dist/routes/search.d.ts.map +1 -1
- package/backend/dist/routes/search.js +34 -4
- package/backend/dist/routes/search.js.map +1 -1
- package/backend/dist/routes/skills.d.ts +3 -0
- package/backend/dist/routes/skills.d.ts.map +1 -0
- package/backend/dist/routes/skills.js +246 -0
- package/backend/dist/routes/skills.js.map +1 -0
- package/backend/dist/routes/stt.d.ts.map +1 -1
- package/backend/dist/routes/stt.js +2 -1
- package/backend/dist/routes/stt.js.map +1 -1
- package/backend/dist/routes/tools.d.ts +3 -0
- package/backend/dist/routes/tools.d.ts.map +1 -0
- package/backend/dist/routes/tools.js +359 -0
- package/backend/dist/routes/tools.js.map +1 -0
- package/backend/dist/routes/tts.d.ts.map +1 -1
- package/backend/dist/routes/tts.js +63 -8
- package/backend/dist/routes/tts.js.map +1 -1
- package/backend/dist/routes/voiceAccess.d.ts +3 -0
- package/backend/dist/routes/voiceAccess.d.ts.map +1 -0
- package/backend/dist/routes/voiceAccess.js +102 -0
- package/backend/dist/routes/voiceAccess.js.map +1 -0
- package/backend/dist/services/apiTokenService.d.ts +2 -2
- package/backend/dist/services/apiTokenService.d.ts.map +1 -1
- package/backend/dist/services/apiTokenService.js +7 -0
- package/backend/dist/services/apiTokenService.js.map +1 -1
- package/backend/dist/services/authorizationService.d.ts +8 -4
- package/backend/dist/services/authorizationService.d.ts.map +1 -1
- package/backend/dist/services/authorizationService.js +31 -1
- package/backend/dist/services/authorizationService.js.map +1 -1
- package/backend/dist/services/automationSchedulerService.d.ts +28 -0
- package/backend/dist/services/automationSchedulerService.d.ts.map +1 -0
- package/backend/dist/services/automationSchedulerService.js +221 -0
- package/backend/dist/services/automationSchedulerService.js.map +1 -0
- package/backend/dist/services/automationService.d.ts +56 -0
- package/backend/dist/services/automationService.d.ts.map +1 -0
- package/backend/dist/services/automationService.js +247 -0
- package/backend/dist/services/automationService.js.map +1 -0
- package/backend/dist/services/builtinToolsService.d.ts +20 -0
- package/backend/dist/services/builtinToolsService.d.ts.map +1 -0
- package/backend/dist/services/builtinToolsService.js +661 -0
- package/backend/dist/services/builtinToolsService.js.map +1 -0
- package/backend/dist/services/calendarService.d.ts +74 -0
- package/backend/dist/services/calendarService.d.ts.map +1 -0
- package/backend/dist/services/calendarService.js +339 -0
- package/backend/dist/services/calendarService.js.map +1 -0
- package/backend/dist/services/channelService.d.ts +195 -0
- package/backend/dist/services/channelService.d.ts.map +1 -0
- package/backend/dist/services/channelService.js +1057 -0
- package/backend/dist/services/channelService.js.map +1 -0
- package/backend/dist/services/chatGenerationService.d.ts.map +1 -1
- package/backend/dist/services/chatGenerationService.js +7 -1
- package/backend/dist/services/chatGenerationService.js.map +1 -1
- package/backend/dist/services/chatRequestService.d.ts.map +1 -1
- package/backend/dist/services/chatRequestService.js +29 -1
- package/backend/dist/services/chatRequestService.js.map +1 -1
- package/backend/dist/services/chatService.d.ts +62 -1
- package/backend/dist/services/chatService.d.ts.map +1 -1
- package/backend/dist/services/chatService.js +242 -1
- package/backend/dist/services/chatService.js.map +1 -1
- package/backend/dist/services/chatToolRuntimeService.d.ts +55 -0
- package/backend/dist/services/chatToolRuntimeService.d.ts.map +1 -0
- package/backend/dist/services/chatToolRuntimeService.js +389 -0
- package/backend/dist/services/chatToolRuntimeService.js.map +1 -0
- package/backend/dist/services/costGovernanceService.d.ts +102 -0
- package/backend/dist/services/costGovernanceService.d.ts.map +1 -0
- package/backend/dist/services/costGovernanceService.js +463 -0
- package/backend/dist/services/costGovernanceService.js.map +1 -0
- package/backend/dist/services/dataArchiveService.d.ts +6 -0
- package/backend/dist/services/dataArchiveService.d.ts.map +1 -1
- package/backend/dist/services/dataArchiveService.js +23 -4
- package/backend/dist/services/dataArchiveService.js.map +1 -1
- package/backend/dist/services/documentService.d.ts +58 -1
- package/backend/dist/services/documentService.d.ts.map +1 -1
- package/backend/dist/services/documentService.js +289 -84
- package/backend/dist/services/documentService.js.map +1 -1
- package/backend/dist/services/durableChatGenerationService.d.ts +9 -0
- package/backend/dist/services/durableChatGenerationService.d.ts.map +1 -1
- package/backend/dist/services/durableChatGenerationService.js +160 -13
- package/backend/dist/services/durableChatGenerationService.js.map +1 -1
- package/backend/dist/services/evaluationService.d.ts +111 -0
- package/backend/dist/services/evaluationService.d.ts.map +1 -0
- package/backend/dist/services/evaluationService.js +403 -0
- package/backend/dist/services/evaluationService.js.map +1 -0
- package/backend/dist/services/galleryService.d.ts +6 -0
- package/backend/dist/services/galleryService.d.ts.map +1 -1
- package/backend/dist/services/galleryService.js +26 -0
- package/backend/dist/services/galleryService.js.map +1 -1
- package/backend/dist/services/mcpClientService.d.ts +29 -0
- package/backend/dist/services/mcpClientService.d.ts.map +1 -0
- package/backend/dist/services/mcpClientService.js +189 -0
- package/backend/dist/services/mcpClientService.js.map +1 -0
- package/backend/dist/services/noteService.d.ts +71 -0
- package/backend/dist/services/noteService.d.ts.map +1 -0
- package/backend/dist/services/noteService.js +397 -0
- package/backend/dist/services/noteService.js.map +1 -0
- package/backend/dist/services/notificationService.d.ts +55 -0
- package/backend/dist/services/notificationService.d.ts.map +1 -0
- package/backend/dist/services/notificationService.js +308 -0
- package/backend/dist/services/notificationService.js.map +1 -0
- package/backend/dist/services/ollamaService.d.ts.map +1 -1
- package/backend/dist/services/ollamaService.js +3 -1
- package/backend/dist/services/ollamaService.js.map +1 -1
- package/backend/dist/services/openApiToolService.d.ts +38 -0
- package/backend/dist/services/openApiToolService.d.ts.map +1 -0
- package/backend/dist/services/openApiToolService.js +254 -0
- package/backend/dist/services/openApiToolService.js.map +1 -0
- package/backend/dist/services/personaService.d.ts +18 -2
- package/backend/dist/services/personaService.d.ts.map +1 -1
- package/backend/dist/services/personaService.js +113 -4
- package/backend/dist/services/personaService.js.map +1 -1
- package/backend/dist/services/pluginImageGenerationService.d.ts +19 -0
- package/backend/dist/services/pluginImageGenerationService.d.ts.map +1 -1
- package/backend/dist/services/pluginImageGenerationService.js +121 -0
- package/backend/dist/services/pluginImageGenerationService.js.map +1 -1
- package/backend/dist/services/pluginService.d.ts +7 -0
- package/backend/dist/services/pluginService.d.ts.map +1 -1
- package/backend/dist/services/pluginService.js +8 -2
- package/backend/dist/services/pluginService.js.map +1 -1
- package/backend/dist/services/promptService.d.ts +74 -0
- package/backend/dist/services/promptService.d.ts.map +1 -0
- package/backend/dist/services/promptService.js +513 -0
- package/backend/dist/services/promptService.js.map +1 -0
- package/backend/dist/services/resourceGrantService.d.ts.map +1 -1
- package/backend/dist/services/resourceGrantService.js +62 -17
- package/backend/dist/services/resourceGrantService.js.map +1 -1
- package/backend/dist/services/searchService.d.ts +29 -0
- package/backend/dist/services/searchService.d.ts.map +1 -0
- package/backend/dist/services/searchService.js +0 -0
- package/backend/dist/services/searchService.js.map +1 -0
- package/backend/dist/services/securityAuditService.d.ts.map +1 -1
- package/backend/dist/services/securityAuditService.js +2 -1
- package/backend/dist/services/securityAuditService.js.map +1 -1
- package/backend/dist/services/sharedResourceAccess.d.ts +25 -0
- package/backend/dist/services/sharedResourceAccess.d.ts.map +1 -0
- package/backend/dist/services/sharedResourceAccess.js +59 -0
- package/backend/dist/services/sharedResourceAccess.js.map +1 -0
- package/backend/dist/services/skillService.d.ts +137 -0
- package/backend/dist/services/skillService.d.ts.map +1 -0
- package/backend/dist/services/skillService.js +852 -0
- package/backend/dist/services/skillService.js.map +1 -0
- package/backend/dist/services/titleGenerationService.d.ts.map +1 -1
- package/backend/dist/services/titleGenerationService.js +5 -1
- package/backend/dist/services/titleGenerationService.js.map +1 -1
- package/backend/dist/services/toolAccessService.d.ts +9 -0
- package/backend/dist/services/toolAccessService.d.ts.map +1 -0
- package/backend/dist/services/toolAccessService.js +60 -0
- package/backend/dist/services/toolAccessService.js.map +1 -0
- package/backend/dist/services/toolApprovalService.d.ts +34 -0
- package/backend/dist/services/toolApprovalService.d.ts.map +1 -0
- package/backend/dist/services/toolApprovalService.js +188 -0
- package/backend/dist/services/toolApprovalService.js.map +1 -0
- package/backend/dist/services/toolGatewayService.d.ts +53 -0
- package/backend/dist/services/toolGatewayService.d.ts.map +1 -0
- package/backend/dist/services/toolGatewayService.js +249 -0
- package/backend/dist/services/toolGatewayService.js.map +1 -0
- package/backend/dist/services/toolServerService.d.ts +57 -0
- package/backend/dist/services/toolServerService.d.ts.map +1 -0
- package/backend/dist/services/toolServerService.js +521 -0
- package/backend/dist/services/toolServerService.js.map +1 -0
- package/backend/dist/services/voiceAccessService.d.ts +10 -0
- package/backend/dist/services/voiceAccessService.d.ts.map +1 -0
- package/backend/dist/services/voiceAccessService.js +74 -0
- package/backend/dist/services/voiceAccessService.js.map +1 -0
- package/backend/dist/services/voiceProfileService.d.ts +20 -0
- package/backend/dist/services/voiceProfileService.d.ts.map +1 -1
- package/backend/dist/services/voiceProfileService.js +60 -0
- package/backend/dist/services/voiceProfileService.js.map +1 -1
- package/backend/dist/services/webSearchPlanService.d.ts +18 -0
- package/backend/dist/services/webSearchPlanService.d.ts.map +1 -0
- package/backend/dist/services/webSearchPlanService.js +160 -0
- package/backend/dist/services/webSearchPlanService.js.map +1 -0
- package/backend/dist/services/webSearchService.d.ts +9 -3
- package/backend/dist/services/webSearchService.d.ts.map +1 -1
- package/backend/dist/services/webSearchService.js +8 -2
- package/backend/dist/services/webSearchService.js.map +1 -1
- package/backend/dist/storage.d.ts +34 -1
- package/backend/dist/storage.d.ts.map +1 -1
- package/backend/dist/storage.js +164 -1
- package/backend/dist/storage.js.map +1 -1
- package/backend/dist/storageMappers.d.ts +2 -2
- package/backend/dist/storageMappers.d.ts.map +1 -1
- package/backend/dist/storageMappers.js.map +1 -1
- package/backend/dist/types/index.d.ts +269 -2
- package/backend/dist/types/index.d.ts.map +1 -1
- package/backend/dist/types/index.js.map +1 -1
- package/backend/dist/types/tools.d.ts +137 -0
- package/backend/dist/types/tools.d.ts.map +1 -0
- package/backend/dist/types/tools.js +25 -0
- package/backend/dist/types/tools.js.map +1 -0
- package/backend/dist/utils/automationSchedule.d.ts +26 -0
- package/backend/dist/utils/automationSchedule.d.ts.map +1 -0
- package/backend/dist/utils/automationSchedule.js +231 -0
- package/backend/dist/utils/automationSchedule.js.map +1 -0
- package/backend/dist/utils/chatDocumentContext.d.ts +26 -1
- package/backend/dist/utils/chatDocumentContext.d.ts.map +1 -1
- package/backend/dist/utils/chatDocumentContext.js +87 -18
- package/backend/dist/utils/chatDocumentContext.js.map +1 -1
- package/backend/dist/utils/documentExtraction.d.ts +69 -0
- package/backend/dist/utils/documentExtraction.d.ts.map +1 -0
- package/backend/dist/utils/documentExtraction.js +545 -0
- package/backend/dist/utils/documentExtraction.js.map +1 -0
- package/backend/dist/utils/hybridRetrieval.d.ts +34 -0
- package/backend/dist/utils/hybridRetrieval.d.ts.map +1 -0
- package/backend/dist/utils/hybridRetrieval.js +119 -0
- package/backend/dist/utils/hybridRetrieval.js.map +1 -0
- package/backend/dist/utils/icsCalendar.d.ts +27 -0
- package/backend/dist/utils/icsCalendar.d.ts.map +1 -0
- package/backend/dist/utils/icsCalendar.js +313 -0
- package/backend/dist/utils/icsCalendar.js.map +1 -0
- package/backend/dist/utils/imageEditUpload.d.ts +29 -0
- package/backend/dist/utils/imageEditUpload.d.ts.map +1 -0
- package/backend/dist/utils/imageEditUpload.js +82 -0
- package/backend/dist/utils/imageEditUpload.js.map +1 -0
- package/backend/dist/utils/logRedaction.d.ts +2 -0
- package/backend/dist/utils/logRedaction.d.ts.map +1 -0
- package/backend/dist/utils/logRedaction.js +66 -0
- package/backend/dist/utils/logRedaction.js.map +1 -0
- package/backend/dist/utils/logger.d.ts +3 -0
- package/backend/dist/utils/logger.d.ts.map +1 -1
- package/backend/dist/utils/logger.js +63 -20
- package/backend/dist/utils/logger.js.map +1 -1
- package/backend/dist/utils/pluginChatAdapter.d.ts +5 -1
- package/backend/dist/utils/pluginChatAdapter.d.ts.map +1 -1
- package/backend/dist/utils/pluginChatAdapter.js +83 -0
- package/backend/dist/utils/pluginChatAdapter.js.map +1 -1
- package/backend/dist/utils/pluginDefinitionTrust.js +1 -1
- package/backend/dist/utils/resourceLimits.d.ts +68 -2
- package/backend/dist/utils/resourceLimits.d.ts.map +1 -1
- package/backend/dist/utils/resourceLimits.js +66 -0
- package/backend/dist/utils/resourceLimits.js.map +1 -1
- package/backend/dist/utils/toolEgress.d.ts +22 -0
- package/backend/dist/utils/toolEgress.d.ts.map +1 -0
- package/backend/dist/utils/toolEgress.js +157 -0
- package/backend/dist/utils/toolEgress.js.map +1 -0
- package/backend/dist/utils/websocketMessages.d.ts +6 -0
- package/backend/dist/utils/websocketMessages.d.ts.map +1 -1
- package/backend/dist/utils/websocketMessages.js +8 -0
- package/backend/dist/utils/websocketMessages.js.map +1 -1
- package/backend/dist/utils/zipArchive.d.ts +32 -0
- package/backend/dist/utils/zipArchive.d.ts.map +1 -0
- package/backend/dist/utils/zipArchive.js +171 -0
- package/backend/dist/utils/zipArchive.js.map +1 -0
- package/backend/dist/websocketServer.d.ts.map +1 -1
- package/backend/dist/websocketServer.js +150 -37
- package/backend/dist/websocketServer.js.map +1 -1
- package/backend/package.json +1 -1
- package/frontend/dist/artifact-runtime/.build-fingerprint +1 -1
- package/frontend/dist/assets/index-BPsH9FII.js +80 -0
- package/frontend/dist/assets/index-BtXF0Oxa.css +1 -0
- package/frontend/dist/index.html +18 -18
- package/frontend/dist/js/{ArtifactContainer-B-hs0F5K.js → ArtifactContainer-CIoFoMv8.js} +1 -1
- package/frontend/dist/js/{ArtifactDemoPage-QFtJw-u4.js → ArtifactDemoPage-D0JEIAbZ.js} +1 -1
- package/frontend/dist/js/{ArtifactSandboxFrame-S9g_q47-.js → ArtifactSandboxFrame-REazyIrx.js} +2 -2
- package/frontend/dist/js/ArtifactSlideOutPanel-A-i94ghF.js +1 -0
- package/frontend/dist/js/AutomationsPage-Dow_lbIf.js +1 -0
- package/frontend/dist/js/{Button-BPf_pOxH.js → Button-C0r3J9G_.js} +1 -1
- package/frontend/dist/js/CalendarPage-DLjasZOd.js +1 -0
- package/frontend/dist/js/ChannelsPage-C8hvTD7J.js +1 -0
- package/frontend/dist/js/ChatPage-BAgBHUES.js +16 -0
- package/frontend/dist/js/EvaluationsPage-Bm8FRoeX.js +2 -0
- package/frontend/dist/js/FirstTimeSetup-BxJapNul.js +1 -0
- package/frontend/dist/js/GalleryPage-DHz9DkK7.js +1 -0
- package/frontend/dist/js/HomePage-BqNy-FWX.js +1 -0
- package/frontend/dist/js/LibreClawPage-BDzDILjY.js +1 -0
- package/frontend/dist/js/{ModelManagerModals-DBmm_slu.js → ModelManagerModals-DX9eT5r2.js} +1 -1
- package/frontend/dist/js/ModelSelector-ol8mK2ja.js +1 -0
- package/frontend/dist/js/NotesPage-DzHEx2O4.js +3 -0
- package/frontend/dist/js/{OptimizedSyntaxHighlighter-Dz7D1LPB.js → OptimizedSyntaxHighlighter-CZNPwN0i.js} +1 -1
- package/frontend/dist/js/PersonasPage-M_WKH2a9.js +13 -0
- package/frontend/dist/js/PluginUsagePage-Ctoka6JZ.js +1 -0
- package/frontend/dist/js/RichMessageContent-BqgafH4p.js +3 -0
- package/frontend/dist/js/SettingsModal-R9EDadAV.js +19 -0
- package/frontend/dist/js/{SettingsToggle-woG_WebF.js → SettingsToggle-BIVyfGjl.js} +1 -1
- package/frontend/dist/js/{SystemPage-Xr7XJv8c.js → SystemPage-aOkrSuY7.js} +1 -1
- package/frontend/dist/js/UserManagementPage-BOuaX3ay.js +1 -0
- package/frontend/dist/js/WorkPage-DxZISerQ.js +6 -0
- package/frontend/dist/js/api-6bSMtSNh.js +14 -0
- package/frontend/dist/js/{appStore-DXlmTjIC.js → appStore-CdObhQBb.js} +2 -2
- package/frontend/dist/js/ar-DT_VLBO3.js +2 -0
- package/frontend/dist/js/{authStore-B97lJ5Ee.js → authStore-DbqqrfZv.js} +3 -3
- package/frontend/dist/js/{babel-D24oDI9y.js → babel-DSqqk5kt.js} +1 -1
- package/frontend/dist/js/bn-BrvEqhGA.js +2 -0
- package/frontend/dist/js/calendarDates-Co3o_05Y.js +1 -0
- package/frontend/dist/js/chatStore-DbZPJNsu.js +3 -0
- package/frontend/dist/js/{config-CmEh9o8U.js → config-8KdgjtEx.js} +1 -1
- package/frontend/dist/js/cs-BthnM_YI.js +2 -0
- package/frontend/dist/js/da-DCth-6_j.js +2 -0
- package/frontend/dist/js/de-BBWt0gJ_.js +2 -0
- package/frontend/dist/js/es-DuslOPqM.js +2 -0
- package/frontend/dist/js/{estree-CZO8RS52.js → estree-BU5-LV_1.js} +7 -7
- package/frontend/dist/js/evaluationsApi-CwZJsgeG.js +1 -0
- package/frontend/dist/js/fr-DZAeg46S.js +2 -0
- package/frontend/dist/js/hi-ijDSiY77.js +2 -0
- package/frontend/dist/js/{html-C31pxhBG.js → html-DeoAy2bo.js} +10 -10
- package/frontend/dist/js/id-BS6qS0oq.js +2 -0
- package/frontend/dist/js/is-CO2PMDte.js +2 -0
- package/frontend/dist/js/it-ChhxT4hs.js +2 -0
- package/frontend/dist/js/ja-cyeqUeJN.js +2 -0
- package/frontend/dist/js/ko-CfqHTLXj.js +2 -0
- package/frontend/dist/js/{markdown-C1CEqXpL.js → markdown-DOLsYkBY.js} +6 -6
- package/frontend/dist/js/markdown-core-BK7WVOb7.js +32 -0
- package/frontend/dist/js/{markdown-math-DK846gXU.js → markdown-math-e1ByS3KY.js} +5 -5
- package/frontend/dist/js/{messageCodeStyles-BqQ6kOSU.js → messageCodeStyles-DhVgizF-.js} +2 -2
- package/frontend/dist/js/ms-DZ5pACe1.js +2 -0
- package/frontend/dist/js/nl-BsRsnOVp.js +2 -0
- package/frontend/dist/js/pl-DXSTAW4M.js +2 -0
- package/frontend/dist/js/{postcss-Bx9CTcIc.js → postcss-Bg15EcFE.js} +1 -1
- package/frontend/dist/js/pt-DmDx0JxB.js +2 -0
- package/frontend/dist/js/{react-vendor-C5yShNlM.js → react-vendor-BG_HPV6X.js} +1 -1
- package/frontend/dist/js/router-vendor-CbexJIsv.js +1 -0
- package/frontend/dist/js/ru-d2g8EWfY.js +2 -0
- package/frontend/dist/js/{standalone-Dz0pkxn0.js → standalone-BO5pzI8_.js} +5 -5
- package/frontend/dist/js/sv-CIhYUTVQ.js +2 -0
- package/frontend/dist/js/{syntax-highlight-3EEX6CGL.js → syntax-highlight-DcR9Vhb7.js} +3 -3
- package/frontend/dist/js/th-B3wSxanB.js +2 -0
- package/frontend/dist/js/tr-R4UGgPOY.js +2 -0
- package/frontend/dist/js/{typescript-DeXPhp90.js → typescript-6GJ8APmn.js} +2 -2
- package/frontend/dist/js/ui-CV0LYmkM.js +2 -0
- package/frontend/dist/js/ui-vendor-CWjIBW-z.js +177 -0
- package/frontend/dist/js/uk-BzUF4Vkt.js +2 -0
- package/frontend/dist/js/{useTranslation-Bho4HCyg.js → useTranslation-5b5UhAc9.js} +1 -1
- package/frontend/dist/js/{utils-D9W1Lnkn.js → utils-BGrdX9ie.js} +1 -1
- package/frontend/dist/js/{utils-vendor-DWh39_3x.js → utils-vendor-9bQ62WeD.js} +1 -1
- package/frontend/dist/js/vi-CKc6Fg4h.js +2 -0
- package/frontend/dist/js/{workApi-C4QtuhcE.js → workApi-BruWTlHM.js} +2 -2
- package/frontend/dist/js/{yaml-DioTxVLA.js → yaml-DUeWcOOH.js} +5 -5
- package/frontend/dist/js/zh-CXL-kDe8.js +2 -0
- package/package.json +2 -2
- package/plugins/openai.json +52 -9
- package/frontend/dist/assets/index-CDQ12KBq.js +0 -34
- package/frontend/dist/assets/index-DkFWzmFD.css +0 -1
- package/frontend/dist/js/ArtifactSlideOutPanel-D0UOo8RW.js +0 -1
- package/frontend/dist/js/ChatPage-fdd8500b.js +0 -23
- package/frontend/dist/js/FirstTimeSetup-BTjafGOq.js +0 -1
- package/frontend/dist/js/GalleryPage-hR9AJ6b-.js +0 -1
- package/frontend/dist/js/HomePage-JvTzq-Pp.js +0 -1
- package/frontend/dist/js/LibreClawPage-CLJvtXft.js +0 -1
- package/frontend/dist/js/ModelSelector-A-viVFEc.js +0 -1
- package/frontend/dist/js/ModelsPage-C4Rm-TZF.js +0 -2
- package/frontend/dist/js/NotesPage-C7bPU_KF.js +0 -1
- package/frontend/dist/js/PersonasPage-DpvxbFzv.js +0 -13
- package/frontend/dist/js/PluginUsagePage-laF-M2X9.js +0 -1
- package/frontend/dist/js/RichMessageContent-D0mK9vRA.js +0 -3
- package/frontend/dist/js/SettingsModal-DNEQ5sTt.js +0 -1
- package/frontend/dist/js/UserManagementPage-DGmbGTC2.js +0 -1
- package/frontend/dist/js/WorkPage-DNKFD9gS.js +0 -6
- package/frontend/dist/js/api-DGVZHSkS.js +0 -14
- package/frontend/dist/js/ar-DW79oSyN.js +0 -2
- package/frontend/dist/js/bn-BEAmE7VH.js +0 -2
- package/frontend/dist/js/chatStore-zilBUd1p.js +0 -3
- package/frontend/dist/js/cs-irpRMzRo.js +0 -2
- package/frontend/dist/js/da-cgilTVxc.js +0 -2
- package/frontend/dist/js/de-BU-eH3gU.js +0 -2
- package/frontend/dist/js/es-B2LRpZ0p.js +0 -2
- package/frontend/dist/js/fr-Cd5JM-oQ.js +0 -2
- package/frontend/dist/js/hi-CvOhdtiK.js +0 -2
- package/frontend/dist/js/id-BtRK5nR_.js +0 -2
- package/frontend/dist/js/is-CnEqwgRF.js +0 -2
- package/frontend/dist/js/it-CI6WjId6.js +0 -2
- package/frontend/dist/js/ja-B6gFOHi6.js +0 -2
- package/frontend/dist/js/ko-DuAhnmMf.js +0 -2
- package/frontend/dist/js/markdown-core-BmPPZiuD.js +0 -31
- package/frontend/dist/js/ms-C8BIP7MZ.js +0 -2
- package/frontend/dist/js/nl-DnEdspER.js +0 -2
- package/frontend/dist/js/pl-Ddyyvbse.js +0 -2
- package/frontend/dist/js/pt-BYuzm70F.js +0 -2
- package/frontend/dist/js/router-vendor-CbPDks39.js +0 -1
- package/frontend/dist/js/ru-CmSPFxPX.js +0 -2
- package/frontend/dist/js/sv-Dd6SQ9f1.js +0 -2
- package/frontend/dist/js/th-CpH8745B.js +0 -2
- package/frontend/dist/js/tr-DuVD75iJ.js +0 -2
- package/frontend/dist/js/ui-CO0DPqrI.js +0 -2
- package/frontend/dist/js/ui-vendor-mqjSfAaS.js +0 -177
- package/frontend/dist/js/uk-B0MU-M8k.js +0 -2
- package/frontend/dist/js/vi-BLmFNp_z.js +0 -2
- package/frontend/dist/js/zh-BQu0TLhw.js +0 -2
|
@@ -34,9 +34,168 @@ const namedResource = (row) => ({
|
|
|
34
34
|
});
|
|
35
35
|
const note = (row) => ({
|
|
36
36
|
...row,
|
|
37
|
+
pinned: number(row.pinned ?? 0, 'note pinned'),
|
|
37
38
|
created_at: number(row.created_at, 'note created_at'),
|
|
38
39
|
updated_at: number(row.updated_at, 'note updated_at'),
|
|
39
40
|
});
|
|
41
|
+
const noteRevision = (row) => ({
|
|
42
|
+
...row,
|
|
43
|
+
created_at: number(row.created_at, 'note revision created_at'),
|
|
44
|
+
});
|
|
45
|
+
const noteAttachment = (row) => ({
|
|
46
|
+
...row,
|
|
47
|
+
size: number(row.size, 'note attachment size'),
|
|
48
|
+
created_at: number(row.created_at, 'note attachment created_at'),
|
|
49
|
+
});
|
|
50
|
+
const calendarEvent = (row) => ({
|
|
51
|
+
...row,
|
|
52
|
+
start_at: number(row.start_at, 'calendar event start_at'),
|
|
53
|
+
end_at: row.end_at === null ? null : number(row.end_at, 'calendar event end_at'),
|
|
54
|
+
all_day: number(row.all_day, 'calendar event all_day'),
|
|
55
|
+
reminder_minutes: row.reminder_minutes === null
|
|
56
|
+
? null
|
|
57
|
+
: number(row.reminder_minutes, 'calendar event reminder_minutes'),
|
|
58
|
+
last_reminded_occurrence: row.last_reminded_occurrence === null
|
|
59
|
+
? null
|
|
60
|
+
: number(row.last_reminded_occurrence, 'calendar event last_reminded_occurrence'),
|
|
61
|
+
created_at: number(row.created_at, 'calendar event created_at'),
|
|
62
|
+
updated_at: number(row.updated_at, 'calendar event updated_at'),
|
|
63
|
+
});
|
|
64
|
+
const calendar = (row) => ({
|
|
65
|
+
...row,
|
|
66
|
+
created_at: number(row.created_at, 'calendar created_at'),
|
|
67
|
+
updated_at: number(row.updated_at, 'calendar updated_at'),
|
|
68
|
+
});
|
|
69
|
+
const channel = (row) => ({
|
|
70
|
+
...row,
|
|
71
|
+
created_at: number(row.created_at, 'channel created_at'),
|
|
72
|
+
updated_at: number(row.updated_at, 'channel updated_at'),
|
|
73
|
+
archived_at: row.archived_at === null
|
|
74
|
+
? null
|
|
75
|
+
: number(row.archived_at, 'channel archived_at'),
|
|
76
|
+
});
|
|
77
|
+
const channelMember = (row) => ({
|
|
78
|
+
...row,
|
|
79
|
+
joined_at: number(row.joined_at, 'channel member joined_at'),
|
|
80
|
+
last_read_at: number(row.last_read_at, 'channel member last_read_at'),
|
|
81
|
+
});
|
|
82
|
+
const channelMessage = (row) => ({
|
|
83
|
+
...row,
|
|
84
|
+
created_at: number(row.created_at, 'channel message created_at'),
|
|
85
|
+
updated_at: number(row.updated_at, 'channel message updated_at'),
|
|
86
|
+
edited_at: row.edited_at === null
|
|
87
|
+
? null
|
|
88
|
+
: number(row.edited_at, 'channel message edited_at'),
|
|
89
|
+
deleted_at: row.deleted_at === null
|
|
90
|
+
? null
|
|
91
|
+
: number(row.deleted_at, 'channel message deleted_at'),
|
|
92
|
+
pinned_at: row.pinned_at === null
|
|
93
|
+
? null
|
|
94
|
+
: number(row.pinned_at, 'channel message pinned_at'),
|
|
95
|
+
});
|
|
96
|
+
const channelReaction = (row) => ({
|
|
97
|
+
...row,
|
|
98
|
+
created_at: number(row.created_at, 'channel reaction created_at'),
|
|
99
|
+
});
|
|
100
|
+
const channelAttachment = (row) => ({
|
|
101
|
+
...row,
|
|
102
|
+
size: number(row.size, 'channel attachment size'),
|
|
103
|
+
created_at: number(row.created_at, 'channel attachment created_at'),
|
|
104
|
+
});
|
|
105
|
+
const notification = (row) => ({
|
|
106
|
+
...row,
|
|
107
|
+
created_at: number(row.created_at, 'notification created_at'),
|
|
108
|
+
read_at: row.read_at === null ? null : number(row.read_at, 'notification read_at'),
|
|
109
|
+
});
|
|
110
|
+
const webhookTarget = (row) => ({
|
|
111
|
+
...row,
|
|
112
|
+
enabled: number(row.enabled, 'webhook target enabled'),
|
|
113
|
+
created_at: number(row.created_at, 'webhook target created_at'),
|
|
114
|
+
updated_at: number(row.updated_at, 'webhook target updated_at'),
|
|
115
|
+
});
|
|
116
|
+
const automation = (row) => ({
|
|
117
|
+
...row,
|
|
118
|
+
next_run_at: row.next_run_at === null
|
|
119
|
+
? null
|
|
120
|
+
: number(row.next_run_at, 'automation next_run_at'),
|
|
121
|
+
last_run_at: row.last_run_at === null
|
|
122
|
+
? null
|
|
123
|
+
: number(row.last_run_at, 'automation last_run_at'),
|
|
124
|
+
created_at: number(row.created_at, 'automation created_at'),
|
|
125
|
+
updated_at: number(row.updated_at, 'automation updated_at'),
|
|
126
|
+
});
|
|
127
|
+
const automationRun = (row) => ({
|
|
128
|
+
...row,
|
|
129
|
+
scheduled_for: number(row.scheduled_for, 'automation run scheduled_for'),
|
|
130
|
+
started_at: row.started_at === null
|
|
131
|
+
? null
|
|
132
|
+
: number(row.started_at, 'automation run started_at'),
|
|
133
|
+
finished_at: row.finished_at === null
|
|
134
|
+
? null
|
|
135
|
+
: number(row.finished_at, 'automation run finished_at'),
|
|
136
|
+
seen_at: row.seen_at === null ? null : number(row.seen_at, 'automation run seen_at'),
|
|
137
|
+
created_at: number(row.created_at, 'automation run created_at'),
|
|
138
|
+
});
|
|
139
|
+
const toolServer = (row) => ({
|
|
140
|
+
...row,
|
|
141
|
+
spec_revision: number(row.spec_revision, 'tool server spec_revision'),
|
|
142
|
+
enabled: number(row.enabled, 'tool server enabled'),
|
|
143
|
+
timeout_ms: number(row.timeout_ms, 'tool server timeout_ms'),
|
|
144
|
+
max_response_bytes: number(row.max_response_bytes, 'tool server max_response_bytes'),
|
|
145
|
+
created_at: number(row.created_at, 'tool server created_at'),
|
|
146
|
+
updated_at: number(row.updated_at, 'tool server updated_at'),
|
|
147
|
+
});
|
|
148
|
+
const toolServerTool = (row) => ({
|
|
149
|
+
...row,
|
|
150
|
+
side_effect: number(row.side_effect, 'tool side_effect'),
|
|
151
|
+
enabled: number(row.enabled, 'tool enabled'),
|
|
152
|
+
created_at: number(row.created_at, 'tool created_at'),
|
|
153
|
+
updated_at: number(row.updated_at, 'tool updated_at'),
|
|
154
|
+
});
|
|
155
|
+
const toolServerCredential = (row) => ({
|
|
156
|
+
...row,
|
|
157
|
+
created_at: number(row.created_at, 'tool credential created_at'),
|
|
158
|
+
updated_at: number(row.updated_at, 'tool credential updated_at'),
|
|
159
|
+
});
|
|
160
|
+
const toolApproval = (row) => ({
|
|
161
|
+
...row,
|
|
162
|
+
created_at: number(row.created_at, 'tool approval created_at'),
|
|
163
|
+
resolved_at: row.resolved_at === null
|
|
164
|
+
? null
|
|
165
|
+
: number(row.resolved_at, 'tool approval resolved_at'),
|
|
166
|
+
expires_at: row.expires_at === null
|
|
167
|
+
? null
|
|
168
|
+
: number(row.expires_at, 'tool approval expires_at'),
|
|
169
|
+
});
|
|
170
|
+
const prompt = (row) => ({
|
|
171
|
+
...row,
|
|
172
|
+
version: number(row.version, 'prompt version'),
|
|
173
|
+
created_at: number(row.created_at, 'prompt created_at'),
|
|
174
|
+
updated_at: number(row.updated_at, 'prompt updated_at'),
|
|
175
|
+
});
|
|
176
|
+
const promptVersion = (row) => ({
|
|
177
|
+
...row,
|
|
178
|
+
version: number(row.version, 'prompt revision version'),
|
|
179
|
+
created_at: number(row.created_at, 'prompt revision created_at'),
|
|
180
|
+
});
|
|
181
|
+
const skill = (row) => ({
|
|
182
|
+
...row,
|
|
183
|
+
enabled: number(row.enabled, 'skill enabled'),
|
|
184
|
+
version: number(row.version, 'skill version'),
|
|
185
|
+
created_at: number(row.created_at, 'skill created_at'),
|
|
186
|
+
updated_at: number(row.updated_at, 'skill updated_at'),
|
|
187
|
+
});
|
|
188
|
+
const skillVersion = (row) => ({
|
|
189
|
+
...row,
|
|
190
|
+
version: number(row.version, 'skill revision version'),
|
|
191
|
+
created_at: number(row.created_at, 'skill revision created_at'),
|
|
192
|
+
});
|
|
193
|
+
const skillFile = (row) => ({
|
|
194
|
+
...row,
|
|
195
|
+
size: number(row.size, 'skill file size'),
|
|
196
|
+
created_at: number(row.created_at, 'skill file created_at'),
|
|
197
|
+
updated_at: number(row.updated_at, 'skill file updated_at'),
|
|
198
|
+
});
|
|
40
199
|
const changes = (rowCount) => rowCount ?? 0;
|
|
41
200
|
const lockOwner = async (client, userId) => {
|
|
42
201
|
const result = await client.query('SELECT id FROM users WHERE id = $1 FOR UPDATE', [userId]);
|
|
@@ -198,118 +357,1541 @@ class PostgresChatSessionRepository {
|
|
|
198
357
|
AND (id = $3 OR parent_id = $3)`, [previousActiveMessageId, sessionId, row.parent_id]);
|
|
199
358
|
}
|
|
200
359
|
}
|
|
201
|
-
return changes(deleted.rowCount) > 0;
|
|
202
|
-
}, { isolationLevel: 'serializable' });
|
|
360
|
+
return changes(deleted.rowCount) > 0;
|
|
361
|
+
}, { isolationLevel: 'serializable' });
|
|
362
|
+
}
|
|
363
|
+
async deleteByOwner(sessionId, userId, beforeCommit) {
|
|
364
|
+
return this.database.transaction(async (client) => changes((await client.query('DELETE FROM sessions WHERE id = $1 AND user_id = $2', [sessionId, userId])).rowCount) > 0, { isolationLevel: 'serializable', beforeCommit });
|
|
365
|
+
}
|
|
366
|
+
async deleteAllByOwner(userId) {
|
|
367
|
+
return changes((await this.database.query('DELETE FROM sessions WHERE user_id = $1', [
|
|
368
|
+
userId,
|
|
369
|
+
])).rowCount);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
class PostgresKnowledgeCollectionRepository {
|
|
373
|
+
constructor(database) {
|
|
374
|
+
this.database = database;
|
|
375
|
+
}
|
|
376
|
+
async listByOwner(userId) {
|
|
377
|
+
const result = await this.database.query(`SELECT * FROM knowledge_collections
|
|
378
|
+
WHERE user_id = $1
|
|
379
|
+
ORDER BY lower(name), name`, [userId]);
|
|
380
|
+
return result.rows.map(namedResource);
|
|
381
|
+
}
|
|
382
|
+
async findById(collectionId) {
|
|
383
|
+
const result = await this.database.query('SELECT * FROM knowledge_collections WHERE id = $1', [collectionId]);
|
|
384
|
+
return result.rows[0] ? namedResource(result.rows[0]) : null;
|
|
385
|
+
}
|
|
386
|
+
async replace(collection) {
|
|
387
|
+
await this.database.transaction(async (client) => {
|
|
388
|
+
await lockOwner(client, collection.user_id);
|
|
389
|
+
const result = await client.query(`INSERT INTO knowledge_collections
|
|
390
|
+
(id, user_id, name, created_at, updated_at)
|
|
391
|
+
VALUES ($1, $2, $3, $4, $5)
|
|
392
|
+
ON CONFLICT (id) DO UPDATE SET
|
|
393
|
+
name = EXCLUDED.name,
|
|
394
|
+
updated_at = EXCLUDED.updated_at
|
|
395
|
+
WHERE knowledge_collections.user_id = EXCLUDED.user_id`, [
|
|
396
|
+
collection.id,
|
|
397
|
+
collection.user_id,
|
|
398
|
+
collection.name,
|
|
399
|
+
collection.created_at,
|
|
400
|
+
collection.updated_at,
|
|
401
|
+
]);
|
|
402
|
+
if (result.rowCount !== 1)
|
|
403
|
+
throw new PersistenceResourceConflictError();
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
async deleteAndDetach(collectionId, userId) {
|
|
407
|
+
return (changes((await this.database.query('DELETE FROM knowledge_collections WHERE id = $1 AND user_id = $2', [collectionId, userId])).rowCount) > 0);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
class PostgresNoteRepository {
|
|
411
|
+
constructor(database) {
|
|
412
|
+
this.database = database;
|
|
413
|
+
}
|
|
414
|
+
async listByOwner(userId, maximum) {
|
|
415
|
+
const result = await this.database.query(`SELECT * FROM notes
|
|
416
|
+
WHERE user_id = $1
|
|
417
|
+
ORDER BY pinned DESC, updated_at DESC
|
|
418
|
+
LIMIT $2`, [userId, maximum]);
|
|
419
|
+
return result.rows.map(note);
|
|
420
|
+
}
|
|
421
|
+
async findByOwner(noteId, userId) {
|
|
422
|
+
const result = await this.database.query('SELECT * FROM notes WHERE id = $1 AND user_id = $2', [noteId, userId]);
|
|
423
|
+
return result.rows[0] ? note(result.rows[0]) : null;
|
|
424
|
+
}
|
|
425
|
+
async replaceWithLimit(value, maximum) {
|
|
426
|
+
await this.database.transaction(async (client) => {
|
|
427
|
+
await lockOwner(client, value.user_id);
|
|
428
|
+
const existing = await client.query('SELECT user_id FROM notes WHERE id = $1 FOR UPDATE', [value.id]);
|
|
429
|
+
if (existing.rows[0] && existing.rows[0].user_id !== value.user_id) {
|
|
430
|
+
throw new PersistenceResourceConflictError();
|
|
431
|
+
}
|
|
432
|
+
if (!existing.rows[0]) {
|
|
433
|
+
const count = await client.query('SELECT COUNT(*)::text AS count FROM notes WHERE user_id = $1', [value.user_id]);
|
|
434
|
+
if (Number(count.rows[0]?.count || 0) >= maximum) {
|
|
435
|
+
throw new PersistenceResourceLimitError('note', maximum);
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
const result = await client.query(`INSERT INTO notes (id, user_id, title, content, pinned, created_at, updated_at)
|
|
439
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7)
|
|
440
|
+
ON CONFLICT (id) DO UPDATE SET
|
|
441
|
+
title = EXCLUDED.title,
|
|
442
|
+
content = EXCLUDED.content,
|
|
443
|
+
pinned = EXCLUDED.pinned,
|
|
444
|
+
updated_at = EXCLUDED.updated_at
|
|
445
|
+
WHERE notes.user_id = EXCLUDED.user_id`, [
|
|
446
|
+
value.id,
|
|
447
|
+
value.user_id,
|
|
448
|
+
value.title,
|
|
449
|
+
value.content,
|
|
450
|
+
value.pinned,
|
|
451
|
+
value.created_at,
|
|
452
|
+
value.updated_at,
|
|
453
|
+
]);
|
|
454
|
+
if (result.rowCount !== 1)
|
|
455
|
+
throw new PersistenceResourceConflictError();
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
async patchByOwner(noteId, userId, patch) {
|
|
459
|
+
const assignments = [];
|
|
460
|
+
const values = [];
|
|
461
|
+
const assign = (column, value) => {
|
|
462
|
+
values.push(value);
|
|
463
|
+
assignments.push(`${column} = $${values.length}`);
|
|
464
|
+
};
|
|
465
|
+
if (patch.title !== undefined)
|
|
466
|
+
assign('title', patch.title);
|
|
467
|
+
if (patch.content !== undefined)
|
|
468
|
+
assign('content', patch.content);
|
|
469
|
+
if (patch.pinned !== undefined)
|
|
470
|
+
assign('pinned', patch.pinned);
|
|
471
|
+
assign('updated_at', patch.updated_at);
|
|
472
|
+
values.push(noteId, userId);
|
|
473
|
+
const result = await this.database.query(`UPDATE notes SET ${assignments.join(', ')}
|
|
474
|
+
WHERE id = $${values.length - 1} AND user_id = $${values.length}
|
|
475
|
+
RETURNING *`, values);
|
|
476
|
+
return result.rows[0] ? note(result.rows[0]) : null;
|
|
477
|
+
}
|
|
478
|
+
async deleteByOwner(noteId, userId) {
|
|
479
|
+
return (changes((await this.database.query('DELETE FROM notes WHERE id = $1 AND user_id = $2', [noteId, userId])).rowCount) > 0);
|
|
480
|
+
}
|
|
481
|
+
async findById(noteId) {
|
|
482
|
+
const result = await this.database.query('SELECT * FROM notes WHERE id = $1', [noteId]);
|
|
483
|
+
return result.rows[0] ? note(result.rows[0]) : null;
|
|
484
|
+
}
|
|
485
|
+
async patchById(noteId, patch) {
|
|
486
|
+
const assignments = [];
|
|
487
|
+
const values = [];
|
|
488
|
+
const assign = (column, value) => {
|
|
489
|
+
values.push(value);
|
|
490
|
+
assignments.push(`${column} = $${values.length}`);
|
|
491
|
+
};
|
|
492
|
+
if (patch.title !== undefined)
|
|
493
|
+
assign('title', patch.title);
|
|
494
|
+
if (patch.content !== undefined)
|
|
495
|
+
assign('content', patch.content);
|
|
496
|
+
if (patch.pinned !== undefined)
|
|
497
|
+
assign('pinned', patch.pinned);
|
|
498
|
+
assign('updated_at', patch.updated_at);
|
|
499
|
+
values.push(noteId);
|
|
500
|
+
const result = await this.database.query(`UPDATE notes SET ${assignments.join(', ')}
|
|
501
|
+
WHERE id = $${values.length}
|
|
502
|
+
RETURNING *`, values);
|
|
503
|
+
return result.rows[0] ? note(result.rows[0]) : null;
|
|
504
|
+
}
|
|
505
|
+
async listRevisions(noteId, maximum) {
|
|
506
|
+
const result = await this.database.query(`SELECT * FROM note_revisions
|
|
507
|
+
WHERE note_id = $1
|
|
508
|
+
ORDER BY created_at DESC, id DESC
|
|
509
|
+
LIMIT $2`, [noteId, maximum]);
|
|
510
|
+
return result.rows.map(noteRevision);
|
|
511
|
+
}
|
|
512
|
+
async findRevision(revisionId) {
|
|
513
|
+
const result = await this.database.query('SELECT * FROM note_revisions WHERE id = $1', [revisionId]);
|
|
514
|
+
return result.rows[0] ? noteRevision(result.rows[0]) : null;
|
|
515
|
+
}
|
|
516
|
+
async insertRevision(revision, maximum) {
|
|
517
|
+
await this.database.transaction(async (client) => {
|
|
518
|
+
// Strictly increasing per note so same-millisecond snapshots keep a
|
|
519
|
+
// deterministic order for listing, restore, and pruning.
|
|
520
|
+
await client.query(`INSERT INTO note_revisions (id, note_id, title, content, created_at)
|
|
521
|
+
SELECT $1, $2, $3, $4,
|
|
522
|
+
GREATEST($5::bigint, COALESCE(MAX(created_at), 0) + 1)
|
|
523
|
+
FROM note_revisions WHERE note_id = $2`, [
|
|
524
|
+
revision.id,
|
|
525
|
+
revision.note_id,
|
|
526
|
+
revision.title,
|
|
527
|
+
revision.content,
|
|
528
|
+
revision.created_at,
|
|
529
|
+
]);
|
|
530
|
+
await client.query(`DELETE FROM note_revisions
|
|
531
|
+
WHERE note_id = $1
|
|
532
|
+
AND id NOT IN (
|
|
533
|
+
SELECT id FROM note_revisions
|
|
534
|
+
WHERE note_id = $1
|
|
535
|
+
ORDER BY created_at DESC, id DESC
|
|
536
|
+
LIMIT $2
|
|
537
|
+
)`, [revision.note_id, maximum]);
|
|
538
|
+
});
|
|
539
|
+
}
|
|
540
|
+
async listAttachments(noteId) {
|
|
541
|
+
const result = await this.database.query(`SELECT * FROM note_attachments
|
|
542
|
+
WHERE note_id = $1
|
|
543
|
+
ORDER BY created_at ASC, id ASC`, [noteId]);
|
|
544
|
+
return result.rows.map(noteAttachment);
|
|
545
|
+
}
|
|
546
|
+
async findAttachment(attachmentId) {
|
|
547
|
+
const result = await this.database.query('SELECT * FROM note_attachments WHERE id = $1', [attachmentId]);
|
|
548
|
+
return result.rows[0] ? noteAttachment(result.rows[0]) : null;
|
|
549
|
+
}
|
|
550
|
+
async insertAttachmentWithLimit(attachment, maximum) {
|
|
551
|
+
await this.database.transaction(async (client) => {
|
|
552
|
+
await client.query('SELECT id FROM notes WHERE id = $1 FOR UPDATE', [
|
|
553
|
+
attachment.note_id,
|
|
554
|
+
]);
|
|
555
|
+
const count = await client.query('SELECT COUNT(*)::text AS count FROM note_attachments WHERE note_id = $1', [attachment.note_id]);
|
|
556
|
+
if (Number(count.rows[0]?.count || 0) >= maximum) {
|
|
557
|
+
throw new PersistenceResourceLimitError('note-attachment', maximum);
|
|
558
|
+
}
|
|
559
|
+
await client.query(`INSERT INTO note_attachments
|
|
560
|
+
(id, note_id, blob_id, filename, content_type, size, created_at)
|
|
561
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7)`, [
|
|
562
|
+
attachment.id,
|
|
563
|
+
attachment.note_id,
|
|
564
|
+
attachment.blob_id,
|
|
565
|
+
attachment.filename,
|
|
566
|
+
attachment.content_type,
|
|
567
|
+
attachment.size,
|
|
568
|
+
attachment.created_at,
|
|
569
|
+
]);
|
|
570
|
+
});
|
|
571
|
+
}
|
|
572
|
+
async deleteAttachment(attachmentId) {
|
|
573
|
+
return (changes((await this.database.query('DELETE FROM note_attachments WHERE id = $1', [attachmentId])).rowCount) > 0);
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
class PostgresCalendarEventRepository {
|
|
577
|
+
constructor(database) {
|
|
578
|
+
this.database = database;
|
|
579
|
+
}
|
|
580
|
+
async listByOwnerBetween(userId, from, to, maximum) {
|
|
581
|
+
const result = await this.database.query(`SELECT * FROM calendar_events
|
|
582
|
+
WHERE user_id = $1 AND start_at >= $2 AND start_at < $3
|
|
583
|
+
ORDER BY start_at ASC
|
|
584
|
+
LIMIT $4`, [userId, from, to, maximum]);
|
|
585
|
+
return result.rows.map(calendarEvent);
|
|
586
|
+
}
|
|
587
|
+
async listRecurringByOwner(userId, maximum) {
|
|
588
|
+
const result = await this.database.query(`SELECT * FROM calendar_events
|
|
589
|
+
WHERE user_id = $1 AND recurrence IS NOT NULL
|
|
590
|
+
ORDER BY start_at ASC
|
|
591
|
+
LIMIT $2`, [userId, maximum]);
|
|
592
|
+
return result.rows.map(calendarEvent);
|
|
593
|
+
}
|
|
594
|
+
async listByCalendarsBetween(calendarIds, from, to, maximum) {
|
|
595
|
+
if (calendarIds.length === 0)
|
|
596
|
+
return [];
|
|
597
|
+
const result = await this.database.query(`SELECT * FROM calendar_events
|
|
598
|
+
WHERE calendar_id = ANY($1::text[])
|
|
599
|
+
AND start_at >= $2 AND start_at < $3
|
|
600
|
+
ORDER BY start_at ASC
|
|
601
|
+
LIMIT $4`, [[...calendarIds], from, to, maximum]);
|
|
602
|
+
return result.rows.map(calendarEvent);
|
|
603
|
+
}
|
|
604
|
+
async listRecurringByCalendars(calendarIds, maximum) {
|
|
605
|
+
if (calendarIds.length === 0)
|
|
606
|
+
return [];
|
|
607
|
+
const result = await this.database.query(`SELECT * FROM calendar_events
|
|
608
|
+
WHERE calendar_id = ANY($1::text[])
|
|
609
|
+
AND recurrence IS NOT NULL
|
|
610
|
+
ORDER BY start_at ASC
|
|
611
|
+
LIMIT $2`, [[...calendarIds], maximum]);
|
|
612
|
+
return result.rows.map(calendarEvent);
|
|
613
|
+
}
|
|
614
|
+
async findByOwner(eventId, userId) {
|
|
615
|
+
const result = await this.database.query('SELECT * FROM calendar_events WHERE id = $1 AND user_id = $2', [eventId, userId]);
|
|
616
|
+
return result.rows[0] ? calendarEvent(result.rows[0]) : null;
|
|
617
|
+
}
|
|
618
|
+
async findById(eventId) {
|
|
619
|
+
const result = await this.database.query('SELECT * FROM calendar_events WHERE id = $1', [eventId]);
|
|
620
|
+
return result.rows[0] ? calendarEvent(result.rows[0]) : null;
|
|
621
|
+
}
|
|
622
|
+
async listWithReminders(maximum) {
|
|
623
|
+
const result = await this.database.query(`SELECT * FROM calendar_events
|
|
624
|
+
WHERE reminder_minutes IS NOT NULL
|
|
625
|
+
ORDER BY start_at ASC
|
|
626
|
+
LIMIT $1`, [maximum]);
|
|
627
|
+
return result.rows.map(calendarEvent);
|
|
628
|
+
}
|
|
629
|
+
async markReminded(eventId, occurrenceStart) {
|
|
630
|
+
return (changes((await this.database.query(`UPDATE calendar_events
|
|
631
|
+
SET last_reminded_occurrence = $1
|
|
632
|
+
WHERE id = $2
|
|
633
|
+
AND (last_reminded_occurrence IS NULL
|
|
634
|
+
OR last_reminded_occurrence < $1)`, [occurrenceStart, eventId])).rowCount) > 0);
|
|
635
|
+
}
|
|
636
|
+
async replaceWithLimit(value, maximum) {
|
|
637
|
+
await this.database.transaction(async (client) => {
|
|
638
|
+
await lockOwner(client, value.user_id);
|
|
639
|
+
const existing = await client.query('SELECT user_id FROM calendar_events WHERE id = $1 FOR UPDATE', [value.id]);
|
|
640
|
+
if (existing.rows[0] && existing.rows[0].user_id !== value.user_id) {
|
|
641
|
+
throw new PersistenceResourceConflictError();
|
|
642
|
+
}
|
|
643
|
+
if (!existing.rows[0]) {
|
|
644
|
+
const count = await client.query('SELECT COUNT(*)::text AS count FROM calendar_events WHERE user_id = $1', [value.user_id]);
|
|
645
|
+
if (Number(count.rows[0]?.count || 0) >= maximum) {
|
|
646
|
+
throw new PersistenceResourceLimitError('calendar-event', maximum);
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
const result = await client.query(`INSERT INTO calendar_events
|
|
650
|
+
(id, user_id, title, notes, start_at, end_at, all_day,
|
|
651
|
+
recurrence, calendar_id, reminder_minutes,
|
|
652
|
+
last_reminded_occurrence, created_at, updated_at)
|
|
653
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13)
|
|
654
|
+
ON CONFLICT (id) DO UPDATE SET
|
|
655
|
+
title = EXCLUDED.title,
|
|
656
|
+
notes = EXCLUDED.notes,
|
|
657
|
+
start_at = EXCLUDED.start_at,
|
|
658
|
+
end_at = EXCLUDED.end_at,
|
|
659
|
+
all_day = EXCLUDED.all_day,
|
|
660
|
+
recurrence = EXCLUDED.recurrence,
|
|
661
|
+
calendar_id = EXCLUDED.calendar_id,
|
|
662
|
+
reminder_minutes = EXCLUDED.reminder_minutes,
|
|
663
|
+
last_reminded_occurrence = EXCLUDED.last_reminded_occurrence,
|
|
664
|
+
updated_at = EXCLUDED.updated_at
|
|
665
|
+
WHERE calendar_events.user_id = EXCLUDED.user_id`, [
|
|
666
|
+
value.id,
|
|
667
|
+
value.user_id,
|
|
668
|
+
value.title,
|
|
669
|
+
value.notes,
|
|
670
|
+
value.start_at,
|
|
671
|
+
value.end_at,
|
|
672
|
+
value.all_day,
|
|
673
|
+
value.recurrence,
|
|
674
|
+
value.calendar_id,
|
|
675
|
+
value.reminder_minutes,
|
|
676
|
+
value.last_reminded_occurrence,
|
|
677
|
+
value.created_at,
|
|
678
|
+
value.updated_at,
|
|
679
|
+
]);
|
|
680
|
+
if (result.rowCount !== 1)
|
|
681
|
+
throw new PersistenceResourceConflictError();
|
|
682
|
+
});
|
|
683
|
+
}
|
|
684
|
+
async deleteByOwner(eventId, userId) {
|
|
685
|
+
return (changes((await this.database.query('DELETE FROM calendar_events WHERE id = $1 AND user_id = $2', [eventId, userId])).rowCount) > 0);
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
class PostgresCalendarRepository {
|
|
689
|
+
constructor(database) {
|
|
690
|
+
this.database = database;
|
|
691
|
+
}
|
|
692
|
+
async listByOwner(userId, maximum) {
|
|
693
|
+
const result = await this.database.query(`SELECT * FROM calendars
|
|
694
|
+
WHERE user_id = $1
|
|
695
|
+
ORDER BY created_at ASC
|
|
696
|
+
LIMIT $2`, [userId, maximum]);
|
|
697
|
+
return result.rows.map(calendar);
|
|
698
|
+
}
|
|
699
|
+
async findByOwner(calendarId, userId) {
|
|
700
|
+
const result = await this.database.query('SELECT * FROM calendars WHERE id = $1 AND user_id = $2', [calendarId, userId]);
|
|
701
|
+
return result.rows[0] ? calendar(result.rows[0]) : null;
|
|
702
|
+
}
|
|
703
|
+
async findById(calendarId) {
|
|
704
|
+
const result = await this.database.query('SELECT * FROM calendars WHERE id = $1', [calendarId]);
|
|
705
|
+
return result.rows[0] ? calendar(result.rows[0]) : null;
|
|
706
|
+
}
|
|
707
|
+
async replaceWithLimit(value, maximum) {
|
|
708
|
+
await this.database.transaction(async (client) => {
|
|
709
|
+
await lockOwner(client, value.user_id);
|
|
710
|
+
const existing = await client.query('SELECT user_id FROM calendars WHERE id = $1 FOR UPDATE', [value.id]);
|
|
711
|
+
if (existing.rows[0] && existing.rows[0].user_id !== value.user_id) {
|
|
712
|
+
throw new PersistenceResourceConflictError();
|
|
713
|
+
}
|
|
714
|
+
if (!existing.rows[0]) {
|
|
715
|
+
const count = await client.query('SELECT COUNT(*)::text AS count FROM calendars WHERE user_id = $1', [value.user_id]);
|
|
716
|
+
if (Number(count.rows[0]?.count || 0) >= maximum) {
|
|
717
|
+
throw new PersistenceResourceLimitError('calendar', maximum);
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
const result = await client.query(`INSERT INTO calendars
|
|
721
|
+
(id, user_id, name, color, created_at, updated_at)
|
|
722
|
+
VALUES ($1, $2, $3, $4, $5, $6)
|
|
723
|
+
ON CONFLICT (id) DO UPDATE SET
|
|
724
|
+
name = EXCLUDED.name,
|
|
725
|
+
color = EXCLUDED.color,
|
|
726
|
+
updated_at = EXCLUDED.updated_at
|
|
727
|
+
WHERE calendars.user_id = EXCLUDED.user_id`, [
|
|
728
|
+
value.id,
|
|
729
|
+
value.user_id,
|
|
730
|
+
value.name,
|
|
731
|
+
value.color,
|
|
732
|
+
value.created_at,
|
|
733
|
+
value.updated_at,
|
|
734
|
+
]);
|
|
735
|
+
if (result.rowCount !== 1)
|
|
736
|
+
throw new PersistenceResourceConflictError();
|
|
737
|
+
});
|
|
738
|
+
}
|
|
739
|
+
async deleteAndDetach(calendarId, userId) {
|
|
740
|
+
return this.database.transaction(async (client) => {
|
|
741
|
+
const deleted = changes((await client.query('DELETE FROM calendars WHERE id = $1 AND user_id = $2', [calendarId, userId])).rowCount) > 0;
|
|
742
|
+
if (deleted) {
|
|
743
|
+
await client.query(`UPDATE calendar_events
|
|
744
|
+
SET calendar_id = NULL
|
|
745
|
+
WHERE calendar_id = $1 AND user_id = $2`, [calendarId, userId]);
|
|
746
|
+
}
|
|
747
|
+
return deleted;
|
|
748
|
+
});
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
class PostgresChannelRepository {
|
|
752
|
+
constructor(database) {
|
|
753
|
+
this.database = database;
|
|
754
|
+
}
|
|
755
|
+
async insertWithOwner(value, owner, maximumPerUser) {
|
|
756
|
+
await this.database.transaction(async (client) => {
|
|
757
|
+
await lockOwner(client, owner.user_id);
|
|
758
|
+
const count = await client.query('SELECT COUNT(*)::text AS count FROM channels WHERE created_by = $1', [owner.user_id]);
|
|
759
|
+
if (Number(count.rows[0]?.count || 0) >= maximumPerUser) {
|
|
760
|
+
throw new PersistenceResourceLimitError('channel', maximumPerUser);
|
|
761
|
+
}
|
|
762
|
+
await client.query(`INSERT INTO channels
|
|
763
|
+
(id, type, name, description, dm_key, created_by,
|
|
764
|
+
created_at, updated_at, archived_at)
|
|
765
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)`, [
|
|
766
|
+
value.id,
|
|
767
|
+
value.type,
|
|
768
|
+
value.name,
|
|
769
|
+
value.description,
|
|
770
|
+
value.dm_key,
|
|
771
|
+
value.created_by,
|
|
772
|
+
value.created_at,
|
|
773
|
+
value.updated_at,
|
|
774
|
+
value.archived_at,
|
|
775
|
+
]);
|
|
776
|
+
await client.query(`INSERT INTO channel_members
|
|
777
|
+
(channel_id, user_id, role, joined_at, last_read_at)
|
|
778
|
+
VALUES ($1, $2, $3, $4, $5)`, [
|
|
779
|
+
owner.channel_id,
|
|
780
|
+
owner.user_id,
|
|
781
|
+
owner.role,
|
|
782
|
+
owner.joined_at,
|
|
783
|
+
owner.last_read_at,
|
|
784
|
+
]);
|
|
785
|
+
});
|
|
786
|
+
}
|
|
787
|
+
async findById(channelId) {
|
|
788
|
+
const result = await this.database.query('SELECT * FROM channels WHERE id = $1', [channelId]);
|
|
789
|
+
return result.rows[0] ? channel(result.rows[0]) : null;
|
|
790
|
+
}
|
|
791
|
+
async findByDmKey(dmKey) {
|
|
792
|
+
const result = await this.database.query('SELECT * FROM channels WHERE dm_key = $1', [dmKey]);
|
|
793
|
+
return result.rows[0] ? channel(result.rows[0]) : null;
|
|
794
|
+
}
|
|
795
|
+
async listForUser(userId, maximum) {
|
|
796
|
+
const result = await this.database.query(`SELECT
|
|
797
|
+
c.id, c.type, c.name, c.description, c.dm_key, c.created_by,
|
|
798
|
+
c.created_at, c.updated_at, c.archived_at,
|
|
799
|
+
m.channel_id AS member_channel_id, m.user_id AS member_user_id,
|
|
800
|
+
m.role AS member_role, m.joined_at AS member_joined_at,
|
|
801
|
+
m.last_read_at AS member_last_read_at
|
|
802
|
+
FROM channel_members m
|
|
803
|
+
JOIN channels c ON c.id = m.channel_id
|
|
804
|
+
WHERE m.user_id = $1
|
|
805
|
+
ORDER BY c.updated_at DESC
|
|
806
|
+
LIMIT $2`, [userId, maximum]);
|
|
807
|
+
return result.rows.map(row => ({
|
|
808
|
+
channel: channel(row),
|
|
809
|
+
member: channelMember({
|
|
810
|
+
channel_id: row.member_channel_id,
|
|
811
|
+
user_id: row.member_user_id,
|
|
812
|
+
role: row.member_role,
|
|
813
|
+
joined_at: row.member_joined_at,
|
|
814
|
+
last_read_at: row.member_last_read_at,
|
|
815
|
+
}),
|
|
816
|
+
}));
|
|
817
|
+
}
|
|
818
|
+
async listPublic(maximum) {
|
|
819
|
+
const result = await this.database.query(`SELECT * FROM channels
|
|
820
|
+
WHERE type = 'public' AND archived_at IS NULL
|
|
821
|
+
ORDER BY updated_at DESC
|
|
822
|
+
LIMIT $1`, [maximum]);
|
|
823
|
+
return result.rows.map(channel);
|
|
824
|
+
}
|
|
825
|
+
async update(value) {
|
|
826
|
+
await this.database.query(`UPDATE channels SET
|
|
827
|
+
name = $1,
|
|
828
|
+
description = $2,
|
|
829
|
+
updated_at = $3,
|
|
830
|
+
archived_at = $4
|
|
831
|
+
WHERE id = $5`, [
|
|
832
|
+
value.name,
|
|
833
|
+
value.description,
|
|
834
|
+
value.updated_at,
|
|
835
|
+
value.archived_at,
|
|
836
|
+
value.id,
|
|
837
|
+
]);
|
|
838
|
+
}
|
|
839
|
+
async delete(channelId) {
|
|
840
|
+
return (changes((await this.database.query('DELETE FROM channels WHERE id = $1', [
|
|
841
|
+
channelId,
|
|
842
|
+
])).rowCount) > 0);
|
|
843
|
+
}
|
|
844
|
+
async upsertMember(member, maximumMembers) {
|
|
845
|
+
await this.database.transaction(async (client) => {
|
|
846
|
+
const existing = await client.query(`SELECT 1 FROM channel_members
|
|
847
|
+
WHERE channel_id = $1 AND user_id = $2
|
|
848
|
+
FOR UPDATE`, [member.channel_id, member.user_id]);
|
|
849
|
+
if (!existing.rows[0]) {
|
|
850
|
+
const count = await client.query(`SELECT COUNT(*)::text AS count FROM channel_members
|
|
851
|
+
WHERE channel_id = $1`, [member.channel_id]);
|
|
852
|
+
if (Number(count.rows[0]?.count || 0) >= maximumMembers) {
|
|
853
|
+
throw new PersistenceResourceLimitError('channel-member', maximumMembers);
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
await client.query(`INSERT INTO channel_members
|
|
857
|
+
(channel_id, user_id, role, joined_at, last_read_at)
|
|
858
|
+
VALUES ($1, $2, $3, $4, $5)
|
|
859
|
+
ON CONFLICT (channel_id, user_id) DO UPDATE SET
|
|
860
|
+
role = EXCLUDED.role`, [
|
|
861
|
+
member.channel_id,
|
|
862
|
+
member.user_id,
|
|
863
|
+
member.role,
|
|
864
|
+
member.joined_at,
|
|
865
|
+
member.last_read_at,
|
|
866
|
+
]);
|
|
867
|
+
});
|
|
868
|
+
}
|
|
869
|
+
async findMember(channelId, userId) {
|
|
870
|
+
const result = await this.database.query('SELECT * FROM channel_members WHERE channel_id = $1 AND user_id = $2', [channelId, userId]);
|
|
871
|
+
return result.rows[0] ? channelMember(result.rows[0]) : null;
|
|
872
|
+
}
|
|
873
|
+
async listMembers(channelId, maximum) {
|
|
874
|
+
const result = await this.database.query(`SELECT * FROM channel_members
|
|
875
|
+
WHERE channel_id = $1
|
|
876
|
+
ORDER BY joined_at ASC
|
|
877
|
+
LIMIT $2`, [channelId, maximum]);
|
|
878
|
+
return result.rows.map(channelMember);
|
|
879
|
+
}
|
|
880
|
+
async removeMember(channelId, userId) {
|
|
881
|
+
return (changes((await this.database.query('DELETE FROM channel_members WHERE channel_id = $1 AND user_id = $2', [channelId, userId])).rowCount) > 0);
|
|
882
|
+
}
|
|
883
|
+
async advanceLastRead(channelId, userId, lastReadAt) {
|
|
884
|
+
return (changes((await this.database.query(`UPDATE channel_members
|
|
885
|
+
SET last_read_at = $1
|
|
886
|
+
WHERE channel_id = $2 AND user_id = $3 AND last_read_at < $1`, [lastReadAt, channelId, userId])).rowCount) > 0);
|
|
887
|
+
}
|
|
888
|
+
async unreadSummaryForUser(userId) {
|
|
889
|
+
const result = await this.database.query(`SELECT
|
|
890
|
+
m.channel_id,
|
|
891
|
+
COUNT(msg.id)::text AS unread_count,
|
|
892
|
+
MAX(msg.created_at) AS latest_message_at
|
|
893
|
+
FROM channel_members m
|
|
894
|
+
LEFT JOIN channel_messages msg
|
|
895
|
+
ON msg.channel_id = m.channel_id
|
|
896
|
+
AND msg.created_at > m.last_read_at
|
|
897
|
+
AND msg.deleted_at IS NULL
|
|
898
|
+
AND (msg.user_id IS NULL OR msg.user_id != m.user_id
|
|
899
|
+
OR msg.author_kind != 'user')
|
|
900
|
+
WHERE m.user_id = $1
|
|
901
|
+
GROUP BY m.channel_id`, [userId]);
|
|
902
|
+
return result.rows.map(row => ({
|
|
903
|
+
channel_id: row.channel_id,
|
|
904
|
+
unread_count: number(row.unread_count, 'channel unread_count'),
|
|
905
|
+
latest_message_at: row.latest_message_at === null
|
|
906
|
+
? null
|
|
907
|
+
: number(row.latest_message_at, 'channel latest_message_at'),
|
|
908
|
+
}));
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
class PostgresChannelMessageRepository {
|
|
912
|
+
constructor(database) {
|
|
913
|
+
this.database = database;
|
|
914
|
+
}
|
|
915
|
+
async insertIfAbsent(message, attachments, maximumPerChannel) {
|
|
916
|
+
return this.database.transaction(async (client) => {
|
|
917
|
+
const existing = await client.query('SELECT * FROM channel_messages WHERE id = $1', [message.id]);
|
|
918
|
+
if (existing.rows[0]) {
|
|
919
|
+
return { stored: channelMessage(existing.rows[0]), inserted: false };
|
|
920
|
+
}
|
|
921
|
+
const count = await client.query(`SELECT COUNT(*)::text AS count FROM channel_messages
|
|
922
|
+
WHERE channel_id = $1`, [message.channel_id]);
|
|
923
|
+
if (Number(count.rows[0]?.count || 0) >= maximumPerChannel) {
|
|
924
|
+
throw new PersistenceResourceLimitError('channel-message', maximumPerChannel);
|
|
925
|
+
}
|
|
926
|
+
await client.query(`INSERT INTO channel_messages
|
|
927
|
+
(id, channel_id, user_id, parent_id, author_kind, model, content,
|
|
928
|
+
metadata, created_at, updated_at, edited_at, deleted_at,
|
|
929
|
+
pinned_at, pinned_by)
|
|
930
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14)`, [
|
|
931
|
+
message.id,
|
|
932
|
+
message.channel_id,
|
|
933
|
+
message.user_id,
|
|
934
|
+
message.parent_id,
|
|
935
|
+
message.author_kind,
|
|
936
|
+
message.model,
|
|
937
|
+
message.content,
|
|
938
|
+
message.metadata,
|
|
939
|
+
message.created_at,
|
|
940
|
+
message.updated_at,
|
|
941
|
+
message.edited_at,
|
|
942
|
+
message.deleted_at,
|
|
943
|
+
message.pinned_at,
|
|
944
|
+
message.pinned_by,
|
|
945
|
+
]);
|
|
946
|
+
for (const attachment of attachments) {
|
|
947
|
+
if (attachment.message_id !== message.id) {
|
|
948
|
+
throw new Error('A channel attachment does not belong to its message');
|
|
949
|
+
}
|
|
950
|
+
await client.query(`INSERT INTO channel_attachments
|
|
951
|
+
(id, message_id, channel_id, blob_id, filename, content_type,
|
|
952
|
+
size, created_by, created_at)
|
|
953
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)`, [
|
|
954
|
+
attachment.id,
|
|
955
|
+
attachment.message_id,
|
|
956
|
+
attachment.channel_id,
|
|
957
|
+
attachment.blob_id,
|
|
958
|
+
attachment.filename,
|
|
959
|
+
attachment.content_type,
|
|
960
|
+
attachment.size,
|
|
961
|
+
attachment.created_by,
|
|
962
|
+
attachment.created_at,
|
|
963
|
+
]);
|
|
964
|
+
}
|
|
965
|
+
await client.query('UPDATE channels SET updated_at = $1 WHERE id = $2', [
|
|
966
|
+
message.created_at,
|
|
967
|
+
message.channel_id,
|
|
968
|
+
]);
|
|
969
|
+
return { stored: message, inserted: true };
|
|
970
|
+
});
|
|
971
|
+
}
|
|
972
|
+
async findById(messageId) {
|
|
973
|
+
const result = await this.database.query('SELECT * FROM channel_messages WHERE id = $1', [messageId]);
|
|
974
|
+
return result.rows[0] ? channelMessage(result.rows[0]) : null;
|
|
975
|
+
}
|
|
976
|
+
async listPage(channelId, options) {
|
|
977
|
+
const clauses = ['channel_id = $1'];
|
|
978
|
+
const parameters = [channelId];
|
|
979
|
+
if (options.parentId === null) {
|
|
980
|
+
clauses.push('parent_id IS NULL');
|
|
981
|
+
}
|
|
982
|
+
else if (options.parentId !== undefined) {
|
|
983
|
+
parameters.push(options.parentId);
|
|
984
|
+
clauses.push(`parent_id = $${parameters.length}`);
|
|
985
|
+
}
|
|
986
|
+
if (options.before) {
|
|
987
|
+
parameters.push(options.before.created_at);
|
|
988
|
+
const createdIndex = parameters.length;
|
|
989
|
+
parameters.push(options.before.id);
|
|
990
|
+
clauses.push(`(created_at < $${createdIndex} OR ` +
|
|
991
|
+
`(created_at = $${createdIndex} AND id < $${parameters.length}))`);
|
|
992
|
+
}
|
|
993
|
+
if (options.after) {
|
|
994
|
+
parameters.push(options.after.created_at);
|
|
995
|
+
const createdIndex = parameters.length;
|
|
996
|
+
parameters.push(options.after.id);
|
|
997
|
+
clauses.push(`(created_at > $${createdIndex} OR ` +
|
|
998
|
+
`(created_at = $${createdIndex} AND id > $${parameters.length}))`);
|
|
999
|
+
}
|
|
1000
|
+
const direction = options.after ? 'ASC' : 'DESC';
|
|
1001
|
+
parameters.push(options.limit);
|
|
1002
|
+
const result = await this.database.query(`SELECT * FROM channel_messages
|
|
1003
|
+
WHERE ${clauses.join(' AND ')}
|
|
1004
|
+
ORDER BY created_at ${direction}, id ${direction}
|
|
1005
|
+
LIMIT $${parameters.length}`, parameters);
|
|
1006
|
+
return result.rows.map(channelMessage);
|
|
1007
|
+
}
|
|
1008
|
+
async listThread(parentId, maximum) {
|
|
1009
|
+
const result = await this.database.query(`SELECT * FROM channel_messages
|
|
1010
|
+
WHERE parent_id = $1
|
|
1011
|
+
ORDER BY created_at ASC, id ASC
|
|
1012
|
+
LIMIT $2`, [parentId, maximum]);
|
|
1013
|
+
return result.rows.map(channelMessage);
|
|
1014
|
+
}
|
|
1015
|
+
async countThreadReplies(parentIds) {
|
|
1016
|
+
if (parentIds.length === 0)
|
|
1017
|
+
return {};
|
|
1018
|
+
const result = await this.database.query(`SELECT parent_id, COUNT(*)::text AS count
|
|
1019
|
+
FROM channel_messages
|
|
1020
|
+
WHERE parent_id = ANY($1::text[]) AND deleted_at IS NULL
|
|
1021
|
+
GROUP BY parent_id`, [[...parentIds]]);
|
|
1022
|
+
const counts = {};
|
|
1023
|
+
for (const row of result.rows) {
|
|
1024
|
+
counts[row.parent_id] = number(row.count, 'channel thread reply count');
|
|
1025
|
+
}
|
|
1026
|
+
return counts;
|
|
1027
|
+
}
|
|
1028
|
+
async update(message) {
|
|
1029
|
+
await this.database.query(`UPDATE channel_messages SET
|
|
1030
|
+
content = $1,
|
|
1031
|
+
metadata = $2,
|
|
1032
|
+
updated_at = $3,
|
|
1033
|
+
edited_at = $4,
|
|
1034
|
+
deleted_at = $5,
|
|
1035
|
+
pinned_at = $6,
|
|
1036
|
+
pinned_by = $7
|
|
1037
|
+
WHERE id = $8`, [
|
|
1038
|
+
message.content,
|
|
1039
|
+
message.metadata,
|
|
1040
|
+
message.updated_at,
|
|
1041
|
+
message.edited_at,
|
|
1042
|
+
message.deleted_at,
|
|
1043
|
+
message.pinned_at,
|
|
1044
|
+
message.pinned_by,
|
|
1045
|
+
message.id,
|
|
1046
|
+
]);
|
|
1047
|
+
}
|
|
1048
|
+
async listPinned(channelId, maximum) {
|
|
1049
|
+
const result = await this.database.query(`SELECT * FROM channel_messages
|
|
1050
|
+
WHERE channel_id = $1 AND pinned_at IS NOT NULL
|
|
1051
|
+
AND deleted_at IS NULL
|
|
1052
|
+
ORDER BY pinned_at DESC
|
|
1053
|
+
LIMIT $2`, [channelId, maximum]);
|
|
1054
|
+
return result.rows.map(channelMessage);
|
|
1055
|
+
}
|
|
1056
|
+
async listRecentForChannels(channelIds, maximum) {
|
|
1057
|
+
if (channelIds.length === 0)
|
|
1058
|
+
return [];
|
|
1059
|
+
const result = await this.database.query(`SELECT * FROM channel_messages
|
|
1060
|
+
WHERE channel_id = ANY($1::text[]) AND deleted_at IS NULL
|
|
1061
|
+
ORDER BY created_at DESC
|
|
1062
|
+
LIMIT $2`, [[...channelIds], maximum]);
|
|
1063
|
+
return result.rows.map(channelMessage);
|
|
1064
|
+
}
|
|
1065
|
+
async addReaction(reaction, maximumPerMessage) {
|
|
1066
|
+
return this.database.transaction(async (client) => {
|
|
1067
|
+
const existing = await client.query(`SELECT 1 FROM channel_reactions
|
|
1068
|
+
WHERE message_id = $1 AND user_id = $2 AND emoji = $3`, [reaction.message_id, reaction.user_id, reaction.emoji]);
|
|
1069
|
+
if (existing.rows[0])
|
|
1070
|
+
return false;
|
|
1071
|
+
const count = await client.query(`SELECT COUNT(*)::text AS count FROM channel_reactions
|
|
1072
|
+
WHERE message_id = $1`, [reaction.message_id]);
|
|
1073
|
+
if (Number(count.rows[0]?.count || 0) >= maximumPerMessage) {
|
|
1074
|
+
throw new PersistenceResourceLimitError('channel-reaction', maximumPerMessage);
|
|
1075
|
+
}
|
|
1076
|
+
const result = await client.query(`INSERT INTO channel_reactions
|
|
1077
|
+
(id, message_id, user_id, emoji, created_at)
|
|
1078
|
+
VALUES ($1, $2, $3, $4, $5)
|
|
1079
|
+
ON CONFLICT (message_id, user_id, emoji) DO NOTHING`, [
|
|
1080
|
+
reaction.id,
|
|
1081
|
+
reaction.message_id,
|
|
1082
|
+
reaction.user_id,
|
|
1083
|
+
reaction.emoji,
|
|
1084
|
+
reaction.created_at,
|
|
1085
|
+
]);
|
|
1086
|
+
return changes(result.rowCount) > 0;
|
|
1087
|
+
});
|
|
1088
|
+
}
|
|
1089
|
+
async removeReaction(messageId, userId, emoji) {
|
|
1090
|
+
return (changes((await this.database.query(`DELETE FROM channel_reactions
|
|
1091
|
+
WHERE message_id = $1 AND user_id = $2 AND emoji = $3`, [messageId, userId, emoji])).rowCount) > 0);
|
|
1092
|
+
}
|
|
1093
|
+
async listReactionsForMessages(messageIds) {
|
|
1094
|
+
if (messageIds.length === 0)
|
|
1095
|
+
return [];
|
|
1096
|
+
const result = await this.database.query(`SELECT * FROM channel_reactions
|
|
1097
|
+
WHERE message_id = ANY($1::text[])
|
|
1098
|
+
ORDER BY created_at ASC`, [[...messageIds]]);
|
|
1099
|
+
return result.rows.map(channelReaction);
|
|
1100
|
+
}
|
|
1101
|
+
async findAttachment(attachmentId) {
|
|
1102
|
+
const result = await this.database.query('SELECT * FROM channel_attachments WHERE id = $1', [attachmentId]);
|
|
1103
|
+
return result.rows[0] ? channelAttachment(result.rows[0]) : null;
|
|
1104
|
+
}
|
|
1105
|
+
async listAttachmentsForMessages(messageIds) {
|
|
1106
|
+
if (messageIds.length === 0)
|
|
1107
|
+
return [];
|
|
1108
|
+
const result = await this.database.query(`SELECT * FROM channel_attachments
|
|
1109
|
+
WHERE message_id = ANY($1::text[])
|
|
1110
|
+
ORDER BY created_at ASC`, [[...messageIds]]);
|
|
1111
|
+
return result.rows.map(channelAttachment);
|
|
1112
|
+
}
|
|
1113
|
+
async listAttachmentBlobIds(channelId) {
|
|
1114
|
+
const result = await this.database.query('SELECT blob_id, created_by FROM channel_attachments WHERE channel_id = $1', [channelId]);
|
|
1115
|
+
return result.rows;
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
class PostgresNotificationRepository {
|
|
1119
|
+
constructor(database) {
|
|
1120
|
+
this.database = database;
|
|
1121
|
+
}
|
|
1122
|
+
async insertWithLimit(value, maximumPerUser) {
|
|
1123
|
+
return this.database.transaction(async (client) => {
|
|
1124
|
+
await lockOwner(client, value.user_id);
|
|
1125
|
+
const result = await client.query(`INSERT INTO notifications
|
|
1126
|
+
(id, user_id, type, title, body, href, source_key,
|
|
1127
|
+
created_at, read_at)
|
|
1128
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
|
|
1129
|
+
ON CONFLICT (user_id, source_key) DO NOTHING`, [
|
|
1130
|
+
value.id,
|
|
1131
|
+
value.user_id,
|
|
1132
|
+
value.type,
|
|
1133
|
+
value.title,
|
|
1134
|
+
value.body,
|
|
1135
|
+
value.href,
|
|
1136
|
+
value.source_key,
|
|
1137
|
+
value.created_at,
|
|
1138
|
+
value.read_at,
|
|
1139
|
+
]);
|
|
1140
|
+
if (changes(result.rowCount) === 0)
|
|
1141
|
+
return false;
|
|
1142
|
+
await client.query(`DELETE FROM notifications
|
|
1143
|
+
WHERE user_id = $1
|
|
1144
|
+
AND id NOT IN (
|
|
1145
|
+
SELECT id FROM notifications
|
|
1146
|
+
WHERE user_id = $1
|
|
1147
|
+
ORDER BY created_at DESC, id DESC
|
|
1148
|
+
LIMIT $2
|
|
1149
|
+
)`, [value.user_id, maximumPerUser]);
|
|
1150
|
+
return true;
|
|
1151
|
+
});
|
|
1152
|
+
}
|
|
1153
|
+
async listByOwner(userId, options) {
|
|
1154
|
+
const clauses = ['user_id = $1'];
|
|
1155
|
+
const parameters = [userId];
|
|
1156
|
+
if (options.before !== undefined) {
|
|
1157
|
+
parameters.push(options.before);
|
|
1158
|
+
clauses.push(`created_at < $${parameters.length}`);
|
|
1159
|
+
}
|
|
1160
|
+
if (options.unreadOnly) {
|
|
1161
|
+
clauses.push('read_at IS NULL');
|
|
1162
|
+
}
|
|
1163
|
+
parameters.push(options.limit);
|
|
1164
|
+
const result = await this.database.query(`SELECT * FROM notifications
|
|
1165
|
+
WHERE ${clauses.join(' AND ')}
|
|
1166
|
+
ORDER BY created_at DESC, id DESC
|
|
1167
|
+
LIMIT $${parameters.length}`, parameters);
|
|
1168
|
+
return result.rows.map(notification);
|
|
1169
|
+
}
|
|
1170
|
+
async countUnread(userId) {
|
|
1171
|
+
const result = await this.database.query(`SELECT COUNT(*)::text AS count FROM notifications
|
|
1172
|
+
WHERE user_id = $1 AND read_at IS NULL`, [userId]);
|
|
1173
|
+
return Number(result.rows[0]?.count || 0);
|
|
1174
|
+
}
|
|
1175
|
+
async markRead(notificationId, userId, readAt) {
|
|
1176
|
+
return (changes((await this.database.query(`UPDATE notifications SET read_at = $1
|
|
1177
|
+
WHERE id = $2 AND user_id = $3 AND read_at IS NULL`, [readAt, notificationId, userId])).rowCount) > 0);
|
|
1178
|
+
}
|
|
1179
|
+
async markAllRead(userId, readAt) {
|
|
1180
|
+
return changes((await this.database.query(`UPDATE notifications SET read_at = $1
|
|
1181
|
+
WHERE user_id = $2 AND read_at IS NULL`, [readAt, userId])).rowCount);
|
|
1182
|
+
}
|
|
1183
|
+
async deleteByOwner(notificationId, userId) {
|
|
1184
|
+
return (changes((await this.database.query('DELETE FROM notifications WHERE id = $1 AND user_id = $2', [notificationId, userId])).rowCount) > 0);
|
|
1185
|
+
}
|
|
1186
|
+
}
|
|
1187
|
+
class PostgresWebhookTargetRepository {
|
|
1188
|
+
constructor(database) {
|
|
1189
|
+
this.database = database;
|
|
1190
|
+
}
|
|
1191
|
+
async list(maximum) {
|
|
1192
|
+
const result = await this.database.query('SELECT * FROM webhook_targets ORDER BY created_at ASC LIMIT $1', [maximum]);
|
|
1193
|
+
return result.rows.map(webhookTarget);
|
|
1194
|
+
}
|
|
1195
|
+
async findById(targetId) {
|
|
1196
|
+
const result = await this.database.query('SELECT * FROM webhook_targets WHERE id = $1', [targetId]);
|
|
1197
|
+
return result.rows[0] ? webhookTarget(result.rows[0]) : null;
|
|
1198
|
+
}
|
|
1199
|
+
async replaceWithLimit(value, maximum) {
|
|
1200
|
+
await this.database.transaction(async (client) => {
|
|
1201
|
+
const existing = await client.query('SELECT 1 FROM webhook_targets WHERE id = $1 FOR UPDATE', [value.id]);
|
|
1202
|
+
if (!existing.rows[0]) {
|
|
1203
|
+
const count = await client.query('SELECT COUNT(*)::text AS count FROM webhook_targets');
|
|
1204
|
+
if (Number(count.rows[0]?.count || 0) >= maximum) {
|
|
1205
|
+
throw new PersistenceResourceLimitError('webhook-target', maximum);
|
|
1206
|
+
}
|
|
1207
|
+
}
|
|
1208
|
+
await client.query(`INSERT INTO webhook_targets
|
|
1209
|
+
(id, name, url, secret, events, enabled, created_by,
|
|
1210
|
+
created_at, updated_at)
|
|
1211
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
|
|
1212
|
+
ON CONFLICT (id) DO UPDATE SET
|
|
1213
|
+
name = EXCLUDED.name,
|
|
1214
|
+
url = EXCLUDED.url,
|
|
1215
|
+
secret = EXCLUDED.secret,
|
|
1216
|
+
events = EXCLUDED.events,
|
|
1217
|
+
enabled = EXCLUDED.enabled,
|
|
1218
|
+
updated_at = EXCLUDED.updated_at`, [
|
|
1219
|
+
value.id,
|
|
1220
|
+
value.name,
|
|
1221
|
+
value.url,
|
|
1222
|
+
value.secret,
|
|
1223
|
+
value.events,
|
|
1224
|
+
value.enabled,
|
|
1225
|
+
value.created_by,
|
|
1226
|
+
value.created_at,
|
|
1227
|
+
value.updated_at,
|
|
1228
|
+
]);
|
|
1229
|
+
});
|
|
1230
|
+
}
|
|
1231
|
+
async delete(targetId) {
|
|
1232
|
+
return (changes((await this.database.query('DELETE FROM webhook_targets WHERE id = $1', [targetId])).rowCount) > 0);
|
|
1233
|
+
}
|
|
1234
|
+
}
|
|
1235
|
+
class PostgresAutomationRepository {
|
|
1236
|
+
constructor(database) {
|
|
1237
|
+
this.database = database;
|
|
1238
|
+
}
|
|
1239
|
+
async listByOwner(userId, maximum) {
|
|
1240
|
+
const result = await this.database.query(`SELECT * FROM automations
|
|
1241
|
+
WHERE user_id = $1
|
|
1242
|
+
ORDER BY updated_at DESC
|
|
1243
|
+
LIMIT $2`, [userId, maximum]);
|
|
1244
|
+
return result.rows.map(automation);
|
|
1245
|
+
}
|
|
1246
|
+
async findByOwner(automationId, userId) {
|
|
1247
|
+
const result = await this.database.query('SELECT * FROM automations WHERE id = $1 AND user_id = $2', [automationId, userId]);
|
|
1248
|
+
return result.rows[0] ? automation(result.rows[0]) : null;
|
|
1249
|
+
}
|
|
1250
|
+
async findById(automationId) {
|
|
1251
|
+
const result = await this.database.query('SELECT * FROM automations WHERE id = $1', [automationId]);
|
|
1252
|
+
return result.rows[0] ? automation(result.rows[0]) : null;
|
|
1253
|
+
}
|
|
1254
|
+
async replaceWithLimit(value, maximum) {
|
|
1255
|
+
await this.database.transaction(async (client) => {
|
|
1256
|
+
await lockOwner(client, value.user_id);
|
|
1257
|
+
const existing = await client.query('SELECT user_id FROM automations WHERE id = $1 FOR UPDATE', [value.id]);
|
|
1258
|
+
if (existing.rows[0] && existing.rows[0].user_id !== value.user_id) {
|
|
1259
|
+
throw new PersistenceResourceConflictError();
|
|
1260
|
+
}
|
|
1261
|
+
if (!existing.rows[0]) {
|
|
1262
|
+
const count = await client.query('SELECT COUNT(*)::text AS count FROM automations WHERE user_id = $1', [value.user_id]);
|
|
1263
|
+
if (Number(count.rows[0]?.count || 0) >= maximum) {
|
|
1264
|
+
throw new PersistenceResourceLimitError('automation', maximum);
|
|
1265
|
+
}
|
|
1266
|
+
}
|
|
1267
|
+
const result = await client.query(`INSERT INTO automations
|
|
1268
|
+
(id, user_id, name, instructions, triggers, provider, model,
|
|
1269
|
+
notify, status, next_run_at, last_run_at, created_at, updated_at)
|
|
1270
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13)
|
|
1271
|
+
ON CONFLICT (id) DO UPDATE SET
|
|
1272
|
+
name = EXCLUDED.name,
|
|
1273
|
+
instructions = EXCLUDED.instructions,
|
|
1274
|
+
triggers = EXCLUDED.triggers,
|
|
1275
|
+
provider = EXCLUDED.provider,
|
|
1276
|
+
model = EXCLUDED.model,
|
|
1277
|
+
notify = EXCLUDED.notify,
|
|
1278
|
+
status = EXCLUDED.status,
|
|
1279
|
+
next_run_at = EXCLUDED.next_run_at,
|
|
1280
|
+
last_run_at = EXCLUDED.last_run_at,
|
|
1281
|
+
updated_at = EXCLUDED.updated_at
|
|
1282
|
+
WHERE automations.user_id = EXCLUDED.user_id`, [
|
|
1283
|
+
value.id,
|
|
1284
|
+
value.user_id,
|
|
1285
|
+
value.name,
|
|
1286
|
+
value.instructions,
|
|
1287
|
+
value.triggers,
|
|
1288
|
+
value.provider,
|
|
1289
|
+
value.model,
|
|
1290
|
+
value.notify,
|
|
1291
|
+
value.status,
|
|
1292
|
+
value.next_run_at,
|
|
1293
|
+
value.last_run_at,
|
|
1294
|
+
value.created_at,
|
|
1295
|
+
value.updated_at,
|
|
1296
|
+
]);
|
|
1297
|
+
if (result.rowCount !== 1)
|
|
1298
|
+
throw new PersistenceResourceConflictError();
|
|
1299
|
+
});
|
|
1300
|
+
}
|
|
1301
|
+
async listDue(now, maximum) {
|
|
1302
|
+
const result = await this.database.query(`SELECT * FROM automations
|
|
1303
|
+
WHERE status = 'active'
|
|
1304
|
+
AND next_run_at IS NOT NULL
|
|
1305
|
+
AND next_run_at <= $1
|
|
1306
|
+
ORDER BY next_run_at ASC
|
|
1307
|
+
LIMIT $2`, [now, maximum]);
|
|
1308
|
+
return result.rows.map(automation);
|
|
1309
|
+
}
|
|
1310
|
+
async advanceNextRun(automationId, observedNextRunAt, nextRunAt, lastRunAt) {
|
|
1311
|
+
return (changes((await this.database.query(`UPDATE automations
|
|
1312
|
+
SET next_run_at = $1, last_run_at = $2
|
|
1313
|
+
WHERE id = $3 AND next_run_at = $4`, [nextRunAt, lastRunAt, automationId, observedNextRunAt])).rowCount) > 0);
|
|
1314
|
+
}
|
|
1315
|
+
async setStatus(automationId, userId, status, nextRunAt, updatedAt) {
|
|
1316
|
+
return (changes((await this.database.query(`UPDATE automations
|
|
1317
|
+
SET status = $1, next_run_at = $2, updated_at = $3
|
|
1318
|
+
WHERE id = $4 AND user_id = $5`, [status, nextRunAt, updatedAt, automationId, userId])).rowCount) > 0);
|
|
1319
|
+
}
|
|
1320
|
+
async deleteByOwner(automationId, userId) {
|
|
1321
|
+
return (changes((await this.database.query('DELETE FROM automations WHERE id = $1 AND user_id = $2', [automationId, userId])).rowCount) > 0);
|
|
1322
|
+
}
|
|
1323
|
+
}
|
|
1324
|
+
class PostgresAutomationRunRepository {
|
|
1325
|
+
constructor(database) {
|
|
1326
|
+
this.database = database;
|
|
1327
|
+
}
|
|
1328
|
+
async insert(run) {
|
|
1329
|
+
await this.database.query(`INSERT INTO automation_runs
|
|
1330
|
+
(id, automation_id, user_id, scheduled_for, started_at, finished_at,
|
|
1331
|
+
status, session_id, assistant_message_id, error, seen_at, created_at)
|
|
1332
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)`, [
|
|
1333
|
+
run.id,
|
|
1334
|
+
run.automation_id,
|
|
1335
|
+
run.user_id,
|
|
1336
|
+
run.scheduled_for,
|
|
1337
|
+
run.started_at,
|
|
1338
|
+
run.finished_at,
|
|
1339
|
+
run.status,
|
|
1340
|
+
run.session_id,
|
|
1341
|
+
run.assistant_message_id,
|
|
1342
|
+
run.error,
|
|
1343
|
+
run.seen_at,
|
|
1344
|
+
run.created_at,
|
|
1345
|
+
]);
|
|
1346
|
+
}
|
|
1347
|
+
async findByOwner(runId, userId) {
|
|
1348
|
+
const result = await this.database.query('SELECT * FROM automation_runs WHERE id = $1 AND user_id = $2', [runId, userId]);
|
|
1349
|
+
return result.rows[0] ? automationRun(result.rows[0]) : null;
|
|
1350
|
+
}
|
|
1351
|
+
async listByOwner(userId, options) {
|
|
1352
|
+
const clauses = ['user_id = $1'];
|
|
1353
|
+
const values = [userId];
|
|
1354
|
+
if (options.automationId !== undefined) {
|
|
1355
|
+
values.push(options.automationId);
|
|
1356
|
+
clauses.push(`automation_id = $${values.length}`);
|
|
1357
|
+
}
|
|
1358
|
+
if (options.from !== undefined) {
|
|
1359
|
+
values.push(options.from);
|
|
1360
|
+
clauses.push(`scheduled_for >= $${values.length}`);
|
|
1361
|
+
}
|
|
1362
|
+
if (options.to !== undefined) {
|
|
1363
|
+
values.push(options.to);
|
|
1364
|
+
clauses.push(`scheduled_for < $${values.length}`);
|
|
1365
|
+
}
|
|
1366
|
+
values.push(options.maximum);
|
|
1367
|
+
const result = await this.database.query(`SELECT * FROM automation_runs
|
|
1368
|
+
WHERE ${clauses.join(' AND ')}
|
|
1369
|
+
ORDER BY scheduled_for DESC
|
|
1370
|
+
LIMIT $${values.length}`, values);
|
|
1371
|
+
return result.rows.map(automationRun);
|
|
1372
|
+
}
|
|
1373
|
+
async listUnfinished(maximum) {
|
|
1374
|
+
const result = await this.database.query(`SELECT * FROM automation_runs
|
|
1375
|
+
WHERE status IN ('queued', 'running')
|
|
1376
|
+
ORDER BY scheduled_for ASC
|
|
1377
|
+
LIMIT $1`, [maximum]);
|
|
1378
|
+
return result.rows.map(automationRun);
|
|
1379
|
+
}
|
|
1380
|
+
async markStarted(runId, sessionId, assistantMessageId, startedAt) {
|
|
1381
|
+
return (changes((await this.database.query(`UPDATE automation_runs
|
|
1382
|
+
SET session_id = $1, assistant_message_id = $2, started_at = $3,
|
|
1383
|
+
status = 'running'
|
|
1384
|
+
WHERE id = $4 AND status = 'queued'`, [sessionId, assistantMessageId, startedAt, runId])).rowCount) > 0);
|
|
1385
|
+
}
|
|
1386
|
+
async finalize(runId, status, finishedAt, error) {
|
|
1387
|
+
return (changes((await this.database.query(`UPDATE automation_runs
|
|
1388
|
+
SET status = $1, finished_at = $2, error = $3
|
|
1389
|
+
WHERE id = $4 AND status IN ('queued', 'running')`, [status, finishedAt, error, runId])).rowCount) > 0);
|
|
1390
|
+
}
|
|
1391
|
+
async countUnseenFinished(userId) {
|
|
1392
|
+
const result = await this.database.query(`SELECT COUNT(*)::text AS count FROM automation_runs
|
|
1393
|
+
WHERE user_id = $1
|
|
1394
|
+
AND status IN ('succeeded', 'failed')
|
|
1395
|
+
AND seen_at IS NULL`, [userId]);
|
|
1396
|
+
return Number(result.rows[0]?.count || 0);
|
|
1397
|
+
}
|
|
1398
|
+
async markSeenBefore(userId, seenAt) {
|
|
1399
|
+
return changes((await this.database.query(`UPDATE automation_runs
|
|
1400
|
+
SET seen_at = $1
|
|
1401
|
+
WHERE user_id = $2
|
|
1402
|
+
AND status IN ('succeeded', 'failed')
|
|
1403
|
+
AND seen_at IS NULL
|
|
1404
|
+
AND finished_at IS NOT NULL
|
|
1405
|
+
AND finished_at <= $3`, [seenAt, userId, seenAt])).rowCount);
|
|
1406
|
+
}
|
|
1407
|
+
}
|
|
1408
|
+
class PostgresToolServerRepository {
|
|
1409
|
+
constructor(database) {
|
|
1410
|
+
this.database = database;
|
|
1411
|
+
}
|
|
1412
|
+
async list(maximum) {
|
|
1413
|
+
const result = await this.database.query(`SELECT * FROM tool_servers
|
|
1414
|
+
ORDER BY updated_at DESC
|
|
1415
|
+
LIMIT $1`, [maximum]);
|
|
1416
|
+
return result.rows.map(toolServer);
|
|
1417
|
+
}
|
|
1418
|
+
async findById(serverId) {
|
|
1419
|
+
const result = await this.database.query('SELECT * FROM tool_servers WHERE id = $1', [serverId]);
|
|
1420
|
+
return result.rows[0] ? toolServer(result.rows[0]) : null;
|
|
1421
|
+
}
|
|
1422
|
+
async replaceWithLimit(value, maximum) {
|
|
1423
|
+
await this.database.transaction(async (client) => {
|
|
1424
|
+
await lockOwner(client, value.user_id);
|
|
1425
|
+
const existing = await client.query('SELECT id FROM tool_servers WHERE id = $1 FOR UPDATE', [value.id]);
|
|
1426
|
+
if (!existing.rows[0]) {
|
|
1427
|
+
const count = await client.query('SELECT COUNT(*)::text AS count FROM tool_servers');
|
|
1428
|
+
if (Number(count.rows[0]?.count || 0) >= maximum) {
|
|
1429
|
+
throw new PersistenceResourceLimitError('tool-server', maximum);
|
|
1430
|
+
}
|
|
1431
|
+
}
|
|
1432
|
+
await client.query(`INSERT INTO tool_servers
|
|
1433
|
+
(id, user_id, name, description, kind, base_url, spec, spec_digest,
|
|
1434
|
+
spec_revision, auth_mode, auth_header, access_mode, enabled,
|
|
1435
|
+
timeout_ms, max_response_bytes, created_at, updated_at)
|
|
1436
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14,
|
|
1437
|
+
$15, $16, $17)
|
|
1438
|
+
ON CONFLICT (id) DO UPDATE SET
|
|
1439
|
+
name = EXCLUDED.name,
|
|
1440
|
+
description = EXCLUDED.description,
|
|
1441
|
+
base_url = EXCLUDED.base_url,
|
|
1442
|
+
spec = EXCLUDED.spec,
|
|
1443
|
+
spec_digest = EXCLUDED.spec_digest,
|
|
1444
|
+
spec_revision = EXCLUDED.spec_revision,
|
|
1445
|
+
auth_mode = EXCLUDED.auth_mode,
|
|
1446
|
+
auth_header = EXCLUDED.auth_header,
|
|
1447
|
+
access_mode = EXCLUDED.access_mode,
|
|
1448
|
+
enabled = EXCLUDED.enabled,
|
|
1449
|
+
timeout_ms = EXCLUDED.timeout_ms,
|
|
1450
|
+
max_response_bytes = EXCLUDED.max_response_bytes,
|
|
1451
|
+
updated_at = EXCLUDED.updated_at`, [
|
|
1452
|
+
value.id,
|
|
1453
|
+
value.user_id,
|
|
1454
|
+
value.name,
|
|
1455
|
+
value.description,
|
|
1456
|
+
value.kind,
|
|
1457
|
+
value.base_url,
|
|
1458
|
+
value.spec,
|
|
1459
|
+
value.spec_digest,
|
|
1460
|
+
value.spec_revision,
|
|
1461
|
+
value.auth_mode,
|
|
1462
|
+
value.auth_header,
|
|
1463
|
+
value.access_mode,
|
|
1464
|
+
value.enabled,
|
|
1465
|
+
value.timeout_ms,
|
|
1466
|
+
value.max_response_bytes,
|
|
1467
|
+
value.created_at,
|
|
1468
|
+
value.updated_at,
|
|
1469
|
+
]);
|
|
1470
|
+
});
|
|
1471
|
+
}
|
|
1472
|
+
async delete(serverId) {
|
|
1473
|
+
return (changes((await this.database.query('DELETE FROM tool_servers WHERE id = $1', [
|
|
1474
|
+
serverId,
|
|
1475
|
+
])).rowCount) > 0);
|
|
1476
|
+
}
|
|
1477
|
+
}
|
|
1478
|
+
class PostgresToolServerToolRepository {
|
|
1479
|
+
constructor(database) {
|
|
1480
|
+
this.database = database;
|
|
1481
|
+
}
|
|
1482
|
+
async listByServer(serverId) {
|
|
1483
|
+
const result = await this.database.query(`SELECT * FROM tool_server_tools
|
|
1484
|
+
WHERE server_id = $1
|
|
1485
|
+
ORDER BY name ASC`, [serverId]);
|
|
1486
|
+
return result.rows.map(toolServerTool);
|
|
1487
|
+
}
|
|
1488
|
+
async replaceAllForServer(serverId, tools) {
|
|
1489
|
+
await this.database.transaction(async (client) => {
|
|
1490
|
+
const previousResult = await client.query(`SELECT name, enabled, side_effect FROM tool_server_tools
|
|
1491
|
+
WHERE server_id = $1 FOR UPDATE`, [serverId]);
|
|
1492
|
+
const previous = new Map(previousResult.rows.map(row => [
|
|
1493
|
+
row.name,
|
|
1494
|
+
{
|
|
1495
|
+
enabled: number(row.enabled, 'tool enabled'),
|
|
1496
|
+
side_effect: number(row.side_effect, 'tool side_effect'),
|
|
1497
|
+
},
|
|
1498
|
+
]));
|
|
1499
|
+
await client.query('DELETE FROM tool_server_tools WHERE server_id = $1', [
|
|
1500
|
+
serverId,
|
|
1501
|
+
]);
|
|
1502
|
+
for (const tool of tools) {
|
|
1503
|
+
if (tool.server_id !== serverId) {
|
|
1504
|
+
throw new Error('A tool row does not belong to its server');
|
|
1505
|
+
}
|
|
1506
|
+
const kept = previous.get(tool.name);
|
|
1507
|
+
await client.query(`INSERT INTO tool_server_tools
|
|
1508
|
+
(id, server_id, name, description, params_schema, detail,
|
|
1509
|
+
side_effect, enabled, created_at, updated_at)
|
|
1510
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)`, [
|
|
1511
|
+
tool.id,
|
|
1512
|
+
tool.server_id,
|
|
1513
|
+
tool.name,
|
|
1514
|
+
tool.description,
|
|
1515
|
+
tool.params_schema,
|
|
1516
|
+
tool.detail,
|
|
1517
|
+
kept ? kept.side_effect : tool.side_effect,
|
|
1518
|
+
kept ? kept.enabled : tool.enabled,
|
|
1519
|
+
tool.created_at,
|
|
1520
|
+
tool.updated_at,
|
|
1521
|
+
]);
|
|
1522
|
+
}
|
|
1523
|
+
});
|
|
203
1524
|
}
|
|
204
|
-
async
|
|
205
|
-
|
|
1525
|
+
async updateOverrides(serverId, toolName, overrides, updatedAt) {
|
|
1526
|
+
const assignments = ['updated_at = $1'];
|
|
1527
|
+
const values = [updatedAt];
|
|
1528
|
+
if (overrides.enabled !== undefined) {
|
|
1529
|
+
values.push(overrides.enabled);
|
|
1530
|
+
assignments.push(`enabled = $${values.length}`);
|
|
1531
|
+
}
|
|
1532
|
+
if (overrides.side_effect !== undefined) {
|
|
1533
|
+
values.push(overrides.side_effect);
|
|
1534
|
+
assignments.push(`side_effect = $${values.length}`);
|
|
1535
|
+
}
|
|
1536
|
+
values.push(serverId, toolName);
|
|
1537
|
+
const result = await this.database.query(`UPDATE tool_server_tools
|
|
1538
|
+
SET ${assignments.join(', ')}
|
|
1539
|
+
WHERE server_id = $${values.length - 1} AND name = $${values.length}
|
|
1540
|
+
RETURNING *`, values);
|
|
1541
|
+
return result.rows[0] ? toolServerTool(result.rows[0]) : null;
|
|
206
1542
|
}
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
1543
|
+
}
|
|
1544
|
+
class PostgresToolServerCredentialRepository {
|
|
1545
|
+
constructor(database) {
|
|
1546
|
+
this.database = database;
|
|
1547
|
+
}
|
|
1548
|
+
async find(serverId, userId) {
|
|
1549
|
+
const result = await this.database.query(`SELECT * FROM tool_server_credentials
|
|
1550
|
+
WHERE server_id = $1 AND user_id = $2`, [serverId, userId]);
|
|
1551
|
+
return result.rows[0] ? toolServerCredential(result.rows[0]) : null;
|
|
1552
|
+
}
|
|
1553
|
+
async upsert(credential) {
|
|
1554
|
+
await this.database.query(`INSERT INTO tool_server_credentials
|
|
1555
|
+
(id, server_id, user_id, secret, created_at, updated_at)
|
|
1556
|
+
VALUES ($1, $2, $3, $4, $5, $6)
|
|
1557
|
+
ON CONFLICT (server_id, user_id) DO UPDATE SET
|
|
1558
|
+
secret = EXCLUDED.secret,
|
|
1559
|
+
updated_at = EXCLUDED.updated_at`, [
|
|
1560
|
+
credential.id,
|
|
1561
|
+
credential.server_id,
|
|
1562
|
+
credential.user_id,
|
|
1563
|
+
credential.secret,
|
|
1564
|
+
credential.created_at,
|
|
1565
|
+
credential.updated_at,
|
|
1566
|
+
]);
|
|
1567
|
+
}
|
|
1568
|
+
async delete(serverId, userId) {
|
|
1569
|
+
return (changes((await this.database.query(`DELETE FROM tool_server_credentials
|
|
1570
|
+
WHERE server_id = $1 AND user_id = $2`, [serverId, userId])).rowCount) > 0);
|
|
211
1571
|
}
|
|
212
1572
|
}
|
|
213
|
-
class
|
|
1573
|
+
class PostgresToolApprovalRepository {
|
|
214
1574
|
constructor(database) {
|
|
215
1575
|
this.database = database;
|
|
216
1576
|
}
|
|
217
|
-
async
|
|
218
|
-
|
|
1577
|
+
async insert(approval) {
|
|
1578
|
+
await this.database.query(`INSERT INTO tool_approvals
|
|
1579
|
+
(id, user_id, session_id, server_id, tool_name, call_id,
|
|
1580
|
+
arguments_digest, scope, status, created_at, resolved_at, expires_at)
|
|
1581
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)`, [
|
|
1582
|
+
approval.id,
|
|
1583
|
+
approval.user_id,
|
|
1584
|
+
approval.session_id,
|
|
1585
|
+
approval.server_id,
|
|
1586
|
+
approval.tool_name,
|
|
1587
|
+
approval.call_id,
|
|
1588
|
+
approval.arguments_digest,
|
|
1589
|
+
approval.scope,
|
|
1590
|
+
approval.status,
|
|
1591
|
+
approval.created_at,
|
|
1592
|
+
approval.resolved_at,
|
|
1593
|
+
approval.expires_at,
|
|
1594
|
+
]);
|
|
1595
|
+
}
|
|
1596
|
+
async findByOwner(approvalId, userId) {
|
|
1597
|
+
const result = await this.database.query('SELECT * FROM tool_approvals WHERE id = $1 AND user_id = $2', [approvalId, userId]);
|
|
1598
|
+
return result.rows[0] ? toolApproval(result.rows[0]) : null;
|
|
1599
|
+
}
|
|
1600
|
+
async findStanding(userId, serverId, toolName, sessionId) {
|
|
1601
|
+
const result = await this.database.query(`SELECT * FROM tool_approvals
|
|
219
1602
|
WHERE user_id = $1
|
|
220
|
-
|
|
221
|
-
|
|
1603
|
+
AND status = 'approved'
|
|
1604
|
+
AND tool_name = $2
|
|
1605
|
+
AND ((server_id IS NULL AND $3::text IS NULL) OR server_id = $3)
|
|
1606
|
+
AND (scope = 'always'
|
|
1607
|
+
OR (scope = 'session' AND session_id IS NOT NULL AND session_id = $4))
|
|
1608
|
+
ORDER BY resolved_at DESC
|
|
1609
|
+
LIMIT 1`, [userId, toolName, serverId, sessionId]);
|
|
1610
|
+
return result.rows[0] ? toolApproval(result.rows[0]) : null;
|
|
222
1611
|
}
|
|
223
|
-
async
|
|
1612
|
+
async resolvePending(approvalId, userId, status, scope, resolvedAt) {
|
|
1613
|
+
const result = await this.database.query(`UPDATE tool_approvals
|
|
1614
|
+
SET status = $1, scope = $2, resolved_at = $3
|
|
1615
|
+
WHERE id = $4 AND user_id = $5 AND status = 'pending'
|
|
1616
|
+
AND (expires_at IS NULL OR expires_at > $3)
|
|
1617
|
+
RETURNING *`, [status, scope, resolvedAt, approvalId, userId]);
|
|
1618
|
+
return result.rows[0] ? toolApproval(result.rows[0]) : null;
|
|
1619
|
+
}
|
|
1620
|
+
async listPendingByOwner(userId, maximum) {
|
|
1621
|
+
const result = await this.database.query(`SELECT * FROM tool_approvals
|
|
1622
|
+
WHERE user_id = $1 AND status = 'pending'
|
|
1623
|
+
ORDER BY created_at ASC
|
|
1624
|
+
LIMIT $2`, [userId, maximum]);
|
|
1625
|
+
return result.rows.map(toolApproval);
|
|
1626
|
+
}
|
|
1627
|
+
async listStandingByOwner(userId, maximum) {
|
|
1628
|
+
const result = await this.database.query(`SELECT * FROM tool_approvals
|
|
1629
|
+
WHERE user_id = $1 AND status = 'approved'
|
|
1630
|
+
AND scope IN ('session', 'always')
|
|
1631
|
+
ORDER BY resolved_at DESC
|
|
1632
|
+
LIMIT $2`, [userId, maximum]);
|
|
1633
|
+
return result.rows.map(toolApproval);
|
|
1634
|
+
}
|
|
1635
|
+
async expirePending(now) {
|
|
1636
|
+
return changes((await this.database.query(`UPDATE tool_approvals
|
|
1637
|
+
SET status = 'expired', resolved_at = $1
|
|
1638
|
+
WHERE status = 'pending' AND expires_at IS NOT NULL
|
|
1639
|
+
AND expires_at <= $1`, [now])).rowCount);
|
|
1640
|
+
}
|
|
1641
|
+
async deleteByOwner(approvalId, userId) {
|
|
1642
|
+
return (changes((await this.database.query('DELETE FROM tool_approvals WHERE id = $1 AND user_id = $2', [approvalId, userId])).rowCount) > 0);
|
|
1643
|
+
}
|
|
1644
|
+
}
|
|
1645
|
+
class PostgresPromptRepository {
|
|
1646
|
+
constructor(database) {
|
|
1647
|
+
this.database = database;
|
|
1648
|
+
}
|
|
1649
|
+
async listByOwner(userId, maximum) {
|
|
1650
|
+
const result = await this.database.query(`SELECT * FROM prompts
|
|
1651
|
+
WHERE user_id = $1
|
|
1652
|
+
ORDER BY updated_at DESC
|
|
1653
|
+
LIMIT $2`, [userId, maximum]);
|
|
1654
|
+
return result.rows.map(prompt);
|
|
1655
|
+
}
|
|
1656
|
+
async findByOwner(promptId, userId) {
|
|
1657
|
+
const result = await this.database.query('SELECT * FROM prompts WHERE id = $1 AND user_id = $2', [promptId, userId]);
|
|
1658
|
+
return result.rows[0] ? prompt(result.rows[0]) : null;
|
|
1659
|
+
}
|
|
1660
|
+
async findById(promptId) {
|
|
1661
|
+
const result = await this.database.query('SELECT * FROM prompts WHERE id = $1', [promptId]);
|
|
1662
|
+
return result.rows[0] ? prompt(result.rows[0]) : null;
|
|
1663
|
+
}
|
|
1664
|
+
async findBySlug(userId, slug) {
|
|
1665
|
+
const result = await this.database.query('SELECT * FROM prompts WHERE user_id = $1 AND slug = $2', [userId, slug]);
|
|
1666
|
+
return result.rows[0] ? prompt(result.rows[0]) : null;
|
|
1667
|
+
}
|
|
1668
|
+
async replaceWithLimit(value, maximum, archivedVersion) {
|
|
224
1669
|
await this.database.transaction(async (client) => {
|
|
225
|
-
await lockOwner(client,
|
|
226
|
-
const
|
|
227
|
-
|
|
228
|
-
|
|
1670
|
+
await lockOwner(client, value.user_id);
|
|
1671
|
+
const existing = await client.query('SELECT user_id FROM prompts WHERE id = $1 FOR UPDATE', [value.id]);
|
|
1672
|
+
if (existing.rows[0] && existing.rows[0].user_id !== value.user_id) {
|
|
1673
|
+
throw new PersistenceResourceConflictError();
|
|
1674
|
+
}
|
|
1675
|
+
if (!existing.rows[0]) {
|
|
1676
|
+
const count = await client.query('SELECT COUNT(*)::text AS count FROM prompts WHERE user_id = $1', [value.user_id]);
|
|
1677
|
+
if (Number(count.rows[0]?.count || 0) >= maximum) {
|
|
1678
|
+
throw new PersistenceResourceLimitError('prompt', maximum);
|
|
1679
|
+
}
|
|
1680
|
+
}
|
|
1681
|
+
const result = await client.query(`INSERT INTO prompts
|
|
1682
|
+
(id, user_id, slug, title, description, content, variables, tags,
|
|
1683
|
+
version, created_at, updated_at)
|
|
1684
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)
|
|
229
1685
|
ON CONFLICT (id) DO UPDATE SET
|
|
230
|
-
|
|
1686
|
+
slug = EXCLUDED.slug,
|
|
1687
|
+
title = EXCLUDED.title,
|
|
1688
|
+
description = EXCLUDED.description,
|
|
1689
|
+
content = EXCLUDED.content,
|
|
1690
|
+
variables = EXCLUDED.variables,
|
|
1691
|
+
tags = EXCLUDED.tags,
|
|
1692
|
+
version = EXCLUDED.version,
|
|
231
1693
|
updated_at = EXCLUDED.updated_at
|
|
232
|
-
WHERE
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
1694
|
+
WHERE prompts.user_id = EXCLUDED.user_id`, [
|
|
1695
|
+
value.id,
|
|
1696
|
+
value.user_id,
|
|
1697
|
+
value.slug,
|
|
1698
|
+
value.title,
|
|
1699
|
+
value.description,
|
|
1700
|
+
value.content,
|
|
1701
|
+
value.variables,
|
|
1702
|
+
value.tags,
|
|
1703
|
+
value.version,
|
|
1704
|
+
value.created_at,
|
|
1705
|
+
value.updated_at,
|
|
238
1706
|
]);
|
|
239
1707
|
if (result.rowCount !== 1)
|
|
240
1708
|
throw new PersistenceResourceConflictError();
|
|
1709
|
+
if (archivedVersion) {
|
|
1710
|
+
await client.query(`INSERT INTO prompt_versions
|
|
1711
|
+
(id, prompt_id, version, content, variables, created_at)
|
|
1712
|
+
VALUES ($1, $2, $3, $4, $5, $6)
|
|
1713
|
+
ON CONFLICT (prompt_id, version) DO NOTHING`, [
|
|
1714
|
+
archivedVersion.id,
|
|
1715
|
+
archivedVersion.prompt_id,
|
|
1716
|
+
archivedVersion.version,
|
|
1717
|
+
archivedVersion.content,
|
|
1718
|
+
archivedVersion.variables,
|
|
1719
|
+
archivedVersion.created_at,
|
|
1720
|
+
]);
|
|
1721
|
+
}
|
|
241
1722
|
});
|
|
242
1723
|
}
|
|
243
|
-
async
|
|
244
|
-
|
|
1724
|
+
async listVersions(promptId, maximum) {
|
|
1725
|
+
const result = await this.database.query(`SELECT * FROM prompt_versions
|
|
1726
|
+
WHERE prompt_id = $1
|
|
1727
|
+
ORDER BY version DESC
|
|
1728
|
+
LIMIT $2`, [promptId, maximum]);
|
|
1729
|
+
return result.rows.map(promptVersion);
|
|
1730
|
+
}
|
|
1731
|
+
async findVersion(promptId, version) {
|
|
1732
|
+
const result = await this.database.query('SELECT * FROM prompt_versions WHERE prompt_id = $1 AND version = $2', [promptId, version]);
|
|
1733
|
+
return result.rows[0] ? promptVersion(result.rows[0]) : null;
|
|
1734
|
+
}
|
|
1735
|
+
async deleteByOwner(promptId, userId) {
|
|
1736
|
+
return (changes((await this.database.query('DELETE FROM prompts WHERE id = $1 AND user_id = $2', [promptId, userId])).rowCount) > 0);
|
|
245
1737
|
}
|
|
246
1738
|
}
|
|
247
|
-
class
|
|
1739
|
+
class PostgresSkillRepository {
|
|
248
1740
|
constructor(database) {
|
|
249
1741
|
this.database = database;
|
|
250
1742
|
}
|
|
251
1743
|
async listByOwner(userId, maximum) {
|
|
252
|
-
const result = await this.database.query(`SELECT * FROM
|
|
1744
|
+
const result = await this.database.query(`SELECT * FROM skills
|
|
253
1745
|
WHERE user_id = $1
|
|
254
1746
|
ORDER BY updated_at DESC
|
|
255
1747
|
LIMIT $2`, [userId, maximum]);
|
|
256
|
-
return result.rows.map(
|
|
1748
|
+
return result.rows.map(skill);
|
|
257
1749
|
}
|
|
258
|
-
async findByOwner(
|
|
259
|
-
const result = await this.database.query('SELECT * FROM
|
|
260
|
-
return result.rows[0] ?
|
|
1750
|
+
async findByOwner(skillId, userId) {
|
|
1751
|
+
const result = await this.database.query('SELECT * FROM skills WHERE id = $1 AND user_id = $2', [skillId, userId]);
|
|
1752
|
+
return result.rows[0] ? skill(result.rows[0]) : null;
|
|
261
1753
|
}
|
|
262
|
-
async
|
|
1754
|
+
async findById(skillId) {
|
|
1755
|
+
const result = await this.database.query('SELECT * FROM skills WHERE id = $1', [skillId]);
|
|
1756
|
+
return result.rows[0] ? skill(result.rows[0]) : null;
|
|
1757
|
+
}
|
|
1758
|
+
async findBySlug(userId, slug) {
|
|
1759
|
+
const result = await this.database.query('SELECT * FROM skills WHERE user_id = $1 AND slug = $2', [userId, slug]);
|
|
1760
|
+
return result.rows[0] ? skill(result.rows[0]) : null;
|
|
1761
|
+
}
|
|
1762
|
+
async replaceWithLimit(value, maximum, archivedVersion) {
|
|
263
1763
|
await this.database.transaction(async (client) => {
|
|
264
1764
|
await lockOwner(client, value.user_id);
|
|
265
|
-
const existing = await client.query('SELECT user_id FROM
|
|
1765
|
+
const existing = await client.query('SELECT user_id FROM skills WHERE id = $1 FOR UPDATE', [value.id]);
|
|
266
1766
|
if (existing.rows[0] && existing.rows[0].user_id !== value.user_id) {
|
|
267
1767
|
throw new PersistenceResourceConflictError();
|
|
268
1768
|
}
|
|
269
1769
|
if (!existing.rows[0]) {
|
|
270
|
-
const count = await client.query('SELECT COUNT(*)::text AS count FROM
|
|
1770
|
+
const count = await client.query('SELECT COUNT(*)::text AS count FROM skills WHERE user_id = $1', [value.user_id]);
|
|
271
1771
|
if (Number(count.rows[0]?.count || 0) >= maximum) {
|
|
272
|
-
throw new PersistenceResourceLimitError('
|
|
1772
|
+
throw new PersistenceResourceLimitError('skill', maximum);
|
|
273
1773
|
}
|
|
274
1774
|
}
|
|
275
|
-
const result = await client.query(`INSERT INTO
|
|
276
|
-
|
|
1775
|
+
const result = await client.query(`INSERT INTO skills
|
|
1776
|
+
(id, user_id, slug, name, description, instructions, enabled,
|
|
1777
|
+
version, created_at, updated_at)
|
|
1778
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
|
|
277
1779
|
ON CONFLICT (id) DO UPDATE SET
|
|
278
|
-
|
|
279
|
-
|
|
1780
|
+
slug = EXCLUDED.slug,
|
|
1781
|
+
name = EXCLUDED.name,
|
|
1782
|
+
description = EXCLUDED.description,
|
|
1783
|
+
instructions = EXCLUDED.instructions,
|
|
1784
|
+
enabled = EXCLUDED.enabled,
|
|
1785
|
+
version = EXCLUDED.version,
|
|
280
1786
|
updated_at = EXCLUDED.updated_at
|
|
281
|
-
WHERE
|
|
1787
|
+
WHERE skills.user_id = EXCLUDED.user_id`, [
|
|
282
1788
|
value.id,
|
|
283
1789
|
value.user_id,
|
|
284
|
-
value.
|
|
285
|
-
value.
|
|
1790
|
+
value.slug,
|
|
1791
|
+
value.name,
|
|
1792
|
+
value.description,
|
|
1793
|
+
value.instructions,
|
|
1794
|
+
value.enabled,
|
|
1795
|
+
value.version,
|
|
286
1796
|
value.created_at,
|
|
287
1797
|
value.updated_at,
|
|
288
1798
|
]);
|
|
289
1799
|
if (result.rowCount !== 1)
|
|
290
1800
|
throw new PersistenceResourceConflictError();
|
|
1801
|
+
if (archivedVersion) {
|
|
1802
|
+
await client.query(`INSERT INTO skill_versions
|
|
1803
|
+
(id, skill_id, version, instructions, created_at)
|
|
1804
|
+
VALUES ($1, $2, $3, $4, $5)
|
|
1805
|
+
ON CONFLICT (skill_id, version) DO NOTHING`, [
|
|
1806
|
+
archivedVersion.id,
|
|
1807
|
+
archivedVersion.skill_id,
|
|
1808
|
+
archivedVersion.version,
|
|
1809
|
+
archivedVersion.instructions,
|
|
1810
|
+
archivedVersion.created_at,
|
|
1811
|
+
]);
|
|
1812
|
+
}
|
|
291
1813
|
});
|
|
292
1814
|
}
|
|
293
|
-
async
|
|
294
|
-
const
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
};
|
|
300
|
-
if (patch.title !== undefined)
|
|
301
|
-
assign('title', patch.title);
|
|
302
|
-
if (patch.content !== undefined)
|
|
303
|
-
assign('content', patch.content);
|
|
304
|
-
assign('updated_at', patch.updated_at);
|
|
305
|
-
values.push(noteId, userId);
|
|
306
|
-
const result = await this.database.query(`UPDATE notes SET ${assignments.join(', ')}
|
|
307
|
-
WHERE id = $${values.length - 1} AND user_id = $${values.length}
|
|
308
|
-
RETURNING *`, values);
|
|
309
|
-
return result.rows[0] ? note(result.rows[0]) : null;
|
|
1815
|
+
async listVersions(skillId, maximum) {
|
|
1816
|
+
const result = await this.database.query(`SELECT * FROM skill_versions
|
|
1817
|
+
WHERE skill_id = $1
|
|
1818
|
+
ORDER BY version DESC
|
|
1819
|
+
LIMIT $2`, [skillId, maximum]);
|
|
1820
|
+
return result.rows.map(skillVersion);
|
|
310
1821
|
}
|
|
311
|
-
async
|
|
312
|
-
|
|
1822
|
+
async findVersion(skillId, version) {
|
|
1823
|
+
const result = await this.database.query('SELECT * FROM skill_versions WHERE skill_id = $1 AND version = $2', [skillId, version]);
|
|
1824
|
+
return result.rows[0] ? skillVersion(result.rows[0]) : null;
|
|
1825
|
+
}
|
|
1826
|
+
async deleteByOwner(skillId, userId) {
|
|
1827
|
+
return (changes((await this.database.query('DELETE FROM skills WHERE id = $1 AND user_id = $2', [skillId, userId])).rowCount) > 0);
|
|
1828
|
+
}
|
|
1829
|
+
}
|
|
1830
|
+
class PostgresSkillFileRepository {
|
|
1831
|
+
constructor(database) {
|
|
1832
|
+
this.database = database;
|
|
1833
|
+
}
|
|
1834
|
+
async listBySkill(skillId) {
|
|
1835
|
+
const result = await this.database.query(`SELECT * FROM skill_files
|
|
1836
|
+
WHERE skill_id = $1
|
|
1837
|
+
ORDER BY path ASC`, [skillId]);
|
|
1838
|
+
return result.rows.map(skillFile);
|
|
1839
|
+
}
|
|
1840
|
+
async find(skillId, path) {
|
|
1841
|
+
const result = await this.database.query('SELECT * FROM skill_files WHERE skill_id = $1 AND path = $2', [skillId, path]);
|
|
1842
|
+
return result.rows[0] ? skillFile(result.rows[0]) : null;
|
|
1843
|
+
}
|
|
1844
|
+
async upsert(file, maximumPerSkill) {
|
|
1845
|
+
await this.database.transaction(async (client) => {
|
|
1846
|
+
const existing = await client.query('SELECT id FROM skill_files WHERE skill_id = $1 AND path = $2 FOR UPDATE', [file.skill_id, file.path]);
|
|
1847
|
+
if (!existing.rows[0]) {
|
|
1848
|
+
const count = await client.query('SELECT COUNT(*)::text AS count FROM skill_files WHERE skill_id = $1', [file.skill_id]);
|
|
1849
|
+
if (Number(count.rows[0]?.count || 0) >= maximumPerSkill) {
|
|
1850
|
+
throw new PersistenceResourceLimitError('skill-file', maximumPerSkill);
|
|
1851
|
+
}
|
|
1852
|
+
}
|
|
1853
|
+
await client.query(`INSERT INTO skill_files
|
|
1854
|
+
(id, skill_id, path, content, size, created_at, updated_at)
|
|
1855
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7)
|
|
1856
|
+
ON CONFLICT (skill_id, path) DO UPDATE SET
|
|
1857
|
+
content = EXCLUDED.content,
|
|
1858
|
+
size = EXCLUDED.size,
|
|
1859
|
+
updated_at = EXCLUDED.updated_at`, [
|
|
1860
|
+
file.id,
|
|
1861
|
+
file.skill_id,
|
|
1862
|
+
file.path,
|
|
1863
|
+
file.content,
|
|
1864
|
+
file.size,
|
|
1865
|
+
file.created_at,
|
|
1866
|
+
file.updated_at,
|
|
1867
|
+
]);
|
|
1868
|
+
});
|
|
1869
|
+
}
|
|
1870
|
+
async replaceAllForSkill(skillId, files) {
|
|
1871
|
+
await this.database.transaction(async (client) => {
|
|
1872
|
+
await client.query('DELETE FROM skill_files WHERE skill_id = $1', [
|
|
1873
|
+
skillId,
|
|
1874
|
+
]);
|
|
1875
|
+
for (const file of files) {
|
|
1876
|
+
if (file.skill_id !== skillId) {
|
|
1877
|
+
throw new Error('A skill file does not belong to its skill');
|
|
1878
|
+
}
|
|
1879
|
+
await client.query(`INSERT INTO skill_files
|
|
1880
|
+
(id, skill_id, path, content, size, created_at, updated_at)
|
|
1881
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7)`, [
|
|
1882
|
+
file.id,
|
|
1883
|
+
file.skill_id,
|
|
1884
|
+
file.path,
|
|
1885
|
+
file.content,
|
|
1886
|
+
file.size,
|
|
1887
|
+
file.created_at,
|
|
1888
|
+
file.updated_at,
|
|
1889
|
+
]);
|
|
1890
|
+
}
|
|
1891
|
+
});
|
|
1892
|
+
}
|
|
1893
|
+
async delete(skillId, path) {
|
|
1894
|
+
return (changes((await this.database.query('DELETE FROM skill_files WHERE skill_id = $1 AND path = $2', [skillId, path])).rowCount) > 0);
|
|
313
1895
|
}
|
|
314
1896
|
}
|
|
315
1897
|
class PostgresSessionFolderRepository {
|
|
@@ -465,6 +2047,10 @@ const ARCHIVE_TABLES = {
|
|
|
465
2047
|
'knowledge-collection': 'knowledge_collections',
|
|
466
2048
|
document: 'documents',
|
|
467
2049
|
persona: 'personas',
|
|
2050
|
+
prompt: 'prompts',
|
|
2051
|
+
skill: 'skills',
|
|
2052
|
+
'tool-server': 'tool_servers',
|
|
2053
|
+
calendar: 'calendars',
|
|
468
2054
|
};
|
|
469
2055
|
class PostgresDataArchiveRepository {
|
|
470
2056
|
constructor(database) {
|
|
@@ -756,14 +2342,337 @@ class PostgresDataArchiveRepository {
|
|
|
756
2342
|
});
|
|
757
2343
|
}
|
|
758
2344
|
}
|
|
2345
|
+
const modelTariff = (row) => ({
|
|
2346
|
+
...row,
|
|
2347
|
+
input_per_million: row.input_per_million === null ? null : Number(row.input_per_million),
|
|
2348
|
+
output_per_million: row.output_per_million === null ? null : Number(row.output_per_million),
|
|
2349
|
+
unit_price: row.unit_price === null ? null : Number(row.unit_price),
|
|
2350
|
+
effective_from: number(row.effective_from, 'tariff effective_from'),
|
|
2351
|
+
created_at: number(row.created_at, 'tariff created_at'),
|
|
2352
|
+
});
|
|
2353
|
+
const usageBudget = (row) => ({
|
|
2354
|
+
...row,
|
|
2355
|
+
amount_usd: Number(row.amount_usd),
|
|
2356
|
+
created_at: number(row.created_at, 'budget created_at'),
|
|
2357
|
+
updated_at: number(row.updated_at, 'budget updated_at'),
|
|
2358
|
+
});
|
|
2359
|
+
const messageFeedback = (row) => ({
|
|
2360
|
+
...row,
|
|
2361
|
+
rating: number(row.rating, 'feedback rating'),
|
|
2362
|
+
created_at: number(row.created_at, 'feedback created_at'),
|
|
2363
|
+
updated_at: number(row.updated_at, 'feedback updated_at'),
|
|
2364
|
+
});
|
|
2365
|
+
const arenaVote = (row) => ({
|
|
2366
|
+
...row,
|
|
2367
|
+
created_at: number(row.created_at, 'arena vote created_at'),
|
|
2368
|
+
});
|
|
2369
|
+
const evalSet = (row) => ({
|
|
2370
|
+
...row,
|
|
2371
|
+
created_at: number(row.created_at, 'eval set created_at'),
|
|
2372
|
+
updated_at: number(row.updated_at, 'eval set updated_at'),
|
|
2373
|
+
});
|
|
2374
|
+
const evalRun = (row) => ({
|
|
2375
|
+
...row,
|
|
2376
|
+
created_at: number(row.created_at, 'eval run created_at'),
|
|
2377
|
+
updated_at: number(row.updated_at, 'eval run updated_at'),
|
|
2378
|
+
completed_at: row.completed_at === null
|
|
2379
|
+
? null
|
|
2380
|
+
: number(row.completed_at, 'eval run completed_at'),
|
|
2381
|
+
});
|
|
2382
|
+
class PostgresModelTariffRepository {
|
|
2383
|
+
constructor(database) {
|
|
2384
|
+
this.database = database;
|
|
2385
|
+
}
|
|
2386
|
+
async listAll(maximum) {
|
|
2387
|
+
const result = await this.database.query(`SELECT * FROM model_tariffs
|
|
2388
|
+
ORDER BY plugin_id ASC, model ASC, effective_from DESC
|
|
2389
|
+
LIMIT $1`, [maximum]);
|
|
2390
|
+
return result.rows.map(modelTariff);
|
|
2391
|
+
}
|
|
2392
|
+
async insert(tariff) {
|
|
2393
|
+
await this.database.query(`INSERT INTO model_tariffs
|
|
2394
|
+
(id, plugin_id, model, input_per_million, output_per_million,
|
|
2395
|
+
unit_price, currency, effective_from, created_by, created_at)
|
|
2396
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)`, [
|
|
2397
|
+
tariff.id,
|
|
2398
|
+
tariff.plugin_id,
|
|
2399
|
+
tariff.model,
|
|
2400
|
+
tariff.input_per_million,
|
|
2401
|
+
tariff.output_per_million,
|
|
2402
|
+
tariff.unit_price,
|
|
2403
|
+
tariff.currency,
|
|
2404
|
+
tariff.effective_from,
|
|
2405
|
+
tariff.created_by,
|
|
2406
|
+
tariff.created_at,
|
|
2407
|
+
]);
|
|
2408
|
+
}
|
|
2409
|
+
async deleteById(tariffId) {
|
|
2410
|
+
const result = await this.database.query('DELETE FROM model_tariffs WHERE id = $1', [tariffId]);
|
|
2411
|
+
return changes(result.rowCount) > 0;
|
|
2412
|
+
}
|
|
2413
|
+
}
|
|
2414
|
+
class PostgresUsageBudgetRepository {
|
|
2415
|
+
constructor(database) {
|
|
2416
|
+
this.database = database;
|
|
2417
|
+
}
|
|
2418
|
+
async listAll(maximum) {
|
|
2419
|
+
const result = await this.database.query(`SELECT * FROM usage_budgets
|
|
2420
|
+
ORDER BY created_at ASC, id ASC
|
|
2421
|
+
LIMIT $1`, [maximum]);
|
|
2422
|
+
return result.rows.map(usageBudget);
|
|
2423
|
+
}
|
|
2424
|
+
async findById(budgetId) {
|
|
2425
|
+
const result = await this.database.query('SELECT * FROM usage_budgets WHERE id = $1', [budgetId]);
|
|
2426
|
+
return result.rows[0] ? usageBudget(result.rows[0]) : null;
|
|
2427
|
+
}
|
|
2428
|
+
async replace(budget) {
|
|
2429
|
+
await this.database.query(`INSERT INTO usage_budgets
|
|
2430
|
+
(id, name, principal_type, principal_id, period, amount_usd,
|
|
2431
|
+
mode, created_by, created_at, updated_at)
|
|
2432
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
|
|
2433
|
+
ON CONFLICT (id) DO UPDATE SET
|
|
2434
|
+
name = EXCLUDED.name,
|
|
2435
|
+
principal_type = EXCLUDED.principal_type,
|
|
2436
|
+
principal_id = EXCLUDED.principal_id,
|
|
2437
|
+
period = EXCLUDED.period,
|
|
2438
|
+
amount_usd = EXCLUDED.amount_usd,
|
|
2439
|
+
mode = EXCLUDED.mode,
|
|
2440
|
+
updated_at = EXCLUDED.updated_at`, [
|
|
2441
|
+
budget.id,
|
|
2442
|
+
budget.name,
|
|
2443
|
+
budget.principal_type,
|
|
2444
|
+
budget.principal_id,
|
|
2445
|
+
budget.period,
|
|
2446
|
+
budget.amount_usd,
|
|
2447
|
+
budget.mode,
|
|
2448
|
+
budget.created_by,
|
|
2449
|
+
budget.created_at,
|
|
2450
|
+
budget.updated_at,
|
|
2451
|
+
]);
|
|
2452
|
+
}
|
|
2453
|
+
async deleteById(budgetId) {
|
|
2454
|
+
const result = await this.database.query('DELETE FROM usage_budgets WHERE id = $1', [budgetId]);
|
|
2455
|
+
return changes(result.rowCount) > 0;
|
|
2456
|
+
}
|
|
2457
|
+
}
|
|
2458
|
+
class PostgresMessageFeedbackRepository {
|
|
2459
|
+
constructor(database) {
|
|
2460
|
+
this.database = database;
|
|
2461
|
+
}
|
|
2462
|
+
async upsertByMessage(feedback) {
|
|
2463
|
+
await this.database.query(`INSERT INTO message_feedback
|
|
2464
|
+
(id, user_id, session_id, message_id, rating, tags, comment,
|
|
2465
|
+
model, plugin_id, snapshot, created_at, updated_at)
|
|
2466
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)
|
|
2467
|
+
ON CONFLICT (user_id, message_id) DO UPDATE SET
|
|
2468
|
+
rating = EXCLUDED.rating,
|
|
2469
|
+
tags = EXCLUDED.tags,
|
|
2470
|
+
comment = EXCLUDED.comment,
|
|
2471
|
+
model = EXCLUDED.model,
|
|
2472
|
+
plugin_id = EXCLUDED.plugin_id,
|
|
2473
|
+
snapshot = EXCLUDED.snapshot,
|
|
2474
|
+
updated_at = EXCLUDED.updated_at`, [
|
|
2475
|
+
feedback.id,
|
|
2476
|
+
feedback.user_id,
|
|
2477
|
+
feedback.session_id,
|
|
2478
|
+
feedback.message_id,
|
|
2479
|
+
feedback.rating,
|
|
2480
|
+
feedback.tags,
|
|
2481
|
+
feedback.comment,
|
|
2482
|
+
feedback.model,
|
|
2483
|
+
feedback.plugin_id,
|
|
2484
|
+
feedback.snapshot,
|
|
2485
|
+
feedback.created_at,
|
|
2486
|
+
feedback.updated_at,
|
|
2487
|
+
]);
|
|
2488
|
+
}
|
|
2489
|
+
async deleteByMessage(userId, messageId) {
|
|
2490
|
+
const result = await this.database.query('DELETE FROM message_feedback WHERE user_id = $1 AND message_id = $2', [userId, messageId]);
|
|
2491
|
+
return changes(result.rowCount) > 0;
|
|
2492
|
+
}
|
|
2493
|
+
async listByOwner(userId, maximum) {
|
|
2494
|
+
const result = await this.database.query(`SELECT * FROM message_feedback
|
|
2495
|
+
WHERE user_id = $1
|
|
2496
|
+
ORDER BY created_at DESC, id DESC
|
|
2497
|
+
LIMIT $2`, [userId, maximum]);
|
|
2498
|
+
return result.rows.map(messageFeedback);
|
|
2499
|
+
}
|
|
2500
|
+
async listAll(maximum) {
|
|
2501
|
+
const result = await this.database.query(`SELECT * FROM message_feedback
|
|
2502
|
+
ORDER BY created_at DESC, id DESC
|
|
2503
|
+
LIMIT $1`, [maximum]);
|
|
2504
|
+
return result.rows.map(messageFeedback);
|
|
2505
|
+
}
|
|
2506
|
+
}
|
|
2507
|
+
class PostgresArenaVoteRepository {
|
|
2508
|
+
constructor(database) {
|
|
2509
|
+
this.database = database;
|
|
2510
|
+
}
|
|
2511
|
+
async insertOnce(vote) {
|
|
2512
|
+
const result = await this.database.query(`INSERT INTO arena_votes
|
|
2513
|
+
(id, user_id, compare_group, model_a, model_b, winner, created_at)
|
|
2514
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7)
|
|
2515
|
+
ON CONFLICT (user_id, compare_group) DO NOTHING`, [
|
|
2516
|
+
vote.id,
|
|
2517
|
+
vote.user_id,
|
|
2518
|
+
vote.compare_group,
|
|
2519
|
+
vote.model_a,
|
|
2520
|
+
vote.model_b,
|
|
2521
|
+
vote.winner,
|
|
2522
|
+
vote.created_at,
|
|
2523
|
+
]);
|
|
2524
|
+
return changes(result.rowCount) > 0;
|
|
2525
|
+
}
|
|
2526
|
+
async listAllOrdered(maximum) {
|
|
2527
|
+
const result = await this.database.query(`SELECT * FROM arena_votes
|
|
2528
|
+
ORDER BY created_at ASC, id ASC
|
|
2529
|
+
LIMIT $1`, [maximum]);
|
|
2530
|
+
return result.rows.map(arenaVote);
|
|
2531
|
+
}
|
|
2532
|
+
}
|
|
2533
|
+
class PostgresEvalSetRepository {
|
|
2534
|
+
constructor(database) {
|
|
2535
|
+
this.database = database;
|
|
2536
|
+
}
|
|
2537
|
+
async listByOwner(userId, maximum) {
|
|
2538
|
+
const result = await this.database.query(`SELECT * FROM eval_sets
|
|
2539
|
+
WHERE user_id = $1
|
|
2540
|
+
ORDER BY updated_at DESC, id DESC
|
|
2541
|
+
LIMIT $2`, [userId, maximum]);
|
|
2542
|
+
return result.rows.map(evalSet);
|
|
2543
|
+
}
|
|
2544
|
+
async findByOwner(setId, userId) {
|
|
2545
|
+
const result = await this.database.query('SELECT * FROM eval_sets WHERE id = $1 AND user_id = $2', [setId, userId]);
|
|
2546
|
+
return result.rows[0] ? evalSet(result.rows[0]) : null;
|
|
2547
|
+
}
|
|
2548
|
+
async replaceWithLimit(value, maximum) {
|
|
2549
|
+
await this.database.transaction(async (client) => {
|
|
2550
|
+
await lockOwner(client, value.user_id);
|
|
2551
|
+
const existing = await client.query('SELECT user_id FROM eval_sets WHERE id = $1 FOR UPDATE', [value.id]);
|
|
2552
|
+
if (existing.rows[0] && existing.rows[0].user_id !== value.user_id) {
|
|
2553
|
+
throw new PersistenceResourceConflictError();
|
|
2554
|
+
}
|
|
2555
|
+
if (!existing.rows[0]) {
|
|
2556
|
+
const count = await client.query('SELECT COUNT(*)::text AS count FROM eval_sets WHERE user_id = $1', [value.user_id]);
|
|
2557
|
+
if (Number(count.rows[0]?.count || 0) >= maximum) {
|
|
2558
|
+
throw new PersistenceResourceLimitError('eval-set', maximum);
|
|
2559
|
+
}
|
|
2560
|
+
}
|
|
2561
|
+
const result = await client.query(`INSERT INTO eval_sets
|
|
2562
|
+
(id, user_id, name, description, items, created_at, updated_at)
|
|
2563
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7)
|
|
2564
|
+
ON CONFLICT (id) DO UPDATE SET
|
|
2565
|
+
name = EXCLUDED.name,
|
|
2566
|
+
description = EXCLUDED.description,
|
|
2567
|
+
items = EXCLUDED.items,
|
|
2568
|
+
updated_at = EXCLUDED.updated_at
|
|
2569
|
+
WHERE eval_sets.user_id = EXCLUDED.user_id`, [
|
|
2570
|
+
value.id,
|
|
2571
|
+
value.user_id,
|
|
2572
|
+
value.name,
|
|
2573
|
+
value.description,
|
|
2574
|
+
value.items,
|
|
2575
|
+
value.created_at,
|
|
2576
|
+
value.updated_at,
|
|
2577
|
+
]);
|
|
2578
|
+
if (result.rowCount !== 1)
|
|
2579
|
+
throw new PersistenceResourceConflictError();
|
|
2580
|
+
});
|
|
2581
|
+
}
|
|
2582
|
+
async deleteByOwner(setId, userId) {
|
|
2583
|
+
const result = await this.database.query('DELETE FROM eval_sets WHERE id = $1 AND user_id = $2', [setId, userId]);
|
|
2584
|
+
return changes(result.rowCount) > 0;
|
|
2585
|
+
}
|
|
2586
|
+
}
|
|
2587
|
+
class PostgresEvalRunRepository {
|
|
2588
|
+
constructor(database) {
|
|
2589
|
+
this.database = database;
|
|
2590
|
+
}
|
|
2591
|
+
async listByOwner(userId, maximum) {
|
|
2592
|
+
const result = await this.database.query(`SELECT * FROM eval_runs
|
|
2593
|
+
WHERE user_id = $1
|
|
2594
|
+
ORDER BY created_at DESC, id DESC
|
|
2595
|
+
LIMIT $2`, [userId, maximum]);
|
|
2596
|
+
return result.rows.map(evalRun);
|
|
2597
|
+
}
|
|
2598
|
+
async listBySet(setId, userId, maximum) {
|
|
2599
|
+
const result = await this.database.query(`SELECT * FROM eval_runs
|
|
2600
|
+
WHERE set_id = $1 AND user_id = $2
|
|
2601
|
+
ORDER BY created_at DESC, id DESC
|
|
2602
|
+
LIMIT $3`, [setId, userId, maximum]);
|
|
2603
|
+
return result.rows.map(evalRun);
|
|
2604
|
+
}
|
|
2605
|
+
async findByOwner(runId, userId) {
|
|
2606
|
+
const result = await this.database.query('SELECT * FROM eval_runs WHERE id = $1 AND user_id = $2', [runId, userId]);
|
|
2607
|
+
return result.rows[0] ? evalRun(result.rows[0]) : null;
|
|
2608
|
+
}
|
|
2609
|
+
async insert(run) {
|
|
2610
|
+
await this.database.query(`INSERT INTO eval_runs
|
|
2611
|
+
(id, set_id, user_id, label, plugin_id, model, status, results,
|
|
2612
|
+
error, created_at, updated_at, completed_at)
|
|
2613
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)`, [
|
|
2614
|
+
run.id,
|
|
2615
|
+
run.set_id,
|
|
2616
|
+
run.user_id,
|
|
2617
|
+
run.label,
|
|
2618
|
+
run.plugin_id,
|
|
2619
|
+
run.model,
|
|
2620
|
+
run.status,
|
|
2621
|
+
run.results,
|
|
2622
|
+
run.error,
|
|
2623
|
+
run.created_at,
|
|
2624
|
+
run.updated_at,
|
|
2625
|
+
run.completed_at,
|
|
2626
|
+
]);
|
|
2627
|
+
}
|
|
2628
|
+
async update(runId, userId, updates) {
|
|
2629
|
+
const result = await this.database.query(`UPDATE eval_runs SET
|
|
2630
|
+
status = $1,
|
|
2631
|
+
results = COALESCE($2, results),
|
|
2632
|
+
error = $3,
|
|
2633
|
+
updated_at = $4,
|
|
2634
|
+
completed_at = COALESCE($5, completed_at)
|
|
2635
|
+
WHERE id = $6 AND user_id = $7`, [
|
|
2636
|
+
updates.status,
|
|
2637
|
+
updates.results ?? null,
|
|
2638
|
+
updates.error ?? null,
|
|
2639
|
+
updates.updated_at,
|
|
2640
|
+
updates.completed_at ?? null,
|
|
2641
|
+
runId,
|
|
2642
|
+
userId,
|
|
2643
|
+
]);
|
|
2644
|
+
return changes(result.rowCount) > 0;
|
|
2645
|
+
}
|
|
2646
|
+
}
|
|
759
2647
|
export const createPostgresResourceRepositories = (database) => ({
|
|
760
2648
|
chatSessions: new PostgresChatSessionRepository(database),
|
|
761
2649
|
knowledgeCollections: new PostgresKnowledgeCollectionRepository(database),
|
|
762
2650
|
notes: new PostgresNoteRepository(database),
|
|
2651
|
+
calendars: new PostgresCalendarRepository(database),
|
|
2652
|
+
calendarEvents: new PostgresCalendarEventRepository(database),
|
|
2653
|
+
channels: new PostgresChannelRepository(database),
|
|
2654
|
+
channelMessages: new PostgresChannelMessageRepository(database),
|
|
2655
|
+
notifications: new PostgresNotificationRepository(database),
|
|
2656
|
+
webhookTargets: new PostgresWebhookTargetRepository(database),
|
|
2657
|
+
automations: new PostgresAutomationRepository(database),
|
|
2658
|
+
automationRuns: new PostgresAutomationRunRepository(database),
|
|
2659
|
+
toolServers: new PostgresToolServerRepository(database),
|
|
2660
|
+
toolServerTools: new PostgresToolServerToolRepository(database),
|
|
2661
|
+
toolServerCredentials: new PostgresToolServerCredentialRepository(database),
|
|
2662
|
+
toolApprovals: new PostgresToolApprovalRepository(database),
|
|
2663
|
+
prompts: new PostgresPromptRepository(database),
|
|
2664
|
+
skills: new PostgresSkillRepository(database),
|
|
2665
|
+
skillFiles: new PostgresSkillFileRepository(database),
|
|
763
2666
|
sessionFolders: new PostgresSessionFolderRepository(database),
|
|
764
2667
|
preferences: new PostgresPreferenceRepository(database),
|
|
765
2668
|
systemSettings: new PostgresSystemSettingRepository(database),
|
|
766
2669
|
archive: new PostgresDataArchiveRepository(database),
|
|
2670
|
+
modelTariffs: new PostgresModelTariffRepository(database),
|
|
2671
|
+
usageBudgets: new PostgresUsageBudgetRepository(database),
|
|
2672
|
+
messageFeedback: new PostgresMessageFeedbackRepository(database),
|
|
2673
|
+
arenaVotes: new PostgresArenaVoteRepository(database),
|
|
2674
|
+
evalSets: new PostgresEvalSetRepository(database),
|
|
2675
|
+
evalRuns: new PostgresEvalRunRepository(database),
|
|
767
2676
|
});
|
|
768
2677
|
/** Create transaction-scoped repositories over one pinned pooled client. */
|
|
769
2678
|
export const createPostgresTransactionalResourceRepositories = (database, client) => {
|