From 623319aa75d70c6b16bc55a2683d2179cb77559b Mon Sep 17 00:00:00 2001 From: Recoup Agent Date: Fri, 13 Mar 2026 13:55:25 +0000 Subject: [PATCH] agent: @U0AJM7X8FBR API - slack client we want to expand our current Slack inte --- app/.well-known/farcaster.json/route.ts | 3 + app/api/account-emails/route.ts | 20 +- app/api/agent-creator/route.ts | 8 +- app/api/agent-templates/favorites/route.ts | 10 +- app/api/agent-templates/route.ts | 24 +- app/api/agentkit/get/route.ts | 4 + app/api/agentkit/run/route.ts | 14 +- app/api/agents/route.ts | 14 +- app/api/apify/route.ts | 10 +- app/api/artist/get_fan_segments/route.ts | 8 +- app/api/artist/pin/route.ts | 6 +- app/api/artist/profile/route.ts | 8 +- app/api/artist/remove/route.ts | 4 + app/api/auth/callback/google/route.ts | 42 +- app/api/chat/create/route.ts | 12 +- app/api/chat/generate/route.ts | 3 + app/api/chat/route.ts | 3 + app/api/credits/get/route.ts | 4 + app/api/cron/recoupDailyStats/route.ts | 5 +- app/api/files/delete/route.ts | 15 +- app/api/files/folder/route.ts | 18 +- app/api/files/get-signed-url/route.ts | 23 +- app/api/files/list/route.ts | 9 +- app/api/files/record/route.ts | 6 +- app/api/files/signed-url/route.ts | 13 +- app/api/files/update/route.ts | 32 +- app/api/funnel_analysis/route.ts | 4 + app/api/get_running_agent/route.ts | 4 + app/api/in_process/createCollection.ts | 17 +- app/api/memories/create/route.ts | 4 + app/api/memories/delete-trailing/route.ts | 13 +- app/api/memories/get/route.ts | 6 +- app/api/prompts/suggestions/route.ts | 4 +- app/api/room/delete/route.ts | 23 +- app/api/roomSegment/route.ts | 8 +- app/api/scheduled-actions/delete/route.ts | 15 +- app/api/segment_report/route.ts | 4 + app/api/segments/create/route.ts | 11 +- app/api/segments/route.ts | 9 +- app/api/storage/signed-upload-url/route.ts | 10 +- app/api/storage/signed-url/route.ts | 16 +- app/api/storage/upload-by-key/route.ts | 6 +- app/api/stripe/portal/create/route.ts | 9 +- app/api/stripe/session/checked/route.ts | 4 + app/api/stripe/session/create/route.ts | 4 + app/api/stripe/session/list/route.ts | 4 + app/api/subscription/status/route.ts | 5 +- app/api/twilio/sms/route.ts | 8 +- app/api/upload/route.ts | 6 +- app/api/workspace/create/route.ts | 3 +- app/api/youtube/channel-info/route.ts | 28 +- app/api/youtube/logout/route.ts | 23 +- components/Agents/useAgentCard.ts | 20 +- components/Agents/useAgentData.ts | 15 +- components/Agents/useAgentToggleFavorite.ts | 20 +- components/Files/types.ts | 3 - .../Sidebar/RecentChats/useRecentChats.ts | 30 +- .../dialogs/tasks/useTaskDetailsDialog.ts | 9 +- .../VercelChat/mapKnowledgeToOptions.ts | 8 +- components/VercelChat/mentionsStyles.ts | 2 +- components/VercelChat/parseMentionedIds.ts | 4 +- .../tools/segment-fans/animations.ts | 18 +- components/hooks/useCommentsResult.ts | 11 +- hooks/useAccountOrganizations.ts | 6 +- hooks/useAddArtistToOrganization.ts | 5 +- hooks/useApiKey.ts | 5 +- hooks/useArtistAgents.ts | 6 +- hooks/useArtistCatalogSongs.ts | 7 +- hooks/useArtistConnectorCallback.ts | 10 +- hooks/useArtistFans.ts | 18 +- hooks/useArtistFilesForMentions.ts | 15 +- hooks/useArtistFromRoom.ts | 23 +- hooks/useArtistImage.ts | 8 +- hooks/useArtistInstruction.ts | 6 +- hooks/useArtistKnowledge.ts | 6 +- hooks/useArtistPosts.ts | 10 +- hooks/useArtistSegments.ts | 10 +- hooks/useArtistSocials.ts | 12 +- hooks/useAttachments.ts | 7 +- hooks/useAutoCollapse.ts | 12 +- hooks/useAutoScroll.ts | 2 + hooks/useBatchSignedUrls.ts | 28 +- hooks/useCatalogSongs.ts | 17 +- hooks/useCatalogSongsFileSelect.ts | 8 +- hooks/useChatTransport.ts | 3 + hooks/useClickOutside.ts | 4 +- hooks/useConnectorHandlers.ts | 6 + hooks/useConnectors.ts | 6 +- hooks/useCopy.ts | 1 + hooks/useCreateArtistTool.ts | 12 +- hooks/useCreateOrganization.ts | 1 - hooks/useCreateSegments.ts | 7 +- hooks/useCreateWorkspaceModal.ts | 1 - hooks/useCronEditor.ts | 30 +- hooks/useDeleteSandbox.ts | 3 + hooks/useDeleteScheduledAction.ts | 4 +- hooks/useDragAndDrop.ts | 62 +- hooks/useDurationTracking.ts | 5 +- hooks/useFileContent.ts | 10 +- hooks/useFileEdit.ts | 44 +- hooks/useFileMentionSuggestions.ts | 30 +- hooks/useFilesManager.ts | 54 +- hooks/useFilesPath.ts | 6 +- hooks/useImageDownloader.ts | 54 +- hooks/useKeyboardSave.ts | 8 +- hooks/useKnowledgeEditor.ts | 2 +- hooks/useMermaid.ts | 203 +- hooks/useMessageLoader.ts | 7 +- hooks/useMiniApp.ts | 2 +- hooks/useMobileDetection.ts | 14 +- hooks/useObserverTarget.ts | 10 +- hooks/useOrgSettings.ts | 76 +- hooks/useOutsideClick.ts | 24 +- hooks/useProStatus.ts | 3 +- hooks/usePulseToggle.ts | 18 +- hooks/usePureFileAttachments.ts | 11 +- hooks/useRenameModal.ts | 29 +- hooks/useReportData.ts | 4 +- hooks/useResearchTimer.ts | 9 +- hooks/useSandboxFileContent.ts | 3 + hooks/useSandboxes.ts | 3 + hooks/useSaveKnowledgeEdit.ts | 10 +- hooks/useScheduledActions.ts | 4 +- hooks/useSidebarPin.ts | 2 +- hooks/useSignedUrl.ts | 11 +- hooks/useSongsByIsrc.ts | 5 +- hooks/useTaskRunStatus.ts | 9 +- hooks/useTaskRuns.ts | 3 + hooks/useTextAttachments.ts | 20 +- hooks/useTextFileContent.ts | 8 +- hooks/useTypingAnimation.ts | 37 +- hooks/useUpdateFile.ts | 8 +- hooks/useUpdateScheduledAction.ts | 4 +- hooks/useVercelChat.ts | 111 +- hooks/useVisibilityDelay.ts | 8 +- hooks/useYouTubeLoginSuccess.ts | 8 +- hooks/useYoutubeStatus.ts | 10 +- lib/__tests__/getConversations.test.ts | 4 +- lib/admin.ts | 4 +- lib/agent-templates/fetchAgentTemplates.ts | 4 +- lib/agent/validateEnvironment.ts | 16 +- lib/ai/featuredModels.ts | 6 +- lib/ai/getAvailableModels.ts | 7 +- lib/ai/getFalModels.ts | 9 +- lib/ai/isFreeModel.ts | 4 +- lib/ai/organizeModels.ts | 16 +- lib/api/artist/getArtistSocials.ts | 8 +- lib/apify/comments/getExistingPostComments.ts | 13 +- .../comments/getOrCreatePostsForComments.ts | 19 +- .../comments/getOrCreateSocialsForComments.ts | 53 +- .../handleInstagramCommentsScraper.ts | 29 +- .../comments/runInstagramCommentsScraper.ts | 12 +- .../comments/saveApifyInstagramComments.ts | 25 +- lib/apify/handleApifyWebhook.ts | 5 +- .../handleInstagramProfileFollowUpRuns.ts | 25 +- .../handleInstagramProfileScraperResults.ts | 15 +- .../posts/runInstagramProfilesScraper.ts | 12 +- lib/apify/posts/saveApifyInstagramPosts.ts | 9 +- lib/apify/sendApifyWebhookEmail.ts | 9 +- lib/apify/types.ts | 2 +- lib/arweave/arweave.ts | 4 + lib/arweave/gateway.ts | 15 +- lib/arweave/ipfs.ts | 24 + lib/arweave/uploadLinkToArweave.ts | 6 +- lib/arweave/uploadMetadataJson.ts | 8 +- lib/arweave/uploadToArweave.ts | 10 +- lib/browser/buildResponseText.ts | 17 +- lib/browser/captureScreenshot.ts | 11 +- lib/browser/constants.ts | 1 - lib/browser/detectPlatform.ts | 3 +- lib/browser/dismissLoginModal.ts | 3 +- lib/browser/extractPageData.ts | 9 +- lib/browser/formatActionsToString.ts | 19 +- lib/browser/formatFieldName.ts | 25 +- lib/browser/formatFieldValue.ts | 37 +- lib/browser/getBrowserResultType.ts | 18 +- lib/browser/getInitialSteps.ts | 5 +- lib/browser/getPlatformInfo.ts | 7 +- lib/browser/getTaskDescription.ts | 9 +- lib/browser/initStagehand.ts | 6 +- lib/browser/isBlockedStartUrl.ts | 27 +- lib/browser/isPlainObject.ts | 9 +- lib/browser/isPriorityField.ts | 3 +- lib/browser/normalizeInstagramUrl.ts | 11 +- lib/browser/performPageSetup.ts | 12 +- lib/browser/priorityFields.ts | 13 +- lib/browser/schemaToZod.ts | 7 +- lib/browser/simulateHumanScrolling.ts | 9 +- lib/browser/uploadScreenshot.ts | 8 +- lib/browser/withBrowser.ts | 9 +- lib/catalog/analyzeCatalogBatch.ts | 15 +- lib/catalog/analyzeFullCatalog.ts | 10 +- lib/catalog/createCatalogResult.ts | 8 +- lib/catalog/createErrorResult.ts | 8 +- lib/catalog/createSearchResult.ts | 9 +- lib/catalog/formatArtists.ts | 9 +- lib/catalog/formatCatalogSongsAsCSV.ts | 6 +- lib/catalog/getCatalogDataAsCSV.ts | 2 + lib/catalog/getCatalogSongs.ts | 24 +- lib/catalog/getCatalogs.ts | 10 +- lib/catalog/getSongsByIsrc.ts | 23 +- lib/catalog/isCompleteSong.ts | 6 +- lib/catalog/parseCommaSeparated.ts | 4 +- lib/catalog/parseCsvFile.ts | 14 +- lib/catalog/postCatalogSongs.ts | 6 +- lib/catalog/processBatchesInParallel.ts | 9 +- lib/catalog/refineResults.ts | 5 +- lib/catalog/uploadBatchSongs.ts | 2 +- lib/chat/__tests__/proxyToApiChat.test.ts | 106 +- lib/chat/assistant/messageParser.ts | 2 + lib/chat/assistant/messageSegmentation.ts | 7 +- lib/chat/assistant/textFormatting.ts | 10 +- lib/chat/buildSystemPromptWithImages.ts | 9 +- lib/chat/cleanFileMentions.ts | 3 +- lib/chat/createFileAttachment.ts | 5 +- lib/chat/extractImageUrlsFromMessages.ts | 14 +- lib/chat/formatTextAttachments.ts | 4 +- lib/chat/generateChatTitle.ts | 2 +- lib/chat/getChatDisplayInfo.ts | 3 +- lib/chat/getChatRoomId.ts | 6 +- lib/chat/getCorsHeaders.ts | 6 +- lib/chat/handleChatCompletion.ts | 14 +- lib/chat/handleChatError.ts | 6 +- lib/chat/parseTextAttachments.ts | 12 +- lib/chat/proxyToApiChat.ts | 5 +- lib/chat/validateChatRequest.ts | 31 +- lib/chat/validateHeaders.ts | 7 +- lib/chat/validateMessages.ts | 8 +- lib/chats/updateChat.ts | 7 + lib/coinbase/getCdpClient.ts | 3 + lib/coinbase/getOrCreatePurchaserAccount.ts | 3 + .../__tests__/authorizeConnectorApi.test.ts | 6 +- .../__tests__/disconnectConnectorApi.test.ts | 12 +- .../api/__tests__/fetchConnectorsApi.test.ts | 15 +- lib/composio/connectorMetadata.ts | 2 + lib/composio/findAuthResult.ts | 10 +- lib/composio/formatConnectorName.ts | 7 +- lib/composio/hasValidAuthData.ts | 2 + lib/consts.ts | 16 +- lib/consts/fileConstants.ts | 1 - lib/consts/fileExtensions.ts | 3 +- lib/copyMessagesClient.ts | 7 +- lib/credits/checkAndResetCredits.ts | 8 +- lib/cron/deriveSimpleModeFromParts.ts | 18 +- lib/cron/padTimePart.ts | 3 +- lib/date/formatDate.ts | 1 + lib/email/generateTxtFileEmail.ts | 5 + lib/email/isTestEmail.ts | 4 +- .../__tests__/extractSendEmailResults.test.ts | 5 +- lib/emails/extractSendEmailResults.ts | 10 +- lib/emails/handleSendEmailToolOutputs.ts | 12 +- lib/errors/serializeError.ts | 2 + lib/files/checkFileAccess.ts | 8 +- lib/files/escapeLikePattern.ts | 8 +- lib/files/fetchFileContent.ts | 10 +- lib/files/filterFilesByPath.ts | 34 +- lib/files/generateStoragePath.ts | 26 +- lib/files/getFileExtension.ts | 7 +- lib/files/getKnowledgeBaseText.ts | 17 +- lib/files/handleToolError.ts | 10 +- lib/files/isAllowedByExtension.ts | 2 +- lib/files/normalizeFileName.ts | 15 +- lib/files/updateFileContent.ts | 9 +- lib/generateAndProcessImage.ts | 12 +- lib/generateUUID.ts | 2 +- lib/handleDailyStats.ts | 4 +- lib/ipfs/hash.ts | 4 + lib/keys/createApiKey.ts | 3 +- lib/keys/deleteApiKey.ts | 6 +- lib/keys/fetchApiKeys.ts | 3 +- lib/messages/clientDeleteTrailingMessages.ts | 19 +- lib/messages/deleteTrailingMessages.ts | 5 + .../filterMessageContentForMemories.ts | 2 +- .../getEarliestFailedUserMessageId.ts | 24 +- lib/messages/getLatestUserMessageText.ts | 14 +- lib/messages/getMessages.ts | 2 +- lib/organizations/assignAccountToOrg.ts | 6 +- .../formatAccountOrganizations.ts | 9 +- lib/perplexity/config.ts | 14 +- lib/perplexity/fetchPerplexityApi.ts | 4 +- .../formatSearchResultsAsMarkdown.ts | 11 +- lib/perplexity/searchApi.ts | 15 +- lib/perplexity/streamChatCompletion.ts | 16 +- lib/perplexity/streamPerplexityApi.ts | 7 +- lib/perplexity/types.ts | 1 - lib/polyfills/base64.ts | 2 - lib/prompts/getSystemPrompt.ts | 12 + lib/pulse/getPulse.ts | 9 +- lib/pulse/updatePulse.ts | 6 + lib/reasoning/extractReasoningTitle.ts | 23 +- lib/reasoning/extractors/headerExtractor.ts | 18 +- .../extractors/paragraphExtractor.ts | 39 +- lib/reasoning/mappers/actionLabelMapper.ts | 28 +- lib/reasoning/mappers/iconMapper.ts | 34 +- lib/reasoning/parseReasoningSteps.ts | 72 +- lib/reasoning/shared/parseUtilities.ts | 62 +- lib/reasoning/types.ts | 6 +- lib/recoup/deleteArtistFromAccount.ts | 14 +- lib/recoup/fetchPosts.ts | 5 + .../__tests__/sandboxStreamTypes.test.ts | 8 +- lib/sandboxes/convertFileTreeEntries.ts | 4 + lib/sandboxes/deleteSandbox.ts | 4 + lib/sandboxes/getFileContents.ts | 5 + lib/sandboxes/getSandboxes.ts | 8 +- lib/sandboxes/getSubtreeAtPath.ts | 8 +- lib/sandboxes/sortFileTree.ts | 4 + lib/search/searchProgressUtils.ts | 7 +- lib/search/timeFormatting.ts | 20 +- lib/search/urlUtils.ts | 15 +- lib/segments/createSegmentResponses.ts | 2 +- lib/segments/createSegments.ts | 64 +- lib/segments/getAnalysisPrompt.ts | 6 +- lib/segments/getFanSegmentsToInsert.ts | 9 +- lib/serpapi/config.ts | 10 +- lib/serpapi/searchImages.ts | 33 +- lib/serpapi/types.ts | 4 - lib/sidebar/themeLabel.ts | 2 + lib/socials/getPlatformDisplayName.ts | 1 + lib/spotify/formatDuration.ts | 3 +- lib/spotify/getSpotifyFollowers.ts | 10 +- lib/spotify/getSpotifyImage.ts | 4 + lib/spotify/spotifyContentUtils.ts | 3 +- lib/stripe/createBillingPortalSession.ts | 5 +- lib/stripe/getActiveSubscriptions.ts | 3 +- lib/stripe/getOrgSubscription.ts | 15 +- lib/styles/darkModeUtils.ts | 36 +- lib/styles/patterns.ts | 93 +- .../__tests__/ensureArtistAccess.test.ts | 17 +- .../checkAccountArtistAccess.ts | 10 +- .../deleteAccountArtistId.ts | 9 +- .../account_artist_ids/getAccountArtistIds.ts | 13 +- .../insertAccountArtistId.ts | 8 +- .../account_artist_ids/toggleArtistPin.ts | 13 +- .../getAccountDetailsByEmails.ts | 10 +- .../account_emails/getAccountEmails.ts | 4 +- .../account_emails/insertAccountEmail.ts | 2 +- .../account_info/getAccountInfoById.ts | 4 +- .../account_info/insertAccountInfo.ts | 8 +- .../account_info/updateAccountInfo.ts | 2 +- .../addAccountToOrganization.ts | 3 +- .../getAccountOrganizations.ts | 21 +- .../account_socials/deleteAccountSocial.ts | 5 +- .../account_socials/getAccountSocials.ts | 6 +- .../account_socials/insertAccountSocial.ts | 5 +- .../insertAccountWorkspaceId.ts | 4 +- .../selectAccountWorkspaceIds.ts | 5 +- lib/supabase/accounts/createAccount.ts | 9 +- lib/supabase/accounts/deleteAccountById.ts | 7 +- lib/supabase/accounts/getAccountByEmail.ts | 10 +- lib/supabase/accounts/getAccountById.ts | 8 +- lib/supabase/accounts/getAccountByWallet.ts | 4 + .../accounts/getAccountWithDetails.ts | 9 +- lib/supabase/accounts/insertAccount.ts | 8 +- lib/supabase/accounts/insertAccountWallet.ts | 5 + lib/supabase/accounts/updateAccount.ts | 9 +- .../addAgentTemplateFavorite.ts | 14 +- .../agent_templates/createAgentTemplate.ts | 15 +- .../createAgentTemplateShares.ts | 21 +- .../agent_templates/deleteAgentTemplate.ts | 6 +- .../deleteAgentTemplateShares.ts | 5 +- .../getAgentTemplateSharesByTemplateIds.ts | 5 +- .../agent_templates/getAgentTemplates.ts | 15 +- .../getSharedEmailsForTemplates.ts | 8 +- .../getSharedTemplatesForUser.ts | 14 +- .../getUserAccessibleTemplates.ts | 10 +- .../getUserTemplateFavorites.ts | 8 +- .../insertAgentTemplateShares.ts | 7 +- .../listAgentTemplatesForUser.ts | 14 +- .../removeAgentTemplateFavorite.ts | 14 +- .../agent_templates/updateAgentTemplate.ts | 12 +- .../updateAgentTemplateShares.ts | 3 +- .../verifyAgentTemplateOwner.ts | 7 +- .../artist/associateArtistWithAccount.ts | 10 +- lib/supabase/artist/createAccountInfo.ts | 7 +- lib/supabase/artist/getArtistById.ts | 3 +- .../addArtistToOrganization.ts | 5 +- .../artist_segments/insertArtistSegments.ts | 4 +- lib/supabase/copyMessages.ts | 8 +- lib/supabase/createArtistInDb.ts | 21 +- lib/supabase/createNewRoom.ts | 6 +- lib/supabase/createSegmentRoom.ts | 5 +- .../credits_usage/selectCreditsUsage.ts | 2 +- .../deleteMemoriesByChatIdAfterTimestamp.ts | 10 +- lib/supabase/ensureArtistAccess.ts | 28 +- lib/supabase/ensureRoomAccess.ts | 7 +- lib/supabase/error_logs/createErrorLog.ts | 10 +- lib/supabase/error_logs/errorTypeParser.ts | 47 +- .../fan_segments/insertFanSegments.ts | 9 +- .../fan_segments/selectFanSegments.ts | 4 +- lib/supabase/files/createFileRecord.ts | 6 +- lib/supabase/files/deleteFileRecord.ts | 14 +- lib/supabase/files/deleteFileRecords.ts | 15 +- lib/supabase/files/deleteFilesInDirectory.ts | 25 +- lib/supabase/files/ensureDirectoryExists.ts | 6 +- lib/supabase/files/escapePostgrestValue.ts | 7 +- lib/supabase/files/findFileByName.ts | 35 +- lib/supabase/files/getFileById.ts | 8 +- lib/supabase/files/getFileByStorageKey.ts | 7 +- lib/supabase/files/getFilesByArtistId.ts | 7 +- lib/supabase/files/getFilesInDirectory.ts | 15 +- lib/supabase/files/listFilesByArtist.ts | 26 +- lib/supabase/files/updateFileName.ts | 27 +- lib/supabase/files/updateFileSizeBytes.ts | 14 +- lib/supabase/files/updateFileStorageKey.ts | 11 +- lib/supabase/getAccountByPhone.ts | 7 +- lib/supabase/getArtistAgents.ts | 14 +- lib/supabase/getArtistKnowledge.ts | 4 + lib/supabase/getArtistSegmentNames.ts | 8 +- lib/supabase/getArtistSegments.ts | 10 +- lib/supabase/getMemoryById.ts | 17 +- lib/supabase/getRoomArtistId.ts | 21 +- lib/supabase/getRoomReports.ts | 1 + lib/supabase/getSegmentCounts.ts | 11 +- lib/supabase/getSegmentWithArtist.ts | 9 +- lib/supabase/getSpotifyPlayButtonClicked.ts | 6 +- lib/supabase/getUserInfo.ts | 9 +- lib/supabase/memories/getMemories.ts | 11 +- .../memory_emails/insertMemoryEmail.ts | 4 +- .../organization_domains/getOrgByDomain.ts | 5 +- .../post_comments/insertPostComments.ts | 10 +- .../post_comments/selectPostComments.ts | 8 +- lib/supabase/posts/getPosts.ts | 13 +- lib/supabase/posts/insertPosts.ts | 1 + lib/supabase/queryMemories.ts | 26 +- lib/supabase/rooms/getRooms.ts | 11 +- lib/supabase/segments/deleteSegments.ts | 6 +- lib/supabase/segments/insertSegments.ts | 11 +- lib/supabase/serverClient.ts | 6 +- lib/supabase/social_fans/selectSocialFans.ts | 7 +- .../social_posts/insertSocialPosts.ts | 5 +- lib/supabase/socials/getSocialByProfileUrl.ts | 4 +- lib/supabase/socials/insertSocials.ts | 4 +- lib/supabase/storage/client.ts | 14 +- lib/supabase/storage/copyFileByKey.ts | 12 +- lib/supabase/storage/createSignedUploadUrl.ts | 9 +- lib/supabase/storage/createSignedUrl.ts | 15 +- lib/supabase/storage/deleteFileByKey.ts | 12 +- lib/supabase/storage/fetchFileContent.ts | 2 + lib/supabase/storage/uploadFileByKey.ts | 22 +- .../youtube_tokens/deleteYouTubeTokens.ts | 7 +- .../youtube_tokens/getYouTubeTokens.ts | 7 +- .../youtube_tokens/insertYouTubeTokens.ts | 9 +- lib/tasks/deleteTask.ts | 6 + lib/tasks/formatDuration.ts | 4 + lib/tasks/formatTimestamp.ts | 4 + lib/tasks/getStatusColor.ts | 11 +- lib/tasks/getStatusLabel.ts | 4 + lib/tasks/getTaskDisplayName.ts | 4 + lib/tasks/getTaskRunStatus.ts | 8 +- lib/tasks/getTaskRuns.ts | 18 +- lib/tasks/getTasks.ts | 6 +- lib/tasks/isRecurring.ts | 2 + lib/tasks/parseCronToHuman.ts | 1 - lib/tasks/updateTask.ts | 6 +- lib/telegram/errors/escapeTelegramMarkdown.ts | 3 +- lib/telegram/errors/formatErrorMessage.ts | 13 +- lib/telegram/errors/sendErrorNotification.ts | 11 +- lib/telegram/sendDailyStatsMessage.ts | 6 + lib/telegram/sendMessage.ts | 8 +- lib/telegram/trimMessage.ts | 3 +- lib/tools/browser/browserAct.ts | 48 +- lib/tools/browser/browserAgent.ts | 34 +- lib/tools/browser/browserExtract.ts | 11 +- lib/tools/browser/browserObserve.ts | 21 +- lib/tools/browser/index.ts | 1 - lib/tools/browser/waitTool.ts | 9 +- lib/tools/catalogs/getCatalogSongs.ts | 9 +- lib/tools/createReleaseReport.ts | 4 +- lib/tools/createSegments.ts | 4 +- lib/tools/deleteArtist.ts | 20 +- lib/tools/files/createFolder.ts | 17 +- lib/tools/files/deleteFile.ts | 42 +- lib/tools/files/index.ts | 2 +- lib/tools/files/listFiles.ts | 22 +- lib/tools/files/moveFile.ts | 49 +- lib/tools/files/readFile.ts | 17 +- lib/tools/files/renameFile.ts | 46 +- lib/tools/files/renameFolder.ts | 54 +- lib/tools/files/updateFile.ts | 40 +- lib/tools/files/writeFile.ts | 36 +- lib/tools/generateMermaidDiagram.ts | 10 +- lib/tools/get-tools-name.ts | 6 +- lib/tools/getApifyScraper.ts | 14 +- lib/tools/getArtistSegments.ts | 5 +- lib/tools/getMcpTools.ts | 3 + lib/tools/getPostComments.ts | 5 +- lib/tools/getSegmentFans.ts | 5 +- lib/tools/getSocialFans.ts | 5 +- lib/tools/getSocialPosts.ts | 5 +- lib/tools/getTwitterTrends.ts | 5 +- lib/tools/searchGoogleImages.ts | 17 +- lib/tools/searchTwitter.ts | 15 +- lib/tools/searchWeb/getWebDeepResearchTool.ts | 33 +- lib/tools/searchWeb/types.ts | 6 +- lib/twilio/client.ts | 8 +- lib/twilio/createSmsResponse.ts | 1 + lib/twilio/parseSmsWebhook.ts | 1 + lib/twilio/processAndReply.ts | 4 +- lib/twilio/sendSmsMessage.ts | 5 +- lib/txtGeneration.ts | 8 +- lib/utils.ts | 4 + lib/utils/base64Polyfill.ts | 2 +- lib/utils/download-mermaid-chart.ts | 110 +- lib/utils/falConfig.ts | 6 +- lib/utils/falErrorHandler.ts | 10 +- lib/utils/formatBytes.ts | 5 +- lib/utils/formatFollowerCount.ts | 4 +- lib/utils/formatScheduledActionDate.ts | 6 +- lib/utils/formatTimestamp.ts | 6 +- lib/utils/getToolsInfo.ts | 9 +- lib/utils/isValidEmail.ts | 1 + lib/utils/normalizeProfileUrl.ts | 4 + lib/workspace/isWorkspaceAccount.ts | 3 +- .../authenticated-channel-monetization.ts | 28 +- lib/youtube/channel-fetcher.ts | 31 +- lib/youtube/channel-monetization-by-id.ts | 153 +- lib/youtube/error-builder.ts | 88 +- lib/youtube/fetchYouTubeChannel.ts | 4 +- lib/youtube/formatDuration.ts | 10 +- lib/youtube/getResizedImageBuffer.ts | 15 +- lib/youtube/getYoutubePlaylistVideos.ts | 7 +- lib/youtube/is-token-expired.ts | 5 +- lib/youtube/oauth-client.ts | 8 +- lib/youtube/queryAnalyticsReports.ts | 12 +- lib/youtube/revenue-error-handler.ts | 7 +- lib/youtube/token-refresher.ts | 64 +- lib/youtube/token-validator.ts | 27 +- lib/youtube/youtube-analytics-oauth-client.ts | 6 +- lib/youtube/youtubeLogin.ts | 4 + tailwind.config.ts | 180 +- types/AgentTemplates.ts | 16 +- types/ArtistSocials.ts | 17 +- types/database.types.ts | 6651 ++++++++--------- types/youtube.ts | 8 +- utils/artistHelpers.ts | 2 +- utils/extractAccountIds.ts | 3 +- utils/getContentSizeBytes.ts | 3 +- utils/getFileVisual.ts | 23 +- utils/getMimeFromPath.ts | 2 +- utils/getRelativeStoragePath.ts | 16 +- utils/isImagePath.ts | 4 +- utils/isTextFile.ts | 5 +- utils/isTextMimeType.ts | 2 - utils/isTextPath.ts | 4 +- utils/isValidFileName.ts | 4 +- utils/isValidFolderName.ts | 6 +- utils/isValidPath.ts | 14 +- utils/isValidStorageKey.ts | 4 +- utils/isValidUUID.ts | 8 +- 549 files changed, 6976 insertions(+), 6805 deletions(-) diff --git a/app/.well-known/farcaster.json/route.ts b/app/.well-known/farcaster.json/route.ts index 2e8bb18a7..11c6a0fb3 100644 --- a/app/.well-known/farcaster.json/route.ts +++ b/app/.well-known/farcaster.json/route.ts @@ -1,3 +1,6 @@ +/** + * + */ export async function GET() { const URL = process.env.NEXT_PUBLIC_URL; diff --git a/app/api/account-emails/route.ts b/app/api/account-emails/route.ts index 32f5fdf4a..a37456d67 100644 --- a/app/api/account-emails/route.ts +++ b/app/api/account-emails/route.ts @@ -2,16 +2,17 @@ import { NextRequest, NextResponse } from "next/server"; import getAccountEmails from "@/lib/supabase/account_emails/getAccountEmails"; import { checkAccountArtistAccess } from "@/lib/supabase/account_artist_ids/checkAccountArtistAccess"; +/** + * + * @param req + */ export async function GET(req: NextRequest) { const accountIds = req.nextUrl.searchParams.getAll("accountIds"); const currentAccountId = req.nextUrl.searchParams.get("currentAccountId"); const artistAccountId = req.nextUrl.searchParams.get("artistAccountId"); if (!currentAccountId || !artistAccountId) { - return NextResponse.json( - { error: "Missing authentication parameters" }, - { status: 400 } - ); + return NextResponse.json({ error: "Missing authentication parameters" }, { status: 400 }); } if (!accountIds || accountIds.length === 0) { @@ -20,10 +21,7 @@ export async function GET(req: NextRequest) { try { // Verify current user has access to this artist - const hasAccess = await checkAccountArtistAccess( - currentAccountId, - artistAccountId - ); + const hasAccess = await checkAccountArtistAccess(currentAccountId, artistAccountId); if (!hasAccess) { return NextResponse.json({ error: "Unauthorized" }, { status: 403 }); @@ -34,12 +32,8 @@ export async function GET(req: NextRequest) { const emails = await getAccountEmails(accountIds); return NextResponse.json(emails); } catch { - return NextResponse.json( - { error: "Failed to fetch account emails" }, - { status: 500 } - ); + return NextResponse.json({ error: "Failed to fetch account emails" }, { status: 500 }); } } export const dynamic = "force-dynamic"; - diff --git a/app/api/agent-creator/route.ts b/app/api/agent-creator/route.ts index 8380a329c..5db7ea2de 100644 --- a/app/api/agent-creator/route.ts +++ b/app/api/agent-creator/route.ts @@ -4,6 +4,10 @@ import { NextRequest } from "next/server"; export const runtime = "edge"; +/** + * + * @param req + */ export async function GET(req: NextRequest) { const creatorId = req.nextUrl.searchParams.get("creatorId"); @@ -18,9 +22,7 @@ export async function GET(req: NextRequest) { return Response.json({ message: "Creator not found" }, { status: 404 }); } - const info = Array.isArray(account.account_info) - ? account.account_info[0] || null - : null; + const info = Array.isArray(account.account_info) ? account.account_info[0] || null : null; const email = Array.isArray(account.account_emails) ? account.account_emails[0]?.email || null : null; diff --git a/app/api/agent-templates/favorites/route.ts b/app/api/agent-templates/favorites/route.ts index d1fb81729..92817b138 100644 --- a/app/api/agent-templates/favorites/route.ts +++ b/app/api/agent-templates/favorites/route.ts @@ -5,6 +5,10 @@ import type { ToggleFavoriteRequest, ToggleFavoriteResponse } from "@/types/Agen export const runtime = "edge"; +/** + * + * @param request + */ export async function POST(request: Request) { try { const { templateId, userId, isFavourite }: ToggleFavoriteRequest = await request.json(); @@ -22,11 +26,11 @@ export async function POST(request: Request) { return NextResponse.json({ success: true } as ToggleFavoriteResponse); } catch (error) { console.error("Error toggling favourite:", error); - return NextResponse.json({ error: "Failed to toggle favourite" } as ToggleFavoriteResponse, { status: 500 }); + return NextResponse.json({ error: "Failed to toggle favourite" } as ToggleFavoriteResponse, { + status: 500, + }); } } export const dynamic = "force-dynamic"; export const revalidate = 0; - - diff --git a/app/api/agent-templates/route.ts b/app/api/agent-templates/route.ts index c594cd80f..0b0177f9a 100644 --- a/app/api/agent-templates/route.ts +++ b/app/api/agent-templates/route.ts @@ -9,6 +9,10 @@ import { getSharedEmailsForTemplates } from "@/lib/supabase/agent_templates/getS export const runtime = "edge"; +/** + * + * @param request + */ export async function GET(request: Request) { try { const { searchParams } = new URL(request.url); @@ -29,16 +33,20 @@ export async function GET(request: Request) { // Add shared emails to templates const templatesWithEmails = templates.map(template => ({ ...template, - shared_emails: template.is_private ? (sharedEmails[template.id] || []) : [] + shared_emails: template.is_private ? sharedEmails[template.id] || [] : [], })); return NextResponse.json(templatesWithEmails); } catch (error) { - console.error('Error fetching agent templates:', error); - return NextResponse.json({ error: 'Failed to fetch templates' }, { status: 500 }); + console.error("Error fetching agent templates:", error); + return NextResponse.json({ error: "Failed to fetch templates" }, { status: 500 }); } } +/** + * + * @param request + */ export async function POST(request: Request) { try { const body = await request.json(); @@ -76,6 +84,10 @@ export async function POST(request: Request) { } } +/** + * + * @param request + */ export async function DELETE(request: Request) { try { const { id, userId } = (await request.json()) as { id: string; userId: string }; @@ -96,6 +108,10 @@ export async function DELETE(request: Request) { } } +/** + * + * @param request + */ export async function PATCH(request: Request) { try { const body = await request.json(); @@ -150,4 +166,4 @@ export async function PATCH(request: Request) { } export const dynamic = "force-dynamic"; -export const revalidate = 0; \ No newline at end of file +export const revalidate = 0; diff --git a/app/api/agentkit/get/route.ts b/app/api/agentkit/get/route.ts index 885c9018b..cd0aecba6 100644 --- a/app/api/agentkit/get/route.ts +++ b/app/api/agentkit/get/route.ts @@ -2,6 +2,10 @@ import getPostComments from "@/lib/getPostComments"; import supabase from "@/lib/supabase/serverClient"; import { NextRequest, NextResponse } from "next/server"; +/** + * + * @param req + */ export async function GET(req: NextRequest) { const agentId = req.nextUrl.searchParams.get("agentId"); try { diff --git a/app/api/agentkit/run/route.ts b/app/api/agentkit/run/route.ts index d32e3cab1..ff145a2fc 100644 --- a/app/api/agentkit/run/route.ts +++ b/app/api/agentkit/run/route.ts @@ -1,15 +1,15 @@ import { NextResponse } from "next/server"; +/** + * + */ export async function GET() { try { - const response = await fetch( - "http://143.198.164.177:3000/api/agentkit/run", - { - headers: { - "Content-Type": "application/json", - }, + const response = await fetch("http://143.198.164.177:3000/api/agentkit/run", { + headers: { + "Content-Type": "application/json", }, - ); + }); const data = await response.json(); return NextResponse.json(data); } catch (error) { diff --git a/app/api/agents/route.ts b/app/api/agents/route.ts index 23a666ec0..992c88ca1 100644 --- a/app/api/agents/route.ts +++ b/app/api/agents/route.ts @@ -1,15 +1,16 @@ import { NextRequest, NextResponse } from "next/server"; import { getArtistAgents } from "@/lib/supabase/getArtistAgents"; +/** + * + * @param request + */ export async function GET(request: NextRequest) { const { searchParams } = new URL(request.url); const socialIds = searchParams.getAll("socialId"); if (!socialIds.length) { - return NextResponse.json( - { error: "At least one Social ID is required" }, - { status: 400 }, - ); + return NextResponse.json({ error: "At least one Social ID is required" }, { status: 400 }); } try { @@ -17,10 +18,7 @@ export async function GET(request: NextRequest) { return NextResponse.json(agents); } catch (error) { console.error("Error fetching segments:", error); - return NextResponse.json( - { error: "Failed to fetch segments" }, - { status: 500 }, - ); + return NextResponse.json({ error: "Failed to fetch segments" }, { status: 500 }); } } diff --git a/app/api/apify/route.ts b/app/api/apify/route.ts index 154deda94..f39bfe48d 100644 --- a/app/api/apify/route.ts +++ b/app/api/apify/route.ts @@ -5,6 +5,8 @@ import apifyPayloadSchema from "@/lib/apify/apifyPayloadSchema"; /** * API endpoint for Apify webhooks. * Accepts a POST request with a JSON payload, optionally fetches a dataset, and always responds with 200. + * + * @param req */ export async function POST(req: NextRequest) { try { @@ -28,10 +30,10 @@ export async function POST(req: NextRequest) { }); } catch { // Always respond with 200, even if parsing fails - return new Response( - JSON.stringify({ message: "Apify webhook received (invalid JSON)" }), - { status: 200, headers: { "Content-Type": "application/json" } } - ); + return new Response(JSON.stringify({ message: "Apify webhook received (invalid JSON)" }), { + status: 200, + headers: { "Content-Type": "application/json" }, + }); } } diff --git a/app/api/artist/get_fan_segments/route.ts b/app/api/artist/get_fan_segments/route.ts index ab12d236c..93ceb3b4d 100644 --- a/app/api/artist/get_fan_segments/route.ts +++ b/app/api/artist/get_fan_segments/route.ts @@ -1,6 +1,10 @@ import supabase from "@/lib/supabase/serverClient"; import { NextRequest } from "next/server"; +/** + * + * @param req + */ export async function GET(req: NextRequest) { const artistId = req.nextUrl.searchParams.get("artistId"); @@ -11,9 +15,7 @@ export async function GET(req: NextRequest) { .eq("account_id", artistId); if (!account_socials) throw new Error("failed"); - const account_social_ids = account_socials.map( - (account_social) => account_social.social.id, - ); + const account_social_ids = account_socials.map(account_social => account_social.social.id); const { data: fan_segments } = await supabase .from("artist_fan_segment") diff --git a/app/api/artist/pin/route.ts b/app/api/artist/pin/route.ts index 4ad102d4f..5367eba4a 100644 --- a/app/api/artist/pin/route.ts +++ b/app/api/artist/pin/route.ts @@ -3,6 +3,10 @@ import { toggleArtistPin } from "@/lib/supabase/account_artist_ids/toggleArtistP export const runtime = "edge"; +/** + * + * @param req + */ export async function POST(req: NextRequest) { const body = await req.json(); const { accountId, artistId, pinned } = body; @@ -10,7 +14,7 @@ export async function POST(req: NextRequest) { if (!accountId || !artistId || typeof pinned !== "boolean") { return Response.json( { message: "Missing required fields: accountId, artistId, pinned" }, - { status: 400 } + { status: 400 }, ); } diff --git a/app/api/artist/profile/route.ts b/app/api/artist/profile/route.ts index c30e353a8..c73573c96 100644 --- a/app/api/artist/profile/route.ts +++ b/app/api/artist/profile/route.ts @@ -5,6 +5,10 @@ import updateArtistSocials from "@/lib/supabase/updateArtistSocials"; import { addArtistToOrganization } from "@/lib/supabase/artist_organization_ids/addArtistToOrganization"; import { NextRequest } from "next/server"; +/** + * + * @param req + */ export async function POST(req: NextRequest) { const body = await req.json(); const image = body.image; @@ -28,7 +32,7 @@ export async function POST(req: NextRequest) { name, instruction, label, - knowledges + knowledges, ); await updateArtistSocials(artistAccountId as string, profileUrls); @@ -49,7 +53,7 @@ export async function POST(req: NextRequest) { { artist: getFormattedArtist(account), }, - { status: 200 } + { status: 200 }, ); } catch (error) { console.error(error); diff --git a/app/api/artist/remove/route.ts b/app/api/artist/remove/route.ts index 9edeaa21c..2a37f777e 100644 --- a/app/api/artist/remove/route.ts +++ b/app/api/artist/remove/route.ts @@ -1,6 +1,10 @@ import supabase from "@/lib/supabase/serverClient"; import { NextRequest } from "next/server"; +/** + * + * @param req + */ export async function GET(req: NextRequest) { const artistId = req.nextUrl.searchParams.get("artistId"); diff --git a/app/api/auth/callback/google/route.ts b/app/api/auth/callback/google/route.ts index f53f4262d..e0ec282bb 100644 --- a/app/api/auth/callback/google/route.ts +++ b/app/api/auth/callback/google/route.ts @@ -1,13 +1,13 @@ /** * YouTube OAuth2 Callback Route - * + * * Handles Google OAuth redirect after YouTube authentication. - * + * * PROCESS: * - Exchanges authorization code for access/refresh tokens * - Saves tokens to database linked to artist_account_id from state parameter * - Redirects back to original page with success/error status - * + * * STATE: Contains JSON with { path, artist_account_id } for context preservation */ @@ -17,6 +17,10 @@ import { createYouTubeOAuthClient } from "@/lib/youtube/oauth-client"; const oauth2Client = createYouTubeOAuthClient(); +/** + * + * @param request + */ export async function GET(request: NextRequest) { const searchParams = request.nextUrl.searchParams; const code = searchParams.get("code"); @@ -47,8 +51,8 @@ export async function GET(request: NextRequest) { (redirectPath.includes("?") ? "&" : "?") + "youtube_auth_error=" + encodeURIComponent(error as string), - request.url - ) + request.url, + ), ); } @@ -59,8 +63,8 @@ export async function GET(request: NextRequest) { redirectPath + (redirectPath.includes("?") ? "&" : "?") + "youtube_auth_error=No+code+provided", - request.url - ) + request.url, + ), ); } @@ -85,8 +89,8 @@ export async function GET(request: NextRequest) { redirectPath + (redirectPath.includes("?") ? "&" : "?") + "youtube_auth_error=No+account+specified", - request.url - ) + request.url, + ), ); } @@ -107,31 +111,25 @@ export async function GET(request: NextRequest) { throw new Error("Failed to save YouTube tokens to database"); } - console.log( - "YouTube tokens saved to database successfully for account:", - artist_account_id - ); + console.log("YouTube tokens saved to database successfully for account:", artist_account_id); console.log("YouTube authentication successful, redirecting..."); return NextResponse.redirect( new URL( - redirectPath + - (redirectPath.includes("?") ? "&" : "?") + - "youtube_auth=success", - request.url - ) + redirectPath + (redirectPath.includes("?") ? "&" : "?") + "youtube_auth=success", + request.url, + ), ); } catch (err) { console.error("Error in YouTube auth callback:", err); - const errorMessage = - err instanceof Error ? err.message : "Error+getting+tokens"; + const errorMessage = err instanceof Error ? err.message : "Error+getting+tokens"; return NextResponse.redirect( new URL( redirectPath + (redirectPath.includes("?") ? "&" : "?") + "youtube_auth_error=" + encodeURIComponent(errorMessage), - request.url - ) + request.url, + ), ); } } diff --git a/app/api/chat/create/route.ts b/app/api/chat/create/route.ts index 56aba678d..ac360b7a6 100644 --- a/app/api/chat/create/route.ts +++ b/app/api/chat/create/route.ts @@ -4,6 +4,10 @@ import { generateChatTitle } from "@/lib/chat/generateChatTitle"; import { sendNewConversationNotification } from "@/lib/telegram/sendNewConversationNotification"; import { CreateChatRequest } from "@/types/Chat"; +/** + * + * @param request + */ export async function POST(request: NextRequest) { try { const body: CreateChatRequest = await request.json(); @@ -18,7 +22,7 @@ export async function POST(request: NextRequest) { }, { status: 400, - } + }, ); } @@ -42,7 +46,7 @@ export async function POST(request: NextRequest) { }, { status: 500, - } + }, ); } @@ -67,7 +71,7 @@ export async function POST(request: NextRequest) { }, { status: 200, - } + }, ); } catch (error) { console.error("Error creating chat:", error); @@ -78,7 +82,7 @@ export async function POST(request: NextRequest) { }, { status: 500, - } + }, ); } } diff --git a/app/api/chat/generate/route.ts b/app/api/chat/generate/route.ts index 216d6ff7d..fca77d28f 100644 --- a/app/api/chat/generate/route.ts +++ b/app/api/chat/generate/route.ts @@ -3,6 +3,9 @@ import { getCorsHeaders } from "@/lib/chat/getCorsHeaders"; import { proxyToApiChat } from "@/lib/chat/proxyToApiChat"; // Handle OPTIONS preflight requests +/** + * + */ export async function OPTIONS() { return new Response(null, { status: 200, diff --git a/app/api/chat/route.ts b/app/api/chat/route.ts index dd72be191..bb357fab9 100644 --- a/app/api/chat/route.ts +++ b/app/api/chat/route.ts @@ -3,6 +3,9 @@ import { getCorsHeaders } from "@/lib/chat/getCorsHeaders"; import { proxyToApiChat } from "@/lib/chat/proxyToApiChat"; // Handle OPTIONS preflight requests +/** + * + */ export async function OPTIONS() { return new Response(null, { status: 200, diff --git a/app/api/credits/get/route.ts b/app/api/credits/get/route.ts index 7e77c7273..38e8c5e96 100644 --- a/app/api/credits/get/route.ts +++ b/app/api/credits/get/route.ts @@ -1,6 +1,10 @@ import { NextRequest } from "next/server"; import { checkAndResetCredits } from "@/lib/credits/checkAndResetCredits"; +/** + * + * @param req + */ export async function GET(req: NextRequest) { const accountId = req.nextUrl.searchParams.get("accountId"); diff --git a/app/api/cron/recoupDailyStats/route.ts b/app/api/cron/recoupDailyStats/route.ts index 92743b49f..bb640f7b8 100644 --- a/app/api/cron/recoupDailyStats/route.ts +++ b/app/api/cron/recoupDailyStats/route.ts @@ -1,6 +1,9 @@ import { NextResponse } from "next/server"; import { handleDailyStats } from "@/lib/handleDailyStats"; +/** + * + */ export async function GET() { try { const stats = await handleDailyStats(); @@ -8,7 +11,7 @@ export async function GET() { } catch (error) { return NextResponse.json( { error: error instanceof Error ? error.message : String(error) }, - { status: 500 } + { status: 500 }, ); } } diff --git a/app/api/files/delete/route.ts b/app/api/files/delete/route.ts index fce4751e1..b156736ce 100644 --- a/app/api/files/delete/route.ts +++ b/app/api/files/delete/route.ts @@ -6,6 +6,10 @@ import { deleteFilesInDirectory } from "@/lib/supabase/files/deleteFilesInDirect import { deleteFileRecord } from "@/lib/supabase/files/deleteFileRecord"; import { deleteFileByKey } from "@/lib/supabase/storage/deleteFileByKey"; +/** + * + * @param req + */ export async function POST(req: Request) { try { const body = await req.json(); @@ -17,7 +21,7 @@ export async function POST(req: Request) { if (!id || !storageKey || !ownerAccountId) { return NextResponse.json( { error: "Missing id, storageKey or ownerAccountId" }, - { status: 400 } + { status: 400 }, ); } @@ -30,10 +34,7 @@ export async function POST(req: Request) { // Verify user has access to this file const hasAccess = await checkFileAccess(ownerAccountId, file); if (!hasAccess) { - return NextResponse.json( - { error: "You don't have access to this file" }, - { status: 403 } - ); + return NextResponse.json({ error: "You don't have access to this file" }, { status: 403 }); } // Handle directory deletion (recursive) @@ -43,7 +44,7 @@ export async function POST(req: Request) { // Delete child files from storage bucket if (childFiles.length > 0) { - const childStorageKeys = childFiles.map((f) => f.storage_key); + const childStorageKeys = childFiles.map(f => f.storage_key); await deleteFileByKey(childStorageKeys); } @@ -63,5 +64,3 @@ export async function POST(req: Request) { return NextResponse.json({ error: message }, { status: 500 }); } } - - diff --git a/app/api/files/folder/route.ts b/app/api/files/folder/route.ts index 4c93f588a..99075dc28 100644 --- a/app/api/files/folder/route.ts +++ b/app/api/files/folder/route.ts @@ -2,6 +2,10 @@ import { NextResponse } from "next/server"; import supabase from "@/lib/supabase/serverClient"; import isValidFolderName from "@/utils/isValidFolderName"; +/** + * + * @param req + */ export async function POST(req: Request) { try { const body = await req.json(); @@ -11,13 +15,21 @@ export async function POST(req: Request) { const name = String(body.name || ""); if (!ownerAccountId || !artistAccountId) { - return NextResponse.json({ error: "Missing ownerAccountId or artistAccountId" }, { status: 400 }); + return NextResponse.json( + { error: "Missing ownerAccountId or artistAccountId" }, + { status: 400 }, + ); } if (!isValidFolderName(name)) { return NextResponse.json({ error: "Invalid folder name" }, { status: 400 }); } - const base = parentPath && parentPath.endsWith("/") ? parentPath : parentPath ? parentPath + "/" : `files/${ownerAccountId}/${artistAccountId}/`; + const base = + parentPath && parentPath.endsWith("/") + ? parentPath + : parentPath + ? parentPath + "/" + : `files/${ownerAccountId}/${artistAccountId}/`; const key = `${base}${name}/`; // conflict check @@ -58,5 +70,3 @@ export async function POST(req: Request) { return NextResponse.json({ error: message }, { status: 500 }); } } - - diff --git a/app/api/files/get-signed-url/route.ts b/app/api/files/get-signed-url/route.ts index 58716cc77..70927a24b 100644 --- a/app/api/files/get-signed-url/route.ts +++ b/app/api/files/get-signed-url/route.ts @@ -4,6 +4,10 @@ import { createSignedUrlForKey } from "@/lib/supabase/storage/createSignedUrl"; import { getFileByStorageKey } from "@/lib/supabase/files/getFileByStorageKey"; import { checkFileAccess } from "@/lib/files/checkFileAccess"; +/** + * + * @param req + */ export async function GET(req: Request) { try { const { searchParams } = new URL(req.url); @@ -12,7 +16,7 @@ export async function GET(req: Request) { const expiresParam = searchParams.get("expires"); const DEFAULT_EXPIRES_SEC = 300; // 5 minutes let expires = DEFAULT_EXPIRES_SEC; - + // Validate expires parameter if (expiresParam !== null && expiresParam !== "") { const parsed = Number(expiresParam); @@ -21,7 +25,7 @@ export async function GET(req: Request) { } expires = parsed; } - + // Validate storage key format if (!isValidStorageKey(key)) { return NextResponse.json({ error: "Invalid key" }, { status: 400 }); @@ -35,11 +39,14 @@ export async function GET(req: Request) { // Get file metadata to check permissions const file = await getFileByStorageKey(key); if (!file) { - return NextResponse.json({ - error: "File not found in database", - details: `No file record found with storage key: ${key}`, - key - }, { status: 404 }); + return NextResponse.json( + { + error: "File not found in database", + details: `No file record found with storage key: ${key}`, + key, + }, + { status: 404 }, + ); } // Check if user has access to this file @@ -58,5 +65,3 @@ export async function GET(req: Request) { } export const dynamic = "force-dynamic"; - - diff --git a/app/api/files/list/route.ts b/app/api/files/list/route.ts index fe14e7978..a4597ea82 100644 --- a/app/api/files/list/route.ts +++ b/app/api/files/list/route.ts @@ -1,6 +1,10 @@ import { NextResponse } from "next/server"; import { listFilesByArtist } from "@/lib/supabase/files/listFilesByArtist"; +/** + * + * @param req + */ export async function GET(req: Request) { try { const { searchParams } = new URL(req.url); @@ -10,7 +14,10 @@ export async function GET(req: Request) { const recursive = searchParams.get("recursive") === "true"; if (!ownerAccountId || !artistAccountId) { - return NextResponse.json({ error: "Missing ownerAccountId or artistAccountId" }, { status: 400 }); + return NextResponse.json( + { error: "Missing ownerAccountId or artistAccountId" }, + { status: 400 }, + ); } // Use shared helper function (properly filters team files and immediate children) diff --git a/app/api/files/record/route.ts b/app/api/files/record/route.ts index 7f4bdf4d9..fdec52111 100644 --- a/app/api/files/record/route.ts +++ b/app/api/files/record/route.ts @@ -1,6 +1,10 @@ import { NextResponse } from "next/server"; import supabase from "@/lib/supabase/serverClient"; +/** + * + * @param req + */ export async function POST(req: Request) { try { const body = await req.json(); @@ -44,5 +48,3 @@ export async function POST(req: Request) { return NextResponse.json({ error: message }, { status: 500 }); } } - - diff --git a/app/api/files/signed-url/route.ts b/app/api/files/signed-url/route.ts index 3276d285e..45c4ededd 100644 --- a/app/api/files/signed-url/route.ts +++ b/app/api/files/signed-url/route.ts @@ -2,14 +2,21 @@ import { NextResponse } from "next/server"; import supabase from "@/lib/supabase/serverClient"; import { SUPABASE_STORAGE_BUCKET } from "@/lib/consts"; +/** + * + * @param req + */ export async function GET(req: Request) { try { const { searchParams } = new URL(req.url); const key = searchParams.get("key"); if (!key) return NextResponse.json({ error: "Missing key" }, { status: 400 }); - const { data, error } = await supabase.storage.from(SUPABASE_STORAGE_BUCKET).createSignedUrl(key, 300); - if (error || !data?.signedUrl) return NextResponse.json({ error: error?.message || "Failed" }, { status: 500 }); + const { data, error } = await supabase.storage + .from(SUPABASE_STORAGE_BUCKET) + .createSignedUrl(key, 300); + if (error || !data?.signedUrl) + return NextResponse.json({ error: error?.message || "Failed" }, { status: 500 }); return NextResponse.redirect(data.signedUrl, 307); } catch (err) { @@ -17,5 +24,3 @@ export async function GET(req: Request) { return NextResponse.json({ error: message }, { status: 500 }); } } - - diff --git a/app/api/files/update/route.ts b/app/api/files/update/route.ts index 39378a463..f62e42676 100644 --- a/app/api/files/update/route.ts +++ b/app/api/files/update/route.ts @@ -4,6 +4,10 @@ import { getFileByStorageKey } from "@/lib/supabase/files/getFileByStorageKey"; import { uploadFileByKey } from "@/lib/supabase/storage/uploadFileByKey"; import { updateFileSizeBytes } from "@/lib/supabase/files/updateFileSizeBytes"; +/** + * + * @param req + */ export async function POST(req: Request) { try { const body = await req.json(); @@ -11,23 +15,17 @@ export async function POST(req: Request) { // Validate required fields if (!storageKey || !isValidStorageKey(storageKey)) { - return NextResponse.json( - { error: "Invalid or missing storage key" }, - { status: 400 } - ); + return NextResponse.json({ error: "Invalid or missing storage key" }, { status: 400 }); } if (typeof content !== "string") { - return NextResponse.json( - { error: "Content must be a string" }, - { status: 400 } - ); + return NextResponse.json({ error: "Content must be a string" }, { status: 400 }); } if (!ownerAccountId || !artistAccountId) { return NextResponse.json( { error: "Missing ownerAccountId or artistAccountId" }, - { status: 400 } + { status: 400 }, ); } @@ -35,10 +33,7 @@ export async function POST(req: Request) { const fileRecord = await getFileByStorageKey(storageKey); if (!fileRecord) { - return NextResponse.json( - { error: "File not found" }, - { status: 404 } - ); + return NextResponse.json({ error: "File not found" }, { status: 404 }); } // Verify ownership @@ -46,10 +41,7 @@ export async function POST(req: Request) { fileRecord.owner_account_id !== ownerAccountId || fileRecord.artist_account_id !== artistAccountId ) { - return NextResponse.json( - { error: "Permission denied" }, - { status: 403 } - ); + return NextResponse.json({ error: "Permission denied" }, { status: 403 }); } // Convert text content to Blob @@ -73,13 +65,9 @@ export async function POST(req: Request) { // Don't fail the request if only metadata update fails } - return NextResponse.json( - { success: true, storageKey, sizeBytes }, - { status: 200 } - ); + return NextResponse.json({ success: true, storageKey, sizeBytes }, { status: 200 }); } catch (err) { const message = err instanceof Error ? err.message : "Unknown error"; return NextResponse.json({ error: message }, { status: 500 }); } } - diff --git a/app/api/funnel_analysis/route.ts b/app/api/funnel_analysis/route.ts index 52eb0d8bc..d5d4aa480 100644 --- a/app/api/funnel_analysis/route.ts +++ b/app/api/funnel_analysis/route.ts @@ -1,6 +1,10 @@ import getFunnelAnalysis from "@/lib/chat/getFunnelAnalysis"; import { NextRequest } from "next/server"; +/** + * + * @param req + */ export async function GET(req: NextRequest) { const pilotId = req.nextUrl.searchParams.get("pilotId"); diff --git a/app/api/get_running_agent/route.ts b/app/api/get_running_agent/route.ts index 313671738..e8c33dcfc 100644 --- a/app/api/get_running_agent/route.ts +++ b/app/api/get_running_agent/route.ts @@ -2,6 +2,10 @@ import supabase from "@/lib/supabase/serverClient"; import { STEP_OF_AGENT } from "@/types/Funnel"; import { NextRequest } from "next/server"; +/** + * + * @param req + */ export async function GET(req: NextRequest) { const artistId = req.nextUrl.searchParams.get("artistId"); try { diff --git a/app/api/in_process/createCollection.ts b/app/api/in_process/createCollection.ts index 8c76a7a50..791196b6d 100644 --- a/app/api/in_process/createCollection.ts +++ b/app/api/in_process/createCollection.ts @@ -1,10 +1,6 @@ import { inProcessProtocolAbi } from "@/abi/inProcessProtocolAbi"; import { getSetupNewTokenCall } from "@/app/lib/in_process/getSetupNewTokenCall"; -import { - IN_PROCESS_PROTOCOL_ADDRESS, - IS_PROD, - PAYMASTER_URL, -} from "@/lib/consts"; +import { IN_PROCESS_PROTOCOL_ADDRESS, IS_PROD, PAYMASTER_URL } from "@/lib/consts"; import { CdpClient } from "@coinbase/cdp-sdk"; import { encodeFunctionData } from "viem"; @@ -14,10 +10,13 @@ interface CreateCollectionParams { uri: string; } -async function createCollection({ - collectionName, - uri, -}: CreateCollectionParams) { +/** + * + * @param root0 + * @param root0.collectionName + * @param root0.uri + */ +async function createCollection({ collectionName, uri }: CreateCollectionParams) { // Initialize CDP client with your credentials const cdp = new CdpClient({ apiKeyId: process.env.CDP_API_KEY_ID, diff --git a/app/api/memories/create/route.ts b/app/api/memories/create/route.ts index f3d46fe72..3b9b490ad 100644 --- a/app/api/memories/create/route.ts +++ b/app/api/memories/create/route.ts @@ -1,5 +1,9 @@ import supabase from "@/lib/supabase/serverClient"; +/** + * + * @param req + */ export async function POST(req: Request) { const body = await req.json(); const content = body.content; diff --git a/app/api/memories/delete-trailing/route.ts b/app/api/memories/delete-trailing/route.ts index a11239401..f62c0afe2 100644 --- a/app/api/memories/delete-trailing/route.ts +++ b/app/api/memories/delete-trailing/route.ts @@ -1,16 +1,17 @@ import { NextRequest, NextResponse } from "next/server"; import { deleteTrailingMessages } from "@/lib/messages/deleteTrailingMessages"; +/** + * + * @param req + */ export async function DELETE(req: NextRequest) { try { const { searchParams } = new URL(req.url); const id = searchParams.get("id"); if (!id) { - return NextResponse.json( - { error: "Memory ID is required" }, - { status: 400 } - ); + return NextResponse.json({ error: "Memory ID is required" }, { status: 400 }); } await deleteTrailingMessages({ id }); @@ -19,9 +20,7 @@ export async function DELETE(req: NextRequest) { } catch (error) { console.error("Error deleting trailing messages:", error); const errorMessage = - error instanceof Error - ? error.message - : "Failed to delete trailing messages"; + error instanceof Error ? error.message : "Failed to delete trailing messages"; return NextResponse.json({ error: errorMessage }, { status: 500 }); } } diff --git a/app/api/memories/get/route.ts b/app/api/memories/get/route.ts index b3cb3cbeb..1c3de7bf5 100644 --- a/app/api/memories/get/route.ts +++ b/app/api/memories/get/route.ts @@ -1,6 +1,10 @@ import { NextRequest } from "next/server"; import queryMemories from "@/lib/supabase/queryMemories"; +/** + * + * @param req + */ export async function GET(req: NextRequest) { const roomId = req.nextUrl.searchParams.get("roomId"); @@ -10,7 +14,7 @@ export async function GET(req: NextRequest) { try { const { data, error } = await queryMemories(roomId, { ascending: true }); - + if (error) { throw error; } diff --git a/app/api/prompts/suggestions/route.ts b/app/api/prompts/suggestions/route.ts index 689dada97..b504b652b 100644 --- a/app/api/prompts/suggestions/route.ts +++ b/app/api/prompts/suggestions/route.ts @@ -16,9 +16,9 @@ export const POST = async (req: NextRequest) => { type: z .enum(["youtube", "tiktok", "instagram", "spotify", "other"]) .describe( - "The type of suggestion. This will be used to determine the type of suggestion to display." + "The type of suggestion. This will be used to determine the type of suggestion to display.", ), - }) + }), ), }), prompt: `Generate 4 suggestions for the following prompt: ${content}`, diff --git a/app/api/room/delete/route.ts b/app/api/room/delete/route.ts index aa22e167c..868a9ef93 100644 --- a/app/api/room/delete/route.ts +++ b/app/api/room/delete/route.ts @@ -1,23 +1,21 @@ import { NextRequest } from "next/server"; import supabase from "@/lib/supabase/serverClient"; +/** + * + * @param req + */ export async function POST(req: NextRequest) { try { const { roomId } = await req.json(); if (!roomId) { - return Response.json( - { message: "Missing required parameter: roomId" }, - { status: 400 } - ); + return Response.json({ message: "Missing required parameter: roomId" }, { status: 400 }); } // First delete any related data // Delete messages in this room - const { error: messagesError } = await supabase - .from("messages") - .delete() - .eq("room_id", roomId); + const { error: messagesError } = await supabase.from("messages").delete().eq("room_id", roomId); if (messagesError) { console.error("Error deleting messages:", messagesError); @@ -36,16 +34,13 @@ export async function POST(req: NextRequest) { } // Finally delete the room itself - const { error } = await supabase - .from("rooms") - .delete() - .eq("id", roomId); + const { error } = await supabase.from("rooms").delete().eq("id", roomId); if (error) { console.error("Error deleting room:", error); return Response.json( { message: "Failed to delete room", error: error.message }, - { status: 500 } + { status: 500 }, ); } @@ -59,4 +54,4 @@ export async function POST(req: NextRequest) { export const dynamic = "force-dynamic"; export const fetchCache = "force-no-store"; -export const revalidate = 0; \ No newline at end of file +export const revalidate = 0; diff --git a/app/api/roomSegment/route.ts b/app/api/roomSegment/route.ts index c7302e598..28beae0c3 100644 --- a/app/api/roomSegment/route.ts +++ b/app/api/roomSegment/route.ts @@ -1,5 +1,9 @@ import { getSegmentIdForRoomId } from "@/lib/supabase/getSegmentIdForRoomId"; +/** + * + * @param req + */ export async function GET(req: Request) { try { const roomId = new URL(req.url).searchParams.get("roomId"); @@ -13,7 +17,7 @@ export async function GET(req: Request) { headers: { "Content-Type": "application/json", }, - } + }, ); } @@ -35,7 +39,7 @@ export async function GET(req: Request) { headers: { "Content-Type": "application/json", }, - } + }, ); } } diff --git a/app/api/scheduled-actions/delete/route.ts b/app/api/scheduled-actions/delete/route.ts index 24e5eec34..cd2be9ff4 100644 --- a/app/api/scheduled-actions/delete/route.ts +++ b/app/api/scheduled-actions/delete/route.ts @@ -1,6 +1,10 @@ import { NextRequest, NextResponse } from "next/server"; import supabase from "@/lib/supabase/serverClient"; +/** + * + * @param req + */ export async function DELETE(req: NextRequest) { const { id } = await req.json(); @@ -9,10 +13,7 @@ export async function DELETE(req: NextRequest) { } try { - const { error } = await supabase - .from("scheduled_actions") - .delete() - .eq("id", id); + const { error } = await supabase.from("scheduled_actions").delete().eq("id", id); if (error) { throw new Error(`Failed to delete task: ${error.message}`); @@ -20,12 +21,8 @@ export async function DELETE(req: NextRequest) { return NextResponse.json({ success: true }); } catch { - return NextResponse.json( - { error: "Failed to delete task from database" }, - { status: 500 } - ); + return NextResponse.json({ error: "Failed to delete task from database" }, { status: 500 }); } } export const dynamic = "force-dynamic"; - diff --git a/app/api/segment_report/route.ts b/app/api/segment_report/route.ts index 2c99ac00e..1100a8af5 100644 --- a/app/api/segment_report/route.ts +++ b/app/api/segment_report/route.ts @@ -1,6 +1,10 @@ import supabase from "@/lib/supabase/serverClient"; import { NextRequest } from "next/server"; +/** + * + * @param req + */ export async function GET(req: NextRequest) { const reportId = req.nextUrl.searchParams.get("reportId"); diff --git a/app/api/segments/create/route.ts b/app/api/segments/create/route.ts index 7540fae5c..893784d0b 100644 --- a/app/api/segments/create/route.ts +++ b/app/api/segments/create/route.ts @@ -1,13 +1,17 @@ import { NextRequest, NextResponse } from "next/server"; import { createSegments } from "@/lib/segments/createSegments"; +/** + * + * @param req + */ export async function POST(req: NextRequest) { try { const { artist_account_id, prompt } = await req.json(); if (!artist_account_id || !prompt) { return NextResponse.json( { error: "artist_account_id and prompt are required" }, - { status: 400 } + { status: 400 }, ); } const result = await createSegments({ artist_account_id, prompt }); @@ -15,10 +19,9 @@ export async function POST(req: NextRequest) { } catch (error) { return NextResponse.json( { - error: - error instanceof Error ? error.message : "Failed to create segments", + error: error instanceof Error ? error.message : "Failed to create segments", }, - { status: 500 } + { status: 500 }, ); } } diff --git a/app/api/segments/route.ts b/app/api/segments/route.ts index deb3db5a6..ced4d6267 100644 --- a/app/api/segments/route.ts +++ b/app/api/segments/route.ts @@ -1,6 +1,10 @@ import { getArtistSegments } from "@/lib/supabase/getArtistSegments"; import { NextRequest } from "next/server"; +/** + * + * @param req + */ export async function GET(req: NextRequest) { const artistId = req.nextUrl.searchParams.get("artistId"); @@ -13,10 +17,7 @@ export async function GET(req: NextRequest) { return Response.json(segments, { status: 200 }); } catch (error) { console.error("Error fetching segments:", error); - return Response.json( - { error: "Failed to fetch segments" }, - { status: 500 } - ); + return Response.json({ error: "Failed to fetch segments" }, { status: 500 }); } } diff --git a/app/api/storage/signed-upload-url/route.ts b/app/api/storage/signed-upload-url/route.ts index 172576f27..94c2dfc88 100644 --- a/app/api/storage/signed-upload-url/route.ts +++ b/app/api/storage/signed-upload-url/route.ts @@ -2,12 +2,16 @@ import { NextResponse } from "next/server"; import { createSignedUploadUrlForKey } from "@/lib/supabase/storage/createSignedUploadUrl"; import isValidStorageKey from "@/utils/isValidStorageKey"; - +/** + * + * @param req + */ export async function POST(req: Request) { try { const body = await req.json().catch(() => ({})); const key = String(body.key || ""); - if (!key || !isValidStorageKey(key)) return NextResponse.json({ error: "Invalid or missing key" }, { status: 400 }); + if (!key || !isValidStorageKey(key)) + return NextResponse.json({ error: "Invalid or missing key" }, { status: 400 }); const result = await createSignedUploadUrlForKey(key); return NextResponse.json({ signedUrl: result.signedUrl, path: result.path }, { status: 200 }); @@ -18,5 +22,3 @@ export async function POST(req: Request) { } export const dynamic = "force-dynamic"; - - diff --git a/app/api/storage/signed-url/route.ts b/app/api/storage/signed-url/route.ts index 9d4a8c791..2b88e49af 100644 --- a/app/api/storage/signed-url/route.ts +++ b/app/api/storage/signed-url/route.ts @@ -2,6 +2,10 @@ import { NextResponse } from "next/server"; import supabase from "@/lib/supabase/serverClient"; import { SUPABASE_STORAGE_BUCKET } from "@/lib/consts"; +/** + * + * @param req + */ export async function POST(req: Request) { try { const body = await req.json(); @@ -12,11 +16,10 @@ export async function POST(req: Request) { if (storageKey) { let cleanKey = storageKey; if (cleanKey.startsWith(`${SUPABASE_STORAGE_BUCKET}/`)) { - cleanKey = cleanKey.slice(SUPABASE_STORAGE_BUCKET.length + 1); + cleanKey = cleanKey.slice(SUPABASE_STORAGE_BUCKET.length + 1); } - const { data, error } = await supabase - .storage + const { data, error } = await supabase.storage .from(SUPABASE_STORAGE_BUCKET) .createSignedUrl(cleanKey, 60 * 60); @@ -35,8 +38,7 @@ export async function POST(req: Request) { return key; }); - const { data, error } = await supabase - .storage + const { data, error } = await supabase.storage .from(SUPABASE_STORAGE_BUCKET) .createSignedUrls(cleanKeys, 60 * 60); @@ -49,8 +51,8 @@ export async function POST(req: Request) { const urlMap: Record = {}; data.forEach((item, index) => { if (item.signedUrl) { - // Map back using the original key from the request - urlMap[storageKeys[index]] = item.signedUrl; + // Map back using the original key from the request + urlMap[storageKeys[index]] = item.signedUrl; } }); diff --git a/app/api/storage/upload-by-key/route.ts b/app/api/storage/upload-by-key/route.ts index 90c217dc5..be54b5e8a 100644 --- a/app/api/storage/upload-by-key/route.ts +++ b/app/api/storage/upload-by-key/route.ts @@ -2,6 +2,10 @@ import { NextResponse } from "next/server"; import { isValidStorageKey } from "@/utils/isValidStorageKey"; import { uploadFileByKey } from "@/lib/supabase/storage/uploadFileByKey"; +/** + * + * @param req + */ export async function POST(req: Request) { try { const formData = await req.formData(); @@ -27,5 +31,3 @@ export async function POST(req: Request) { return NextResponse.json({ error: message }, { status: 500 }); } } - - diff --git a/app/api/stripe/portal/create/route.ts b/app/api/stripe/portal/create/route.ts index dd946e7ff..b20fda214 100644 --- a/app/api/stripe/portal/create/route.ts +++ b/app/api/stripe/portal/create/route.ts @@ -1,16 +1,17 @@ import createBillingPortalSession from "@/lib/stripe/createBillingPortalSession"; import { NextRequest } from "next/server"; +/** + * + * @param req + */ export async function POST(req: NextRequest) { const body = await req.json(); const accountId = body.accountId; const returnUrl = body.returnUrl; try { - const portalSession = await createBillingPortalSession( - accountId, - returnUrl - ); + const portalSession = await createBillingPortalSession(accountId, returnUrl); return Response.json({ data: portalSession }, { status: 200 }); } catch (error) { console.error(error); diff --git a/app/api/stripe/session/checked/route.ts b/app/api/stripe/session/checked/route.ts index f8b69d19c..758a4d39c 100644 --- a/app/api/stripe/session/checked/route.ts +++ b/app/api/stripe/session/checked/route.ts @@ -1,6 +1,10 @@ import stripeClient from "@/lib/stripe/client"; import { NextRequest } from "next/server"; +/** + * + * @param req + */ export async function GET(req: NextRequest) { const sessionId = req.nextUrl.searchParams.get("sessionId"); const accountId = req.nextUrl.searchParams.get("accountId"); diff --git a/app/api/stripe/session/create/route.ts b/app/api/stripe/session/create/route.ts index 68274a0b3..468505d20 100644 --- a/app/api/stripe/session/create/route.ts +++ b/app/api/stripe/session/create/route.ts @@ -1,6 +1,10 @@ import createSession from "@/lib/stripe/createSession"; import { NextRequest } from "next/server"; +/** + * + * @param req + */ export async function POST(req: NextRequest) { const body = await req.json(); const accountId = body.accountId; diff --git a/app/api/stripe/session/list/route.ts b/app/api/stripe/session/list/route.ts index 4ea241fa2..64a15ceec 100644 --- a/app/api/stripe/session/list/route.ts +++ b/app/api/stripe/session/list/route.ts @@ -1,6 +1,10 @@ import stripeClient from "@/lib/stripe/client"; import { NextRequest } from "next/server"; +/** + * + * @param req + */ export async function GET(req: NextRequest) { const referenceId = req.nextUrl.searchParams.get("referenceId"); diff --git a/app/api/subscription/status/route.ts b/app/api/subscription/status/route.ts index dd460e060..f92b1ecc1 100644 --- a/app/api/subscription/status/route.ts +++ b/app/api/subscription/status/route.ts @@ -10,6 +10,8 @@ export interface ProStatusResponse { /** * GET /api/subscription/status?accountId=xxx * Returns whether the account has pro status (via account or org subscription) + * + * @param req */ export async function GET(req: NextRequest): Promise { const accountId = req.nextUrl.searchParams.get("accountId"); @@ -26,8 +28,7 @@ export async function GET(req: NextRequest): Promise { ]); const isPro = - isActiveSubscription(accountSubscription) || - isActiveSubscription(orgSubscription); + isActiveSubscription(accountSubscription) || isActiveSubscription(orgSubscription); return Response.json({ isPro }, { status: 200 }); } catch (error) { diff --git a/app/api/twilio/sms/route.ts b/app/api/twilio/sms/route.ts index 39211cb2c..cccb44331 100644 --- a/app/api/twilio/sms/route.ts +++ b/app/api/twilio/sms/route.ts @@ -7,6 +7,8 @@ import { processAndReply } from "@/lib/twilio/processAndReply"; * Webhook endpoint to receive SMS messages from Twilio * * Strategy: Respond immediately (within 15s timeout), then process AI asynchronously + * + * @param request */ export async function POST(request: NextRequest) { try { @@ -14,12 +16,10 @@ export async function POST(request: NextRequest) { const formData = await request.formData(); const smsData = parseSmsWebhook(formData); - console.log( - `[${new Date().toISOString()}] SMS received from ${smsData.from}: ${smsData.body}` - ); + console.log(`[${new Date().toISOString()}] SMS received from ${smsData.from}: ${smsData.body}`); // Process AI generation and send response asynchronously (don't await) - processAndReply(smsData).catch((error) => { + processAndReply(smsData).catch(error => { console.error("Error in async SMS processing:", error); }); diff --git a/app/api/upload/route.ts b/app/api/upload/route.ts index 2f42d3aa0..a6a1fc63c 100644 --- a/app/api/upload/route.ts +++ b/app/api/upload/route.ts @@ -2,6 +2,10 @@ import { NextResponse } from "next/server"; import uploadToArweave from "@/lib/arweave/uploadToArweave"; import { getFetchableUrl } from "@/lib/arweave/gateway"; +/** + * + * @param request + */ export async function POST(request: Request) { try { const formData = await request.formData(); @@ -33,7 +37,7 @@ export async function POST(request: Request) { success: false, error: error instanceof Error ? error.message : "Unknown error", }, - { status: 500 } + { status: 500 }, ); } } diff --git a/app/api/workspace/create/route.ts b/app/api/workspace/create/route.ts index ea7f599fb..0f1c771ea 100644 --- a/app/api/workspace/create/route.ts +++ b/app/api/workspace/create/route.ts @@ -2,6 +2,7 @@ import { NextRequest } from "next/server"; import { NEW_API_BASE_URL } from "@/lib/consts"; /** + * @param req * @deprecated This endpoint is deprecated. Use recoup-api directly at POST /api/workspaces * * Create a blank workspace for an account. @@ -48,7 +49,7 @@ export async function POST(req: NextRequest) { const message = error instanceof Error ? error.message : "Failed to create workspace"; return Response.json( { status: "error", error: message }, - { status: 500, headers: deprecationHeaders } + { status: 500, headers: deprecationHeaders }, ); } } diff --git a/app/api/youtube/channel-info/route.ts b/app/api/youtube/channel-info/route.ts index f6402a448..e8c0e6684 100644 --- a/app/api/youtube/channel-info/route.ts +++ b/app/api/youtube/channel-info/route.ts @@ -2,6 +2,10 @@ import { NextRequest, NextResponse } from "next/server"; import { validateYouTubeTokens } from "@/lib/youtube/token-validator"; import { fetchYouTubeChannelInfo } from "@/lib/youtube/channel-fetcher"; +/** + * + * @param request + */ export async function GET(request: NextRequest) { try { const { searchParams } = new URL(request.url); @@ -9,25 +13,25 @@ export async function GET(request: NextRequest) { if (!artistAccountId) { return NextResponse.json( - { + { success: false, error: "Missing artist_account_id parameter", - tokenStatus: "missing_param" + tokenStatus: "missing_param", }, - { status: 400 } + { status: 400 }, ); } const tokenValidation = await validateYouTubeTokens(artistAccountId); if (!tokenValidation.success) { return NextResponse.json( - { + { success: false, error: "YouTube authentication required", tokenStatus: "invalid", - channels: null + channels: null, }, - { status: 200 } + { status: 200 }, ); } @@ -40,20 +44,20 @@ export async function GET(request: NextRequest) { if (!channelResult.success) { return NextResponse.json( - { + { success: false, error: channelResult.error!.message, tokenStatus: "api_error", - channels: null + channels: null, }, - { status: 200 } + { status: 200 }, ); } return NextResponse.json({ success: true, channels: channelResult.channelData, - tokenStatus: "valid" + tokenStatus: "valid", }); } catch (error) { console.error("❌ Error in YouTube channel info API:", error); @@ -64,9 +68,9 @@ export async function GET(request: NextRequest) { error: "Failed to fetch YouTube channel information", details: error instanceof Error ? error.message : "Unknown error", tokenStatus: "error", - channels: null + channels: null, }, - { status: 200 } // Return 200 so frontend can parse the response + { status: 200 }, // Return 200 so frontend can parse the response ); } } diff --git a/app/api/youtube/logout/route.ts b/app/api/youtube/logout/route.ts index 2d190ec03..447a7d216 100644 --- a/app/api/youtube/logout/route.ts +++ b/app/api/youtube/logout/route.ts @@ -1,15 +1,16 @@ import { NextRequest, NextResponse } from "next/server"; import deleteYouTubeTokens from "@/lib/supabase/youtube_tokens/deleteYouTubeTokens"; +/** + * + * @param request + */ export async function DELETE(request: NextRequest) { const { searchParams } = new URL(request.url); const artistAccountId = searchParams.get("artist_account_id"); if (!artistAccountId) { - return NextResponse.json( - { error: "Artist account ID is required" }, - { status: 400 } - ); + return NextResponse.json({ error: "Artist account ID is required" }, { status: 400 }); } try { @@ -18,19 +19,13 @@ export async function DELETE(request: NextRequest) { if (success) { return NextResponse.json({ message: "YouTube access removed successfully", - status: "disconnected" + status: "disconnected", }); } else { - return NextResponse.json( - { error: "Failed to remove YouTube access" }, - { status: 500 } - ); + return NextResponse.json({ error: "Failed to remove YouTube access" }, { status: 500 }); } } catch (error) { console.error("Error in YouTube logout:", error); - return NextResponse.json( - { error: "Internal server error" }, - { status: 500 } - ); + return NextResponse.json({ error: "Internal server error" }, { status: 500 }); } -} \ No newline at end of file +} diff --git a/components/Agents/useAgentCard.ts b/components/Agents/useAgentCard.ts index 8ff280fc3..b79ad7325 100644 --- a/components/Agents/useAgentCard.ts +++ b/components/Agents/useAgentCard.ts @@ -25,6 +25,13 @@ interface UseAgentCardReturn { handleToggleFavorite: (agentId: string, nextFavourite: boolean) => void; } +/** + * + * @param root0 + * @param root0.agent + * @param root0.onClick + * @param root0.onToggleFavorite + */ export function useAgentCard({ agent, onClick, @@ -42,8 +49,7 @@ export function useAgentCard({ ]; // Filter and prioritize action tags - const displayedActionTags = - agent.tags?.filter((tag) => actionTags.includes(tag)) || []; + const displayedActionTags = agent.tags?.filter(tag => actionTags.includes(tag)) || []; const pillTag = displayedActionTags[0] ?? agent.tags?.[0] ?? "General"; // Check if this agent is shared with the current user @@ -52,7 +58,7 @@ export function useAgentCard({ !!userData?.id && userData.id !== agent.creator && !!email && - agent.shared_emails?.includes(email) + agent.shared_emails?.includes(email), ); const handleCardKeyDown = useCallback( @@ -73,14 +79,14 @@ export function useAgentCard({ onClick(agent); } }, - [onClick, agent] + [onClick, agent], ); const handleCardClick = useCallback( (event: React.MouseEvent) => { const target = event.target as HTMLElement; const interactiveAncestor = target.closest( - "button, [role='button'], input, textarea, select, a" + "button, [role='button'], input, textarea, select, a", ); // Ignore clicks on interactive elements, but allow the card itself (role=button) @@ -93,14 +99,14 @@ export function useAgentCard({ onClick(agent); }, - [onClick, agent] + [onClick, agent], ); const handleToggleFavorite = useCallback( (agentId: string, nextFavourite: boolean) => { onToggleFavorite?.(agentId, nextFavourite); }, - [onToggleFavorite] + [onToggleFavorite], ); return { diff --git a/components/Agents/useAgentData.ts b/components/Agents/useAgentData.ts index b148ab2a2..4a51f35e1 100644 --- a/components/Agents/useAgentData.ts +++ b/components/Agents/useAgentData.ts @@ -6,6 +6,9 @@ import fetchAgentTemplates from "@/lib/agent-templates/fetchAgentTemplates"; export type Agent = AgentTemplateRow; +/** + * + */ export function useAgentData() { const { userData } = useUserProvider(); const queryClient = useQueryClient(); @@ -38,8 +41,8 @@ export function useAgentData() { new Set( data .flatMap((agent: Agent) => agent.tags || []) - .filter((tag: string) => !!tag && !actionTags.includes(tag)) - ) + .filter((tag: string) => !!tag && !actionTags.includes(tag)), + ), ); const allTags = ["Recommended", ...uniqueTags]; setTags(Array.from(new Set(allTags))); @@ -52,12 +55,10 @@ export function useAgentData() { // Get all agents except the special card, filtered by the selected tag and public/private const filteredAgents = agents.filter( - (agent) => + agent => agent.title !== "Audience Segmentation" && - (selectedTag === "Recommended" - ? true - : agent.tags?.includes(selectedTag)) && - (isPrivate ? agent.is_private === true : agent.is_private !== true) + (selectedTag === "Recommended" ? true : agent.tags?.includes(selectedTag)) && + (isPrivate ? agent.is_private === true : agent.is_private !== true), ); // Hide the "Audience Segmentation" card from UI - keep all other logic intact const gridAgents = filteredAgents; diff --git a/components/Agents/useAgentToggleFavorite.ts b/components/Agents/useAgentToggleFavorite.ts index 0d87717b1..8a8857049 100644 --- a/components/Agents/useAgentToggleFavorite.ts +++ b/components/Agents/useAgentToggleFavorite.ts @@ -3,16 +3,16 @@ import { useQueryClient } from "@tanstack/react-query"; import { toast } from "sonner"; import type { ToggleFavoriteRequest } from "@/types/AgentTemplates"; +/** + * + */ export function useAgentToggleFavorite() { const { userData } = useUserProvider(); const queryClient = useQueryClient(); - const handleToggleFavorite = async ( - templateId: string, - nextFavourite: boolean - ) => { + const handleToggleFavorite = async (templateId: string, nextFavourite: boolean) => { if (!userData?.id || !templateId) return; - + try { const body: ToggleFavoriteRequest = { templateId, @@ -24,15 +24,13 @@ export function useAgentToggleFavorite() { headers: { "Content-Type": "application/json" }, body: JSON.stringify(body), }); - + if (!res.ok) { throw new Error("Failed to toggle favorite"); } - - toast.success( - nextFavourite ? "Added to favorites" : "Removed from favorites" - ); - + + toast.success(nextFavourite ? "Added to favorites" : "Removed from favorites"); + // Invalidate templates list so is_favourite and favorites_count refresh queryClient.invalidateQueries({ queryKey: ["agent-templates"] }); } catch { diff --git a/components/Files/types.ts b/components/Files/types.ts index 627608b4a..bb7a6d8f6 100644 --- a/components/Files/types.ts +++ b/components/Files/types.ts @@ -7,7 +7,6 @@ export interface FileRow { size_bytes?: number | null; } - // Shared types for File Access UI export type ArtistAccess = { artistId: string; @@ -22,5 +21,3 @@ export type AccessArtistsResponse = { success: boolean; data?: { artists: ArtistAccess[]; count: number; fileId: string; accountId: string }; }; - - diff --git a/components/Sidebar/RecentChats/useRecentChats.ts b/components/Sidebar/RecentChats/useRecentChats.ts index d27d570c5..5c4aa47d6 100644 --- a/components/Sidebar/RecentChats/useRecentChats.ts +++ b/components/Sidebar/RecentChats/useRecentChats.ts @@ -13,8 +13,7 @@ interface UseRecentChatsParams { } export const useRecentChats = ({ toggleModal }: UseRecentChatsParams) => { - const { conversations, isLoading, isFetching, refetchConversations } = - useConversationsProvider(); + const { conversations, isLoading, isFetching, refetchConversations } = useConversationsProvider(); const { handleClick } = useClickChat(); const isMobile = useMobileDetection(); const params = useParams(); @@ -26,7 +25,7 @@ export const useRecentChats = ({ toggleModal }: UseRecentChatsParams) => { ? params.roomId : typeof params?.agentId === "string" ? params.agentId - : null + : null, ); useEffect(() => { @@ -39,8 +38,7 @@ export const useRecentChats = ({ toggleModal }: UseRecentChatsParams) => { } const roomId = typeof params?.roomId === "string" ? params.roomId : null; - const agentId = - typeof params?.agentId === "string" ? params.agentId : null; + const agentId = typeof params?.agentId === "string" ? params.agentId : null; setActiveChatId(roomId || agentId || null); }; @@ -53,9 +51,7 @@ export const useRecentChats = ({ toggleModal }: UseRecentChatsParams) => { chatRooms?: Array; }>({ type: null, chatRoom: null }); - const [selectedChatIds, setSelectedChatIds] = useState>( - new Set() - ); + const [selectedChatIds, setSelectedChatIds] = useState>(new Set()); const [lastClickedId, setLastClickedId] = useState(null); const [isShiftPressed, setIsShiftPressed] = useState(false); @@ -91,10 +87,7 @@ export const useRecentChats = ({ toggleModal }: UseRecentChatsParams) => { onClose: () => setOpenMenuId(null), }); - const openModal = ( - type: "rename" | "delete", - chatRoom: Conversation | ArtistAgent - ) => { + const openModal = (type: "rename" | "delete", chatRoom: Conversation | ArtistAgent) => { setModalState({ type, chatRoom }); setOpenMenuId(null); }; @@ -109,15 +102,12 @@ export const useRecentChats = ({ toggleModal }: UseRecentChatsParams) => { await refetchConversations(); setSelectedChatIds(new Set()); } catch (error) { - console.error( - `Error refreshing conversations after ${modalState.type}:`, - error - ); + console.error(`Error refreshing conversations after ${modalState.type}:`, error); } }; const handleChatSelection = (chatId: string, isShiftKey: boolean) => { - setSelectedChatIds((prev) => { + setSelectedChatIds(prev => { const newSelection = new Set(prev); if (isShiftKey && lastClickedId) { @@ -146,9 +136,7 @@ export const useRecentChats = ({ toggleModal }: UseRecentChatsParams) => { }; const handleBulkDelete = () => { - const chatsToDelete = conversations.filter((chat) => - selectedChatIds.has(getChatRoomId(chat)) - ); + const chatsToDelete = conversations.filter(chat => selectedChatIds.has(getChatRoomId(chat))); if (chatsToDelete.length === 0) { return; @@ -169,7 +157,7 @@ export const useRecentChats = ({ toggleModal }: UseRecentChatsParams) => { }; const toggleMenu = (roomId: string) => { - setOpenMenuId((current) => (current === roomId ? null : roomId)); + setOpenMenuId(current => (current === roomId ? null : roomId)); }; const isSelectionMode = selectedChatIds.size > 0; diff --git a/components/VercelChat/dialogs/tasks/useTaskDetailsDialog.ts b/components/VercelChat/dialogs/tasks/useTaskDetailsDialog.ts index 20a8fac78..637539c1f 100644 --- a/components/VercelChat/dialogs/tasks/useTaskDetailsDialog.ts +++ b/components/VercelChat/dialogs/tasks/useTaskDetailsDialog.ts @@ -7,16 +7,11 @@ interface UseTaskDetailsDialogParams { isDeleted?: boolean; } -export const useTaskDetailsDialog = ({ - task, - isDeleted = false, -}: UseTaskDetailsDialogParams) => { +export const useTaskDetailsDialog = ({ task, isDeleted = false }: UseTaskDetailsDialogParams) => { const [isDialogOpen, setIsDialogOpen] = useState(false); const [editTitle, setEditTitle] = useState(task.title); const [editPrompt, setEditPrompt] = useState(task.prompt); - const [editCron, setEditCron] = useState( - task.schedule?.trim() || "0 9 * * *" - ); + const [editCron, setEditCron] = useState(task.schedule?.trim() || "0 9 * * *"); const [editModel, setEditModel] = useState(task.model || DEFAULT_MODEL); // Sync edit state when task prop changes diff --git a/components/VercelChat/mapKnowledgeToOptions.ts b/components/VercelChat/mapKnowledgeToOptions.ts index 1cd7b79b4..9122f800e 100644 --- a/components/VercelChat/mapKnowledgeToOptions.ts +++ b/components/VercelChat/mapKnowledgeToOptions.ts @@ -1,10 +1,10 @@ import type { SuggestionDataItem } from "react-mentions"; export const mapKnowledgeToOptions = ( - files: Array<{ url: string; name: string }> + files: Array<{ url: string; name: string }>, ): SuggestionDataItem[] => (files || []) - .filter((f) => typeof f?.url === "string" && !!f.url) - .map((f) => ({ id: String(f.url), display: String(f.name || f.url) })); + .filter(f => typeof f?.url === "string" && !!f.url) + .map(f => ({ id: String(f.url), display: String(f.name || f.url) })); -export default mapKnowledgeToOptions; \ No newline at end of file +export default mapKnowledgeToOptions; diff --git a/components/VercelChat/mentionsStyles.ts b/components/VercelChat/mentionsStyles.ts index 5f3081d9e..7c8ae628a 100644 --- a/components/VercelChat/mentionsStyles.ts +++ b/components/VercelChat/mentionsStyles.ts @@ -47,4 +47,4 @@ export const mentionsStyles = { backgroundColor: "transparent", }, }, -} as const; \ No newline at end of file +} as const; diff --git a/components/VercelChat/parseMentionedIds.ts b/components/VercelChat/parseMentionedIds.ts index 42146ec0c..7c53b1d06 100644 --- a/components/VercelChat/parseMentionedIds.ts +++ b/components/VercelChat/parseMentionedIds.ts @@ -3,11 +3,11 @@ export const parseMentionedIds = (value: string | undefined): Set => { const text = value || ""; const regex = /@\[[^\]]+\]\(([^)]+)\)/g; let m: RegExpExecArray | null; - // eslint-disable-next-line no-cond-assign + while ((m = regex.exec(text))) { if (m[1]) ids.add(m[1]); } return ids; }; -export default parseMentionedIds; \ No newline at end of file +export default parseMentionedIds; diff --git a/components/VercelChat/tools/segment-fans/animations.ts b/components/VercelChat/tools/segment-fans/animations.ts index e1ff9b662..b9c26d645 100644 --- a/components/VercelChat/tools/segment-fans/animations.ts +++ b/components/VercelChat/tools/segment-fans/animations.ts @@ -5,24 +5,24 @@ export const containerVariants = { opacity: 1, transition: { staggerChildren: 0.05, - delayChildren: 0.1 - } - } + delayChildren: 0.1, + }, + }, }; // Header slide-in animation export const headerVariants = { hidden: { opacity: 0, y: -10 }, - show: { - opacity: 1, + show: { + opacity: 1, y: 0, - transition: { duration: 0.3 } - } + transition: { duration: 0.3 }, + }, }; // Simple fade + slide animation for error/empty states export const fadeInVariants = { initial: { opacity: 0, y: 10 }, animate: { opacity: 1, y: 0 }, - transition: { duration: 0.3 } -}; \ No newline at end of file + transition: { duration: 0.3 }, +}; diff --git a/components/hooks/useCommentsResult.ts b/components/hooks/useCommentsResult.ts index be4814d96..c76c302a5 100644 --- a/components/hooks/useCommentsResult.ts +++ b/components/hooks/useCommentsResult.ts @@ -9,7 +9,7 @@ export const useCommentsResult = (comments: Comment[]) => { // Handle comment click const handleCommentClick = (comment: Comment) => { - const index = comments.findIndex((c) => c.id === comment.id); + const index = comments.findIndex(c => c.id === comment.id); setCurrentIndex(index !== -1 ? index : 0); setSelectedComment(comment); setDialogOpen(true); @@ -26,10 +26,7 @@ export const useCommentsResult = (comments: Comment[]) => { const newIndex = currentIndex - 1; setCurrentIndex(newIndex); setSelectedComment(comments[newIndex]); - } else if ( - direction === "next" && - currentIndex < comments.length - 1 - ) { + } else if (direction === "next" && currentIndex < comments.length - 1) { const newIndex = currentIndex + 1; setCurrentIndex(newIndex); setSelectedComment(comments[newIndex]); @@ -43,6 +40,6 @@ export const useCommentsResult = (comments: Comment[]) => { totalComments: comments.length, handleCommentClick, handleCloseDialog, - handleNavigation + handleNavigation, }; -}; \ No newline at end of file +}; diff --git a/hooks/useAccountOrganizations.ts b/hooks/useAccountOrganizations.ts index 0f735b9c1..a700c2f8d 100644 --- a/hooks/useAccountOrganizations.ts +++ b/hooks/useAccountOrganizations.ts @@ -17,10 +17,10 @@ interface OrganizationsResponse { /** * Fetch account's organizations from the API. * The API resolves the account from the Bearer token — no query params needed. + * + * @param accessToken */ -const fetchAccountOrganizations = async ( - accessToken: string, -): Promise => { +const fetchAccountOrganizations = async (accessToken: string): Promise => { const response = await fetch(`${NEW_API_BASE_URL}/api/organizations`, { headers: { Authorization: `Bearer ${accessToken}`, diff --git a/hooks/useAddArtistToOrganization.ts b/hooks/useAddArtistToOrganization.ts index ac37aaf5e..9373a35a1 100644 --- a/hooks/useAddArtistToOrganization.ts +++ b/hooks/useAddArtistToOrganization.ts @@ -8,6 +8,8 @@ interface UseAddArtistToOrganizationOptions { /** * Hook to handle adding an artist to an organization. * Manages loading state and API call. + * + * @param options */ const useAddArtistToOrganization = (options?: UseAddArtistToOrganizationOptions) => { const [addingToOrgId, setAddingToOrgId] = useState(null); @@ -34,7 +36,7 @@ const useAddArtistToOrganization = (options?: UseAddArtistToOrganizationOptions) setAddingToOrgId(null); } }, - [options] + [options], ); return { @@ -45,4 +47,3 @@ const useAddArtistToOrganization = (options?: UseAddArtistToOrganizationOptions) }; export default useAddArtistToOrganization; - diff --git a/hooks/useApiKey.ts b/hooks/useApiKey.ts index b6e1e979c..eb920c00c 100644 --- a/hooks/useApiKey.ts +++ b/hooks/useApiKey.ts @@ -18,6 +18,9 @@ interface UseApiKeyReturn { deleteApiKey: (keyId: string) => Promise; } +/** + * + */ export default function useApiKey(): UseApiKeyReturn { const [apiKey, setApiKey] = useState(null); const [showApiKeyModal, setShowApiKeyModal] = useState(false); @@ -48,7 +51,7 @@ export default function useApiKey(): UseApiKeyReturn { } return createApiKey(keyName.trim(), accessToken, selectedOrgId); }, - onSuccess: (key) => { + onSuccess: key => { setApiKey(key); setShowApiKeyModal(true); queryClient.invalidateQueries({ queryKey }); diff --git a/hooks/useArtistAgents.ts b/hooks/useArtistAgents.ts index f268e653b..ad5540107 100644 --- a/hooks/useArtistAgents.ts +++ b/hooks/useArtistAgents.ts @@ -10,10 +10,8 @@ const useArtistAgents = () => { useEffect(() => { const getAgents = async () => { if (selectedArtist) { - const socialIds = selectedArtist.account_socials?.map( - (social: SOCIAL) => social.id - ); - const queryString = socialIds?.map((id) => `socialId=${id}`).join("&"); + const socialIds = selectedArtist.account_socials?.map((social: SOCIAL) => social.id); + const queryString = socialIds?.map(id => `socialId=${id}`).join("&"); const response = await fetch(`/api/agents?${queryString}`); const data = await response.json(); if (data.error) return; diff --git a/hooks/useArtistCatalogSongs.ts b/hooks/useArtistCatalogSongs.ts index 8fbf06e51..490eedf1d 100644 --- a/hooks/useArtistCatalogSongs.ts +++ b/hooks/useArtistCatalogSongs.ts @@ -16,12 +16,9 @@ const useArtistCatalogSongs = ({ const { selectedArtist } = useArtistProvider(); const activeArtistName = selectedArtist?.name ?? undefined; - const [shouldUseArtistFilter, setShouldUseArtistFilter] = - useState(!!activeArtistName); + const [shouldUseArtistFilter, setShouldUseArtistFilter] = useState(!!activeArtistName); - const effectiveArtistName = shouldUseArtistFilter - ? activeArtistName - : undefined; + const effectiveArtistName = shouldUseArtistFilter ? activeArtistName : undefined; const queryResult = useCatalogSongs({ catalogId, diff --git a/hooks/useArtistConnectorCallback.ts b/hooks/useArtistConnectorCallback.ts index 53139290d..7a30feefc 100644 --- a/hooks/useArtistConnectorCallback.ts +++ b/hooks/useArtistConnectorCallback.ts @@ -22,11 +22,7 @@ import { toast } from "sonner"; */ export function useArtistConnectorCallback(): string { const searchParams = useSearchParams(); - const { - artists, - toggleUpdate, - setIsOpenSettingModal, - } = useArtistProvider(); + const { artists, toggleUpdate, setIsOpenSettingModal } = useArtistProvider(); const [defaultTab, setDefaultTab] = useState("general"); useEffect(() => { @@ -38,9 +34,7 @@ export function useArtistConnectorCallback(): string { // Wait until artists are loaded before finding the target if (!artists || artists.length === 0) return; - const artist = artists.find( - (a: { account_id: string }) => a.account_id === artistId, - ); + const artist = artists.find((a: { account_id: string }) => a.account_id === artistId); if (!artist) return; diff --git a/hooks/useArtistFans.ts b/hooks/useArtistFans.ts index 3681ddf31..2f0e28610 100644 --- a/hooks/useArtistFans.ts +++ b/hooks/useArtistFans.ts @@ -35,15 +35,19 @@ export interface FansError { /** * Fetches fans for a specific artist from the API with pagination + * + * @param artistAccountId + * @param page + * @param limit */ async function fetchFans( artistAccountId: string, page: number = 1, - limit: number = 20 + limit: number = 20, ): Promise { try { const response = await fetch( - `https://api.recoupable.com/api/fans?artist_account_id=${artistAccountId}&page=${page}&limit=${limit}` + `https://api.recoupable.com/api/fans?artist_account_id=${artistAccountId}&page=${page}&limit=${limit}`, ); if (!response.ok) { @@ -73,17 +77,19 @@ async function fetchFans( /** * Hook to fetch and manage fans for an artist with automatic pagination * This hook will automatically fetch all pages with a controlled delay between requests + * + * @param artistAccountId + * @param limit */ export function useArtistFans( artistAccountId?: string, - limit: number = 20 + limit: number = 20, ): UseInfiniteQueryResult, Error> { const queryResult = useInfiniteQuery({ queryKey: ["fans", artistAccountId, limit], - queryFn: ({ pageParam = 1 }) => - fetchFans(artistAccountId!, pageParam, limit), + queryFn: ({ pageParam = 1 }) => fetchFans(artistAccountId!, pageParam, limit), initialPageParam: 1, - getNextPageParam: (lastPage) => { + getNextPageParam: lastPage => { const { pagination } = lastPage; // If we're on the last page, return undefined to indicate no more pages if (pagination.page >= pagination.total_pages) { diff --git a/hooks/useArtistFilesForMentions.ts b/hooks/useArtistFilesForMentions.ts index 26e6429e0..87496b507 100644 --- a/hooks/useArtistFilesForMentions.ts +++ b/hooks/useArtistFilesForMentions.ts @@ -13,23 +13,30 @@ export type MentionableFile = { relative_path: string; }; +/** + * + */ export default function useArtistFilesForMentions() { const fm = useFilesManager(undefined, true); const ownerAccountId = fm.ownerAccountId; const artistAccountId = fm.artistAccountId; - const rows = (fm.files as unknown) as ListedFileRow[]; + const rows = fm.files as unknown as ListedFileRow[]; const files: MentionableFile[] = useMemo(() => { - return rows.map((r) => ({ + return rows.map(r => ({ id: r.id, file_name: r.file_name, storage_key: r.storage_key, mime_type: r.mime_type ?? null, is_directory: r.is_directory, - relative_path: getRelativeStoragePath(r.storage_key, ownerAccountId, artistAccountId, r.is_directory), + relative_path: getRelativeStoragePath( + r.storage_key, + ownerAccountId, + artistAccountId, + r.is_directory, + ), })); }, [rows, ownerAccountId, artistAccountId]); return { files, isLoading: fm.isLoading }; } - diff --git a/hooks/useArtistFromRoom.ts b/hooks/useArtistFromRoom.ts index 01e6563b3..d74b6004f 100644 --- a/hooks/useArtistFromRoom.ts +++ b/hooks/useArtistFromRoom.ts @@ -5,35 +5,36 @@ import type { ArtistRecord } from "@/types/Artist"; /** * A hook that automatically selects the artist associated with a room. - * @param roomId The ID of the room to get the artist for + * + * @param roomId - The ID of the room to get the artist for */ export function useArtistFromRoom(roomId: string) { const { userData } = useUserProvider(); const { selectedArtist, artists, setSelectedArtist, getArtists } = useArtistProvider(); const hasRun = useRef(false); - + useEffect(() => { if (hasRun.current || !roomId || !userData?.id) return; hasRun.current = true; - + (async () => { try { const response = await fetch( - `/api/room/artist?roomId=${encodeURIComponent(roomId)}&accountId=${encodeURIComponent(userData.id)}` + `/api/room/artist?roomId=${encodeURIComponent(roomId)}&accountId=${encodeURIComponent(userData.id)}`, ); - + if (!response.ok) return; const data = await response.json(); - + if (data.new_room_id && data.new_room_id !== roomId) { - window.history.replaceState({}, '', `/chat/${data.new_room_id}`); + window.history.replaceState({}, "", `/chat/${data.new_room_id}`); } - + if (!data.artist_id || selectedArtist?.account_id === data.artist_id) return; - + const artistList = artists as ArtistRecord[]; const artist = artistList.find(a => a.account_id === data.artist_id); - + if (artist) { setSelectedArtist(artist); } else { @@ -44,4 +45,4 @@ export function useArtistFromRoom(roomId: string) { } })(); }, [roomId, userData, selectedArtist, artists, setSelectedArtist, getArtists]); -} \ No newline at end of file +} diff --git a/hooks/useArtistImage.ts b/hooks/useArtistImage.ts index 742548616..35d298241 100644 --- a/hooks/useArtistImage.ts +++ b/hooks/useArtistImage.ts @@ -6,9 +6,7 @@ interface UseArtistImageResult { artistName: string | null; } -export const useArtistImage = ( - artistAccountId?: string | null -): UseArtistImageResult => { +export const useArtistImage = (artistAccountId?: string | null): UseArtistImageResult => { const { artists } = useArtistProvider(); const artist = useMemo(() => { @@ -16,9 +14,7 @@ export const useArtistImage = ( return null; } - const match = artists.find( - (candidate) => candidate.account_id === artistAccountId - ); + const match = artists.find(candidate => candidate.account_id === artistAccountId); if (match) { return match; } diff --git a/hooks/useArtistInstruction.ts b/hooks/useArtistInstruction.ts index 2e010642d..0d6da9f87 100644 --- a/hooks/useArtistInstruction.ts +++ b/hooks/useArtistInstruction.ts @@ -1,5 +1,9 @@ import { useQuery } from "@tanstack/react-query"; +/** + * + * @param artistId + */ export function useArtistInstruction(artistId?: string) { return useQuery({ queryKey: ["artist-instruction", artistId], @@ -16,5 +20,3 @@ export function useArtistInstruction(artistId?: string) { } export default useArtistInstruction; - - diff --git a/hooks/useArtistKnowledge.ts b/hooks/useArtistKnowledge.ts index 262ab49ef..0ee4de719 100644 --- a/hooks/useArtistKnowledge.ts +++ b/hooks/useArtistKnowledge.ts @@ -1,6 +1,10 @@ import { useQuery } from "@tanstack/react-query"; import type { KnowledgeBaseEntry } from "@/lib/supabase/getArtistKnowledge"; +/** + * + * @param artistId + */ export function useArtistKnowledge(artistId?: string) { return useQuery({ queryKey: ["artist-knowledge", artistId], @@ -19,5 +23,3 @@ export function useArtistKnowledge(artistId?: string) { } export default useArtistKnowledge; - - diff --git a/hooks/useArtistPosts.ts b/hooks/useArtistPosts.ts index 0053e782b..ef3d16f29 100644 --- a/hooks/useArtistPosts.ts +++ b/hooks/useArtistPosts.ts @@ -3,10 +3,7 @@ import { type UseInfiniteQueryResult, type InfiniteData, } from "@tanstack/react-query"; -import fetchPosts, { - type PostsResponse, - type PostsError, -} from "@/lib/recoup/fetchPosts"; +import fetchPosts, { type PostsResponse, type PostsError } from "@/lib/recoup/fetchPosts"; export type PostsInfiniteResponse = { pages: PostsResponse[]; @@ -15,10 +12,13 @@ export type PostsInfiniteResponse = { /** * Hook to fetch and manage posts for an artist with infinite scrolling + * + * @param artistAccountId + * @param limit */ export function useArtistPosts( artistAccountId?: string, - limit: number = 20 + limit: number = 20, ): UseInfiniteQueryResult, PostsError> { return useInfiniteQuery({ queryKey: ["posts", artistAccountId, limit], diff --git a/hooks/useArtistSegments.ts b/hooks/useArtistSegments.ts index 879cb902f..48766bd40 100644 --- a/hooks/useArtistSegments.ts +++ b/hooks/useArtistSegments.ts @@ -1,15 +1,23 @@ import { type Segment } from "@/lib/supabase/getArtistSegments"; import { useQuery } from "@tanstack/react-query"; +/** + * + * @param artistId + */ async function fetchSegments(artistId: string): Promise { const response = await fetch(`/api/segments?artistId=${artistId}`); if (!response.ok) { throw new Error("Failed to fetch segments"); } const segments: Segment[] = await response.json(); - return segments.filter((s) => s.size > 0); + return segments.filter(s => s.size > 0); } +/** + * + * @param artistId + */ export function useArtistSegments(artistId?: string) { return useQuery({ queryKey: ["segments", artistId], diff --git a/hooks/useArtistSocials.ts b/hooks/useArtistSocials.ts index 67013cf8a..365f282f5 100644 --- a/hooks/useArtistSocials.ts +++ b/hooks/useArtistSocials.ts @@ -2,23 +2,23 @@ import { useQuery } from "@tanstack/react-query"; import { getArtistSocials } from "@/lib/api/artist/getArtistSocials"; import { useArtistProvider } from "@/providers/ArtistProvider"; +/** + * + */ export function useArtistSocials() { const { selectedArtist } = useArtistProvider(); const artist_account_id = selectedArtist?.account_id; const { data: socialsData } = useQuery({ queryKey: ["artistSocials", artist_account_id], queryFn: () => - artist_account_id - ? getArtistSocials(artist_account_id) - : Promise.resolve(undefined), + artist_account_id ? getArtistSocials(artist_account_id) : Promise.resolve(undefined), enabled: !!artist_account_id, staleTime: 1000 * 60 * 5, }); const hasInstagram = - socialsData?.socials?.some((s) => - s.profile_url?.toLowerCase().includes("instagram.com") - ) ?? false; + socialsData?.socials?.some(s => s.profile_url?.toLowerCase().includes("instagram.com")) ?? + false; return { socialsData, hasInstagram }; } diff --git a/hooks/useAttachments.ts b/hooks/useAttachments.ts index 7bf8bd1d3..3a2fac990 100644 --- a/hooks/useAttachments.ts +++ b/hooks/useAttachments.ts @@ -19,10 +19,10 @@ export default function useAttachments() { } setAttachments((prevAttachments: FileUIPart[]) => - prevAttachments.filter((_, index) => index !== indexToRemove) + prevAttachments.filter((_, index) => index !== indexToRemove), ); }, - [attachments] + [attachments], ); // Clear all attachments @@ -39,8 +39,7 @@ export default function useAttachments() { // Filter for pending attachments (currently being uploaded) const pendingAttachments = attachments.filter( - (attachment: FileUIPart) => - attachment.url?.startsWith("blob:") || !attachment.url + (attachment: FileUIPart) => attachment.url?.startsWith("blob:") || !attachment.url, ); // Check if there are any pending uploads diff --git a/hooks/useAutoCollapse.ts b/hooks/useAutoCollapse.ts index c9c2151ff..6f1de11ca 100644 --- a/hooks/useAutoCollapse.ts +++ b/hooks/useAutoCollapse.ts @@ -1,11 +1,11 @@ /** * Custom hook for auto-collapse behavior - * + * * Single Responsibility: Manages auto-open/close state based on streaming status * Used by components that need to automatically collapse when content finishes loading */ -import { useEffect, useRef, useState } from 'react'; +import { useEffect, useRef, useState } from "react"; interface UseAutoCollapseOptions { isStreaming: boolean; @@ -13,6 +13,13 @@ interface UseAutoCollapseOptions { hasContent: boolean; } +/** + * + * @param root0 + * @param root0.isStreaming + * @param root0.defaultOpen + * @param root0.hasContent + */ export function useAutoCollapse({ isStreaming, defaultOpen = true, @@ -39,4 +46,3 @@ export function useAutoCollapse({ return { isOpen, setIsOpen }; } - diff --git a/hooks/useAutoScroll.ts b/hooks/useAutoScroll.ts index a06b671e5..90eaf93a7 100644 --- a/hooks/useAutoScroll.ts +++ b/hooks/useAutoScroll.ts @@ -3,6 +3,8 @@ import { useEffect, useRef } from "react"; /** * Returns a ref to attach to a scrollable container. * Automatically scrolls to the bottom whenever the dependency changes. + * + * @param dep */ export function useAutoScroll(dep: unknown) { const ref = useRef(null); diff --git a/hooks/useBatchSignedUrls.ts b/hooks/useBatchSignedUrls.ts index 6c9bfaed1..6b0fa5c13 100644 --- a/hooks/useBatchSignedUrls.ts +++ b/hooks/useBatchSignedUrls.ts @@ -4,25 +4,31 @@ import { useState, useEffect } from "react"; import { createBatchSignedUrlsClient } from "@/lib/supabase/storage/client"; import { GroupedSuggestion } from "@/hooks/useFileMentionSuggestions"; +/** + * + * @param suggestions + */ export function useBatchSignedUrls(suggestions: GroupedSuggestion[]) { const [signedUrls, setSignedUrls] = useState>({}); useEffect(() => { const imagesToFetch: string[] = []; - - suggestions.forEach((entry) => { - const isImage = entry.mime_type?.startsWith("image/") || /\.(png|jpg|jpeg|gif|webp|svg)$/i.test(entry.storage_key || entry.display || ""); - - // If it's an image and we don't have the URL yet, add to fetch list - if (isImage && entry.storage_key && !signedUrls[entry.storage_key]) { - imagesToFetch.push(entry.storage_key); - } + + suggestions.forEach(entry => { + const isImage = + entry.mime_type?.startsWith("image/") || + /\.(png|jpg|jpeg|gif|webp|svg)$/i.test(entry.storage_key || entry.display || ""); + + // If it's an image and we don't have the URL yet, add to fetch list + if (isImage && entry.storage_key && !signedUrls[entry.storage_key]) { + imagesToFetch.push(entry.storage_key); + } }); if (imagesToFetch.length > 0) { - createBatchSignedUrlsClient(imagesToFetch).then((newUrls) => { - setSignedUrls(prev => ({ ...prev, ...newUrls })); - }); + createBatchSignedUrlsClient(imagesToFetch).then(newUrls => { + setSignedUrls(prev => ({ ...prev, ...newUrls })); + }); } }, [suggestions, signedUrls]); diff --git a/hooks/useCatalogSongs.ts b/hooks/useCatalogSongs.ts index 44179903e..82afa98cf 100644 --- a/hooks/useCatalogSongs.ts +++ b/hooks/useCatalogSongs.ts @@ -1,12 +1,5 @@ -import { - useInfiniteQuery, - InfiniteData, - UseInfiniteQueryResult, -} from "@tanstack/react-query"; -import { - CatalogSongsResponse, - getCatalogSongs, -} from "@/lib/catalog/getCatalogSongs"; +import { useInfiniteQuery, InfiniteData, UseInfiniteQueryResult } from "@tanstack/react-query"; +import { CatalogSongsResponse, getCatalogSongs } from "@/lib/catalog/getCatalogSongs"; import useObserverTarget from "./useObserverTarget"; import { RefObject } from "react"; @@ -17,9 +10,7 @@ interface UseCatalogSongsOptions { artistName?: string; } -type UseCatalogSongsReturn = UseInfiniteQueryResult< - InfiniteData -> & { +type UseCatalogSongsReturn = UseInfiniteQueryResult> & { observerTarget: RefObject; }; @@ -35,7 +26,7 @@ const useCatalogSongs = ({ return await getCatalogSongs(catalogId, pageSize, pageParam, artistName); }, enabled: enabled && !!catalogId, - getNextPageParam: (lastPage) => { + getNextPageParam: lastPage => { const { page, total_pages } = lastPage.pagination; const nextPage = page < total_pages ? page + 1 : undefined; return nextPage; diff --git a/hooks/useCatalogSongsFileSelect.ts b/hooks/useCatalogSongsFileSelect.ts index 1f53ccd69..488a0fde7 100644 --- a/hooks/useCatalogSongsFileSelect.ts +++ b/hooks/useCatalogSongsFileSelect.ts @@ -12,6 +12,10 @@ export interface UploadResult { message: string; } +/** + * + * @param catalogId + */ export function useCatalogSongsFileSelect(catalogId?: string) { const [uploadProgress, setUploadProgress] = useState({ current: 0, @@ -47,9 +51,7 @@ export function useCatalogSongsFileSelect(catalogId?: string) { }, }); - const handleFileSelect = async ( - event: React.ChangeEvent - ) => { + const handleFileSelect = async (event: React.ChangeEvent) => { const file = event.target.files?.[0]; if (!file) return; diff --git a/hooks/useChatTransport.ts b/hooks/useChatTransport.ts index fb4898c28..62424b870 100644 --- a/hooks/useChatTransport.ts +++ b/hooks/useChatTransport.ts @@ -4,6 +4,9 @@ import { NEW_API_BASE_URL } from "@/lib/consts"; import { useAccessToken } from "./useAccessToken"; import { useApiOverride } from "./useApiOverride"; +/** + * + */ export function useChatTransport() { const accessToken = useAccessToken(); const apiOverride = useApiOverride(); diff --git a/hooks/useClickOutside.ts b/hooks/useClickOutside.ts index c8919b1d6..0c747776d 100644 --- a/hooks/useClickOutside.ts +++ b/hooks/useClickOutside.ts @@ -2,6 +2,7 @@ import { useEffect, RefObject } from "react"; /** * Hook that calls a handler when clicking outside the referenced element. + * * @param ref - React ref to the element to detect clicks outside of * @param handler - Callback function to call when clicking outside * @param enabled - Whether the listener is active (default: true) @@ -9,7 +10,7 @@ import { useEffect, RefObject } from "react"; const useClickOutside = ( ref: RefObject, handler: () => void, - enabled: boolean = true + enabled: boolean = true, ) => { useEffect(() => { if (!enabled) return; @@ -26,4 +27,3 @@ const useClickOutside = ( }; export default useClickOutside; - diff --git a/hooks/useConnectorHandlers.ts b/hooks/useConnectorHandlers.ts index d7182872f..ceef58eb3 100644 --- a/hooks/useConnectorHandlers.ts +++ b/hooks/useConnectorHandlers.ts @@ -16,6 +16,12 @@ interface UseConnectorHandlersReturn { /** * Hook for managing connector connect/disconnect state and handlers. + * + * @param root0 + * @param root0.slug + * @param root0.connectedAccountId + * @param root0.onConnect + * @param root0.onDisconnect */ export function useConnectorHandlers({ slug, diff --git a/hooks/useConnectors.ts b/hooks/useConnectors.ts index 2cd8faa1d..a900284da 100644 --- a/hooks/useConnectors.ts +++ b/hooks/useConnectors.ts @@ -31,6 +31,8 @@ interface UseConnectorsConfig { /** * Hook for managing connectors. * Works for both user-level and artist-level connectors via optional config. + * + * @param config */ export function useConnectors(config?: UseConnectorsConfig) { const { accountId, allowedSlugs, callbackUrl } = config ?? {}; @@ -59,9 +61,7 @@ export function useConnectors(config?: UseConnectorsConfig) { try { const allConnectors = await fetchConnectorsApi(accessToken, accountId); const allowed = new Set(slugFilter); - const visible = allConnectors.filter((c) => - allowed.has(c.slug.toLowerCase()), - ); + const visible = allConnectors.filter(c => allowed.has(c.slug.toLowerCase())); setConnectors(visible); } catch (err) { setError(err instanceof Error ? err.message : "Unknown error"); diff --git a/hooks/useCopy.ts b/hooks/useCopy.ts index 267b5548a..9fb6cdcbe 100644 --- a/hooks/useCopy.ts +++ b/hooks/useCopy.ts @@ -8,6 +8,7 @@ interface UseCopyReturn { /** * Hook for copying text to clipboard with visual feedback + * * @param resetDelay - Time in milliseconds before resetting the copied state (default: 2000) * @returns Object with copied state and copy function */ diff --git a/hooks/useCreateArtistTool.ts b/hooks/useCreateArtistTool.ts index 00d17cba6..42a4f755d 100644 --- a/hooks/useCreateArtistTool.ts +++ b/hooks/useCreateArtistTool.ts @@ -7,6 +7,8 @@ import copyMessagesClient from "@/lib/copyMessagesClient"; /** * Hook for managing the create artist tool result * Handles refreshing artists, copying messages, and navigation + * + * @param result */ export function useCreateArtistTool(result: CreateArtistResult) { const { status, id } = useVercelChatContext(); @@ -21,10 +23,7 @@ export function useCreateArtistTool(result: CreateArtistResult) { // Skip if no artist data or already processing const shouldSkip = - !result.artist || - !result.artist.account_id || - isProcessing || - !isFinishedStreaming; + !result.artist || !result.artist.account_id || isProcessing || !isFinishedStreaming; if (shouldSkip) { return; } @@ -38,10 +37,7 @@ export function useCreateArtistTool(result: CreateArtistResult) { if (needsRedirect) { // Copy messages from current room to the newly created room - const success = await copyMessagesClient( - id as string, - result.newRoomId as string - ); + const success = await copyMessagesClient(id as string, result.newRoomId as string); // Refresh conversations to show the new chat await refetchConversations(); diff --git a/hooks/useCreateOrganization.ts b/hooks/useCreateOrganization.ts index f2d8837b6..72f0eeec3 100644 --- a/hooks/useCreateOrganization.ts +++ b/hooks/useCreateOrganization.ts @@ -62,4 +62,3 @@ const useCreateOrganization = () => { }; export default useCreateOrganization; - diff --git a/hooks/useCreateSegments.ts b/hooks/useCreateSegments.ts index 80ebb5d33..533d11a3f 100644 --- a/hooks/useCreateSegments.ts +++ b/hooks/useCreateSegments.ts @@ -2,6 +2,9 @@ import { useState } from "react"; import { toast } from "react-toastify"; import { useArtistProvider } from "@/providers/ArtistProvider"; +/** + * + */ export function useCreateSegments() { const { selectedArtist } = useArtistProvider(); const artist_account_id = selectedArtist?.account_id; @@ -26,9 +29,7 @@ export function useCreateSegments() { toast.success("Segments generated successfully!"); if (onSuccess) onSuccess(); } catch (error) { - toast.error( - error instanceof Error ? error.message : "Failed to generate segments" - ); + toast.error(error instanceof Error ? error.message : "Failed to generate segments"); console.error(error); } finally { setLoading(false); diff --git a/hooks/useCreateWorkspaceModal.ts b/hooks/useCreateWorkspaceModal.ts index 1caf0a0c3..ed35cb268 100644 --- a/hooks/useCreateWorkspaceModal.ts +++ b/hooks/useCreateWorkspaceModal.ts @@ -20,4 +20,3 @@ export function useCreateWorkspaceModal() { } export default useCreateWorkspaceModal; - diff --git a/hooks/useCronEditor.ts b/hooks/useCronEditor.ts index fdc5fe846..3c62ad802 100644 --- a/hooks/useCronEditor.ts +++ b/hooks/useCronEditor.ts @@ -17,14 +17,8 @@ const DEFAULT_SIMPLE_MODE: SimpleModeSettings = { dayOfMonth: "1", }; -const useCronEditor = ({ - cronExpression, - onCronExpressionChange, -}: UseCronEditorArgs) => { - const parsedParts = useMemo( - () => parseCronParts(cronExpression), - [cronExpression] - ); +const useCronEditor = ({ cronExpression, onCronExpressionChange }: UseCronEditorArgs) => { + const parsedParts = useMemo(() => parseCronParts(cronExpression), [cronExpression]); const [fieldValues, setFieldValues] = useState(parsedParts); const [mode, setMode] = useState<"simple" | "advanced">("simple"); const [simpleMode, setSimpleMode] = useState(() => { @@ -40,12 +34,10 @@ const useCronEditor = ({ }, [parsedParts]); const handleFieldChange = (index: number) => (value: string) => { - setFieldValues((prev) => { + setFieldValues(prev => { const updated = [...prev]; updated[index] = value; - const normalizedParts = updated.map((part) => - part.trim() === "" ? "*" : part.trim() - ); + const normalizedParts = updated.map(part => (part.trim() === "" ? "*" : part.trim())); onCronExpressionChange(normalizedParts.join(" ")); return updated; }); @@ -59,15 +51,11 @@ const useCronEditor = ({ } }; - const handleSimpleModeChange = ( - field: keyof SimpleModeSettings, - value: string - ) => { - setSimpleMode((prev) => { + const handleSimpleModeChange = (field: keyof SimpleModeSettings, value: string) => { + setSimpleMode(prev => { const updated = { ...prev, [field]: value }; - const [hour, minute] = - updated.frequency === "hourly" ? ["*", "0"] : updated.time.split(":"); + const [hour, minute] = updated.frequency === "hourly" ? ["*", "0"] : updated.time.split(":"); let cron = "* * * * *"; @@ -95,9 +83,7 @@ const useCronEditor = ({ }; const normalizedCron = useMemo(() => { - return fieldValues - .map((part) => (part.trim() === "" ? "*" : part.trim())) - .join(" "); + return fieldValues.map(part => (part.trim() === "" ? "*" : part.trim())).join(" "); }, [fieldValues]); const humanReadable = useMemo(() => { diff --git a/hooks/useDeleteSandbox.ts b/hooks/useDeleteSandbox.ts index 79e38c562..2749661b1 100644 --- a/hooks/useDeleteSandbox.ts +++ b/hooks/useDeleteSandbox.ts @@ -8,6 +8,9 @@ interface UseDeleteSandboxReturn { isDeleting: boolean; } +/** + * + */ export default function useDeleteSandbox(): UseDeleteSandboxReturn { const { getAccessToken } = usePrivy(); const queryClient = useQueryClient(); diff --git a/hooks/useDeleteScheduledAction.ts b/hooks/useDeleteScheduledAction.ts index cf2d043bd..0d9493d82 100644 --- a/hooks/useDeleteScheduledAction.ts +++ b/hooks/useDeleteScheduledAction.ts @@ -31,9 +31,9 @@ export const useDeleteScheduledAction = () => { throw error; } finally { setIsLoading(false); - queryClient.invalidateQueries({ + queryClient.invalidateQueries({ queryKey: ["scheduled-actions"], - exact: false + exact: false, }); } }; diff --git a/hooks/useDragAndDrop.ts b/hooks/useDragAndDrop.ts index 2741e4b56..5ca500d0e 100644 --- a/hooks/useDragAndDrop.ts +++ b/hooks/useDragAndDrop.ts @@ -13,6 +13,13 @@ type UseDragAndDropConfig = { /** * Hook to handle drag-and-drop file upload using react-dropzone * Provides drag state and handlers for file upload + * + * @param root0 + * @param root0.onDrop + * @param root0.maxFiles + * @param root0.maxSizeMB + * @param root0.allowedTypes + * @param root0.disabled */ export function useDragAndDrop({ onDrop, @@ -26,15 +33,12 @@ export function useDragAndDrop({ /** * Validate file extension against blocked list */ - const isBlockedFile = useCallback( - (fileName: string): boolean => { - // Blocked file types for security - const blockedExtensions = [".exe", ".dll", ".bat", ".sh", ".cmd"]; - const lowerName = fileName.toLowerCase(); - return blockedExtensions.some((ext) => lowerName.endsWith(ext)); - }, - [] - ); + const isBlockedFile = useCallback((fileName: string): boolean => { + // Blocked file types for security + const blockedExtensions = [".exe", ".dll", ".bat", ".sh", ".cmd"]; + const lowerName = fileName.toLowerCase(); + return blockedExtensions.some(ext => lowerName.endsWith(ext)); + }, []); /** * Handle accepted files @@ -42,7 +46,7 @@ export function useDragAndDrop({ const handleAcceptedFiles = useCallback( (acceptedFiles: File[]) => { // Filter out blocked file types (additional security check) - const safeFiles = acceptedFiles.filter((file) => { + const safeFiles = acceptedFiles.filter(file => { if (isBlockedFile(file.name)) { toast.error(`File type not allowed: ${file.name}`); return false; @@ -54,7 +58,7 @@ export function useDragAndDrop({ onDrop(safeFiles); } }, - [onDrop, isBlockedFile] + [onDrop, isBlockedFile], ); /** @@ -62,10 +66,10 @@ export function useDragAndDrop({ */ const handleRejectedFiles = useCallback( (fileRejections: FileRejection[]) => { - fileRejections.forEach((rejection) => { + fileRejections.forEach(rejection => { const { file, errors } = rejection; - - errors.forEach((error) => { + + errors.forEach(error => { if (error.code === "file-too-large") { toast.error(`File "${file.name}" exceeds ${maxSizeMB}MB limit`); } else if (error.code === "too-many-files") { @@ -78,29 +82,28 @@ export function useDragAndDrop({ }); }); }, - [maxFiles, maxSizeMB] + [maxFiles, maxSizeMB], ); // Configure react-dropzone - const { - getRootProps, - getInputProps, - isDragActive, - isDragReject, - } = useDropzone({ + const { getRootProps, getInputProps, isDragActive, isDragReject } = useDropzone({ onDrop: handleAcceptedFiles, onDropRejected: handleRejectedFiles, maxFiles, maxSize: maxSizeBytes, - accept: allowedTypes.length > 0 - ? allowedTypes.reduce((acc, ext) => { - // Convert extensions to MIME types for react-dropzone - acc[ext] = []; - return acc; - }, {} as Record) - : undefined, + accept: + allowedTypes.length > 0 + ? allowedTypes.reduce( + (acc, ext) => { + // Convert extensions to MIME types for react-dropzone + acc[ext] = []; + return acc; + }, + {} as Record, + ) + : undefined, disabled, - noClick: true, // Prevent click to upload (we have a separate upload button) + noClick: true, // Prevent click to upload (we have a separate upload button) noKeyboard: true, }); @@ -111,4 +114,3 @@ export function useDragAndDrop({ isDragReject, }; } - diff --git a/hooks/useDurationTracking.ts b/hooks/useDurationTracking.ts index 119a40276..712bbf6d3 100644 --- a/hooks/useDurationTracking.ts +++ b/hooks/useDurationTracking.ts @@ -1,11 +1,11 @@ /** * Duration Tracking Hook - * + * * Tracks how long a streaming/thinking process takes. * Single responsibility: Time duration of streaming operations. */ -import { useEffect, useState } from 'react'; +import { useEffect, useState } from "react"; const MS_IN_S = 1000; @@ -16,6 +16,7 @@ interface UseDurationTrackingReturn { /** * Hook to track duration of streaming operations + * * @param isStreaming - Whether the operation is currently streaming * @returns Object with duration in seconds and start time */ diff --git a/hooks/useFileContent.ts b/hooks/useFileContent.ts index 34c9eccf6..b0833f233 100644 --- a/hooks/useFileContent.ts +++ b/hooks/useFileContent.ts @@ -11,12 +11,17 @@ type UseFileContentResult = { /** * Hook to fetch and manage text file content using TanStack Query + * * @param fileName - Name of the file to fetch * @param storageKey - Storage key of the file * @param accountId - Account ID of the user requesting access */ -export function useFileContent(fileName: string, storageKey: string, accountId: string): UseFileContentResult { - const isTextFile = TEXT_EXTENSIONS.some((ext) => fileName.toLowerCase().endsWith(ext)); +export function useFileContent( + fileName: string, + storageKey: string, + accountId: string, +): UseFileContentResult { + const isTextFile = TEXT_EXTENSIONS.some(ext => fileName.toLowerCase().endsWith(ext)); const { data, isLoading, error } = useQuery({ queryKey: ["file-content", storageKey, accountId], @@ -32,4 +37,3 @@ export function useFileContent(fileName: string, storageKey: string, accountId: isTextFile, }; } - diff --git a/hooks/useFileEdit.ts b/hooks/useFileEdit.ts index 9db44de6e..b11b31912 100644 --- a/hooks/useFileEdit.ts +++ b/hooks/useFileEdit.ts @@ -15,6 +15,14 @@ type UseFileEditParams = { /** * Hook to manage file editing state and operations + * + * @param root0 + * @param root0.content + * @param root0.storageKey + * @param root0.mimeType + * @param root0.ownerAccountId + * @param root0.artistAccountId + * @param root0.isOpen */ export function useFileEdit({ content, @@ -26,7 +34,7 @@ export function useFileEdit({ }: UseFileEditParams) { const [isEditing, setIsEditing] = useState(false); const [editedContent, setEditedContent] = useState(""); - + const { mutate: updateFile, isPending: isSaving } = useUpdateFile(); // Check if content has unsaved changes @@ -58,7 +66,7 @@ export function useFileEdit({ const contentSize = getContentSizeBytes(editedContent); if (contentSize > MAX_TEXT_FILE_SIZE_BYTES) { toast.error( - `File size exceeds 10MB limit. Current size: ${(contentSize / 1024 / 1024).toFixed(2)}MB` + `File size exceeds 10MB limit. Current size: ${(contentSize / 1024 / 1024).toFixed(2)}MB`, ); return; } @@ -76,26 +84,29 @@ export function useFileEdit({ setIsEditing(false); setEditedContent(""); }, - } + }, ); }, [storageKey, editedContent, mimeType, ownerAccountId, artistAccountId, updateFile]); // Toggle edit mode with confirmation if there are unsaved changes - const handleEditToggle = useCallback((editing: boolean) => { - // If canceling with unsaved changes, confirm first - if (!editing && hasUnsavedChanges) { - if (!window.confirm("You have unsaved changes. Are you sure you want to discard them?")) { - return; + const handleEditToggle = useCallback( + (editing: boolean) => { + // If canceling with unsaved changes, confirm first + if (!editing && hasUnsavedChanges) { + if (!window.confirm("You have unsaved changes. Are you sure you want to discard them?")) { + return; + } } - } - if (editing && content) { - setEditedContent(content); - } else { - setEditedContent(""); - } - setIsEditing(editing); - }, [content, hasUnsavedChanges]); + if (editing && content) { + setEditedContent(content); + } else { + setEditedContent(""); + } + setIsEditing(editing); + }, + [content, hasUnsavedChanges], + ); return { isEditing, @@ -107,4 +118,3 @@ export function useFileEdit({ handleEditToggle, }; } - diff --git a/hooks/useFileMentionSuggestions.ts b/hooks/useFileMentionSuggestions.ts index 8d3a385e0..fe4720e0a 100644 --- a/hooks/useFileMentionSuggestions.ts +++ b/hooks/useFileMentionSuggestions.ts @@ -11,6 +11,10 @@ export interface GroupedSuggestion extends SuggestionDataItem { storage_key: string; } +/** + * + * @param value + */ export default function useFileMentionSuggestions(value: string) { const { files: artistFiles = [] } = useArtistFilesForMentions(); @@ -22,34 +26,34 @@ export default function useFileMentionSuggestions(value: string) { (query: string): GroupedSuggestion[] => { const q = (query || "").toLowerCase(); const items: GroupedSuggestion[] = artistFiles - .filter((f) => !f.is_directory) - .map((f) => { + .filter(f => !f.is_directory) + .map(f => { const rel = f.relative_path || f.file_name; const lastSlash = rel.lastIndexOf("/"); const group = lastSlash > 0 ? rel.slice(0, lastSlash) : "Home"; const name = lastSlash > -1 ? rel.slice(lastSlash + 1) : rel; - return { - id: f.id, - display: name, + return { + id: f.id, + display: name, group, mime_type: f.mime_type, - storage_key: f.storage_key + storage_key: f.storage_key, } as GroupedSuggestion; }) - .filter((it) => !mentionedIds.has(String(it.id))) + .filter(it => !mentionedIds.has(String(it.id))) .filter( - (it) => + it => (it.display || String(it.id)).toLowerCase().includes(q) || - it.group.toLowerCase().includes(q) + it.group.toLowerCase().includes(q), ) .sort((a, b) => a.group === b.group ? (a.display ?? "").localeCompare(b.display ?? "") - : a.group.localeCompare(b.group) + : a.group.localeCompare(b.group), ); return items.slice(0, 20); }, - [artistFiles, mentionedIds] + [artistFiles, mentionedIds], ); const provideSuggestions = useCallback( @@ -58,10 +62,8 @@ export default function useFileMentionSuggestions(value: string) { setLastResults(grouped); callback(grouped); }, - [buildGroupedResults] + [buildGroupedResults], ); return { provideSuggestions, lastResults }; } - - diff --git a/hooks/useFilesManager.ts b/hooks/useFilesManager.ts index a4f3b1691..facb67c44 100644 --- a/hooks/useFilesManager.ts +++ b/hooks/useFilesManager.ts @@ -13,6 +13,11 @@ export interface ListedFileRow { is_directory?: boolean; } +/** + * + * @param activePath + * @param recursive + */ export default function useFilesManager(activePath?: string, recursive: boolean = false) { const { userData } = useUserProvider(); const { selectedArtist } = useArtistProvider(); @@ -38,7 +43,7 @@ export default function useFilesManager(activePath?: string, recursive: boolean if (relativePath) relativePath += "/"; } } - + const p = relativePath ? `&path=${encodeURIComponent(relativePath)}` : ""; const r = recursive ? "&recursive=true" : ""; const url = `/api/files/list?ownerAccountId=${ownerAccountId}&artistAccountId=${artistAccountId}${p}${r}`; @@ -54,21 +59,25 @@ export default function useFilesManager(activePath?: string, recursive: boolean const items = data?.files || []; if (items.length === 0) return; const basePath = activePath - ? activePath.endsWith("/") ? activePath : activePath + "/" + ? activePath.endsWith("/") + ? activePath + : activePath + "/" : `files/${ownerAccountId}/${artistAccountId}/`; - items.filter((f) => f.is_directory).forEach((dir) => { - const childPath = `${basePath}${dir.file_name}/`; - qc.prefetchQuery({ - queryKey: ["files", ownerAccountId, artistAccountId, childPath], - queryFn: async () => { - const url = `/api/files/list?ownerAccountId=${ownerAccountId}&artistAccountId=${artistAccountId}&path=${encodeURIComponent(childPath)}`; - const res = await fetch(url, { cache: "no-store" }); - if (!res.ok) throw new Error("Failed to load files"); - return res.json(); - }, - staleTime: 30000, + items + .filter(f => f.is_directory) + .forEach(dir => { + const childPath = `${basePath}${dir.file_name}/`; + qc.prefetchQuery({ + queryKey: ["files", ownerAccountId, artistAccountId, childPath], + queryFn: async () => { + const url = `/api/files/list?ownerAccountId=${ownerAccountId}&artistAccountId=${artistAccountId}&path=${encodeURIComponent(childPath)}`; + const res = await fetch(url, { cache: "no-store" }); + if (!res.ok) throw new Error("Failed to load files"); + return res.json(); + }, + staleTime: 30000, + }); }); - }); }, [data?.files, activePath, ownerAccountId, artistAccountId, qc]); const createFolderMutation = useMutation({ @@ -76,7 +85,12 @@ export default function useFilesManager(activePath?: string, recursive: boolean const res = await fetch("/api/files/folder", { method: "POST", headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ ownerAccountId, artistAccountId, name, parentPath: activePath || `files/${ownerAccountId}/${artistAccountId}/` }), + body: JSON.stringify({ + ownerAccountId, + artistAccountId, + name, + parentPath: activePath || `files/${ownerAccountId}/${artistAccountId}/`, + }), }); const json = await res.json(); if (!res.ok) throw new Error(json.error || "Failed to create folder"); @@ -87,6 +101,10 @@ export default function useFilesManager(activePath?: string, recursive: boolean }, }); + /** + * + * @param selectedFile + */ async function handleUpload(selectedFile?: File) { const targetFile = selectedFile || file; if (!targetFile) return; @@ -98,7 +116,9 @@ export default function useFilesManager(activePath?: string, recursive: boolean const safeName = targetFile.name.replace(/[^a-zA-Z0-9._-]/g, "_"); const basePath = activePath - ? (activePath.endsWith("/") ? activePath : activePath + "/") + ? activePath.endsWith("/") + ? activePath + : activePath + "/" : `files/${ownerAccountId}/${artistAccountId}/`; const key = `${basePath}${safeName}`; @@ -173,5 +193,3 @@ export default function useFilesManager(activePath?: string, recursive: boolean }, }; } - - diff --git a/hooks/useFilesPath.ts b/hooks/useFilesPath.ts index 2da79aaeb..eb8b6f71a 100644 --- a/hooks/useFilesPath.ts +++ b/hooks/useFilesPath.ts @@ -3,6 +3,10 @@ import { useEffect, useMemo } from "react"; import { useSearchParams, useRouter } from "next/navigation"; +/** + * + * @param base + */ export default function useFilesPath(base: string) { const params = useSearchParams(); const router = useRouter(); @@ -41,5 +45,3 @@ export default function useFilesPath(base: string) { return { path: normalized, goTo, join, parent }; } - - diff --git a/hooks/useImageDownloader.ts b/hooks/useImageDownloader.ts index 8f50618cf..97f5b2797 100644 --- a/hooks/useImageDownloader.ts +++ b/hooks/useImageDownloader.ts @@ -5,6 +5,12 @@ interface UseImageDownloaderOptions { enabled?: boolean; } +/** + * + * @param root0 + * @param root0.imageUrl + * @param root0.enabled + */ export function useImageDownloader({ imageUrl, enabled = true }: UseImageDownloaderOptions) { const [imageBlob, setImageBlob] = useState(null); const [isDownloading, setIsDownloading] = useState(false); @@ -26,41 +32,45 @@ export function useImageDownloader({ imageUrl, enabled = true }: UseImageDownloa setIsPrefetching(false); } }; - + prefetchImage(); }, [imageUrl, enabled]); const handleDownload = async () => { if (!imageUrl) return; - + setIsDownloading(true); - + try { // Use prefetched blob if available, otherwise fetch it - const blob = imageBlob || await (async () => { - const response = await fetch(imageUrl as string); - return response.blob(); - })(); - + const blob = + imageBlob || + (await (async () => { + const response = await fetch(imageUrl as string); + return response.blob(); + })()); + const url = window.URL.createObjectURL(blob); const link = document.createElement("a"); link.href = url; - + // Format: "Recoup Image May 15, 2025, 09_59_47 PM" const now = new Date(); - const formattedDate = now.toLocaleDateString('en-US', { - year: 'numeric', - month: 'short', - day: 'numeric' + const formattedDate = now.toLocaleDateString("en-US", { + year: "numeric", + month: "short", + day: "numeric", }); - const formattedTime = now.toLocaleTimeString('en-US', { - hour: '2-digit', - minute: '2-digit', - second: '2-digit', - hour12: true - }).replace(/:/g, '_'); + const formattedTime = now + .toLocaleTimeString("en-US", { + hour: "2-digit", + minute: "2-digit", + second: "2-digit", + hour12: true, + }) + .replace(/:/g, "_"); link.download = `Recoup Image ${formattedDate}, ${formattedTime}`; - + document.body.appendChild(link); link.click(); document.body.removeChild(link); @@ -77,6 +87,6 @@ export function useImageDownloader({ imageUrl, enabled = true }: UseImageDownloa isDownloading, isPrefetching, isReady: !!imageBlob, - handleDownload + handleDownload, }; -} \ No newline at end of file +} diff --git a/hooks/useKeyboardSave.ts b/hooks/useKeyboardSave.ts index d8ac0e1f9..f0f84bffe 100644 --- a/hooks/useKeyboardSave.ts +++ b/hooks/useKeyboardSave.ts @@ -10,6 +10,13 @@ type UseKeyboardSaveParams = { /** * Hook to handle Cmd+S / Ctrl+S keyboard shortcut for saving + * + * @param root0 + * @param root0.isOpen + * @param root0.isEditing + * @param root0.hasUnsavedChanges + * @param root0.isSaving + * @param root0.onSave */ export function useKeyboardSave({ isOpen, @@ -34,4 +41,3 @@ export function useKeyboardSave({ } }, [isOpen, isEditing, hasUnsavedChanges, isSaving, onSave]); } - diff --git a/hooks/useKnowledgeEditor.ts b/hooks/useKnowledgeEditor.ts index 5ac0591e9..36d675efb 100644 --- a/hooks/useKnowledgeEditor.ts +++ b/hooks/useKnowledgeEditor.ts @@ -36,4 +36,4 @@ export const useKnowledgeEditor = ({ isText, textContent }: Args) => { } as const; }; -export default useKnowledgeEditor; \ No newline at end of file +export default useKnowledgeEditor; diff --git a/hooks/useMermaid.ts b/hooks/useMermaid.ts index 206df3b10..70bd98b9c 100644 --- a/hooks/useMermaid.ts +++ b/hooks/useMermaid.ts @@ -1,112 +1,107 @@ import { useEffect, useId, useRef, useState } from "react"; -import styles from '../components/Chat/markdown.module.css'; // Import the CSS module +import styles from "../components/Chat/markdown.module.css"; // Import the CSS module type MermaidApi = { - run: (options: { nodes: HTMLElement[] }) => void; - initialize: (config: Record) => void; + run: (options: { nodes: HTMLElement[] }) => void; + initialize: (config: Record) => void; }; -export const useMermaid = ({ - id, - chart -}: { - id?: string - chart: string -}) => { - const containerRef = useRef(null); - const generatedId = `mermaid-diagram-${useId()}`; - const uniqueId = id || generatedId; - const mermaidRef = useRef(null); - const [isLibraryLoaded, setIsLibraryLoaded] = useState(false); - const [hasError, setHasError] = useState(false); - - useEffect(() => { - let didCancel = false; - - const loadMermaid = async () => { - try { - // @ts-expect-error - TypeScript cannot analyze remote modules (Replaced ts-ignore) - const mermaidModule = await import(/* webpackIgnore: true */ 'https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs'); - const mermaid = mermaidModule.default || mermaidModule; - - if (!didCancel) { - mermaidRef.current = mermaid; - setIsLibraryLoaded(true); - setHasError(false); - } - } catch (error) { - if (!didCancel) { - console.error('Failed to dynamically import Mermaid:', error, uniqueId); - } - } - }; - - // Only attempt import if not already loaded - if (!mermaidRef.current) { - loadMermaid(); - } - - // Cleanup function to set flag if component unmounts during import - return () => { - didCancel = true; - }; - }, [uniqueId]); // Run only once on mount - - useEffect(() => { - // Render/re-render the chart when the library is loaded and the chart changes - if (isLibraryLoaded && mermaidRef.current && containerRef.current) { - const mermaid = mermaidRef.current; - const element = containerRef.current; - - // Reset error state before attempting to render - setHasError(false); - - // Ensure the container is clean before rendering - element.innerHTML = chart; // Set content for mermaid to process - element.removeAttribute('data-processed'); - - try { - mermaid.run({ nodes: [element] }); - // Ensure visibility is set correctly after successful render - element.style.visibility = 'visible'; - } catch (error) { - console.error('Mermaid rendering failed:', error, uniqueId); - // Set error state to true on failure - setHasError(true); - // Clear the container content on error to prevent showing raw code or old diagrams - element.innerHTML = ''; - // Hide the container to avoid empty space if needed, or let fallback handle layout - element.style.visibility = 'hidden'; - } - } else if (isLibraryLoaded && !containerRef.current) { - // Handle case where ref might be null unexpectedly after load - console.warn('Mermaid container ref not available after library load:', uniqueId); - setHasError(true); // Indicate an error state - } - }, [isLibraryLoaded, chart, uniqueId]); // Depend on load state and chart content - - useEffect(() => { - const parent = containerRef.current?.parentElement as HTMLElement | null; - const grandparent = parent?.parentElement as HTMLElement | null; - - if (parent) { - parent.classList.add(styles.mermaidParentOverride); +export const useMermaid = ({ id, chart }: { id?: string; chart: string }) => { + const containerRef = useRef(null); + const generatedId = `mermaid-diagram-${useId()}`; + const uniqueId = id || generatedId; + const mermaidRef = useRef(null); + const [isLibraryLoaded, setIsLibraryLoaded] = useState(false); + const [hasError, setHasError] = useState(false); + + useEffect(() => { + let didCancel = false; + + const loadMermaid = async () => { + try { + // @ts-expect-error - TypeScript cannot analyze remote modules (Replaced ts-ignore) + const mermaidModule = await import( + /* webpackIgnore: true */ "https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs" + ); + const mermaid = mermaidModule.default || mermaidModule; + + if (!didCancel) { + mermaidRef.current = mermaid; + setIsLibraryLoaded(true); + setHasError(false); } - if (grandparent) { - grandparent.classList.add(styles.mermaidGrandparentOverride); + } catch (error) { + if (!didCancel) { + console.error("Failed to dynamically import Mermaid:", error, uniqueId); } - - // Cleanup function to remove classes when component unmounts or dependencies change - return () => { - if (parent) { - parent.classList.remove(styles.mermaidParentOverride); - } - if (grandparent) { - grandparent.classList.remove(styles.mermaidGrandparentOverride); - } - }; - }, [isLibraryLoaded, hasError, chart]); // Keep dependencies as they are working - - return { isLibraryLoaded, hasError, uniqueId, containerRef }; + } + }; + + // Only attempt import if not already loaded + if (!mermaidRef.current) { + loadMermaid(); + } + + // Cleanup function to set flag if component unmounts during import + return () => { + didCancel = true; + }; + }, [uniqueId]); // Run only once on mount + + useEffect(() => { + // Render/re-render the chart when the library is loaded and the chart changes + if (isLibraryLoaded && mermaidRef.current && containerRef.current) { + const mermaid = mermaidRef.current; + const element = containerRef.current; + + // Reset error state before attempting to render + setHasError(false); + + // Ensure the container is clean before rendering + element.innerHTML = chart; // Set content for mermaid to process + element.removeAttribute("data-processed"); + + try { + mermaid.run({ nodes: [element] }); + // Ensure visibility is set correctly after successful render + element.style.visibility = "visible"; + } catch (error) { + console.error("Mermaid rendering failed:", error, uniqueId); + // Set error state to true on failure + setHasError(true); + // Clear the container content on error to prevent showing raw code or old diagrams + element.innerHTML = ""; + // Hide the container to avoid empty space if needed, or let fallback handle layout + element.style.visibility = "hidden"; + } + } else if (isLibraryLoaded && !containerRef.current) { + // Handle case where ref might be null unexpectedly after load + console.warn("Mermaid container ref not available after library load:", uniqueId); + setHasError(true); // Indicate an error state + } + }, [isLibraryLoaded, chart, uniqueId]); // Depend on load state and chart content + + useEffect(() => { + const parent = containerRef.current?.parentElement as HTMLElement | null; + const grandparent = parent?.parentElement as HTMLElement | null; + + if (parent) { + parent.classList.add(styles.mermaidParentOverride); + } + if (grandparent) { + grandparent.classList.add(styles.mermaidGrandparentOverride); + } + + // Cleanup function to remove classes when component unmounts or dependencies change + return () => { + if (parent) { + parent.classList.remove(styles.mermaidParentOverride); + } + if (grandparent) { + grandparent.classList.remove(styles.mermaidGrandparentOverride); + } + }; + }, [isLibraryLoaded, hasError, chart]); // Keep dependencies as they are working + + return { isLibraryLoaded, hasError, uniqueId, containerRef }; }; - diff --git a/hooks/useMessageLoader.ts b/hooks/useMessageLoader.ts index fd98abc01..e52b9f851 100644 --- a/hooks/useMessageLoader.ts +++ b/hooks/useMessageLoader.ts @@ -4,6 +4,7 @@ import getClientMessages from "@/lib/supabase/getClientMessages"; /** * Hook for loading existing messages from a room + * * @param roomId - The room ID to load messages from (undefined to skip loading) * @param userId - The current user ID (messages won't load if user is not authenticated) * @param setMessages - Callback function to set the loaded messages @@ -12,7 +13,7 @@ import getClientMessages from "@/lib/supabase/getClientMessages"; export function useMessageLoader( roomId: string | undefined, userId: string | undefined, - setMessages: (messages: UIMessage[]) => void + setMessages: (messages: UIMessage[]) => void, ) { const [isLoading, setIsLoading] = useState(!!roomId); const [error, setError] = useState(null); @@ -39,9 +40,7 @@ export function useMessageLoader( } } catch (err) { console.error("Error loading messages:", err); - setError( - err instanceof Error ? err : new Error("Failed to load messages") - ); + setError(err instanceof Error ? err : new Error("Failed to load messages")); } finally { setIsLoading(false); } diff --git a/hooks/useMiniApp.ts b/hooks/useMiniApp.ts index 7c3d2ea57..881d53eeb 100644 --- a/hooks/useMiniApp.ts +++ b/hooks/useMiniApp.ts @@ -11,7 +11,7 @@ export const useMiniApp = (): MiniApp => { const [isMiniApp, setIsMiniApp] = useState(false); useEffect(() => { - sdk.isInMiniApp().then((isMiniApp) => { + sdk.isInMiniApp().then(isMiniApp => { setIsMiniApp(isMiniApp); setIsLoading(false); }); diff --git a/hooks/useMobileDetection.ts b/hooks/useMobileDetection.ts index 86762d9d1..9448fbef0 100644 --- a/hooks/useMobileDetection.ts +++ b/hooks/useMobileDetection.ts @@ -1,4 +1,4 @@ -import { useState, useEffect } from 'react'; +import { useState, useEffect } from "react"; export const useMobileDetection = () => { const [isMobile, setIsMobile] = useState(false); @@ -7,16 +7,16 @@ export const useMobileDetection = () => { const checkIfMobile = () => { setIsMobile(window.innerWidth < 768); // Use md breakpoint (768px) }; - + // Check initially checkIfMobile(); - + // Add event listener for window resize - window.addEventListener('resize', checkIfMobile); - + window.addEventListener("resize", checkIfMobile); + // Clean up - return () => window.removeEventListener('resize', checkIfMobile); + return () => window.removeEventListener("resize", checkIfMobile); }, []); return isMobile; -}; \ No newline at end of file +}; diff --git a/hooks/useObserverTarget.ts b/hooks/useObserverTarget.ts index fbc7843f4..2c872e145 100644 --- a/hooks/useObserverTarget.ts +++ b/hooks/useObserverTarget.ts @@ -6,21 +6,17 @@ interface UseObserverTargetOptions { threshold?: number; } -const useObserverTarget = ({ - onIntersect, - enabled, - threshold = 0.1, -}: UseObserverTargetOptions) => { +const useObserverTarget = ({ onIntersect, enabled, threshold = 0.1 }: UseObserverTargetOptions) => { const observerTarget = useRef(null); useEffect(() => { const observer = new IntersectionObserver( - (entries) => { + entries => { if (entries[0].isIntersecting && enabled) { onIntersect(); } }, - { threshold } + { threshold }, ); const currentTarget = observerTarget.current; diff --git a/hooks/useOrgSettings.ts b/hooks/useOrgSettings.ts index d40846edd..10c864708 100644 --- a/hooks/useOrgSettings.ts +++ b/hooks/useOrgSettings.ts @@ -46,9 +46,7 @@ const useOrgSettings = (orgId: string | null) => { } // Find the organization from the list (same as button does) - const selectedOrg = organizations.find( - (org) => org.organization_id === orgId - ); + const selectedOrg = organizations.find(org => org.organization_id === orgId); if (!selectedOrg) { setIsLoading(false); @@ -63,9 +61,7 @@ const useOrgSettings = (orgId: string | null) => { const fetchOrgDetails = async () => { setIsLoading(true); try { - const response = await fetch( - `${NEW_API_BASE_URL}/api/accounts/${orgId}` - ); + const response = await fetch(`${NEW_API_BASE_URL}/api/accounts/${orgId}`); if (response.ok) { const data = await response.json(); // Response structure: { status: "success", account: {...} } @@ -84,21 +80,18 @@ const useOrgSettings = (orgId: string | null) => { fetchOrgDetails(); }, [orgId, organizations]); - const handleImageSelected = useCallback( - async (e: React.ChangeEvent) => { - const file = e.target.files?.[0]; - if (!file) return; + const handleImageSelected = useCallback(async (e: React.ChangeEvent) => { + const file = e.target.files?.[0]; + if (!file) return; - setImageUploading(true); - try { - const { uri } = await uploadFile(file); - setImage(uri); - } finally { - setImageUploading(false); - } - }, - [] - ); + setImageUploading(true); + try { + const { uri } = await uploadFile(file); + setImage(uri); + } finally { + setImageUploading(false); + } + }, []); const removeImage = useCallback(() => { setImage(""); @@ -107,33 +100,30 @@ const useOrgSettings = (orgId: string | null) => { } }, []); - const handleKnowledgesSelected = useCallback( - async (e: React.ChangeEvent) => { - const files = e.target.files; - if (!files) return; + const handleKnowledgesSelected = useCallback(async (e: React.ChangeEvent) => { + const files = e.target.files; + if (!files) return; - setKnowledgeUploading(true); - const newKnowledges: KnowledgeItem[] = []; - try { - for (const file of files) { - const name = file.name; - const type = getFileMimeType(file); - const { uri } = await uploadFile(file); - newKnowledges.push({ name, url: uri, type }); - } - setKnowledges((prev) => [...prev, ...newKnowledges]); - } finally { - setKnowledgeUploading(false); - if (knowledgeRef.current) { - knowledgeRef.current.value = ""; - } + setKnowledgeUploading(true); + const newKnowledges: KnowledgeItem[] = []; + try { + for (const file of files) { + const name = file.name; + const type = getFileMimeType(file); + const { uri } = await uploadFile(file); + newKnowledges.push({ name, url: uri, type }); } - }, - [] - ); + setKnowledges(prev => [...prev, ...newKnowledges]); + } finally { + setKnowledgeUploading(false); + if (knowledgeRef.current) { + knowledgeRef.current.value = ""; + } + } + }, []); const handleDeleteKnowledge = useCallback((index: number) => { - setKnowledges((prev) => prev.filter((_, i) => i !== index)); + setKnowledges(prev => prev.filter((_, i) => i !== index)); }, []); const save = useCallback(async () => { diff --git a/hooks/useOutsideClick.ts b/hooks/useOutsideClick.ts index 4fec9011b..3ce5a2cb9 100644 --- a/hooks/useOutsideClick.ts +++ b/hooks/useOutsideClick.ts @@ -1,5 +1,5 @@ -import { useEffect } from 'react'; -import type { RefObject } from 'react'; +import { useEffect } from "react"; +import type { RefObject } from "react"; type Props = { menuRef: RefObject; @@ -12,27 +12,27 @@ export const useOutsideClick = ({ menuRef, buttonRefs, isOpen, onClose }: Props) useEffect(() => { // Only add listener if the menu is open if (!isOpen) return; - + const handleClickOutside = (event: MouseEvent) => { // Don't close if clicking on the menu itself if (menuRef.current?.contains(event.target as Node)) { return; } - + // Don't close if clicking on the button that opens the menu - const clickedOnMenuButton = Object.values(buttonRefs.current || {}).some( - (buttonRef) => buttonRef?.contains(event.target as Node) + const clickedOnMenuButton = Object.values(buttonRefs.current || {}).some(buttonRef => + buttonRef?.contains(event.target as Node), ); - + if (!clickedOnMenuButton) { onClose(); } }; - - document.addEventListener('mousedown', handleClickOutside); - + + document.addEventListener("mousedown", handleClickOutside); + return () => { - document.removeEventListener('mousedown', handleClickOutside); + document.removeEventListener("mousedown", handleClickOutside); }; }, [menuRef, buttonRefs, isOpen, onClose]); -}; \ No newline at end of file +}; diff --git a/hooks/useProStatus.ts b/hooks/useProStatus.ts index e88b78fe4..c9a82b6c9 100644 --- a/hooks/useProStatus.ts +++ b/hooks/useProStatus.ts @@ -4,6 +4,8 @@ import { ProStatusResponse } from "@/app/api/subscription/status/route"; /** * Fetch pro status from the API + * + * @param accountId */ const fetchProStatus = async (accountId: string): Promise => { const response = await fetch(`/api/subscription/status?accountId=${accountId}`); @@ -28,4 +30,3 @@ const useProStatus = (): UseQueryResult => { }; export default useProStatus; - diff --git a/hooks/usePulseToggle.ts b/hooks/usePulseToggle.ts index cf8dd1a8b..fd1add336 100644 --- a/hooks/usePulseToggle.ts +++ b/hooks/usePulseToggle.ts @@ -6,6 +6,9 @@ import { toast } from "sonner"; const QUERY_KEY = ["pulse"]; +/** + * + */ export function usePulseToggle() { const accessToken = useAccessToken(); const queryClient = useQueryClient(); @@ -17,15 +20,12 @@ export function usePulseToggle() { }); const { mutate, isPending: isToggling } = useMutation({ - mutationFn: (active: boolean) => - updatePulse({ accessToken: accessToken!, active }), - onMutate: async (newActive) => { + mutationFn: (active: boolean) => updatePulse({ accessToken: accessToken!, active }), + onMutate: async newActive => { await queryClient.cancelQueries({ queryKey: QUERY_KEY }); const previousData = queryClient.getQueryData(QUERY_KEY); queryClient.setQueryData(QUERY_KEY, (old: typeof data) => - old - ? { ...old, pulses: [{ ...old.pulses[0], active: newActive }] } - : old + old ? { ...old, pulses: [{ ...old.pulses[0], active: newActive }] } : old, ); return { previousData }; }, @@ -33,10 +33,8 @@ export function usePulseToggle() { queryClient.setQueryData(QUERY_KEY, context?.previousData); toast.error("Failed to update pulse status"); }, - onSuccess: (data) => { - toast.success( - data.pulses[0].active ? "Pulse activated" : "Pulse deactivated" - ); + onSuccess: data => { + toast.success(data.pulses[0].active ? "Pulse activated" : "Pulse deactivated"); }, onSettled: () => { queryClient.invalidateQueries({ queryKey: QUERY_KEY }); diff --git a/hooks/usePureFileAttachments.ts b/hooks/usePureFileAttachments.ts index 0515cfcdf..0774bbd1e 100644 --- a/hooks/usePureFileAttachments.ts +++ b/hooks/usePureFileAttachments.ts @@ -5,6 +5,9 @@ import { CHAT_INPUT_SUPPORTED_FILE } from "@/lib/chat/config"; import { isAllowedByExtension } from "@/lib/files/isAllowedByExtension"; import { getFileExtension } from "@/lib/files/getFileExtension"; +/** + * + */ export function usePureFileAttachments() { const { setAttachments, addTextAttachment } = useVercelChatContext(); const fileInputRef = useRef(null); @@ -83,8 +86,8 @@ export function usePureFileAttachments() { mediaType: data.fileType, url: data.url, } as FileUIPart) - : attachment - ) + : attachment, + ), ); // Revoke the temporary object URL to avoid memory leaks @@ -92,9 +95,7 @@ export function usePureFileAttachments() { } catch (error) { console.error("Error uploading file:", error); // Remove the failed attachment - setAttachments((prev: FileUIPart[]) => - prev.filter((a: FileUIPart) => a.url !== tempUrl) - ); + setAttachments((prev: FileUIPart[]) => prev.filter((a: FileUIPart) => a.url !== tempUrl)); // Revoke the temporary object URL URL.revokeObjectURL(tempUrl); } diff --git a/hooks/useRenameModal.ts b/hooks/useRenameModal.ts index cf8b9a033..00f19dae6 100644 --- a/hooks/useRenameModal.ts +++ b/hooks/useRenameModal.ts @@ -8,10 +8,8 @@ import { useConversationsProvider } from "@/providers/ConversationsProvider"; type ChatItem = Conversation | ArtistAgent; const isChatRoom = (item: ChatItem): item is Conversation => "id" in item; -const getChatName = (item: ChatItem): string => - isChatRoom(item) ? item.topic : item.type; -const getChatId = (item: ChatItem): string => - isChatRoom(item) ? item.id : item.agentId; +const getChatName = (item: ChatItem): string => (isChatRoom(item) ? item.topic : item.type); +const getChatId = (item: ChatItem): string => (isChatRoom(item) ? item.id : item.agentId); const validateName = (value: string): string => { const trimmed = value.trim(); @@ -30,11 +28,14 @@ type UseRenameModalParams = { onClose: () => void; }; -export function useRenameModal({ - isOpen, - chatRoom, - onClose, -}: UseRenameModalParams) { +/** + * + * @param root0 + * @param root0.isOpen + * @param root0.chatRoom + * @param root0.onClose + */ +export function useRenameModal({ isOpen, chatRoom, onClose }: UseRenameModalParams) { const accessToken = useAccessToken(); const { refetchConversations } = useConversationsProvider(); @@ -70,7 +71,7 @@ export function useRenameModal({ setName(value); if (touched) setError(validateName(value)); }, - [touched] + [touched], ); const handleBlur = useCallback(() => { @@ -108,15 +109,11 @@ export function useRenameModal({ await refetchConversations(); onClose(); } catch (err) { - setError( - err instanceof Error - ? err.message - : "Failed to rename chat. Please try again." - ); + setError(err instanceof Error ? err.message : "Failed to rename chat. Please try again."); setIsSubmitting(false); } }, - [name, accessToken, chatRoom, refetchConversations, onClose] + [name, accessToken, chatRoom, refetchConversations, onClose], ); const handleModalClose = useCallback(() => { diff --git a/hooks/useReportData.ts b/hooks/useReportData.ts index 80125d384..a383cc5eb 100644 --- a/hooks/useReportData.ts +++ b/hooks/useReportData.ts @@ -3,6 +3,8 @@ import { ReportData } from "@/components/Chat/ChatReport/types"; /** * Checks if a report is complete by verifying both report and next_steps are available + * + * @param data */ const isReportComplete = (data: ReportData | undefined): boolean => { return Boolean(data?.report && data?.next_steps); @@ -21,7 +23,7 @@ export const useReportData = (reportId: string) => { return useQuery({ queryKey: ["report", reportId], queryFn: () => fetchReport(reportId), - refetchInterval: (query) => { + refetchInterval: query => { const data = query.state.data; return isReportComplete(data) ? false : 3000; }, diff --git a/hooks/useResearchTimer.ts b/hooks/useResearchTimer.ts index def498410..8e7caa21b 100644 --- a/hooks/useResearchTimer.ts +++ b/hooks/useResearchTimer.ts @@ -14,6 +14,10 @@ interface UseResearchTimerResult { activityMessages: string[]; } +/** + * + * @param isActive + */ export function useResearchTimer(isActive: boolean): UseResearchTimerResult { const [elapsedSeconds, setElapsedSeconds] = useState(0); const [messageIndex, setMessageIndex] = useState(0); @@ -28,7 +32,7 @@ export function useResearchTimer(isActive: boolean): UseResearchTimerResult { const timer = setInterval(() => { setElapsedSeconds(prev => prev + 1); }, 1000); - + return () => clearInterval(timer); }, [isActive]); @@ -42,7 +46,7 @@ export function useResearchTimer(isActive: boolean): UseResearchTimerResult { const messageTimer = setInterval(() => { setMessageIndex(prev => (prev + 1) % ACTIVITY_MESSAGES.length); }, 5000); - + return () => clearInterval(messageTimer); }, [isActive]); @@ -52,4 +56,3 @@ export function useResearchTimer(isActive: boolean): UseResearchTimerResult { activityMessages: ACTIVITY_MESSAGES, }; } - diff --git a/hooks/useSandboxFileContent.ts b/hooks/useSandboxFileContent.ts index 1f820cf93..2d57ec230 100644 --- a/hooks/useSandboxFileContent.ts +++ b/hooks/useSandboxFileContent.ts @@ -11,6 +11,9 @@ interface UseSandboxFileContentReturn { select: (path: string) => void; } +/** + * + */ export default function useSandboxFileContent(): UseSandboxFileContentReturn { const { getAccessToken } = usePrivy(); const [selectedPath, setSelectedPath] = useState(); diff --git a/hooks/useSandboxes.ts b/hooks/useSandboxes.ts index 0deff6d7f..53104570c 100644 --- a/hooks/useSandboxes.ts +++ b/hooks/useSandboxes.ts @@ -15,6 +15,9 @@ interface UseSandboxesReturn { refetch: () => void; } +/** + * + */ export default function useSandboxes(): UseSandboxesReturn { const { getAccessToken, authenticated } = usePrivy(); diff --git a/hooks/useSaveKnowledgeEdit.ts b/hooks/useSaveKnowledgeEdit.ts index a12bcfcff..1c28e93b4 100644 --- a/hooks/useSaveKnowledgeEdit.ts +++ b/hooks/useSaveKnowledgeEdit.ts @@ -10,11 +10,7 @@ type UseSaveKnowledgeEditArgs = { editedText: string; }; -export const useSaveKnowledgeEdit = ({ - name, - url, - editedText, -}: UseSaveKnowledgeEditArgs) => { +export const useSaveKnowledgeEdit = ({ name, url, editedText }: UseSaveKnowledgeEditArgs) => { const { knowledgeUploading, setKnowledgeUploading, @@ -38,8 +34,8 @@ export const useSaveKnowledgeEdit = ({ setKnowledgeUploading(true); const file = new File([editedText], name || "file.txt", { type: mime }); const { uri } = await uploadFile(file); - const next = bases.map((b) => ({ ...b })); - const idx = next.findIndex((b) => b.url === url && b.name === name); + const next = bases.map(b => ({ ...b })); + const idx = next.findIndex(b => b.url === url && b.name === name); if (idx >= 0) { next[idx] = { name, url: uri, type: mime } as { name: string; diff --git a/hooks/useScheduledActions.ts b/hooks/useScheduledActions.ts index 87ecad9dd..1bfbc0585 100644 --- a/hooks/useScheduledActions.ts +++ b/hooks/useScheduledActions.ts @@ -8,9 +8,7 @@ interface UseScheduledActionsParams { artistAccountId?: string; } -export const useScheduledActions = ({ - artistAccountId, -}: UseScheduledActionsParams) => { +export const useScheduledActions = ({ artistAccountId }: UseScheduledActionsParams) => { return useQuery({ queryKey: ["scheduled-actions", { artistAccountId }], queryFn: () => diff --git a/hooks/useSidebarPin.ts b/hooks/useSidebarPin.ts index 753a6c486..56a89d629 100644 --- a/hooks/useSidebarPin.ts +++ b/hooks/useSidebarPin.ts @@ -32,7 +32,7 @@ const useSidebarPin = () => { } }, [isPinned]); - const togglePin = () => setIsPinned((prev) => !prev); + const togglePin = () => setIsPinned(prev => !prev); return { isPinned, togglePin }; }; diff --git a/hooks/useSignedUrl.ts b/hooks/useSignedUrl.ts index dcd6a206a..d8d31b3cb 100644 --- a/hooks/useSignedUrl.ts +++ b/hooks/useSignedUrl.ts @@ -3,17 +3,21 @@ import { useEffect, useState } from "react"; import { createSignedUrlClient } from "@/lib/supabase/storage/client"; +/** + * + * @param storageKey + */ export default function useSignedUrl(storageKey?: string) { const [url, setUrl] = useState(undefined); useEffect(() => { if (!storageKey) { - setUrl(undefined); - return; + setUrl(undefined); + return; } let isMounted = true; - createSignedUrlClient(storageKey).then((signed) => { + createSignedUrlClient(storageKey).then(signed => { if (isMounted) setUrl(signed); }); @@ -24,4 +28,3 @@ export default function useSignedUrl(storageKey?: string) { return url; } - diff --git a/hooks/useSongsByIsrc.ts b/hooks/useSongsByIsrc.ts index a91ca6852..61bf03522 100644 --- a/hooks/useSongsByIsrc.ts +++ b/hooks/useSongsByIsrc.ts @@ -1,8 +1,5 @@ import { useQuery, UseQueryResult } from "@tanstack/react-query"; -import { - getSongsByIsrc, - SongsByIsrcResponse, -} from "@/lib/catalog/getSongsByIsrc"; +import { getSongsByIsrc, SongsByIsrcResponse } from "@/lib/catalog/getSongsByIsrc"; interface UseSongsByIsrcOptions { isrc: string; diff --git a/hooks/useTaskRunStatus.ts b/hooks/useTaskRunStatus.ts index 9c0d7acce..7f1cda0d0 100644 --- a/hooks/useTaskRunStatus.ts +++ b/hooks/useTaskRunStatus.ts @@ -1,9 +1,6 @@ import { useQuery } from "@tanstack/react-query"; import { useAccessToken } from "@/hooks/useAccessToken"; -import { - getTaskRunStatus, - type TaskRunStatus, -} from "@/lib/tasks/getTaskRunStatus"; +import { getTaskRunStatus, type TaskRunStatus } from "@/lib/tasks/getTaskRunStatus"; const TERMINAL_STATUSES = new Set([ "COMPLETED", @@ -19,6 +16,8 @@ const isTerminal = (data: TaskRunStatus | undefined): boolean => /** * Polls the task run status every 3s until the run reaches a terminal state. + * + * @param runId */ export function useTaskRunStatus(runId: string) { const accessToken = useAccessToken(); @@ -27,7 +26,7 @@ export function useTaskRunStatus(runId: string) { queryKey: ["taskRunStatus", runId], queryFn: () => getTaskRunStatus(runId, accessToken!), enabled: !!runId && !!accessToken, - refetchInterval: (query) => (isTerminal(query.state.data) ? false : 3000), + refetchInterval: query => (isTerminal(query.state.data) ? false : 3000), retry: 3, staleTime: 1000, }); diff --git a/hooks/useTaskRuns.ts b/hooks/useTaskRuns.ts index d2b8270b1..5d968aa7f 100644 --- a/hooks/useTaskRuns.ts +++ b/hooks/useTaskRuns.ts @@ -2,6 +2,9 @@ import { useQuery } from "@tanstack/react-query"; import { usePrivy } from "@privy-io/react-auth"; import { getTaskRuns, type TaskRunItem } from "@/lib/tasks/getTaskRuns"; +/** + * + */ export function useTaskRuns() { const { getAccessToken, authenticated } = usePrivy(); diff --git a/hooks/useTextAttachments.ts b/hooks/useTextAttachments.ts index 0a0244f79..1e40a252c 100644 --- a/hooks/useTextAttachments.ts +++ b/hooks/useTextAttachments.ts @@ -8,22 +8,14 @@ import { TextAttachment } from "@/types/textAttachment"; export function useTextAttachments() { const [textAttachments, setTextAttachments] = useState([]); - const addTextAttachment = useCallback( - async (file: File, type: TextAttachment["type"]) => { - const content = await file.text(); - const lineCount = content.split("\n").length; - setTextAttachments((prev) => [ - ...prev, - { filename: file.name, content, lineCount, type }, - ]); - }, - [] - ); + const addTextAttachment = useCallback(async (file: File, type: TextAttachment["type"]) => { + const content = await file.text(); + const lineCount = content.split("\n").length; + setTextAttachments(prev => [...prev, { filename: file.name, content, lineCount, type }]); + }, []); const removeTextAttachment = useCallback((indexToRemove: number) => { - setTextAttachments((prev) => - prev.filter((_, index) => index !== indexToRemove) - ); + setTextAttachments(prev => prev.filter((_, index) => index !== indexToRemove)); }, []); const clearTextAttachments = useCallback(() => { diff --git a/hooks/useTextFileContent.ts b/hooks/useTextFileContent.ts index 3717cbadb..bf05d27c7 100644 --- a/hooks/useTextFileContent.ts +++ b/hooks/useTextFileContent.ts @@ -18,14 +18,14 @@ export const useTextFileContent = (url: string | null | undefined): TextFileStat setError(null); setContent(""); fetch(url) - .then((res) => { + .then(res => { if (!res.ok) throw new Error("Failed to fetch file"); return res.text(); }) - .then((text) => { + .then(text => { if (!cancelled) setContent(text); }) - .catch((e) => { + .catch(e => { if (!cancelled) setError(e.message || "Error loading file"); }) .finally(() => { @@ -41,5 +41,3 @@ export const useTextFileContent = (url: string | null | undefined): TextFileStat }; export default useTextFileContent; - - diff --git a/hooks/useTypingAnimation.ts b/hooks/useTypingAnimation.ts index e66a6abf4..db55e9e89 100644 --- a/hooks/useTypingAnimation.ts +++ b/hooks/useTypingAnimation.ts @@ -1,58 +1,55 @@ -import { useState, useEffect } from 'react'; +import { useState, useEffect } from "react"; /** * A custom hook that creates a typing animation effect - * - * @param words Array of words to animate typing - * @param isActive Whether the animation should be active - * @param typingSpeed Speed for typing animation in ms - * @param deletingSpeed Speed for deleting animation in ms - * @param pauseTime Time to pause after typing a word in ms + * + * @param words - Array of words to animate typing + * @param isActive - Whether the animation should be active + * @param typingSpeed - Speed for typing animation in ms + * @param deletingSpeed - Speed for deleting animation in ms + * @param pauseTime - Time to pause after typing a word in ms */ export function useTypingAnimation( words: string[], isActive: boolean, typingSpeed = 200, deletingSpeed = 100, - pauseTime = 1000 + pauseTime = 1000, ) { const [currentWord, setCurrentWord] = useState(""); const [isDeleting, setIsDeleting] = useState(false); const [wordIndex, setWordIndex] = useState(0); - + useEffect(() => { if (!isActive) return; - + const currentFullWord = words[wordIndex]; - + const typeNextCharacter = () => { if (isDeleting) { // Deleting logic if (currentWord.length > 0) { - setCurrentWord((prev) => prev.slice(0, -1)); + setCurrentWord(prev => prev.slice(0, -1)); } else { setIsDeleting(false); - setWordIndex((prev) => (prev + 1) % words.length); + setWordIndex(prev => (prev + 1) % words.length); } } else { // Typing logic if (currentWord.length < currentFullWord.length) { - setCurrentWord((prev) => currentFullWord.slice(0, prev.length + 1)); + setCurrentWord(prev => currentFullWord.slice(0, prev.length + 1)); } else { setTimeout(() => setIsDeleting(true), pauseTime); } } }; - const timer = setTimeout( - typeNextCharacter, - isDeleting ? deletingSpeed : typingSpeed - ); - + const timer = setTimeout(typeNextCharacter, isDeleting ? deletingSpeed : typingSpeed); + return () => clearTimeout(timer); }, [currentWord, isDeleting, wordIndex, words, isActive, typingSpeed, deletingSpeed, pauseTime]); return { currentWord }; } -export default useTypingAnimation; \ No newline at end of file +export default useTypingAnimation; diff --git a/hooks/useUpdateFile.ts b/hooks/useUpdateFile.ts index 76ec38715..74fbfae3f 100644 --- a/hooks/useUpdateFile.ts +++ b/hooks/useUpdateFile.ts @@ -1,9 +1,6 @@ import { useMutation, useQueryClient } from "@tanstack/react-query"; import { toast } from "sonner"; -import { - updateFileContent, - type UpdateFileParams, -} from "@/lib/files/updateFileContent"; +import { updateFileContent, type UpdateFileParams } from "@/lib/files/updateFileContent"; /** * TanStack Query mutation hook for updating file content @@ -14,7 +11,7 @@ export function useUpdateFile() { return useMutation({ mutationFn: (params: UpdateFileParams) => updateFileContent(params), - onSuccess: (data) => { + onSuccess: data => { // Invalidate the file content cache to refetch updated content queryClient.invalidateQueries({ queryKey: ["file-content", data.storageKey], @@ -28,4 +25,3 @@ export function useUpdateFile() { }, }); } - diff --git a/hooks/useUpdateScheduledAction.ts b/hooks/useUpdateScheduledAction.ts index eb45871e5..8bbfdd863 100644 --- a/hooks/useUpdateScheduledAction.ts +++ b/hooks/useUpdateScheduledAction.ts @@ -35,9 +35,9 @@ export const useUpdateScheduledAction = () => { throw error; } finally { setIsLoading(false); - queryClient.invalidateQueries({ + queryClient.invalidateQueries({ queryKey: ["scheduled-actions"], - exact: false + exact: false, }); } }; diff --git a/hooks/useVercelChat.ts b/hooks/useVercelChat.ts index e50ad1297..233e8acc2 100644 --- a/hooks/useVercelChat.ts +++ b/hooks/useVercelChat.ts @@ -36,6 +36,12 @@ interface UseVercelChatProps { * A hook that provides all chat functionality for the Vercel Chat component * Combines useChat, and useMessageLoader * Accesses user and artist data directly from providers + * + * @param root0 + * @param root0.id + * @param root0.initialMessages + * @param root0.attachments + * @param root0.textAttachments */ export function useVercelChat({ id, @@ -55,10 +61,7 @@ export function useVercelChat({ const { addOptimisticConversation } = useConversationsProvider(); const { data: availableModels = [] } = useAvailableModels(); const [input, setInput] = useState(""); - const [model, setModel] = useLocalStorage( - "RECOUP_MODEL", - availableModels[0]?.id ?? "", - ); + const [model, setModel] = useLocalStorage("RECOUP_MODEL", availableModels[0]?.id ?? ""); const { refetchCredits } = usePaymentProvider(); const { transport, headers } = useChatTransport(); const accessToken = useAccessToken(); @@ -71,7 +74,7 @@ export function useVercelChat({ const ids = new Set(); const regex = /@\[[^\]]+\]\(([^)]+)\)/g; let match: RegExpExecArray | null; - // eslint-disable-next-line no-cond-assign + while ((match = regex.exec(input))) { if (match[1]) ids.add(match[1]); } @@ -79,9 +82,7 @@ export function useVercelChat({ }, [input]); // Resolve selected files to signed URLs for attachment - const [knowledgeFiles, setKnowledgeFiles] = useState( - [], - ); + const [knowledgeFiles, setKnowledgeFiles] = useState([]); const [isLoadingSignedUrls, setIsLoadingSignedUrls] = useState(false); // Cache signed URLs by storage_key to avoid redundant refetches const signedUrlCacheRef = useRef>(new Map()); @@ -89,27 +90,27 @@ export function useVercelChat({ let cancelled = false; const run = async () => { if (!selectedFileIds.length) { - if (!cancelled) setKnowledgeFiles((prev) => (prev.length ? [] : prev)); - if (!cancelled) setIsLoadingSignedUrls((prev) => (prev ? false : prev)); + if (!cancelled) setKnowledgeFiles(prev => (prev.length ? [] : prev)); + if (!cancelled) setIsLoadingSignedUrls(prev => (prev ? false : prev)); return; } const idSet = new Set(selectedFileIds); - const selected = allArtistFiles.filter((f) => idSet.has(f.id)); + const selected = allArtistFiles.filter(f => idSet.has(f.id)); if (selected.length === 0) { - if (!cancelled) setKnowledgeFiles((prev) => (prev.length ? [] : prev)); - if (!cancelled) setIsLoadingSignedUrls((prev) => (prev ? false : prev)); + if (!cancelled) setKnowledgeFiles(prev => (prev.length ? [] : prev)); + if (!cancelled) setIsLoadingSignedUrls(prev => (prev ? false : prev)); return; } try { const cache = signedUrlCacheRef.current; // Determine which of the selected files need fetching - const toFetch = selected.filter((f) => !cache.has(f.storage_key)); + const toFetch = selected.filter(f => !cache.has(f.storage_key)); if (toFetch.length === 0) { // All selected entries are cached and valid const entries = selected - .map((f) => cache.get(f.storage_key)) + .map(f => cache.get(f.storage_key)) .filter((e): e is KnowledgeBaseEntry => Boolean(e)); if (!cancelled) setKnowledgeFiles(entries); if (!cancelled) setIsLoadingSignedUrls(false); @@ -119,7 +120,7 @@ export function useVercelChat({ if (!cancelled) setIsLoadingSignedUrls(true); await Promise.all( - toFetch.map(async (f) => { + toFetch.map(async f => { const res = await fetch( `/api/files/get-signed-url?key=${encodeURIComponent(f.storage_key)}&accountId=${encodeURIComponent(userData?.account_id || "")}&expires=${SIGNED_URL_EXPIRES_SECONDS}`, ); @@ -136,15 +137,15 @@ export function useVercelChat({ // Compose final entries in the order of selection const entries = selected - .map((f) => signedUrlCacheRef.current.get(f.storage_key)) + .map(f => signedUrlCacheRef.current.get(f.storage_key)) .filter((e): e is KnowledgeBaseEntry => Boolean(e)); if (!cancelled) setKnowledgeFiles(entries); if (!cancelled) setIsLoadingSignedUrls(false); } catch (e) { console.error(e); - if (!cancelled) setKnowledgeFiles((prev) => (prev.length ? [] : prev)); - if (!cancelled) setIsLoadingSignedUrls((prev) => (prev ? false : prev)); + if (!cancelled) setKnowledgeFiles(prev => (prev.length ? [] : prev)); + if (!cancelled) setIsLoadingSignedUrls(prev => (prev ? false : prev)); } }; run(); @@ -178,22 +179,21 @@ export function useVercelChat({ [id, artistId, organizationId, model, headers], ); - const { messages, status, stop, sendMessage, setMessages, regenerate } = - useChat({ - id, - transport, - experimental_throttle: 100, - generateId: generateUUID, - onError: (e) => { - console.error("An error occurred, please try again!", e); - toast.error("An error occurred, please try again!"); - setHasChatApiError(true); - }, - onFinish: async () => { - // Update credits after AI response completes - await refetchCredits(); - }, - }); + const { messages, status, stop, sendMessage, setMessages, regenerate } = useChat({ + id, + transport, + experimental_throttle: 100, + generateId: generateUUID, + onError: e => { + console.error("An error occurred, please try again!", e); + toast.error("An error occurred, please try again!"); + setHasChatApiError(true); + }, + onFinish: async () => { + // Update credits after AI response completes + await refetchCredits(); + }, + }); const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); @@ -201,16 +201,11 @@ export function useVercelChat({ // Combine all attachments const combined: FileUIPart[] = []; if (attachments && attachments.length > 0) combined.push(...attachments); - if (selectedFileAttachments.length > 0) - combined.push(...selectedFileAttachments); + if (selectedFileAttachments.length > 0) combined.push(...selectedFileAttachments); // Separate audio files (can't be sent to AI as file parts) - const audioAttachments = combined.filter((f) => - f.mediaType?.startsWith("audio/"), - ); - const nonAudioAttachments = combined.filter( - (f) => !f.mediaType?.startsWith("audio/"), - ); + const audioAttachments = combined.filter(f => f.mediaType?.startsWith("audio/")); + const nonAudioAttachments = combined.filter(f => !f.mediaType?.startsWith("audio/")); // Build message text with text file content and audio URLs prepended let messageText = input; @@ -224,7 +219,7 @@ export function useVercelChat({ // Prepend audio URLs if (audioAttachments.length > 0) { const audioContext = audioAttachments - .map((a) => `[Audio: ${a.filename || "audio"}]\nURL: ${a.url}`) + .map(a => `[Audio: ${a.filename || "audio"}]\nURL: ${a.url}`) .join("\n\n"); messageText = audioContext + "\n\n" + messageText; } @@ -260,17 +255,14 @@ export function useVercelChat({ const isGeneratingResponse = ["streaming", "submitted"].includes(status); const deleteTrailingMessages = async () => { - const earliestFailedUserMessageId = - getEarliestFailedUserMessageId(messages); + const earliestFailedUserMessageId = getEarliestFailedUserMessageId(messages); if (earliestFailedUserMessageId) { const successfulDeletion = await clientDeleteTrailingMessages({ id: earliestFailedUserMessageId, }); if (successfulDeletion) { - setMessages((messages) => { - const index = messages.findIndex( - (m) => m.id === earliestFailedUserMessageId, - ); + setMessages(messages => { + const index = messages.findIndex(m => m.id === earliestFailedUserMessageId); if (index !== -1) { return [...messages.slice(0, index)]; } @@ -323,28 +315,15 @@ export function useVercelChat({ const hasInitialMessages = initialMessages && initialMessages.length > 0; const hasAccessToken = !!accessToken; // Wait for access token before sending initial message to avoid 401 errors - if ( - !hasInitialMessages || - !isReady || - hasMessages || - !isFullyLoggedIn || - !hasAccessToken - ) + if (!hasInitialMessages || !isReady || hasMessages || !isFullyLoggedIn || !hasAccessToken) return; handleSendQueryMessages(initialMessages[0]); - }, [ - initialMessages, - status, - userId, - handleSendQueryMessages, - messages.length, - accessToken, - ]); + }, [initialMessages, status, userId, handleSendQueryMessages, messages.length, accessToken]); // Sync state when models first load and prioritize preferred model useEffect(() => { if (!availableModels.length || model) return; - const preferred = availableModels.find((m) => m.id === DEFAULT_MODEL); + const preferred = availableModels.find(m => m.id === DEFAULT_MODEL); const defaultId = preferred ? preferred.id : availableModels[0].id; setModel(defaultId); }, [availableModels, model, setModel]); diff --git a/hooks/useVisibilityDelay.ts b/hooks/useVisibilityDelay.ts index a53d2a03d..9fed3633d 100644 --- a/hooks/useVisibilityDelay.ts +++ b/hooks/useVisibilityDelay.ts @@ -8,12 +8,14 @@ type UseVisibilityDelayOptions = { /** * Delay in milliseconds before showing content + * * @default 300 */ delay?: number; /** * Dependencies to watch for changes + * * @default [] */ deps?: ReadonlyArray; @@ -22,6 +24,10 @@ type UseVisibilityDelayOptions = { /** * Hook to handle delayed visibility transitions * + * @param root0 + * @param root0.shouldBeVisible + * @param root0.delay + * @param root0.deps * @example * const { isVisible } = useVisibilityDelay({ * shouldBeVisible: !!userData && !!artistData, @@ -34,7 +40,7 @@ export function useVisibilityDelay({ deps = [], }: UseVisibilityDelayOptions) { const [isVisible, setIsVisible] = useState(false); - + useEffect(() => { // Only proceed if conditions are met if (!shouldBeVisible) { diff --git a/hooks/useYouTubeLoginSuccess.ts b/hooks/useYouTubeLoginSuccess.ts index 9be2098f5..55eb28397 100644 --- a/hooks/useYouTubeLoginSuccess.ts +++ b/hooks/useYouTubeLoginSuccess.ts @@ -29,14 +29,12 @@ export function useYouTubeLoginSuccess() { // Check if the FINAL tool call in the latest message is YouTube (meaning it failed) const parts = latestMessage.parts || []; - const toolParts = parts.filter((part) => isToolUIPart(part)); + const toolParts = parts.filter(part => isToolUIPart(part)); const lastToolPart = toolParts[toolParts.length - 1]; // Type guard to check if it's a tool invocation with the right structure const isLastToolYouTube = - lastToolPart && - isToolUIPart(lastToolPart) && - getToolName(lastToolPart) === "youtube_login"; + lastToolPart && isToolUIPart(lastToolPart) && getToolName(lastToolPart) === "youtube_login"; if (!isLastToolYouTube) { return; @@ -45,7 +43,7 @@ export function useYouTubeLoginSuccess() { hasCheckedOAuth.current = true; if (selectedArtist?.account_id) { - fetchYouTubeChannel(selectedArtist.account_id).then((youtubeChannel) => { + fetchYouTubeChannel(selectedArtist.account_id).then(youtubeChannel => { if (youtubeChannel.success) { const successMessage = { id: generateUUID(), diff --git a/hooks/useYoutubeStatus.ts b/hooks/useYoutubeStatus.ts index 0299f29da..5928e4bae 100644 --- a/hooks/useYoutubeStatus.ts +++ b/hooks/useYoutubeStatus.ts @@ -2,11 +2,7 @@ import { YoutubeStatus } from "@/types/youtube"; import useYoutubeChannel from "./useYoutubeChannel"; const useYoutubeStatus = (artistAccountId?: string) => { - const { - data: channelResponse, - isLoading, - error, - } = useYoutubeChannel(artistAccountId || ""); + const { data: channelResponse, isLoading, error } = useYoutubeChannel(artistAccountId || ""); const data = artistAccountId ? { @@ -14,9 +10,7 @@ const useYoutubeStatus = (artistAccountId?: string) => { if (error) return "error"; if (isLoading) return "invalid"; if (channelResponse) { - return channelResponse.tokenStatus === "valid" - ? "valid" - : "invalid"; + return channelResponse.tokenStatus === "valid" ? "valid" : "invalid"; } return "invalid"; })(), diff --git a/lib/__tests__/getConversations.test.ts b/lib/__tests__/getConversations.test.ts index 09403d9ac..686abcf6c 100644 --- a/lib/__tests__/getConversations.test.ts +++ b/lib/__tests__/getConversations.test.ts @@ -39,7 +39,7 @@ describe("getConversations", () => { "Content-Type": "application/json", Authorization: "Bearer test-token", }), - }) + }), ); expect(result).toEqual(mockChats); }); @@ -111,7 +111,7 @@ describe("getConversations", () => { headers: expect.objectContaining({ Authorization: "Bearer my-privy-token", }), - }) + }), ); }); }); diff --git a/lib/admin.ts b/lib/admin.ts index 0b5207890..59d0d0efe 100644 --- a/lib/admin.ts +++ b/lib/admin.ts @@ -1,6 +1,4 @@ // Make sure this information remain private on server side import "server-only"; -export const ADMIN_EMAILS: string[] = [ - "sidney+1@recoupable.com", -]; +export const ADMIN_EMAILS: string[] = ["sidney+1@recoupable.com"]; diff --git a/lib/agent-templates/fetchAgentTemplates.ts b/lib/agent-templates/fetchAgentTemplates.ts index b43e9f0b2..813860f51 100644 --- a/lib/agent-templates/fetchAgentTemplates.ts +++ b/lib/agent-templates/fetchAgentTemplates.ts @@ -1,9 +1,7 @@ import type { AgentTemplateRow } from "@/types/AgentTemplates"; import type { AccountWithDetails } from "@/lib/supabase/accounts/getAccountWithDetails"; -const fetchAgentTemplates = async ( - userData: AccountWithDetails -): Promise => { +const fetchAgentTemplates = async (userData: AccountWithDetails): Promise => { const res = await fetch(`/api/agent-templates?userId=${userData?.id}`); if (!res.ok) throw new Error("Failed to fetch agent templates"); return (await res.json()) as AgentTemplateRow[]; diff --git a/lib/agent/validateEnvironment.ts b/lib/agent/validateEnvironment.ts index c6ce1960d..e43d6d2a2 100644 --- a/lib/agent/validateEnvironment.ts +++ b/lib/agent/validateEnvironment.ts @@ -2,12 +2,19 @@ const REQUIRED_ENV_VARS = ["ANTHROPIC_API_KEY"] as const; type RequiredEnvVar = (typeof REQUIRED_ENV_VARS)[number]; +/** + * + */ class EnvironmentError extends Error { + /** + * + * @param missingVars + */ constructor(missingVars: RequiredEnvVar[]) { super( `Missing required environment variables: ${missingVars.join( - ", " - )}. Please check your .env file.` + ", ", + )}. Please check your .env file.`, ); this.name = "EnvironmentError"; } @@ -15,12 +22,11 @@ class EnvironmentError extends Error { /** * Validates that all required environment variables are present + * * @throws {EnvironmentError} If any required variables are missing */ const validateEnvironment = (): void => { - const missingVars = REQUIRED_ENV_VARS.filter( - (varName) => !process.env[varName] - ); + const missingVars = REQUIRED_ENV_VARS.filter(varName => !process.env[varName]); if (missingVars.length > 0) { throw new EnvironmentError(missingVars); diff --git a/lib/ai/featuredModels.ts b/lib/ai/featuredModels.ts index dd94bc6fa..d53771861 100644 --- a/lib/ai/featuredModels.ts +++ b/lib/ai/featuredModels.ts @@ -81,6 +81,8 @@ export const FEATURED_MODELS: FeaturedModelConfig[] = [ /** * Check if a model ID is in the featured list + * + * @param modelId */ export const isFeaturedModel = (modelId: string): boolean => { return FEATURED_MODELS.some(model => model.id === modelId); @@ -88,7 +90,9 @@ export const isFeaturedModel = (modelId: string): boolean => { /** * Get featured model config by ID + * + * @param modelId */ export const getFeaturedModelConfig = (modelId: string): FeaturedModelConfig | undefined => { return FEATURED_MODELS.find(model => model.id === modelId); -}; \ No newline at end of file +}; diff --git a/lib/ai/getAvailableModels.ts b/lib/ai/getAvailableModels.ts index 44218b5a3..9a4903019 100644 --- a/lib/ai/getAvailableModels.ts +++ b/lib/ai/getAvailableModels.ts @@ -6,16 +6,13 @@ import { getFalModels } from "./getFalModels"; * Returns the list of available LLMs from both Vercel AI Gateway and Fal AI. * Combines models from both providers and filters out embed models. */ -export const getAvailableModels = async (): Promise< - GatewayLanguageModelEntry[] -> => { +export const getAvailableModels = async (): Promise => { try { - // Fetch models from Vercel AI Gateway let gatewayModels: GatewayLanguageModelEntry[] = []; try { const apiResponse = await gateway.getAvailableModels(); - gatewayModels = apiResponse.models.filter((m) => !isEmbedModel(m)); + gatewayModels = apiResponse.models.filter(m => !isEmbedModel(m)); // Successfully fetched gateway models } catch { // Error fetching gateway models - continuing with fallback diff --git a/lib/ai/getFalModels.ts b/lib/ai/getFalModels.ts index 8bea7e797..83420962a 100644 --- a/lib/ai/getFalModels.ts +++ b/lib/ai/getFalModels.ts @@ -10,8 +10,7 @@ export const getFalModels = (): GatewayLanguageModelEntry[] => { { id: "fal-ai/nano-banana/edit", name: "Nano Banana", - description: - "Google's state-of-the-art image generation and editing model", + description: "Google's state-of-the-art image generation and editing model", pricing: { input: "0.0000", // Free for testing output: "0.0000", @@ -39,8 +38,7 @@ export const getFalModels = (): GatewayLanguageModelEntry[] => { { id: "fal-ai/flux-pro/kontext", name: "FLUX Pro Kontext", - description: - "FLUX.1 Kontext [pro] handles both text and reference images", + description: "FLUX.1 Kontext [pro] handles both text and reference images", pricing: { input: "0.0002", output: "0.002", @@ -54,8 +52,7 @@ export const getFalModels = (): GatewayLanguageModelEntry[] => { { id: "fal-ai/ideogram/character", name: "Ideogram Character", - description: - "Generate consistent character appearances across multiple images", + description: "Generate consistent character appearances across multiple images", pricing: { input: "0.0001", output: "0.001", diff --git a/lib/ai/isFreeModel.ts b/lib/ai/isFreeModel.ts index 6793401ca..04aaed3c6 100644 --- a/lib/ai/isFreeModel.ts +++ b/lib/ai/isFreeModel.ts @@ -3,11 +3,11 @@ import { GatewayLanguageModelEntry } from "@ai-sdk/gateway"; export const isFreeModel = (m: GatewayLanguageModelEntry) => { const pricing = m.pricing; if (!pricing) return false; - + const input = parseFloat(pricing.input); const output = parseFloat(pricing.output); if (Number.isNaN(input) || Number.isNaN(output)) return false; - + // Model is considered free if both input and output costs are very low const inputPerMillion = input * 1_000_000; const outputPerMillion = output * 1_000_000; diff --git a/lib/ai/organizeModels.ts b/lib/ai/organizeModels.ts index eb248c896..e4e31969b 100644 --- a/lib/ai/organizeModels.ts +++ b/lib/ai/organizeModels.ts @@ -1,9 +1,6 @@ import { GatewayLanguageModelEntry } from "@ai-sdk/gateway"; import { isFreeModel } from "./isFreeModel"; -import { - FEATURED_MODELS, - isFeaturedModel -} from "./featuredModels"; +import { FEATURED_MODELS, isFeaturedModel } from "./featuredModels"; export interface OrganizedModels { featuredModels: GatewayLanguageModelEntry[]; @@ -12,6 +9,7 @@ export interface OrganizedModels { /** * Organizes available models into featured models and other models + * * @param availableModels - All available models from AI Gateway * @returns Organized model groups */ @@ -29,7 +27,7 @@ export const organizeModels = (availableModels: GatewayLanguageModelEntry[]): Or featuredModels.push({ ...actualModel, // Override display name if specified in config - name: featuredConfig.displayName || actualModel.name + name: featuredConfig.displayName || actualModel.name, }); } }); @@ -45,17 +43,17 @@ export const organizeModels = (availableModels: GatewayLanguageModelEntry[]): Or otherModels.sort((a, b) => { const aIsFree = isFreeModel(a); const bIsFree = isFreeModel(b); - + // Pro models (not free) come first if (aIsFree && !bIsFree) return 1; if (!aIsFree && bIsFree) return -1; - + // Within the same tier, sort alphabetically return a.name.localeCompare(b.name); }); return { featuredModels, - otherModels + otherModels, }; -}; \ No newline at end of file +}; diff --git a/lib/api/artist/getArtistSocials.ts b/lib/api/artist/getArtistSocials.ts index 380542cfa..4db7c942b 100644 --- a/lib/api/artist/getArtistSocials.ts +++ b/lib/api/artist/getArtistSocials.ts @@ -27,9 +27,11 @@ export interface SocialResponse { message?: string; } -export async function getArtistSocials( - artist_account_id: string -): Promise { +/** + * + * @param artist_account_id + */ +export async function getArtistSocials(artist_account_id: string): Promise { // Construct URL with query parameters const url = new URL(`${NEW_API_BASE_URL}/api/artist/socials`); url.searchParams.append("artist_account_id", artist_account_id); diff --git a/lib/apify/comments/getExistingPostComments.ts b/lib/apify/comments/getExistingPostComments.ts index 9f823239d..1ac3e6d0b 100644 --- a/lib/apify/comments/getExistingPostComments.ts +++ b/lib/apify/comments/getExistingPostComments.ts @@ -2,12 +2,11 @@ import { selectPostComments, PostComment } from "@/lib/supabase/post_comments/se /** * Gets existing post comments for the provided post URLs + * * @param postUrls - Array of Instagram post URLs to check for existing comments * @returns Promise with array of existing post comments and helper arrays */ -export default async function getExistingPostComments( - postUrls: string[] -): Promise<{ +export default async function getExistingPostComments(postUrls: string[]): Promise<{ existingComments: PostComment[]; urlsWithComments: string[]; urlsWithoutComments: string[]; @@ -26,16 +25,14 @@ export default async function getExistingPostComments( // Extract URLs that have existing comments const urlsWithComments = existingComments - .map((comment) => comment.post?.post_url) + .map(comment => comment.post?.post_url) .filter((url): url is string => Boolean(url)); // Remove duplicates const uniqueUrlsWithComments = Array.from(new Set(urlsWithComments)); // Find URLs that don't have comments - const urlsWithoutComments = postUrls.filter( - (url) => !uniqueUrlsWithComments.includes(url) - ); + const urlsWithoutComments = postUrls.filter(url => !uniqueUrlsWithComments.includes(url)); return { existingComments, @@ -50,4 +47,4 @@ export default async function getExistingPostComments( urlsWithoutComments: postUrls, // Assume no comments exist on error }; } -} \ No newline at end of file +} diff --git a/lib/apify/comments/getOrCreatePostsForComments.ts b/lib/apify/comments/getOrCreatePostsForComments.ts index 631de9185..4dd0bd9ac 100644 --- a/lib/apify/comments/getOrCreatePostsForComments.ts +++ b/lib/apify/comments/getOrCreatePostsForComments.ts @@ -4,39 +4,40 @@ import { TablesInsert, Tables } from "@/types/database.types"; /** * Gets or creates posts for the given post URLs and returns them as a map + * * @param postUrls - Array of post URLs from Instagram comments * @returns Map of post URL to post record */ export default async function getOrCreatePostsForComments( - postUrls: string[] + postUrls: string[], ): Promise>> { const uniquePostUrls = Array.from(new Set(postUrls)); - + // First, try to get existing posts const existingPosts = await getPosts(uniquePostUrls); const existingPostUrls = new Set(existingPosts.map(post => post.post_url)); - + // Identify which posts need to be created const missingPostUrls = uniquePostUrls.filter(url => !existingPostUrls.has(url)); - + if (missingPostUrls.length > 0) { // Create posts for missing URLs const postsToInsert: TablesInsert<"posts">[] = missingPostUrls.map(url => ({ post_url: url, updated_at: new Date().toISOString(), })); - + await insertPosts(postsToInsert); } - + // Get all posts (existing + newly created) const allPosts = await getPosts(uniquePostUrls); - + // Return as a map for easy lookup const postMap = new Map>(); allPosts.forEach(post => { postMap.set(post.post_url, post); }); - + return postMap; -} \ No newline at end of file +} diff --git a/lib/apify/comments/getOrCreateSocialsForComments.ts b/lib/apify/comments/getOrCreateSocialsForComments.ts index a9e03ba73..6f8474abd 100644 --- a/lib/apify/comments/getOrCreateSocialsForComments.ts +++ b/lib/apify/comments/getOrCreateSocialsForComments.ts @@ -4,50 +4,53 @@ import { InstagramComment } from "@/lib/apify/comments/handleInstagramCommentsSc /** * Gets or creates socials for the given Instagram comment authors and returns them as a map + * * @param comments - Array of Instagram comments * @returns Map of username to social record */ export default async function getOrCreateSocialsForComments( - comments: InstagramComment[] + comments: InstagramComment[], ): Promise>> { // Create a unique set of comment authors const uniqueAuthors = Array.from( - new Map(comments.map(comment => [ - comment.ownerUsername, - { - username: comment.ownerUsername, - profilePicUrl: comment.ownerProfilePicUrl, - profileUrl: `instagram.com/${comment.ownerUsername}`, - } - ])).values() + new Map( + comments.map(comment => [ + comment.ownerUsername, + { + username: comment.ownerUsername, + profilePicUrl: comment.ownerProfilePicUrl, + profileUrl: `instagram.com/${comment.ownerUsername}`, + }, + ]), + ).values(), ); - + // Prepare all socials for batch upsert const socialsToUpsert: TablesInsert<"socials">[] = uniqueAuthors - .filter((author) => author.username && author.profileUrl) - .map((author) => ({ - username: author.username, - profile_url: author.profileUrl, - avatar: author.profilePicUrl, - bio: null, - region: null, - followerCount: null, - followingCount: null, - })); - + .filter(author => author.username && author.profileUrl) + .map(author => ({ + username: author.username, + profile_url: author.profileUrl, + avatar: author.profilePicUrl, + bio: null, + region: null, + followerCount: null, + followingCount: null, + })); + try { // Batch upsert all socials (creates new or returns existing based on profile_url) const upsertedSocials = await insertSocials(socialsToUpsert); - + // Create map for easy lookup by username const socialMap = new Map>(); upsertedSocials.forEach(social => { socialMap.set(social.username, social); }); - + return socialMap; } catch (error) { - console.error('Error batch upserting socials:', error); + console.error("Error batch upserting socials:", error); return new Map>(); } -} \ No newline at end of file +} diff --git a/lib/apify/comments/handleInstagramCommentsScraper.ts b/lib/apify/comments/handleInstagramCommentsScraper.ts index d2ad6f576..e849755c5 100644 --- a/lib/apify/comments/handleInstagramCommentsScraper.ts +++ b/lib/apify/comments/handleInstagramCommentsScraper.ts @@ -16,17 +16,18 @@ export interface InstagramComment { /** * Handles Instagram Comments Scraper results: fetches dataset, processes comments, and returns results. + * * @param parsed - The parsed and validated Apify webhook payload * @returns An object with comments and processing metadata */ export default async function handleInstagramCommentsScraper( - parsed: z.infer + parsed: z.infer, ) { const datasetId = parsed.resource.defaultDatasetId; let comments: InstagramComment[] = []; let processedPostUrls: string[] = []; let totalComments = 0; - + const fallbackResponse = { comments: [], processedPostUrls: [], @@ -36,23 +37,23 @@ export default async function handleInstagramCommentsScraper( try { if (datasetId) { const dataset = await getDataset(datasetId); - + if (dataset && Array.isArray(dataset)) { // Assign comments directly from the dataset comments = dataset as InstagramComment[]; // Extract unique post URLs processedPostUrls = Array.from( - new Set(dataset.map((item: InstagramComment) => item.postUrl).filter(Boolean)) + new Set(dataset.map((item: InstagramComment) => item.postUrl).filter(Boolean)), ); - + totalComments = dataset.length; - + console.log(`Processed ${totalComments} comments from ${processedPostUrls.length} posts`); - + // Log sample data for debugging if (comments.length > 0) { - console.log('Sample comment:', comments[0]); + console.log("Sample comment:", comments[0]); } // Save comments to post_comments table @@ -60,18 +61,20 @@ export default async function handleInstagramCommentsScraper( // Extract unique fan profiles (usernames) for profile scraping const fanHandles = Array.from( - new Set(comments.map((comment) => comment.ownerUsername).filter(Boolean)) + new Set(comments.map(comment => comment.ownerUsername).filter(Boolean)), ); // Trigger profile scraper for fan profiles if (fanHandles.length > 0) { console.log(`Triggering profile scraper for ${fanHandles.length} fan handles`); const profileScrapingResult = await runInstagramProfilesScraper(fanHandles); - + if (profileScrapingResult.error) { - console.error('Profile scraping failed:', profileScrapingResult.error); + console.error("Profile scraping failed:", profileScrapingResult.error); } else { - console.log(`Profile scraping initiated: runId=${profileScrapingResult.runId}, datasetId=${profileScrapingResult.datasetId}`); + console.log( + `Profile scraping initiated: runId=${profileScrapingResult.runId}, datasetId=${profileScrapingResult.datasetId}`, + ); } } } @@ -86,4 +89,4 @@ export default async function handleInstagramCommentsScraper( console.error("Failed to handle Instagram Comments Scraper webhook:", error); return fallbackResponse; } -} \ No newline at end of file +} diff --git a/lib/apify/comments/runInstagramCommentsScraper.ts b/lib/apify/comments/runInstagramCommentsScraper.ts index 86ed8e8c1..c265c14af 100644 --- a/lib/apify/comments/runInstagramCommentsScraper.ts +++ b/lib/apify/comments/runInstagramCommentsScraper.ts @@ -3,13 +3,14 @@ import { ApifyScraperResult } from "@/lib/apify/types"; /** * Runs the Instagram comments scraper for the provided post URLs + * * @param postUrls - Array of Instagram post URLs to fetch comments for * @param resultsLimit - Optional limit on the number of comments to fetch per post * @returns Promise with runId, datasetId, and error information */ export default async function runInstagramCommentsScraper( postUrls: string[], - resultsLimit: number = 100 + resultsLimit: number = 100, ): Promise { try { if (!postUrls || postUrls.length === 0) { @@ -22,7 +23,7 @@ export default async function runInstagramCommentsScraper( // Construct URL with postUrls as query parameters const url = new URL("https://api.recoupable.com/api/instagram/comments"); - postUrls.forEach((postUrl) => { + postUrls.forEach(postUrl => { url.searchParams.append("postUrls", postUrl); }); @@ -48,10 +49,7 @@ export default async function runInstagramCommentsScraper( return { runId: "", datasetId: "", - error: - error instanceof Error - ? error.message - : "Failed to scrape Instagram comments", + error: error instanceof Error ? error.message : "Failed to scrape Instagram comments", }; } -} \ No newline at end of file +} diff --git a/lib/apify/comments/saveApifyInstagramComments.ts b/lib/apify/comments/saveApifyInstagramComments.ts index 4a9fe7850..8a803aed5 100644 --- a/lib/apify/comments/saveApifyInstagramComments.ts +++ b/lib/apify/comments/saveApifyInstagramComments.ts @@ -6,11 +6,12 @@ import { InstagramComment } from "@/lib/apify/comments/handleInstagramCommentsSc /** * Saves Instagram comments to the post_comments table in the database + * * @param comments - Array of Instagram comments to save * @returns Promise that resolves when comments are saved */ export default async function saveApifyInstagramComments( - comments: InstagramComment[] + comments: InstagramComment[], ): Promise { if (comments.length === 0) { return; @@ -18,30 +19,30 @@ export default async function saveApifyInstagramComments( try { console.log(`Saving ${comments.length} comments to database...`); - + // Get or create posts for the comment URLs const postUrls = Array.from(new Set(comments.map(comment => comment.postUrl).filter(Boolean))); const postsMap = await getOrCreatePostsForComments(postUrls); - + // Get or create socials for the comment authors const socialsMap = await getOrCreateSocialsForComments(comments); - + // Map Instagram comments to post_comments table format const postCommentsToInsert: TablesInsert<"post_comments">[] = []; - + for (const comment of comments) { if (!comment.postUrl || !comment.ownerUsername) { continue; } - + const post = postsMap.get(comment.postUrl); const social = socialsMap.get(comment.ownerUsername); - + if (!post || !social) { console.warn(`Missing post or social for comment: ${comment.id}`); continue; } - + postCommentsToInsert.push({ post_id: post.id, social_id: social.id, @@ -49,14 +50,14 @@ export default async function saveApifyInstagramComments( commented_at: comment.timestamp, }); } - + if (postCommentsToInsert.length > 0) { await insertPostComments(postCommentsToInsert); console.log(`Successfully saved ${postCommentsToInsert.length} comments to database`); } else { - console.warn('No valid comments to save to database'); + console.warn("No valid comments to save to database"); } } catch (error) { - console.error('Error saving comments to database:', error); + console.error("Error saving comments to database:", error); } -} \ No newline at end of file +} diff --git a/lib/apify/handleApifyWebhook.ts b/lib/apify/handleApifyWebhook.ts index 0134d2b06..a4cf6aac0 100644 --- a/lib/apify/handleApifyWebhook.ts +++ b/lib/apify/handleApifyWebhook.ts @@ -5,12 +5,11 @@ import handleInstagramCommentsScraper from "@/lib/apify/comments/handleInstagram /** * Handles the Apify webhook payload: routes to appropriate handler based on actorId. + * * @param parsed - The parsed and validated Apify webhook payload * @returns An object with posts, socials, accountSocials, accountArtistIds, accountEmails, and sentEmails */ -export default async function handleApifyWebhook( - parsed: z.infer -) { +export default async function handleApifyWebhook(parsed: z.infer) { const fallbackResponse = { posts: [], social: null, diff --git a/lib/apify/posts/handleInstagramProfileFollowUpRuns.ts b/lib/apify/posts/handleInstagramProfileFollowUpRuns.ts index e2aa5b3fc..cf8c01385 100644 --- a/lib/apify/posts/handleInstagramProfileFollowUpRuns.ts +++ b/lib/apify/posts/handleInstagramProfileFollowUpRuns.ts @@ -4,39 +4,46 @@ import { ApifyInstagramPost, ApifyInstagramProfileResult } from "@/types/Apify"; /** * Handles Instagram profile follow-up runs: comment scraping for new posts + * * @param dataset - The complete dataset from the profile scraper * @param firstResult - The first result from the dataset containing latest posts and profile info * @returns Promise */ export default async function handleInstagramProfileFollowUpRuns( dataset: unknown[], - firstResult: ApifyInstagramProfileResult + firstResult: ApifyInstagramProfileResult, ): Promise { // Trigger comment scraping for the new posts // Only call runInstagramCommentsScraper if dataset.length === 1 // If more than 1 profile, these are fans and should only be saved if (dataset.length === 1 && firstResult.latestPosts && firstResult.latestPosts.length > 0) { const postUrls = (firstResult.latestPosts as ApifyInstagramPost[]) - .map((post) => post.url) + .map(post => post.url) .filter(Boolean); - + if (postUrls.length > 0) { console.log("Checking existing comments for posts:", postUrls); - + // Get existing comments for these post URLs const { urlsWithComments, urlsWithoutComments } = await getExistingPostComments(postUrls); - + // Handle posts with existing comments (use resultsLimit) if (urlsWithComments.length > 0) { - console.log("Triggering comment scraping for posts with existing comments (resultsLimit=1):", urlsWithComments); + console.log( + "Triggering comment scraping for posts with existing comments (resultsLimit=1):", + urlsWithComments, + ); await runInstagramCommentsScraper(urlsWithComments, 1); } - + // Handle posts without existing comments (no resultsLimit) if (urlsWithoutComments.length > 0) { - console.log("Triggering comment scraping for posts without existing comments:", urlsWithoutComments); + console.log( + "Triggering comment scraping for posts without existing comments:", + urlsWithoutComments, + ); await runInstagramCommentsScraper(urlsWithoutComments); } } } -} \ No newline at end of file +} diff --git a/lib/apify/posts/handleInstagramProfileScraperResults.ts b/lib/apify/posts/handleInstagramProfileScraperResults.ts index 401cf6c42..b8200466d 100644 --- a/lib/apify/posts/handleInstagramProfileScraperResults.ts +++ b/lib/apify/posts/handleInstagramProfileScraperResults.ts @@ -20,11 +20,12 @@ import { getFetchableUrl } from "@/lib/arweave/gateway"; /** * Handles Instagram profile scraper results: fetches dataset, saves posts, saves socials, and returns results. + * * @param parsed - The parsed and validated Apify webhook payload * @returns An object with posts, socials, accountSocials, accountArtistIds, accountEmails, and sentEmails */ export default async function handleInstagramProfileScraperResults( - parsed: z.infer + parsed: z.infer, ) { const datasetId = parsed.resource.defaultDatasetId; let posts: Tables<"posts">[] = []; @@ -41,11 +42,11 @@ export default async function handleInstagramProfileScraperResults( if (firstResult?.latestPosts) { // Save posts const { supabasePosts: sp } = await saveApifyInstagramPosts( - firstResult.latestPosts as ApifyInstagramPost[] + firstResult.latestPosts as ApifyInstagramPost[], ); posts = sp; const arweaveResult = await uploadLinkToArweave( - firstResult.profilePicUrlHD || firstResult.profilePicUrl + firstResult.profilePicUrlHD || firstResult.profilePicUrl, ); if (arweaveResult) { firstResult.profilePicUrl = getFetchableUrl(arweaveResult); @@ -66,7 +67,7 @@ export default async function handleInstagramProfileScraperResults( console.log("social", social); if (social) { if (posts.length) { - const socialPostRows = posts.map((post) => ({ + const socialPostRows = posts.map(post => ({ post_id: post.id, updated_at: post.updated_at, social_id: social!.id, @@ -77,11 +78,11 @@ export default async function handleInstagramProfileScraperResults( accountSocials = await getAccountSocials({ socialId: socialIds }); console.log("accountSocials", accountSocials); accountArtistIds = await getAccountArtistIds({ - artistIds: accountSocials.map((a) => a.account_id as string), + artistIds: accountSocials.map(a => a.account_id as string), }); // Get emails for all unique account_ids const uniqueAccountIds = Array.from( - new Set(accountArtistIds.map((a) => a.account_id).filter(Boolean)) + new Set(accountArtistIds.map(a => a.account_id).filter(Boolean)), ); const emails = await getAccountEmails(uniqueAccountIds as string[]); console.log("emails", emails); @@ -89,7 +90,7 @@ export default async function handleInstagramProfileScraperResults( // Send the Apify webhook email using the new utility sentEmails = await sendApifyWebhookEmail( firstResult, - emails.map((e) => e.email).filter(Boolean) as string[] + emails.map(e => e.email).filter(Boolean) as string[], ); // Trigger comment scraping for the new posts diff --git a/lib/apify/posts/runInstagramProfilesScraper.ts b/lib/apify/posts/runInstagramProfilesScraper.ts index c1d7097ee..cacffb30a 100644 --- a/lib/apify/posts/runInstagramProfilesScraper.ts +++ b/lib/apify/posts/runInstagramProfilesScraper.ts @@ -3,11 +3,12 @@ import { ApifyScraperResult } from "@/lib/apify/types"; /** * Runs the Instagram profiles scraper for the provided handles + * * @param handles - Array of Instagram handles to fetch profile data for * @returns Promise with runId, datasetId, and error information */ export default async function runInstagramProfilesScraper( - handles: string[] + handles: string[], ): Promise { try { if (!handles || handles.length === 0) { @@ -20,7 +21,7 @@ export default async function runInstagramProfilesScraper( // Construct URL with handles as query parameters const url = new URL("https://api.recoupable.com/api/instagram/profiles"); - handles.forEach((handle) => { + handles.forEach(handle => { url.searchParams.append("handles", handle); }); @@ -43,10 +44,7 @@ export default async function runInstagramProfilesScraper( return { runId: "", datasetId: "", - error: - error instanceof Error - ? error.message - : "Failed to scrape Instagram profiles", + error: error instanceof Error ? error.message : "Failed to scrape Instagram profiles", }; } -} \ No newline at end of file +} diff --git a/lib/apify/posts/saveApifyInstagramPosts.ts b/lib/apify/posts/saveApifyInstagramPosts.ts index 1246d909d..8ea4c2b11 100644 --- a/lib/apify/posts/saveApifyInstagramPosts.ts +++ b/lib/apify/posts/saveApifyInstagramPosts.ts @@ -5,17 +5,16 @@ import { TablesInsert, Tables } from "@/types/database.types"; /** * Saves an array of ApifyInstagramPost to Supabase and returns the save result and the posts from Supabase. + * * @param apifyPosts - Array of ApifyInstagramPost * @returns An object with saveResult and supabasePosts */ -export default async function saveApifyInstagramPosts( - apifyPosts: ApifyInstagramPost[] -) { - const posts: TablesInsert<"posts">[] = apifyPosts.map((post) => ({ +export default async function saveApifyInstagramPosts(apifyPosts: ApifyInstagramPost[]) { + const posts: TablesInsert<"posts">[] = apifyPosts.map(post => ({ post_url: post.url, updated_at: post.timestamp, })); - const postUrls = posts.map((post) => post.post_url); + const postUrls = posts.map(post => post.post_url); await insertPosts(posts); const supabasePosts: Tables<"posts">[] = await getPosts(postUrls); return { supabasePosts }; diff --git a/lib/apify/sendApifyWebhookEmail.ts b/lib/apify/sendApifyWebhookEmail.ts index 47e29475d..3275cca6e 100644 --- a/lib/apify/sendApifyWebhookEmail.ts +++ b/lib/apify/sendApifyWebhookEmail.ts @@ -4,14 +4,13 @@ import { RECOUP_FROM_EMAIL } from "../consts"; /** * Sends a Recoup Apify webhook email to a list of emails, summarizing the dataset and using a strong CTA. + * * @param dataset - Array of dataset objects (from Apify) + * @param d * @param emails - Array of email addresses to send to * @returns The result of sendEmail */ -export default async function sendApifyWebhookEmail( - d: Record, - emails: string[] -) { +export default async function sendApifyWebhookEmail(d: Record, emails: string[]) { if (!emails?.length) return null; const prompt = `You have a new Apify dataset update. Here is the data: @@ -24,7 +23,7 @@ Biography: ${d.biography} External URL: ${d.externalUrls} Followers: ${d.followersCount} Following: ${d.followsCount} -Latest Posts: ${((d.latestPosts as unknown[]) || []).map((p) => JSON.stringify(p)).join(", ")} +Latest Posts: ${((d.latestPosts as unknown[]) || []).map(p => JSON.stringify(p)).join(", ")} `; const { text } = await generateText({ diff --git a/lib/apify/types.ts b/lib/apify/types.ts index ec364b7ae..b47b9ae92 100644 --- a/lib/apify/types.ts +++ b/lib/apify/types.ts @@ -5,4 +5,4 @@ export interface ApifyScraperResult { runId: string; datasetId: string; error: string | null; -} \ No newline at end of file +} diff --git a/lib/arweave/arweave.ts b/lib/arweave/arweave.ts index 2dec77c86..56fd66a80 100644 --- a/lib/arweave/arweave.ts +++ b/lib/arweave/arweave.ts @@ -1,5 +1,9 @@ export type ArweaveURL = `ar://${string}`; +/** + * + * @param url + */ export function isArweaveURL(url: string | null | undefined): boolean { return url && typeof url === "string" ? url.startsWith("ar://") : false; } diff --git a/lib/arweave/gateway.ts b/lib/arweave/gateway.ts index c8c38153c..acb0c63e1 100644 --- a/lib/arweave/gateway.ts +++ b/lib/arweave/gateway.ts @@ -5,12 +5,19 @@ const IPFS_GATEWAY = "https://magic.decentralized-content.com"; const ARWEAVE_GATEWAY = "https://arweave.net"; +/** + * + * @param normalizedArweaveUrl + */ export function arweaveGatewayUrl(normalizedArweaveUrl: string | null) { - if (!normalizedArweaveUrl || typeof normalizedArweaveUrl !== "string") - return null; + if (!normalizedArweaveUrl || typeof normalizedArweaveUrl !== "string") return null; return normalizedArweaveUrl.replace("ar://", `${ARWEAVE_GATEWAY}/`); } +/** + * + * @param url + */ export function ipfsGatewayUrl(url: string | null) { if (!url || typeof url !== "string") return null; const normalizedIPFSUrl = normalizeIPFSUrl(url); @@ -20,6 +27,10 @@ export function ipfsGatewayUrl(url: string | null) { return null; } +/** + * + * @param uri + */ export function getFetchableUrl(uri: string | null | undefined): string | null { if (!uri || typeof uri !== "string") return null; diff --git a/lib/arweave/ipfs.ts b/lib/arweave/ipfs.ts index 9df2c3742..fc48fc5ec 100644 --- a/lib/arweave/ipfs.ts +++ b/lib/arweave/ipfs.ts @@ -2,6 +2,10 @@ import { CID } from "multiformats/cid"; export type IPFSUrl = `ipfs://${string}`; +/** + * + * @param str + */ export function isCID(str: string | null | undefined): boolean { if (!str) return false; @@ -15,6 +19,10 @@ export function isCID(str: string | null | undefined): boolean { } } +/** + * + * @param url + */ export function normalizeIPFSUrl(url: string | null | undefined): IPFSUrl | null { if (!url || typeof url !== "string") return null; @@ -47,10 +55,18 @@ export function normalizeIPFSUrl(url: string | null | undefined): IPFSUrl | null return null; } +/** + * + * @param url + */ function isNormalizedIPFSURL(url: string | null | undefined): boolean { return url && typeof url === "string" ? url.startsWith("ipfs://") : false; } +/** + * + * @param url + */ function isGatewayIPFSUrl(url: string | null | undefined): boolean { if (url && typeof url === "string") { try { @@ -64,10 +80,18 @@ function isGatewayIPFSUrl(url: string | null | undefined): boolean { return false; } +/** + * + * @param url + */ function isIPFSUrl(url: string | null | undefined): boolean { return url ? isNormalizedIPFSURL(url) || isGatewayIPFSUrl(url) : false; } +/** + * + * @param url + */ export function isNormalizeableIPFSUrl(url: string | null | undefined): boolean { return url ? isIPFSUrl(url) || isCID(url) : false; } diff --git a/lib/arweave/uploadLinkToArweave.ts b/lib/arweave/uploadLinkToArweave.ts index db42f1b74..49a9d3a49 100644 --- a/lib/arweave/uploadLinkToArweave.ts +++ b/lib/arweave/uploadLinkToArweave.ts @@ -4,12 +4,10 @@ import uploadToArweave from "./uploadToArweave"; * Uploads an image from a remote URL to Arweave and returns the Arweave URL. * Falls back to the original URL if upload fails. * - * @param imageUrl The remote image URL + * @param imageUrl - The remote image URL * @returns The Arweave URL or the original URL if upload fails */ -export default async function uploadLinkToArweave( - imageUrl: string -): Promise { +export default async function uploadLinkToArweave(imageUrl: string): Promise { try { const imgRes = await fetch(imageUrl); const imgBuffer = Buffer.from(await imgRes.arrayBuffer()); diff --git a/lib/arweave/uploadMetadataJson.ts b/lib/arweave/uploadMetadataJson.ts index bb52705ae..e62252683 100644 --- a/lib/arweave/uploadMetadataJson.ts +++ b/lib/arweave/uploadMetadataJson.ts @@ -14,13 +14,13 @@ interface CreateMetadataArgs { /** * Uploads a metadata JSON object to Arweave as a base64-encoded file. - * @param args The metadata creation arguments + * + * @param args - The metadata creation arguments + * @param metadata * @returns The result from uploadToArweave */ export async function uploadMetadataJson(metadata: CreateMetadataArgs) { - const metadataBase64 = Buffer.from(JSON.stringify(metadata)).toString( - "base64" - ); + const metadataBase64 = Buffer.from(JSON.stringify(metadata)).toString("base64"); const metadataResult = await uploadToArweave({ base64Data: metadataBase64, mimeType: "application/json", diff --git a/lib/arweave/uploadToArweave.ts b/lib/arweave/uploadToArweave.ts index 27ebc37ad..dc00ad2bb 100644 --- a/lib/arweave/uploadToArweave.ts +++ b/lib/arweave/uploadToArweave.ts @@ -5,7 +5,7 @@ const rawArweaveKey = process.env.ARWEAVE_KEY; if (!rawArweaveKey) { throw new Error( - "ARWEAVE_KEY environment variable is missing. Please set it to a base64-encoded JSON key." + "ARWEAVE_KEY environment variable is missing. Please set it to a base64-encoded JSON key.", ); } @@ -17,7 +17,7 @@ const ARWEAVE_KEY: JWKInterface = (() => { throw new Error( `Failed to decode ARWEAVE_KEY. Ensure it is base64-encoded JSON. ${ error instanceof Error ? error.message : error - }` + }`, ); } })(); @@ -32,7 +32,7 @@ const arweave = Arweave.init({ const uploadToArweave = async ( imageData: { base64Data: string; mimeType: string }, - getProgress: (progress: number) => void = () => {} + getProgress: (progress: number) => void = () => {}, ): Promise => { const buffer = Buffer.from(imageData.base64Data, "base64"); @@ -40,7 +40,7 @@ const uploadToArweave = async ( { data: buffer, }, - ARWEAVE_KEY + ARWEAVE_KEY, ); transaction.addTag("Content-Type", imageData.mimeType); await arweave.transactions.sign(transaction, ARWEAVE_KEY); @@ -48,7 +48,7 @@ const uploadToArweave = async ( while (!uploader.isComplete) { console.log( - `${uploader.pctComplete}% complete, ${uploader.uploadedChunks}/${uploader.totalChunks}` + `${uploader.pctComplete}% complete, ${uploader.uploadedChunks}/${uploader.totalChunks}`, ); getProgress(uploader.pctComplete); await uploader.uploadChunk(); diff --git a/lib/browser/buildResponseText.ts b/lib/browser/buildResponseText.ts index 6c3bda26d..7099d63ad 100644 --- a/lib/browser/buildResponseText.ts +++ b/lib/browser/buildResponseText.ts @@ -2,29 +2,35 @@ import { CONTENT_LIMITS } from "./constants"; /** * Build formatted response text with rate limit warnings, content, and actions + * + * @param visibleContent + * @param actionsText + * @param modalDismissed + * @param platformName + * @param isRateLimited */ export function buildResponseText( visibleContent: string, actionsText: string, modalDismissed: boolean, platformName: string, - isRateLimited: boolean + isRateLimited: boolean, ): string { let responseText = ""; - + // Add rate limit warning if detected if (isRateLimited) { responseText += "⚠️ RATE LIMIT DETECTED\n"; - responseText += `${platformName || 'The website'} is limiting automated requests. Try:\n`; + responseText += `${platformName || "The website"} is limiting automated requests. Try:\n`; responseText += "1. Wait a few minutes before trying again\n"; responseText += "2. Reduce request frequency\n"; responseText += "3. Add delays between requests\n\n"; } - + if (modalDismissed) { responseText += "✅ Login modal detected and dismissed\n\n"; } - + responseText += "📄 VISIBLE PAGE CONTENT:\n"; responseText += "════════════════════════════════════════\n"; responseText += visibleContent.trim().slice(0, CONTENT_LIMITS.MAX_VISIBLE_CONTENT_LENGTH); @@ -37,4 +43,3 @@ export function buildResponseText( return responseText; } - diff --git a/lib/browser/captureScreenshot.ts b/lib/browser/captureScreenshot.ts index 060548b98..1ea75809f 100644 --- a/lib/browser/captureScreenshot.ts +++ b/lib/browser/captureScreenshot.ts @@ -2,10 +2,12 @@ import type { Page } from "@browserbasehq/stagehand"; import { detectPlatform } from "./detectPlatform"; import { uploadScreenshot } from "./uploadScreenshot"; -export async function captureScreenshot( - page: Page, - url: string -): Promise { +/** + * + * @param page + * @param url + */ +export async function captureScreenshot(page: Page, url: string): Promise { const screenshotBuffer = await page.screenshot(); const screenshotBase64 = screenshotBuffer.toString("base64"); const platformName = detectPlatform(url); @@ -13,4 +15,3 @@ export async function captureScreenshot( return screenshotUrl; } - diff --git a/lib/browser/constants.ts b/lib/browser/constants.ts index 5d77dc44a..cbeca23ac 100644 --- a/lib/browser/constants.ts +++ b/lib/browser/constants.ts @@ -18,4 +18,3 @@ export const BROWSER_AGENT_CONFIG = { MAX_STEPS: 100, // Maximum steps for autonomous agent execution DEFAULT_MODEL: "gemini-2.5-computer-use-preview-10-2025", // Default AI model for agent } as const; - diff --git a/lib/browser/detectPlatform.ts b/lib/browser/detectPlatform.ts index 3ae8edd69..40256f9f8 100644 --- a/lib/browser/detectPlatform.ts +++ b/lib/browser/detectPlatform.ts @@ -1,5 +1,7 @@ /** * Detect platform/social network from URL + * + * @param url */ export function detectPlatform(url?: string): string { if (!url) return "browser"; @@ -15,4 +17,3 @@ export function detectPlatform(url?: string): string { return "browser"; } - diff --git a/lib/browser/dismissLoginModal.ts b/lib/browser/dismissLoginModal.ts index 6ba0fe5df..08e334bd5 100644 --- a/lib/browser/dismissLoginModal.ts +++ b/lib/browser/dismissLoginModal.ts @@ -3,6 +3,8 @@ import type { Page } from "@browserbasehq/stagehand"; /** * Attempt to automatically dismiss login modals/popups * Returns true if a modal was dismissed, false otherwise + * + * @param page */ export async function dismissLoginModal(page: Page): Promise { try { @@ -15,4 +17,3 @@ export async function dismissLoginModal(page: Page): Promise { return false; } } - diff --git a/lib/browser/extractPageData.ts b/lib/browser/extractPageData.ts index 4836f39fc..510592250 100644 --- a/lib/browser/extractPageData.ts +++ b/lib/browser/extractPageData.ts @@ -3,19 +3,22 @@ import { z } from "zod"; /** * Extract visible content and observe interactive elements + * + * @param page + * @param instruction */ export async function extractPageData(page: Page, instruction?: string) { const { visibleContent } = await page.extract({ - instruction: "Extract all visible textual content from the page, including any visible counts, labels, captions, and metadata", + instruction: + "Extract all visible textual content from the page, including any visible counts, labels, captions, and metadata", schema: z.object({ visibleContent: z.string().describe("All visible text content on the page"), }), }); - + const observeResult = await page.observe({ instruction: instruction || "Find all interactive elements and actions", }); return { visibleContent, observeResult }; } - diff --git a/lib/browser/formatActionsToString.ts b/lib/browser/formatActionsToString.ts index 192ea3a6a..e26a5866d 100644 --- a/lib/browser/formatActionsToString.ts +++ b/lib/browser/formatActionsToString.ts @@ -1,35 +1,35 @@ /** * Convert observe results to human-readable action strings * Handles strings, objects with description/action properties, and fallback to JSON + * + * @param observeResult */ export function formatActionsToString(observeResult: unknown): string { // Normalize to array - const actions = Array.isArray(observeResult) - ? observeResult - : [observeResult]; + const actions = Array.isArray(observeResult) ? observeResult : [observeResult]; // Convert each action to a human-readable string const actionStrings = actions .map(action => { - if (typeof action === 'string') { + if (typeof action === "string") { return action.trim(); } - if (action && typeof action === 'object') { + if (action && typeof action === "object") { // Try to extract a descriptive property - if ('description' in action && typeof action.description === 'string') { + if ("description" in action && typeof action.description === "string") { return action.description.trim(); } - if ('action' in action && typeof action.action === 'string') { + if ("action" in action && typeof action.action === "string") { return action.action.trim(); } // Fallback to JSON.stringify for unknown objects try { return JSON.stringify(action); } catch { - return '[Unknown action]'; + return "[Unknown action]"; } } - return ''; + return ""; }) .filter(str => str.length > 0); // Remove empty strings @@ -37,4 +37,3 @@ export function formatActionsToString(observeResult: unknown): string { ? actionStrings.map(str => `- ${str}`).join("\n") : "No specific actions identified"; } - diff --git a/lib/browser/formatFieldName.ts b/lib/browser/formatFieldName.ts index 726032ddb..2c7f7888e 100644 --- a/lib/browser/formatFieldName.ts +++ b/lib/browser/formatFieldName.ts @@ -1,16 +1,19 @@ /** * Format camelCase or snake_case field names to readable labels + * + * @param fieldName */ export function formatFieldName(fieldName: string): string { - return fieldName - // Handle camelCase: insert space before capitals - .replace(/([A-Z])/g, ' $1') - // Handle snake_case: replace underscores with spaces - .replace(/_/g, ' ') - // Capitalize first letter of each word - .split(' ') - .map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()) - .join(' ') - .trim(); + return ( + fieldName + // Handle camelCase: insert space before capitals + .replace(/([A-Z])/g, " $1") + // Handle snake_case: replace underscores with spaces + .replace(/_/g, " ") + // Capitalize first letter of each word + .split(" ") + .map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()) + .join(" ") + .trim() + ); } - diff --git a/lib/browser/formatFieldValue.ts b/lib/browser/formatFieldValue.ts index ac3390306..9dba991ac 100644 --- a/lib/browser/formatFieldValue.ts +++ b/lib/browser/formatFieldValue.ts @@ -4,30 +4,32 @@ const MAX_JSON_DEPTH = 3; /** * Format field values with appropriate type handling + * + * @param value */ export function formatFieldValue(value: unknown): string | null { if (value === null || value === undefined) { return null; } - if (typeof value === 'string') { + if (typeof value === "string") { return value.trim() || null; } - if (typeof value === 'number') { + if (typeof value === "number") { // Format large numbers with commas return value.toLocaleString(); } - if (typeof value === 'boolean') { - return value ? 'Yes' : 'No'; + if (typeof value === "boolean") { + return value ? "Yes" : "No"; } if (Array.isArray(value)) { - return value.length > 0 ? value.join(', ') : null; + return value.length > 0 ? value.join(", ") : null; } - if (typeof value === 'object') { + if (typeof value === "object") { return safeStringifyObject(value); } @@ -35,22 +37,26 @@ export function formatFieldValue(value: unknown): string | null { } // Safe JSON serialization with depth and length limits +/** + * + * @param obj + */ function safeStringifyObject(obj: object): string { try { // Recursively walk object with per-branch depth tracking const limitDepth = (value: unknown, currentDepth: number): unknown => { if (currentDepth > MAX_JSON_DEPTH) { - return '[Max depth]'; + return "[Max depth]"; } - - if (value === null || typeof value !== 'object') { + + if (value === null || typeof value !== "object") { return value; } - + if (Array.isArray(value)) { return value.map(item => limitDepth(item, currentDepth + 1)); } - + // Plain object - recurse into each property const limited: Record = {}; for (const [key, val] of Object.entries(value)) { @@ -61,15 +67,14 @@ function safeStringifyObject(obj: object): string { const depthLimited = limitDepth(obj, 0); const result = JSON.stringify(depthLimited, null, 2); - + if (result.length > MAX_JSON_LENGTH) { - return result.slice(0, MAX_JSON_LENGTH) + '...'; + return result.slice(0, MAX_JSON_LENGTH) + "..."; } - + return result; } catch { // Handle circular references or stringify failures - return '[Object (truncated)]'; + return "[Object (truncated)]"; } } - diff --git a/lib/browser/getBrowserResultType.ts b/lib/browser/getBrowserResultType.ts index 6b24a9edf..ebb35ca5f 100644 --- a/lib/browser/getBrowserResultType.ts +++ b/lib/browser/getBrowserResultType.ts @@ -2,16 +2,20 @@ import type { BrowserToolResultType } from "@/components/VercelChat/tools/browse /** * Detect result type and compute display values + * + * @param result */ export function getBrowserResultType(result: BrowserToolResultType) { const isExtractResult = result.data !== undefined; const isMessageResult = result.message !== undefined; - const displayScreenshot = result.finalScreenshotUrl || result.initialScreenshotUrl || result.screenshotUrl; - - const title = isExtractResult ? "Data Extracted Successfully" : - isMessageResult ? "Page Observed Successfully" : - "Operation completed successfully"; - + const displayScreenshot = + result.finalScreenshotUrl || result.initialScreenshotUrl || result.screenshotUrl; + + const title = isExtractResult + ? "Data Extracted Successfully" + : isMessageResult + ? "Page Observed Successfully" + : "Operation completed successfully"; + return { isExtractResult, isMessageResult, displayScreenshot, title }; } - diff --git a/lib/browser/getInitialSteps.ts b/lib/browser/getInitialSteps.ts index c8c576145..e23c2bad6 100644 --- a/lib/browser/getInitialSteps.ts +++ b/lib/browser/getInitialSteps.ts @@ -6,6 +6,8 @@ interface ProgressStep { /** * Get initial progress steps for browser tool skeleton based on tool type + * + * @param toolName */ export function getInitialSteps(toolName: string): ProgressStep[] { if (toolName === "browser_extract") { @@ -40,7 +42,7 @@ export function getInitialSteps(toolName: string): ProgressStep[] { { label: "Completing workflow", completed: false, active: false }, ]; } - + return [ { label: "Starting browser automation", completed: false, active: true }, { label: "Processing request", completed: false, active: false }, @@ -48,4 +50,3 @@ export function getInitialSteps(toolName: string): ProgressStep[] { } export type { ProgressStep }; - diff --git a/lib/browser/getPlatformInfo.ts b/lib/browser/getPlatformInfo.ts index af9700d23..4b7ef3c76 100644 --- a/lib/browser/getPlatformInfo.ts +++ b/lib/browser/getPlatformInfo.ts @@ -2,10 +2,12 @@ import { detectPlatform } from "./detectPlatform"; /** * Get platform name and emoji for display + * + * @param url */ export function getPlatformInfo(url?: string) { const platform = detectPlatform(url); - + const platformEmojis: Record = { instagram: "📷", facebook: "👥", @@ -15,10 +17,9 @@ export function getPlatformInfo(url?: string) { threads: "🧵", browser: "🌐", }; - + return { name: platform, emoji: platformEmojis[platform] || "🌐", }; } - diff --git a/lib/browser/getTaskDescription.ts b/lib/browser/getTaskDescription.ts index e30164be6..83b3ef9f4 100644 --- a/lib/browser/getTaskDescription.ts +++ b/lib/browser/getTaskDescription.ts @@ -1,9 +1,13 @@ /** * Get task description text for browser tool skeleton based on tool type + * + * @param toolName + * @param platformName + * @param url */ export function getTaskDescription(toolName: string, platformName: string, url?: string): string { const accountName = url?.split("/").pop()?.replace("@", "") || ""; - + if (toolName === "browser_extract") { return `Extract the current follower count for the ${platformName} account ${accountName ? "@" + accountName : ""}.`; } else if (toolName === "browser_act") { @@ -13,7 +17,6 @@ export function getTaskDescription(toolName: string, platformName: string, url?: } else if (toolName === "browser_agent") { return `Autonomously navigate and extract data from ${platformName}.`; } - + return `Processing ${platformName}...`; } - diff --git a/lib/browser/initStagehand.ts b/lib/browser/initStagehand.ts index 84b641e7e..00899d94e 100644 --- a/lib/browser/initStagehand.ts +++ b/lib/browser/initStagehand.ts @@ -1,5 +1,8 @@ import { Stagehand } from "@browserbasehq/stagehand"; +/** + * + */ export async function initStagehand(): Promise<{ stagehand: Stagehand; sessionUrl?: string; @@ -11,7 +14,7 @@ export async function initStagehand(): Promise<{ if (!apiKey || !projectId) { throw new Error( - "Missing Browserbase credentials. Please set BROWSERBASE_API_KEY and BROWSERBASE_PROJECT_ID environment variables." + "Missing Browserbase credentials. Please set BROWSERBASE_API_KEY and BROWSERBASE_PROJECT_ID environment variables.", ); } @@ -34,4 +37,3 @@ export async function initStagehand(): Promise<{ debugUrl: initResult.debugUrl, }; } - diff --git a/lib/browser/isBlockedStartUrl.ts b/lib/browser/isBlockedStartUrl.ts index e57612831..edeb04eb5 100644 --- a/lib/browser/isBlockedStartUrl.ts +++ b/lib/browser/isBlockedStartUrl.ts @@ -1,40 +1,41 @@ /** * Check if a startURL points to a private or disallowed host * Blocks SSRF attacks by rejecting internal/private hosts (IPv4 and IPv6) + * + * @param startUrl */ export function isBlockedStartUrl(startUrl: string): boolean { try { const url = new URL(startUrl); - + // Only allow http(s) protocols - if (!['http:', 'https:'].includes(url.protocol)) { + if (!["http:", "https:"].includes(url.protocol)) { return true; } - + const host = url.hostname.toLowerCase(); - + // Block localhost variants - if (host === 'localhost' || host === '0.0.0.0') { + if (host === "localhost" || host === "0.0.0.0") { return true; } - + // Block IPv6 localhost and private ranges - if (host === '::1' || host === '[::1]') return true; // IPv6 localhost - if (host.startsWith('fc') || host.startsWith('fd')) return true; // fc00::/7 unique local - if (host.startsWith('fe80:')) return true; // fe80::/10 link-local - if (host.startsWith('::ffff:127.') || host.startsWith('::ffff:10.')) return true; // IPv4-mapped - + if (host === "::1" || host === "[::1]") return true; // IPv6 localhost + if (host.startsWith("fc") || host.startsWith("fd")) return true; // fc00::/7 unique local + if (host.startsWith("fe80:")) return true; // fe80::/10 link-local + if (host.startsWith("::ffff:127.") || host.startsWith("::ffff:10.")) return true; // IPv4-mapped + // Block IPv4 private ranges if (/^127\.\d+\.\d+\.\d+$/.test(host)) return true; // 127.x.x.x if (/^10\.\d+\.\d+\.\d+$/.test(host)) return true; // 10.x.x.x if (/^192\.168\.\d+\.\d+$/.test(host)) return true; // 192.168.x.x if (/^172\.(1[6-9]|2\d|3[01])\.\d+\.\d+$/.test(host)) return true; // 172.16-31.x.x if (/^169\.254\.\d+\.\d+$/.test(host)) return true; // 169.254.x.x (link-local) - + return false; } catch { // Invalid URL return true; } } - diff --git a/lib/browser/isPlainObject.ts b/lib/browser/isPlainObject.ts index e1bdbfd21..f1a7327e2 100644 --- a/lib/browser/isPlainObject.ts +++ b/lib/browser/isPlainObject.ts @@ -1,12 +1,13 @@ /** * Runtime type guard: Ensure value is a plain object (not array, null, or class instance) + * + * @param value */ export function isPlainObject(value: unknown): value is Record { return ( - typeof value === 'object' && - value !== null && - !Array.isArray(value) && + typeof value === "object" && + value !== null && + !Array.isArray(value) && Object.getPrototypeOf(value) === Object.prototype ); } - diff --git a/lib/browser/isPriorityField.ts b/lib/browser/isPriorityField.ts index bfc7252c1..17ce1c7ea 100644 --- a/lib/browser/isPriorityField.ts +++ b/lib/browser/isPriorityField.ts @@ -2,7 +2,8 @@ import { PRIORITY_FIELDS } from "./priorityFields"; /** * Check if field name is a priority metric + * + * @param key */ export const isPriorityField = (key: string): boolean => PRIORITY_FIELDS.some(field => key.toLowerCase().includes(field.toLowerCase())); - diff --git a/lib/browser/normalizeInstagramUrl.ts b/lib/browser/normalizeInstagramUrl.ts index 0a687fa07..181f8a34d 100644 --- a/lib/browser/normalizeInstagramUrl.ts +++ b/lib/browser/normalizeInstagramUrl.ts @@ -1,22 +1,23 @@ /** * Normalize Instagram URLs to use www subdomain * This helps avoid more aggressive rate limiting on the main domain + * + * @param url */ export function normalizeInstagramUrl(url: string): string { - if (!url.includes('instagram.com')) { + if (!url.includes("instagram.com")) { return url; } try { const urlObj = new URL(url); - if (urlObj.hostname === 'instagram.com') { - urlObj.hostname = 'www.instagram.com'; + if (urlObj.hostname === "instagram.com") { + urlObj.hostname = "www.instagram.com"; return urlObj.toString(); } return url; } catch { // Fallback for malformed URLs - use safer regex approach - return url.replace(/^(https?:\/\/)instagram\.com/, '$1www.instagram.com'); + return url.replace(/^(https?:\/\/)instagram\.com/, "$1www.instagram.com"); } } - diff --git a/lib/browser/performPageSetup.ts b/lib/browser/performPageSetup.ts index 122eff24b..f83c54529 100644 --- a/lib/browser/performPageSetup.ts +++ b/lib/browser/performPageSetup.ts @@ -7,14 +7,17 @@ import { BROWSER_TIMEOUTS } from "./constants"; /** * Perform initial page setup: navigate, wait, scroll, dismiss modal * Returns whether a modal was dismissed + * + * @param page + * @param url */ export async function performPageSetup(page: Page, url: string): Promise { const targetUrl = normalizeInstagramUrl(url); - await page.goto(targetUrl, { - waitUntil: "domcontentloaded", - timeout: BROWSER_TIMEOUTS.PAGE_NAVIGATION + await page.goto(targetUrl, { + waitUntil: "domcontentloaded", + timeout: BROWSER_TIMEOUTS.PAGE_NAVIGATION, }); - + // Wait for initial page load await page.waitForTimeout(BROWSER_TIMEOUTS.INITIAL_PAGE_LOAD); @@ -22,4 +25,3 @@ export async function performPageSetup(page: Page, url: string): Promise): z.ZodObject { const schemaKeys = Object.keys(schema); - + if (schemaKeys.length > MAX_SCHEMA_KEYS) { throw new Error(`Schema has too many fields (max ${MAX_SCHEMA_KEYS})`); } @@ -37,4 +41,3 @@ export function schemaToZod(schema: Record): z.ZodObject { try { // Scroll down await page.evaluate(() => { - window.scrollTo({ top: 300, behavior: 'smooth' }); + window.scrollTo({ top: 300, behavior: "smooth" }); }); await page.waitForTimeout(1500); - + // Scroll back up await page.evaluate(() => { - window.scrollTo({ top: 0, behavior: 'smooth' }); + window.scrollTo({ top: 0, behavior: "smooth" }); }); await page.waitForTimeout(1000); } catch { // Scrolling behavior skipped - continue silently } } - diff --git a/lib/browser/uploadScreenshot.ts b/lib/browser/uploadScreenshot.ts index 8e9255169..eeefb6911 100644 --- a/lib/browser/uploadScreenshot.ts +++ b/lib/browser/uploadScreenshot.ts @@ -2,9 +2,14 @@ import { uploadFileByKey } from "@/lib/supabase/storage/uploadFileByKey"; import { createSignedUrlForKey } from "@/lib/supabase/storage/createSignedUrl"; import { BROWSER_TIMEOUTS } from "./constants"; +/** + * + * @param screenshot + * @param platformName + */ export async function uploadScreenshot( screenshot: string, - platformName: string = "browser" + platformName: string = "browser", ): Promise { try { const buffer = Buffer.from(screenshot, "base64"); @@ -25,4 +30,3 @@ export async function uploadScreenshot( return ""; } } - diff --git a/lib/browser/withBrowser.ts b/lib/browser/withBrowser.ts index b2129f0d8..e703658b3 100644 --- a/lib/browser/withBrowser.ts +++ b/lib/browser/withBrowser.ts @@ -1,8 +1,12 @@ import type { Page } from "@browserbasehq/stagehand"; import { initStagehand } from "./initStagehand"; +/** + * + * @param operation + */ export async function withBrowser( - operation: (page: Page, liveViewUrl?: string, sessionUrl?: string) => Promise + operation: (page: Page, liveViewUrl?: string, sessionUrl?: string) => Promise, ): Promise { const { stagehand, liveViewUrl, sessionUrl } = await initStagehand(); @@ -14,9 +18,8 @@ export async function withBrowser( try { await stagehand.close(); } catch (closeError) { - console.warn('[withBrowser] Failed to close stagehand during error cleanup:', closeError); + console.warn("[withBrowser] Failed to close stagehand during error cleanup:", closeError); } throw error; } } - diff --git a/lib/catalog/analyzeCatalogBatch.ts b/lib/catalog/analyzeCatalogBatch.ts index 22c459fa6..54a213898 100644 --- a/lib/catalog/analyzeCatalogBatch.ts +++ b/lib/catalog/analyzeCatalogBatch.ts @@ -6,10 +6,13 @@ import { DEFAULT_MODEL } from "@/lib/consts"; /** * Analyzes a single batch of catalog songs using AI to filter by criteria * Single Responsibility: Process one batch of songs with AI filtering + * + * @param songs + * @param criteria */ export async function analyzeCatalogBatch( songs: CatalogSong[], - criteria: string + criteria: string, ): Promise { // Use AI to select relevant songs from this batch const { object } = await generateObject({ @@ -23,20 +26,18 @@ export async function analyzeCatalogBatch( Songs: ${JSON.stringify( - songs.map((s) => ({ + songs.map(s => ({ isrc: s.isrc, name: s.name, - artist: s.artists.map((a) => a.name).join(", "), + artist: s.artists.map(a => a.name).join(", "), })), null, - 2 + 2, )} Return only the ISRCs of songs that match the criteria.`, }); // Filter songs based on AI selection - return songs.filter((song) => - (object.selected_song_isrcs as string[]).includes(song.isrc) - ); + return songs.filter(song => (object.selected_song_isrcs as string[]).includes(song.isrc)); } diff --git a/lib/catalog/analyzeFullCatalog.ts b/lib/catalog/analyzeFullCatalog.ts index b58e7c4f7..6e538eeb1 100644 --- a/lib/catalog/analyzeFullCatalog.ts +++ b/lib/catalog/analyzeFullCatalog.ts @@ -11,6 +11,10 @@ export interface AnalyzeFullCatalogOptions { /** * Fetches all songs from a catalog and filters them using AI in parallel batches * Following Open-Closed Principle: open for extension (custom filtering logic), closed for modification + * + * @param root0 + * @param root0.catalogId + * @param root0.criteria */ export async function analyzeFullCatalog({ catalogId, @@ -27,10 +31,8 @@ export async function analyzeFullCatalog({ // Fetch all pages in parallel const pageNumbers = Array.from({ length: totalPages }, (_, i) => i + 1); - const pagePromises = pageNumbers.map(async (pageNum) => - pageNum === 1 - ? firstPage.songs - : (await getCatalogSongs(catalogId, BATCH_SIZE, pageNum)).songs + const pagePromises = pageNumbers.map(async pageNum => + pageNum === 1 ? firstPage.songs : (await getCatalogSongs(catalogId, BATCH_SIZE, pageNum)).songs, ); const allPages = await Promise.all(pagePromises); diff --git a/lib/catalog/createCatalogResult.ts b/lib/catalog/createCatalogResult.ts index b78bff525..6ecd403c2 100644 --- a/lib/catalog/createCatalogResult.ts +++ b/lib/catalog/createCatalogResult.ts @@ -3,11 +3,11 @@ import { CatalogSongsResponse } from "./getCatalogSongs"; /** * Creates a CatalogSongsResult from paginated catalog data + * + * @param pages */ -export const createCatalogResult = ( - pages: CatalogSongsResponse[] -): CatalogSongsResult => { - const allSongs = pages.flatMap((page) => page.songs); +export const createCatalogResult = (pages: CatalogSongsResponse[]): CatalogSongsResult => { + const allSongs = pages.flatMap(page => page.songs); const totalCount = pages[0].pagination.total_count; return { diff --git a/lib/catalog/createErrorResult.ts b/lib/catalog/createErrorResult.ts index 465b4a88c..2492913fc 100644 --- a/lib/catalog/createErrorResult.ts +++ b/lib/catalog/createErrorResult.ts @@ -2,13 +2,11 @@ import { CatalogSongsResult } from "@/components/VercelChat/tools/catalog/Catalo /** * Creates a CatalogSongsResult for error states + * + * @param error */ export const createErrorResult = (error: Error | null): CatalogSongsResult => ({ success: false, error: - error instanceof Error - ? error.message - : error - ? "Failed to load songs" - : "No data available", + error instanceof Error ? error.message : error ? "Failed to load songs" : "No data available", }); diff --git a/lib/catalog/createSearchResult.ts b/lib/catalog/createSearchResult.ts index e81e02109..05eff8abc 100644 --- a/lib/catalog/createSearchResult.ts +++ b/lib/catalog/createSearchResult.ts @@ -3,14 +3,18 @@ import { SongsByIsrcResponse } from "./getSongsByIsrc"; /** * Creates a CatalogSongsResult from ISRC search data + * + * @param searchData + * @param catalogId + * @param activeIsrc */ export const createSearchResult = ( searchData: SongsByIsrcResponse, catalogId: string, - activeIsrc: string + activeIsrc: string, ): CatalogSongsResult => ({ success: searchData.status === "success", - songs: searchData.songs.map((song) => ({ + songs: searchData.songs.map(song => ({ ...song, catalog_id: catalogId, })), @@ -27,4 +31,3 @@ export const createSearchResult = ( : `No songs found for ISRC: ${activeIsrc}`, error: searchData.error, }); - diff --git a/lib/catalog/formatArtists.ts b/lib/catalog/formatArtists.ts index 1f220c9bd..6a3efa894 100644 --- a/lib/catalog/formatArtists.ts +++ b/lib/catalog/formatArtists.ts @@ -3,11 +3,10 @@ import { CatalogSongsResponse } from "./getCatalogSongs"; /** * Formats an array of artists into a comma-separated string * Returns "—" if no artists exist + * + * @param artists */ -export const formatArtists = ( - artists: CatalogSongsResponse["songs"][0]["artists"] -): string => { +export const formatArtists = (artists: CatalogSongsResponse["songs"][0]["artists"]): string => { if (!artists || artists.length === 0) return "—"; - return artists.map((artist) => artist.name).join(", "); + return artists.map(artist => artist.name).join(", "); }; - diff --git a/lib/catalog/formatCatalogSongsAsCSV.ts b/lib/catalog/formatCatalogSongsAsCSV.ts index f9d0bf226..29cc443cb 100644 --- a/lib/catalog/formatCatalogSongsAsCSV.ts +++ b/lib/catalog/formatCatalogSongsAsCSV.ts @@ -2,10 +2,12 @@ import { CatalogSong } from "./getCatalogSongs"; /** * Formats catalog songs into the CSV-like format expected by the scorer + * + * @param songs */ export function formatCatalogSongsAsCSV(songs: CatalogSong[]): string { - const csvLines = songs.map((song) => { - const artistNames = song.artists.map((artist) => artist.name).join(", "); + const csvLines = songs.map(song => { + const artistNames = song.artists.map(artist => artist.name).join(", "); return `${song.isrc},"${song.name}","${song.album}","${artistNames}"`; }); diff --git a/lib/catalog/getCatalogDataAsCSV.ts b/lib/catalog/getCatalogDataAsCSV.ts index ea529c37e..4a86fc0ee 100644 --- a/lib/catalog/getCatalogDataAsCSV.ts +++ b/lib/catalog/getCatalogDataAsCSV.ts @@ -3,6 +3,8 @@ import { formatCatalogSongsAsCSV } from "./formatCatalogSongsAsCSV"; /** * Gets all catalog songs and formats them as CSV for the scorer + * + * @param catalogId */ export async function getCatalogDataAsCSV(catalogId: string): Promise { const allSongs: CatalogSong[] = []; diff --git a/lib/catalog/getCatalogSongs.ts b/lib/catalog/getCatalogSongs.ts index b82747a6c..d7b5ca620 100644 --- a/lib/catalog/getCatalogSongs.ts +++ b/lib/catalog/getCatalogSongs.ts @@ -25,11 +25,18 @@ export interface CatalogSongsResponse { error?: string; } +/** + * + * @param catalogId + * @param pageSize + * @param page + * @param artistName + */ export async function getCatalogSongs( catalogId: string, pageSize: number = 100, page: number = 1, - artistName?: string + artistName?: string, ): Promise { try { const params = new URLSearchParams({ @@ -42,15 +49,12 @@ export async function getCatalogSongs( params.append("artistName", artistName); } - const response = await fetch( - `${NEW_API_BASE_URL}/api/catalogs/songs?${params}`, - { - method: "GET", - headers: { - "Content-Type": "application/json", - }, - } - ); + const response = await fetch(`${NEW_API_BASE_URL}/api/catalogs/songs?${params}`, { + method: "GET", + headers: { + "Content-Type": "application/json", + }, + }); if (!response.ok) { const errorText = await response.text(); diff --git a/lib/catalog/getCatalogs.ts b/lib/catalog/getCatalogs.ts index 179d1f3a9..4ac8a842d 100644 --- a/lib/catalog/getCatalogs.ts +++ b/lib/catalog/getCatalogs.ts @@ -8,9 +8,11 @@ export interface CatalogsResponse { error?: string; } -export async function getCatalogs( - accountId: string -): Promise { +/** + * + * @param accountId + */ +export async function getCatalogs(accountId: string): Promise { try { const response = await fetch( `https://api.recoupable.com/api/catalogs?account_id=${accountId}`, @@ -19,7 +21,7 @@ export async function getCatalogs( headers: { "Content-Type": "application/json", }, - } + }, ); if (!response.ok) { diff --git a/lib/catalog/getSongsByIsrc.ts b/lib/catalog/getSongsByIsrc.ts index d8b8d4edc..27721526d 100644 --- a/lib/catalog/getSongsByIsrc.ts +++ b/lib/catalog/getSongsByIsrc.ts @@ -17,9 +17,11 @@ export interface SongsByIsrcResponse { error?: string; } -export async function getSongsByIsrc( - isrc: string -): Promise { +/** + * + * @param isrc + */ +export async function getSongsByIsrc(isrc: string): Promise { try { if (!isrc || isrc.trim() === "") { throw new Error("ISRC code is required"); @@ -29,15 +31,12 @@ export async function getSongsByIsrc( isrc: isrc.trim(), }); - const response = await fetch( - `https://api.recoupable.com/api/songs?${params}`, - { - method: "GET", - headers: { - "Content-Type": "application/json", - }, - } - ); + const response = await fetch(`https://api.recoupable.com/api/songs?${params}`, { + method: "GET", + headers: { + "Content-Type": "application/json", + }, + }); if (!response.ok) { const errorText = await response.text(); diff --git a/lib/catalog/isCompleteSong.ts b/lib/catalog/isCompleteSong.ts index 6349a6bcf..b38787464 100644 --- a/lib/catalog/isCompleteSong.ts +++ b/lib/catalog/isCompleteSong.ts @@ -2,13 +2,17 @@ import { CatalogSongsResponse } from "./getCatalogSongs"; export type CatalogSongItem = CatalogSongsResponse["songs"][0]; +/** + * + * @param song + */ export function isCompleteSong(song: CatalogSongItem): boolean { const hasTitle = !!song.name && song.name.trim().length > 0; const hasIsrc = !!song.isrc && song.isrc.trim().length > 0; const hasAlbum = !!song.album && song.album.trim().length > 0; const hasNotes = !!song.notes && song.notes.trim().length > 0; const hasArtist = Array.isArray(song.artists) - ? song.artists.some((a) => !!a?.name && a.name.trim().length > 0) + ? song.artists.some(a => !!a?.name && a.name.trim().length > 0) : false; return hasTitle && hasArtist && hasAlbum && hasIsrc && hasNotes; } diff --git a/lib/catalog/parseCommaSeparated.ts b/lib/catalog/parseCommaSeparated.ts index 184c72410..be71bd205 100644 --- a/lib/catalog/parseCommaSeparated.ts +++ b/lib/catalog/parseCommaSeparated.ts @@ -2,6 +2,6 @@ export const parseCommaSeparated = (input?: string): string[] => { if (!input) return []; return input .split(",") - .map((value) => value.trim()) - .filter((value) => value.length > 0); + .map(value => value.trim()) + .filter(value => value.length > 0); }; diff --git a/lib/catalog/parseCsvFile.ts b/lib/catalog/parseCsvFile.ts index c4d1b0b27..21dee1ef7 100644 --- a/lib/catalog/parseCsvFile.ts +++ b/lib/catalog/parseCsvFile.ts @@ -14,11 +14,11 @@ type ParsedRow = Partial> & { * catalog_id is provided as a parameter * * Handles quoted fields, escaped quotes, and multiline cells correctly + * + * @param text + * @param catalogId */ -export function parseCsvFile( - text: string, - catalogId: string -): CatalogSongInput[] { +export function parseCsvFile(text: string, catalogId: string): CatalogSongInput[] { // Parse CSV using papaparse with optimized configuration const parseResult = Papa.parse(text, { header: true, // Automatically parse as objects with field names as keys @@ -29,12 +29,10 @@ export function parseCsvFile( // Check for critical parsing errors const criticalErrors = parseResult.errors.filter( - (e) => e.type === "Delimiter" || e.type === "FieldMismatch" + e => e.type === "Delimiter" || e.type === "FieldMismatch", ); if (criticalErrors.length > 0) { - throw new Error( - `CSV parsing errors: ${criticalErrors.map((e) => e.message).join(", ")}` - ); + throw new Error(`CSV parsing errors: ${criticalErrors.map(e => e.message).join(", ")}`); } const rows = parseResult.data; diff --git a/lib/catalog/postCatalogSongs.ts b/lib/catalog/postCatalogSongs.ts index c6dc209ac..ff519b1ad 100644 --- a/lib/catalog/postCatalogSongs.ts +++ b/lib/catalog/postCatalogSongs.ts @@ -13,10 +13,10 @@ export interface CatalogSongInput { /** * Adds songs to a catalog by ISRC in batch + * + * @param songs */ -export async function postCatalogSongs( - songs: CatalogSongInput[] -): Promise { +export async function postCatalogSongs(songs: CatalogSongInput[]): Promise { try { const response = await fetch(`${NEW_API_BASE_URL}/api/catalogs/songs`, { method: "POST", diff --git a/lib/catalog/processBatchesInParallel.ts b/lib/catalog/processBatchesInParallel.ts index 35bba9d85..c5dcb7ca2 100644 --- a/lib/catalog/processBatchesInParallel.ts +++ b/lib/catalog/processBatchesInParallel.ts @@ -6,19 +6,20 @@ const BATCH_SIZE = 100; /** * Processes batches of songs in parallel using AI filtering * Single Responsibility: Coordinate parallel batch processing + * + * @param songs + * @param criteria */ export async function processBatchesInParallel( songs: CatalogSong[], - criteria: string + criteria: string, ): Promise { const batches = []; for (let i = 0; i < songs.length; i += BATCH_SIZE) { batches.push(songs.slice(i, i + BATCH_SIZE)); } - const batchPromises = batches.map((batch) => - analyzeCatalogBatch(batch, criteria) - ); + const batchPromises = batches.map(batch => analyzeCatalogBatch(batch, criteria)); const results = await Promise.all(batchPromises); return results.flat(); diff --git a/lib/catalog/refineResults.ts b/lib/catalog/refineResults.ts index cc0e27620..3b79de69b 100644 --- a/lib/catalog/refineResults.ts +++ b/lib/catalog/refineResults.ts @@ -6,10 +6,13 @@ const MAX_RESULTS = 1000; /** * Recursively filters song selection until results are under MAX_RESULTS * Single Responsibility: Ensure result count stays within LLM context limits + * + * @param songs + * @param criteria */ export async function refineResults( songs: CatalogSong[], - criteria: string + criteria: string, ): Promise { if (songs.length <= MAX_RESULTS) return songs; diff --git a/lib/catalog/uploadBatchSongs.ts b/lib/catalog/uploadBatchSongs.ts index 23e390bd1..d74758220 100644 --- a/lib/catalog/uploadBatchSongs.ts +++ b/lib/catalog/uploadBatchSongs.ts @@ -5,7 +5,7 @@ const BATCH_SIZE = 1000; export const uploadBatchSongs = async ( songs: CatalogSongInput[], - onProgress?: (current: number, total: number) => void + onProgress?: (current: number, total: number) => void, ) => { let allSongs: CatalogSong[] = []; const batches = []; diff --git a/lib/chat/__tests__/proxyToApiChat.test.ts b/lib/chat/__tests__/proxyToApiChat.test.ts index cb2821adc..707b8d32d 100644 --- a/lib/chat/__tests__/proxyToApiChat.test.ts +++ b/lib/chat/__tests__/proxyToApiChat.test.ts @@ -114,13 +114,10 @@ describe("proxyToApiChat", () => { }); it("should handle error responses from recoup-api", async () => { - const mockErrorResponse = new Response( - JSON.stringify({ error: "Unauthorized" }), - { - status: 401, - headers: { "Content-Type": "application/json" }, - } - ); + const mockErrorResponse = new Response(JSON.stringify({ error: "Unauthorized" }), { + status: 401, + headers: { "Content-Type": "application/json" }, + }); mockFetch.mockResolvedValueOnce(mockErrorResponse); const request = new NextRequest("https://chat.recoupable.com/api/chat", { @@ -169,21 +166,18 @@ describe("proxyToApiChat", () => { { status: 200, headers: { "Content-Type": "application/json" }, - } + }, ); mockFetch.mockResolvedValueOnce(mockResponse); - const request = new NextRequest( - "https://chat.recoupable.com/api/chat/generate", - { - method: "POST", - headers: { - Authorization: "Bearer test-token", - "Content-Type": "application/json", - }, - body: JSON.stringify({ prompt: "Say hello" }), - } - ); + const request = new NextRequest("https://chat.recoupable.com/api/chat/generate", { + method: "POST", + headers: { + Authorization: "Bearer test-token", + "Content-Type": "application/json", + }, + body: JSON.stringify({ prompt: "Say hello" }), + }); await proxyToApiChat(request, { streaming: false }); @@ -202,22 +196,19 @@ describe("proxyToApiChat", () => { { status: 200, headers: { "Content-Type": "application/json" }, - } + }, ); mockFetch.mockResolvedValueOnce(mockResponse); - const request = new NextRequest( - "https://chat.recoupable.com/api/chat/generate", - { - method: "POST", - headers: { - Authorization: "Bearer test-token", - "x-api-key": "test-key", - "Content-Type": "application/json", - }, - body: JSON.stringify({ prompt: "Say hello" }), - } - ); + const request = new NextRequest("https://chat.recoupable.com/api/chat/generate", { + method: "POST", + headers: { + Authorization: "Bearer test-token", + "x-api-key": "test-key", + "Content-Type": "application/json", + }, + body: JSON.stringify({ prompt: "Say hello" }), + }); await proxyToApiChat(request, { streaming: false }); @@ -238,17 +229,14 @@ describe("proxyToApiChat", () => { }); mockFetch.mockResolvedValueOnce(mockResponse); - const request = new NextRequest( - "https://chat.recoupable.com/api/chat/generate", - { - method: "POST", - headers: { - Authorization: "Bearer test-token", - "Content-Type": "application/json", - }, - body: JSON.stringify({ prompt: "Say hello" }), - } - ); + const request = new NextRequest("https://chat.recoupable.com/api/chat/generate", { + method: "POST", + headers: { + Authorization: "Bearer test-token", + "Content-Type": "application/json", + }, + body: JSON.stringify({ prompt: "Say hello" }), + }); const response = await proxyToApiChat(request, { streaming: false }); @@ -304,26 +292,20 @@ describe("proxyToApiChat", () => { }); it("should include Deprecation header in non-streaming response", async () => { - const mockResponse = new Response( - JSON.stringify({ text: "Hello!", finishReason: "stop" }), - { - status: 200, - headers: { "Content-Type": "application/json" }, - } - ); + const mockResponse = new Response(JSON.stringify({ text: "Hello!", finishReason: "stop" }), { + status: 200, + headers: { "Content-Type": "application/json" }, + }); mockFetch.mockResolvedValueOnce(mockResponse); - const request = new NextRequest( - "https://chat.recoupable.com/api/chat/generate", - { - method: "POST", - headers: { - Authorization: "Bearer test-token", - "Content-Type": "application/json", - }, - body: JSON.stringify({ prompt: "Say hello" }), - } - ); + const request = new NextRequest("https://chat.recoupable.com/api/chat/generate", { + method: "POST", + headers: { + Authorization: "Bearer test-token", + "Content-Type": "application/json", + }, + body: JSON.stringify({ prompt: "Say hello" }), + }); const response = await proxyToApiChat(request, { streaming: false }); @@ -373,7 +355,7 @@ describe("proxyToApiChat", () => { const response = await proxyToApiChat(request, { streaming: true }); const linkHeader = response.headers.get("Link"); - expect(linkHeader).toContain("rel=\"deprecation\""); + expect(linkHeader).toContain('rel="deprecation"'); expect(linkHeader).toContain("recoup-api"); }); diff --git a/lib/chat/assistant/messageParser.ts b/lib/chat/assistant/messageParser.ts index a0d16415c..1c1650115 100644 --- a/lib/chat/assistant/messageParser.ts +++ b/lib/chat/assistant/messageParser.ts @@ -14,6 +14,8 @@ export interface JsonObject { /** * Finds and validates JSON objects containing fan data within a text string + * + * @param text */ export const findJsonObjects = (text: string): JsonObject[] => { const results: JsonObject[] = []; diff --git a/lib/chat/assistant/messageSegmentation.ts b/lib/chat/assistant/messageSegmentation.ts index 5e009d073..cc910344c 100644 --- a/lib/chat/assistant/messageSegmentation.ts +++ b/lib/chat/assistant/messageSegmentation.ts @@ -7,10 +7,13 @@ export interface MessageSegment { /** * Splits a message into segments of text and fan data + * + * @param content + * @param jsonObjects */ export const createMessageSegments = ( content: string, - jsonObjects: JsonObject[] + jsonObjects: JsonObject[], ): MessageSegment[] => { if (jsonObjects.length === 0) { return [{ type: "text", content }]; @@ -19,7 +22,7 @@ export const createMessageSegments = ( const segments: MessageSegment[] = []; let lastEnd = 0; - jsonObjects.forEach((obj) => { + jsonObjects.forEach(obj => { // Add text before the JSON if any if (obj.start > lastEnd) { const textContent = content.slice(lastEnd, obj.start).trim(); diff --git a/lib/chat/assistant/textFormatting.ts b/lib/chat/assistant/textFormatting.ts index 253bf81fc..b7528ade9 100644 --- a/lib/chat/assistant/textFormatting.ts +++ b/lib/chat/assistant/textFormatting.ts @@ -1,5 +1,7 @@ /** * Formats text content by converting markdown syntax to HTML and formatting lists + * + * @param text */ export const formatText = (text: string): string => { return ( @@ -9,18 +11,18 @@ export const formatText = (text: string): string => { // Format numbered lists while preserving original numbers .replace( /(?:\d+\.\s+[^\n]+\n?)+/g, - (match) => `
+ match => `
${match .split("\n") .filter(Boolean) .map( - (line) => `
+ line => `
${line.match(/^\d+/)?.[0]}. ${line.replace(/^\d+\.\s+/, "")} -
` +
`, ) .join("\n")} -
` +
`, ) ); }; diff --git a/lib/chat/buildSystemPromptWithImages.ts b/lib/chat/buildSystemPromptWithImages.ts index 7c9cab8d3..397ba751b 100644 --- a/lib/chat/buildSystemPromptWithImages.ts +++ b/lib/chat/buildSystemPromptWithImages.ts @@ -1,17 +1,14 @@ /** * Appends image URLs to system prompt for GPT to extract for tool parameters + * * @param basePrompt - The base system prompt * @param imageUrls - Array of image URLs to append * @returns System prompt with image URLs appended (if any) */ -export function buildSystemPromptWithImages( - basePrompt: string, - imageUrls: string[] -): string { +export function buildSystemPromptWithImages(basePrompt: string, imageUrls: string[]): string { if (imageUrls.length === 0) { return basePrompt; } - return `${basePrompt}\n\n**ATTACHED IMAGE URLS (for edit_image imageUrl parameter):**\n${imageUrls.map((url, i) => `- Image ${i}: ${url}`).join('\n')}`; + return `${basePrompt}\n\n**ATTACHED IMAGE URLS (for edit_image imageUrl parameter):**\n${imageUrls.map((url, i) => `- Image ${i}: ${url}`).join("\n")}`; } - diff --git a/lib/chat/cleanFileMentions.ts b/lib/chat/cleanFileMentions.ts index f653b3fae..ad49e9749 100644 --- a/lib/chat/cleanFileMentions.ts +++ b/lib/chat/cleanFileMentions.ts @@ -1,9 +1,10 @@ /** * Removes file mention markup from text for display purposes * Converts @[filename](id) to @filename + * + * @param text */ export function cleanFileMentions(text: string): string { // Replace @[display](id) with just @display return text.replace(/@\[([^\]]+)\]\([^)]+\)/g, "@$1"); } - diff --git a/lib/chat/createFileAttachment.ts b/lib/chat/createFileAttachment.ts index 8250db39d..3bd613079 100644 --- a/lib/chat/createFileAttachment.ts +++ b/lib/chat/createFileAttachment.ts @@ -1,9 +1,6 @@ import { FileUIPart } from "ai"; -const createMessageFileAttachment = (file: { - url: string; - type: string; -}): FileUIPart | null => { +const createMessageFileAttachment = (file: { url: string; type: string }): FileUIPart | null => { if (file.type === "application/pdf") { return { type: "file" as const, diff --git a/lib/chat/extractImageUrlsFromMessages.ts b/lib/chat/extractImageUrlsFromMessages.ts index 373e08fed..336276161 100644 --- a/lib/chat/extractImageUrlsFromMessages.ts +++ b/lib/chat/extractImageUrlsFromMessages.ts @@ -2,27 +2,27 @@ import { UIMessage } from "ai"; /** * Extracts image URLs from user messages with file attachments + * * @param messages - Array of UI messages from the chat * @returns Array of image URLs found in message attachments */ export function extractImageUrlsFromMessages(messages: UIMessage[]): string[] { const imageUrls: string[] = []; - + for (const message of messages) { if (message.parts) { for (const part of message.parts) { if ( - part.type === 'file' && - part.mediaType?.startsWith('image/') && - typeof part.url === 'string' && - part.url.trim() !== '' + part.type === "file" && + part.mediaType?.startsWith("image/") && + typeof part.url === "string" && + part.url.trim() !== "" ) { imageUrls.push(part.url); } } } } - + return imageUrls; } - diff --git a/lib/chat/formatTextAttachments.ts b/lib/chat/formatTextAttachments.ts index b26e40dca..304f92d22 100644 --- a/lib/chat/formatTextAttachments.ts +++ b/lib/chat/formatTextAttachments.ts @@ -3,12 +3,14 @@ import { TextAttachment } from "@/types/textAttachment"; /** * Formats text attachments into a string to prepend to message content. * Each file is wrapped with labeled delimiters for the AI to understand context. + * + * @param attachments */ export function formatTextAttachments(attachments: TextAttachment[]): string { if (attachments.length === 0) return ""; return attachments - .map((t) => { + .map(t => { const label = t.type === "md" ? "Markdown" : "CSV"; return `--- ${label} File: ${t.filename} ---\n${t.content}\n--- End of ${label} ---`; }) diff --git a/lib/chat/generateChatTitle.ts b/lib/chat/generateChatTitle.ts index 5311ef848..c6fe1ff08 100644 --- a/lib/chat/generateChatTitle.ts +++ b/lib/chat/generateChatTitle.ts @@ -17,5 +17,5 @@ export async function generateChatTitle(question: string): Promise { }); // In case model accidentally generates quotes again, remove them here - return response.text.replace(/^["']|["']$/g, ''); + return response.text.replace(/^["']|["']$/g, ""); } diff --git a/lib/chat/getChatDisplayInfo.ts b/lib/chat/getChatDisplayInfo.ts index 509604ba4..fb61fba73 100644 --- a/lib/chat/getChatDisplayInfo.ts +++ b/lib/chat/getChatDisplayInfo.ts @@ -7,8 +7,7 @@ export const getChatDisplayInfo = (item: Conversation | ArtistAgent) => { const displayName = isChatRoom ? item.topic : capitalize(item.type); return { - displayName: - displayName || `${capitalize(isChatRoom ? "Chat" : item.type)} Analysis`, + displayName: displayName || `${capitalize(isChatRoom ? "Chat" : item.type)} Analysis`, isChatRoom, }; }; diff --git a/lib/chat/getChatRoomId.ts b/lib/chat/getChatRoomId.ts index a9caa0301..5f303fdff 100644 --- a/lib/chat/getChatRoomId.ts +++ b/lib/chat/getChatRoomId.ts @@ -1,7 +1,5 @@ import type { Conversation } from "@/types/Chat"; import type { ArtistAgent } from "@/lib/supabase/getArtistAgents"; -export const getChatRoomId = ( - chatRoom: Conversation | ArtistAgent, -): string => ("id" in chatRoom ? chatRoom.id : chatRoom.agentId); - +export const getChatRoomId = (chatRoom: Conversation | ArtistAgent): string => + "id" in chatRoom ? chatRoom.id : chatRoom.agentId; diff --git a/lib/chat/getCorsHeaders.ts b/lib/chat/getCorsHeaders.ts index 7f6f4d249..8c0526c24 100644 --- a/lib/chat/getCorsHeaders.ts +++ b/lib/chat/getCorsHeaders.ts @@ -1,9 +1,11 @@ +/** + * + */ export function getCorsHeaders() { return { "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS", - "Access-Control-Allow-Headers": - "Content-Type, Authorization, X-Requested-With", + "Access-Control-Allow-Headers": "Content-Type, Authorization, X-Requested-With", "Access-Control-Allow-Credentials": "true", }; } diff --git a/lib/chat/handleChatCompletion.ts b/lib/chat/handleChatCompletion.ts index 07571c6ed..fb4b03810 100644 --- a/lib/chat/handleChatCompletion.ts +++ b/lib/chat/handleChatCompletion.ts @@ -12,9 +12,14 @@ import { ChatRequestBody } from "./validateChatRequest"; import { UIMessage } from "ai"; import { handleSendEmailToolOutputs } from "@/lib/emails/handleSendEmailToolOutputs"; +/** + * + * @param body + * @param responseMessages + */ export async function handleChatCompletion( body: ChatRequestBody, - responseMessages: UIMessage[] + responseMessages: UIMessage[], ): Promise { try { const { messages, roomId = "", accountId, artistId } = body; @@ -31,8 +36,7 @@ export async function handleChatCompletion( // Create room and send notification if this is a new conversation if (!room) { - const latestMessageText = - lastMessage.parts.find((part) => part.type === "text")?.text || ""; + const latestMessageText = lastMessage.parts.find(part => part.type === "text")?.text || ""; const conversationName = await generateChatTitle(latestMessageText); await Promise.all([ @@ -63,9 +67,7 @@ export async function handleChatCompletion( await createMemories({ id: responseMessages[responseMessages.length - 1].id, room_id: roomId, - content: filterMessageContentForMemories( - responseMessages[responseMessages.length - 1] - ), + content: filterMessageContentForMemories(responseMessages[responseMessages.length - 1]), }); await handleSendEmailToolOutputs(responseMessages); diff --git a/lib/chat/handleChatError.ts b/lib/chat/handleChatError.ts index d58454de0..76531801e 100644 --- a/lib/chat/handleChatError.ts +++ b/lib/chat/handleChatError.ts @@ -1,3 +1,7 @@ +/** + * + * @param error + */ export function handleChatError(error: unknown) { console.error("[Chat] Error processing request:", { error, @@ -14,6 +18,6 @@ export function handleChatError(error: unknown) { headers: { "Content-Type": "application/json", }, - } + }, ); } diff --git a/lib/chat/parseTextAttachments.ts b/lib/chat/parseTextAttachments.ts index 6a1b57dd3..48b28403e 100644 --- a/lib/chat/parseTextAttachments.ts +++ b/lib/chat/parseTextAttachments.ts @@ -1,10 +1,7 @@ import { TextAttachment } from "@/types/textAttachment"; // Uses Pick to extract only the properties we need, following DRY principle -export type ParsedTextAttachment = Pick< - TextAttachment, - "filename" | "type" | "lineCount" ->; +export type ParsedTextAttachment = Pick; export interface ParsedMessageContent { textAttachments: ParsedTextAttachment[]; @@ -17,6 +14,8 @@ const TEXT_ATTACHMENT_REGEX = /** * Parses message text to extract text file attachment sections. * Returns the extracted attachments and the remaining text content. + * + * @param text */ export function parseTextAttachments(text: string): ParsedMessageContent { const textAttachments: ParsedTextAttachment[] = []; @@ -43,7 +42,10 @@ export function parseTextAttachments(text: string): ParsedMessageContent { TEXT_ATTACHMENT_REGEX.lastIndex = 0; // Clean up extra whitespace from removed sections - remainingText = remainingText.replace(/^\n+/, "").replace(/\n{3,}/g, "\n\n").trim(); + remainingText = remainingText + .replace(/^\n+/, "") + .replace(/\n{3,}/g, "\n\n") + .trim(); return { textAttachments, remainingText }; } diff --git a/lib/chat/proxyToApiChat.ts b/lib/chat/proxyToApiChat.ts index 6c701759a..2c41b9a9e 100644 --- a/lib/chat/proxyToApiChat.ts +++ b/lib/chat/proxyToApiChat.ts @@ -9,6 +9,9 @@ export type ProxyOptions = { const SUNSET_DAYS = 90; +/** + * + */ function getDeprecationHeaders(): Record { const sunsetDate = new Date(); sunsetDate.setDate(sunsetDate.getDate() + SUNSET_DAYS); @@ -35,7 +38,7 @@ function getDeprecationHeaders(): Record { */ export async function proxyToApiChat( request: NextRequest, - options: ProxyOptions + options: ProxyOptions, ): Promise { const endpoint = options.streaming ? "/api/chat" : "/api/chat/generate"; const url = `${NEW_API_BASE_URL}${endpoint}`; diff --git a/lib/chat/validateChatRequest.ts b/lib/chat/validateChatRequest.ts index 9ab73e9c1..955b6e495 100644 --- a/lib/chat/validateChatRequest.ts +++ b/lib/chat/validateChatRequest.ts @@ -19,10 +19,8 @@ export const chatRequestSchema = z excludeTools: z.array(z.string()).optional(), }) .superRefine((data, ctx) => { - const hasMessages = - Array.isArray(data.messages) && data.messages.length > 0; - const hasPrompt = - typeof data.prompt === "string" && data.prompt.trim().length > 0; + const hasMessages = Array.isArray(data.messages) && data.messages.length > 0; + const hasPrompt = typeof data.prompt === "string" && data.prompt.trim().length > 0; if ((hasMessages && hasPrompt) || (!hasMessages && !hasPrompt)) { ctx.addIssue({ @@ -51,9 +49,11 @@ export type ChatRequestBody = BaseChatRequestBody & { * Mirrors the behavior of other API validators: returns * - Response (400/401/500) when invalid (body or headers) * - Parsed & augmented body when valid (including header-derived accountId) + * + * @param request */ export async function validateChatRequest( - request: NextRequest + request: NextRequest, ): Promise { const json = await request.json(); const validationResult = chatRequestSchema.safeParse(json); @@ -63,7 +63,7 @@ export async function validateChatRequest( { status: "error", message: "Invalid input", - errors: validationResult.error.issues.map((err) => ({ + errors: validationResult.error.issues.map(err => ({ field: err.path.join("."), message: err.message, })), @@ -71,12 +71,11 @@ export async function validateChatRequest( { status: 400, headers: getCorsHeaders(), - } + }, ); } - const validatedBody: BaseChatRequestBody & { accessToken?: string } = - validationResult.data; + const validatedBody: BaseChatRequestBody & { accessToken?: string } = validationResult.data; // If auth headers are present, resolve accountId via GET /api/accounts/id const headerValidationResult = await validateHeaders(request); @@ -91,11 +90,9 @@ export async function validateChatRequest( } const hasAccountId = - typeof validatedBody.accountId === "string" && - validatedBody.accountId.trim().length > 0; + typeof validatedBody.accountId === "string" && validatedBody.accountId.trim().length > 0; const hasAccessToken = - typeof validatedBody.accessToken === "string" && - validatedBody.accessToken.trim().length > 0; + typeof validatedBody.accessToken === "string" && validatedBody.accessToken.trim().length > 0; if (!hasAccountId || !hasAccessToken) { return NextResponse.json( @@ -106,18 +103,16 @@ export async function validateChatRequest( { status: 401, headers: getCorsHeaders(), - } + }, ); } // Normalize chat content: // - If messages are provided, keep them as-is // - If only prompt is provided, convert it into a single user UIMessage - const hasMessages = - Array.isArray(validatedBody.messages) && validatedBody.messages.length > 0; + const hasMessages = Array.isArray(validatedBody.messages) && validatedBody.messages.length > 0; const hasPrompt = - typeof validatedBody.prompt === "string" && - validatedBody.prompt.trim().length > 0; + typeof validatedBody.prompt === "string" && validatedBody.prompt.trim().length > 0; if (!hasMessages && hasPrompt) { validatedBody.messages = getMessages(validatedBody.prompt); diff --git a/lib/chat/validateHeaders.ts b/lib/chat/validateHeaders.ts index 979ab1bc2..d38d99a75 100644 --- a/lib/chat/validateHeaders.ts +++ b/lib/chat/validateHeaders.ts @@ -14,12 +14,13 @@ export type HeaderValidationResult = { * * If no auth headers are present, returns an empty result and does nothing. * On error, returns a Response to be sent directly from the route handler. + * + * @param request */ export async function validateHeaders( - request: NextRequest + request: NextRequest, ): Promise { - const hasAuthHeader = - request.headers.has("authorization") || request.headers.has("x-api-key"); + const hasAuthHeader = request.headers.has("authorization") || request.headers.has("x-api-key"); if (!hasAuthHeader) { return {}; diff --git a/lib/chat/validateMessages.ts b/lib/chat/validateMessages.ts index dcf3416e5..37fa2f38a 100644 --- a/lib/chat/validateMessages.ts +++ b/lib/chat/validateMessages.ts @@ -1,5 +1,9 @@ import { UIMessage } from "ai"; +/** + * + * @param messages + */ export function validateMessages(messages: UIMessage[]) { if (!messages.length) { throw new Error("No messages provided"); @@ -7,8 +11,6 @@ export function validateMessages(messages: UIMessage[]) { return { lastMessage: messages[messages.length - 1], - validMessages: messages.filter( - (m) => m.parts.find((part) => part.type === "text")?.text?.length - ), + validMessages: messages.filter(m => m.parts.find(part => part.type === "text")?.text?.length), }; } diff --git a/lib/chats/updateChat.ts b/lib/chats/updateChat.ts index c0edd2097..c9e49a037 100644 --- a/lib/chats/updateChat.ts +++ b/lib/chats/updateChat.ts @@ -18,6 +18,13 @@ export type UpdateChatResponse = { error?: string; }; +/** + * + * @param root0 + * @param root0.accessToken + * @param root0.chatId + * @param root0.topic + */ export async function updateChat({ accessToken, chatId, diff --git a/lib/coinbase/getCdpClient.ts b/lib/coinbase/getCdpClient.ts index 9fba73342..5f706d3f4 100644 --- a/lib/coinbase/getCdpClient.ts +++ b/lib/coinbase/getCdpClient.ts @@ -2,6 +2,9 @@ import { CdpClient } from "@coinbase/cdp-sdk"; let cdp: CdpClient | null = null; +/** + * + */ export function getCdpClient(): CdpClient { if (!cdp) { cdp = new CdpClient(); diff --git a/lib/coinbase/getOrCreatePurchaserAccount.ts b/lib/coinbase/getOrCreatePurchaserAccount.ts index b8eb65e84..7b513c72b 100644 --- a/lib/coinbase/getOrCreatePurchaserAccount.ts +++ b/lib/coinbase/getOrCreatePurchaserAccount.ts @@ -1,6 +1,9 @@ import { Account, toAccount } from "viem/accounts"; import { getCdpClient } from "./getCdpClient"; +/** + * + */ export async function getOrCreatePurchaserAccount(): Promise { const cdpClient = getCdpClient(); const account = await cdpClient.evm.getOrCreateAccount({ diff --git a/lib/composio/api/__tests__/authorizeConnectorApi.test.ts b/lib/composio/api/__tests__/authorizeConnectorApi.test.ts index c21f50406..97a631e6a 100644 --- a/lib/composio/api/__tests__/authorizeConnectorApi.test.ts +++ b/lib/composio/api/__tests__/authorizeConnectorApi.test.ts @@ -97,9 +97,9 @@ describe("authorizeConnectorApi", () => { status: 400, }); - await expect( - authorizeConnectorApi("test-token", { connector: "tiktok" }), - ).rejects.toThrow("Failed to authorize connector"); + await expect(authorizeConnectorApi("test-token", { connector: "tiktok" })).rejects.toThrow( + "Failed to authorize connector", + ); }); }); }); diff --git a/lib/composio/api/__tests__/disconnectConnectorApi.test.ts b/lib/composio/api/__tests__/disconnectConnectorApi.test.ts index abe156581..2df514467 100644 --- a/lib/composio/api/__tests__/disconnectConnectorApi.test.ts +++ b/lib/composio/api/__tests__/disconnectConnectorApi.test.ts @@ -67,17 +67,17 @@ describe("disconnectConnectorApi", () => { status: 403, }); - await expect( - disconnectConnectorApi("test-token", "conn-abc"), - ).rejects.toThrow("Failed to disconnect connector"); + await expect(disconnectConnectorApi("test-token", "conn-abc")).rejects.toThrow( + "Failed to disconnect connector", + ); }); it("throws when fetch fails", async () => { mockFetch.mockRejectedValueOnce(new Error("Network error")); - await expect( - disconnectConnectorApi("test-token", "conn-abc"), - ).rejects.toThrow("Network error"); + await expect(disconnectConnectorApi("test-token", "conn-abc")).rejects.toThrow( + "Network error", + ); }); }); }); diff --git a/lib/composio/api/__tests__/fetchConnectorsApi.test.ts b/lib/composio/api/__tests__/fetchConnectorsApi.test.ts index 471c4b0c6..f1dfd03cd 100644 --- a/lib/composio/api/__tests__/fetchConnectorsApi.test.ts +++ b/lib/composio/api/__tests__/fetchConnectorsApi.test.ts @@ -13,7 +13,12 @@ describe("fetchConnectorsApi", () => { describe("successful responses", () => { it("fetches connectors without account_id when none provided", async () => { const mockConnectors = [ - { slug: "googlesheets", name: "Google Sheets", isConnected: true, connectedAccountId: "abc" }, + { + slug: "googlesheets", + name: "Google Sheets", + isConnected: true, + connectedAccountId: "abc", + }, ]; mockFetch.mockResolvedValueOnce({ @@ -63,17 +68,13 @@ describe("fetchConnectorsApi", () => { status: 401, }); - await expect(fetchConnectorsApi("test-token")).rejects.toThrow( - "Failed to fetch connectors", - ); + await expect(fetchConnectorsApi("test-token")).rejects.toThrow("Failed to fetch connectors"); }); it("throws when fetch fails", async () => { mockFetch.mockRejectedValueOnce(new Error("Network error")); - await expect(fetchConnectorsApi("test-token")).rejects.toThrow( - "Network error", - ); + await expect(fetchConnectorsApi("test-token")).rejects.toThrow("Network error"); }); }); }); diff --git a/lib/composio/connectorMetadata.ts b/lib/composio/connectorMetadata.ts index 770018531..95ff669cf 100644 --- a/lib/composio/connectorMetadata.ts +++ b/lib/composio/connectorMetadata.ts @@ -20,6 +20,8 @@ export const connectorMetadata: Record = { /** * Get metadata for a connector by slug. * Returns default values if not found. + * + * @param slug */ export function getConnectorMeta(slug: string): ConnectorMeta { return ( diff --git a/lib/composio/findAuthResult.ts b/lib/composio/findAuthResult.ts index 126cd97ad..052bceae0 100644 --- a/lib/composio/findAuthResult.ts +++ b/lib/composio/findAuthResult.ts @@ -3,16 +3,14 @@ import { ComposioResultEntry } from "./types"; /** * Find the auth result entry that has redirect_url or active status. * Returns null if no valid auth result is found. + * + * @param results */ export function findAuthResult( - results: Record | undefined + results: Record | undefined, ): ComposioResultEntry | null { if (!results) return null; const entries = Object.values(results); - return ( - entries.find( - (r) => r.redirect_url || r.status?.toLowerCase() === "active" - ) || null - ); + return entries.find(r => r.redirect_url || r.status?.toLowerCase() === "active") || null; } diff --git a/lib/composio/formatConnectorName.ts b/lib/composio/formatConnectorName.ts index 24f31d6a7..a3d2786a3 100644 --- a/lib/composio/formatConnectorName.ts +++ b/lib/composio/formatConnectorName.ts @@ -12,6 +12,11 @@ const CONNECTOR_DISPLAY_NAMES: Record = { tiktok: "TikTok", }; +/** + * + * @param name + * @param slug + */ export function formatConnectorName(name: string, slug?: string): string { const key = (slug || name).toLowerCase(); @@ -22,6 +27,6 @@ export function formatConnectorName(name: string, slug?: string): string { // Fallback: capitalize and add spaces before capitals return name .replace(/([A-Z])/g, " $1") - .replace(/^./, (str) => str.toUpperCase()) + .replace(/^./, str => str.toUpperCase()) .trim(); } diff --git a/lib/composio/hasValidAuthData.ts b/lib/composio/hasValidAuthData.ts index b4c17bac5..9677ba92a 100644 --- a/lib/composio/hasValidAuthData.ts +++ b/lib/composio/hasValidAuthData.ts @@ -2,6 +2,8 @@ import { ComposioResultEntry } from "./types"; /** * Check if the result contains valid auth data. + * + * @param result */ export function hasValidAuthData(result: unknown): result is { data?: { results?: Record }; diff --git a/lib/consts.ts b/lib/consts.ts index ccd36201b..3981e270d 100644 --- a/lib/consts.ts +++ b/lib/consts.ts @@ -49,8 +49,7 @@ export const ROUTING_STATUS_DATA_TYPE = "data-agent-routing-status" as const; export const TITLE = "Recoupable"; -export const META_DESCRIPTION = - "Recoup helps artists build their own record label."; +export const META_DESCRIPTION = "Recoup helps artists build their own record label."; export const DEFAULT_CREDITS = 333; export const PRO_CREDITS = 1000; @@ -207,10 +206,7 @@ export const MERMAID_INSTRUCTIONS_PROMPT = ` \`\`\` `; -export const SUGGESTIONS = [ - "Create an artist.", - "Analyze my artists' TikTok account.", -]; +export const SUGGESTIONS = ["Create an artist.", "Analyze my artists' TikTok account."]; export const HTML_RESPONSE_FORMAT_INSTRUCTIONS = ` Please provide a wide range of HTML formats with embedded HTML tags such as
,

,

    ,
  • , and , along with CSS styles including font size, margin, and padding. @@ -1878,10 +1874,4 @@ Analyze the fan data to create highly specific niche-based segments that artists // EVALS export const EVAL_ACCOUNT_ID = "fb678396-a68f-4294-ae50-b8cacf9ce77b"; export const EVAL_ACCESS_TOKEN = process.env.EVAL_ACCESS_TOKEN || ""; -export const EVAL_ARTISTS = [ - "Gliiico", - "Mac Miller", - "Wiz Khalifa", - "Mod Sun", - "Julius Black", -]; +export const EVAL_ARTISTS = ["Gliiico", "Mac Miller", "Wiz Khalifa", "Mod Sun", "Julius Black"]; diff --git a/lib/consts/fileConstants.ts b/lib/consts/fileConstants.ts index 70955104b..79f3c4e5a 100644 --- a/lib/consts/fileConstants.ts +++ b/lib/consts/fileConstants.ts @@ -4,4 +4,3 @@ // Maximum file size for text file editing (10MB) export const MAX_TEXT_FILE_SIZE_BYTES = 10 * 1024 * 1024; // 10MB - diff --git a/lib/consts/fileExtensions.ts b/lib/consts/fileExtensions.ts index 2d41aaf60..e2228c718 100644 --- a/lib/consts/fileExtensions.ts +++ b/lib/consts/fileExtensions.ts @@ -13,5 +13,4 @@ export const TEXT_EXTENSIONS = [ ".yml", ] as const; -export type TextExtension = typeof TEXT_EXTENSIONS[number]; - +export type TextExtension = (typeof TEXT_EXTENSIONS)[number]; diff --git a/lib/copyMessagesClient.ts b/lib/copyMessagesClient.ts index f442a5a50..1af45a4be 100644 --- a/lib/copyMessagesClient.ts +++ b/lib/copyMessagesClient.ts @@ -1,12 +1,13 @@ /** * Client function to copy messages between rooms - * @param sourceRoomId ID of the source room to copy messages from - * @param targetRoomId ID of the target room to copy messages to + * + * @param sourceRoomId - ID of the source room to copy messages from + * @param targetRoomId - ID of the target room to copy messages to * @returns Promise resolving to a boolean indicating success */ export async function copyMessagesClient( sourceRoomId: string, - targetRoomId: string + targetRoomId: string, ): Promise { try { const response = await fetch("/api/memories/copy", { diff --git a/lib/credits/checkAndResetCredits.ts b/lib/credits/checkAndResetCredits.ts index 72502f5a7..ae9029cae 100644 --- a/lib/credits/checkAndResetCredits.ts +++ b/lib/credits/checkAndResetCredits.ts @@ -6,9 +6,7 @@ import isActiveSubscription from "../stripe/isActiveSubscription"; import { getActiveSubscriptionDetails } from "../stripe/getActiveSubscriptionDetails"; import { getOrgSubscription } from "../stripe/getOrgSubscription"; -export const checkAndResetCredits = async ( - accountId: string -): Promise => { +export const checkAndResetCredits = async (accountId: string): Promise => { const found = await selectCreditsUsage({ account_id: accountId }); if (!found || found.length === 0) return null; @@ -37,9 +35,7 @@ export const checkAndResetCredits = async ( activeSubscription?.current_period_start ?? activeSubscription?.start_date; const isMonthlyRefill = lastUpdatedCredits < oneMonthAgo; const hasActiveSubscription = isPro && subscriptionStartUnix; - const subscriptionStart = hasActiveSubscription - ? new Date(subscriptionStartUnix * 1000) - : null; + const subscriptionStart = hasActiveSubscription ? new Date(subscriptionStartUnix * 1000) : null; const isSubscriptionStartedAfterLastUpdate = subscriptionStart && lastUpdatedCredits < subscriptionStart; const isRefill = isMonthlyRefill || isSubscriptionStartedAfterLastUpdate; diff --git a/lib/cron/deriveSimpleModeFromParts.ts b/lib/cron/deriveSimpleModeFromParts.ts index 0d6607ca8..911414120 100644 --- a/lib/cron/deriveSimpleModeFromParts.ts +++ b/lib/cron/deriveSimpleModeFromParts.ts @@ -1,11 +1,6 @@ import { padTimePart } from "./padTimePart"; -export type SimpleModeFrequency = - | "hourly" - | "daily" - | "weekdays" - | "weekly" - | "monthly"; +export type SimpleModeFrequency = "hourly" | "daily" | "weekdays" | "weekly" | "monthly"; export interface SimpleModeSettings { frequency: SimpleModeFrequency; @@ -14,17 +9,10 @@ export interface SimpleModeSettings { dayOfMonth: string; } -export const deriveSimpleModeFromParts = ( - parts: string[] -): SimpleModeSettings | null => { +export const deriveSimpleModeFromParts = (parts: string[]): SimpleModeSettings | null => { const [minute, hour, dayOfMonth, , dayOfWeek] = parts; - if ( - minute === "0" && - hour === "*" && - dayOfMonth === "*" && - dayOfWeek === "*" - ) { + if (minute === "0" && hour === "*" && dayOfMonth === "*" && dayOfWeek === "*") { return { frequency: "hourly", time: "00:00", diff --git a/lib/cron/padTimePart.ts b/lib/cron/padTimePart.ts index 150f6e2ae..e4997b5ba 100644 --- a/lib/cron/padTimePart.ts +++ b/lib/cron/padTimePart.ts @@ -1,4 +1,3 @@ -export const padTimePart = (value: number): string => - value.toString().padStart(2, "0"); +export const padTimePart = (value: number): string => value.toString().padStart(2, "0"); export default padTimePart; diff --git a/lib/date/formatDate.ts b/lib/date/formatDate.ts index fd56c58e4..0f1aed712 100644 --- a/lib/date/formatDate.ts +++ b/lib/date/formatDate.ts @@ -1,5 +1,6 @@ /** * Format a date string to a human-readable format (e.g., "January 15, 2024") + * * @param dateString - ISO date string to format * @returns Formatted date string */ diff --git a/lib/email/generateTxtFileEmail.ts b/lib/email/generateTxtFileEmail.ts index 6e2635e26..2831ba661 100644 --- a/lib/email/generateTxtFileEmail.ts +++ b/lib/email/generateTxtFileEmail.ts @@ -5,8 +5,13 @@ import { getFetchableUrl } from "../arweave/gateway"; /** * Sends a Recoup Apify webhook email to a list of emails, summarizing the dataset and using a strong CTA. + * * @param dataset - Array of dataset objects (from Apify) + * @param dataset.rawTextFile * @param emails - Array of email addresses to send to + * @param dataset.arweaveFile + * @param dataset.emails + * @param dataset.conversationId * @returns The result of sendEmail */ export default async function generateTxtFileEmail({ diff --git a/lib/email/isTestEmail.ts b/lib/email/isTestEmail.ts index 2b5afe0ae..8a05a3525 100644 --- a/lib/email/isTestEmail.ts +++ b/lib/email/isTestEmail.ts @@ -1,6 +1,4 @@ // Returns true if the email is a test email export const isTestEmail = (email: string): boolean => { - return ( - email === "sweetmantech@gmail.com" || email === "sidney@recoupable.com" - ); + return email === "sweetmantech@gmail.com" || email === "sidney@recoupable.com"; }; diff --git a/lib/emails/__tests__/extractSendEmailResults.test.ts b/lib/emails/__tests__/extractSendEmailResults.test.ts index b57585910..f4e8f9720 100644 --- a/lib/emails/__tests__/extractSendEmailResults.test.ts +++ b/lib/emails/__tests__/extractSendEmailResults.test.ts @@ -14,10 +14,7 @@ const createMcpOutput = (data: { id?: string }) => ({ }); // Helper to create a send_email dynamic-tool part -const createSendEmailPart = ( - state: string, - output?: ReturnType -) => +const createSendEmailPart = (state: string, output?: ReturnType) => ({ type: "dynamic-tool", toolName: "send_email", diff --git a/lib/emails/extractSendEmailResults.ts b/lib/emails/extractSendEmailResults.ts index 7cd685316..ec4ac2aa2 100644 --- a/lib/emails/extractSendEmailResults.ts +++ b/lib/emails/extractSendEmailResults.ts @@ -1,8 +1,4 @@ -import { - getToolOrDynamicToolName, - isToolOrDynamicToolUIPart, - UIMessage, -} from "ai"; +import { getToolOrDynamicToolName, isToolOrDynamicToolUIPart, UIMessage } from "ai"; interface SendEmailResult { emailId: string; @@ -16,9 +12,7 @@ interface SendEmailResult { * @param responseMessages - The assistant messages from the chat response * @returns Array of send email results with emailId and messageId */ -export function extractSendEmailResults( - responseMessages: UIMessage[] -): SendEmailResult[] { +export function extractSendEmailResults(responseMessages: UIMessage[]): SendEmailResult[] { const results: SendEmailResult[] = []; for (const message of responseMessages) { diff --git a/lib/emails/handleSendEmailToolOutputs.ts b/lib/emails/handleSendEmailToolOutputs.ts index 98040ec2c..fe753c9b9 100644 --- a/lib/emails/handleSendEmailToolOutputs.ts +++ b/lib/emails/handleSendEmailToolOutputs.ts @@ -2,9 +2,11 @@ import { UIMessage } from "ai"; import { extractSendEmailResults } from "./extractSendEmailResults"; import { insertMemoryEmail } from "@/lib/supabase/memory_emails/insertMemoryEmail"; -export async function handleSendEmailToolOutputs( - responseMessages: UIMessage[] -): Promise { +/** + * + * @param responseMessages + */ +export async function handleSendEmailToolOutputs(responseMessages: UIMessage[]): Promise { const emailResults = extractSendEmailResults(responseMessages); if (emailResults.length === 0) return; @@ -14,7 +16,7 @@ export async function handleSendEmailToolOutputs( email_id: emailId, memory: messageId, message_id: messageId, - }) - ) + }), + ), ); } diff --git a/lib/errors/serializeError.ts b/lib/errors/serializeError.ts index 2d8ec1262..e517bb8e5 100644 --- a/lib/errors/serializeError.ts +++ b/lib/errors/serializeError.ts @@ -10,6 +10,8 @@ export interface SerializedError { /** * Extracts serializable properties from error objects * Ensures errors can be properly JSON serialized + * + * @param error */ export function serializeError(error: unknown): SerializedError { if (error instanceof Error) { diff --git a/lib/files/checkFileAccess.ts b/lib/files/checkFileAccess.ts index a59e82138..0205a9d9a 100644 --- a/lib/files/checkFileAccess.ts +++ b/lib/files/checkFileAccess.ts @@ -2,18 +2,20 @@ import { checkAccountArtistAccess } from "@/lib/supabase/account_artist_ids/chec /** * Check if a user has access to a file - * + * * Access is granted if: * - User is the file owner, OR * - User has access to the artist that owns the file - * + * * @param userId - The user's account ID * @param file - File record with owner_account_id and artist_account_id + * @param file.owner_account_id + * @param file.artist_account_id * @returns true if user has access, false otherwise */ export async function checkFileAccess( userId: string, - file: { owner_account_id: string; artist_account_id: string } + file: { owner_account_id: string; artist_account_id: string }, ): Promise { // User owns the file directly if (file.owner_account_id === userId) { diff --git a/lib/files/escapeLikePattern.ts b/lib/files/escapeLikePattern.ts index 23f52520d..a2f3d479c 100644 --- a/lib/files/escapeLikePattern.ts +++ b/lib/files/escapeLikePattern.ts @@ -1,11 +1,9 @@ /** * Escape special LIKE wildcard characters in SQL patterns to prevent injection * Handles: backslash (\), percent (%), underscore (_) + * + * @param input */ export function escapeLikePattern(input: string): string { - return input - .replace(/\\/g, '\\\\') - .replace(/%/g, '\\%') - .replace(/_/g, '\\_'); + return input.replace(/\\/g, "\\\\").replace(/%/g, "\\%").replace(/_/g, "\\_"); } - diff --git a/lib/files/fetchFileContent.ts b/lib/files/fetchFileContent.ts index e47b91ffc..ce8f4bad0 100644 --- a/lib/files/fetchFileContent.ts +++ b/lib/files/fetchFileContent.ts @@ -1,20 +1,20 @@ /** * Fetches file content from storage by getting a signed URL and then fetching the content + * * @param storageKey - The storage key of the file to fetch * @param accountId - The account ID of the user requesting access */ export async function fetchFileContent(storageKey: string, accountId: string): Promise { const apiUrl = `/api/files/get-signed-url?key=${encodeURIComponent(storageKey)}&accountId=${encodeURIComponent(accountId)}`; const response = await fetch(apiUrl); - + if (!response.ok) throw new Error("Failed to get signed URL"); - + const { signedUrl } = await response.json(); if (!signedUrl) throw new Error("No signed URL returned"); - + const contentResponse = await fetch(signedUrl); if (!contentResponse.ok) throw new Error("Failed to fetch content"); - + return contentResponse.text(); } - diff --git a/lib/files/filterFilesByPath.ts b/lib/files/filterFilesByPath.ts index ea26827ab..125db01a4 100644 --- a/lib/files/filterFilesByPath.ts +++ b/lib/files/filterFilesByPath.ts @@ -4,48 +4,42 @@ type FileRecord = Tables<"files">; /** * Filter files by path to get immediate children only - * + * * This is opinionated business logic that: * - Extracts relative paths from storage keys * - Filters to immediate children of a directory (no nested files) * - Handles trailing slashes consistently - * + * * @param files - Array of file records to filter * @param path - Optional subdirectory path to filter by (e.g., 'reports', 'research/data') * @returns Filtered array containing only immediate children */ -export function filterFilesByPath( - files: FileRecord[], - path?: string -): FileRecord[] { - return files.filter((file) => { +export function filterFilesByPath(files: FileRecord[], path?: string): FileRecord[] { + return files.filter(file => { // Extract relative path from storage key pattern: files/{artistId}/{accountId}/{relativePath} const match = file.storage_key.match(/^files\/[^\/]+\/[^\/]+\/(.+)$/); if (!match) return false; - + const relativePath = match[1]; - + // If a path filter is specified, check if file is in that directory if (path) { - const pathPrefix = path.endsWith('/') ? path : path + '/'; + const pathPrefix = path.endsWith("/") ? path : path + "/"; if (!relativePath.startsWith(pathPrefix)) return false; - + // Get the path relative to the filter const relativeToFilter = relativePath.slice(pathPrefix.length); - const trimmed = relativeToFilter.endsWith("/") - ? relativeToFilter.slice(0, -1) + const trimmed = relativeToFilter.endsWith("/") + ? relativeToFilter.slice(0, -1) : relativeToFilter; - + // Only include immediate children (no nested paths) return trimmed.length > 0 && !trimmed.includes("/"); } - + // No path filter: only include root-level files - const trimmed = relativePath.endsWith("/") - ? relativePath.slice(0, -1) - : relativePath; - + const trimmed = relativePath.endsWith("/") ? relativePath.slice(0, -1) : relativePath; + return trimmed.length > 0 && !trimmed.includes("/"); }); } - diff --git a/lib/files/generateStoragePath.ts b/lib/files/generateStoragePath.ts index c55ed3bd8..037d1e04b 100644 --- a/lib/files/generateStoragePath.ts +++ b/lib/files/generateStoragePath.ts @@ -1,34 +1,38 @@ /** * Generate storage path for files in the format: * files/{account_id}/{artist_id}/{optional_path}/ - * + * * Normalizes path by removing leading/trailing slashes + * + * @param accountId + * @param artistId + * @param path */ -export function generateStoragePath( - accountId: string, - artistId: string, - path?: string -): string { +export function generateStoragePath(accountId: string, artistId: string, path?: string): string { const baseStoragePath = `files/${accountId}/${artistId}`; - + if (!path) { return `${baseStoragePath}/`; } - - const normalizedPath = path.replace(/^\/+|\/+$/g, ''); + + const normalizedPath = path.replace(/^\/+|\/+$/g, ""); return `${baseStoragePath}/${normalizedPath}/`; } /** * Generate full storage key including filename + * + * @param accountId + * @param artistId + * @param fileName + * @param path */ export function generateStorageKey( accountId: string, artistId: string, fileName: string, - path?: string + path?: string, ): string { const storagePath = generateStoragePath(accountId, artistId, path); return `${storagePath}${fileName}`; } - diff --git a/lib/files/getFileExtension.ts b/lib/files/getFileExtension.ts index 4eae4c8a8..7ce841adc 100644 --- a/lib/files/getFileExtension.ts +++ b/lib/files/getFileExtension.ts @@ -1,10 +1,11 @@ /** * Extract file extension from filename (e.g., "report.pdf" -> ".pdf") * Returns empty string if no extension found + * + * @param fileName */ export function getFileExtension(fileName: string): string { - const lastDot = fileName.lastIndexOf('.'); - if (lastDot === -1 || lastDot === 0) return ''; + const lastDot = fileName.lastIndexOf("."); + if (lastDot === -1 || lastDot === 0) return ""; return fileName.slice(lastDot); } - diff --git a/lib/files/getKnowledgeBaseText.ts b/lib/files/getKnowledgeBaseText.ts index 3a73db98c..5ad6a8eae 100644 --- a/lib/files/getKnowledgeBaseText.ts +++ b/lib/files/getKnowledgeBaseText.ts @@ -7,26 +7,19 @@ import type { Knowledge } from "@/lib/supabase/artist/updateArtistProfile"; * @param knowledges - Array of knowledge base entries with name, url, and type properties * @returns Combined text content from all text-based knowledge files, or undefined if no valid files found */ -export async function getKnowledgeBaseText( - knowledges: unknown -): Promise { +export async function getKnowledgeBaseText(knowledges: unknown): Promise { if (!knowledges || !Array.isArray(knowledges) || knowledges.length === 0) { return undefined; } - const textTypes = new Set([ - "text/plain", - "text/markdown", - "application/json", - "text/csv", - ]); + const textTypes = new Set(["text/plain", "text/markdown", "application/json", "text/csv"]); const knowledgeFiles = knowledges as Knowledge[]; const texts = await Promise.all( knowledgeFiles - .filter((f) => f.type && textTypes.has(f.type) && f.url) - .map(async (f) => { + .filter(f => f.type && textTypes.has(f.type) && f.url) + .map(async f => { try { const res = await fetch(f.url!); if (!res.ok) return ""; @@ -35,7 +28,7 @@ export async function getKnowledgeBaseText( } catch { return ""; } - }) + }), ); const combinedText = texts.filter(Boolean).join("\n\n"); diff --git a/lib/files/handleToolError.ts b/lib/files/handleToolError.ts index 0246bc880..47ee527cd 100644 --- a/lib/files/handleToolError.ts +++ b/lib/files/handleToolError.ts @@ -1,18 +1,21 @@ /** * Standardized error handler for file management tools * Returns consistent error response structure + * + * @param error + * @param context + * @param itemName */ export function handleToolError( error: unknown, context: string, - itemName?: string + itemName?: string, ): { success: false; error: string; message: string; } { - const errorMessage = - error instanceof Error ? error.message : "An unexpected error occurred"; + const errorMessage = error instanceof Error ? error.message : "An unexpected error occurred"; const message = itemName ? `Failed to ${context} '${itemName}': ${errorMessage}` @@ -24,4 +27,3 @@ export function handleToolError( message, }; } - diff --git a/lib/files/isAllowedByExtension.ts b/lib/files/isAllowedByExtension.ts index 96701c40d..8b39d96b5 100644 --- a/lib/files/isAllowedByExtension.ts +++ b/lib/files/isAllowedByExtension.ts @@ -4,7 +4,7 @@ import { getFileExtension } from "./getFileExtension"; const allowedExtensions = new Set( Object.values(CHAT_INPUT_SUPPORTED_FILE) .flat() - .map((extension: string) => extension.toLowerCase()) + .map((extension: string) => extension.toLowerCase()), ); /** diff --git a/lib/files/normalizeFileName.ts b/lib/files/normalizeFileName.ts index f4c2b4e00..2dd7b315a 100644 --- a/lib/files/normalizeFileName.ts +++ b/lib/files/normalizeFileName.ts @@ -2,10 +2,10 @@ * Normalize filename by adding .md extension if no extension is present * Ensures consistency between file creation and file lookup operations * Dotfiles (hidden files starting with '.') are returned unchanged - * + * * @param fileName - Original filename provided by user * @returns Normalized filename with extension - * + * * @example * normalizeFileName("notes") => "notes.md" * normalizeFileName("report.pdf") => "report.pdf" @@ -14,16 +14,15 @@ */ export function normalizeFileName(fileName: string): string { // Guard: return dotfiles (hidden files) unchanged - if (fileName.startsWith('.')) { + if (fileName.startsWith(".")) { return fileName; } - - const hasExtension = fileName.includes('.') && fileName.lastIndexOf('.') > 0; - + + const hasExtension = fileName.includes(".") && fileName.lastIndexOf(".") > 0; + if (!hasExtension) { return `${fileName}.md`; } - + return fileName; } - diff --git a/lib/files/updateFileContent.ts b/lib/files/updateFileContent.ts index 3549ca18d..deb4351e6 100644 --- a/lib/files/updateFileContent.ts +++ b/lib/files/updateFileContent.ts @@ -15,9 +15,11 @@ export type UpdateFileResponse = { sizeBytes: number; }; -export async function updateFileContent( - params: UpdateFileParams -): Promise { +/** + * + * @param params + */ +export async function updateFileContent(params: UpdateFileParams): Promise { const response = await fetch("/api/files/update", { method: "POST", headers: { @@ -33,4 +35,3 @@ export async function updateFileContent( return response.json(); } - diff --git a/lib/generateAndProcessImage.ts b/lib/generateAndProcessImage.ts index 8dacee4a6..7cdd75cb0 100644 --- a/lib/generateAndProcessImage.ts +++ b/lib/generateAndProcessImage.ts @@ -22,10 +22,16 @@ interface FileInput { type: string; } +/** + * + * @param prompt + * @param accountId + * @param files + */ export async function generateAndProcessImage( prompt: string, accountId: string, - files?: FileInput[] + files?: FileInput[], ): Promise { if (!prompt) { throw new Error("Prompt is required"); @@ -41,9 +47,7 @@ export async function generateAndProcessImage( // Format files parameter: files=url1:type1|url2:type2 if (files && files.length > 0) { - const filesParam = files - .map((file) => `${file.url}:${file.type}`) - .join("|"); + const filesParam = files.map(file => `${file.url}:${file.type}`).join("|"); apiUrl.searchParams.set("files", filesParam); } diff --git a/lib/generateUUID.ts b/lib/generateUUID.ts index fe9f08996..99f5513ed 100644 --- a/lib/generateUUID.ts +++ b/lib/generateUUID.ts @@ -12,7 +12,7 @@ export function generateUUID(): string { } // Fallback implementation for older browsers or Node.js environments - return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => { + return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, c => { const r = (Math.random() * 16) | 0; const v = c === "x" ? r : (r & 0x3) | 0x8; return v.toString(16); diff --git a/lib/handleDailyStats.ts b/lib/handleDailyStats.ts index 2136b3e7a..622b386c7 100644 --- a/lib/handleDailyStats.ts +++ b/lib/handleDailyStats.ts @@ -29,9 +29,7 @@ export async function handleDailyStats(): Promise { const newRoomsCount = rooms.length; const prevRoomsCount = prevRooms.length; const roomsDelta = - prevRoomsCount === 0 - ? 0 - : ((newRoomsCount - prevRoomsCount) / prevRoomsCount) * 100; + prevRoomsCount === 0 ? 0 : ((newRoomsCount - prevRoomsCount) / prevRoomsCount) * 100; const newMemoriesCount = memories.length; const prevMemoriesCount = prevMemories.length; const memoriesDelta = diff --git a/lib/ipfs/hash.ts b/lib/ipfs/hash.ts index d4a62c7de..98d03f9d2 100644 --- a/lib/ipfs/hash.ts +++ b/lib/ipfs/hash.ts @@ -1,5 +1,9 @@ import * as crypto from "crypto"; +/** + * + * @param files + */ export function hashFiles(files: File[]): string { const hash = crypto.createHash("sha256"); for (const file of files) { diff --git a/lib/keys/createApiKey.ts b/lib/keys/createApiKey.ts index 4140a3b88..ce8450221 100644 --- a/lib/keys/createApiKey.ts +++ b/lib/keys/createApiKey.ts @@ -2,6 +2,7 @@ import { NEW_API_BASE_URL } from "@/lib/consts"; /** * Create a new API key for the authenticated account or organization + * * @param keyName - The name for the API key * @param accessToken - The access token for authentication * @param organizationId - Optional organization ID to create the key for @@ -10,7 +11,7 @@ import { NEW_API_BASE_URL } from "@/lib/consts"; export async function createApiKey( keyName: string, accessToken: string, - organizationId?: string | null + organizationId?: string | null, ): Promise { const body: { key_name: string; organizationId?: string } = { key_name: keyName, diff --git a/lib/keys/deleteApiKey.ts b/lib/keys/deleteApiKey.ts index 2611139d6..943ba931d 100644 --- a/lib/keys/deleteApiKey.ts +++ b/lib/keys/deleteApiKey.ts @@ -2,14 +2,12 @@ import { NEW_API_BASE_URL } from "@/lib/consts"; /** * Delete an API key + * * @param keyId - The ID of the API key to delete * @param accessToken - The access token for authentication * @returns Promise with the deletion result */ -export async function deleteApiKey( - keyId: string, - accessToken: string -): Promise { +export async function deleteApiKey(keyId: string, accessToken: string): Promise { const response = await fetch(`${NEW_API_BASE_URL}/api/keys`, { method: "DELETE", headers: { diff --git a/lib/keys/fetchApiKeys.ts b/lib/keys/fetchApiKeys.ts index 4ba2daed9..b7b5fbe70 100644 --- a/lib/keys/fetchApiKeys.ts +++ b/lib/keys/fetchApiKeys.ts @@ -5,13 +5,14 @@ export type ApiKey = Tables<"account_api_keys">; /** * Fetch API keys for the authenticated account or organization + * * @param accessToken - The access token for authentication * @param organizationId - Optional organization ID to fetch keys for * @returns Promise with the list of API keys */ export async function fetchApiKeys( accessToken: string, - organizationId?: string | null + organizationId?: string | null, ): Promise { const url = new URL(`${NEW_API_BASE_URL}/api/keys`); if (organizationId) { diff --git a/lib/messages/clientDeleteTrailingMessages.ts b/lib/messages/clientDeleteTrailingMessages.ts index 23800f0af..17bd3e6bd 100644 --- a/lib/messages/clientDeleteTrailingMessages.ts +++ b/lib/messages/clientDeleteTrailingMessages.ts @@ -1,19 +1,14 @@ /** * Client-side function to delete trailing messages after a specific memory - * @param id The ID of the memory to delete messages after + * + * @param id.id + * @param id - The ID of the memory to delete messages after * @returns A promise that resolves when the operation is complete */ -export async function clientDeleteTrailingMessages({ - id, -}: { - id: string; -}): Promise { - const response = await fetch( - `/api/memories/delete-trailing?id=${encodeURIComponent(id)}`, - { - method: "DELETE", - } - ); +export async function clientDeleteTrailingMessages({ id }: { id: string }): Promise { + const response = await fetch(`/api/memories/delete-trailing?id=${encodeURIComponent(id)}`, { + method: "DELETE", + }); if (!response.ok) { const errorData = await response.json(); diff --git a/lib/messages/deleteTrailingMessages.ts b/lib/messages/deleteTrailingMessages.ts index 17bcc9390..29139b3db 100644 --- a/lib/messages/deleteTrailingMessages.ts +++ b/lib/messages/deleteTrailingMessages.ts @@ -1,6 +1,11 @@ import { deleteMemoriesByRoomIdAfterTimestamp } from "../supabase/deleteMemoriesByChatIdAfterTimestamp"; import { getMemoryById } from "../supabase/getMemoryById"; +/** + * + * @param root0 + * @param root0.id + */ export async function deleteTrailingMessages({ id }: { id: string }) { const memory = await getMemoryById({ id }); diff --git a/lib/messages/filterMessageContentForMemories.ts b/lib/messages/filterMessageContentForMemories.ts index 9ac0f7cf5..3b46440ed 100644 --- a/lib/messages/filterMessageContentForMemories.ts +++ b/lib/messages/filterMessageContentForMemories.ts @@ -4,7 +4,7 @@ const filterMessageContentForMemories = (message: UIMessage) => { return { role: message.role, parts: message.parts, - content: message.parts.filter((part) => part.type === "text").join(""), + content: message.parts.filter(part => part.type === "text").join(""), }; }; diff --git a/lib/messages/getEarliestFailedUserMessageId.ts b/lib/messages/getEarliestFailedUserMessageId.ts index 051ab0027..33ec30f1b 100644 --- a/lib/messages/getEarliestFailedUserMessageId.ts +++ b/lib/messages/getEarliestFailedUserMessageId.ts @@ -1,8 +1,6 @@ import { isToolUIPart, UIMessage } from "ai"; -const getEarliestFailedUserMessageId = ( - messages: UIMessage[] -): string | null => { +const getEarliestFailedUserMessageId = (messages: UIMessage[]): string | null => { if (!messages || messages.length === 0) { return null; } @@ -21,15 +19,11 @@ const getEarliestFailedUserMessageId = ( const isLastMessage = i === messages.length - 1; const isNextMessageUser = messages[i + 1]?.role === "user"; - const hasNoAssistantMessages = !messages.some( - (msg) => msg.role === "assistant" - ); + const hasNoAssistantMessages = !messages.some(msg => msg.role === "assistant"); if (isLastMessage || isNextMessageUser) { if (hasNoAssistantMessages) { // Return the earliest user message if no assistant messages exist - const earliestUserMessage = messages.find( - (msg) => msg.role === "user" - ); + const earliestUserMessage = messages.find(msg => msg.role === "user"); return earliestUserMessage?.id || null; } } @@ -38,22 +32,16 @@ const getEarliestFailedUserMessageId = ( // For assistant messages, check if it's successful if (currentMessage.role === "assistant") { - const isContentEmpty = !currentMessage.parts - .filter((part) => part.type === "text") - .join(""); + const isContentEmpty = !currentMessage.parts.filter(part => part.type === "text").join(""); if (isContentEmpty) { return earliestUserMessageSinceLastSuccess; } // Check if all tool invocations in parts have state: "result" - const toolParts = currentMessage.parts?.filter((part) => - isToolUIPart(part) - ); + const toolParts = currentMessage.parts?.filter(part => isToolUIPart(part)); if (toolParts && toolParts.length > 0) { - const allToolsSuccessful = toolParts.every( - (part) => part.state === "output-available" - ); + const allToolsSuccessful = toolParts.every(part => part.state === "output-available"); if (!allToolsSuccessful) { return earliestUserMessageSinceLastSuccess; } diff --git a/lib/messages/getLatestUserMessageText.ts b/lib/messages/getLatestUserMessageText.ts index 830f20c8b..54868e7e5 100644 --- a/lib/messages/getLatestUserMessageText.ts +++ b/lib/messages/getLatestUserMessageText.ts @@ -1,11 +1,11 @@ import { UIMessage } from "ai"; -export default function getLatestUserMessageText( - messages: UIMessage[] -): string { - const userMessages = messages.filter((msg) => msg.role === "user"); +/** + * + * @param messages + */ +export default function getLatestUserMessageText(messages: UIMessage[]): string { + const userMessages = messages.filter(msg => msg.role === "user"); const latestUserMessage = userMessages[userMessages.length - 1]; - return ( - latestUserMessage?.parts.find((part) => part.type === "text")?.text || "" - ); + return latestUserMessage?.parts.find(part => part.type === "text")?.text || ""; } diff --git a/lib/messages/getMessages.ts b/lib/messages/getMessages.ts index 078faa37e..dec3bb6b6 100644 --- a/lib/messages/getMessages.ts +++ b/lib/messages/getMessages.ts @@ -5,7 +5,7 @@ import generateUUID from "@/lib/generateUUID"; * Converts a string message to an array of properly formatted message objects * Can be used to generate initial messages for chat components * - * @param content The text content of the message + * @param content - The text content of the message * @returns An array of properly formatted message objects */ export function getMessages(content?: string): UIMessage[] { diff --git a/lib/organizations/assignAccountToOrg.ts b/lib/organizations/assignAccountToOrg.ts index 92f99fcc4..a283b873f 100644 --- a/lib/organizations/assignAccountToOrg.ts +++ b/lib/organizations/assignAccountToOrg.ts @@ -10,10 +10,7 @@ import addAccountToOrganization from "@/lib/supabase/account_organization_ids/ad * @param email - The account's email address * @returns The org ID if assigned, null otherwise */ -export async function assignAccountToOrg( - accountId: string, - email: string -): Promise { +export async function assignAccountToOrg(accountId: string, email: string): Promise { if (!accountId || !email) return null; const domain = extractDomain(email); @@ -27,4 +24,3 @@ export async function assignAccountToOrg( } export default assignAccountToOrg; - diff --git a/lib/organizations/formatAccountOrganizations.ts b/lib/organizations/formatAccountOrganizations.ts index 65c91a9e1..95c9dd214 100644 --- a/lib/organizations/formatAccountOrganizations.ts +++ b/lib/organizations/formatAccountOrganizations.ts @@ -10,23 +10,24 @@ export interface FormattedOrganization { /** * Formats raw account organizations into a flat structure for the frontend. * Deduplicates by organization_id. + * + * @param rawOrgs */ export function formatAccountOrganizations( - rawOrgs: AccountOrganization[] + rawOrgs: AccountOrganization[], ): FormattedOrganization[] { const seen = new Set(); return rawOrgs - .filter((org) => { + .filter(org => { if (!org.organization_id || seen.has(org.organization_id)) return false; seen.add(org.organization_id); return true; }) - .map((org) => ({ + .map(org => ({ id: org.id, organization_id: org.organization_id, organization_name: org.organization?.name || null, organization_image: org.organization?.account_info?.[0]?.image || null, })); } - diff --git a/lib/perplexity/config.ts b/lib/perplexity/config.ts index 9025e8eb7..38ffaa487 100644 --- a/lib/perplexity/config.ts +++ b/lib/perplexity/config.ts @@ -1,22 +1,28 @@ export const PERPLEXITY_BASE_URL = "https://api.perplexity.ai"; +/** + * + */ export function getPerplexityApiKey(): string { const apiKey = process.env.PERPLEXITY_API_KEY; - + if (!apiKey) { throw new Error( "PERPLEXITY_API_KEY environment variable is not set. " + - "Please add it to your environment variables." + "Please add it to your environment variables.", ); } - + return apiKey; } +/** + * + * @param apiKey + */ export function getPerplexityHeaders(apiKey: string): HeadersInit { return { "Content-Type": "application/json", Authorization: `Bearer ${apiKey}`, }; } - diff --git a/lib/perplexity/fetchPerplexityApi.ts b/lib/perplexity/fetchPerplexityApi.ts index a3790b11d..70eb09c4d 100644 --- a/lib/perplexity/fetchPerplexityApi.ts +++ b/lib/perplexity/fetchPerplexityApi.ts @@ -3,11 +3,11 @@ import { getPerplexityApiKey, getPerplexityHeaders, PERPLEXITY_BASE_URL } from " const fetchPerplexityApi = async ( messages: Array<{ role: string; content: string }>, - model: string = "sonar-pro" + model: string = "sonar-pro", ): Promise => { const apiKey = getPerplexityApiKey(); const url = `${PERPLEXITY_BASE_URL}/chat/completions`; - + const body = { model, messages, diff --git a/lib/perplexity/formatSearchResultsAsMarkdown.ts b/lib/perplexity/formatSearchResultsAsMarkdown.ts index 9d76114c6..64cf84d9d 100644 --- a/lib/perplexity/formatSearchResultsAsMarkdown.ts +++ b/lib/perplexity/formatSearchResultsAsMarkdown.ts @@ -1,8 +1,12 @@ import { SearchResponse } from "./searchApi"; +/** + * + * @param response + */ export function formatSearchResultsAsMarkdown(response: SearchResponse): string { const { results } = response; - + if (results.length === 0) { return "No search results found."; } @@ -12,15 +16,14 @@ export function formatSearchResultsAsMarkdown(response: SearchResponse): string results.forEach((result, index) => { formatted += `### ${index + 1}. ${result.title}\n\n`; formatted += `**URL:** ${result.url}\n\n`; - + if (result.date) { formatted += `**Published:** ${result.date}\n\n`; } - + formatted += `${result.snippet}\n\n`; formatted += `---\n\n`; }); return formatted; } - diff --git a/lib/perplexity/searchApi.ts b/lib/perplexity/searchApi.ts index 40b70a2e9..64bfa7021 100644 --- a/lib/perplexity/searchApi.ts +++ b/lib/perplexity/searchApi.ts @@ -21,9 +21,11 @@ export interface SearchParams { search_domain_filter?: string[]; } -export async function searchPerplexity( - params: SearchParams -): Promise { +/** + * + * @param params + */ +export async function searchPerplexity(params: SearchParams): Promise { const apiKey = getPerplexityApiKey(); const url = `${PERPLEXITY_BASE_URL}/search`; @@ -32,8 +34,8 @@ export async function searchPerplexity( max_results: params.max_results || 10, max_tokens_per_page: params.max_tokens_per_page || 1024, ...(params.country && { country: params.country }), - ...(params.search_domain_filter && { - search_domain_filter: params.search_domain_filter + ...(params.search_domain_filter && { + search_domain_filter: params.search_domain_filter, }), }; @@ -47,7 +49,7 @@ export async function searchPerplexity( if (!response.ok) { const errorText = await response.text(); throw new Error( - `Perplexity Search API error: ${response.status} ${response.statusText}\n${errorText}` + `Perplexity Search API error: ${response.status} ${response.statusText}\n${errorText}`, ); } @@ -56,4 +58,3 @@ export async function searchPerplexity( throw new Error(`Failed to search Perplexity API: ${error}`); } } - diff --git a/lib/perplexity/streamChatCompletion.ts b/lib/perplexity/streamChatCompletion.ts index 453450049..555f274f3 100644 --- a/lib/perplexity/streamChatCompletion.ts +++ b/lib/perplexity/streamChatCompletion.ts @@ -1,13 +1,14 @@ import streamPerplexityApi from "./streamPerplexityApi"; -import { - PerplexityMessage, - PerplexityStreamChunk, - StreamedResponse, -} from "./types"; +import { PerplexityMessage, PerplexityStreamChunk, StreamedResponse } from "./types"; +/** + * + * @param messages + * @param model + */ async function* streamChatCompletion( messages: PerplexityMessage[], - model: string = "sonar-pro" + model: string = "sonar-pro", ): AsyncGenerator { const response = await streamPerplexityApi(messages, model); @@ -30,7 +31,7 @@ async function* streamChatCompletion( if (done) break; buffer += decoder.decode(value, { stream: true }); - + const lines = buffer.split("\n"); buffer = lines.pop() || ""; @@ -74,4 +75,3 @@ async function* streamChatCompletion( } export default streamChatCompletion; - diff --git a/lib/perplexity/streamPerplexityApi.ts b/lib/perplexity/streamPerplexityApi.ts index d1c82c39c..1a90ca9f4 100644 --- a/lib/perplexity/streamPerplexityApi.ts +++ b/lib/perplexity/streamPerplexityApi.ts @@ -3,11 +3,11 @@ import { getPerplexityApiKey, getPerplexityHeaders, PERPLEXITY_BASE_URL } from " const streamPerplexityApi = async ( messages: PerplexityMessage[], - model: string = "sonar-pro" + model: string = "sonar-pro", ): Promise => { const apiKey = getPerplexityApiKey(); const url = `${PERPLEXITY_BASE_URL}/chat/completions`; - + const body = { model, messages, @@ -24,7 +24,7 @@ const streamPerplexityApi = async ( if (!response.ok) { const errorText = await response.text(); throw new Error( - `Perplexity API error: ${response.status} ${response.statusText}\n${errorText}` + `Perplexity API error: ${response.status} ${response.statusText}\n${errorText}`, ); } @@ -35,4 +35,3 @@ const streamPerplexityApi = async ( }; export default streamPerplexityApi; - diff --git a/lib/perplexity/types.ts b/lib/perplexity/types.ts index 7e606497c..1fb590b10 100644 --- a/lib/perplexity/types.ts +++ b/lib/perplexity/types.ts @@ -35,4 +35,3 @@ export interface StreamedResponse { searchResults: SearchResult[]; citations: string[]; } - diff --git a/lib/polyfills/base64.ts b/lib/polyfills/base64.ts index 91a9b3615..5846341a7 100644 --- a/lib/polyfills/base64.ts +++ b/lib/polyfills/base64.ts @@ -13,5 +13,3 @@ if (typeof g.btoa === "undefined") { } export {}; // side-effect only - - diff --git a/lib/prompts/getSystemPrompt.ts b/lib/prompts/getSystemPrompt.ts index 49fef8f31..77e9c88bc 100644 --- a/lib/prompts/getSystemPrompt.ts +++ b/lib/prompts/getSystemPrompt.ts @@ -1,6 +1,18 @@ import { SYSTEM_PROMPT } from "@/lib/consts"; import getUserInfo from "../supabase/getUserInfo"; +/** + * + * @param root0 + * @param root0.roomId + * @param root0.artistId + * @param root0.accountId + * @param root0.organizationId + * @param root0.email + * @param root0.knowledgeBaseText + * @param root0.artistInstruction + * @param root0.conversationName + */ export async function getSystemPrompt({ roomId, artistId, diff --git a/lib/pulse/getPulse.ts b/lib/pulse/getPulse.ts index b710f55ef..e20fd348b 100644 --- a/lib/pulse/getPulse.ts +++ b/lib/pulse/getPulse.ts @@ -17,9 +17,12 @@ export type GetPulseParams = { accessToken: string; }; -export async function getPulse({ - accessToken, -}: GetPulseParams): Promise { +/** + * + * @param root0 + * @param root0.accessToken + */ +export async function getPulse({ accessToken }: GetPulseParams): Promise { const response = await fetch(PULSE_API_URL, { method: "GET", headers: { diff --git a/lib/pulse/updatePulse.ts b/lib/pulse/updatePulse.ts index 3d6aac049..4815a2b7a 100644 --- a/lib/pulse/updatePulse.ts +++ b/lib/pulse/updatePulse.ts @@ -5,6 +5,12 @@ export type UpdatePulseParams = { active: boolean; }; +/** + * + * @param root0 + * @param root0.accessToken + * @param root0.active + */ export async function updatePulse({ accessToken, active, diff --git a/lib/reasoning/extractReasoningTitle.ts b/lib/reasoning/extractReasoningTitle.ts index 363d0dc26..5fdffa1f8 100644 --- a/lib/reasoning/extractReasoningTitle.ts +++ b/lib/reasoning/extractReasoningTitle.ts @@ -1,6 +1,6 @@ /** * Reasoning Title Extraction Utility - * + * * Extracts the first line or sentence as the title for reasoning content, * with intelligent markdown stripping and length handling. * Single responsibility: Extract meaningful titles from reasoning text. @@ -8,32 +8,33 @@ /** * Extract the first line of reasoning content as a clean title + * * @param content - The reasoning content to extract title from * @returns Clean, formatted title string */ export function extractReasoningTitle(content?: string): string { if (!content) return "Reasoning"; - + // Get the first non-empty line - const lines = content.split('\n').filter(line => line.trim()); + const lines = content.split("\n").filter(line => line.trim()); if (lines.length > 0) { let firstLine = lines[0].trim(); - + // Strip markdown formatting firstLine = firstLine - .replace(/\*\*(.*?)\*\*/g, '$1') // Remove bold **text** - .replace(/\*(.*?)\*/g, '$1') // Remove italic *text* - .replace(/`(.*?)`/g, '$1') // Remove code `text` - .replace(/#{1,6}\s*/g, '') // Remove headers # ## ### + .replace(/\*\*(.*?)\*\*/g, "$1") // Remove bold **text** + .replace(/\*(.*?)\*/g, "$1") // Remove italic *text* + .replace(/`(.*?)`/g, "$1") // Remove code `text` + .replace(/#{1,6}\s*/g, "") // Remove headers # ## ### .trim(); - + // If it's a short line (likely a title), use it as-is if (firstLine.length < 100) { return firstLine; } // If it's a long line, extract the first sentence - const firstSentence = firstLine.split('.')[0]; - return firstSentence.length < 100 ? firstSentence : firstSentence.substring(0, 97) + '...'; + const firstSentence = firstLine.split(".")[0]; + return firstSentence.length < 100 ? firstSentence : firstSentence.substring(0, 97) + "..."; } return "Reasoning"; } diff --git a/lib/reasoning/extractors/headerExtractor.ts b/lib/reasoning/extractors/headerExtractor.ts index 9af2654e3..f88f07904 100644 --- a/lib/reasoning/extractors/headerExtractor.ts +++ b/lib/reasoning/extractors/headerExtractor.ts @@ -1,31 +1,33 @@ /** * Header Extraction Utility - * + * * Extracts steps from markdown headers (# ## ###). * Single responsibility: Parse header-based reasoning structure. */ -import { ParsedStep, createStepFromMatch } from '../shared/parseUtilities'; +import { ParsedStep, createStepFromMatch } from "../shared/parseUtilities"; /** * Extract steps based on markdown headers (# ## ###) + * + * @param content */ export function extractHeaderSteps(content: string): ParsedStep[] { if (!content.trim()) return []; - - const lines = content.split('\n'); + + const lines = content.split("\n"); const steps: ParsedStep[] = []; - + for (let i = 0; i < lines.length; i++) { const line = lines[i]; const headerMatch = line.match(/^(#{1,6})\s*(.+)$/); - + if (headerMatch) { const label = headerMatch[2].trim(); - const step = createStepFromMatch(label, '', lines, i); + const step = createStepFromMatch(label, "", lines, i); steps.push(step); } } - + return steps; } diff --git a/lib/reasoning/extractors/paragraphExtractor.ts b/lib/reasoning/extractors/paragraphExtractor.ts index f378419e0..7e31f52a5 100644 --- a/lib/reasoning/extractors/paragraphExtractor.ts +++ b/lib/reasoning/extractors/paragraphExtractor.ts @@ -1,61 +1,66 @@ /** * Paragraph Extraction Utility - * + * * Extracts steps from paragraph breaks when no clear structure exists. * Single responsibility: Parse unstructured reasoning into logical steps. */ -import { ParsedStep, truncateLabel } from '../shared/parseUtilities'; +import { ParsedStep, truncateLabel } from "../shared/parseUtilities"; /** * Extract steps based on paragraph breaks (fallback for unstructured content) + * + * @param content */ export function extractParagraphSteps(content: string): ParsedStep[] { if (!content.trim()) return []; - + // Try double newlines first (clear paragraph breaks) - let paragraphs = content.split('\n\n').filter(p => p.trim()); - + let paragraphs = content.split("\n\n").filter(p => p.trim()); + // If no clear paragraphs, split by substantial lines if (paragraphs.length < 2) { paragraphs = splitBySubstantialLines(content); } - + if (paragraphs.length === 0) return []; - + return paragraphs.map(paragraph => { - const firstLine = paragraph.split('\n')[0].trim(); + const firstLine = paragraph.split("\n")[0].trim(); const label = truncateLabel(firstLine); - + return { label, - content: paragraph.trim() + content: paragraph.trim(), }; }); } /** * Split content by lines that are substantial enough to be new thoughts + * + * @param content */ function splitBySubstantialLines(content: string): string[] { - const lines = content.split('\n').filter(line => line.trim()); + const lines = content.split("\n").filter(line => line.trim()); const paragraphs: string[] = []; - let currentParagraph = ''; - + let currentParagraph = ""; + for (const line of lines) { - if (line.trim().length > 50) { // Substantial line, likely a new thought + if (line.trim().length > 50) { + // Substantial line, likely a new thought if (currentParagraph) { paragraphs.push(currentParagraph.trim()); } currentParagraph = line; } else if (currentParagraph) { - currentParagraph += '\n' + line; + currentParagraph += "\n" + line; } } - + if (currentParagraph) { paragraphs.push(currentParagraph.trim()); } - + return paragraphs; } diff --git a/lib/reasoning/mappers/actionLabelMapper.ts b/lib/reasoning/mappers/actionLabelMapper.ts index 32f32844e..513312199 100644 --- a/lib/reasoning/mappers/actionLabelMapper.ts +++ b/lib/reasoning/mappers/actionLabelMapper.ts @@ -1,35 +1,37 @@ /** * Action Label Mapper - * + * * Maps reasoning content to action-oriented labels like "Searching", "Analyzing". * Single responsibility: Convert content to Perplexity-style action verbs. */ /** * Extract action verb from content to create step labels + * + * @param content */ export function mapContentToActionLabel(content: string): string { const lowerContent = content.toLowerCase(); - + // Map keywords to action labels const actionMap = [ - { keywords: ['search', 'find', 'look'], action: 'Searching' }, - { keywords: ['analyz', 'consider', 'think'], action: 'Analyzing' }, - { keywords: ['review', 'check', 'examine'], action: 'Reviewing' }, - { keywords: ['plan', 'design', 'strategy'], action: 'Planning' }, - { keywords: ['gather', 'collect', 'compile'], action: 'Gathering' }, - { keywords: ['clarify', 'confirm', 'verify'], action: 'Clarifying' }, - { keywords: ['propose', 'suggest', 'recommend'], action: 'Proposing' }, - { keywords: ['assess', 'evaluat', 'determin'], action: 'Assessing' }, + { keywords: ["search", "find", "look"], action: "Searching" }, + { keywords: ["analyz", "consider", "think"], action: "Analyzing" }, + { keywords: ["review", "check", "examine"], action: "Reviewing" }, + { keywords: ["plan", "design", "strategy"], action: "Planning" }, + { keywords: ["gather", "collect", "compile"], action: "Gathering" }, + { keywords: ["clarify", "confirm", "verify"], action: "Clarifying" }, + { keywords: ["propose", "suggest", "recommend"], action: "Proposing" }, + { keywords: ["assess", "evaluat", "determin"], action: "Assessing" }, ]; - + // Find first matching action for (const { keywords, action } of actionMap) { if (keywords.some(keyword => lowerContent.includes(keyword))) { return action; } } - + // Default action - return 'Processing'; + return "Processing"; } diff --git a/lib/reasoning/mappers/iconMapper.ts b/lib/reasoning/mappers/iconMapper.ts index 6462f4908..f00db3608 100644 --- a/lib/reasoning/mappers/iconMapper.ts +++ b/lib/reasoning/mappers/iconMapper.ts @@ -1,44 +1,46 @@ /** * Icon Mapper - * + * * Maps reasoning content to appropriate Lucide icons. * Single responsibility: Assign contextual icons to reasoning steps. */ -import { - BrainIcon, - SearchIcon, - LightbulbIcon, +import { + BrainIcon, + SearchIcon, + LightbulbIcon, CheckCircleIcon, ListIcon, TargetIcon, DotIcon, - type LucideIcon -} from 'lucide-react'; + type LucideIcon, +} from "lucide-react"; /** * Determine appropriate icon based on step content + * + * @param content */ export function mapContentToIcon(content: string): LucideIcon { const lowerContent = content.toLowerCase(); - + // Map keywords to icons const iconMap = [ - { keywords: ['search', 'find', 'look'], icon: SearchIcon }, - { keywords: ['analyz', 'consider', 'think'], icon: BrainIcon }, - { keywords: ['solution', 'idea', 'approach'], icon: LightbulbIcon }, - { keywords: ['conclusion', 'result', 'final'], icon: CheckCircleIcon }, - { keywords: ['step', 'list', 'item'], icon: ListIcon }, - { keywords: ['goal', 'target', 'objective'], icon: TargetIcon }, + { keywords: ["search", "find", "look"], icon: SearchIcon }, + { keywords: ["analyz", "consider", "think"], icon: BrainIcon }, + { keywords: ["solution", "idea", "approach"], icon: LightbulbIcon }, + { keywords: ["conclusion", "result", "final"], icon: CheckCircleIcon }, + { keywords: ["step", "list", "item"], icon: ListIcon }, + { keywords: ["goal", "target", "objective"], icon: TargetIcon }, ]; - + // Find first matching icon for (const { keywords, icon } of iconMap) { if (keywords.some(keyword => lowerContent.includes(keyword))) { return icon; } } - + // Default to DotIcon (like Perplexity) return DotIcon; } diff --git a/lib/reasoning/parseReasoningSteps.ts b/lib/reasoning/parseReasoningSteps.ts index 05173d019..6add78c2c 100644 --- a/lib/reasoning/parseReasoningSteps.ts +++ b/lib/reasoning/parseReasoningSteps.ts @@ -1,24 +1,27 @@ /** * Simplified Reasoning Parser - * + * * Converts unstructured reasoning text into structured steps. * Single responsibility: Main parsing orchestration with simplified logic. */ -import { ReasoningStep } from './types'; -import { extractHeaderSteps } from './extractors/headerExtractor'; -import { extractParagraphSteps } from './extractors/paragraphExtractor'; -import { mapContentToActionLabel } from './mappers/actionLabelMapper'; -import { mapContentToIcon } from './mappers/iconMapper'; -import { removeFirstLine } from './shared/parseUtilities'; -import { BrainIcon } from 'lucide-react'; +import { ReasoningStep } from "./types"; +import { extractHeaderSteps } from "./extractors/headerExtractor"; +import { extractParagraphSteps } from "./extractors/paragraphExtractor"; +import { mapContentToActionLabel } from "./mappers/actionLabelMapper"; +import { mapContentToIcon } from "./mappers/iconMapper"; +import { removeFirstLine } from "./shared/parseUtilities"; +import { BrainIcon } from "lucide-react"; /** * Parse reasoning content into structured steps (simplified to 2 strategies) + * + * @param content + * @param isStreaming */ export function parseReasoningSteps( - content: string, - isStreaming: boolean = false + content: string, + isStreaming: boolean = false, ): ReasoningStep[] { if (!content?.trim()) { return []; @@ -43,45 +46,54 @@ export function parseReasoningSteps( } // Final fallback: Single step - return [{ - icon: BrainIcon, - label: "Analysis", - status: isStreaming ? 'active' : 'complete', - content: contentWithoutTitle - }]; + return [ + { + icon: BrainIcon, + label: "Analysis", + status: isStreaming ? "active" : "complete", + content: contentWithoutTitle, + }, + ]; } /** * Convert parsed steps to reasoning steps with metadata + * + * @param steps + * @param isStreaming */ function convertToReasoningSteps( - steps: Array<{ label: string; content: string }>, - isStreaming: boolean + steps: Array<{ label: string; content: string }>, + isStreaming: boolean, ): ReasoningStep[] { return steps.map((step, index) => ({ icon: mapContentToIcon(step.content), label: mapContentToActionLabel(step.content), status: getStepStatus(index, steps.length, isStreaming), - content: step.content + content: step.content, })); } /** * Determine step status based on position and streaming state + * + * @param stepIndex + * @param totalSteps + * @param isStreaming */ function getStepStatus( - stepIndex: number, - totalSteps: number, - isStreaming: boolean -): 'complete' | 'active' | 'pending' { + stepIndex: number, + totalSteps: number, + isStreaming: boolean, +): "complete" | "active" | "pending" { if (!isStreaming) { - return 'complete'; + return "complete"; } - + // Simple streaming logic: assume 70% progress const currentStep = Math.floor(totalSteps * 0.7); - - if (stepIndex < currentStep) return 'complete'; - if (stepIndex === currentStep) return 'active'; - return 'pending'; -} \ No newline at end of file + + if (stepIndex < currentStep) return "complete"; + if (stepIndex === currentStep) return "active"; + return "pending"; +} diff --git a/lib/reasoning/shared/parseUtilities.ts b/lib/reasoning/shared/parseUtilities.ts index fd46ad232..1a4ef04d2 100644 --- a/lib/reasoning/shared/parseUtilities.ts +++ b/lib/reasoning/shared/parseUtilities.ts @@ -1,6 +1,6 @@ /** * Shared Parsing Utilities - * + * * Common utilities for parsing reasoning content to eliminate code duplication. * Single responsibility: Shared parsing logic and utilities. */ @@ -13,84 +13,98 @@ export interface ParsedStep { /** * Create a step from a regex match and accumulate content + * + * @param label + * @param initialContent + * @param lines + * @param startIndex */ export function createStepFromMatch( label: string, initialContent: string, lines: string[], - startIndex: number + startIndex: number, ): ParsedStep { let content = initialContent; - + // Accumulate content from subsequent lines until next match or end for (let i = startIndex + 1; i < lines.length; i++) { const line = lines[i]; - + // Stop if we hit another match pattern (this would be handled by the specific extractor) if (isNewStepIndicator(line)) { break; } - + if (line.trim()) { - content += '\n' + line; + content += "\n" + line; } } - + return { label, - content: content.trim() + content: content.trim(), }; } /** * Check if a line indicates a new step (used to stop content accumulation) + * + * @param line */ function isNewStepIndicator(line: string): boolean { // Check for common step indicators return ( - /^#{1,6}\s/.test(line) || // Headers - /^\d+\.\s/.test(line) || // Numbered lists - /^[-•*]\s/.test(line) || // Bullet points - /^\*\*(.+?)\*\*:?\s*/.test(line) // Bold text + /^#{1,6}\s/.test(line) || // Headers + /^\d+\.\s/.test(line) || // Numbered lists + /^[-•*]\s/.test(line) || // Bullet points + /^\*\*(.+?)\*\*:?\s*/.test(line) // Bold text ); } /** * Truncate text to a reasonable label length + * + * @param text + * @param maxLength */ export function truncateLabel(text: string, maxLength: number = 100): string { if (text.length <= maxLength) { return text; } - + // Try to break at sentence boundary - const firstSentence = text.split('.')[0]; + const firstSentence = text.split(".")[0]; if (firstSentence.length <= maxLength) { return firstSentence; } - + // Fallback to character limit - return text.substring(0, maxLength - 3) + '...'; + return text.substring(0, maxLength - 3) + "..."; } /** * Strip markdown formatting from text + * + * @param text */ export function stripMarkdownFormatting(text: string): string { return text - .replace(/\*\*(.*?)\*\*/g, '$1') // Remove bold **text** - .replace(/\*(.*?)\*/g, '$1') // Remove italic *text* - .replace(/`(.*?)`/g, '$1') // Remove code `text` - .replace(/#{1,6}\s*/g, '') // Remove headers # ## ### + .replace(/\*\*(.*?)\*\*/g, "$1") // Remove bold **text** + .replace(/\*(.*?)\*/g, "$1") // Remove italic *text* + .replace(/`(.*?)`/g, "$1") // Remove code `text` + .replace(/#{1,6}\s*/g, "") // Remove headers # ## ### .trim(); } /** * Remove first line from content to avoid header duplication + * + * @param content */ export function removeFirstLine(content: string): string { - const lines = content.split('\n'); - if (lines.length <= 1) return ''; - - return lines.slice(1).join('\n').trim(); + const lines = content.split("\n"); + if (lines.length <= 1) return ""; + + return lines.slice(1).join("\n").trim(); } diff --git a/lib/reasoning/types.ts b/lib/reasoning/types.ts index 6b1eb5551..71f6e3c17 100644 --- a/lib/reasoning/types.ts +++ b/lib/reasoning/types.ts @@ -1,17 +1,17 @@ /** * Reasoning Types - * + * * Shared type definitions for reasoning components. * Single responsibility: Type definitions for reasoning system. */ -import { type LucideIcon } from 'lucide-react'; +import { type LucideIcon } from "lucide-react"; export interface ReasoningStep { icon: LucideIcon; label: string; description?: string; - status: 'complete' | 'active' | 'pending'; + status: "complete" | "active" | "pending"; content: string; } diff --git a/lib/recoup/deleteArtistFromAccount.ts b/lib/recoup/deleteArtistFromAccount.ts index 0bcb9dfc9..25eede0bd 100644 --- a/lib/recoup/deleteArtistFromAccount.ts +++ b/lib/recoup/deleteArtistFromAccount.ts @@ -7,14 +7,11 @@ import deleteAccountById from "@/lib/supabase/accounts/deleteAccountById"; * Delete an artist association from an account * If no other accounts have this artist, also delete the artist account and related data * - * @param artistAccountId The ID of the artist account to delete - * @param ownerAccountId The ID of the owner account + * @param artistAccountId - The ID of the artist account to delete + * @param ownerAccountId - The ID of the owner account * @returns Object with success status, message, and artist name if successful */ -export async function deleteArtistFromAccount( - artistAccountId: string, - ownerAccountId: string -) { +export async function deleteArtistFromAccount(artistAccountId: string, ownerAccountId: string) { try { // First get the artist data using getArtistById utility const artistData = await getArtistById(artistAccountId); @@ -23,10 +20,7 @@ export async function deleteArtistFromAccount( const artistName = artistData?.name || "Unknown artist"; // Delete the account_artist_ids record using utility - const deleteResult = await deleteAccountArtistId( - artistAccountId, - ownerAccountId - ); + const deleteResult = await deleteAccountArtistId(artistAccountId, ownerAccountId); if (!deleteResult.success) { return { diff --git a/lib/recoup/fetchPosts.ts b/lib/recoup/fetchPosts.ts index c475a4796..598269716 100644 --- a/lib/recoup/fetchPosts.ts +++ b/lib/recoup/fetchPosts.ts @@ -28,6 +28,11 @@ export interface PostsError { /** * Fetches posts for a specific artist from the API + * + * @param root0 + * @param root0.artistAccountId + * @param root0.page + * @param root0.limit */ async function fetchPosts({ artistAccountId, diff --git a/lib/sandboxes/__tests__/sandboxStreamTypes.test.ts b/lib/sandboxes/__tests__/sandboxStreamTypes.test.ts index a4292e792..1c20a0d39 100644 --- a/lib/sandboxes/__tests__/sandboxStreamTypes.test.ts +++ b/lib/sandboxes/__tests__/sandboxStreamTypes.test.ts @@ -6,9 +6,11 @@ import type { SandboxStreamProgress } from "../sandboxStreamTypes"; * the UI should show the polling component instead of the streaming component. */ -function getPromptSandboxComponentType( - result: SandboxStreamProgress, -): "polling" | "streaming" { +/** + * + * @param result + */ +function getPromptSandboxComponentType(result: SandboxStreamProgress): "polling" | "streaming" { if (result.runId) return "polling"; return "streaming"; } diff --git a/lib/sandboxes/convertFileTreeEntries.ts b/lib/sandboxes/convertFileTreeEntries.ts index f5fbc3b99..4c984c999 100644 --- a/lib/sandboxes/convertFileTreeEntries.ts +++ b/lib/sandboxes/convertFileTreeEntries.ts @@ -8,6 +8,10 @@ export interface FileTreeEntry { size?: number; } +/** + * + * @param entries + */ export function convertFileTreeEntries(entries: FileTreeEntry[]): FileNode[] { const root: FileNode[] = []; const pathMap = new Map(); diff --git a/lib/sandboxes/deleteSandbox.ts b/lib/sandboxes/deleteSandbox.ts index 0ed85531b..12d9b7651 100644 --- a/lib/sandboxes/deleteSandbox.ts +++ b/lib/sandboxes/deleteSandbox.ts @@ -1,5 +1,9 @@ import { NEW_API_BASE_URL } from "@/lib/consts"; +/** + * + * @param accessToken + */ export async function deleteSandbox(accessToken: string): Promise { const response = await fetch(`${NEW_API_BASE_URL}/api/sandboxes`, { method: "DELETE", diff --git a/lib/sandboxes/getFileContents.ts b/lib/sandboxes/getFileContents.ts index 97bd6654c..2bd7ca881 100644 --- a/lib/sandboxes/getFileContents.ts +++ b/lib/sandboxes/getFileContents.ts @@ -6,6 +6,11 @@ interface GetFileContentsResponse { error?: string; } +/** + * + * @param accessToken + * @param path + */ export async function getFileContents( accessToken: string, path: string, diff --git a/lib/sandboxes/getSandboxes.ts b/lib/sandboxes/getSandboxes.ts index 13fb87fb9..254a0d6d3 100644 --- a/lib/sandboxes/getSandboxes.ts +++ b/lib/sandboxes/getSandboxes.ts @@ -14,9 +14,11 @@ export interface GetSandboxesResult { filetree: FileTreeEntry[]; } -export async function getSandboxes( - accessToken: string -): Promise { +/** + * + * @param accessToken + */ +export async function getSandboxes(accessToken: string): Promise { const response = await fetch(`${NEW_API_BASE_URL}/api/sandboxes`, { method: "GET", headers: { diff --git a/lib/sandboxes/getSubtreeAtPath.ts b/lib/sandboxes/getSubtreeAtPath.ts index c6d7cc9ab..17052229a 100644 --- a/lib/sandboxes/getSubtreeAtPath.ts +++ b/lib/sandboxes/getSubtreeAtPath.ts @@ -2,14 +2,16 @@ import type { FileNode } from "./parseFileTree"; const WORKSPACE_ORGS_PATH = ".openclaw/workspace/orgs"; +/** + * + * @param nodes + */ export default function getSubtreeAtPath(nodes: FileNode[]): FileNode[] { const segments = WORKSPACE_ORGS_PATH.split("/"); let current = nodes; for (const segment of segments) { - const found = current.find( - (node) => node.name === segment && node.type === "folder", - ); + const found = current.find(node => node.name === segment && node.type === "folder"); if (!found?.children) return nodes; current = found.children; } diff --git a/lib/sandboxes/sortFileTree.ts b/lib/sandboxes/sortFileTree.ts index 2c0aad3c4..3b48f036b 100644 --- a/lib/sandboxes/sortFileTree.ts +++ b/lib/sandboxes/sortFileTree.ts @@ -1,5 +1,9 @@ import type { FileNode } from "./parseFileTree"; +/** + * + * @param nodes + */ export function sortFileTree(nodes: FileNode[]): void { nodes.sort((a, b) => { if (a.type !== b.type) { diff --git a/lib/search/searchProgressUtils.ts b/lib/search/searchProgressUtils.ts index 0fec6f7a8..3f09b66a7 100644 --- a/lib/search/searchProgressUtils.ts +++ b/lib/search/searchProgressUtils.ts @@ -1,10 +1,9 @@ import { SearchProgress } from "@/lib/tools/searchWeb/types"; export const isSearchProgressUpdate = (result: unknown): result is SearchProgress => { - return result !== null && - typeof result === 'object' && - 'status' in result && - !('success' in result); // Final results have 'success', progress updates have 'status' + return ( + result !== null && typeof result === "object" && "status" in result && !("success" in result) + ); // Final results have 'success', progress updates have 'status' }; export const asSearchProgress = (result: unknown): SearchProgress | null => { diff --git a/lib/search/timeFormatting.ts b/lib/search/timeFormatting.ts index d79230865..1639bac34 100644 --- a/lib/search/timeFormatting.ts +++ b/lib/search/timeFormatting.ts @@ -1,19 +1,27 @@ +/** + * + * @param seconds + */ export function formatTimeRemaining(seconds: number): string { const mins = Math.floor(seconds / 60); const secs = seconds % 60; - + if (mins === 0) { return `${secs} seconds`; } - + if (secs === 0) { - return `${mins} ${mins === 1 ? 'minute' : 'minutes'}`; + return `${mins} ${mins === 1 ? "minute" : "minutes"}`; } - - return `${mins} ${mins === 1 ? 'minute' : 'minutes'}`; + + return `${mins} ${mins === 1 ? "minute" : "minutes"}`; } +/** + * + * @param elapsedSeconds + * @param totalSeconds + */ export function calculateProgressPercent(elapsedSeconds: number, totalSeconds: number): number { return Math.min((elapsedSeconds / totalSeconds) * 100, 100); } - diff --git a/lib/search/urlUtils.ts b/lib/search/urlUtils.ts index 1107bb1eb..27c159b10 100644 --- a/lib/search/urlUtils.ts +++ b/lib/search/urlUtils.ts @@ -1,17 +1,28 @@ +/** + * + * @param url + */ export function getDomain(url: string): string { try { const urlObj = new URL(url); - return urlObj.hostname.replace('www.', ''); + return urlObj.hostname.replace("www.", ""); } catch { return url; } } +/** + * + * @param domain + * @param size + */ export function getFaviconUrl(domain: string, size: number = 32): string { return `https://www.google.com/s2/favicons?domain=${domain}&sz=${size}`; } +/** + * + */ export function getFallbackFaviconUrl(): string { return 'data:image/svg+xml,'; } - diff --git a/lib/segments/createSegmentResponses.ts b/lib/segments/createSegmentResponses.ts index 0e270400d..2177a9d8b 100644 --- a/lib/segments/createSegmentResponses.ts +++ b/lib/segments/createSegmentResponses.ts @@ -11,7 +11,7 @@ interface CreateArtistSegmentsSuccessData { export const successResponse = ( message: string, data: CreateArtistSegmentsSuccessData, - count: number + count: number, ) => ({ success: true, status: "success", diff --git a/lib/segments/createSegments.ts b/lib/segments/createSegments.ts index f53ecef2a..e92c3a1f8 100644 --- a/lib/segments/createSegments.ts +++ b/lib/segments/createSegments.ts @@ -16,10 +16,7 @@ interface CreateArtistSegmentsParams { prompt: string; } -export const createSegments = async ({ - artist_account_id, - prompt, -}: CreateArtistSegmentsParams) => { +export const createSegments = async ({ artist_account_id, prompt }: CreateArtistSegmentsParams) => { try { // Get artist info for better error messages const artistInfo = await getArtistById(artist_account_id); @@ -29,19 +26,18 @@ export const createSegments = async ({ const accountSocials = await getAccountSocials({ accountId: artist_account_id, }); - const socialIds = accountSocials.map( - (as: { social_id: string }) => as.social_id - ); + const socialIds = accountSocials.map((as: { social_id: string }) => as.social_id); if (socialIds.length === 0) { return { ...errorResponse("No social account found for this artist"), - feedback: `No Instagram accounts found for ${artistName}. To automatically set up Instagram accounts, please follow these steps:\n` + - `1. Call 'search_web' to search for "${artistName} Instagram handle"\n` + - "2. Call 'update_artist_socials' with the discovered Instagram profile URL\n" + - "3. Call 'create_segments' again to retry segment creation\n" + - "Instagram is required for fan segmentation as it's the primary social platform configured for segments." - } + feedback: + `No Instagram accounts found for ${artistName}. To automatically set up Instagram accounts, please follow these steps:\n` + + `1. Call 'search_web' to search for "${artistName} Instagram handle"\n` + + "2. Call 'update_artist_socials' with the discovered Instagram profile URL\n" + + "3. Call 'create_segments' again to retry segment creation\n" + + "Instagram is required for fan segmentation as it's the primary social platform configured for segments.", + }; } // Step 2: Get all fans for the artist @@ -54,13 +50,14 @@ export const createSegments = async ({ if (fans.length === 0) { return { ...errorResponse("No fans found for this artist"), - feedback: `No social_fans records found for ${artistName}. Before creating segments, you need social_fans data. Follow these steps:\n` + - "1. Call 'scrape_instagram_profile' with the artist's Instagram handles to get posts\n" + - "2. Call 'scrape_instagram_comments' with Instagram post URLs to scrape comment data\n" + - "3. Wait for the scraping jobs to complete and process into social_fans records\n" + - "4. Call 'create_segments' again once social_fans records are populated\n" + - "Note: Scraping jobs may take several minutes to complete." - } + feedback: + `No social_fans records found for ${artistName}. Before creating segments, you need social_fans data. Follow these steps:\n` + + "1. Call 'scrape_instagram_profile' with the artist's Instagram handles to get posts\n" + + "2. Call 'scrape_instagram_comments' with Instagram post URLs to scrape comment data\n" + + "3. Wait for the scraping jobs to complete and process into social_fans records\n" + + "4. Call 'create_segments' again once social_fans records are populated\n" + + "Note: Scraping jobs may take several minutes to complete.", + }; } // Step 3: Generate segment names using AI @@ -82,26 +79,19 @@ export const createSegments = async ({ const insertedSegments = await insertSegments(segmentsToInsert); // Step 6: Associate segments with the artist - const artistSegmentsToInsert = insertedSegments.map( - (segment: Tables<"segments">) => ({ - artist_account_id, - segment_id: segment.id, - updated_at: new Date().toISOString(), - }) - ); + const artistSegmentsToInsert = insertedSegments.map((segment: Tables<"segments">) => ({ + artist_account_id, + segment_id: segment.id, + updated_at: new Date().toISOString(), + })); - const insertedArtistSegments = await insertArtistSegments( - artistSegmentsToInsert - ); + const insertedArtistSegments = await insertArtistSegments(artistSegmentsToInsert); // Step 7: Associate fans with the new segments // Build a set of valid IDs from the fetched fan list const validFanIds = new Set(fans.map(f => f.fan_social_id)); - const fanSegmentsToInsert = getFanSegmentsToInsert( - segments, - insertedSegments - ).filter(fs => { + const fanSegmentsToInsert = getFanSegmentsToInsert(segments, insertedSegments).filter(fs => { const ok = validFanIds.has(fs.fan_social_id); if (!ok) console.warn(`Skipping unknown fan_social_id: ${fs.fan_social_id}`); return ok; @@ -121,14 +111,12 @@ export const createSegments = async ({ supabase_fan_segments: insertedFanSegments, segments, }, - segments.length + segments.length, ); } catch (error) { console.error("Error creating artist segments:", error); return errorResponse( - error instanceof Error - ? error.message - : "Failed to create artist segments" + error instanceof Error ? error.message : "Failed to create artist segments", ); } }; diff --git a/lib/segments/getAnalysisPrompt.ts b/lib/segments/getAnalysisPrompt.ts index 2290b08a7..6b22e2b37 100644 --- a/lib/segments/getAnalysisPrompt.ts +++ b/lib/segments/getAnalysisPrompt.ts @@ -3,7 +3,7 @@ import { GenerateSegmentsParams } from "./generateSegments"; const getAnalysisPrompt = ({ fans, prompt }: GenerateSegmentsParams) => { const fanCount = fans.length; - const fanData = fans.map((fan) => { + const fanData = fans.map(fan => { const obj = { fan_social_id: fan.fan_social_id, username: fan.fan_social.username, @@ -13,9 +13,7 @@ const getAnalysisPrompt = ({ fans, prompt }: GenerateSegmentsParams) => { comment: fan.latest_engagement_comment?.comment || null, }; // Remove keys with null values - return Object.fromEntries( - Object.entries(obj).filter(([, value]) => value !== null) - ); + return Object.fromEntries(Object.entries(obj).filter(([, value]) => value !== null)); }); const maxFans = 111; diff --git a/lib/segments/getFanSegmentsToInsert.ts b/lib/segments/getFanSegmentsToInsert.ts index 19e9d7cf2..82b0e02d6 100644 --- a/lib/segments/getFanSegmentsToInsert.ts +++ b/lib/segments/getFanSegmentsToInsert.ts @@ -4,14 +4,15 @@ import { Tables } from "@/types/database.types"; /** * Returns an array of fan-segment associations to insert, based on the AI-generated segments and the inserted segment records. * Each fan is only associated with the segment(s) they are assigned to in the segments array. + * + * @param segments + * @param insertedSegments */ export function getFanSegmentsToInsert( segments: GenerateArrayResult[], - insertedSegments: Tables<"segments">[] + insertedSegments: Tables<"segments">[], ) { - const segmentNameToId = new Map( - insertedSegments.map((seg) => [seg.name, seg.id]) - ); + const segmentNameToId = new Map(insertedSegments.map(seg => [seg.name, seg.id])); return segments.flatMap((segment: GenerateArrayResult) => { const segmentId = segmentNameToId.get(segment.segmentName); diff --git a/lib/serpapi/config.ts b/lib/serpapi/config.ts index 7251dddf2..9f190f9fc 100644 --- a/lib/serpapi/config.ts +++ b/lib/serpapi/config.ts @@ -1,15 +1,17 @@ export const SERPAPI_BASE_URL = "https://serpapi.com"; +/** + * + */ export function getSerpApiKey(): string { const apiKey = process.env.SERPAPI_API_KEY; - + if (!apiKey) { throw new Error( "SERPAPI_API_KEY environment variable is not set. " + - "Please add it to your environment variables." + "Please add it to your environment variables.", ); } - + return apiKey; } - diff --git a/lib/serpapi/searchImages.ts b/lib/serpapi/searchImages.ts index d7bbb51e6..453ce621a 100644 --- a/lib/serpapi/searchImages.ts +++ b/lib/serpapi/searchImages.ts @@ -1,20 +1,12 @@ import { getSerpApiKey, SERPAPI_BASE_URL } from "./config"; -import type { - SerpApiResponse, - SearchImagesOptions -} from "./types"; +import type { SerpApiResponse, SearchImagesOptions } from "./types"; -export async function searchGoogleImages( - options: SearchImagesOptions -): Promise { - const { - query, - limit = 10, - page = 0, - imageSize, - imageType, - aspectRatio - } = options; +/** + * + * @param options + */ +export async function searchGoogleImages(options: SearchImagesOptions): Promise { + const { query, limit = 10, page = 0, imageSize, imageType, aspectRatio } = options; const apiKey = getSerpApiKey(); @@ -57,9 +49,7 @@ export async function searchGoogleImages( if (!response.ok) { const errorText = await response.text(); - throw new Error( - `SerpAPI request failed: ${response.status} - ${errorText}` - ); + throw new Error(`SerpAPI request failed: ${response.status} - ${errorText}`); } const data: SerpApiResponse = await response.json(); @@ -73,16 +63,15 @@ export async function searchGoogleImages( } catch (error) { // Clear timeout in case of error clearTimeout(timeoutId); - + // Handle timeout specifically if (error instanceof Error && error.name === "AbortError") { throw new Error( - "Google Images search timed out after 10 seconds. Please try again with a more specific query." + "Google Images search timed out after 10 seconds. Please try again with a more specific query.", ); } - + // Re-throw other errors throw error; } } - diff --git a/lib/serpapi/types.ts b/lib/serpapi/types.ts index 278892366..87bf109e3 100644 --- a/lib/serpapi/types.ts +++ b/lib/serpapi/types.ts @@ -31,7 +31,3 @@ export interface SearchImagesOptions { imageType?: "photo" | "clipart" | "lineart" | "animated"; aspectRatio?: "square" | "wide" | "tall" | "panoramic"; } - - - - diff --git a/lib/sidebar/themeLabel.ts b/lib/sidebar/themeLabel.ts index 6564a9d85..668e6722c 100644 --- a/lib/sidebar/themeLabel.ts +++ b/lib/sidebar/themeLabel.ts @@ -1,6 +1,8 @@ /** * Maps a next-themes theme value to a human-readable label. * Returns "Dark", "Light", or "System" as the fallback. + * + * @param theme */ const themeLabel = (theme: string | undefined): string => { if (theme === "dark") return "Dark"; diff --git a/lib/socials/getPlatformDisplayName.ts b/lib/socials/getPlatformDisplayName.ts index 9efcd9a79..8bb94b78e 100644 --- a/lib/socials/getPlatformDisplayName.ts +++ b/lib/socials/getPlatformDisplayName.ts @@ -1,5 +1,6 @@ /** * Converts a platform type to a display-friendly name. + * * @param platformType - The platform type (e.g., "SPOTIFY", "TWITTER") * @returns Display-friendly platform name (e.g., "Spotify", "Twitter") */ diff --git a/lib/spotify/formatDuration.ts b/lib/spotify/formatDuration.ts index 4cc3e23ed..2f205b361 100644 --- a/lib/spotify/formatDuration.ts +++ b/lib/spotify/formatDuration.ts @@ -1,5 +1,6 @@ /** * Formats milliseconds to MM:SS format for track/album durations + * * @param ms - Duration in milliseconds * @returns Formatted duration string (e.g., "3:42") */ @@ -7,4 +8,4 @@ export const formatDuration = (ms: number): string => { const minutes = Math.floor(ms / 60000); const seconds = Math.floor((ms % 60000) / 1000); return `${minutes}:${seconds.toString().padStart(2, "0")}`; -}; \ No newline at end of file +}; diff --git a/lib/spotify/getSpotifyFollowers.ts b/lib/spotify/getSpotifyFollowers.ts index da4903686..acd1c3be8 100644 --- a/lib/spotify/getSpotifyFollowers.ts +++ b/lib/spotify/getSpotifyFollowers.ts @@ -37,6 +37,7 @@ interface SpotifySearchResponse { /** * Get Spotify follower count for an artist + * * @param artistName - The name of the artist to search for * @returns Promise - The follower count of the first matching artist */ @@ -48,9 +49,7 @@ export async function getSpotifyFollowers(artistName: string): Promise { const response = await fetch(url); if (!response.ok) { - throw new Error( - `API request failed: ${response.status} ${response.statusText}` - ); + throw new Error(`API request failed: ${response.status} ${response.statusText}`); } const data: SpotifySearchResponse = await response.json(); @@ -61,10 +60,7 @@ export async function getSpotifyFollowers(artistName: string): Promise { return data.artists.items[0].followers.total; } catch (error) { - console.error( - `Error fetching Spotify followers for "${artistName}":`, - error - ); + console.error(`Error fetching Spotify followers for "${artistName}":`, error); throw error; } } diff --git a/lib/spotify/getSpotifyImage.ts b/lib/spotify/getSpotifyImage.ts index b1671a60b..778f880c6 100644 --- a/lib/spotify/getSpotifyImage.ts +++ b/lib/spotify/getSpotifyImage.ts @@ -1,4 +1,8 @@ // Utility function to get the first image URL from a Spotify item +/** + * + * @param item + */ export function getSpotifyImage(item: unknown): string | undefined { if (typeof item === "object" && item !== null) { const obj = item as { diff --git a/lib/spotify/spotifyContentUtils.ts b/lib/spotify/spotifyContentUtils.ts index be82edbee..653328822 100644 --- a/lib/spotify/spotifyContentUtils.ts +++ b/lib/spotify/spotifyContentUtils.ts @@ -20,6 +20,7 @@ export type SpotifyContent = /** * Generates appropriate subtitle text for different Spotify content types + * * @param content - Any Spotify content object (track, artist, album, etc.) * @returns Formatted subtitle string based on content type */ @@ -45,4 +46,4 @@ export const getSpotifySubtitle = (content: SpotifyContent): string => { default: return ""; } -}; \ No newline at end of file +}; diff --git a/lib/stripe/createBillingPortalSession.ts b/lib/stripe/createBillingPortalSession.ts index 5d9bd9296..ed6a1d364 100644 --- a/lib/stripe/createBillingPortalSession.ts +++ b/lib/stripe/createBillingPortalSession.ts @@ -1,10 +1,7 @@ import stripeClient from "./client"; import { getActiveSubscriptionDetails } from "./getActiveSubscriptionDetails"; -const createBillingPortalSession = async ( - accountId: string, - returnUrl: string -) => { +const createBillingPortalSession = async (accountId: string, returnUrl: string) => { try { const activeSubscription = await getActiveSubscriptionDetails(accountId); diff --git a/lib/stripe/getActiveSubscriptions.ts b/lib/stripe/getActiveSubscriptions.ts index 87ff4be78..8c60546d1 100644 --- a/lib/stripe/getActiveSubscriptions.ts +++ b/lib/stripe/getActiveSubscriptions.ts @@ -11,8 +11,7 @@ export const getActiveSubscriptions = async (accountId: string) => { }); const activeSubscriptions = subscriptions?.data?.filter( - (subscription: Stripe.Subscription) => - subscription.metadata?.accountId === accountId + (subscription: Stripe.Subscription) => subscription.metadata?.accountId === accountId, ); return activeSubscriptions || []; diff --git a/lib/stripe/getOrgSubscription.ts b/lib/stripe/getOrgSubscription.ts index 1723c670f..7b746b416 100644 --- a/lib/stripe/getOrgSubscription.ts +++ b/lib/stripe/getOrgSubscription.ts @@ -9,9 +9,7 @@ import Stripe from "stripe"; * @param accountId - The account ID * @returns The org's Stripe subscription if found, null otherwise */ -export async function getOrgSubscription( - accountId: string -): Promise { +export async function getOrgSubscription(accountId: string): Promise { if (!accountId) return null; const accountOrgs = await getAccountOrganizations(accountId); @@ -19,16 +17,13 @@ export async function getOrgSubscription( // Check all orgs in parallel for faster UX const orgIds = accountOrgs - .map((org) => org.organization_id) + .map(org => org.organization_id) .filter((id): id is string => id !== null); - - const subscriptions = await Promise.all( - orgIds.map((orgId) => getActiveSubscriptionDetails(orgId)) - ); + + const subscriptions = await Promise.all(orgIds.map(orgId => getActiveSubscriptionDetails(orgId))); // Return first active subscription found - return subscriptions.find((sub) => sub !== null) ?? null; + return subscriptions.find(sub => sub !== null) ?? null; } export default getOrgSubscription; - diff --git a/lib/styles/darkModeUtils.ts b/lib/styles/darkModeUtils.ts index 68145953f..fbd941f43 100644 --- a/lib/styles/darkModeUtils.ts +++ b/lib/styles/darkModeUtils.ts @@ -1,7 +1,7 @@ /** * Dark mode utility functions using semantic tokens. * All utilities use semantic tokens that automatically adapt to light/dark mode. - * + * * Reference: documentation/styling-system.md */ @@ -55,23 +55,31 @@ export const shadowUtils = { /** * Common input field styling with semantic tokens + * + * @param hasError */ export const getInputClasses = (hasError = false): string => { - const base = "w-full rounded-md border px-3 py-2 bg-background text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"; - + const base = + "w-full rounded-md border px-3 py-2 bg-background text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"; + if (hasError) { return `${base} border-destructive focus-visible:ring-destructive`; } - + return `${base} border-input`; }; /** * Common button styling with semantic tokens + * + * @param variant */ -export const getButtonClasses = (variant: 'primary' | 'secondary' | 'danger' | 'ghost' | 'outline' = 'primary'): string => { - const base = "inline-flex items-center justify-center rounded-md px-4 py-2 text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50"; - +export const getButtonClasses = ( + variant: "primary" | "secondary" | "danger" | "ghost" | "outline" = "primary", +): string => { + const base = + "inline-flex items-center justify-center rounded-md px-4 py-2 text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50"; + const variants = { primary: "bg-primary text-primary-foreground hover:bg-primary/90", secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80", @@ -79,20 +87,22 @@ export const getButtonClasses = (variant: 'primary' | 'secondary' | 'danger' | ' ghost: "hover:bg-accent hover:text-accent-foreground", outline: "border border-input bg-background hover:bg-accent hover:text-accent-foreground", }; - + return `${base} ${variants[variant]}`; }; /** * Common card/container styling with semantic tokens + * + * @param interactive */ export const getCardClasses = (interactive = false): string => { const base = "bg-card text-card-foreground border border-border rounded-lg shadow-sm"; - + if (interactive) { return `${base} hover:shadow-md transition-shadow cursor-pointer`; } - + return base; }; @@ -119,13 +129,15 @@ export const getSkeletonClasses = (): string => { /** * Icon wrapper with semantic tokens + * + * @param variant */ -export const getIconClasses = (variant: 'primary' | 'secondary' | 'muted' = 'primary'): string => { +export const getIconClasses = (variant: "primary" | "secondary" | "muted" = "primary"): string => { const variants = { primary: "text-foreground", secondary: "text-muted-foreground", muted: "text-muted-foreground", }; - + return variants[variant]; }; diff --git a/lib/styles/patterns.ts b/lib/styles/patterns.ts index f754fae36..bc91088c2 100644 --- a/lib/styles/patterns.ts +++ b/lib/styles/patterns.ts @@ -1,9 +1,9 @@ /** * Centralized UI patterns using semantic tokens from the styling system. * All patterns use semantic tokens that automatically adapt to light/dark mode. - * + * * Reference: documentation/styling-system.md - * + * * Usage: * import { buttonPatterns, cardPatterns, textPatterns } from '@/lib/styles/patterns'; * @@ -15,15 +15,16 @@ export const buttonPatterns = { primary: "bg-primary text-primary-foreground hover:bg-primary/90 transition-colors", - + secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80 transition-colors", - + danger: "bg-destructive text-destructive-foreground hover:bg-destructive/90 transition-colors", - + ghost: "hover:bg-accent hover:text-accent-foreground transition-colors", - - outline: "border border-input bg-background hover:bg-accent hover:text-accent-foreground transition-colors", - + + outline: + "border border-input bg-background hover:bg-accent hover:text-accent-foreground transition-colors", + icon: "p-2 cursor-pointer hover:bg-accent rounded-full transition-colors", } as const; @@ -32,14 +33,16 @@ export const buttonPatterns = { // ============================================================================ export const formPatterns = { - input: "w-full rounded-md border border-input bg-background px-3 py-2 text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50", - + input: + "w-full rounded-md border border-input bg-background px-3 py-2 text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50", + label: "text-sm font-medium text-foreground", - + error: "text-sm text-destructive", - - textarea: "flex min-h-[60px] w-full rounded-md border border-input bg-background px-3 py-2 text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50", - + + textarea: + "flex min-h-[60px] w-full rounded-md border border-input bg-background px-3 py-2 text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50", + helper: "text-sm text-muted-foreground", } as const; @@ -49,17 +52,19 @@ export const formPatterns = { export const containerPatterns = { card: "bg-card text-card-foreground border border-border rounded-lg shadow-sm", - - cardHover: "bg-card text-card-foreground border border-border rounded-lg shadow-sm hover:shadow-md transition-shadow", - + + cardHover: + "bg-card text-card-foreground border border-border rounded-lg shadow-sm hover:shadow-md transition-shadow", + modal: "bg-card text-card-foreground border border-border rounded-lg shadow-lg", - - modalOverlay: "fixed left-0 top-0 w-screen h-screen z-[1000] flex items-center justify-center bg-black/50 backdrop-blur-sm", - + + modalOverlay: + "fixed left-0 top-0 w-screen h-screen z-[1000] flex items-center justify-center bg-black/50 backdrop-blur-sm", + sidebar: "bg-sidebar border-r border-sidebar-border", - + dropdown: "bg-popover text-popover-foreground border border-border rounded-md shadow-md", - + popover: "bg-popover text-popover-foreground border border-border rounded-md shadow-md", } as const; @@ -69,19 +74,19 @@ export const containerPatterns = { export const textPatterns = { primary: "text-foreground", - + secondary: "text-muted-foreground", - + muted: "text-muted-foreground", - + placeholder: "text-muted-foreground", - + heading: "text-foreground font-semibold", - + error: "text-destructive", - + success: "text-green-600 dark:text-green-400", - + link: "text-primary hover:underline", } as const; @@ -91,13 +96,13 @@ export const textPatterns = { export const iconPatterns = { primary: "text-foreground", - + secondary: "text-muted-foreground", - + muted: "text-muted-foreground", - + error: "text-destructive", - + success: "text-green-600 dark:text-green-400", } as const; @@ -107,11 +112,11 @@ export const iconPatterns = { export const borderPatterns = { default: "border border-border", - + light: "border border-border/50", - + focus: "border border-input focus:border-ring focus:ring-2 focus:ring-ring focus:ring-offset-2", - + divider: "border-b border-border", } as const; @@ -121,14 +126,15 @@ export const borderPatterns = { export const statePatterns = { hover: "hover:bg-accent", - + active: "bg-accent", - + selected: "bg-accent text-accent-foreground", - + disabled: "opacity-50 cursor-not-allowed pointer-events-none", - - focus: "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2", + + focus: + "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2", } as const; // ============================================================================ @@ -137,9 +143,9 @@ export const statePatterns = { export const loadingPatterns = { skeleton: "animate-pulse bg-muted rounded-md", - + shimmer: "animate-shimmer bg-gradient-to-r from-muted via-accent to-muted", - + spinner: "animate-spin text-muted-foreground", } as const; @@ -149,6 +155,7 @@ export const loadingPatterns = { /** * Combine multiple pattern classes with custom classes + * * @param patterns - Array of pattern strings * @param customClasses - Additional custom classes */ diff --git a/lib/supabase/__tests__/ensureArtistAccess.test.ts b/lib/supabase/__tests__/ensureArtistAccess.test.ts index 9f9ecdeec..088b3436d 100644 --- a/lib/supabase/__tests__/ensureArtistAccess.test.ts +++ b/lib/supabase/__tests__/ensureArtistAccess.test.ts @@ -1,5 +1,8 @@ import { describe, it, expect, vi, beforeEach } from "vitest"; +// Import after mocks +import { ensureArtistAccess } from "../ensureArtistAccess"; + // Create chainable mock for Supabase queries const createChainableMock = () => { const chain: { @@ -21,9 +24,7 @@ const createChainableMock = () => { chain.select.mockReturnValue(chain); chain.eq.mockReturnValue(chain); chain.single.mockImplementation(() => Promise.resolve(chain.resolvedData)); - chain.maybeSingle.mockImplementation(() => - Promise.resolve(chain.resolvedData) - ); + chain.maybeSingle.mockImplementation(() => Promise.resolve(chain.resolvedData)); chain.insert.mockImplementation(() => Promise.resolve(chain.resolvedData)); return chain; @@ -44,9 +45,6 @@ vi.mock("../serverClient", () => ({ }, })); -// Import after mocks -import { ensureArtistAccess } from "../ensureArtistAccess"; - describe("ensureArtistAccess", () => { const mockArtistId = "artist-123"; const mockAccountId = "account-456"; @@ -135,14 +133,14 @@ describe("ensureArtistAccess", () => { // Track when each operation starts and completes accountsChain.single.mockImplementation(async () => { executionOrder.push("artistCheck:start"); - await new Promise((resolve) => setTimeout(resolve, 10)); + await new Promise(resolve => setTimeout(resolve, 10)); executionOrder.push("artistCheck:end"); return { data: { id: mockArtistId }, error: null }; }); accountArtistIdsChain.maybeSingle.mockImplementation(async () => { executionOrder.push("accessCheck:start"); - await new Promise((resolve) => setTimeout(resolve, 10)); + await new Promise(resolve => setTimeout(resolve, 10)); executionOrder.push("accessCheck:end"); return { data: { artist_id: mockArtistId }, error: null }; // User already has access }); @@ -165,8 +163,7 @@ describe("ensureArtistAccess", () => { // At least one start should come before the other's end (proves parallelism) const bothStartedBeforeAnyEnds = - Math.max(artistStartIndex, accessStartIndex) < - Math.min(artistEndIndex, accessEndIndex); + Math.max(artistStartIndex, accessStartIndex) < Math.min(artistEndIndex, accessEndIndex); expect(bothStartedBeforeAnyEnds).toBe(true); }); diff --git a/lib/supabase/account_artist_ids/checkAccountArtistAccess.ts b/lib/supabase/account_artist_ids/checkAccountArtistAccess.ts index c2df321b4..40978b04f 100644 --- a/lib/supabase/account_artist_ids/checkAccountArtistAccess.ts +++ b/lib/supabase/account_artist_ids/checkAccountArtistAccess.ts @@ -2,20 +2,20 @@ import supabase from "@/lib/supabase/serverClient"; /** * Check if an account has access to a specific artist - * + * * Access is granted if: * 1. Account has direct access via account_artist_ids, OR * 2. Account and artist share an organization - * + * * Fails closed: returns false on any database error to deny access safely. - * + * * @param accountId - The account ID to check * @param artistId - The artist ID to check access for * @returns true if the account has access to the artist, false otherwise */ export async function checkAccountArtistAccess( accountId: string, - artistId: string + artistId: string, ): Promise { try { // 1. Check direct access via account_artist_ids @@ -55,7 +55,7 @@ export async function checkAccountArtistAccess( if (!artistOrgs?.length) return false; // Check if user belongs to any of those orgs - const orgIds = artistOrgs.map((o) => o.organization_id).filter(Boolean); + const orgIds = artistOrgs.map(o => o.organization_id).filter(Boolean); if (!orgIds.length) return false; const { data: userOrgAccess, error: userOrgError } = await supabase diff --git a/lib/supabase/account_artist_ids/deleteAccountArtistId.ts b/lib/supabase/account_artist_ids/deleteAccountArtistId.ts index c822f2ff3..b4a27c4e3 100644 --- a/lib/supabase/account_artist_ids/deleteAccountArtistId.ts +++ b/lib/supabase/account_artist_ids/deleteAccountArtistId.ts @@ -3,14 +3,11 @@ import supabase from "@/lib/supabase/serverClient"; /** * Delete an association between an account and an artist * - * @param artistId The ID of the artist - * @param accountId The ID of the account + * @param artistId - The ID of the artist + * @param accountId - The ID of the account * @returns Object with success status, deleted links data, and any error */ -export async function deleteAccountArtistId( - artistId: string, - accountId: string -) { +export async function deleteAccountArtistId(artistId: string, accountId: string) { try { const { data, error } = await supabase .from("account_artist_ids") diff --git a/lib/supabase/account_artist_ids/getAccountArtistIds.ts b/lib/supabase/account_artist_ids/getAccountArtistIds.ts index 7a8388ca4..56ad34cf5 100644 --- a/lib/supabase/account_artist_ids/getAccountArtistIds.ts +++ b/lib/supabase/account_artist_ids/getAccountArtistIds.ts @@ -5,13 +5,12 @@ import type { ArtistRecord } from "@/types/Artist"; /** * Get all artists for an array of artist IDs or account IDs, with full info * - * @param params Object with artistIds or accountIds array + * @param params - Object with artistIds or accountIds array + * @param params.artistIds + * @param params.accountIds * @returns Array of formatted artist objects */ -export async function getAccountArtistIds(params: { - artistIds?: string[]; - accountIds?: string[]; -}) { +export async function getAccountArtistIds(params: { artistIds?: string[]; accountIds?: string[] }) { const { artistIds, accountIds } = params; if (!artistIds && !accountIds) { throw new Error("Must provide either artistIds or accountIds"); @@ -38,9 +37,7 @@ export async function getAccountArtistIds(params: { return []; } // Format each artist_info using getFormattedArtist - return (data || []).map((row: { artist_info: ArtistRecord; }) => - getFormattedArtist(row) - ); + return (data || []).map((row: { artist_info: ArtistRecord }) => getFormattedArtist(row)); } catch (error) { console.error("Unexpected error in getAccountArtistIds:", error); return []; diff --git a/lib/supabase/account_artist_ids/insertAccountArtistId.ts b/lib/supabase/account_artist_ids/insertAccountArtistId.ts index 60ad38c8c..4b0627f9a 100644 --- a/lib/supabase/account_artist_ids/insertAccountArtistId.ts +++ b/lib/supabase/account_artist_ids/insertAccountArtistId.ts @@ -2,13 +2,9 @@ import supabase from "@/lib/supabase/serverClient"; import type { Tables } from "@/types/database.types"; const insertAccountArtistId = async ( - record: Partial> + record: Partial>, ): Promise | null> => { - const { data } = await supabase - .from("account_artist_ids") - .insert(record) - .select("*") - .single(); + const { data } = await supabase.from("account_artist_ids").insert(record).select("*").single(); return data || null; }; diff --git a/lib/supabase/account_artist_ids/toggleArtistPin.ts b/lib/supabase/account_artist_ids/toggleArtistPin.ts index dcb76a779..33d5a4a4a 100644 --- a/lib/supabase/account_artist_ids/toggleArtistPin.ts +++ b/lib/supabase/account_artist_ids/toggleArtistPin.ts @@ -9,17 +9,18 @@ interface ToggleArtistPinParams { /** * Toggle the pinned status of an artist for a user. * Uses upsert to create the row if it doesn't exist (for org artists). + * + * @param root0 + * @param root0.accountId + * @param root0.artistId + * @param root0.pinned */ -export const toggleArtistPin = async ({ - accountId, - artistId, - pinned, -}: ToggleArtistPinParams) => { +export const toggleArtistPin = async ({ accountId, artistId, pinned }: ToggleArtistPinParams) => { const { error } = await supabase .from("account_artist_ids") .upsert( { account_id: accountId, artist_id: artistId, pinned }, - { onConflict: "account_id,artist_id" } + { onConflict: "account_id,artist_id" }, ); if (error) { diff --git a/lib/supabase/account_emails/getAccountDetailsByEmails.ts b/lib/supabase/account_emails/getAccountDetailsByEmails.ts index 4a38f7f51..cc1303ee8 100644 --- a/lib/supabase/account_emails/getAccountDetailsByEmails.ts +++ b/lib/supabase/account_emails/getAccountDetailsByEmails.ts @@ -3,18 +3,16 @@ import type { Tables } from "@/types/database.types"; /** * Get account_emails by email addresses - * @param emails Array of email addresses to query + * + * @param emails - Array of email addresses to query * @returns Array of account_emails rows */ export default async function getAccountDetailsByEmails( - emails: string[] + emails: string[], ): Promise[]> { if (!Array.isArray(emails) || emails.length === 0) return []; - const { data, error } = await supabase - .from("account_emails") - .select("*") - .in("email", emails); + const { data, error } = await supabase.from("account_emails").select("*").in("email", emails); if (error) { console.error("Error fetching account_emails by emails:", error); diff --git a/lib/supabase/account_emails/getAccountEmails.ts b/lib/supabase/account_emails/getAccountEmails.ts index 575c4b424..7e0a2c0d3 100644 --- a/lib/supabase/account_emails/getAccountEmails.ts +++ b/lib/supabase/account_emails/getAccountEmails.ts @@ -3,9 +3,7 @@ import { Tables } from "@/types/database.types"; type AccountEmail = Tables<"account_emails">; -export const getAccountEmails = async ( - accountIds: string | string[] -): Promise => { +export const getAccountEmails = async (accountIds: string | string[]): Promise => { const ids = Array.isArray(accountIds) ? accountIds : [accountIds]; if (ids.length === 0) return []; diff --git a/lib/supabase/account_emails/insertAccountEmail.ts b/lib/supabase/account_emails/insertAccountEmail.ts index ee9f6a575..c50a4c4de 100644 --- a/lib/supabase/account_emails/insertAccountEmail.ts +++ b/lib/supabase/account_emails/insertAccountEmail.ts @@ -3,7 +3,7 @@ import type { Tables } from "@/types/database.types"; const insertAccountEmail = async ( accountId: string, - email: string + email: string, ): Promise | null> => { const { data } = await supabase .from("account_emails") diff --git a/lib/supabase/account_info/getAccountInfoById.ts b/lib/supabase/account_info/getAccountInfoById.ts index 8fe4df87c..e8d926eca 100644 --- a/lib/supabase/account_info/getAccountInfoById.ts +++ b/lib/supabase/account_info/getAccountInfoById.ts @@ -1,9 +1,7 @@ import supabase from "@/lib/supabase/serverClient"; import type { Tables } from "@/types/database.types"; -const getAccountInfoById = async ( - accountId: string -): Promise | null> => { +const getAccountInfoById = async (accountId: string): Promise | null> => { const { data } = await supabase .from("account_info") .select("*") diff --git a/lib/supabase/account_info/insertAccountInfo.ts b/lib/supabase/account_info/insertAccountInfo.ts index faed8ce63..c68cbb899 100644 --- a/lib/supabase/account_info/insertAccountInfo.ts +++ b/lib/supabase/account_info/insertAccountInfo.ts @@ -2,13 +2,9 @@ import supabase from "@/lib/supabase/serverClient"; import type { Tables } from "@/types/database.types"; const insertAccountInfo = async ( - info: Partial> + info: Partial>, ): Promise | null> => { - const { data } = await supabase - .from("account_info") - .insert(info) - .select("*") - .single(); + const { data } = await supabase.from("account_info").insert(info).select("*").single(); return data || null; }; diff --git a/lib/supabase/account_info/updateAccountInfo.ts b/lib/supabase/account_info/updateAccountInfo.ts index 7e725fc65..02cc17321 100644 --- a/lib/supabase/account_info/updateAccountInfo.ts +++ b/lib/supabase/account_info/updateAccountInfo.ts @@ -3,7 +3,7 @@ import type { Tables } from "@/types/database.types"; const updateAccountInfo = async ( accountId: string, - update: Partial> + update: Partial>, ): Promise | null> => { const { data } = await supabase .from("account_info") diff --git a/lib/supabase/account_organization_ids/addAccountToOrganization.ts b/lib/supabase/account_organization_ids/addAccountToOrganization.ts index 7825423d0..ae91fa4b9 100644 --- a/lib/supabase/account_organization_ids/addAccountToOrganization.ts +++ b/lib/supabase/account_organization_ids/addAccountToOrganization.ts @@ -10,7 +10,7 @@ import supabase from "@/lib/supabase/serverClient"; */ export async function addAccountToOrganization( accountId: string, - organizationId: string + organizationId: string, ): Promise { if (!accountId || !organizationId) return null; @@ -29,4 +29,3 @@ export async function addAccountToOrganization( } export default addAccountToOrganization; - diff --git a/lib/supabase/account_organization_ids/getAccountOrganizations.ts b/lib/supabase/account_organization_ids/getAccountOrganizations.ts index 358284b0a..ad45c0f1c 100644 --- a/lib/supabase/account_organization_ids/getAccountOrganizations.ts +++ b/lib/supabase/account_organization_ids/getAccountOrganizations.ts @@ -3,28 +3,32 @@ import type { Tables } from "@/types/database.types"; /** Row type with joined organization account and its info */ export type AccountOrganization = Tables<"account_organization_ids"> & { - organization: (Tables<"accounts"> & { - account_info: Tables<"account_info">[] | null; - }) | null; + organization: + | (Tables<"accounts"> & { + account_info: Tables<"account_info">[] | null; + }) + | null; }; /** * Get all organizations an account belongs to + * + * @param accountId */ -export async function getAccountOrganizations( - accountId: string -): Promise { +export async function getAccountOrganizations(accountId: string): Promise { if (!accountId) return []; const { data, error } = await supabase .from("account_organization_ids") - .select(` + .select( + ` *, organization:accounts!account_organization_ids_organization_id_fkey ( *, account_info ( * ) ) - `) + `, + ) .eq("account_id", accountId); if (error) return []; @@ -33,4 +37,3 @@ export async function getAccountOrganizations( } export default getAccountOrganizations; - diff --git a/lib/supabase/account_socials/deleteAccountSocial.ts b/lib/supabase/account_socials/deleteAccountSocial.ts index 83a4cf3dd..ca78b8e65 100644 --- a/lib/supabase/account_socials/deleteAccountSocial.ts +++ b/lib/supabase/account_socials/deleteAccountSocial.ts @@ -1,9 +1,6 @@ import supabase from "../serverClient"; -const deleteAccountSocial = async ( - accountId: string, - socialId: string -): Promise => { +const deleteAccountSocial = async (accountId: string, socialId: string): Promise => { await supabase .from("account_socials") .delete() diff --git a/lib/supabase/account_socials/getAccountSocials.ts b/lib/supabase/account_socials/getAccountSocials.ts index 09ba19168..959299a4b 100644 --- a/lib/supabase/account_socials/getAccountSocials.ts +++ b/lib/supabase/account_socials/getAccountSocials.ts @@ -10,7 +10,10 @@ interface Params { socialId?: string | string[]; } -const getAccountSocials = async ({ accountId, socialId }: Params): Promise => { +const getAccountSocials = async ({ + accountId, + socialId, +}: Params): Promise => { let query = supabase.from("account_socials").select("*, social:socials(*)"); if (accountId) { @@ -30,4 +33,3 @@ const getAccountSocials = async ({ accountId, socialId }: Params): Promise => { +const insertAccountSocial = async (accountId: string, socialId: string): Promise => { await supabase.from("account_socials").insert({ account_id: accountId, social_id: socialId, diff --git a/lib/supabase/account_workspace_ids/insertAccountWorkspaceId.ts b/lib/supabase/account_workspace_ids/insertAccountWorkspaceId.ts index bb90e0d4d..09d140cb1 100644 --- a/lib/supabase/account_workspace_ids/insertAccountWorkspaceId.ts +++ b/lib/supabase/account_workspace_ids/insertAccountWorkspaceId.ts @@ -2,13 +2,14 @@ import supabase from "@/lib/supabase/serverClient"; /** * Link a workspace to an owner account + * * @param accountId - The owner's account ID * @param workspaceId - The workspace account ID * @returns The created record or null if failed */ export async function insertAccountWorkspaceId( accountId: string, - workspaceId: string + workspaceId: string, ): Promise<{ id: string } | null> { if (!accountId || !workspaceId) return null; @@ -35,4 +36,3 @@ export async function insertAccountWorkspaceId( } export default insertAccountWorkspaceId; - diff --git a/lib/supabase/account_workspace_ids/selectAccountWorkspaceIds.ts b/lib/supabase/account_workspace_ids/selectAccountWorkspaceIds.ts index f1fddbd65..27d7ee603 100644 --- a/lib/supabase/account_workspace_ids/selectAccountWorkspaceIds.ts +++ b/lib/supabase/account_workspace_ids/selectAccountWorkspaceIds.ts @@ -5,9 +5,11 @@ export type AccountWorkspaceRow = Tables<"account_workspace_ids">; /** * Select account_workspace_ids row by workspace_id + * + * @param workspaceId */ export async function selectAccountWorkspaceIds( - workspaceId: string + workspaceId: string, ): Promise { if (!workspaceId) return null; @@ -24,4 +26,3 @@ export async function selectAccountWorkspaceIds( } export default selectAccountWorkspaceIds; - diff --git a/lib/supabase/accounts/createAccount.ts b/lib/supabase/accounts/createAccount.ts index ba5464e1c..90dff2324 100644 --- a/lib/supabase/accounts/createAccount.ts +++ b/lib/supabase/accounts/createAccount.ts @@ -6,16 +6,13 @@ export type AccountRow = Tables<"accounts">; /** * Create a new account in the database - * @param name Name of the account to create + * + * @param name - Name of the account to create * @returns Created account data or null if creation failed */ export async function createAccount(name: string): Promise { try { - const { data, error } = await supabase - .from("accounts") - .insert({ name }) - .select("*") - .single(); + const { data, error } = await supabase.from("accounts").insert({ name }).select("*").single(); if (error) { console.error("Error creating account:", error); diff --git a/lib/supabase/accounts/deleteAccountById.ts b/lib/supabase/accounts/deleteAccountById.ts index 095044431..2285ae6a6 100644 --- a/lib/supabase/accounts/deleteAccountById.ts +++ b/lib/supabase/accounts/deleteAccountById.ts @@ -5,16 +5,13 @@ import supabase from "@/lib/supabase/serverClient"; * This will cascade delete related records due to foreign key constraints * including: account_info, account_socials, rooms, and other dependent records * - * @param accountId The ID of the account to delete + * @param accountId - The ID of the account to delete * @returns Object with success status, metadata, and any error */ export async function deleteAccountById(accountId: string) { try { // Delete the account directly - const { error } = await supabase - .from("accounts") - .delete() - .eq("id", accountId); + const { error } = await supabase.from("accounts").delete().eq("id", accountId); if (error) { console.error("Error deleting account:", error); diff --git a/lib/supabase/accounts/getAccountByEmail.ts b/lib/supabase/accounts/getAccountByEmail.ts index d36cf8116..d3e4113d1 100644 --- a/lib/supabase/accounts/getAccountByEmail.ts +++ b/lib/supabase/accounts/getAccountByEmail.ts @@ -1,14 +1,8 @@ import supabase from "@/lib/supabase/serverClient"; import type { Tables } from "@/types/database.types"; -const getAccountByEmail = async ( - email: string -): Promise | null> => { - const { data } = await supabase - .from("account_emails") - .select("*") - .eq("email", email) - .single(); +const getAccountByEmail = async (email: string): Promise | null> => { + const { data } = await supabase.from("account_emails").select("*").eq("email", email).single(); return data || null; }; diff --git a/lib/supabase/accounts/getAccountById.ts b/lib/supabase/accounts/getAccountById.ts index d9484dd6d..033c3d9f6 100644 --- a/lib/supabase/accounts/getAccountById.ts +++ b/lib/supabase/accounts/getAccountById.ts @@ -7,14 +7,10 @@ export type AccountWithInfoAndEmail = Tables<"accounts"> & { account_wallets: Array>; }; -const getAccountById = async ( - id: string -): Promise => { +const getAccountById = async (id: string): Promise => { const { data, error } = await supabase .from("accounts") - .select( - "*, account_emails(email), account_info(*), account_wallets(wallet)" - ) + .select("*, account_emails(email), account_info(*), account_wallets(wallet)") .eq("id", id) .single(); diff --git a/lib/supabase/accounts/getAccountByWallet.ts b/lib/supabase/accounts/getAccountByWallet.ts index fe530de27..ad95da27f 100644 --- a/lib/supabase/accounts/getAccountByWallet.ts +++ b/lib/supabase/accounts/getAccountByWallet.ts @@ -1,5 +1,9 @@ import supabase from "@/lib/supabase/serverClient"; +/** + * + * @param wallet + */ export async function getAccountByWallet(wallet: string) { const { data: walletFound, error: walletError } = await supabase .from("account_wallets") diff --git a/lib/supabase/accounts/getAccountWithDetails.ts b/lib/supabase/accounts/getAccountWithDetails.ts index f2d43af6b..d8f89c455 100644 --- a/lib/supabase/accounts/getAccountWithDetails.ts +++ b/lib/supabase/accounts/getAccountWithDetails.ts @@ -6,14 +6,9 @@ type AccountInfo = Tables<"account_info">; type AccountEmail = Tables<"account_emails">; type AccountWallet = Tables<"account_wallets">; -export type AccountWithDetails = AccountInfo & - AccountEmail & - AccountWallet & - Account; +export type AccountWithDetails = AccountInfo & AccountEmail & AccountWallet & Account; -export const getAccountWithDetails = async ( - accountId: string -): Promise => { +export const getAccountWithDetails = async (accountId: string): Promise => { const { data: account } = await supabase .from("accounts") .select("*, account_info(*), account_emails(*), account_wallets(*)") diff --git a/lib/supabase/accounts/insertAccount.ts b/lib/supabase/accounts/insertAccount.ts index 802283dc2..ce8e4e4c4 100644 --- a/lib/supabase/accounts/insertAccount.ts +++ b/lib/supabase/accounts/insertAccount.ts @@ -3,12 +3,12 @@ import type { Tables } from "@/types/database.types"; /** * Insert a new account (used on login/signup) - * @param account Account data to insert + * + * @param account - Account data to insert + * @param account.name * @returns Created account or null if failed */ -const insertAccount = async (account: { - name: string; -}): Promise | null> => { +const insertAccount = async (account: { name: string }): Promise | null> => { const { data } = await supabase .from("accounts") .insert({ ...account }) diff --git a/lib/supabase/accounts/insertAccountWallet.ts b/lib/supabase/accounts/insertAccountWallet.ts index ba95c688f..df4ca8520 100644 --- a/lib/supabase/accounts/insertAccountWallet.ts +++ b/lib/supabase/accounts/insertAccountWallet.ts @@ -1,5 +1,10 @@ import supabase from "@/lib/supabase/serverClient"; +/** + * + * @param accountId + * @param wallet + */ export async function insertAccountWallet(accountId: string, wallet: string) { const { data, error } = await supabase .from("account_wallets") diff --git a/lib/supabase/accounts/updateAccount.ts b/lib/supabase/accounts/updateAccount.ts index 8769fa5f7..e42cefab5 100644 --- a/lib/supabase/accounts/updateAccount.ts +++ b/lib/supabase/accounts/updateAccount.ts @@ -3,14 +3,9 @@ import type { Tables } from "@/types/database.types"; const updateAccount = async ( id: string, - update: Partial> + update: Partial>, ): Promise | null> => { - const { data } = await supabase - .from("accounts") - .update(update) - .eq("id", id) - .select("*") - .single(); + const { data } = await supabase.from("accounts").update(update).eq("id", id).select("*").single(); return data || null; }; diff --git a/lib/supabase/agent_templates/addAgentTemplateFavorite.ts b/lib/supabase/agent_templates/addAgentTemplateFavorite.ts index 261909c15..fcedf78be 100644 --- a/lib/supabase/agent_templates/addAgentTemplateFavorite.ts +++ b/lib/supabase/agent_templates/addAgentTemplateFavorite.ts @@ -1,18 +1,20 @@ import supabase from "@/lib/supabase/serverClient"; -export async function addAgentTemplateFavorite( - templateId: string, - userId: string -) { +/** + * + * @param templateId + * @param userId + */ +export async function addAgentTemplateFavorite(templateId: string, userId: string) { const { error } = await supabase .from("agent_template_favorites") .insert({ template_id: templateId, user_id: userId }) .select("template_id") .maybeSingle(); - + if (error && error.code !== "23505") { throw error; // ignore unique violation } - + return { success: true } as const; } diff --git a/lib/supabase/agent_templates/createAgentTemplate.ts b/lib/supabase/agent_templates/createAgentTemplate.ts index 276f6e31c..42c38196b 100644 --- a/lib/supabase/agent_templates/createAgentTemplate.ts +++ b/lib/supabase/agent_templates/createAgentTemplate.ts @@ -1,6 +1,17 @@ import supabase from "@/lib/supabase/serverClient"; import { createAgentTemplateShares } from "./createAgentTemplateShares"; +/** + * + * @param params + * @param params.title + * @param params.description + * @param params.prompt + * @param params.tags + * @param params.isPrivate + * @param params.shareEmails + * @param params.userId + */ export async function createAgentTemplate(params: { title: string; description: string; @@ -20,7 +31,9 @@ export async function createAgentTemplate(params: { is_private: params.isPrivate, creator: params.userId ?? null, }) - .select("id, title, description, prompt, tags, creator, is_private, created_at, favorites_count") + .select( + "id, title, description, prompt, tags, creator, is_private, created_at, favorites_count", + ) .single(); if (error) throw error; diff --git a/lib/supabase/agent_templates/createAgentTemplateShares.ts b/lib/supabase/agent_templates/createAgentTemplateShares.ts index c2714b57e..c90d89503 100644 --- a/lib/supabase/agent_templates/createAgentTemplateShares.ts +++ b/lib/supabase/agent_templates/createAgentTemplateShares.ts @@ -3,12 +3,13 @@ import { insertAgentTemplateShares } from "./insertAgentTemplateShares"; /** * Create agent template shares for multiple email addresses + * * @param templateId - The template ID to share * @param emails - Array of email addresses to share with */ export async function createAgentTemplateShares( templateId: string, - emails: string[] + emails: string[], ): Promise { if (!emails || emails.length === 0) { return; @@ -21,14 +22,14 @@ export async function createAgentTemplateShares( return; } - // Create share records for found users (filter out null account_ids) - const sharesData = userEmails - .filter(userEmail => userEmail.account_id !== null) - .map(userEmail => ({ - template_id: templateId, - user_id: userEmail.account_id!, - })); + // Create share records for found users (filter out null account_ids) + const sharesData = userEmails + .filter(userEmail => userEmail.account_id !== null) + .map(userEmail => ({ + template_id: templateId, + user_id: userEmail.account_id!, + })); - // Insert shares using utility function - await insertAgentTemplateShares(sharesData); + // Insert shares using utility function + await insertAgentTemplateShares(sharesData); } diff --git a/lib/supabase/agent_templates/deleteAgentTemplate.ts b/lib/supabase/agent_templates/deleteAgentTemplate.ts index e1212334c..af2ae0d65 100644 --- a/lib/supabase/agent_templates/deleteAgentTemplate.ts +++ b/lib/supabase/agent_templates/deleteAgentTemplate.ts @@ -1,9 +1,11 @@ import supabase from "@/lib/supabase/serverClient"; +/** + * + * @param id + */ export async function deleteAgentTemplate(id: string) { const { error } = await supabase.from("agent_templates").delete().eq("id", id); if (error) throw error; return { success: true } as const; } - - diff --git a/lib/supabase/agent_templates/deleteAgentTemplateShares.ts b/lib/supabase/agent_templates/deleteAgentTemplateShares.ts index cd3e74c2a..906d02e0d 100644 --- a/lib/supabase/agent_templates/deleteAgentTemplateShares.ts +++ b/lib/supabase/agent_templates/deleteAgentTemplateShares.ts @@ -9,11 +9,12 @@ interface AgentTemplateShare { /** * Delete all agent template shares for a specific template - * @param templateId The template ID to delete shares for + * + * @param templateId - The template ID to delete shares for * @returns Array of deleted share records */ export async function deleteAgentTemplateSharesByTemplateId( - templateId: string + templateId: string, ): Promise { const { data, error } = await supabase .from("agent_template_shares") diff --git a/lib/supabase/agent_templates/getAgentTemplateSharesByTemplateIds.ts b/lib/supabase/agent_templates/getAgentTemplateSharesByTemplateIds.ts index 27326f4b6..2a5db77a9 100644 --- a/lib/supabase/agent_templates/getAgentTemplateSharesByTemplateIds.ts +++ b/lib/supabase/agent_templates/getAgentTemplateSharesByTemplateIds.ts @@ -8,11 +8,12 @@ interface AgentTemplateShare { /** * Get all agent template shares for specific template IDs - * @param templateIds Array of template IDs to get shares for + * + * @param templateIds - Array of template IDs to get shares for * @returns Array of share records */ export async function getAgentTemplateSharesByTemplateIds( - templateIds: string[] + templateIds: string[], ): Promise { if (!Array.isArray(templateIds) || templateIds.length === 0) return []; diff --git a/lib/supabase/agent_templates/getAgentTemplates.ts b/lib/supabase/agent_templates/getAgentTemplates.ts index 70cfaf61b..2ee601d33 100644 --- a/lib/supabase/agent_templates/getAgentTemplates.ts +++ b/lib/supabase/agent_templates/getAgentTemplates.ts @@ -1,14 +1,17 @@ import supabase from "@/lib/supabase/serverClient"; +/** + * + */ export async function getAgentTemplates() { const { data, error } = await supabase - .from('agent_templates') - .select('id, title, description, prompt, tags, creator, is_private') - .order('title'); - + .from("agent_templates") + .select("id, title, description, prompt, tags, creator, is_private") + .order("title"); + if (error) { throw error; } - + return data || []; -} \ No newline at end of file +} diff --git a/lib/supabase/agent_templates/getSharedEmailsForTemplates.ts b/lib/supabase/agent_templates/getSharedEmailsForTemplates.ts index 0a2fa8874..30d35bdf9 100644 --- a/lib/supabase/agent_templates/getSharedEmailsForTemplates.ts +++ b/lib/supabase/agent_templates/getSharedEmailsForTemplates.ts @@ -1,7 +1,13 @@ import getAccountEmails from "@/lib/supabase/account_emails/getAccountEmails"; import { getAgentTemplateSharesByTemplateIds } from "./getAgentTemplateSharesByTemplateIds"; -export async function getSharedEmailsForTemplates(templateIds: string[]): Promise> { +/** + * + * @param templateIds + */ +export async function getSharedEmailsForTemplates( + templateIds: string[], +): Promise> { if (!templateIds || templateIds.length === 0) return {}; // Get all shares for these templates using existing utility diff --git a/lib/supabase/agent_templates/getSharedTemplatesForUser.ts b/lib/supabase/agent_templates/getSharedTemplatesForUser.ts index b9d0a23e8..959888d9e 100644 --- a/lib/supabase/agent_templates/getSharedTemplatesForUser.ts +++ b/lib/supabase/agent_templates/getSharedTemplatesForUser.ts @@ -5,14 +5,20 @@ interface SharedTemplateData { templates: AgentTemplateRow | AgentTemplateRow[]; } +/** + * + * @param userId + */ export async function getSharedTemplatesForUser(userId: string): Promise { const { data, error } = await supabase .from("agent_template_shares") - .select(` + .select( + ` templates:agent_templates( id, title, description, prompt, tags, creator, is_private, created_at, favorites_count, updated_at ) - `) + `, + ) .eq("user_id", userId); if (error) throw error; @@ -24,9 +30,7 @@ export async function getSharedTemplatesForUser(userId: string): Promise { if (template && template.id && !processedIds.has(template.id)) { diff --git a/lib/supabase/agent_templates/getUserAccessibleTemplates.ts b/lib/supabase/agent_templates/getUserAccessibleTemplates.ts index 0762f54e6..561cfa77f 100644 --- a/lib/supabase/agent_templates/getUserAccessibleTemplates.ts +++ b/lib/supabase/agent_templates/getUserAccessibleTemplates.ts @@ -3,6 +3,10 @@ import { listAgentTemplatesForUser } from "./listAgentTemplatesForUser"; import { getSharedTemplatesForUser } from "./getSharedTemplatesForUser"; import { getUserTemplateFavorites } from "./getUserTemplateFavorites"; +/** + * + * @param userId + */ export async function getUserAccessibleTemplates(userId?: string | null) { if (userId && userId !== "undefined") { // Get owned and public templates @@ -15,7 +19,7 @@ export async function getUserAccessibleTemplates(userId?: string | null) { const allTemplates = [...ownedAndPublic]; const templateIds = new Set(ownedAndPublic.map(t => t.id)); - sharedTemplates.forEach((template) => { + sharedTemplates.forEach(template => { if (!templateIds.has(template.id)) { allTemplates.push(template); templateIds.add(template.id); @@ -36,8 +40,6 @@ export async function getUserAccessibleTemplates(userId?: string | null) { const publicTemplates = await listAgentTemplatesForUser(null); return publicTemplates.map((template: AgentTemplateRow) => ({ ...template, - is_favourite: false + is_favourite: false, })); } - - diff --git a/lib/supabase/agent_templates/getUserTemplateFavorites.ts b/lib/supabase/agent_templates/getUserTemplateFavorites.ts index 04a9bc6a3..7a8420ead 100644 --- a/lib/supabase/agent_templates/getUserTemplateFavorites.ts +++ b/lib/supabase/agent_templates/getUserTemplateFavorites.ts @@ -4,6 +4,10 @@ interface TemplateFavorite { template_id: string; } +/** + * + * @param userId + */ export async function getUserTemplateFavorites(userId: string): Promise> { const { data, error } = await supabase .from("agent_template_favorites") @@ -12,7 +16,5 @@ export async function getUserTemplateFavorites(userId: string): Promise( - (data || []).map((f: TemplateFavorite) => f.template_id) - ); + return new Set((data || []).map((f: TemplateFavorite) => f.template_id)); } diff --git a/lib/supabase/agent_templates/insertAgentTemplateShares.ts b/lib/supabase/agent_templates/insertAgentTemplateShares.ts index 23677c60a..72eeba153 100644 --- a/lib/supabase/agent_templates/insertAgentTemplateShares.ts +++ b/lib/supabase/agent_templates/insertAgentTemplateShares.ts @@ -14,11 +14,12 @@ interface AgentTemplateShareInsert { /** * Insert multiple agent template shares - * @param shares Array of share records to insert + * + * @param shares - Array of share records to insert * @returns Array of inserted share records */ export async function insertAgentTemplateShares( - shares: AgentTemplateShareInsert[] + shares: AgentTemplateShareInsert[], ): Promise { if (!Array.isArray(shares) || shares.length === 0) { return []; @@ -28,7 +29,7 @@ export async function insertAgentTemplateShares( .from("agent_template_shares") .upsert(shares, { onConflict: "template_id,user_id", - ignoreDuplicates: true + ignoreDuplicates: true, }) .select(); diff --git a/lib/supabase/agent_templates/listAgentTemplatesForUser.ts b/lib/supabase/agent_templates/listAgentTemplatesForUser.ts index f1674d463..7e0fe4618 100644 --- a/lib/supabase/agent_templates/listAgentTemplatesForUser.ts +++ b/lib/supabase/agent_templates/listAgentTemplatesForUser.ts @@ -1,10 +1,16 @@ import supabase from "@/lib/supabase/serverClient"; +/** + * + * @param userId + */ export async function listAgentTemplatesForUser(userId?: string | null) { if (userId && userId !== "undefined") { const { data, error } = await supabase .from("agent_templates") - .select("id, title, description, prompt, tags, creator, is_private, created_at, favorites_count, updated_at") + .select( + "id, title, description, prompt, tags, creator, is_private, created_at, favorites_count, updated_at", + ) .or(`creator.eq.${userId},is_private.eq.false`) .order("title"); if (error) throw error; @@ -13,11 +19,11 @@ export async function listAgentTemplatesForUser(userId?: string | null) { const { data, error } = await supabase .from("agent_templates") - .select("id, title, description, prompt, tags, creator, is_private, created_at, favorites_count, updated_at") + .select( + "id, title, description, prompt, tags, creator, is_private, created_at, favorites_count, updated_at", + ) .eq("is_private", false) .order("title"); if (error) throw error; return data ?? []; } - - diff --git a/lib/supabase/agent_templates/removeAgentTemplateFavorite.ts b/lib/supabase/agent_templates/removeAgentTemplateFavorite.ts index aff40ec1c..a341b8065 100644 --- a/lib/supabase/agent_templates/removeAgentTemplateFavorite.ts +++ b/lib/supabase/agent_templates/removeAgentTemplateFavorite.ts @@ -1,18 +1,20 @@ import supabase from "@/lib/supabase/serverClient"; -export async function removeAgentTemplateFavorite( - templateId: string, - userId: string -) { +/** + * + * @param templateId + * @param userId + */ +export async function removeAgentTemplateFavorite(templateId: string, userId: string) { const { error } = await supabase .from("agent_template_favorites") .delete() .eq("template_id", templateId) .eq("user_id", userId); - + if (error) { throw error; } - + return { success: true } as const; } diff --git a/lib/supabase/agent_templates/updateAgentTemplate.ts b/lib/supabase/agent_templates/updateAgentTemplate.ts index 1c489cf60..922aec661 100644 --- a/lib/supabase/agent_templates/updateAgentTemplate.ts +++ b/lib/supabase/agent_templates/updateAgentTemplate.ts @@ -9,10 +9,16 @@ export type AgentTemplateUpdates = { is_private?: boolean; }; +/** + * + * @param id + * @param updates + * @param shareEmails + */ export async function updateAgentTemplate( id: string, updates: AgentTemplateUpdates, - shareEmails?: string[] + shareEmails?: string[], ) { const { data, error } = await supabase .from("agent_templates") @@ -22,7 +28,7 @@ export async function updateAgentTemplate( }) .eq("id", id) .select( - "id, title, description, prompt, tags, creator, is_private, created_at, favorites_count, updated_at" + "id, title, description, prompt, tags, creator, is_private, created_at, favorites_count, updated_at", ) .single(); if (error) throw error; @@ -33,4 +39,4 @@ export async function updateAgentTemplate( } return data; -} \ No newline at end of file +} diff --git a/lib/supabase/agent_templates/updateAgentTemplateShares.ts b/lib/supabase/agent_templates/updateAgentTemplateShares.ts index 3ea34298e..0244aaa6f 100644 --- a/lib/supabase/agent_templates/updateAgentTemplateShares.ts +++ b/lib/supabase/agent_templates/updateAgentTemplateShares.ts @@ -4,12 +4,13 @@ import { insertAgentTemplateShares } from "./insertAgentTemplateShares"; /** * Update agent template shares - replaces existing shares with new ones + * * @param templateId - The template ID to update shares for * @param emails - Array of email addresses to share with (replaces existing) */ export async function updateAgentTemplateShares( templateId: string, - emails: string[] + emails: string[], ): Promise { // First, delete existing shares await deleteAgentTemplateSharesByTemplateId(templateId); diff --git a/lib/supabase/agent_templates/verifyAgentTemplateOwner.ts b/lib/supabase/agent_templates/verifyAgentTemplateOwner.ts index 668b090de..45d2e4a91 100644 --- a/lib/supabase/agent_templates/verifyAgentTemplateOwner.ts +++ b/lib/supabase/agent_templates/verifyAgentTemplateOwner.ts @@ -1,5 +1,10 @@ import supabase from "@/lib/supabase/serverClient"; +/** + * + * @param id + * @param userId + */ export async function verifyAgentTemplateOwner(id: string, userId: string): Promise { const { data, error } = await supabase .from("agent_templates") @@ -9,5 +14,3 @@ export async function verifyAgentTemplateOwner(id: string, userId: string): Prom if (error) throw error; return Boolean(data && data.creator === userId); } - - diff --git a/lib/supabase/artist/associateArtistWithAccount.ts b/lib/supabase/artist/associateArtistWithAccount.ts index c728dae44..970575d84 100644 --- a/lib/supabase/artist/associateArtistWithAccount.ts +++ b/lib/supabase/artist/associateArtistWithAccount.ts @@ -2,14 +2,12 @@ import supabase from "@/lib/supabase/serverClient"; /** * Associate an artist with a user account - * @param account_id ID of the user account - * @param artist_id ID of the artist account + * + * @param account_id - ID of the user account + * @param artist_id - ID of the artist account * @returns True if successful, false if failed */ -export async function associateArtistWithAccount( - account_id: string, - artist_id: string -) { +export async function associateArtistWithAccount(account_id: string, artist_id: string) { try { const { error } = await supabase.from("account_artist_ids").insert({ account_id, diff --git a/lib/supabase/artist/createAccountInfo.ts b/lib/supabase/artist/createAccountInfo.ts index 6c1a10be9..d5c5daf5c 100644 --- a/lib/supabase/artist/createAccountInfo.ts +++ b/lib/supabase/artist/createAccountInfo.ts @@ -2,14 +2,13 @@ import supabase from "@/lib/supabase/serverClient"; /** * Create account info for a new artist - * @param account_id ID of the artist account + * + * @param account_id - ID of the artist account * @returns True if successful, false if failed */ export async function createAccountInfo(account_id: string) { try { - const { error } = await supabase - .from("account_info") - .insert({ account_id }); + const { error } = await supabase.from("account_info").insert({ account_id }); if (error) { console.error("Error creating account info:", error); diff --git a/lib/supabase/artist/getArtistById.ts b/lib/supabase/artist/getArtistById.ts index 649ddb079..b2a2a1019 100644 --- a/lib/supabase/artist/getArtistById.ts +++ b/lib/supabase/artist/getArtistById.ts @@ -2,7 +2,8 @@ import supabase from "@/lib/supabase/serverClient"; /** * Get full artist data including account_socials and account_info - * @param id ID of the artist account + * + * @param id - ID of the artist account * @returns Artist data or null if not found */ export async function getArtistById(id: string) { diff --git a/lib/supabase/artist_organization_ids/addArtistToOrganization.ts b/lib/supabase/artist_organization_ids/addArtistToOrganization.ts index e97b71057..d3a4302a3 100644 --- a/lib/supabase/artist_organization_ids/addArtistToOrganization.ts +++ b/lib/supabase/artist_organization_ids/addArtistToOrganization.ts @@ -10,7 +10,7 @@ import supabase from "@/lib/supabase/serverClient"; */ export async function addArtistToOrganization( artistId: string, - organizationId: string + organizationId: string, ): Promise { if (!artistId || !organizationId) return null; @@ -21,7 +21,7 @@ export async function addArtistToOrganization( .from("artist_organization_ids") .upsert( { artist_id: artistId, organization_id: organizationId }, - { onConflict: "artist_id,organization_id", ignoreDuplicates: false } + { onConflict: "artist_id,organization_id", ignoreDuplicates: false }, ) .select("id") .single(); @@ -37,4 +37,3 @@ export async function addArtistToOrganization( } export default addArtistToOrganization; - diff --git a/lib/supabase/artist_segments/insertArtistSegments.ts b/lib/supabase/artist_segments/insertArtistSegments.ts index 53bd53400..20645e534 100644 --- a/lib/supabase/artist_segments/insertArtistSegments.ts +++ b/lib/supabase/artist_segments/insertArtistSegments.ts @@ -5,7 +5,7 @@ type ArtistSegment = Tables<"artist_segments">; type ArtistSegmentInsert = Partial; export const insertArtistSegments = async ( - artistSegments: ArtistSegmentInsert[] + artistSegments: ArtistSegmentInsert[], ): Promise => { const { data, error } = await serverClient .from("artist_segments") @@ -18,4 +18,4 @@ export const insertArtistSegments = async ( } return data; -}; \ No newline at end of file +}; diff --git a/lib/supabase/copyMessages.ts b/lib/supabase/copyMessages.ts index c255bba11..a1b49e879 100644 --- a/lib/supabase/copyMessages.ts +++ b/lib/supabase/copyMessages.ts @@ -4,11 +4,15 @@ import supabase from "./serverClient"; /** * Copies messages from source room to target room + * + * @param sourceRoomId + * @param targetRoomId + * @param clearExisting */ async function copyMessages( sourceRoomId: string, targetRoomId: string, - clearExisting: boolean + clearExisting: boolean, ): Promise { try { // Get messages from source room @@ -26,7 +30,7 @@ async function copyMessages( } // Prepare new messages - const newMessages = messages.map((msg) => ({ + const newMessages = messages.map(msg => ({ id: generateUUID(), room_id: targetRoomId, content: msg.content, diff --git a/lib/supabase/createArtistInDb.ts b/lib/supabase/createArtistInDb.ts index 76397d024..60b3060e9 100644 --- a/lib/supabase/createArtistInDb.ts +++ b/lib/supabase/createArtistInDb.ts @@ -7,17 +7,18 @@ import { addArtistToOrganization } from "./artist_organization_ids/addArtistToOr /** * Create a new account in the database and associate it with an owner account - * @param name Name of the account to create - * @param account_id ID of the owner account that will have access - * @param isWorkspace If true, creates a workspace; if false, creates an artist (default) - * @param organizationId Optional organization ID to link the new artist to + * + * @param name - Name of the account to create + * @param account_id - ID of the owner account that will have access + * @param isWorkspace - If true, creates a workspace; if false, creates an artist (default) + * @param organizationId - Optional organization ID to link the new artist to * @returns Created account object or null if creation failed */ export async function createArtistInDb( name: string, account_id: string, isWorkspace: boolean = false, - organizationId?: string | null + organizationId?: string | null, ) { try { // Step 1: Create the account (no account_type needed) @@ -52,16 +53,16 @@ export async function createArtistInDb( // This avoids fragile spread-order dependencies where account_info.id // could accidentally overwrite accounts.id const accountInfo = artist.account_info?.[0]; - + return { // Core account fields from accounts table id: artist.id, - account_id: artist.id, // Alias used by ArtistRecord type + account_id: artist.id, // Alias used by ArtistRecord type name: artist.name, - isWorkspace, // Return this so UI knows what type it is + isWorkspace, // Return this so UI knows what type it is created_at: artist.created_at, updated_at: artist.updated_at, - + // Profile fields from account_info table (explicitly picked, not spread) image: accountInfo?.image ?? null, instruction: accountInfo?.instruction ?? null, @@ -73,7 +74,7 @@ export async function createArtistInDb( role_type: accountInfo?.role_type ?? null, onboarding_status: accountInfo?.onboarding_status ?? null, onboarding_data: accountInfo?.onboarding_data ?? null, - + // Related data arrays account_info: artist.account_info, account_socials: artist.account_socials, diff --git a/lib/supabase/createNewRoom.ts b/lib/supabase/createNewRoom.ts index 401ee2578..28691bac3 100644 --- a/lib/supabase/createNewRoom.ts +++ b/lib/supabase/createNewRoom.ts @@ -14,12 +14,10 @@ interface CreateRoomParams { /** * Creates a new room in the database * - * @param params Parameters for room creation + * @param params - Parameters for room creation * @returns The ID of the newly created room or null if creation failed */ -export async function createNewRoom( - params: CreateRoomParams -): Promise { +export async function createNewRoom(params: CreateRoomParams): Promise { try { const { account_id, artist_id } = params; diff --git a/lib/supabase/createSegmentRoom.ts b/lib/supabase/createSegmentRoom.ts index 5b91f0058..dd124b94c 100644 --- a/lib/supabase/createSegmentRoom.ts +++ b/lib/supabase/createSegmentRoom.ts @@ -5,10 +5,7 @@ interface CreateSegmentRoomParams { room_id: string; } -export const createSegmentRoom = async ({ - segment_id, - room_id, -}: CreateSegmentRoomParams) => { +export const createSegmentRoom = async ({ segment_id, room_id }: CreateSegmentRoomParams) => { const { data, error } = await supabase .from("segment_rooms") .insert({ diff --git a/lib/supabase/credits_usage/selectCreditsUsage.ts b/lib/supabase/credits_usage/selectCreditsUsage.ts index 92a412b78..8755829d1 100644 --- a/lib/supabase/credits_usage/selectCreditsUsage.ts +++ b/lib/supabase/credits_usage/selectCreditsUsage.ts @@ -8,7 +8,7 @@ interface SelectCreditsUsageParams { } export const selectCreditsUsage = async ( - params?: SelectCreditsUsageParams + params?: SelectCreditsUsageParams, ): Promise => { let query = serverClient.from("credits_usage").select(); diff --git a/lib/supabase/deleteMemoriesByChatIdAfterTimestamp.ts b/lib/supabase/deleteMemoriesByChatIdAfterTimestamp.ts index 9ef1a82fe..090641f30 100644 --- a/lib/supabase/deleteMemoriesByChatIdAfterTimestamp.ts +++ b/lib/supabase/deleteMemoriesByChatIdAfterTimestamp.ts @@ -1,5 +1,11 @@ import supabase from "./serverClient"; +/** + * + * @param root0 + * @param root0.roomId + * @param root0.timestamp + */ export async function deleteMemoriesByRoomIdAfterTimestamp({ roomId, timestamp, @@ -21,9 +27,7 @@ export async function deleteMemoriesByRoomIdAfterTimestamp({ return count || 0; } catch (error) { - console.error( - "Failed to delete memories by room_id after timestamp from database" - ); + console.error("Failed to delete memories by room_id after timestamp from database"); throw error; } } diff --git a/lib/supabase/ensureArtistAccess.ts b/lib/supabase/ensureArtistAccess.ts index 1a25077d8..e27a00334 100644 --- a/lib/supabase/ensureArtistAccess.ts +++ b/lib/supabase/ensureArtistAccess.ts @@ -2,23 +2,17 @@ import supabase from "./serverClient"; /** * Ensures the user has access to an artist - * @param artistId The ID of the artist to ensure access to - * @param accountId The ID of the user to grant access to + * + * @param artistId - The ID of the artist to ensure access to + * @param accountId - The ID of the user to grant access to * @returns True if access was newly granted, false if the user already had access */ -export async function ensureArtistAccess( - artistId: string, - accountId: string -): Promise { +export async function ensureArtistAccess(artistId: string, accountId: string): Promise { try { // Run both checks in parallel - they're independent const [artistResult, accessResult] = await Promise.all([ // Verify the artist exists - supabase - .from("accounts") - .select("id") - .eq("id", artistId) - .single(), + supabase.from("accounts").select("id").eq("id", artistId).single(), // Check if user already has access supabase .from("account_artist_ids") @@ -34,16 +28,14 @@ export async function ensureArtistAccess( if (accessResult.data) return false; // Grant access - const { error: insertError } = await supabase - .from("account_artist_ids") - .insert({ - account_id: accountId, - artist_id: artistId, - }); + const { error: insertError } = await supabase.from("account_artist_ids").insert({ + account_id: accountId, + artist_id: artistId, + }); return !insertError; } catch (error) { console.error("Error ensuring artist access:", error); return false; } -} \ No newline at end of file +} diff --git a/lib/supabase/ensureRoomAccess.ts b/lib/supabase/ensureRoomAccess.ts index e679331b6..bee74bd61 100644 --- a/lib/supabase/ensureRoomAccess.ts +++ b/lib/supabase/ensureRoomAccess.ts @@ -5,13 +5,14 @@ import copyRoomMessages from "./copyMessages"; /** * Ensures the user has access to a room by creating a copy of a shared room - * @param sourceRoomId The ID of the original room being shared - * @param accountId The ID of the user to grant access to + * + * @param sourceRoomId - The ID of the original room being shared + * @param accountId - The ID of the user to grant access to * @returns The ID of the user's room (new or existing) */ export async function ensureRoomAccess( sourceRoomId: string, - accountId: string + accountId: string, ): Promise { try { // Check for existing room diff --git a/lib/supabase/error_logs/createErrorLog.ts b/lib/supabase/error_logs/createErrorLog.ts index 9a0e77923..6ff101e4c 100644 --- a/lib/supabase/error_logs/createErrorLog.ts +++ b/lib/supabase/error_logs/createErrorLog.ts @@ -15,10 +15,10 @@ type ErrorLogInsert = Database["public"]["Tables"]["error_logs"]["Insert"]; * Creates an error log entry in Supabase error_logs table * Maps ErrorContext data to appropriate database columns * Handles account lookup and graceful error handling + * + * @param params */ -export async function createErrorLog( - params: CreateErrorLogParams -): Promise { +export async function createErrorLog(params: CreateErrorLogParams): Promise { try { // Look up account_id from email if provided let account_id: string | null = null; @@ -36,7 +36,7 @@ export async function createErrorLog( const last_message = params.messages && params.messages.length > 0 ? params.messages[params.messages.length - 1]?.parts - .filter((part) => part.type === "text") + .filter(part => part.type === "text") .join("") : null; @@ -47,7 +47,7 @@ export async function createErrorLog( const tool_name = extractToolNameFromError( params.error.message || "", params.error.stack || "", - params.error.name || "" + params.error.name || "", ); // Insert error log record diff --git a/lib/supabase/error_logs/errorTypeParser.ts b/lib/supabase/error_logs/errorTypeParser.ts index 5520843ef..74c399c41 100644 --- a/lib/supabase/error_logs/errorTypeParser.ts +++ b/lib/supabase/error_logs/errorTypeParser.ts @@ -11,20 +11,21 @@ export function extractToolNameFromError( errorMessage: string, stackTrace: string, - errorType: string + errorType: string, ): string | null { // Rate limit errors - if (errorType === 'AI_RetryError' && ( - errorMessage.includes('rate limit') || - errorMessage.includes('Rate limit') || - errorMessage.includes('input tokens per minute') - )) { - return 'Rate Limit'; + if ( + errorType === "AI_RetryError" && + (errorMessage.includes("rate limit") || + errorMessage.includes("Rate limit") || + errorMessage.includes("input tokens per minute")) + ) { + return "Rate Limit"; } // Object serialization errors - if (errorMessage.includes('[object Object]') || errorMessage.includes('object Object')) { - return 'ObjectObject'; + if (errorMessage.includes("[object Object]") || errorMessage.includes("object Object")) { + return "ObjectObject"; } // Tool execution errors - extract actual tool name @@ -33,12 +34,12 @@ export function extractToolNameFromError( // Check for specific tool patterns in error messages or stack traces const toolPatterns = [ - { pattern: /get_spotify_artist_top_tracks/, name: 'get_spotify_artist_top_tracks' }, - { pattern: /search_web/, name: 'search_web' }, - { pattern: /searchTwitter/, name: 'searchTwitter' }, - { pattern: /contactTeam/, name: 'contactTeam' }, - { pattern: /createArtist/, name: 'createArtist' }, - { pattern: /sendEmail/, name: 'sendEmail' }, + { pattern: /get_spotify_artist_top_tracks/, name: "get_spotify_artist_top_tracks" }, + { pattern: /search_web/, name: "search_web" }, + { pattern: /searchTwitter/, name: "searchTwitter" }, + { pattern: /contactTeam/, name: "contactTeam" }, + { pattern: /createArtist/, name: "createArtist" }, + { pattern: /sendEmail/, name: "sendEmail" }, // Add more as needed ]; @@ -50,9 +51,9 @@ export function extractToolNameFromError( // Stack trace patterns for tool detection const stackToolPatterns = [ - /\/tools\/([^\/\s]+)/, // /tools/toolName - /Tool\.([^\.\s]+)/, // Tool.toolName - /tool[._]([^\.\s]+)/i, // tool.name or tool_name + /\/tools\/([^\/\s]+)/, // /tools/toolName + /Tool\.([^\.\s]+)/, // Tool.toolName + /tool[._]([^\.\s]+)/i, // tool.name or tool_name ]; for (const pattern of stackToolPatterns) { @@ -63,10 +64,10 @@ export function extractToolNameFromError( } // Generic error type mapping - if (errorType === 'TypeError') return 'TypeError'; - if (errorType === 'ReferenceError') return 'ReferenceError'; - if (errorType === 'SyntaxError') return 'SyntaxError'; - + if (errorType === "TypeError") return "TypeError"; + if (errorType === "ReferenceError") return "ReferenceError"; + if (errorType === "SyntaxError") return "SyntaxError"; + return null; } @@ -74,4 +75,4 @@ export function extractToolNameFromError( * Example usage: * extractToolNameFromError('Error executing tool get_spotify_artist_top_tracks: ...', '', 'AI_ToolExecutionError'); * // => 'get_spotify_artist_top_tracks' - */ \ No newline at end of file + */ diff --git a/lib/supabase/fan_segments/insertFanSegments.ts b/lib/supabase/fan_segments/insertFanSegments.ts index 7ac0497b0..1e3c3211c 100644 --- a/lib/supabase/fan_segments/insertFanSegments.ts +++ b/lib/supabase/fan_segments/insertFanSegments.ts @@ -4,13 +4,8 @@ import { Tables } from "@/types/database.types"; type FanSegment = Tables<"fan_segments">; type FanSegmentInsert = Partial; -export const insertFanSegments = async ( - fanSegments: FanSegmentInsert[] -): Promise => { - const { data, error } = await serverClient - .from("fan_segments") - .insert(fanSegments) - .select(); +export const insertFanSegments = async (fanSegments: FanSegmentInsert[]): Promise => { + const { data, error } = await serverClient.from("fan_segments").insert(fanSegments).select(); if (error) { console.error("Error inserting fan segments:", error); diff --git a/lib/supabase/fan_segments/selectFanSegments.ts b/lib/supabase/fan_segments/selectFanSegments.ts index 78ffddc7f..e8f9a2e40 100644 --- a/lib/supabase/fan_segments/selectFanSegments.ts +++ b/lib/supabase/fan_segments/selectFanSegments.ts @@ -7,9 +7,7 @@ interface SelectFanSegmentsParams { segment_id: string; } -export const selectFanSegments = async ( - params: SelectFanSegmentsParams -): Promise => { +export const selectFanSegments = async (params: SelectFanSegmentsParams): Promise => { try { const { data, error } = await serverClient .from("fan_segments") diff --git a/lib/supabase/files/createFileRecord.ts b/lib/supabase/files/createFileRecord.ts index c5a061187..a0d9563c2 100644 --- a/lib/supabase/files/createFileRecord.ts +++ b/lib/supabase/files/createFileRecord.ts @@ -16,10 +16,10 @@ type CreateFileRecordParams = { /** * Create a file record in the database (server-side) + * + * @param params */ -export async function createFileRecord( - params: CreateFileRecordParams -): Promise { +export async function createFileRecord(params: CreateFileRecordParams): Promise { const { ownerAccountId, artistAccountId, diff --git a/lib/supabase/files/deleteFileRecord.ts b/lib/supabase/files/deleteFileRecord.ts index ea9941f8b..a73a7b5e5 100644 --- a/lib/supabase/files/deleteFileRecord.ts +++ b/lib/supabase/files/deleteFileRecord.ts @@ -3,13 +3,14 @@ import { isValidUUID } from "@/utils/isValidUUID"; /** * Delete a file record from the database - * @param fileId UUID of the file record to delete + * + * @param fileId - UUID of the file record to delete * @throws Error if deletion fails or fileId is invalid */ export async function deleteFileRecord(fileId: string): Promise { // Input validation: check if fileId is a non-empty string and valid UUID - if (!fileId || typeof fileId !== 'string' || fileId.trim().length === 0) { - throw new Error('File ID must be a non-empty string'); + if (!fileId || typeof fileId !== "string" || fileId.trim().length === 0) { + throw new Error("File ID must be a non-empty string"); } // Validate UUID v4 format using shared utility @@ -18,11 +19,7 @@ export async function deleteFileRecord(fileId: string): Promise { } // Perform delete and request deleted rows back - const { data, error } = await supabase - .from("files") - .delete() - .eq("id", fileId) - .select(); + const { data, error } = await supabase.from("files").delete().eq("id", fileId).select(); // Check for supabase error if (error) { @@ -34,4 +31,3 @@ export async function deleteFileRecord(fileId: string): Promise { throw new Error(`File record not found or not deleted: ${fileId}`); } } - diff --git a/lib/supabase/files/deleteFileRecords.ts b/lib/supabase/files/deleteFileRecords.ts index f797f9c70..f671aa2f0 100644 --- a/lib/supabase/files/deleteFileRecords.ts +++ b/lib/supabase/files/deleteFileRecords.ts @@ -4,7 +4,8 @@ import { isValidUUID } from "@/utils/isValidUUID"; /** * Delete multiple file records from the database * Used after successfully deleting storage blobs to maintain data integrity - * @param fileIds Array of file record UUIDs to delete + * + * @param fileIds - Array of file record UUIDs to delete * @throws Error if deletion fails or any fileId is invalid */ export async function deleteFileRecords(fileIds: string[]): Promise { @@ -15,23 +16,19 @@ export async function deleteFileRecords(fileIds: string[]): Promise { // Validate all UUIDs for (const fileId of fileIds) { - if (!fileId || typeof fileId !== 'string' || fileId.trim().length === 0) { - throw new Error('All file IDs must be non-empty strings'); + if (!fileId || typeof fileId !== "string" || fileId.trim().length === 0) { + throw new Error("All file IDs must be non-empty strings"); } - + if (!isValidUUID(fileId)) { throw new Error(`Invalid UUID format for file ID: ${fileId}`); } } // Perform batch delete - const { error } = await supabase - .from("files") - .delete() - .in("id", fileIds); + const { error } = await supabase.from("files").delete().in("id", fileIds); if (error) { throw new Error(`Failed to delete file records: ${error.message}`); } } - diff --git a/lib/supabase/files/deleteFilesInDirectory.ts b/lib/supabase/files/deleteFilesInDirectory.ts index 7734ab7f8..c8100ffe0 100644 --- a/lib/supabase/files/deleteFilesInDirectory.ts +++ b/lib/supabase/files/deleteFilesInDirectory.ts @@ -9,38 +9,40 @@ type FileRecord = { /** * Apply common filters to a query (LIKE pattern and optional exclude) * Note: Uses 'any' type to work with Supabase's dynamic query builder API - * @param query Base Supabase query to apply filters to - * @param escapedPattern Escaped LIKE pattern - * @param excludeId Optional ID to exclude from results + * + * @param query - Base Supabase query to apply filters to + * @param escapedPattern - Escaped LIKE pattern + * @param excludeId - Optional ID to exclude from results * @returns Query with filters applied */ function applyDirectoryFilters( // eslint-disable-next-line @typescript-eslint/no-explicit-any query: any, escapedPattern: string, - excludeId?: string + excludeId?: string, // eslint-disable-next-line @typescript-eslint/no-explicit-any ): any { let filteredQuery = query.like("storage_key", escapedPattern); - + if (excludeId) { filteredQuery = filteredQuery.neq("id", excludeId); } - + return filteredQuery; } /** * Delete all file records in a directory (recursive) * Returns array of storage keys that need to be deleted from storage - * @param directoryStorageKey Storage key of the directory (used as prefix match) - * @param excludeId Optional file ID to exclude from deletion (e.g., the directory itself) + * + * @param directoryStorageKey - Storage key of the directory (used as prefix match) + * @param excludeId - Optional file ID to exclude from deletion (e.g., the directory itself) * @returns Array of storage keys that were deleted from database * @throws Error if database operation fails */ export async function deleteFilesInDirectory( directoryStorageKey: string, - excludeId?: string + excludeId?: string, ): Promise { // Sanitize input by escaping special LIKE wildcard characters const escapedKey = escapeLikePattern(directoryStorageKey); @@ -50,7 +52,7 @@ export async function deleteFilesInDirectory( const selectQuery = applyDirectoryFilters( supabase.from("files").select("id, storage_key"), escapedPattern, - excludeId + excludeId, ); const { data: childFiles, error: selectError } = await selectQuery; @@ -69,7 +71,7 @@ export async function deleteFilesInDirectory( const deleteQuery = applyDirectoryFilters( supabase.from("files").delete(), escapedPattern, - excludeId + excludeId, ); const { error: deleteError } = await deleteQuery; @@ -80,4 +82,3 @@ export async function deleteFilesInDirectory( return storageKeys; } - diff --git a/lib/supabase/files/ensureDirectoryExists.ts b/lib/supabase/files/ensureDirectoryExists.ts index 4c1b471c3..be232d120 100644 --- a/lib/supabase/files/ensureDirectoryExists.ts +++ b/lib/supabase/files/ensureDirectoryExists.ts @@ -3,11 +3,15 @@ import supabase from "@/lib/supabase/serverClient"; /** * Ensure a directory exists in the database, creating it if needed * This makes directories visible in the UI + * + * @param ownerAccountId + * @param artistAccountId + * @param path */ export async function ensureDirectoryExists( ownerAccountId: string, artistAccountId: string, - path: string + path: string, ): Promise { // Skip if path is empty or root if (!path || path === "root") return; diff --git a/lib/supabase/files/escapePostgrestValue.ts b/lib/supabase/files/escapePostgrestValue.ts index 16c6eeccf..04b964903 100644 --- a/lib/supabase/files/escapePostgrestValue.ts +++ b/lib/supabase/files/escapePostgrestValue.ts @@ -1,17 +1,18 @@ /** * Escape special characters for PostgREST filter values * Wraps value in double quotes if it contains reserved characters (comma, period, colon, parentheses) + * + * @param value */ export function escapePostgrestValue(value: string): string { // PostgREST reserved characters that need escaping const needsEscaping = /[,.:()]/; - + if (needsEscaping.test(value)) { // Escape any existing double quotes, then wrap in quotes const escaped = value.replace(/"/g, '\\"'); return `"${escaped}"`; } - + return value; } - diff --git a/lib/supabase/files/findFileByName.ts b/lib/supabase/files/findFileByName.ts index be8c9ba4c..349d633e3 100644 --- a/lib/supabase/files/findFileByName.ts +++ b/lib/supabase/files/findFileByName.ts @@ -8,23 +8,28 @@ type FileRecord = Tables<"files">; /** * Find a file by name within artist context * Searches in the artist's file storage directory + * + * @param fileName + * @param ownerAccountId + * @param artistAccountId + * @param path */ export async function findFileByName( fileName: string, ownerAccountId: string, artistAccountId: string, - path?: string + path?: string, ): Promise { // Validate path to prevent directory traversal attacks if (path && !isValidPath(path)) { throw new Error( - 'Invalid path: paths cannot contain directory traversal sequences (.., ./), ' + - 'backslashes, control characters, or be absolute paths' + "Invalid path: paths cannot contain directory traversal sequences (.., ./), " + + "backslashes, control characters, or be absolute paths", ); } // Normalize path: remove leading/trailing slashes - const normalizedPath = path?.replace(/^\/+|\/+$/g, ''); + const normalizedPath = path?.replace(/^\/+|\/+$/g, ""); // Find by artist and filename only (matches any owner who shares this artist) const { data, error } = await supabase @@ -40,14 +45,16 @@ export async function findFileByName( if (!data || data.length === 0) { // Try with filename normalization (spaces ↔ underscores) - const fileNameWithSpaces = fileName.replace(/_/g, ' '); - const fileNameWithUnderscores = fileName.replace(/ /g, '_'); - + const fileNameWithSpaces = fileName.replace(/_/g, " "); + const fileNameWithUnderscores = fileName.replace(/ /g, "_"); + const { data: normalizedData } = await supabase .from("files") .select() .eq("artist_account_id", artistAccountId) - .or(`file_name.eq.${escapePostgrestValue(fileNameWithSpaces)},file_name.eq.${escapePostgrestValue(fileNameWithUnderscores)}`) + .or( + `file_name.eq.${escapePostgrestValue(fileNameWithSpaces)},file_name.eq.${escapePostgrestValue(fileNameWithUnderscores)}`, + ) .order("created_at", { ascending: false }); if (!normalizedData || normalizedData.length === 0) { @@ -56,14 +63,14 @@ export async function findFileByName( // Filter by path if specified const filtered = normalizedPath - ? normalizedData.filter((file) => { + ? normalizedData.filter(file => { const match = file.storage_key.match(/^files\/[^\/]+\/[^\/]+\/(.+)$/); if (!match) return false; const relativePath = match[1]; - const expectedPrefix = normalizedPath + '/'; + const expectedPrefix = normalizedPath + "/"; return relativePath.startsWith(expectedPrefix); }) - : normalizedData.filter((file) => { + : normalizedData.filter(file => { const match = file.storage_key.match(/^files\/[^\/]+\/[^\/]+\/([^\/]+)\/?$/); return !!match; }); @@ -73,14 +80,14 @@ export async function findFileByName( // Filter by path if specified const filtered = normalizedPath - ? data.filter((file) => { + ? data.filter(file => { const match = file.storage_key.match(/^files\/[^\/]+\/[^\/]+\/(.+)$/); if (!match) return false; const relativePath = match[1]; - const expectedPrefix = normalizedPath + '/'; + const expectedPrefix = normalizedPath + "/"; return relativePath.startsWith(expectedPrefix); }) - : data.filter((file) => { + : data.filter(file => { const match = file.storage_key.match(/^files\/[^\/]+\/[^\/]+\/([^\/]+)\/?$/); return !!match; }); diff --git a/lib/supabase/files/getFileById.ts b/lib/supabase/files/getFileById.ts index 64e972285..6c4ed4a25 100644 --- a/lib/supabase/files/getFileById.ts +++ b/lib/supabase/files/getFileById.ts @@ -14,13 +14,12 @@ export type FileWithPermissions = { /** * Get a file record by ID with all fields needed for permission checks - * @param fileId UUID of the file to retrieve + * + * @param fileId - UUID of the file to retrieve * @returns File record with permission fields or null if not found * @throws Error if database operation fails (but not if file doesn't exist) */ -export async function getFileById( - fileId: string -): Promise { +export async function getFileById(fileId: string): Promise { const { data, error } = await supabase .from("files") .select("id, owner_account_id, artist_account_id, is_directory, storage_key") @@ -37,4 +36,3 @@ export async function getFileById( return data; } - diff --git a/lib/supabase/files/getFileByStorageKey.ts b/lib/supabase/files/getFileByStorageKey.ts index 70e2c369d..97b5407f8 100644 --- a/lib/supabase/files/getFileByStorageKey.ts +++ b/lib/supabase/files/getFileByStorageKey.ts @@ -8,10 +8,10 @@ type FilePermission = { /** * Get file metadata by storage key for permission validation + * + * @param storageKey */ -export async function getFileByStorageKey( - storageKey: string -): Promise { +export async function getFileByStorageKey(storageKey: string): Promise { const { data, error } = await supabase .from("files") .select("id, owner_account_id, artist_account_id") @@ -24,4 +24,3 @@ export async function getFileByStorageKey( return data; } - diff --git a/lib/supabase/files/getFilesByArtistId.ts b/lib/supabase/files/getFilesByArtistId.ts index 84a196903..92152c199 100644 --- a/lib/supabase/files/getFilesByArtistId.ts +++ b/lib/supabase/files/getFilesByArtistId.ts @@ -6,14 +6,12 @@ type FileRecord = Tables<"files">; /** * Get all files for an artist account * Simple database query with no filtering logic - * + * * @param artistAccountId - The artist's account ID * @returns Array of file records ordered by creation date (newest first) * @throws Error if database query fails */ -export async function getFilesByArtistId( - artistAccountId: string -): Promise { +export async function getFilesByArtistId(artistAccountId: string): Promise { const { data, error } = await supabase .from("files") .select() @@ -26,4 +24,3 @@ export async function getFilesByArtistId( return data || []; } - diff --git a/lib/supabase/files/getFilesInDirectory.ts b/lib/supabase/files/getFilesInDirectory.ts index fd6818f75..5c4a6d2ae 100644 --- a/lib/supabase/files/getFilesInDirectory.ts +++ b/lib/supabase/files/getFilesInDirectory.ts @@ -9,25 +9,23 @@ type FileRecord = { /** * Get all file records in a directory (without deleting them) * Used to retrieve child files before performing storage operations - * @param directoryStorageKey Storage key of the directory (used as prefix match) - * @param excludeId Optional file ID to exclude from results (e.g., the directory itself) + * + * @param directoryStorageKey - Storage key of the directory (used as prefix match) + * @param excludeId - Optional file ID to exclude from results (e.g., the directory itself) * @returns Array of file records with id and storage_key * @throws Error if database operation fails */ export async function getFilesInDirectory( directoryStorageKey: string, - excludeId?: string + excludeId?: string, ): Promise { // Sanitize input by escaping special LIKE wildcard characters const escapedKey = escapeLikePattern(directoryStorageKey); const escapedPattern = `${escapedKey}%`; // Build select query - let query = supabase - .from("files") - .select("id, storage_key") - .like("storage_key", escapedPattern); - + let query = supabase.from("files").select("id, storage_key").like("storage_key", escapedPattern); + if (excludeId) { query = query.neq("id", excludeId); } @@ -40,4 +38,3 @@ export async function getFilesInDirectory( return childFiles || []; } - diff --git a/lib/supabase/files/listFilesByArtist.ts b/lib/supabase/files/listFilesByArtist.ts index a5adddd4e..afb597001 100644 --- a/lib/supabase/files/listFilesByArtist.ts +++ b/lib/supabase/files/listFilesByArtist.ts @@ -6,14 +6,14 @@ type FileRecord = Tables<"files">; /** * List files for an artist, optionally filtered by path - * + * * This is a convenience function that combines: * 1. Database query (getFilesByArtistId) * 2. Path filtering logic (filterFilesByPath) - * + * * Note: With file sharing enabled, this returns files from ALL team members * who have access to the artist, not just the specified owner. - * + * * @param ownerAccountId - Currently unused, kept for backward compatibility * @param artistAccountId - The artist account to get files for * @param path - Optional path filter for immediate children only @@ -23,24 +23,24 @@ export async function listFilesByArtist( ownerAccountId: string, artistAccountId: string, path?: string, - recursive: boolean = false + recursive: boolean = false, ): Promise { // Get all files for the artist from database const allFiles = await getFilesByArtistId(artistAccountId); if (recursive) { if (path) { - // Basic prefix filtering for recursive mode - const pathPrefix = path.endsWith('/') ? path : path + '/'; - return allFiles.filter(f => { - const match = f.storage_key.match(/^files\/[^\/]+\/[^\/]+\/(.+)$/); - if (!match) return false; - const relativePath = match[1]; - return relativePath.startsWith(pathPrefix); - }); + // Basic prefix filtering for recursive mode + const pathPrefix = path.endsWith("/") ? path : path + "/"; + return allFiles.filter(f => { + const match = f.storage_key.match(/^files\/[^\/]+\/[^\/]+\/(.+)$/); + if (!match) return false; + const relativePath = match[1]; + return relativePath.startsWith(pathPrefix); + }); } return allFiles; - } + } // Apply path filtering logic return filterFilesByPath(allFiles, path); diff --git a/lib/supabase/files/updateFileName.ts b/lib/supabase/files/updateFileName.ts index bc0eb7659..2b05a6714 100644 --- a/lib/supabase/files/updateFileName.ts +++ b/lib/supabase/files/updateFileName.ts @@ -5,7 +5,8 @@ import { isValidUUID } from "@/utils/isValidUUID"; /** * Checks if storage key contains control characters - * @param key Storage key to check + * + * @param key - Storage key to check * @returns true if contains control characters, false otherwise */ function hasControlCharacters(key: string): boolean { @@ -15,15 +16,16 @@ function hasControlCharacters(key: string): boolean { /** * Update the file_name and storage_key of a file record (used for rename operations) - * @param fileId UUID of the file record to update - * @param newFileName New display name for the file - * @param newStorageKey New storage key path + * + * @param fileId - UUID of the file record to update + * @param newFileName - New display name for the file + * @param newStorageKey - New storage key path * @throws Error if validation fails or update fails */ export async function updateFileName( fileId: string, newFileName: string, - newStorageKey: string + newStorageKey: string, ): Promise { // Validate fileId is a valid UUID if (!isValidUUID(fileId)) { @@ -32,31 +34,31 @@ export async function updateFileName( // Validate newFileName is non-empty and contains valid characters if (!newFileName || newFileName.trim().length === 0) { - throw new Error('File name cannot be empty'); + throw new Error("File name cannot be empty"); } if (!isValidFileName(newFileName)) { throw new Error( - 'Invalid file name: must not contain path separators, path traversal sequences, ' + - 'control characters, or reserved names, and must be 255 characters or less' + "Invalid file name: must not contain path separators, path traversal sequences, " + + "control characters, or reserved names, and must be 255 characters or less", ); } // Validate newStorageKey format and security if (!newStorageKey || newStorageKey.trim().length === 0) { - throw new Error('Storage key cannot be empty'); + throw new Error("Storage key cannot be empty"); } if (!isValidStorageKey(newStorageKey)) { throw new Error( - 'Invalid storage key: must not start with /, contain path traversal (..), ' + - 'or backslashes, and must be 1024 characters or less' + "Invalid storage key: must not start with /, contain path traversal (..), " + + "or backslashes, and must be 1024 characters or less", ); } // Check for control characters in storage key if (hasControlCharacters(newStorageKey)) { - throw new Error('Storage key cannot contain control characters'); + throw new Error("Storage key cannot contain control characters"); } // All validation passed, proceed with update @@ -73,4 +75,3 @@ export async function updateFileName( throw new Error(`Failed to update file name: ${error.message}`); } } - diff --git a/lib/supabase/files/updateFileSizeBytes.ts b/lib/supabase/files/updateFileSizeBytes.ts index 21437d69e..b4ee96240 100644 --- a/lib/supabase/files/updateFileSizeBytes.ts +++ b/lib/supabase/files/updateFileSizeBytes.ts @@ -2,18 +2,14 @@ import supabase from "@/lib/supabase/serverClient"; /** * Update file size in bytes in the files table + * + * @param fileId + * @param sizeBytes */ -export async function updateFileSizeBytes( - fileId: string, - sizeBytes: number -): Promise { - const { error } = await supabase - .from("files") - .update({ size_bytes: sizeBytes }) - .eq("id", fileId); +export async function updateFileSizeBytes(fileId: string, sizeBytes: number): Promise { + const { error } = await supabase.from("files").update({ size_bytes: sizeBytes }).eq("id", fileId); if (error) { throw new Error(`Failed to update file size: ${error.message}`); } } - diff --git a/lib/supabase/files/updateFileStorageKey.ts b/lib/supabase/files/updateFileStorageKey.ts index 92518e378..812b9b790 100644 --- a/lib/supabase/files/updateFileStorageKey.ts +++ b/lib/supabase/files/updateFileStorageKey.ts @@ -2,14 +2,12 @@ import supabase from "@/lib/supabase/serverClient"; /** * Update the storage_key of a file record (used for move operations) - * @param fileId UUID of the file record to update - * @param newStorageKey New storage key path + * + * @param fileId - UUID of the file record to update + * @param newStorageKey - New storage key path * @throws Error if update fails */ -export async function updateFileStorageKey( - fileId: string, - newStorageKey: string -): Promise { +export async function updateFileStorageKey(fileId: string, newStorageKey: string): Promise { const { error } = await supabase .from("files") .update({ @@ -22,4 +20,3 @@ export async function updateFileStorageKey( throw new Error(`Failed to update file storage key: ${error.message}`); } } - diff --git a/lib/supabase/getAccountByPhone.ts b/lib/supabase/getAccountByPhone.ts index cb477b862..9bd4048f0 100644 --- a/lib/supabase/getAccountByPhone.ts +++ b/lib/supabase/getAccountByPhone.ts @@ -7,13 +7,12 @@ type AccountResponse = { /** * Retrieves an account by phone number - * @param phone The phone number to search for + * + * @param phone - The phone number to search for * @returns The account data if found * @throws Error if phone number is not provided or if there's a database error */ -export async function getAccountByPhone( - phone: string, -): Promise { +export async function getAccountByPhone(phone: string): Promise { if (!phone) { throw new Error("Phone number is required"); } diff --git a/lib/supabase/getArtistAgents.ts b/lib/supabase/getArtistAgents.ts index 9cba47cfa..9e5092bfa 100644 --- a/lib/supabase/getArtistAgents.ts +++ b/lib/supabase/getArtistAgents.ts @@ -7,9 +7,11 @@ export interface ArtistAgent { updated_at: string; } -export async function getArtistAgents( - artistSocialIds: string[], -): Promise { +/** + * + * @param artistSocialIds + */ +export async function getArtistAgents(artistSocialIds: string[]): Promise { const { data, error } = await supabase .from("agent_status") .select("*, agent:agents(*)") @@ -22,7 +24,7 @@ export async function getArtistAgents( if (!data) return []; - const agentIds = [...new Set(data.map((ele) => ele.agent.id))]; + const agentIds = [...new Set(data.map(ele => ele.agent.id))]; const { data: agents } = await supabase .from("agents") @@ -31,7 +33,7 @@ export async function getArtistAgents( if (!agents) return []; - const transformedAgents = agents.map((agent) => ({ + const transformedAgents = agents.map(agent => ({ type: new String( agent.agent_status.length > 1 ? "wrapped" @@ -44,7 +46,7 @@ export async function getArtistAgents( // eslint-disable-next-line const aggregatedAgents: any = {}; - transformedAgents.forEach((agent) => { + transformedAgents.forEach(agent => { const type = agent.type.toLowerCase(); aggregatedAgents[type] = agent; }); diff --git a/lib/supabase/getArtistKnowledge.ts b/lib/supabase/getArtistKnowledge.ts index 18145145c..631debf55 100644 --- a/lib/supabase/getArtistKnowledge.ts +++ b/lib/supabase/getArtistKnowledge.ts @@ -6,6 +6,10 @@ export interface KnowledgeBaseEntry { type: string; } +/** + * + * @param artistId + */ export async function getArtistKnowledge(artistId: string): Promise { const { data, error } = await supabase .from("account_info") diff --git a/lib/supabase/getArtistSegmentNames.ts b/lib/supabase/getArtistSegmentNames.ts index e8f0e7537..5e3dc34e3 100644 --- a/lib/supabase/getArtistSegmentNames.ts +++ b/lib/supabase/getArtistSegmentNames.ts @@ -3,10 +3,10 @@ import supabase from "./serverClient"; /** * Get all segments associated with an artist + * + * @param artistId */ -export async function getArtistSegmentNames( - artistId: string -): Promise { +export async function getArtistSegmentNames(artistId: string): Promise { try { const { data: segments, error: segmentsError } = await supabase .from("artist_segments") @@ -14,7 +14,7 @@ export async function getArtistSegmentNames( ` *, segment:segments(*) - ` + `, ) .eq("artist_account_id", artistId); diff --git a/lib/supabase/getArtistSegments.ts b/lib/supabase/getArtistSegments.ts index 63d12a207..1b91613dc 100644 --- a/lib/supabase/getArtistSegments.ts +++ b/lib/supabase/getArtistSegments.ts @@ -42,19 +42,21 @@ export interface SegmentWithCount extends ArtistSegment { /** * Get all segments with their fan counts for an artist + * + * @param artistId */ export async function getArtistSegments(artistId: string): Promise { const segments = await getArtistSegmentNames(artistId); if (!segments.length) return []; - const segmentIds = segments.map((s) => s.segment_id); + const segmentIds = segments.map(s => s.segment_id); const counts = await getSegmentCounts(segmentIds); - const countMap = new Map(counts.map((c) => [c.segment_id, c.count])); + const countMap = new Map(counts.map(c => [c.segment_id, c.count])); // Get fans for each segment (limit to 5 for social proof) const segmentsWithFans = await Promise.all( - segments.map(async (segment) => { + segments.map(async segment => { const fans = await selectFanSegments({ segment_id: segment.segment_id }); return { id: segment.segment_id, @@ -63,7 +65,7 @@ export async function getArtistSegments(artistId: string): Promise { icon: undefined, fans, }; - }) + }), ); return segmentsWithFans; diff --git a/lib/supabase/getMemoryById.ts b/lib/supabase/getMemoryById.ts index e81df6f2f..77acac689 100644 --- a/lib/supabase/getMemoryById.ts +++ b/lib/supabase/getMemoryById.ts @@ -1,17 +1,14 @@ import supabase from "./serverClient"; import { Tables } from "../../types/database.types"; -export async function getMemoryById({ - id, -}: { - id: string; -}): Promise | null> { +/** + * + * @param root0 + * @param root0.id + */ +export async function getMemoryById({ id }: { id: string }): Promise | null> { try { - const { data, error } = await supabase - .from("memories") - .select("*") - .eq("id", id) - .single(); + const { data, error } = await supabase.from("memories").select("*").eq("id", id).single(); if (error) { console.error("Failed to get memory by id:", error.message); diff --git a/lib/supabase/getRoomArtistId.ts b/lib/supabase/getRoomArtistId.ts index a27ac994e..d78b5bc57 100644 --- a/lib/supabase/getRoomArtistId.ts +++ b/lib/supabase/getRoomArtistId.ts @@ -2,53 +2,54 @@ import supabase from "./serverClient"; /** * Gets the artist ID associated with a room - * @param roomId The ID of the room to get the artist ID for + * + * @param roomId - The ID of the room to get the artist ID for * @returns The artist ID associated with the room, or null if not found */ export async function getRoomArtistId(roomId: string): Promise { try { console.log(`Fetching artist ID for room: ${roomId}`); - + // Get the first room with matching ID to find the artist_id const { data, error } = await supabase .from("rooms") .select("artist_id") .eq("id", roomId) .maybeSingle(); - + if (error) { console.error("Error getting room artist ID:", error.message); return null; } - + if (!data?.artist_id) { console.log(`No artist_id found for room: ${roomId}`); return null; } - + console.log(`Found artist_id: ${data.artist_id} for room: ${roomId}`); - + // Verify artist exists in the database const { data: artistExists, error: artistError } = await supabase .from("accounts") .select("id") .eq("id", data.artist_id) .single(); - + if (artistError) { console.error("Artist not found:", artistError.message); return null; } - + if (!artistExists) { console.log(`Artist ${data.artist_id} does not exist in accounts table`); return null; } - + console.log(`Verified artist ${data.artist_id} exists`); return data.artist_id; } catch (error) { console.error("Unexpected error getting room artist ID:", error); return null; } -} \ No newline at end of file +} diff --git a/lib/supabase/getRoomReports.ts b/lib/supabase/getRoomReports.ts index 24af0fb98..ea2ba44e8 100644 --- a/lib/supabase/getRoomReports.ts +++ b/lib/supabase/getRoomReports.ts @@ -2,6 +2,7 @@ import supabase from "./serverClient"; /** * Fetches report data for a specific room + * * @param roomId - The ID of the room to fetch reports for * @returns The report data if found, null otherwise */ diff --git a/lib/supabase/getSegmentCounts.ts b/lib/supabase/getSegmentCounts.ts index 83075d661..1b70c1cd2 100644 --- a/lib/supabase/getSegmentCounts.ts +++ b/lib/supabase/getSegmentCounts.ts @@ -3,10 +3,10 @@ import supabase from "./serverClient"; /** * Get fan counts for a list of segment IDs + * + * @param segmentIds */ -export async function getSegmentCounts( - segmentIds: string[] -): Promise { +export async function getSegmentCounts(segmentIds: string[]): Promise { try { const { data: fanSegments, error: countsError } = await supabase .from("fan_segments") @@ -18,10 +18,9 @@ export async function getSegmentCounts( return []; } - const counts = segmentIds.map((segmentId) => ({ + const counts = segmentIds.map(segmentId => ({ segment_id: segmentId, - count: - fanSegments?.filter((fs) => fs.segment_id === segmentId).length || 0, + count: fanSegments?.filter(fs => fs.segment_id === segmentId).length || 0, })); return counts; diff --git a/lib/supabase/getSegmentWithArtist.ts b/lib/supabase/getSegmentWithArtist.ts index 62f3c8115..5d3240c5e 100644 --- a/lib/supabase/getSegmentWithArtist.ts +++ b/lib/supabase/getSegmentWithArtist.ts @@ -20,9 +20,7 @@ const createNotFoundError = (segmentId: string): PostgrestError => ({ name: "PostgrestError", }); -export const getSegmentWithArtist = async ( - segmentId: string -): Promise => { +export const getSegmentWithArtist = async (segmentId: string): Promise => { try { const { data: segmentData, error: segmentError } = await supabase .from("segments") @@ -32,7 +30,7 @@ export const getSegmentWithArtist = async ( artist_segments ( artist_account_id ) - ` + `, ) .eq("id", segmentId) .single(); @@ -51,8 +49,7 @@ export const getSegmentWithArtist = async ( }; } - const artistAccountId = - segmentData.artist_segments?.[0]?.artist_account_id || null; + const artistAccountId = segmentData.artist_segments?.[0]?.artist_account_id || null; return { segment: segmentData, diff --git a/lib/supabase/getSpotifyPlayButtonClicked.ts b/lib/supabase/getSpotifyPlayButtonClicked.ts index 5478d312d..65645f814 100644 --- a/lib/supabase/getSpotifyPlayButtonClicked.ts +++ b/lib/supabase/getSpotifyPlayButtonClicked.ts @@ -13,8 +13,12 @@ export interface GetSpotifyPlayButtonClickedResult { total: number; } +/** + * + * @param params + */ export async function getSpotifyPlayButtonClicked( - params: GetSpotifyPlayButtonClickedParams + params: GetSpotifyPlayButtonClickedParams, ): Promise { const { campaignId } = params; diff --git a/lib/supabase/getUserInfo.ts b/lib/supabase/getUserInfo.ts index ba40708d9..e1f731228 100644 --- a/lib/supabase/getUserInfo.ts +++ b/lib/supabase/getUserInfo.ts @@ -13,7 +13,8 @@ export interface UserInfo { /** * Gets the full user information from account_info and accounts tables - * @param accountId The user's account ID + * + * @param accountId - The user's account ID * @returns The user info object or null if not found */ async function getUserInfo(accountId: string): Promise { @@ -27,14 +28,14 @@ async function getUserInfo(accountId: string): Promise { .select("instruction, organization, knowledges, job_title, role_type, company_name") .eq("account_id", accountId) .single(); - + // Get user name from accounts table (email is in account_emails table) const { data: account } = await supabase .from("accounts") .select("name") .eq("id", accountId) .single(); - + // Get user email from account_emails table const { data: accountEmail } = await supabase .from("account_emails") @@ -56,7 +57,7 @@ async function getUserInfo(accountId: string): Promise { organization: accountInfo?.organization, knowledges: accountInfo?.knowledges, }; - + return userInfo; } diff --git a/lib/supabase/memories/getMemories.ts b/lib/supabase/memories/getMemories.ts index 24326b32e..a707362de 100644 --- a/lib/supabase/memories/getMemories.ts +++ b/lib/supabase/memories/getMemories.ts @@ -2,15 +2,12 @@ import supabase from "../serverClient"; /** * Returns all memories updated at or after the given timestamp. + * * @param since - ISO timestamp string (UTC) + * @param since.startDate + * @param since.endDate */ -export async function getMemories({ - startDate, - endDate, -}: { - startDate: string; - endDate?: string; -}) { +export async function getMemories({ startDate, endDate }: { startDate: string; endDate?: string }) { let query = supabase.from("memories").select("*"); if (startDate) query = query.gte("updated_at", startDate); diff --git a/lib/supabase/memory_emails/insertMemoryEmail.ts b/lib/supabase/memory_emails/insertMemoryEmail.ts index b08e4a25b..1928f6fe1 100644 --- a/lib/supabase/memory_emails/insertMemoryEmail.ts +++ b/lib/supabase/memory_emails/insertMemoryEmail.ts @@ -15,9 +15,7 @@ interface InsertMemoryEmailParams { * @param params.message_id - The message ID from the chat * @returns The inserted memory_email record */ -export async function insertMemoryEmail( - params: InsertMemoryEmailParams -): Promise { +export async function insertMemoryEmail(params: InsertMemoryEmailParams): Promise { const { error } = await supabase.from("memory_emails").insert(params); if (error) { diff --git a/lib/supabase/organization_domains/getOrgByDomain.ts b/lib/supabase/organization_domains/getOrgByDomain.ts index aeb352f08..237317953 100644 --- a/lib/supabase/organization_domains/getOrgByDomain.ts +++ b/lib/supabase/organization_domains/getOrgByDomain.ts @@ -7,9 +7,7 @@ import supabase from "@/lib/supabase/serverClient"; * @param domain - Email domain (e.g., "rostrum.com") * @returns Organization ID if found, null otherwise */ -export async function getOrgByDomain( - domain: string -): Promise { +export async function getOrgByDomain(domain: string): Promise { if (!domain) return null; try { @@ -31,4 +29,3 @@ export async function getOrgByDomain( } export default getOrgByDomain; - diff --git a/lib/supabase/post_comments/insertPostComments.ts b/lib/supabase/post_comments/insertPostComments.ts index 52eedd94c..7df8aa3e5 100644 --- a/lib/supabase/post_comments/insertPostComments.ts +++ b/lib/supabase/post_comments/insertPostComments.ts @@ -4,14 +4,12 @@ import { Tables } from "@/types/database.types"; type PostComment = Tables<"post_comments">; type PostCommentInsert = Partial; -export const insertPostComments = async ( - comments: PostCommentInsert[] -): Promise => { +export const insertPostComments = async (comments: PostCommentInsert[]): Promise => { const { data, error } = await serverClient .from("post_comments") - .upsert(comments, { + .upsert(comments, { onConflict: "post_id,social_id,comment,commented_at", - ignoreDuplicates: true + ignoreDuplicates: true, }) .select(); @@ -21,4 +19,4 @@ export const insertPostComments = async ( } return data || []; -}; \ No newline at end of file +}; diff --git a/lib/supabase/post_comments/selectPostComments.ts b/lib/supabase/post_comments/selectPostComments.ts index 5e24376b7..c56b0aceb 100644 --- a/lib/supabase/post_comments/selectPostComments.ts +++ b/lib/supabase/post_comments/selectPostComments.ts @@ -12,7 +12,7 @@ interface SelectPostCommentsParams { } export const selectPostComments = async ( - params?: SelectPostCommentsParams + params?: SelectPostCommentsParams, ): Promise => { let query = serverClient.from("post_comments").select(` *, @@ -23,9 +23,9 @@ export const selectPostComments = async ( if (params?.postUrls && params.postUrls.length > 0) { // First get post IDs for the given URLs const posts = await getPosts(params.postUrls); - + if (posts.length > 0) { - const postIds = posts.map((post) => post.id); + const postIds = posts.map(post => post.id); query = query.in("post_id", postIds); } else { // If no posts found for the URLs, return empty array @@ -43,4 +43,4 @@ export const selectPostComments = async ( return data || []; }; -export default selectPostComments; \ No newline at end of file +export default selectPostComments; diff --git a/lib/supabase/posts/getPosts.ts b/lib/supabase/posts/getPosts.ts index 373883140..4535f9013 100644 --- a/lib/supabase/posts/getPosts.ts +++ b/lib/supabase/posts/getPosts.ts @@ -1,14 +1,13 @@ import supabase from "@/lib/supabase/serverClient"; import { Tables } from "@/types/database.types"; -export default async function getPosts( - postUrls: string[] -): Promise[]> { +/** + * + * @param postUrls + */ +export default async function getPosts(postUrls: string[]): Promise[]> { if (!Array.isArray(postUrls) || postUrls.length === 0) return []; - const { data, error } = await supabase - .from("posts") - .select("*") - .in("post_url", postUrls); + const { data, error } = await supabase.from("posts").select("*").in("post_url", postUrls); if (error) { console.error("Error fetching posts:", error); return []; diff --git a/lib/supabase/posts/insertPosts.ts b/lib/supabase/posts/insertPosts.ts index 8e3fd5cf0..35cbf5885 100644 --- a/lib/supabase/posts/insertPosts.ts +++ b/lib/supabase/posts/insertPosts.ts @@ -4,6 +4,7 @@ import { TablesInsert } from "@/types/database.types"; /** * Saves an array of posts to the Supabase 'posts' table. * Uses upsert to avoid errors on duplicate post_url (unique constraint). + * * @param posts - Array of posts matching the posts table insert type. * @returns The result of the Supabase upsert operation. */ diff --git a/lib/supabase/queryMemories.ts b/lib/supabase/queryMemories.ts index 0b35b0048..29cc811bb 100644 --- a/lib/supabase/queryMemories.ts +++ b/lib/supabase/queryMemories.ts @@ -2,26 +2,32 @@ import supabase from "./serverClient"; /** * Queries the memories table in Supabase - * @param roomId The room ID to query memories for - * @param options Options for the query (ascending order and limit) + * + * @param roomId - The room ID to query memories for + * @param options - Options for the query (ascending order and limit) + * @param options.ascending + * @param options.limit * @returns Supabase query result with memories data */ -export default async function queryMemories(roomId: string, options?: { - ascending?: boolean; - limit?: number; -}) { +export default async function queryMemories( + roomId: string, + options?: { + ascending?: boolean; + limit?: number; + }, +) { const ascending = options?.ascending ?? false; const limit = options?.limit; - + let query = supabase .from("memories") .select("*") .eq("room_id", roomId) .order("updated_at", { ascending }); - + if (limit) { query = query.limit(limit); } - + return query; -} \ No newline at end of file +} diff --git a/lib/supabase/rooms/getRooms.ts b/lib/supabase/rooms/getRooms.ts index f148585de..63c4cb8be 100644 --- a/lib/supabase/rooms/getRooms.ts +++ b/lib/supabase/rooms/getRooms.ts @@ -2,16 +2,13 @@ import supabase from "../serverClient"; /** * Returns all rooms updated at or after the given timestamp, and optionally up to an end date (inclusive). + * * @param since - ISO timestamp string (UTC, lower bound) + * @param since.startDate * @param endDate - ISO timestamp string (UTC, upper bound, inclusive) + * @param since.endDate */ -export async function getRooms({ - startDate, - endDate, -}: { - startDate: string; - endDate?: string; -}) { +export async function getRooms({ startDate, endDate }: { startDate: string; endDate?: string }) { let query = supabase.from("rooms").select("*"); if (startDate) query = query.gte("updated_at", startDate); diff --git a/lib/supabase/segments/deleteSegments.ts b/lib/supabase/segments/deleteSegments.ts index b5706cb1d..54ce6243b 100644 --- a/lib/supabase/segments/deleteSegments.ts +++ b/lib/supabase/segments/deleteSegments.ts @@ -3,9 +3,7 @@ import { Tables } from "@/types/database.types"; type Segment = Tables<"segments">; -export const deleteSegments = async ( - artist_account_id: string -): Promise => { +export const deleteSegments = async (artist_account_id: string): Promise => { // First, get all segment_ids associated with the artist from artist_segments table const { data: artistSegments, error: artistSegmentsError } = await serverClient .from("artist_segments") @@ -43,4 +41,4 @@ export const deleteSegments = async ( } return data; -}; \ No newline at end of file +}; diff --git a/lib/supabase/segments/insertSegments.ts b/lib/supabase/segments/insertSegments.ts index 927800104..0948914d9 100644 --- a/lib/supabase/segments/insertSegments.ts +++ b/lib/supabase/segments/insertSegments.ts @@ -4,13 +4,8 @@ import { Tables } from "@/types/database.types"; type Segment = Tables<"segments">; type SegmentInsert = Partial; -export const insertSegments = async ( - segments: SegmentInsert[] -): Promise => { - const { data, error } = await serverClient - .from("segments") - .insert(segments) - .select(); +export const insertSegments = async (segments: SegmentInsert[]): Promise => { + const { data, error } = await serverClient.from("segments").insert(segments).select(); if (error) { console.error("Error inserting segments:", error); @@ -18,4 +13,4 @@ export const insertSegments = async ( } return data; -}; \ No newline at end of file +}; diff --git a/lib/supabase/serverClient.ts b/lib/supabase/serverClient.ts index 53816e6b0..c16e27754 100644 --- a/lib/supabase/serverClient.ts +++ b/lib/supabase/serverClient.ts @@ -5,12 +5,12 @@ const SUPABASE_KEY = process.env.SUPABASE_SERVICE_ROLE_KEY as string; // Validate environment variables are present if (!SUPABASE_URL || !SUPABASE_KEY) { - console.error('❌ Missing Supabase credentials:', { + console.error("❌ Missing Supabase credentials:", { hasUrl: !!SUPABASE_URL, hasKey: !!SUPABASE_KEY, - environment: process.env.VERCEL_ENV || 'local' + environment: process.env.VERCEL_ENV || "local", }); - throw new Error('Missing required Supabase environment variables'); + throw new Error("Missing required Supabase environment variables"); } const supabase = createClient(SUPABASE_URL, SUPABASE_KEY); diff --git a/lib/supabase/social_fans/selectSocialFans.ts b/lib/supabase/social_fans/selectSocialFans.ts index 149fee362..6f09bed46 100644 --- a/lib/supabase/social_fans/selectSocialFans.ts +++ b/lib/supabase/social_fans/selectSocialFans.ts @@ -31,7 +31,7 @@ interface SelectSocialFansParams { } export const selectSocialFans = async ( - params?: SelectSocialFansParams + params?: SelectSocialFansParams, ): Promise => { let query = serverClient.from("social_fans").select(` *, @@ -71,10 +71,7 @@ export const selectSocialFans = async ( } // Only allow ordering by top-level columns - if ( - params?.orderBy && - SOCIAL_FANS_ORDERABLE_COLUMNS.includes(params.orderBy) - ) { + if (params?.orderBy && SOCIAL_FANS_ORDERABLE_COLUMNS.includes(params.orderBy)) { query = query.order(params.orderBy, { ascending: params.orderDirection !== "desc", nullsFirst: false, diff --git a/lib/supabase/social_posts/insertSocialPosts.ts b/lib/supabase/social_posts/insertSocialPosts.ts index a01b5fe26..323b93a4a 100644 --- a/lib/supabase/social_posts/insertSocialPosts.ts +++ b/lib/supabase/social_posts/insertSocialPosts.ts @@ -4,12 +4,11 @@ import type { TablesInsert } from "@/types/database.types"; /** * Creates records in the social_posts table. * Uses upsert to avoid unique constraint violation on (post_id, social_id). + * * @param socialPosts - Array of rows to insert. * @returns Supabase upsert result */ -export default async function insertSocialPosts( - socialPosts: TablesInsert<"social_posts">[] -) { +export default async function insertSocialPosts(socialPosts: TablesInsert<"social_posts">[]) { const { data, error } = await supabase .from("social_posts") .upsert(socialPosts, { onConflict: "post_id,social_id" }); diff --git a/lib/supabase/socials/getSocialByProfileUrl.ts b/lib/supabase/socials/getSocialByProfileUrl.ts index 9a15e3c12..0060bf1c1 100644 --- a/lib/supabase/socials/getSocialByProfileUrl.ts +++ b/lib/supabase/socials/getSocialByProfileUrl.ts @@ -1,9 +1,7 @@ import supabase from "../serverClient"; import type { Tables } from "@/types/database.types"; -const getSocialByProfileUrl = async ( - profileUrl: string -): Promise | null> => { +const getSocialByProfileUrl = async (profileUrl: string): Promise | null> => { const { data } = await supabase .from("socials") .select("*") diff --git a/lib/supabase/socials/insertSocials.ts b/lib/supabase/socials/insertSocials.ts index 5138bea3d..196df5a0a 100644 --- a/lib/supabase/socials/insertSocials.ts +++ b/lib/supabase/socials/insertSocials.ts @@ -1,9 +1,7 @@ import supabase from "../serverClient"; import type { Tables, TablesInsert } from "@/types/database.types"; -const insertSocials = async ( - socials: TablesInsert<"socials">[] -): Promise[]> => { +const insertSocials = async (socials: TablesInsert<"socials">[]): Promise[]> => { const { data, error } = await supabase .from("socials") .upsert(socials, { onConflict: "profile_url" }) diff --git a/lib/supabase/storage/client.ts b/lib/supabase/storage/client.ts index b21fe7b26..2d92a49d1 100644 --- a/lib/supabase/storage/client.ts +++ b/lib/supabase/storage/client.ts @@ -1,5 +1,9 @@ "use client"; +/** + * + * @param storageKey + */ export async function createSignedUrlClient(storageKey: string): Promise { try { const res = await fetch("/api/storage/signed-url", { @@ -21,9 +25,15 @@ export async function createSignedUrlClient(storageKey: string): Promise } } -export async function createBatchSignedUrlsClient(storageKeys: string[]): Promise> { +/** + * + * @param storageKeys + */ +export async function createBatchSignedUrlsClient( + storageKeys: string[], +): Promise> { if (storageKeys.length === 0) return {}; - + try { const res = await fetch("/api/storage/signed-url", { method: "POST", diff --git a/lib/supabase/storage/copyFileByKey.ts b/lib/supabase/storage/copyFileByKey.ts index e477d0262..882a8b68a 100644 --- a/lib/supabase/storage/copyFileByKey.ts +++ b/lib/supabase/storage/copyFileByKey.ts @@ -5,15 +5,16 @@ import { uploadFileByKey } from "./uploadFileByKey"; /** * Copy a file from one storage key to another by downloading and re-uploading * Used for rename and move operations since Supabase has no native copy/move - * @param sourceKey Original storage key - * @param targetKey New storage key - * @param contentType Optional MIME type for the target file + * + * @param sourceKey - Original storage key + * @param targetKey - New storage key + * @param contentType - Optional MIME type for the target file * @throws Error if download or upload fails */ export async function copyFileByKey( sourceKey: string, targetKey: string, - contentType?: string + contentType?: string, ): Promise { // Download file from source const { data: fileData, error: downloadError } = await supabase.storage @@ -22,7 +23,7 @@ export async function copyFileByKey( if (downloadError || !fileData) { throw new Error( - `Failed to download file from ${sourceKey}: ${downloadError?.message || "No data returned"}` + `Failed to download file from ${sourceKey}: ${downloadError?.message || "No data returned"}`, ); } @@ -32,4 +33,3 @@ export async function copyFileByKey( upsert: false, // Don't overwrite - we check for conflicts before calling this }); } - diff --git a/lib/supabase/storage/createSignedUploadUrl.ts b/lib/supabase/storage/createSignedUploadUrl.ts index e03148bf4..28f3eac12 100644 --- a/lib/supabase/storage/createSignedUploadUrl.ts +++ b/lib/supabase/storage/createSignedUploadUrl.ts @@ -6,9 +6,12 @@ export type SignedUploadResult = { path: string; }; +/** + * + * @param key + */ export async function createSignedUploadUrlForKey(key: string): Promise { - const { data, error } = await supabase - .storage + const { data, error } = await supabase.storage .from(SUPABASE_STORAGE_BUCKET) .createSignedUploadUrl(key); @@ -18,5 +21,3 @@ export async function createSignedUploadUrlForKey(key: string): Promise { - const { data, error } = await supabase - .storage +/** + * + * @param key + * @param expiresInSec + */ +export async function createSignedUrlForKey( + key: string, + expiresInSec: number = 300, +): Promise { + const { data, error } = await supabase.storage .from(SUPABASE_STORAGE_BUCKET) .createSignedUrl(key, expiresInSec); @@ -13,5 +20,3 @@ export async function createSignedUrlForKey(key: string, expiresInSec: number = return data.signedUrl; } - - diff --git a/lib/supabase/storage/deleteFileByKey.ts b/lib/supabase/storage/deleteFileByKey.ts index 165221221..4be878b8d 100644 --- a/lib/supabase/storage/deleteFileByKey.ts +++ b/lib/supabase/storage/deleteFileByKey.ts @@ -3,20 +3,16 @@ import { SUPABASE_STORAGE_BUCKET } from "@/lib/consts"; /** * Delete one or more files from Supabase storage by their storage keys - * @param keys Single storage key or array of storage keys to delete + * + * @param keys - Single storage key or array of storage keys to delete * @throws Error if deletion fails */ -export async function deleteFileByKey( - keys: string | string[] -): Promise { +export async function deleteFileByKey(keys: string | string[]): Promise { const keysArray = Array.isArray(keys) ? keys : [keys]; - const { error } = await supabase.storage - .from(SUPABASE_STORAGE_BUCKET) - .remove(keysArray); + const { error } = await supabase.storage.from(SUPABASE_STORAGE_BUCKET).remove(keysArray); if (error) { throw new Error(`Failed to delete file(s) from storage: ${error.message}`); } } - diff --git a/lib/supabase/storage/fetchFileContent.ts b/lib/supabase/storage/fetchFileContent.ts index 5b930718d..604044046 100644 --- a/lib/supabase/storage/fetchFileContent.ts +++ b/lib/supabase/storage/fetchFileContent.ts @@ -3,6 +3,8 @@ import { createSignedUrlForKey } from "./createSignedUrl"; /** * Server-side function to fetch file content from storage * Uses Supabase storage directly (no API calls needed) + * + * @param storageKey */ export async function fetchFileContentServer(storageKey: string): Promise { // Get signed URL directly from Supabase diff --git a/lib/supabase/storage/uploadFileByKey.ts b/lib/supabase/storage/uploadFileByKey.ts index 9c34246f3..a59d3b82e 100644 --- a/lib/supabase/storage/uploadFileByKey.ts +++ b/lib/supabase/storage/uploadFileByKey.ts @@ -3,9 +3,12 @@ import { SUPABASE_STORAGE_BUCKET } from "@/lib/consts"; /** * Upload file to Supabase storage by key - * @param key Storage key path - * @param file File or Blob to upload - * @param options Upload options including upsert + * + * @param key - Storage key path + * @param file - File or Blob to upload + * @param options - Upload options including upsert + * @param options.contentType + * @param options.upsert */ export async function uploadFileByKey( key: string, @@ -13,17 +16,14 @@ export async function uploadFileByKey( options: { contentType?: string; upsert?: boolean; - } = {} + } = {}, ): Promise { - const { error } = await supabase.storage - .from(SUPABASE_STORAGE_BUCKET) - .upload(key, file, { - contentType: options.contentType || "application/octet-stream", - upsert: options.upsert ?? false, - }); + const { error } = await supabase.storage.from(SUPABASE_STORAGE_BUCKET).upload(key, file, { + contentType: options.contentType || "application/octet-stream", + upsert: options.upsert ?? false, + }); if (error) { throw new Error(`Failed to upload file: ${error.message}`); } } - diff --git a/lib/supabase/youtube_tokens/deleteYouTubeTokens.ts b/lib/supabase/youtube_tokens/deleteYouTubeTokens.ts index 268dd4bac..7c25963ac 100644 --- a/lib/supabase/youtube_tokens/deleteYouTubeTokens.ts +++ b/lib/supabase/youtube_tokens/deleteYouTubeTokens.ts @@ -2,12 +2,11 @@ import supabase from "@/lib/supabase/serverClient"; /** * Delete YouTube tokens for a specific account + * * @param artist_account_id - The artist account ID to delete tokens for * @returns boolean indicating success */ -const deleteYouTubeTokens = async ( - artist_account_id: string -): Promise => { +const deleteYouTubeTokens = async (artist_account_id: string): Promise => { try { const { error } = await supabase .from("youtube_tokens") @@ -26,4 +25,4 @@ const deleteYouTubeTokens = async ( } }; -export default deleteYouTubeTokens; \ No newline at end of file +export default deleteYouTubeTokens; diff --git a/lib/supabase/youtube_tokens/getYouTubeTokens.ts b/lib/supabase/youtube_tokens/getYouTubeTokens.ts index 8f112b1e6..ff9b3eb45 100644 --- a/lib/supabase/youtube_tokens/getYouTubeTokens.ts +++ b/lib/supabase/youtube_tokens/getYouTubeTokens.ts @@ -3,12 +3,11 @@ import type { YouTubeTokensRow } from "@/types/youtube"; /** * Get YouTube tokens for a specific account + * * @param artist_account_id - The artist account ID to get tokens for * @returns YouTube tokens or null if not found */ -const getYouTubeTokens = async ( - artist_account_id: string -): Promise => { +const getYouTubeTokens = async (artist_account_id: string): Promise => { try { const { data, error } = await supabase .from("youtube_tokens") @@ -31,4 +30,4 @@ const getYouTubeTokens = async ( } }; -export default getYouTubeTokens; \ No newline at end of file +export default getYouTubeTokens; diff --git a/lib/supabase/youtube_tokens/insertYouTubeTokens.ts b/lib/supabase/youtube_tokens/insertYouTubeTokens.ts index 72457017d..96c98b407 100644 --- a/lib/supabase/youtube_tokens/insertYouTubeTokens.ts +++ b/lib/supabase/youtube_tokens/insertYouTubeTokens.ts @@ -4,18 +4,19 @@ import type { YouTubeTokensRow, YouTubeTokensInsert } from "@/types/youtube"; /** * Insert or update YouTube tokens for an account * Uses upsert to handle both new tokens and token refreshes + * * @param tokens - YouTube tokens data to insert/update * @returns Inserted/updated YouTube tokens or null if operation failed */ const insertYouTubeTokens = async ( - tokens: YouTubeTokensInsert + tokens: YouTubeTokensInsert, ): Promise => { try { const { data, error } = await supabase .from("youtube_tokens") - .upsert(tokens, { + .upsert(tokens, { onConflict: "artist_account_id", - ignoreDuplicates: false + ignoreDuplicates: false, }) .select("*") .single(); @@ -32,4 +33,4 @@ const insertYouTubeTokens = async ( } }; -export default insertYouTubeTokens; \ No newline at end of file +export default insertYouTubeTokens; diff --git a/lib/tasks/deleteTask.ts b/lib/tasks/deleteTask.ts index 06466865e..656ecca9e 100644 --- a/lib/tasks/deleteTask.ts +++ b/lib/tasks/deleteTask.ts @@ -10,6 +10,8 @@ const SCHEDULE_NOT_FOUND_MSG = "Schedule not found"; /** * Check if error indicates schedule not found in external scheduler * Paused tasks are removed from the scheduler, making this error expected + * + * @param errorText */ function isScheduleNotFoundError(errorText: string): boolean { return errorText.includes(SCHEDULE_NOT_FOUND_MSG); @@ -17,6 +19,8 @@ function isScheduleNotFoundError(errorText: string): boolean { /** * Delete task record from database when scheduler deletion isn't possible + * + * @param taskId */ async function deleteTaskFromDatabase(taskId: string): Promise { await fetch("/api/scheduled-actions/delete", { @@ -28,6 +32,8 @@ async function deleteTaskFromDatabase(taskId: string): Promise { /** * Deletes a task via the Recoup API and database + * + * @param params * @see https://docs.recoupable.com/tasks/delete */ export async function deleteTask(params: DeleteTaskParams): Promise { diff --git a/lib/tasks/formatDuration.ts b/lib/tasks/formatDuration.ts index f0db4bb6e..7a626e1a6 100644 --- a/lib/tasks/formatDuration.ts +++ b/lib/tasks/formatDuration.ts @@ -1,3 +1,7 @@ +/** + * + * @param durationMs + */ export function formatDuration(durationMs: number | null): string { if (durationMs === null) return ""; if (durationMs < 1000) return `${durationMs}ms`; diff --git a/lib/tasks/formatTimestamp.ts b/lib/tasks/formatTimestamp.ts index 3d4c6c11e..cb5a06b42 100644 --- a/lib/tasks/formatTimestamp.ts +++ b/lib/tasks/formatTimestamp.ts @@ -1,3 +1,7 @@ +/** + * + * @param dateStr + */ export function formatTimestamp(dateStr: string): string { const date = new Date(dateStr); return date.toLocaleString(undefined, { diff --git a/lib/tasks/getStatusColor.ts b/lib/tasks/getStatusColor.ts index 8239276a0..aae2b64ee 100644 --- a/lib/tasks/getStatusColor.ts +++ b/lib/tasks/getStatusColor.ts @@ -1,12 +1,11 @@ const SUCCESS_STATUSES = new Set(["COMPLETED"]); -const ERROR_STATUSES = new Set([ - "FAILED", - "CRASHED", - "SYSTEM_FAILURE", - "INTERRUPTED", -]); +const ERROR_STATUSES = new Set(["FAILED", "CRASHED", "SYSTEM_FAILURE", "INTERRUPTED"]); const ACTIVE_STATUSES = new Set(["EXECUTING", "REATTEMPTING"]); +/** + * + * @param status + */ export function getStatusColor(status: string): string { if (SUCCESS_STATUSES.has(status)) { return "bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-400"; diff --git a/lib/tasks/getStatusLabel.ts b/lib/tasks/getStatusLabel.ts index 666e516c6..69d643fc6 100644 --- a/lib/tasks/getStatusLabel.ts +++ b/lib/tasks/getStatusLabel.ts @@ -13,6 +13,10 @@ const STATUS_LABELS: Record = { PENDING_VERSION: "Pending Version", }; +/** + * + * @param status + */ export function getStatusLabel(status: string): string { return STATUS_LABELS[status] ?? status; } diff --git a/lib/tasks/getTaskDisplayName.ts b/lib/tasks/getTaskDisplayName.ts index 7bdc44003..672f4e08d 100644 --- a/lib/tasks/getTaskDisplayName.ts +++ b/lib/tasks/getTaskDisplayName.ts @@ -6,6 +6,10 @@ const TASK_NAME_MAP: Record = { "pro-artist-social-profiles-scrape": "Social Scrape", }; +/** + * + * @param taskIdentifier + */ export function getTaskDisplayName(taskIdentifier: string): string { return TASK_NAME_MAP[taskIdentifier] ?? taskIdentifier; } diff --git a/lib/tasks/getTaskRunStatus.ts b/lib/tasks/getTaskRunStatus.ts index 84cd54329..4963520ff 100644 --- a/lib/tasks/getTaskRunStatus.ts +++ b/lib/tasks/getTaskRunStatus.ts @@ -20,11 +20,11 @@ export interface TaskRunStatus { /** * Fetches the current status of a Trigger.dev task run from the Recoup API. + * + * @param runId + * @param accessToken */ -export async function getTaskRunStatus( - runId: string, - accessToken: string, -): Promise { +export async function getTaskRunStatus(runId: string, accessToken: string): Promise { const url = new URL(`${TASKS_API_URL}/runs`); url.searchParams.set("runId", runId); diff --git a/lib/tasks/getTaskRuns.ts b/lib/tasks/getTaskRuns.ts index 7081f2bd4..e8bc68f2a 100644 --- a/lib/tasks/getTaskRuns.ts +++ b/lib/tasks/getTaskRuns.ts @@ -26,19 +26,13 @@ interface TaskRunListResponse { * @param limit - Maximum number of runs to return (default 20) * @returns Array of task run items */ -export async function getTaskRuns( - accessToken: string, - limit: number = 20, -): Promise { - const response = await fetch( - `${NEW_API_BASE_URL}/api/tasks/runs?limit=${limit}`, - { - method: "GET", - headers: { - Authorization: `Bearer ${accessToken}`, - }, +export async function getTaskRuns(accessToken: string, limit: number = 20): Promise { + const response = await fetch(`${NEW_API_BASE_URL}/api/tasks/runs?limit=${limit}`, { + method: "GET", + headers: { + Authorization: `Bearer ${accessToken}`, }, - ); + }); const data: TaskRunListResponse = await response.json(); diff --git a/lib/tasks/getTasks.ts b/lib/tasks/getTasks.ts index ca24029c8..7d73d9e9e 100644 --- a/lib/tasks/getTasks.ts +++ b/lib/tasks/getTasks.ts @@ -17,11 +17,11 @@ export interface GetTasksResponse { /** * Fetches tasks from the Recoup API + * + * @param params * @see https://docs.recoupable.com/tasks/get */ -export async function getTasks( - params?: GetTasksParams -): Promise { +export async function getTasks(params?: GetTasksParams): Promise { try { const url = new URL(TASKS_API_URL); diff --git a/lib/tasks/isRecurring.ts b/lib/tasks/isRecurring.ts index 067ad1e28..a97d1abf6 100644 --- a/lib/tasks/isRecurring.ts +++ b/lib/tasks/isRecurring.ts @@ -1,5 +1,7 @@ /** * Checks if a cron expression represents a recurring schedule (daily, weekly, or monthly) + * + * @param cronExpression */ export const isRecurring = (cronExpression: string): boolean => { try { diff --git a/lib/tasks/parseCronToHuman.ts b/lib/tasks/parseCronToHuman.ts index 2325f7ee9..39844bf14 100644 --- a/lib/tasks/parseCronToHuman.ts +++ b/lib/tasks/parseCronToHuman.ts @@ -8,4 +8,3 @@ export const parseCronToHuman = (cronExpression: string): string => { return cronExpression; } }; - diff --git a/lib/tasks/updateTask.ts b/lib/tasks/updateTask.ts index 33ace436d..ef3dab061 100644 --- a/lib/tasks/updateTask.ts +++ b/lib/tasks/updateTask.ts @@ -17,11 +17,11 @@ export interface UpdateTaskParams { /** * Updates an existing task via the Recoup API + * + * @param params * @see https://docs.recoupable.com/tasks/update */ -export async function updateTask( - params: UpdateTaskParams -): Promise { +export async function updateTask(params: UpdateTaskParams): Promise { try { const response = await fetch(TASKS_API_URL, { method: "PATCH", diff --git a/lib/telegram/errors/escapeTelegramMarkdown.ts b/lib/telegram/errors/escapeTelegramMarkdown.ts index 6a731684a..bff749a30 100644 --- a/lib/telegram/errors/escapeTelegramMarkdown.ts +++ b/lib/telegram/errors/escapeTelegramMarkdown.ts @@ -1,8 +1,9 @@ /** * Escapes special characters for Telegram Markdown to prevent parse errors. + * * @param text - The string to escape * @returns Escaped string safe for Telegram Markdown */ export function escapeTelegramMarkdown(text: string): string { - return text.replace(/[\_\*\[\]\(\)~`>#+\-=|{}.!]/g, (match) => `\\${match}`); + return text.replace(/[\_\*\[\]\(\)~`>#+\-=|{}.!]/g, match => `\\${match}`); } diff --git a/lib/telegram/errors/formatErrorMessage.ts b/lib/telegram/errors/formatErrorMessage.ts index 608572abf..5689cfb09 100644 --- a/lib/telegram/errors/formatErrorMessage.ts +++ b/lib/telegram/errors/formatErrorMessage.ts @@ -3,17 +3,12 @@ import { ErrorContext } from "./sendErrorNotification"; /** * Formats error message for Telegram notification and escapes for Telegram Markdown. + * * @param params - Error context object * @returns Escaped, formatted error message string */ export function formatErrorMessage(params: ErrorContext): string { - const { - error, - email = "unknown", - roomId = "new chat", - path, - messages, - } = params; + const { error, email = "unknown", roomId = "new chat", path, messages } = params; const timestamp = new Date().toISOString(); let message = `❌ Error Alert\n`; @@ -38,9 +33,7 @@ export function formatErrorMessage(params: ErrorContext): string { if (messages && messages.length > 0) { const lastMessage = messages[messages.length - 1]; - const lastMessageText = lastMessage?.parts - .filter((part) => part.type === "text") - .join(""); + const lastMessageText = lastMessage?.parts.filter(part => part.type === "text").join(""); if (lastMessageText) { message += `\nLast Message:\n${lastMessageText}`; } diff --git a/lib/telegram/errors/sendErrorNotification.ts b/lib/telegram/errors/sendErrorNotification.ts index f52822de0..4de6d402b 100644 --- a/lib/telegram/errors/sendErrorNotification.ts +++ b/lib/telegram/errors/sendErrorNotification.ts @@ -15,23 +15,22 @@ export interface ErrorContext { /** * Sends error notification to Telegram and stores in Supabase error_logs * Non-blocking to avoid impacting API operations + * + * @param params */ -export async function sendErrorNotification( - params: ErrorContext -): Promise { +export async function sendErrorNotification(params: ErrorContext): Promise { try { const message = formatErrorMessage(params); const telegramResponse = await sendMessage(message, { parse_mode: "Markdown" }); - + // Store error in Supabase database (non-blocking) // Database failures should not impact Telegram notifications createErrorLog({ ...params, telegram_message_id: telegramResponse.message_id, - }).catch((err) => { + }).catch(err => { console.error("Failed to store error log in database:", err); }); - } catch (err) { console.error("Error in sendErrorNotification:", err); } diff --git a/lib/telegram/sendDailyStatsMessage.ts b/lib/telegram/sendDailyStatsMessage.ts index 4e2da710b..c48b6413a 100644 --- a/lib/telegram/sendDailyStatsMessage.ts +++ b/lib/telegram/sendDailyStatsMessage.ts @@ -3,6 +3,12 @@ import { sendMessage } from "./sendMessage"; /** * Sends a formatted daily stats message to Telegram. + * + * @param root0 + * @param root0.newRoomsCount + * @param root0.roomsDelta + * @param root0.newMemoriesCount + * @param root0.memoriesDelta */ export async function sendDailyStatsMessage({ newRoomsCount, diff --git a/lib/telegram/sendMessage.ts b/lib/telegram/sendMessage.ts index 5ea83ea1a..3c49e2246 100644 --- a/lib/telegram/sendMessage.ts +++ b/lib/telegram/sendMessage.ts @@ -4,7 +4,7 @@ import { trimMessage } from "./trimMessage"; export const sendMessage = async ( text: string, - options?: TelegramBot.SendMessageOptions + options?: TelegramBot.SendMessageOptions, ): Promise => { if (!process.env.TELEGRAM_CHAT_ID) { throw new Error("TELEGRAM_CHAT_ID environment variable is required"); @@ -12,9 +12,5 @@ export const sendMessage = async ( const trimmedText = trimMessage(text); - return telegramClient.sendMessage( - process.env.TELEGRAM_CHAT_ID, - trimmedText, - options - ); + return telegramClient.sendMessage(process.env.TELEGRAM_CHAT_ID, trimmedText, options); }; diff --git a/lib/telegram/trimMessage.ts b/lib/telegram/trimMessage.ts index d8438cc24..c9d700ec6 100644 --- a/lib/telegram/trimMessage.ts +++ b/lib/telegram/trimMessage.ts @@ -3,7 +3,8 @@ const MAX_MESSAGE_LENGTH = 4000; // Using slightly less than 4096 to be safe /** * Trims a message to fit within Telegram's character limit - * @param text The text message to trim + * + * @param text - The text message to trim * @returns Trimmed text that fits within Telegram's message limits */ export const trimMessage = (text: string): string => { diff --git a/lib/tools/browser/browserAct.ts b/lib/tools/browser/browserAct.ts index 340c61656..78d0ba936 100644 --- a/lib/tools/browser/browserAct.ts +++ b/lib/tools/browser/browserAct.ts @@ -22,46 +22,37 @@ const browserAct = tool({ url: z .string() .url() - .refine( - (url) => { - try { - const parsed = new URL(url); - return parsed.protocol === 'http:' || parsed.protocol === 'https:'; - } catch { - return false; - } - }, - "URL must use http or https protocol" - ) - .refine( - (url) => !isBlockedStartUrl(url), - "URL points to a private or disallowed host" - ) + .refine(url => { + try { + const parsed = new URL(url); + return parsed.protocol === "http:" || parsed.protocol === "https:"; + } catch { + return false; + } + }, "URL must use http or https protocol") + .refine(url => !isBlockedStartUrl(url), "URL points to a private or disallowed host") .describe("The URL of the webpage to interact with"), action: z .string() .min(1, "Action is required") .max(500, "Action description must be 500 characters or less") - .refine( - (action) => { - // Remove dangerous patterns (script tags, protocol handlers) - const dangerous = / action.trim().replace(/\s+/g, ' ')) + .refine(action => { + // Remove dangerous patterns (script tags, protocol handlers) + const dangerous = / action.trim().replace(/\s+/g, " ")) .describe( - "Natural language description of the action to perform (e.g., 'click the submit button')" + "Natural language description of the action to perform (e.g., 'click the submit button')", ), }), execute: async ({ url, action }) => { try { return await withBrowser(async (page, liveViewUrl, sessionUrl) => { const targetUrl = normalizeInstagramUrl(url); - await page.goto(targetUrl, { - waitUntil: "domcontentloaded", - timeout: BROWSER_TIMEOUTS.PAGE_NAVIGATION + await page.goto(targetUrl, { + waitUntil: "domcontentloaded", + timeout: BROWSER_TIMEOUTS.PAGE_NAVIGATION, }); await page.act(action); @@ -86,4 +77,3 @@ const browserAct = tool({ }); export default browserAct; - diff --git a/lib/tools/browser/browserAgent.ts b/lib/tools/browser/browserAgent.ts index 22cab6da9..01f42e6e6 100644 --- a/lib/tools/browser/browserAgent.ts +++ b/lib/tools/browser/browserAgent.ts @@ -9,32 +9,29 @@ import { BROWSER_AGENT_CONFIG, BROWSER_TIMEOUTS } from "@/lib/browser/constants" const browserAgent = tool({ description: `Autonomous multi-step website navigation. Use when task requires multiple actions: "go to [site] and [do something]", "find [X] on [site]", "navigate and tell me". Example: "Go to fatbeats.com and find Instagram handle". Takes longer (up to 20 steps).`, inputSchema: z.object({ - startUrl: z - .string() - .url() - .describe("The URL where the agent should start the task"), + startUrl: z.string().url().describe("The URL where the agent should start the task"), task: z .string() .describe( - "Natural language description of the complete workflow to execute (e.g., 'search for laptops under $1000 and extract the top 5 results')" + "Natural language description of the complete workflow to execute (e.g., 'search for laptops under $1000 and extract the top 5 results')", ), model: z .string() .optional() .describe( - "AI model to use for the agent (defaults to gemini-2.5-computer-use-preview-10-2025)" + "AI model to use for the agent (defaults to gemini-2.5-computer-use-preview-10-2025)", ), }), execute: async function* ({ startUrl, task, model }) { const { stagehand, liveViewUrl, sessionUrl } = await initStagehand(); - + try { // Yield initial status with LIVE browser link IMMEDIATELY yield { - status: 'initializing', - message: liveViewUrl + status: "initializing", + message: liveViewUrl ? `🎥 **WATCH LIVE:** ${liveViewUrl}\n\n🤖 Initializing autonomous agent...\n\n💡 Click the link above to watch AI control the browser in real-time!` - : 'Initializing autonomous agent...', + : "Initializing autonomous agent...", liveViewUrl, sessionUrl, }; @@ -53,19 +50,19 @@ const browserAgent = tool({ const targetUrl = normalizeInstagramUrl(startUrl); yield { - status: 'navigating', + status: "navigating", message: `Navigating to ${targetUrl}...\n\n🎥 **WATCH LIVE:** ${liveViewUrl || sessionUrl}`, liveViewUrl, sessionUrl, }; - await stagehand.page.goto(targetUrl, { - waitUntil: "domcontentloaded", - timeout: BROWSER_TIMEOUTS.PAGE_NAVIGATION + await stagehand.page.goto(targetUrl, { + waitUntil: "domcontentloaded", + timeout: BROWSER_TIMEOUTS.PAGE_NAVIGATION, }); yield { - status: 'executing', + status: "executing", message: `🤖 Agent is working autonomously...\n\n**Task:** ${task}\n\n🎥 **WATCH LIVE:** ${liveViewUrl || sessionUrl}\n\n💡 Watch the AI click, type, and navigate the browser above!`, liveViewUrl, sessionUrl, @@ -92,12 +89,12 @@ const browserAgent = tool({ platformName, }; } catch (error) { - console.error('[browser_agent] Browser agent error:', error); - + console.error("[browser_agent] Browser agent error:", error); + try { await stagehand.close(); } catch (closeError) { - console.error('[browser_agent] Failed to close stagehand:', closeError); + console.error("[browser_agent] Failed to close stagehand:", closeError); } const errorMessage = error instanceof Error ? error.message : String(error); @@ -111,4 +108,3 @@ const browserAgent = tool({ }); export default browserAgent; - diff --git a/lib/tools/browser/browserExtract.ts b/lib/tools/browser/browserExtract.ts index 050df5ebc..66a830811 100644 --- a/lib/tools/browser/browserExtract.ts +++ b/lib/tools/browser/browserExtract.ts @@ -24,22 +24,22 @@ const browserExtract = tool({ schema: z .record(z.string()) .describe( - "Schema object defining the structure of data to extract (keys are field names, values are types: 'string', 'number', 'boolean', 'array')" + "Schema object defining the structure of data to extract (keys are field names, values are types: 'string', 'number', 'boolean', 'array')", ), instruction: z .string() .optional() .describe( - "Optional instruction to guide the extraction (e.g., 'extract product information from the main listing')" + "Optional instruction to guide the extraction (e.g., 'extract product information from the main listing')", ), }), execute: async ({ url, schema, instruction }) => { try { return await withBrowser(async (page, liveViewUrl, sessionUrl) => { const targetUrl = normalizeInstagramUrl(url); - await page.goto(targetUrl, { - waitUntil: "domcontentloaded", - timeout: BROWSER_TIMEOUTS.PAGE_NAVIGATION + await page.goto(targetUrl, { + waitUntil: "domcontentloaded", + timeout: BROWSER_TIMEOUTS.PAGE_NAVIGATION, }); const screenshotUrl = await captureScreenshot(page, url); @@ -71,4 +71,3 @@ const browserExtract = tool({ }); export default browserExtract; - diff --git a/lib/tools/browser/browserObserve.ts b/lib/tools/browser/browserObserve.ts index 1433017d8..f05e53c1a 100644 --- a/lib/tools/browser/browserObserve.ts +++ b/lib/tools/browser/browserObserve.ts @@ -20,15 +20,12 @@ export interface BrowserObserveResult { const browserObserve = tool({ description: `View any website and return all visible text. Use when user mentions a website/URL/social platform and wants to see content. Example: "What's on instagram.com/artist" or "Show me fatbeats.com"`, inputSchema: z.object({ - url: z - .string() - .url() - .describe("The URL of the webpage to observe"), + url: z.string().url().describe("The URL of the webpage to observe"), instruction: z .string() .optional() .describe( - "Optional instruction to guide the observation (e.g., 'find submit buttons', 'locate navigation menu')" + "Optional instruction to guide the observation (e.g., 'find submit buttons', 'locate navigation menu')", ), }), execute: async ({ url, instruction }) => { @@ -36,24 +33,25 @@ const browserObserve = tool({ return await withBrowser(async (page, liveViewUrl, sessionUrl) => { // 1. Setup: navigate, scroll, dismiss modals const modalDismissed = await performPageSetup(page, url); - + // 2. Extract: get page content and interactive elements const { visibleContent, observeResult } = await extractPageData(page, instruction); - + // 3. Capture: screenshot and metadata const screenshotUrl = await captureScreenshot(page, url); const actionsText = formatActionsToString(observeResult); const platformName = detectPlatform(url); - + // 4. Build: formatted response text - const isRateLimited = visibleContent.includes('Take a quick pause') || - visibleContent.includes('more requests than usual'); + const isRateLimited = + visibleContent.includes("Take a quick pause") || + visibleContent.includes("more requests than usual"); const responseText = buildResponseText( visibleContent, actionsText, modalDismissed, platformName, - isRateLimited + isRateLimited, ); return { @@ -76,4 +74,3 @@ const browserObserve = tool({ }); export default browserObserve; - diff --git a/lib/tools/browser/index.ts b/lib/tools/browser/index.ts index d1f150890..39e20434a 100644 --- a/lib/tools/browser/index.ts +++ b/lib/tools/browser/index.ts @@ -13,4 +13,3 @@ const browserTools = { }; export default browserTools; - diff --git a/lib/tools/browser/waitTool.ts b/lib/tools/browser/waitTool.ts index eb771706f..036022375 100644 --- a/lib/tools/browser/waitTool.ts +++ b/lib/tools/browser/waitTool.ts @@ -23,16 +23,12 @@ USAGE: This tool helps avoid detection as automated traffic by spacing out requests naturally.`, inputSchema: z.object({ - seconds: z - .number() - .min(1) - .max(600) - .describe("Number of seconds to wait (1-600)"), + seconds: z.number().min(1).max(600).describe("Number of seconds to wait (1-600)"), }), execute: async ({ seconds }) => { try { await new Promise(resolve => setTimeout(resolve, seconds * 1000)); - + return { success: true, message: `Waited ${seconds} seconds successfully`, @@ -47,4 +43,3 @@ This tool helps avoid detection as automated traffic by spacing out requests nat }); export default waitTool; - diff --git a/lib/tools/catalogs/getCatalogSongs.ts b/lib/tools/catalogs/getCatalogSongs.ts index 5bf090dc8..9cdeea9d3 100644 --- a/lib/tools/catalogs/getCatalogSongs.ts +++ b/lib/tools/catalogs/getCatalogSongs.ts @@ -25,12 +25,12 @@ const getCatalogSongsTool: Tool = { catalog_id: z .string() .describe( - "The unique identifier of the catalog to query songs for. Get this from the select_catalogs tool." + "The unique identifier of the catalog to query songs for. Get this from the select_catalogs tool.", ), criteria: z .string() .describe( - "The search criteria or theme to filter songs by (e.g., 'Halloween party songs', 'workout music', 'romantic ballads')" + "The search criteria or theme to filter songs by (e.g., 'Halloween party songs', 'workout music', 'romantic ballads')", ), }), execute: async ({ catalog_id, criteria }) => { @@ -56,10 +56,7 @@ const getCatalogSongsTool: Tool = { console.error("Error fetching catalog songs:", error); return { success: false, - error: - error instanceof Error - ? error.message - : "Failed to fetch catalog songs", + error: error instanceof Error ? error.message : "Failed to fetch catalog songs", songs: [], total_added: 0, }; diff --git a/lib/tools/createReleaseReport.ts b/lib/tools/createReleaseReport.ts index a6feed638..b4a3d04b4 100644 --- a/lib/tools/createReleaseReport.ts +++ b/lib/tools/createReleaseReport.ts @@ -19,9 +19,7 @@ const createReleaseReport = tool({ Plan thoroughly before every tool call and reflect on the outcome after each tool call. `, inputSchema: z.object({ - songTitle: z - .string() - .describe("The title of the song to create a release report for"), + songTitle: z.string().describe("The title of the song to create a release report for"), }), execute: async ({ songTitle }) => { return { diff --git a/lib/tools/createSegments.ts b/lib/tools/createSegments.ts index d1a21bc6d..6924ccdae 100644 --- a/lib/tools/createSegments.ts +++ b/lib/tools/createSegments.ts @@ -7,13 +7,13 @@ const schema = z.object({ .string() .min( 1, - "artist_account_id is required and should be pulled from the system prompt. Never request this from the user." + "artist_account_id is required and should be pulled from the system prompt. Never request this from the user.", ), prompt: z .string() .min( 1, - "Prompt is required and should be generated by the system if not provided. Never ask the user for this parameter; if not provided, make it up, but do not ever ask the user for it." + "Prompt is required and should be generated by the system if not provided. Never ask the user for this parameter; if not provided, make it up, but do not ever ask the user for it.", ), }); diff --git a/lib/tools/deleteArtist.ts b/lib/tools/deleteArtist.ts index 91b87d222..a42296d42 100644 --- a/lib/tools/deleteArtist.ts +++ b/lib/tools/deleteArtist.ts @@ -24,18 +24,13 @@ const deleteArtist = tool({ artist_account_id: z .string() .describe( - "The ID of the artist to delete. If not provided, use the active artist_account_id." + "The ID of the artist to delete. If not provided, use the active artist_account_id.", ), account_id: z .string() - .describe( - "The ID of the account that owns the artist. If not provided, use the account_id." - ), + .describe("The ID of the account that owns the artist. If not provided, use the account_id."), }), - execute: async ({ - artist_account_id, - account_id, - }): Promise => { + execute: async ({ artist_account_id, account_id }): Promise => { try { // If no artist_account_id was provided, attempt to get the active artist // This would be handled by the AI when calling the tool @@ -48,10 +43,7 @@ const deleteArtist = tool({ } // Call the API to delete the artist - const response = await deleteArtistFromAccount( - artist_account_id, - account_id - ); + const response = await deleteArtistFromAccount(artist_account_id, account_id); if (!response.success) { return { @@ -68,9 +60,7 @@ const deleteArtist = tool({ }; } catch (error) { const errorMessage = - error instanceof Error - ? error.message - : "Failed to delete artist for unknown reason"; + error instanceof Error ? error.message : "Failed to delete artist for unknown reason"; return { success: false, diff --git a/lib/tools/files/createFolder.ts b/lib/tools/files/createFolder.ts index a5864eaf7..80d84975e 100644 --- a/lib/tools/files/createFolder.ts +++ b/lib/tools/files/createFolder.ts @@ -27,12 +27,8 @@ Note: .string() .optional() .describe("Optional parent directory path (e.g., 'projects', 'documents')"), - active_account_id: z - .string() - .describe("Pull active_account_id from the system prompt"), - active_artist_id: z - .string() - .describe("Pull active_artist_id from the system prompt"), + active_account_id: z.string().describe("Pull active_account_id from the system prompt"), + active_artist_id: z.string().describe("Pull active_artist_id from the system prompt"), }), execute: async ({ folderName, path, active_account_id, active_artist_id }) => { try { @@ -58,7 +54,7 @@ Note: let sanitizedPath: string | undefined; if (path) { sanitizedPath = path.trim(); - + if (!isValidPath(sanitizedPath)) { return { success: false, @@ -70,11 +66,7 @@ Note: const fullPath = sanitizedPath ? `${sanitizedPath}/${trimmedFolderName}` : trimmedFolderName; - await ensureDirectoryExists( - active_account_id, - active_artist_id, - fullPath - ); + await ensureDirectoryExists(active_account_id, active_artist_id, fullPath); return { success: true, @@ -89,4 +81,3 @@ Note: }); export default createFolder; - diff --git a/lib/tools/files/deleteFile.ts b/lib/tools/files/deleteFile.ts index f0605a63c..0349008ca 100644 --- a/lib/tools/files/deleteFile.ts +++ b/lib/tools/files/deleteFile.ts @@ -37,40 +37,26 @@ Important: .boolean() .optional() .default(false) - .describe("If true, delete directory and all its contents. Required for non-empty directories."), - active_account_id: z - .string() - .describe("Pull active_account_id from the system prompt"), - active_artist_id: z - .string() - .describe("Pull active_artist_id from the system prompt"), + .describe( + "If true, delete directory and all its contents. Required for non-empty directories.", + ), + active_account_id: z.string().describe("Pull active_account_id from the system prompt"), + active_artist_id: z.string().describe("Pull active_artist_id from the system prompt"), }), - execute: async ({ - fileName, - path, - recursive, - active_account_id, - active_artist_id, - }) => { + execute: async ({ fileName, path, recursive, active_account_id, active_artist_id }) => { const normalizedFileName = normalizeFileName(fileName); - + try { - let fileRecord = await findFileByName( normalizedFileName, active_account_id, active_artist_id, - path + path, ); // If not found with normalized name, try original (for directories) if (!fileRecord && normalizedFileName !== fileName) { - fileRecord = await findFileByName( - fileName, - active_account_id, - active_artist_id, - path - ); + fileRecord = await findFileByName(fileName, active_account_id, active_artist_id, path); } if (!fileRecord) { @@ -91,7 +77,7 @@ Important: const childFiles = await listFilesByArtist( active_account_id, active_artist_id, - path ? `${path}/${fileName}` : fileName + path ? `${path}/${fileName}` : fileName, ); if (childFiles.length > 0) { @@ -103,10 +89,7 @@ Important: } } else { // Recursive deletion: get all children WITHOUT deleting DB records yet - const childFiles = await getFilesInDirectory( - fileRecord.storage_key, - fileRecord.id - ); + const childFiles = await getFilesInDirectory(fileRecord.storage_key, fileRecord.id); // Collect storage keys and file IDs for later deletion childFiles.forEach(child => { @@ -130,7 +113,7 @@ Important: if (fileIdsToDelete.length > 0) { await deleteFileRecords(fileIdsToDelete); } - + // Delete the main file/directory record await deleteFileRecord(fileRecord.id); @@ -151,4 +134,3 @@ Important: }); export default deleteFile; - diff --git a/lib/tools/files/index.ts b/lib/tools/files/index.ts index cc7e11c75..b16c7a483 100644 --- a/lib/tools/files/index.ts +++ b/lib/tools/files/index.ts @@ -20,4 +20,4 @@ const filesTools = { create_folder: createFolder, }; -export default filesTools; \ No newline at end of file +export default filesTools; diff --git a/lib/tools/files/listFiles.ts b/lib/tools/files/listFiles.ts index aa40912d1..40143a414 100644 --- a/lib/tools/files/listFiles.ts +++ b/lib/tools/files/listFiles.ts @@ -18,18 +18,18 @@ When to use: path: z .string() .optional() - .describe("Optional subdirectory path to list files from (e.g., 'research', 'reports'). Defaults to root directory."), + .describe( + "Optional subdirectory path to list files from (e.g., 'research', 'reports'). Defaults to root directory.", + ), textFilesOnly: z .boolean() .optional() .default(false) - .describe("If true, only return text-based files (md, txt, json, etc). If false, return all files."), - active_account_id: z - .string() - .describe("Pull active_account_id from the system prompt"), - active_artist_id: z - .string() - .describe("Pull active_artist_id from the system prompt"), + .describe( + "If true, only return text-based files (md, txt, json, etc). If false, return all files.", + ), + active_account_id: z.string().describe("Pull active_account_id from the system prompt"), + active_artist_id: z.string().describe("Pull active_artist_id from the system prompt"), }), execute: async ({ path, textFilesOnly, active_account_id, active_artist_id }) => { try { @@ -38,14 +38,12 @@ When to use: // Filter to text files only if requested if (textFilesOnly && files.length > 0) { - files = files.filter((file) => { + files = files.filter(file => { // Keep directories (for navigation) if (file.is_directory) return true; // Check if file has a text extension - return TEXT_EXTENSIONS.some((ext) => - file.file_name.toLowerCase().endsWith(ext) - ); + return TEXT_EXTENSIONS.some(ext => file.file_name.toLowerCase().endsWith(ext)); }); } diff --git a/lib/tools/files/moveFile.ts b/lib/tools/files/moveFile.ts index 066122dc9..2a80e2b25 100644 --- a/lib/tools/files/moveFile.ts +++ b/lib/tools/files/moveFile.ts @@ -27,39 +27,28 @@ Important: - Target file cannot already exist `, inputSchema: z.object({ - fileName: z - .string() - .describe("Name of the file to move (e.g., 'research.md')"), + fileName: z.string().describe("Name of the file to move (e.g., 'research.md')"), sourcePath: z .string() .optional() - .describe("Current directory path (e.g., 'old-folder'). Leave empty if file is in root directory."), + .describe( + "Current directory path (e.g., 'old-folder'). Leave empty if file is in root directory.", + ), targetPath: z .string() .describe("Destination directory path (e.g., 'reports', 'research/2024'). Cannot be empty."), - active_account_id: z - .string() - .describe("Pull active_account_id from the system prompt"), - active_artist_id: z - .string() - .describe("Pull active_artist_id from the system prompt"), + active_account_id: z.string().describe("Pull active_account_id from the system prompt"), + active_artist_id: z.string().describe("Pull active_artist_id from the system prompt"), }), - execute: async ({ - fileName, - sourcePath, - targetPath, - active_account_id, - active_artist_id, - }) => { + execute: async ({ fileName, sourcePath, targetPath, active_account_id, active_artist_id }) => { const normalizedFileName = normalizeFileName(fileName); - + try { - const fileRecord = await findFileByName( normalizedFileName, active_account_id, active_artist_id, - sourcePath + sourcePath, ); if (!fileRecord) { @@ -82,15 +71,12 @@ Important: return { success: false, error: "Target path cannot be empty.", - message: "Please specify a destination directory. Use rename_file_or_folder if you want to keep the file in the same location.", + message: + "Please specify a destination directory. Use rename_file_or_folder if you want to keep the file in the same location.", }; } - const fullTargetPath = generateStoragePath( - active_account_id, - active_artist_id, - targetPath - ); + const fullTargetPath = generateStoragePath(active_account_id, active_artist_id, targetPath); if (!isValidStorageKey(fullTargetPath)) { return { @@ -117,7 +103,7 @@ Important: normalizedFileName, active_account_id, active_artist_id, - targetPath + targetPath, ); if (existingFile) { @@ -132,14 +118,10 @@ Important: active_account_id, active_artist_id, normalizedFileName, - targetPath + targetPath, ); - await copyFileByKey( - fileRecord.storage_key, - newStorageKey, - fileRecord.mime_type || undefined - ); + await copyFileByKey(fileRecord.storage_key, newStorageKey, fileRecord.mime_type || undefined); await updateFileStorageKey(fileRecord.id, newStorageKey); await deleteFileByKey(fileRecord.storage_key); @@ -159,4 +141,3 @@ Important: }); export default moveFile; - diff --git a/lib/tools/files/readFile.ts b/lib/tools/files/readFile.ts index 2ed4a6cd8..ec053af96 100644 --- a/lib/tools/files/readFile.ts +++ b/lib/tools/files/readFile.ts @@ -22,24 +22,21 @@ When to use: path: z .string() .optional() - .describe("Optional subdirectory path within the artist's storage (e.g., 'research', 'reports')"), - active_account_id: z - .string() - .describe("Pull active_account_id from the system prompt"), - active_artist_id: z - .string() - .describe("Pull active_artist_id from the system prompt"), + .describe( + "Optional subdirectory path within the artist's storage (e.g., 'research', 'reports')", + ), + active_account_id: z.string().describe("Pull active_account_id from the system prompt"), + active_artist_id: z.string().describe("Pull active_artist_id from the system prompt"), }), execute: async ({ fileName, path, active_account_id, active_artist_id }) => { const normalizedFileName = normalizeFileName(fileName); - + try { - const fileRecord = await findFileByName( normalizedFileName, active_account_id, active_artist_id, - path + path, ); if (!fileRecord) { diff --git a/lib/tools/files/renameFile.ts b/lib/tools/files/renameFile.ts index 315ae1208..5e2a1efdd 100644 --- a/lib/tools/files/renameFile.ts +++ b/lib/tools/files/renameFile.ts @@ -27,32 +27,22 @@ Important: - Special characters and path separators are not allowed `, inputSchema: z.object({ - fileName: z - .string() - .describe("Current name of the file (e.g., 'old-research.md')"), + fileName: z.string().describe("Current name of the file (e.g., 'old-research.md')"), newFileName: z .string() - .describe("New name for the file (e.g., 'updated-research'). Do not include extension - it will be preserved automatically."), + .describe( + "New name for the file (e.g., 'updated-research'). Do not include extension - it will be preserved automatically.", + ), path: z .string() .optional() .describe("Optional subdirectory path where file is located (e.g., 'research', 'reports')"), - active_account_id: z - .string() - .describe("Pull active_account_id from the system prompt"), - active_artist_id: z - .string() - .describe("Pull active_artist_id from the system prompt"), + active_account_id: z.string().describe("Pull active_account_id from the system prompt"), + active_artist_id: z.string().describe("Pull active_artist_id from the system prompt"), }), - execute: async ({ - fileName, - newFileName, - path, - active_account_id, - active_artist_id, - }) => { + execute: async ({ fileName, newFileName, path, active_account_id, active_artist_id }) => { const normalizedFileName = normalizeFileName(fileName); - + try { if (path && !isValidPath(path)) { return { @@ -61,12 +51,12 @@ Important: message: `Path '${path}' is invalid.`, }; } - + const fileRecord = await findFileByName( normalizedFileName, active_account_id, active_artist_id, - path + path, ); if (!fileRecord) { @@ -86,9 +76,8 @@ Important: } const extension = getFileExtension(normalizedFileName); - const finalNewFileName = extension && !newFileName.endsWith(extension) - ? newFileName + extension - : newFileName; + const finalNewFileName = + extension && !newFileName.endsWith(extension) ? newFileName + extension : newFileName; if (!isValidFileName(finalNewFileName)) { return { @@ -102,7 +91,7 @@ Important: finalNewFileName, active_account_id, active_artist_id, - path + path, ); if (existingFile) { @@ -117,14 +106,10 @@ Important: active_account_id, active_artist_id, finalNewFileName, - path + path, ); - await copyFileByKey( - fileRecord.storage_key, - newStorageKey, - fileRecord.mime_type || undefined - ); + await copyFileByKey(fileRecord.storage_key, newStorageKey, fileRecord.mime_type || undefined); await updateFileName(fileRecord.id, finalNewFileName, newStorageKey); await deleteFileByKey(fileRecord.storage_key); @@ -144,4 +129,3 @@ Important: }); export default renameFile; - diff --git a/lib/tools/files/renameFolder.ts b/lib/tools/files/renameFolder.ts index a1d5633c8..016d2cd33 100644 --- a/lib/tools/files/renameFolder.ts +++ b/lib/tools/files/renameFolder.ts @@ -27,30 +27,16 @@ Important: - Special characters and path separators are not allowed `, inputSchema: z.object({ - folderName: z - .string() - .describe("Current name of the folder (e.g., 'Albums', 'old-folder')"), - newFolderName: z - .string() - .describe("New name for the folder (e.g., 'Music', 'new-folder')"), + folderName: z.string().describe("Current name of the folder (e.g., 'Albums', 'old-folder')"), + newFolderName: z.string().describe("New name for the folder (e.g., 'Music', 'new-folder')"), path: z .string() .optional() .describe("Optional parent directory path (e.g., 'projects', 'documents')"), - active_account_id: z - .string() - .describe("Pull active_account_id from the system prompt"), - active_artist_id: z - .string() - .describe("Pull active_artist_id from the system prompt"), + active_account_id: z.string().describe("Pull active_account_id from the system prompt"), + active_artist_id: z.string().describe("Pull active_artist_id from the system prompt"), }), - execute: async ({ - folderName, - newFolderName, - path, - active_account_id, - active_artist_id, - }) => { + execute: async ({ folderName, newFolderName, path, active_account_id, active_artist_id }) => { try { if (path && !isValidPath(path)) { return { @@ -64,7 +50,7 @@ Important: folderName, active_account_id, active_artist_id, - path + path, ); if (!folderRecord) { @@ -103,7 +89,7 @@ Important: newFolderName, active_account_id, active_artist_id, - path + path, ); if (existingFolder) { @@ -118,10 +104,10 @@ Important: active_account_id, active_artist_id, `${newFolderName}/`, - path + path, ); const oldStorageKey = folderRecord.storage_key; - + const escapedKey = escapeLikePattern(oldStorageKey); const { data: children, error: childrenError } = await supabase .from("files") @@ -135,23 +121,22 @@ Important: if (children && children.length > 0) { for (const child of children) { - const newChildStorageKey = child.storage_key.replace( - oldStorageKey, - newStorageKey - ); - + const newChildStorageKey = child.storage_key.replace(oldStorageKey, newStorageKey); + // Only move actual files in storage, not directory records - const isChildDirectory = child.storage_key.endsWith('/'); - + const isChildDirectory = child.storage_key.endsWith("/"); + if (!isChildDirectory) { // Copy file to new location in storage try { await copyFileByKey(child.storage_key, newChildStorageKey); } catch (copyError) { - throw new Error(`Failed to copy file in storage: ${copyError instanceof Error ? copyError.message : 'Unknown error'}`); + throw new Error( + `Failed to copy file in storage: ${copyError instanceof Error ? copyError.message : "Unknown error"}`, + ); } } - + // Update database record with new storage_key const { error: updateError } = await supabase .from("files") @@ -164,7 +149,7 @@ Important: if (updateError) { throw new Error(`Failed to update child database record: ${updateError.message}`); } - + // Delete old file from storage if (!isChildDirectory) { try { @@ -186,7 +171,7 @@ Important: childrenUpdated: children?.length || 0, path: path || "root", storageKey: newStorageKey, - message: `Successfully renamed folder '${folderName}' to '${newFolderName}'${children?.length ? ` (updated ${children.length} items inside)` : ''}.`, + message: `Successfully renamed folder '${folderName}' to '${newFolderName}'${children?.length ? ` (updated ${children.length} items inside)` : ""}.`, }; } catch (error) { return handleToolError(error, "rename folder", folderName); @@ -195,4 +180,3 @@ Important: }); export default renameFolder; - diff --git a/lib/tools/files/updateFile.ts b/lib/tools/files/updateFile.ts index 92b13903b..adac3d6a5 100644 --- a/lib/tools/files/updateFile.ts +++ b/lib/tools/files/updateFile.ts @@ -24,39 +24,24 @@ Important: - Specify fileName and optionally path to locate the file `, inputSchema: z.object({ - fileName: z - .string() - .describe("Name of the file to update (e.g., 'research.md', 'report.txt')"), - newContent: z - .string() - .describe("The new content that will replace the current file content"), + fileName: z.string().describe("Name of the file to update (e.g., 'research.md', 'report.txt')"), + newContent: z.string().describe("The new content that will replace the current file content"), path: z .string() .optional() .describe("Optional subdirectory path where file is located (e.g., 'research', 'reports')"), - active_account_id: z - .string() - .describe("Pull active_account_id from the system prompt"), - active_artist_id: z - .string() - .describe("Pull active_artist_id from the system prompt"), + active_account_id: z.string().describe("Pull active_account_id from the system prompt"), + active_artist_id: z.string().describe("Pull active_artist_id from the system prompt"), }), - execute: async ({ - fileName, - newContent, - path, - active_account_id, - active_artist_id, - }) => { + execute: async ({ fileName, newContent, path, active_account_id, active_artist_id }) => { const normalizedFileName = normalizeFileName(fileName); - + try { - const fileRecord = await findFileByName( normalizedFileName, active_account_id, active_artist_id, - path + path, ); if (!fileRecord) { @@ -79,7 +64,7 @@ Important: const normalizedCurrent = normalizeContent(currentContent); const normalizedNewPreUpload = normalizeContent(newContent); - + if (normalizedCurrent === normalizedNewPreUpload) { return { success: false, @@ -88,7 +73,7 @@ Important: message: `No update needed - '${normalizedFileName}' already contains this exact content.`, }; } - + const blob = new Blob([newContent], { type: fileRecord.mime_type || "text/plain", }); @@ -100,7 +85,7 @@ Important: contentType: fileRecord.mime_type || "text/plain", upsert: true, }); - + // Wait for Supabase Storage cache to update before verifying await new Promise(resolve => setTimeout(resolve, 300)); @@ -108,14 +93,15 @@ Important: const normalizedUpdated = normalizeContent(updatedContent); const normalizedNew = normalizeContent(newContent); - + if (normalizedUpdated !== normalizedNew) { return { success: false, verified: false, error: "File content does not match what was uploaded.", message: `Update verification failed - '${normalizedFileName}' was modified but doesn't contain the expected content. Found ${updatedContent.length} bytes instead of expected ${newContent.length} bytes.`, - suggestion: "Read the current file content to see what it contains, then retry the update.", + suggestion: + "Read the current file content to see what it contains, then retry the update.", }; } diff --git a/lib/tools/files/writeFile.ts b/lib/tools/files/writeFile.ts index aa1fd3a27..0e8211d13 100644 --- a/lib/tools/files/writeFile.ts +++ b/lib/tools/files/writeFile.ts @@ -37,28 +37,25 @@ Important: inputSchema: z.object({ fileName: z .string() - .describe("Name of the file to create. Choose extension based on content: .json for JSON data, .csv for tabular data, .md for text/documentation (preferred over .txt), or omit extension to default to .md. Examples: 'research', 'data.json', 'results.csv'"), - content: z - .string() - .describe("The text content to write to the file"), + .describe( + "Name of the file to create. Choose extension based on content: .json for JSON data, .csv for tabular data, .md for text/documentation (preferred over .txt), or omit extension to default to .md. Examples: 'research', 'data.json', 'results.csv'", + ), + content: z.string().describe("The text content to write to the file"), mimeType: z .string() .optional() - .describe("MIME type of the file (e.g., 'text/plain', 'text/markdown', 'application/json'). Auto-detected if not provided."), + .describe( + "MIME type of the file (e.g., 'text/plain', 'text/markdown', 'application/json'). Auto-detected if not provided.", + ), path: z .string() .optional() - .describe("Optional subdirectory path where file should be created (e.g., 'research', 'reports')"), - description: z - .string() - .optional() - .describe("Optional description of the file content"), - active_account_id: z - .string() - .describe("Pull active_account_id from the system prompt"), - active_artist_id: z - .string() - .describe("Pull active_artist_id from the system prompt"), + .describe( + "Optional subdirectory path where file should be created (e.g., 'research', 'reports')", + ), + description: z.string().optional().describe("Optional description of the file content"), + active_account_id: z.string().describe("Pull active_account_id from the system prompt"), + active_artist_id: z.string().describe("Pull active_artist_id from the system prompt"), }), execute: async ({ fileName, @@ -70,14 +67,13 @@ Important: active_artist_id, }) => { const normalizedFileName = normalizeFileName(fileName); - - try { + try { const existingFile = await findFileByName( normalizedFileName, active_account_id, active_artist_id, - path + path, ); if (existingFile) { @@ -96,7 +92,7 @@ Important: active_account_id, active_artist_id, normalizedFileName, - path + path, ); let detectedMimeType = mimeType; diff --git a/lib/tools/generateMermaidDiagram.ts b/lib/tools/generateMermaidDiagram.ts index 0164889cf..1edf9f302 100644 --- a/lib/tools/generateMermaidDiagram.ts +++ b/lib/tools/generateMermaidDiagram.ts @@ -17,7 +17,7 @@ export const generateMermaidDiagram = tool({ "Detailed description of the desired diagram, including entities, relationships, flow, or structure. " + "Specify the type of diagram if known (e.g., flowchart, sequence diagram). " + "Example: 'Flowchart for user login: Start -> Enter Credentials -> Validate -> Success/Failure'. " + - "Example: 'Sequence diagram for API call: User -> Frontend -> API -> Database'" + "Example: 'Sequence diagram for API call: User -> Frontend -> API -> Database'", ), }), execute: async ({ context }) => { @@ -28,12 +28,8 @@ export const generateMermaidDiagram = tool({ `, }); - const extractedMermaid = result.text.match( - /```mermaid\\n?([\\s\\S]*?)\\n?```/ - ); - const mermaidContent = extractedMermaid - ? extractedMermaid[0] - : result.text.trim(); + const extractedMermaid = result.text.match(/```mermaid\\n?([\\s\\S]*?)\\n?```/); + const mermaidContent = extractedMermaid ? extractedMermaid[0] : result.text.trim(); return { content: [{ type: "text", text: mermaidContent }], diff --git a/lib/tools/get-tools-name.ts b/lib/tools/get-tools-name.ts index 56986faab..f909d706d 100644 --- a/lib/tools/get-tools-name.ts +++ b/lib/tools/get-tools-name.ts @@ -1,8 +1,6 @@ export const getDisplayToolName = (name: string) => { // Remove default_api. prefix if present (for beta AI SDK compatibility) - const cleanName = name.startsWith("default_api.") - ? name.replace("default_api.", "") - : name; + const cleanName = name.startsWith("default_api.") ? name.replace("default_api.", "") : name; switch (cleanName) { case "search_web": @@ -18,7 +16,7 @@ export const getDisplayToolName = (name: string) => { default: return cleanName .split("_") - .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) + .map(word => word.charAt(0).toUpperCase() + word.slice(1)) .join(" "); } }; diff --git a/lib/tools/getApifyScraper.ts b/lib/tools/getApifyScraper.ts index 2c0e6b7c2..4cc07319d 100644 --- a/lib/tools/getApifyScraper.ts +++ b/lib/tools/getApifyScraper.ts @@ -28,30 +28,24 @@ This tool uses the Apify API Client to fetch the current status of a scraper run execute: async ({ runId }): Promise => { try { const response = await fetch( - `https://api.recoupable.com/api/apify/scraper?runId=${encodeURIComponent( - runId - )}`, + `https://api.recoupable.com/api/apify/scraper?runId=${encodeURIComponent(runId)}`, { method: "GET", headers: { "Content-Type": "application/json", }, - } + }, ); if (!response.ok) { - throw new Error( - `Failed to fetch Apify scraper results: ${response.statusText}` - ); + throw new Error(`Failed to fetch Apify scraper results: ${response.statusText}`); } const data = await response.json(); return apifyScraperResponseSchema.parse(data); } catch (error) { console.error("Error in getApifyScraper tool:", error); - throw error instanceof Error - ? error - : new Error("Failed to fetch Apify scraper results"); + throw error instanceof Error ? error : new Error("Failed to fetch Apify scraper results"); } }, }); diff --git a/lib/tools/getArtistSegments.ts b/lib/tools/getArtistSegments.ts index e5a80f146..0a67ee221 100644 --- a/lib/tools/getArtistSegments.ts +++ b/lib/tools/getArtistSegments.ts @@ -65,10 +65,7 @@ const getArtistSegments = tool({ return { success: false, status: "error", - message: - error instanceof Error - ? error.message - : "Failed to fetch artist segments", + message: error instanceof Error ? error.message : "Failed to fetch artist segments", segments: [], pagination: { total_count: 0, diff --git a/lib/tools/getMcpTools.ts b/lib/tools/getMcpTools.ts index 67722e565..b32e7a534 100644 --- a/lib/tools/getMcpTools.ts +++ b/lib/tools/getMcpTools.ts @@ -21,6 +21,9 @@ import filesTools from "./files"; import browserTools from "./browser"; import getCatalogSongs from "./catalogs/getCatalogSongs"; +/** + * + */ export function getMcpTools(): ToolSet { const tools = { create_segments: createSegments, diff --git a/lib/tools/getPostComments.ts b/lib/tools/getPostComments.ts index a7ebe2b97..e6ff3e4fa 100644 --- a/lib/tools/getPostComments.ts +++ b/lib/tools/getPostComments.ts @@ -44,10 +44,7 @@ const getPostComments = tool({ return { success: false, status: "error", - message: - error instanceof Error - ? error.message - : "Failed to fetch post comments", + message: error instanceof Error ? error.message : "Failed to fetch post comments", comments: [], pagination: { total_count: 0, diff --git a/lib/tools/getSegmentFans.ts b/lib/tools/getSegmentFans.ts index c8b49c2d2..b651b9f87 100644 --- a/lib/tools/getSegmentFans.ts +++ b/lib/tools/getSegmentFans.ts @@ -45,10 +45,7 @@ const getSegmentFans = tool({ return { success: false, status: "error", - message: - error instanceof Error - ? error.message - : "Failed to fetch segment fans", + message: error instanceof Error ? error.message : "Failed to fetch segment fans", fans: [], pagination: { total_count: 0, diff --git a/lib/tools/getSocialFans.ts b/lib/tools/getSocialFans.ts index fa87cde8b..c3b062c1c 100644 --- a/lib/tools/getSocialFans.ts +++ b/lib/tools/getSocialFans.ts @@ -27,10 +27,7 @@ const getSocialFans = tool({ return { success: false, status: "error", - message: - error instanceof Error - ? error.message - : "Failed to fetch social fans", + message: error instanceof Error ? error.message : "Failed to fetch social fans", data: [], count: 0, }; diff --git a/lib/tools/getSocialPosts.ts b/lib/tools/getSocialPosts.ts index 4b952af48..148b068da 100644 --- a/lib/tools/getSocialPosts.ts +++ b/lib/tools/getSocialPosts.ts @@ -65,10 +65,7 @@ const getSocialPosts = tool({ return { success: false, status: "error", - message: - error instanceof Error - ? error.message - : "Failed to fetch social posts", + message: error instanceof Error ? error.message : "Failed to fetch social posts", posts: [], pagination: { total_count: 0, diff --git a/lib/tools/getTwitterTrends.ts b/lib/tools/getTwitterTrends.ts index 4c7c284ab..f6ed097c0 100644 --- a/lib/tools/getTwitterTrends.ts +++ b/lib/tools/getTwitterTrends.ts @@ -34,10 +34,7 @@ const getTwitterTrends = tool({ return { status: "error", trends: [], - message: - error instanceof Error - ? error.message - : "Failed to fetch Twitter trends", + message: error instanceof Error ? error.message : "Failed to fetch Twitter trends", }; } }, diff --git a/lib/tools/searchGoogleImages.ts b/lib/tools/searchGoogleImages.ts index 95fbdf307..045f253b7 100644 --- a/lib/tools/searchGoogleImages.ts +++ b/lib/tools/searchGoogleImages.ts @@ -23,29 +23,26 @@ const getSearchGoogleImagesTool = () => { query: z .string() .describe( - "The search query (e.g., 'Mac Miller concert', 'Wiz Khalifa album cover'). Be specific for best results." + "The search query (e.g., 'Mac Miller concert', 'Wiz Khalifa album cover'). Be specific for best results.", ), - limit: z - .number() - .optional() - .describe("Number of images to return (1-100, default: 8)."), + limit: z.number().optional().describe("Number of images to return (1-100, default: 8)."), imageSize: z .enum(["l", "m", "i"]) .optional() .describe( - "Image size: 'l' (large, recommended), 'm' (medium), 'i' (icon/small). Leave unset if unsure." + "Image size: 'l' (large, recommended), 'm' (medium), 'i' (icon/small). Leave unset if unsure.", ), imageType: z .enum(["photo", "clipart", "lineart", "animated"]) .optional() .describe( - "Type of image: 'photo' (default, recommended), 'clipart', 'lineart', 'animated'. Leave unset if unsure." + "Type of image: 'photo' (default, recommended), 'clipart', 'lineart', 'animated'. Leave unset if unsure.", ), aspectRatio: z .enum(["square", "wide", "tall", "panoramic"]) .optional() .describe( - "Aspect ratio filter. WARNING: May not always be supported. Only use if specifically requested. Leave unset for general searches." + "Aspect ratio filter. WARNING: May not always be supported. Only use if specifically requested. Leave unset for general searches.", ), }), execute: async function* ({ @@ -67,7 +64,7 @@ const getSearchGoogleImagesTool = () => { } satisfies SearchProgress; // Small delay to ensure UI renders the searching state - await new Promise((resolve) => setTimeout(resolve, 500)); + await new Promise(resolve => setTimeout(resolve, 500)); try { // Perform the search @@ -84,7 +81,7 @@ const getSearchGoogleImagesTool = () => { success: true, query, total_results: response.images_results.length, - images: response.images_results.map((img) => ({ + images: response.images_results.map(img => ({ position: img.position, thumbnail: img.thumbnail, original: img.original, diff --git a/lib/tools/searchTwitter.ts b/lib/tools/searchTwitter.ts index 210923030..448223eb3 100644 --- a/lib/tools/searchTwitter.ts +++ b/lib/tools/searchTwitter.ts @@ -20,11 +20,7 @@ const SEARCH_OPERATORS = { // Zod schema for parameter validation const schema = z.object({ query: z.string().min(1, "Search query is required"), - maxTweets: z - .number() - .min(1) - .max(1000) - .describe("Maximum number of tweets to return (1-1000)"), + maxTweets: z.number().min(1).max(1000).describe("Maximum number of tweets to return (1-1000)"), searchMode: z .enum(SEARCH_MODES) .optional() @@ -94,11 +90,7 @@ You can combine these operators to create powerful search queries. For example: Note: The tool will automatically use get_artist_socials to find the correct Twitter handle before searching.`, inputSchema: schema, - execute: async ({ - query, - maxTweets, - searchMode, - }): Promise => { + execute: async ({ query, maxTweets, searchMode }): Promise => { try { const url = new URL("https://api.recoupable.com/api/x/search"); url.searchParams.append("query", query); @@ -124,8 +116,7 @@ Note: The tool will automatically use get_artist_socials to find the correct Twi return { status: "error", tweets: [], - message: - error instanceof Error ? error.message : "Failed to search Twitter", + message: error instanceof Error ? error.message : "Failed to search Twitter", }; } }, diff --git a/lib/tools/searchWeb/getWebDeepResearchTool.ts b/lib/tools/searchWeb/getWebDeepResearchTool.ts index fd12e1737..523671dc2 100644 --- a/lib/tools/searchWeb/getWebDeepResearchTool.ts +++ b/lib/tools/searchWeb/getWebDeepResearchTool.ts @@ -18,22 +18,20 @@ const getWebDeepResearchTool = (model: string = "sonar-deep-research") => { z.object({ role: z.string(), content: z.string(), - }) + }), ), }), execute: async function* ({ messages }) { if (!Array.isArray(messages)) { - throw new Error( - "Invalid arguments for web_deep_research: 'messages' must be an array" - ); + throw new Error("Invalid arguments for web_deep_research: 'messages' must be an array"); } const query = messages[messages.length - 1]?.content || "research query"; // Initial searching status yield { - status: 'searching' as const, - message: 'Conducting deep research...', + status: "searching" as const, + message: "Conducting deep research...", query, } satisfies SearchProgress; @@ -45,16 +43,16 @@ const getWebDeepResearchTool = (model: string = "sonar-deep-research") => { // Manually iterate to capture both yielded values and return value let hasYieldedSources = false; - + while (true) { const { value, done } = await stream.next(); - + if (done) { // value here is the return value (StreamedResponse) finalMetadata = value; break; } - + // value here is a yielded content chunk accumulatedContent += value; chunkCount++; @@ -62,8 +60,8 @@ const getWebDeepResearchTool = (model: string = "sonar-deep-research") => { // Yield streaming progress every CHUNK_BATCH_SIZE chunks if (chunkCount % CHUNK_BATCH_SIZE === 0) { yield { - status: 'streaming' as const, - message: 'Analyzing sources...', + status: "streaming" as const, + message: "Analyzing sources...", query, accumulatedContent, } satisfies SearchProgress; @@ -73,12 +71,12 @@ const getWebDeepResearchTool = (model: string = "sonar-deep-research") => { // Extract metadata from final result const searchResults = finalMetadata?.searchResults || []; const finalCitations = finalMetadata?.citations || []; - + // If we got search results, yield a reviewing status if (searchResults.length > 0 && !hasYieldedSources) { hasYieldedSources = true; yield { - status: 'reviewing' as const, + status: "reviewing" as const, message: `Reviewing sources · ${searchResults.length}`, query, searchResults, @@ -93,8 +91,8 @@ const getWebDeepResearchTool = (model: string = "sonar-deep-research") => { // Yield final streaming update if we didn't just yield one if (chunkCount % CHUNK_BATCH_SIZE !== 0) { yield { - status: 'streaming' as const, - message: 'Analyzing sources...', + status: "streaming" as const, + message: "Analyzing sources...", query, accumulatedContent, } satisfies SearchProgress; @@ -111,8 +109,8 @@ const getWebDeepResearchTool = (model: string = "sonar-deep-research") => { // Yield complete status yield { - status: 'complete' as const, - message: 'Research complete', + status: "complete" as const, + message: "Research complete", query, accumulatedContent: finalContent, citations: finalCitations, @@ -131,4 +129,3 @@ const getWebDeepResearchTool = (model: string = "sonar-deep-research") => { }; export default getWebDeepResearchTool; - diff --git a/lib/tools/searchWeb/types.ts b/lib/tools/searchWeb/types.ts index 135269590..e094066c0 100644 --- a/lib/tools/searchWeb/types.ts +++ b/lib/tools/searchWeb/types.ts @@ -1,8 +1,4 @@ -export type SearchProgressStatus = - | "searching" - | "reviewing" - | "streaming" - | "complete"; +export type SearchProgressStatus = "searching" | "reviewing" | "streaming" | "complete"; export type SearchProgress = { status: SearchProgressStatus; diff --git a/lib/twilio/client.ts b/lib/twilio/client.ts index 956e2750a..c24e52625 100644 --- a/lib/twilio/client.ts +++ b/lib/twilio/client.ts @@ -4,14 +4,10 @@ const TWILIO_ACCOUNT_SID = process.env.TWILIO_ACCOUNT_SID as string; const TWILIO_AUTH_TOKEN = process.env.TWILIO_AUTH_TOKEN as string; if (!TWILIO_ACCOUNT_SID || !TWILIO_AUTH_TOKEN) { - console.warn( - "Twilio credentials not configured. SMS functionality will be disabled." - ); + console.warn("Twilio credentials not configured. SMS functionality will be disabled."); } const twilioClient = - TWILIO_ACCOUNT_SID && TWILIO_AUTH_TOKEN - ? twilio(TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN) - : null; + TWILIO_ACCOUNT_SID && TWILIO_AUTH_TOKEN ? twilio(TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN) : null; export default twilioClient; diff --git a/lib/twilio/createSmsResponse.ts b/lib/twilio/createSmsResponse.ts index ec501d9b6..4e118d73f 100644 --- a/lib/twilio/createSmsResponse.ts +++ b/lib/twilio/createSmsResponse.ts @@ -4,6 +4,7 @@ const MessagingResponse = twilio.twiml.MessagingResponse; /** * Creates a TwiML messaging response + * * @param message - Message to send back to the user * @returns TwiML string response */ diff --git a/lib/twilio/parseSmsWebhook.ts b/lib/twilio/parseSmsWebhook.ts index aa001011f..44e48a6dd 100644 --- a/lib/twilio/parseSmsWebhook.ts +++ b/lib/twilio/parseSmsWebhook.ts @@ -2,6 +2,7 @@ import { ParsedSmsMessage } from "@/types/twilio"; /** * Parses Twilio SMS webhook FormData into a structured object + * * @param formData - FormData from Twilio webhook request * @returns Parsed SMS message data */ diff --git a/lib/twilio/processAndReply.ts b/lib/twilio/processAndReply.ts index d2eb4e59f..a08b074f6 100644 --- a/lib/twilio/processAndReply.ts +++ b/lib/twilio/processAndReply.ts @@ -8,9 +8,7 @@ import { SMS_FALLBACK_MESSAGE } from "./constants"; * * @param smsData - Parsed SMS message data */ -export const processAndReply = async ( - smsData: ParsedSmsMessage -): Promise => { +export const processAndReply = async (smsData: ParsedSmsMessage): Promise => { try { // Send fallback message await sendSmsMessage(smsData.from, SMS_FALLBACK_MESSAGE); diff --git a/lib/twilio/sendSmsMessage.ts b/lib/twilio/sendSmsMessage.ts index d3a7e6101..bdb7ef52e 100644 --- a/lib/twilio/sendSmsMessage.ts +++ b/lib/twilio/sendSmsMessage.ts @@ -8,10 +8,7 @@ import twilioClient from "./client"; * @param body - Message content * @returns Message SID or null on error */ -export const sendSmsMessage = async ( - to: string, - body: string -): Promise => { +export const sendSmsMessage = async (to: string, body: string): Promise => { try { const fromNumber = process.env.TWILIO_PHONE_NUMBER; diff --git a/lib/txtGeneration.ts b/lib/txtGeneration.ts index 5d9e963c2..3462b2989 100644 --- a/lib/txtGeneration.ts +++ b/lib/txtGeneration.ts @@ -15,9 +15,11 @@ export interface GeneratedTxtResponse { transactionHash: string | null; } -export async function generateAndStoreTxtFile( - contents: string -): Promise { +/** + * + * @param contents + */ +export async function generateAndStoreTxtFile(contents: string): Promise { if (!contents) { throw new Error("Contents are required"); } diff --git a/lib/utils.ts b/lib/utils.ts index 365058ceb..a849c335f 100644 --- a/lib/utils.ts +++ b/lib/utils.ts @@ -1,6 +1,10 @@ import { type ClassValue, clsx } from "clsx"; import { twMerge } from "tailwind-merge"; +/** + * + * @param {...any} inputs + */ export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)); } diff --git a/lib/utils/base64Polyfill.ts b/lib/utils/base64Polyfill.ts index 09f9b2ee3..c8ddcaf2c 100644 --- a/lib/utils/base64Polyfill.ts +++ b/lib/utils/base64Polyfill.ts @@ -6,7 +6,7 @@ export function ensureBase64Polyfills(): void { if (typeof globalThis.atob === "undefined") { globalThis.atob = (data: string) => Buffer.from(data, "base64").toString("binary"); } - + if (typeof globalThis.btoa === "undefined") { globalThis.btoa = (data: string) => Buffer.from(data, "binary").toString("base64"); } diff --git a/lib/utils/download-mermaid-chart.ts b/lib/utils/download-mermaid-chart.ts index d4ddc191f..09654c182 100644 --- a/lib/utils/download-mermaid-chart.ts +++ b/lib/utils/download-mermaid-chart.ts @@ -1,59 +1,61 @@ import { RefObject } from "react"; const handleDownload = ({ containerRef }: { containerRef: RefObject }) => { - if (!containerRef.current) return; - - const svgElement = containerRef.current.querySelector('svg'); - if (!svgElement) { - console.error('Could not find SVG element to download.'); - return; - } - - // Serialize the SVG - const serializer = new XMLSerializer(); - let svgString = serializer.serializeToString(svgElement); - - // Add XML declaration and potentially namespace if missing - if (!svgString.startsWith('\n' + svgString; - } - if (!svgString.includes('xmlns="http://www.w3.org/2000/svg"')) { - svgString = svgString.replace(' void }): void { +export function configureFalClient(fal: { + config: (options: { credentials?: string }) => void; +}): void { fal.config({ credentials: getFalCredentials(), }); diff --git a/lib/utils/falErrorHandler.ts b/lib/utils/falErrorHandler.ts index 2bdd8c9f0..4cd5c16ea 100644 --- a/lib/utils/falErrorHandler.ts +++ b/lib/utils/falErrorHandler.ts @@ -1,22 +1,24 @@ /** * Maps Fal API errors to user-friendly error messages * Centralizes error handling logic for consistent messaging across all Fal tools + * + * @param originalError */ export function mapFalError(originalError: string): string { const errorMessage = originalError.toLowerCase(); - + if (errorMessage.includes("api key") || errorMessage.includes("credentials")) { return "Fal AI API key is missing or invalid. Please check your FAL_KEY environment variable."; } - + if (errorMessage.includes("content policy")) { return "Your prompt may violate content policy. Please try a different prompt."; } - + if (errorMessage.includes("rate limit")) { return "Rate limit exceeded. Please try again later."; } - + // Return original error if no specific mapping found return originalError; } diff --git a/lib/utils/formatBytes.ts b/lib/utils/formatBytes.ts index 9868ad23c..07a87fcc3 100644 --- a/lib/utils/formatBytes.ts +++ b/lib/utils/formatBytes.ts @@ -1,5 +1,7 @@ /** * Format bytes to human-readable string (KB, MB, GB, etc.) + * + * @param bytes */ export function formatBytes(bytes: number | null | undefined): string { if (bytes === null || bytes === undefined) return "Unknown"; @@ -8,7 +10,6 @@ export function formatBytes(bytes: number | null | undefined): string { const k = 1024; const sizes = ["Bytes", "KB", "MB", "GB", "TB"]; const i = Math.floor(Math.log(bytes) / Math.log(k)); - + return `${parseFloat((bytes / Math.pow(k, i)).toFixed(2))} ${sizes[i]}`; } - diff --git a/lib/utils/formatFollowerCount.ts b/lib/utils/formatFollowerCount.ts index 5567c3ab3..1fa4c5a2b 100644 --- a/lib/utils/formatFollowerCount.ts +++ b/lib/utils/formatFollowerCount.ts @@ -2,7 +2,7 @@ * Formats a follower count number to a human-readable string * Converts large numbers to K or M format (e.g. 1.2K, 3.5M) * - * @param count Number to format or null + * @param count - Number to format or null * @returns Formatted string representation */ const formatFollowerCount = (count: number | null): string => { @@ -12,4 +12,4 @@ const formatFollowerCount = (count: number | null): string => { return count.toString(); }; -export default formatFollowerCount; \ No newline at end of file +export default formatFollowerCount; diff --git a/lib/utils/formatScheduledActionDate.ts b/lib/utils/formatScheduledActionDate.ts index 70fd2ac22..1b09b4d3b 100644 --- a/lib/utils/formatScheduledActionDate.ts +++ b/lib/utils/formatScheduledActionDate.ts @@ -1,7 +1,7 @@ /** * Format date for scheduled actions with time (e.g. "Jan 15, 2:30 PM") - * - * @param dateString ISO date string or null + * + * @param dateString - ISO date string or null * @returns Formatted date string or "Never" if null */ export const formatScheduledActionDate = (dateString: string | null): string => { @@ -16,4 +16,4 @@ export const formatScheduledActionDate = (dateString: string | null): string => } catch { return "Invalid date"; } -}; \ No newline at end of file +}; diff --git a/lib/utils/formatTimestamp.ts b/lib/utils/formatTimestamp.ts index bd953fd0c..d3e1eab0f 100644 --- a/lib/utils/formatTimestamp.ts +++ b/lib/utils/formatTimestamp.ts @@ -4,8 +4,8 @@ import { formatDistanceToNow, format } from "date-fns"; * Formats a timestamp to a human-readable string * Returns relative time (e.g. "2 hours ago") and optionally the full date * - * @param timestamp ISO date string to format - * @param short If true, returns only the relative time without the full date + * @param timestamp - ISO date string to format + * @param short - If true, returns only the relative time without the full date * @returns Formatted timestamp string */ const formatTimestamp = (timestamp: string, short = false): string => { @@ -19,4 +19,4 @@ const formatTimestamp = (timestamp: string, short = false): string => { } }; -export default formatTimestamp; \ No newline at end of file +export default formatTimestamp; diff --git a/lib/utils/getToolsInfo.ts b/lib/utils/getToolsInfo.ts index e67925b95..5f75ea153 100644 --- a/lib/utils/getToolsInfo.ts +++ b/lib/utils/getToolsInfo.ts @@ -1,3 +1,7 @@ +/** + * + * @param toolName + */ function getToolInfo(toolName: string): { message: string } { // Spotify related tools if (toolName.includes("spotify")) { @@ -24,10 +28,7 @@ function getToolInfo(toolName: string): { message: string } { }; } // Social media content tools - else if ( - toolName === "get_social_posts" || - toolName === "get_post_comments" - ) { + else if (toolName === "get_social_posts" || toolName === "get_post_comments") { return { message: "Social content analyzed", }; diff --git a/lib/utils/isValidEmail.ts b/lib/utils/isValidEmail.ts index 355c928ea..0ba04c68e 100644 --- a/lib/utils/isValidEmail.ts +++ b/lib/utils/isValidEmail.ts @@ -1,5 +1,6 @@ /** * Validates if a string is a valid email address + * * @param email - The email string to validate * @returns true if the email is valid, false otherwise */ diff --git a/lib/utils/normalizeProfileUrl.ts b/lib/utils/normalizeProfileUrl.ts index 25f4d946d..5bd446ea4 100644 --- a/lib/utils/normalizeProfileUrl.ts +++ b/lib/utils/normalizeProfileUrl.ts @@ -1,5 +1,9 @@ // Normalizes a profile URL to match DB and trigger logic // Removes protocol, leading www., trailing slashes, and lowercases +/** + * + * @param url + */ export default function normalizeProfileUrl(url: string): string { if (!url) return ""; let result = url.trim(); diff --git a/lib/workspace/isWorkspaceAccount.ts b/lib/workspace/isWorkspaceAccount.ts index 9aec592a9..aaa77f377 100644 --- a/lib/workspace/isWorkspaceAccount.ts +++ b/lib/workspace/isWorkspaceAccount.ts @@ -2,6 +2,8 @@ import selectAccountWorkspaceIds from "@/lib/supabase/account_workspace_ids/sele /** * Check if an account is a workspace + * + * @param accountId */ export async function isWorkspaceAccount(accountId: string): Promise { const row = await selectAccountWorkspaceIds(accountId); @@ -9,4 +11,3 @@ export async function isWorkspaceAccount(accountId: string): Promise { } export default isWorkspaceAccount; - diff --git a/lib/youtube/authenticated-channel-monetization.ts b/lib/youtube/authenticated-channel-monetization.ts index 51550dbdf..bfb5b02e9 100644 --- a/lib/youtube/authenticated-channel-monetization.ts +++ b/lib/youtube/authenticated-channel-monetization.ts @@ -1,8 +1,5 @@ import { YouTubeTokensRow, MonetizationCheckResult } from "@/types/youtube"; -import { - YouTubeErrorBuilder, - YouTubeErrorMessages, -} from "@/lib/youtube/error-builder"; +import { YouTubeErrorBuilder, YouTubeErrorMessages } from "@/lib/youtube/error-builder"; import { checkChannelMonetizationById } from "./channel-monetization-by-id"; import { createYouTubeAPIClient } from "./oauth-client"; @@ -14,26 +11,20 @@ import { createYouTubeAPIClient } from "./oauth-client"; * @returns Promise with monetization status or error details */ export async function checkAuthenticatedChannelMonetization( - tokens: YouTubeTokensRow + tokens: YouTubeTokensRow, ): Promise { try { - const youtube = createYouTubeAPIClient( - tokens.access_token, - tokens.refresh_token ?? undefined - ); + const youtube = createYouTubeAPIClient(tokens.access_token, tokens.refresh_token ?? undefined); const channelResponse = await youtube.channels.list({ part: ["id"], mine: true, }); - if ( - !channelResponse.data.items || - channelResponse.data.items.length === 0 - ) { + if (!channelResponse.data.items || channelResponse.data.items.length === 0) { return YouTubeErrorBuilder.createUtilityError( "CHANNEL_NOT_FOUND", - YouTubeErrorMessages.NO_CHANNELS + YouTubeErrorMessages.NO_CHANNELS, ); } @@ -41,20 +32,17 @@ export async function checkAuthenticatedChannelMonetization( if (!channelId) { return YouTubeErrorBuilder.createUtilityError( "CHANNEL_NOT_FOUND", - YouTubeErrorMessages.NO_CHANNELS + YouTubeErrorMessages.NO_CHANNELS, ); } // Now check monetization for this channel return await checkChannelMonetizationById(tokens, channelId); } catch (error: unknown) { - console.error( - "Error fetching user's channel for monetization check:", - error - ); + console.error("Error fetching user's channel for monetization check:", error); return YouTubeErrorBuilder.createUtilityError( "API_ERROR", - error instanceof Error ? error.message : YouTubeErrorMessages.API_ERROR + error instanceof Error ? error.message : YouTubeErrorMessages.API_ERROR, ); } } diff --git a/lib/youtube/channel-fetcher.ts b/lib/youtube/channel-fetcher.ts index 8567e7134..5300c5062 100644 --- a/lib/youtube/channel-fetcher.ts +++ b/lib/youtube/channel-fetcher.ts @@ -1,13 +1,14 @@ import { createYouTubeAPIClient } from "@/lib/youtube/oauth-client"; import { YouTubeChannelData } from "@/types/youtube"; -import { - YouTubeErrorBuilder, - YouTubeErrorMessages, -} from "@/lib/youtube/error-builder"; +import { YouTubeErrorBuilder, YouTubeErrorMessages } from "@/lib/youtube/error-builder"; /** * Fetches YouTube channel information using authenticated tokens + * * @param params - { accessToken, refreshToken, includeBranding } + * @param params.accessToken + * @param params.refreshToken + * @param params.includeBranding * @returns Promise with array of channel data or error details */ export async function fetchYouTubeChannelInfo({ @@ -41,7 +42,7 @@ export async function fetchYouTubeChannelInfo({ if (!response.data.items || response.data.items.length === 0) { return YouTubeErrorBuilder.createUtilityError( "NO_CHANNELS", - YouTubeErrorMessages.NO_CHANNELS + YouTubeErrorMessages.NO_CHANNELS, ); } @@ -67,8 +68,7 @@ export async function fetchYouTubeChannelInfo({ subscriberCount: channelData.statistics?.subscriberCount || "0", videoCount: channelData.statistics?.videoCount || "0", viewCount: channelData.statistics?.viewCount || "0", - hiddenSubscriberCount: - channelData.statistics?.hiddenSubscriberCount === true, + hiddenSubscriberCount: channelData.statistics?.hiddenSubscriberCount === true, }, customUrl: channelData.snippet?.customUrl || null, country: channelData.snippet?.country || null, @@ -76,8 +76,7 @@ export async function fetchYouTubeChannelInfo({ ...(includeBranding && { branding: { keywords: channelData.brandingSettings?.channel?.keywords || null, - defaultLanguage: - channelData.brandingSettings?.channel?.defaultLanguage || null, + defaultLanguage: channelData.brandingSettings?.channel?.defaultLanguage || null, }, }), })); @@ -90,21 +89,13 @@ export async function fetchYouTubeChannelInfo({ console.error("Error fetching YouTube channel info:", error); // If token is invalid/expired, return appropriate error - if ( - error && - typeof error === "object" && - "code" in error && - error.code === 401 - ) { - return YouTubeErrorBuilder.createUtilityError( - "API_ERROR", - YouTubeErrorMessages.AUTH_FAILED - ); + if (error && typeof error === "object" && "code" in error && error.code === 401) { + return YouTubeErrorBuilder.createUtilityError("API_ERROR", YouTubeErrorMessages.AUTH_FAILED); } return YouTubeErrorBuilder.createUtilityError( "API_ERROR", - error instanceof Error ? error.message : YouTubeErrorMessages.API_ERROR + error instanceof Error ? error.message : YouTubeErrorMessages.API_ERROR, ); } } diff --git a/lib/youtube/channel-monetization-by-id.ts b/lib/youtube/channel-monetization-by-id.ts index dd68e37a6..ee53ead24 100644 --- a/lib/youtube/channel-monetization-by-id.ts +++ b/lib/youtube/channel-monetization-by-id.ts @@ -5,89 +5,98 @@ import { YouTubeErrorBuilder, YouTubeErrorMessages } from "@/lib/youtube/error-b /** * Checks if a YouTube channel is monetized by attempting to fetch revenue data * Uses YouTube Analytics API to query estimatedRevenue for yesterday - * + * * Required OAuth scopes: * - https://www.googleapis.com/auth/yt-analytics.readonly * - https://www.googleapis.com/auth/yt-analytics-monetary.readonly - * + * * @param tokens - Valid YouTube tokens with Analytics scope * @param channelId - YouTube channel ID to check * @returns Promise with monetization status or error details */ export async function checkChannelMonetizationById( - tokens: YouTubeTokensRow, - channelId: string - ): Promise { - try { - // Create YouTube Analytics API client - const ytAnalytics = createYouTubeAnalyticsClient( - tokens.access_token, - tokens.refresh_token ?? undefined - ); - - // Use yesterday's date for the query (YYYY-MM-DD format) - const yesterday = new Date(); - yesterday.setDate(yesterday.getDate() - 1); - const isoDate = yesterday.toISOString().split("T")[0]; - - // Query estimated revenue for yesterday - const response = await ytAnalytics.reports.query({ - ids: `channel==${channelId}`, - startDate: isoDate, - endDate: isoDate, - metrics: "estimatedRevenue", - }); - - // Interpret the response - const rows = response.data.rows || []; - - if (rows.length === 0) { - // No data returned - channel is not monetized - return { - success: true, - isMonetized: false - }; - } - - // Check if we got valid revenue data (even if $0) - const revenueValue = rows[0]?.[0]; - if (revenueValue !== undefined && revenueValue !== null) { - // Valid numeric response indicates monetization is enabled - return { - success: true, - isMonetized: true - }; - } - - // Fallback: treat as not monetized + tokens: YouTubeTokensRow, + channelId: string, +): Promise { + try { + // Create YouTube Analytics API client + const ytAnalytics = createYouTubeAnalyticsClient( + tokens.access_token, + tokens.refresh_token ?? undefined, + ); + + // Use yesterday's date for the query (YYYY-MM-DD format) + const yesterday = new Date(); + yesterday.setDate(yesterday.getDate() - 1); + const isoDate = yesterday.toISOString().split("T")[0]; + + // Query estimated revenue for yesterday + const response = await ytAnalytics.reports.query({ + ids: `channel==${channelId}`, + startDate: isoDate, + endDate: isoDate, + metrics: "estimatedRevenue", + }); + + // Interpret the response + const rows = response.data.rows || []; + + if (rows.length === 0) { + // No data returned - channel is not monetized return { success: true, - isMonetized: false + isMonetized: false, }; - - } catch (error: unknown) { - console.error("Error checking channel monetization:", error); - - // Handle specific API errors - if (error && typeof error === 'object' && 'code' in error) { - const apiError = error as { code: number; message?: string }; - - if (apiError.code === 401) { - return YouTubeErrorBuilder.createUtilityError('EXPIRED', YouTubeErrorMessages.EXPIRED_TOKENS); - } - - if (apiError.code === 403) { - // Check if it's a scope issue - const errorMessage = apiError.message || ''; - if (errorMessage.includes('scope') || errorMessage.includes('permission')) { - return YouTubeErrorBuilder.createUtilityError('INSUFFICIENT_SCOPE', YouTubeErrorMessages.INSUFFICIENT_SCOPE); - } - return YouTubeErrorBuilder.createUtilityError('CHANNEL_NOT_FOUND', YouTubeErrorMessages.CHANNEL_NOT_FOUND); + } + + // Check if we got valid revenue data (even if $0) + const revenueValue = rows[0]?.[0]; + if (revenueValue !== undefined && revenueValue !== null) { + // Valid numeric response indicates monetization is enabled + return { + success: true, + isMonetized: true, + }; + } + + // Fallback: treat as not monetized + return { + success: true, + isMonetized: false, + }; + } catch (error: unknown) { + console.error("Error checking channel monetization:", error); + + // Handle specific API errors + if (error && typeof error === "object" && "code" in error) { + const apiError = error as { code: number; message?: string }; + + if (apiError.code === 401) { + return YouTubeErrorBuilder.createUtilityError( + "EXPIRED", + YouTubeErrorMessages.EXPIRED_TOKENS, + ); + } + + if (apiError.code === 403) { + // Check if it's a scope issue + const errorMessage = apiError.message || ""; + if (errorMessage.includes("scope") || errorMessage.includes("permission")) { + return YouTubeErrorBuilder.createUtilityError( + "INSUFFICIENT_SCOPE", + YouTubeErrorMessages.INSUFFICIENT_SCOPE, + ); } + return YouTubeErrorBuilder.createUtilityError( + "CHANNEL_NOT_FOUND", + YouTubeErrorMessages.CHANNEL_NOT_FOUND, + ); } - - return YouTubeErrorBuilder.createUtilityError('ANALYTICS_ERROR', - error instanceof Error ? error.message : YouTubeErrorMessages.ANALYTICS_ERROR - ); } - } \ No newline at end of file + + return YouTubeErrorBuilder.createUtilityError( + "ANALYTICS_ERROR", + error instanceof Error ? error.message : YouTubeErrorMessages.ANALYTICS_ERROR, + ); + } +} diff --git a/lib/youtube/error-builder.ts b/lib/youtube/error-builder.ts index e19ec734f..5fd5ad1fd 100644 --- a/lib/youtube/error-builder.ts +++ b/lib/youtube/error-builder.ts @@ -1,4 +1,12 @@ -import { APIErrorResponse, APISuccessResponse, AuthStatusErrorResponse, AuthStatusSuccessResponse, ToolErrorResponse, ToolSuccessResponse, UtilitySuccessResponse } from "@/types/youtube"; +import { + APIErrorResponse, + APISuccessResponse, + AuthStatusErrorResponse, + AuthStatusSuccessResponse, + ToolErrorResponse, + ToolSuccessResponse, + UtilitySuccessResponse, +} from "@/types/youtube"; import { NextResponse } from "next/server"; /** @@ -8,40 +16,52 @@ import { NextResponse } from "next/server"; export class YouTubeErrorBuilder { /** * Create tool error response (for MCP tools) + * + * @param message */ static createToolError(message: string): ToolErrorResponse { return { success: false, status: "error", - message + message, }; } /** * Create authentication status error response + * + * @param message */ static createAuthStatusError(message: string): AuthStatusErrorResponse { return { authenticated: false, - message + message, }; } /** * Create API route error response (NextResponse) + * + * @param message */ static createAPIError(message: string): NextResponse { return NextResponse.json({ success: false, status: "error", - message + message, }); } /** * Create utility function error response (with error codes) + * + * @param code + * @param message */ - static createUtilityError(code: T, message: string): { + static createUtilityError( + code: T, + message: string, + ): { success: false; error: { code: T; @@ -52,66 +72,78 @@ export class YouTubeErrorBuilder { success: false, error: { code, - message - } + message, + }, }; } /** * Create tool success response + * + * @param message + * @param data */ static createToolSuccess>( - message: string, - data?: T + message: string, + data?: T, ): ToolSuccessResponse & Partial { return { success: true, status: "success", message, - ...(data || {} as T) + ...(data || ({} as T)), }; } /** * Create authentication status success response + * + * @param message + * @param expiresAt + * @param createdAt */ static createAuthStatusSuccess( message: string, expiresAt?: string, - createdAt?: string + createdAt?: string, ): AuthStatusSuccessResponse { return { authenticated: true, message, ...(expiresAt && { expiresAt }), - ...(createdAt && { createdAt }) + ...(createdAt && { createdAt }), }; } /** * Create API route success response (NextResponse) + * + * @param message + * @param data */ static createAPISuccess>( message: string, - data: T + data: T, ): NextResponse { return NextResponse.json({ success: true, status: "success", message, - ...data + ...data, }); } /** * Create utility function success response + * + * @param data */ static createUtilitySuccess>( - data: T + data: T, ): UtilitySuccessResponse & T { return { success: true, - ...data + ...data, }; } } @@ -122,26 +154,30 @@ export const YouTubeErrorMessages = { NO_ACCOUNT_ID: "Account ID is required", NO_TOKENS: "No YouTube tokens found for this account. Please authenticate first.", EXPIRED_TOKENS: "YouTube access token has expired for this account. Please re-authenticate.", - EXPIRED_TOKENS_NO_REFRESH: "YouTube access token has expired, and no refresh token is available. Please re-authenticate.", + EXPIRED_TOKENS_NO_REFRESH: + "YouTube access token has expired, and no refresh token is available. Please re-authenticate.", FETCH_ERROR: "Failed to validate YouTube tokens. Please try again.", DB_UPDATE_FAILED: "Failed to save refreshed YouTube token to the database.", - + // Token Refresh Specific Errors - REFRESH_INCOMPLETE_CREDENTIALS: "Failed to refresh token: incomplete credentials received from Google.", + REFRESH_INCOMPLETE_CREDENTIALS: + "Failed to refresh token: incomplete credentials received from Google.", REFRESH_GENERAL_FAILURE: "Failed to refresh YouTube token. Please try re-authenticating.", - REFRESH_INVALID_GRANT: "YouTube refresh token is invalid or has been revoked. Please re-authenticate your YouTube account.", - + REFRESH_INVALID_GRANT: + "YouTube refresh token is invalid or has been revoked. Please re-authenticate your YouTube account.", + // API specific errors NO_CHANNELS: "No YouTube channels found for this authenticated account", API_ERROR: "Failed to fetch YouTube channel information", AUTH_FAILED: "YouTube authentication failed. Please sign in again.", - + // Monetization specific errors - INSUFFICIENT_SCOPE: "YouTube Analytics access required. Please re-authenticate with Analytics permissions.", + INSUFFICIENT_SCOPE: + "YouTube Analytics access required. Please re-authenticate with Analytics permissions.", ANALYTICS_ERROR: "Failed to fetch YouTube Analytics data. Please try again.", CHANNEL_NOT_FOUND: "Channel not found or not accessible for Analytics.", - + // Generic errors GENERAL_ERROR: "An unexpected error occurred", - STATUS_CHECK_FAILED: "Failed to check YouTube authentication status" -}; \ No newline at end of file + STATUS_CHECK_FAILED: "Failed to check YouTube authentication status", +}; diff --git a/lib/youtube/fetchYouTubeChannel.ts b/lib/youtube/fetchYouTubeChannel.ts index 571774718..deada54f5 100644 --- a/lib/youtube/fetchYouTubeChannel.ts +++ b/lib/youtube/fetchYouTubeChannel.ts @@ -1,7 +1,5 @@ const fetchYouTubeChannel = async (artistAccountId: string) => { - const response = await fetch( - `/api/youtube/channel-info?artist_account_id=${artistAccountId}` - ); + const response = await fetch(`/api/youtube/channel-info?artist_account_id=${artistAccountId}`); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); diff --git a/lib/youtube/formatDuration.ts b/lib/youtube/formatDuration.ts index 780c6e163..a4455d9ff 100644 --- a/lib/youtube/formatDuration.ts +++ b/lib/youtube/formatDuration.ts @@ -1,13 +1,17 @@ +/** + * + * @param duration + */ export function formatDuration(duration: string): string { const match = duration.match(/PT(?:(\d+)H)?(?:(\d+)M)?(?:(\d+)S)?/); if (!match) return "0:00"; - + const hours = match[1] ? parseInt(match[1]) : 0; const minutes = match[2] ? parseInt(match[2]) : 0; const seconds = match[3] ? parseInt(match[3]) : 0; - + if (hours > 0) { return `${hours}:${minutes.toString().padStart(2, "0")}:${seconds.toString().padStart(2, "0")}`; } return `${minutes}:${seconds.toString().padStart(2, "0")}`; -} \ No newline at end of file +} diff --git a/lib/youtube/getResizedImageBuffer.ts b/lib/youtube/getResizedImageBuffer.ts index 6eb3b1ca3..0524b6709 100644 --- a/lib/youtube/getResizedImageBuffer.ts +++ b/lib/youtube/getResizedImageBuffer.ts @@ -1,7 +1,11 @@ import sharp from "sharp"; +/** + * + * @param thumbnail_url + */ export async function getResizedImageBuffer( - thumbnail_url: string + thumbnail_url: string, ): Promise<{ buffer?: Buffer; error?: string }> { try { const res = await fetch(thumbnail_url); @@ -12,11 +16,7 @@ export async function getResizedImageBuffer( } const arrayBuffer = await res.arrayBuffer(); const uint8 = new Uint8Array(arrayBuffer); - let buffer = Buffer.from( - uint8.buffer, - uint8.byteOffset, - uint8.byteLength - ) as Buffer; + let buffer = Buffer.from(uint8.buffer, uint8.byteOffset, uint8.byteLength) as Buffer; const MAX_SIZE = 2_097_152; // 2MB in bytes // If image is too large, resize and compress @@ -37,8 +37,7 @@ export async function getResizedImageBuffer( return { buffer }; } catch (error) { return { - error: - error instanceof Error ? error.message : "Failed to process image.", + error: error instanceof Error ? error.message : "Failed to process image.", }; } } diff --git a/lib/youtube/getYoutubePlaylistVideos.ts b/lib/youtube/getYoutubePlaylistVideos.ts index 63fcaa908..f3a8db2d1 100644 --- a/lib/youtube/getYoutubePlaylistVideos.ts +++ b/lib/youtube/getYoutubePlaylistVideos.ts @@ -4,10 +4,15 @@ import { youtube_v3 } from "googleapis"; /** * Fetches videos from a YouTube playlist using the YouTube API client, * then fetches full video details using the videos.list endpoint. + * + * @param access_token.access_token * @param access_token - OAuth access token * @param refresh_token - OAuth refresh token (optional) * @param playlist_id - The playlist ID * @param max_results - Maximum number of results to return (default 25) + * @param access_token.refresh_token + * @param access_token.playlist_id + * @param access_token.max_results * @returns An object with videos (full details), nextPageToken, totalResults, resultsPerPage */ export async function getYoutubePlaylistVideos({ @@ -32,7 +37,7 @@ export async function getYoutubePlaylistVideos({ const items = playlistResponse.data.items || []; const videoIds = items - .map((item) => item.contentDetails?.videoId) + .map(item => item.contentDetails?.videoId) .filter((id): id is string => Boolean(id)); let videos: youtube_v3.Schema$Video[] = []; diff --git a/lib/youtube/is-token-expired.ts b/lib/youtube/is-token-expired.ts index d0bfa5005..13f0c199a 100644 --- a/lib/youtube/is-token-expired.ts +++ b/lib/youtube/is-token-expired.ts @@ -5,10 +5,7 @@ * @param safetyBufferMs - Safety buffer in milliseconds (default: 60000 = 1 minute) * @returns True if token is expired or about to expire */ -export function isTokenExpired( - expiresAt: string, - safetyBufferMs: number = 60000 -): boolean { +export function isTokenExpired(expiresAt: string, safetyBufferMs: number = 60000): boolean { const now = Date.now(); const expirationTime = new Date(expiresAt).getTime(); return now > expirationTime - safetyBufferMs; diff --git a/lib/youtube/oauth-client.ts b/lib/youtube/oauth-client.ts index c260344e0..561b4f0ae 100644 --- a/lib/youtube/oauth-client.ts +++ b/lib/youtube/oauth-client.ts @@ -2,25 +2,27 @@ import { google } from "googleapis"; /** * Create and configure a YouTube OAuth2 client + * * @returns Configured OAuth2 client */ export function createYouTubeOAuthClient() { return new google.auth.OAuth2( process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID, process.env.GOOGLE_CLIENT_SECRET, - `${process.env.NEXT_PUBLIC_URL}/api/auth/callback/google` + `${process.env.NEXT_PUBLIC_URL}/api/auth/callback/google`, ); } /** * Create a YouTube API client with authentication + * * @param accessToken - YouTube access token * @param refreshToken - YouTube refresh token (optional) * @returns Configured YouTube API client */ export function createYouTubeAPIClient(accessToken: string, refreshToken?: string) { const oauth2Client = createYouTubeOAuthClient(); - + oauth2Client.setCredentials({ access_token: accessToken, refresh_token: refreshToken, @@ -30,4 +32,4 @@ export function createYouTubeAPIClient(accessToken: string, refreshToken?: strin version: "v3", auth: oauth2Client, }); -} \ No newline at end of file +} diff --git a/lib/youtube/queryAnalyticsReports.ts b/lib/youtube/queryAnalyticsReports.ts index d4ab068b4..577ae604b 100644 --- a/lib/youtube/queryAnalyticsReports.ts +++ b/lib/youtube/queryAnalyticsReports.ts @@ -11,7 +11,13 @@ import { AnalyticsReportsResult } from "@/types/youtube"; /** * Query YouTube Analytics reports for specified metrics and date range + * * @param params - { accessToken, refreshToken, startDate, endDate, metrics } + * @param params.accessToken + * @param params.refreshToken + * @param params.startDate + * @param params.endDate + * @param params.metrics * @returns Analytics data with daily breakdown and totals */ export async function queryAnalyticsReports({ @@ -37,14 +43,14 @@ export async function queryAnalyticsReports({ if (!channelResponse.data.items || channelResponse.data.items.length === 0) { throw new Error( - "No YouTube channel found for this account. Please ensure you have a YouTube channel." + "No YouTube channel found for this account. Please ensure you have a YouTube channel.", ); } const channelId = channelResponse.data.items[0].id; if (!channelId) { throw new Error( - "Unable to retrieve channel ID. Please ensure your YouTube account is properly set up." + "Unable to retrieve channel ID. Please ensure your YouTube account is properly set up.", ); } @@ -66,7 +72,7 @@ export async function queryAnalyticsReports({ if (rows.length === 0) { throw new Error( - "No revenue data found. This could mean your channel is not monetized or you don't have the required Analytics scope permissions. Please ensure your channel is eligible for monetization and you've granted Analytics permissions." + "No revenue data found. This could mean your channel is not monetized or you don't have the required Analytics scope permissions. Please ensure your channel is eligible for monetization and you've granted Analytics permissions.", ); } diff --git a/lib/youtube/revenue-error-handler.ts b/lib/youtube/revenue-error-handler.ts index f33755df9..aa8b70539 100644 --- a/lib/youtube/revenue-error-handler.ts +++ b/lib/youtube/revenue-error-handler.ts @@ -5,8 +5,7 @@ import { YouTubeRevenueResult } from "@/types/youtube"; const isApiError = (error: unknown): error is { code: number } => error !== null && typeof error === "object" && "code" in error; -const isForbiddenError = (error: unknown): boolean => - isApiError(error) && error.code === 403; +const isForbiddenError = (error: unknown): boolean => isApiError(error) && error.code === 403; const isUnauthorizedClientError = (error: unknown): boolean => error instanceof Error && error.message.includes("unauthorized_client"); @@ -22,13 +21,13 @@ const getErrorMessage = (error: unknown): string => export const handleRevenueError = (error: unknown): YouTubeRevenueResult => { if (isForbiddenError(error)) { return YouTubeErrorBuilder.createToolError( - "Access denied. Channel may not be monetized or lacks Analytics permissions." + "Access denied. Channel may not be monetized or lacks Analytics permissions.", ); } if (isUnauthorizedClientError(error)) { return YouTubeErrorBuilder.createToolError( - "Unauthorized client. Please re-authenticate your YouTube account." + "Unauthorized client. Please re-authenticate your YouTube account.", ); } diff --git a/lib/youtube/token-refresher.ts b/lib/youtube/token-refresher.ts index ff86b864c..505dfa7c5 100644 --- a/lib/youtube/token-refresher.ts +++ b/lib/youtube/token-refresher.ts @@ -1,6 +1,6 @@ /** * YouTube Token Refresh Utility - * + * * Handles automatic refresh of expired YouTube access tokens using refresh tokens. * Provides a clean, reusable interface for token refresh operations. */ @@ -14,32 +14,35 @@ export interface TokenRefreshResult { success: boolean; tokens?: YouTubeTokensRow; error?: { - code: 'REFRESH_INCOMPLETE_CREDENTIALS' | 'DB_UPDATE_FAILED' | 'REFRESH_INVALID_GRANT' | 'REFRESH_GENERAL_FAILURE'; + code: + | "REFRESH_INCOMPLETE_CREDENTIALS" + | "DB_UPDATE_FAILED" + | "REFRESH_INVALID_GRANT" + | "REFRESH_GENERAL_FAILURE"; message: string; }; } /** * Refreshes an expired YouTube access token using the refresh token - * + * * @param storedTokens - The current stored tokens including refresh_token * @param artist_account_id - Artist account ID for logging purposes * @returns Promise with refresh result containing new tokens or error details */ export async function refreshStoredYouTubeToken( - storedTokens: YouTubeTokensRow, - artist_account_id: string + storedTokens: YouTubeTokensRow, + artist_account_id: string, ): Promise { - if (!storedTokens.refresh_token) { return YouTubeErrorBuilder.createUtilityError( - 'REFRESH_GENERAL_FAILURE', - 'No refresh token available for token refresh' + "REFRESH_GENERAL_FAILURE", + "No refresh token available for token refresh", ); } console.log(`Access token for account ${artist_account_id} expired. Attempting refresh.`); - + try { // Configure OAuth2 client with refresh token const oauth2Client = createYouTubeOAuthClient(); @@ -53,11 +56,11 @@ export async function refreshStoredYouTubeToken( if (!credentials.access_token || !credentials.expiry_date) { console.error("Refresh token response missing access_token or expiry_date", credentials); return YouTubeErrorBuilder.createUtilityError( - 'REFRESH_INCOMPLETE_CREDENTIALS', - YouTubeErrorMessages.REFRESH_INCOMPLETE_CREDENTIALS + "REFRESH_INCOMPLETE_CREDENTIALS", + YouTubeErrorMessages.REFRESH_INCOMPLETE_CREDENTIALS, ); } - + // Prepare updated token data const newExpiresAt = new Date(credentials.expiry_date).toISOString(); const updatedTokensData: YouTubeTokensRow = { @@ -73,35 +76,42 @@ export async function refreshStoredYouTubeToken( if (!updateResult) { console.error(`Failed to update refreshed tokens in DB for account ${artist_account_id}`); return YouTubeErrorBuilder.createUtilityError( - 'DB_UPDATE_FAILED', - YouTubeErrorMessages.DB_UPDATE_FAILED + "DB_UPDATE_FAILED", + YouTubeErrorMessages.DB_UPDATE_FAILED, ); } - + console.log(`Successfully updated tokens in DB for account ${artist_account_id}`); return { success: true, tokens: updateResult, }; - } catch (refreshError: unknown) { console.error(`Error refreshing YouTube token for account ${artist_account_id}:`, refreshError); - + // Handle specific Google OAuth errors - if (refreshError && typeof refreshError === 'object' && 'response' in refreshError && - refreshError.response && typeof refreshError.response === 'object' && 'data' in refreshError.response && - refreshError.response.data && typeof refreshError.response.data === 'object' && 'error' in refreshError.response.data && - refreshError.response.data.error === 'invalid_grant') { + if ( + refreshError && + typeof refreshError === "object" && + "response" in refreshError && + refreshError.response && + typeof refreshError.response === "object" && + "data" in refreshError.response && + refreshError.response.data && + typeof refreshError.response.data === "object" && + "error" in refreshError.response.data && + refreshError.response.data.error === "invalid_grant" + ) { return YouTubeErrorBuilder.createUtilityError( - 'REFRESH_INVALID_GRANT', - YouTubeErrorMessages.REFRESH_INVALID_GRANT + "REFRESH_INVALID_GRANT", + YouTubeErrorMessages.REFRESH_INVALID_GRANT, ); } - + // General refresh failure return YouTubeErrorBuilder.createUtilityError( - 'REFRESH_GENERAL_FAILURE', - YouTubeErrorMessages.REFRESH_GENERAL_FAILURE + "REFRESH_GENERAL_FAILURE", + YouTubeErrorMessages.REFRESH_GENERAL_FAILURE, ); } -} \ No newline at end of file +} diff --git a/lib/youtube/token-validator.ts b/lib/youtube/token-validator.ts index faa52df2a..cd21904d9 100644 --- a/lib/youtube/token-validator.ts +++ b/lib/youtube/token-validator.ts @@ -8,17 +8,19 @@ import { isTokenExpired } from "@/lib/youtube/is-token-expired"; * Validates YouTube tokens for a given account * Checks if tokens exist and haven't expired (with 1-minute safety buffer). * Attempts to refresh the token if it's expired and a refresh token is available. - * + * * @param artist_account_id - The artist account ID to validate tokens for * @returns Promise with validation result including tokens or error details */ -export async function validateYouTubeTokens(artist_account_id: string): Promise { +export async function validateYouTubeTokens( + artist_account_id: string, +): Promise { try { // Get tokens from database const storedTokens = await getYouTubeTokens(artist_account_id); - + if (!storedTokens) { - return YouTubeErrorBuilder.createUtilityError('NO_TOKENS', YouTubeErrorMessages.NO_TOKENS); + return YouTubeErrorBuilder.createUtilityError("NO_TOKENS", YouTubeErrorMessages.NO_TOKENS); } // Check if token has expired or is about to expire @@ -26,10 +28,7 @@ export async function validateYouTubeTokens(artist_account_id: string): Promise< // Token is expired or about to expire, try to refresh if (storedTokens.refresh_token) { // Attempt token refresh using dedicated refresh module - const refreshResult = await refreshStoredYouTubeToken( - storedTokens, - artist_account_id - ); + const refreshResult = await refreshStoredYouTubeToken(storedTokens, artist_account_id); if (refreshResult.success) { return { @@ -44,18 +43,18 @@ export async function validateYouTubeTokens(artist_account_id: string): Promise< // Expired and no refresh token available return YouTubeErrorBuilder.createUtilityError( "EXPIRED_NO_REFRESH", - YouTubeErrorMessages.EXPIRED_TOKENS_NO_REFRESH + YouTubeErrorMessages.EXPIRED_TOKENS_NO_REFRESH, ); - } + } } // Token is valid and not expired return { success: true, - tokens: storedTokens + tokens: storedTokens, }; } catch (error) { - console.error('Error validating YouTube tokens:', error); - return YouTubeErrorBuilder.createUtilityError('FETCH_ERROR', YouTubeErrorMessages.FETCH_ERROR); + console.error("Error validating YouTube tokens:", error); + return YouTubeErrorBuilder.createUtilityError("FETCH_ERROR", YouTubeErrorMessages.FETCH_ERROR); } -} \ No newline at end of file +} diff --git a/lib/youtube/youtube-analytics-oauth-client.ts b/lib/youtube/youtube-analytics-oauth-client.ts index 86c6959f9..1e9f678d2 100644 --- a/lib/youtube/youtube-analytics-oauth-client.ts +++ b/lib/youtube/youtube-analytics-oauth-client.ts @@ -3,14 +3,12 @@ import { google } from "googleapis"; /** * Create a YouTube Analytics API client with authentication + * * @param accessToken - YouTube access token with Analytics scope * @param refreshToken - YouTube refresh token (optional) * @returns Configured YouTube Analytics API client */ -export function createYouTubeAnalyticsClient( - accessToken: string, - refreshToken?: string -) { +export function createYouTubeAnalyticsClient(accessToken: string, refreshToken?: string) { const oauth2Client = createYouTubeOAuthClient(); oauth2Client.setCredentials({ diff --git a/lib/youtube/youtubeLogin.ts b/lib/youtube/youtubeLogin.ts index 371aeba10..c488679f5 100644 --- a/lib/youtube/youtubeLogin.ts +++ b/lib/youtube/youtubeLogin.ts @@ -1,3 +1,7 @@ +/** + * + * @param artist_account_id + */ export function youtubeLogin(artist_account_id?: string) { const clientId = process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID; const redirectUri = `${process.env.NEXT_PUBLIC_URL}/api/auth/callback/google`; diff --git a/tailwind.config.ts b/tailwind.config.ts index f01b738d7..3954d9b35 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -1,16 +1,16 @@ import type { Config } from "tailwindcss"; const config: Config = { - darkMode: "class", - content: [ + darkMode: "class", + content: [ "./pages/**/*.{js,ts,jsx,tsx,mdx}", "./components/**/*.{js,ts,jsx,tsx,mdx}", "./app/**/*.{js,ts,jsx,tsx,mdx}", ], theme: { - extend: { + extend: { fontFamily: { - heading: ['var(--font-heading)'], + heading: ["var(--font-heading)"], }, keyframes: { "success-pulse": { @@ -26,93 +26,93 @@ const config: Config = { "success-pulse": "success-pulse 1.5s ease-in-out", "ping-slow": "ping-slow 1.5s cubic-bezier(0, 0, 0.2, 1) infinite", }, - colors: { - background: 'hsl(var(--background))', - black: { - DEFAULT: '#000000', - light: '#444444' - }, - while: { - DEFAULT: '#ffffff' - }, - grey: { - DEFAULT: '#E6E6E6', - dark: '#71717A', - "dark-1": "#A1A1AA", - primary: '#C6C6C6', - secondary: '#F8F8F8', - light: '#E0E0E0', - "light-1": "#F1F1F1", - "light-2": "#EBEBEB", - "light-3": "#FAFAFA" - }, - green: { - DEFAULT: '#345A5D' - }, - purple: { - DEFAULT: '#BD99B3', - dark: '#720762' - }, - foreground: 'hsl(var(--foreground))', - card: { - DEFAULT: 'hsl(var(--card))', - foreground: 'hsl(var(--card-foreground))' - }, - popover: { - DEFAULT: 'hsl(var(--popover))', - foreground: 'hsl(var(--popover-foreground))' - }, - primary: { - DEFAULT: 'hsl(var(--primary))', - foreground: 'hsl(var(--primary-foreground))' - }, - secondary: { - DEFAULT: 'hsl(var(--secondary))', - foreground: 'hsl(var(--secondary-foreground))' - }, - muted: { - DEFAULT: 'hsl(var(--muted))', - foreground: 'hsl(var(--muted-foreground))' - }, - accent: { - DEFAULT: 'hsl(var(--accent))', - foreground: 'hsl(var(--accent-foreground))' - }, - destructive: { - DEFAULT: 'hsl(var(--destructive))', - foreground: 'hsl(var(--destructive-foreground))' - }, - border: 'hsl(var(--border))', - input: 'hsl(var(--input))', - ring: 'hsl(var(--ring))', - chart: { - '1': 'hsl(var(--chart-1))', - '2': 'hsl(var(--chart-2))', - '3': 'hsl(var(--chart-3))', - '4': 'hsl(var(--chart-4))', - '5': 'hsl(var(--chart-5))' - }, + colors: { + background: "hsl(var(--background))", + black: { + DEFAULT: "#000000", + light: "#444444", + }, + while: { + DEFAULT: "#ffffff", + }, + grey: { + DEFAULT: "#E6E6E6", + dark: "#71717A", + "dark-1": "#A1A1AA", + primary: "#C6C6C6", + secondary: "#F8F8F8", + light: "#E0E0E0", + "light-1": "#F1F1F1", + "light-2": "#EBEBEB", + "light-3": "#FAFAFA", + }, + green: { + DEFAULT: "#345A5D", + }, + purple: { + DEFAULT: "#BD99B3", + dark: "#720762", + }, + foreground: "hsl(var(--foreground))", + card: { + DEFAULT: "hsl(var(--card))", + foreground: "hsl(var(--card-foreground))", + }, + popover: { + DEFAULT: "hsl(var(--popover))", + foreground: "hsl(var(--popover-foreground))", + }, + primary: { + DEFAULT: "hsl(var(--primary))", + foreground: "hsl(var(--primary-foreground))", + }, + secondary: { + DEFAULT: "hsl(var(--secondary))", + foreground: "hsl(var(--secondary-foreground))", + }, + muted: { + DEFAULT: "hsl(var(--muted))", + foreground: "hsl(var(--muted-foreground))", + }, + accent: { + DEFAULT: "hsl(var(--accent))", + foreground: "hsl(var(--accent-foreground))", + }, + destructive: { + DEFAULT: "hsl(var(--destructive))", + foreground: "hsl(var(--destructive-foreground))", + }, + border: "hsl(var(--border))", + input: "hsl(var(--input))", + ring: "hsl(var(--ring))", + chart: { + "1": "hsl(var(--chart-1))", + "2": "hsl(var(--chart-2))", + "3": "hsl(var(--chart-3))", + "4": "hsl(var(--chart-4))", + "5": "hsl(var(--chart-5))", + }, sidebar: { - DEFAULT: 'hsl(var(--sidebar))', - foreground: 'hsl(var(--sidebar-foreground))', - primary: 'hsl(var(--sidebar-primary))', - 'primary-foreground': 'hsl(var(--sidebar-primary-foreground))', - accent: 'hsl(var(--sidebar-accent))', - 'accent-foreground': 'hsl(var(--sidebar-accent-foreground))', - border: 'hsl(var(--sidebar-border))', - ring: 'hsl(var(--sidebar-ring))', - }, - }, - boxShadow: { - grey: '1px 1px 1px 1px #E6E6E6', - 'grey-light': '1px 3px 8px 1px #E6E6E6' - }, - borderRadius: { - lg: 'var(--radius)', - md: 'calc(var(--radius) - 2px)', - sm: 'calc(var(--radius) - 4px)' - } - } + DEFAULT: "hsl(var(--sidebar))", + foreground: "hsl(var(--sidebar-foreground))", + primary: "hsl(var(--sidebar-primary))", + "primary-foreground": "hsl(var(--sidebar-primary-foreground))", + accent: "hsl(var(--sidebar-accent))", + "accent-foreground": "hsl(var(--sidebar-accent-foreground))", + border: "hsl(var(--sidebar-border))", + ring: "hsl(var(--sidebar-ring))", + }, + }, + boxShadow: { + grey: "1px 1px 1px 1px #E6E6E6", + "grey-light": "1px 3px 8px 1px #E6E6E6", + }, + borderRadius: { + lg: "var(--radius)", + md: "calc(var(--radius) - 2px)", + sm: "calc(var(--radius) - 4px)", + }, + }, }, plugins: [require("tailwindcss-animate")], }; diff --git a/types/AgentTemplates.ts b/types/AgentTemplates.ts index bc082a211..82a8b5c24 100644 --- a/types/AgentTemplates.ts +++ b/types/AgentTemplates.ts @@ -4,12 +4,14 @@ export type ToggleFavoriteRequest = { isFavourite: boolean; }; -export type ToggleFavoriteResponse = { - success: true; - favorites_count: number | null; -} | { - error: string; -}; +export type ToggleFavoriteResponse = + | { + success: true; + favorites_count: number | null; + } + | { + error: string; + }; export type AgentTemplateRow = { id: string; @@ -27,5 +29,3 @@ export type AgentTemplateRow = { // emails the template is shared with (only for private templates) shared_emails?: string[]; }; - - diff --git a/types/ArtistSocials.ts b/types/ArtistSocials.ts index 8bbcbbbb5..6832b8fb2 100644 --- a/types/ArtistSocials.ts +++ b/types/ArtistSocials.ts @@ -1,11 +1,14 @@ import { Database } from "./database.types"; -type SocialBase = Omit; +type SocialBase = Omit< + Database["public"]["Tables"]["socials"]["Row"], + "followerCount" | "followingCount" +>; export type Social = SocialBase & { social_id: string; follower_count: number; - following_count: number; + following_count: number; }; export interface ArtistSocialsResultType { @@ -13,9 +16,9 @@ export interface ArtistSocialsResultType { socials: Array; success: boolean; pagination: { - page: number; - limit: number; - total_count: number; - total_pages: number; + page: number; + limit: number; + total_count: number; + total_pages: number; }; -} \ No newline at end of file +} diff --git a/types/database.types.ts b/types/database.types.ts index cd14166ce..d82994302 100644 --- a/types/database.types.ts +++ b/types/database.types.ts @@ -1,3756 +1,3750 @@ -export type Json = - | string - | number - | boolean - | null - | { [key: string]: Json | undefined } - | Json[] +export type Json = string | number | boolean | null | { [key: string]: Json | undefined } | Json[]; export type Database = { // Allows to automatically instantiate createClient with right options // instead of createClient(URL, KEY) __InternalSupabase: { - PostgrestVersion: "12.2.3 (519615d)" - } + PostgrestVersion: "12.2.3 (519615d)"; + }; public: { Tables: { account_api_keys: { Row: { - account: string | null - created_at: string - id: string - key_hash: string | null - last_used: string | null - name: string - } - Insert: { - account?: string | null - created_at?: string - id?: string - key_hash?: string | null - last_used?: string | null - name: string - } - Update: { - account?: string | null - created_at?: string - id?: string - key_hash?: string | null - last_used?: string | null - name?: string - } + account: string | null; + created_at: string; + id: string; + key_hash: string | null; + last_used: string | null; + name: string; + }; + Insert: { + account?: string | null; + created_at?: string; + id?: string; + key_hash?: string | null; + last_used?: string | null; + name: string; + }; + Update: { + account?: string | null; + created_at?: string; + id?: string; + key_hash?: string | null; + last_used?: string | null; + name?: string; + }; Relationships: [ { - foreignKeyName: "account_api_keys_account_fkey" - columns: ["account"] - isOneToOne: false - referencedRelation: "accounts" - referencedColumns: ["id"] + foreignKeyName: "account_api_keys_account_fkey"; + columns: ["account"]; + isOneToOne: false; + referencedRelation: "accounts"; + referencedColumns: ["id"]; }, - ] - } + ]; + }; account_artist_ids: { Row: { - account_id: string | null - artist_id: string | null - id: string - pinned: boolean - updated_at: string | null - } - Insert: { - account_id?: string | null - artist_id?: string | null - id?: string - pinned?: boolean - updated_at?: string | null - } - Update: { - account_id?: string | null - artist_id?: string | null - id?: string - pinned?: boolean - updated_at?: string | null - } + account_id: string | null; + artist_id: string | null; + id: string; + pinned: boolean; + updated_at: string | null; + }; + Insert: { + account_id?: string | null; + artist_id?: string | null; + id?: string; + pinned?: boolean; + updated_at?: string | null; + }; + Update: { + account_id?: string | null; + artist_id?: string | null; + id?: string; + pinned?: boolean; + updated_at?: string | null; + }; Relationships: [ { - foreignKeyName: "account_artist_ids_account_id_fkey" - columns: ["account_id"] - isOneToOne: false - referencedRelation: "accounts" - referencedColumns: ["id"] + foreignKeyName: "account_artist_ids_account_id_fkey"; + columns: ["account_id"]; + isOneToOne: false; + referencedRelation: "accounts"; + referencedColumns: ["id"]; }, { - foreignKeyName: "account_artist_ids_artist_id_fkey" - columns: ["artist_id"] - isOneToOne: false - referencedRelation: "accounts" - referencedColumns: ["id"] + foreignKeyName: "account_artist_ids_artist_id_fkey"; + columns: ["artist_id"]; + isOneToOne: false; + referencedRelation: "accounts"; + referencedColumns: ["id"]; }, - ] - } + ]; + }; account_catalogs: { Row: { - account: string - catalog: string - created_at: string - id: string - updated_at: string - } - Insert: { - account: string - catalog: string - created_at?: string - id?: string - updated_at?: string - } - Update: { - account?: string - catalog?: string - created_at?: string - id?: string - updated_at?: string - } + account: string; + catalog: string; + created_at: string; + id: string; + updated_at: string; + }; + Insert: { + account: string; + catalog: string; + created_at?: string; + id?: string; + updated_at?: string; + }; + Update: { + account?: string; + catalog?: string; + created_at?: string; + id?: string; + updated_at?: string; + }; Relationships: [ { - foreignKeyName: "account_catalogs_account_fkey" - columns: ["account"] - isOneToOne: false - referencedRelation: "accounts" - referencedColumns: ["id"] + foreignKeyName: "account_catalogs_account_fkey"; + columns: ["account"]; + isOneToOne: false; + referencedRelation: "accounts"; + referencedColumns: ["id"]; }, { - foreignKeyName: "account_catalogs_catalog_fkey" - columns: ["catalog"] - isOneToOne: false - referencedRelation: "catalogs" - referencedColumns: ["id"] + foreignKeyName: "account_catalogs_catalog_fkey"; + columns: ["catalog"]; + isOneToOne: false; + referencedRelation: "catalogs"; + referencedColumns: ["id"]; }, - ] - } + ]; + }; account_emails: { Row: { - account_id: string | null - email: string | null - id: string - updated_at: string - } - Insert: { - account_id?: string | null - email?: string | null - id?: string - updated_at?: string - } - Update: { - account_id?: string | null - email?: string | null - id?: string - updated_at?: string - } + account_id: string | null; + email: string | null; + id: string; + updated_at: string; + }; + Insert: { + account_id?: string | null; + email?: string | null; + id?: string; + updated_at?: string; + }; + Update: { + account_id?: string | null; + email?: string | null; + id?: string; + updated_at?: string; + }; Relationships: [ { - foreignKeyName: "account_emails_account_id_fkey" - columns: ["account_id"] - isOneToOne: false - referencedRelation: "accounts" - referencedColumns: ["id"] + foreignKeyName: "account_emails_account_id_fkey"; + columns: ["account_id"]; + isOneToOne: false; + referencedRelation: "accounts"; + referencedColumns: ["id"]; }, - ] - } + ]; + }; account_info: { Row: { - account_id: string | null - company_name: string | null - id: string - image: string | null - instruction: string | null - job_title: string | null - knowledges: Json | null - label: string | null - onboarding_data: Json | null - onboarding_status: Json | null - organization: string | null - role_type: string | null - updated_at: string - } - Insert: { - account_id?: string | null - company_name?: string | null - id?: string - image?: string | null - instruction?: string | null - job_title?: string | null - knowledges?: Json | null - label?: string | null - onboarding_data?: Json | null - onboarding_status?: Json | null - organization?: string | null - role_type?: string | null - updated_at?: string - } - Update: { - account_id?: string | null - company_name?: string | null - id?: string - image?: string | null - instruction?: string | null - job_title?: string | null - knowledges?: Json | null - label?: string | null - onboarding_data?: Json | null - onboarding_status?: Json | null - organization?: string | null - role_type?: string | null - updated_at?: string - } + account_id: string | null; + company_name: string | null; + id: string; + image: string | null; + instruction: string | null; + job_title: string | null; + knowledges: Json | null; + label: string | null; + onboarding_data: Json | null; + onboarding_status: Json | null; + organization: string | null; + role_type: string | null; + updated_at: string; + }; + Insert: { + account_id?: string | null; + company_name?: string | null; + id?: string; + image?: string | null; + instruction?: string | null; + job_title?: string | null; + knowledges?: Json | null; + label?: string | null; + onboarding_data?: Json | null; + onboarding_status?: Json | null; + organization?: string | null; + role_type?: string | null; + updated_at?: string; + }; + Update: { + account_id?: string | null; + company_name?: string | null; + id?: string; + image?: string | null; + instruction?: string | null; + job_title?: string | null; + knowledges?: Json | null; + label?: string | null; + onboarding_data?: Json | null; + onboarding_status?: Json | null; + organization?: string | null; + role_type?: string | null; + updated_at?: string; + }; Relationships: [ { - foreignKeyName: "account_info_account_id_fkey" - columns: ["account_id"] - isOneToOne: false - referencedRelation: "accounts" - referencedColumns: ["id"] + foreignKeyName: "account_info_account_id_fkey"; + columns: ["account_id"]; + isOneToOne: false; + referencedRelation: "accounts"; + referencedColumns: ["id"]; }, - ] - } + ]; + }; account_organization_ids: { Row: { - account_id: string | null - id: string - organization_id: string | null - updated_at: string | null - } - Insert: { - account_id?: string | null - id?: string - organization_id?: string | null - updated_at?: string | null - } - Update: { - account_id?: string | null - id?: string - organization_id?: string | null - updated_at?: string | null - } + account_id: string | null; + id: string; + organization_id: string | null; + updated_at: string | null; + }; + Insert: { + account_id?: string | null; + id?: string; + organization_id?: string | null; + updated_at?: string | null; + }; + Update: { + account_id?: string | null; + id?: string; + organization_id?: string | null; + updated_at?: string | null; + }; Relationships: [ { - foreignKeyName: "account_organization_ids_account_id_fkey" - columns: ["account_id"] - isOneToOne: false - referencedRelation: "accounts" - referencedColumns: ["id"] + foreignKeyName: "account_organization_ids_account_id_fkey"; + columns: ["account_id"]; + isOneToOne: false; + referencedRelation: "accounts"; + referencedColumns: ["id"]; }, { - foreignKeyName: "account_organization_ids_organization_id_fkey" - columns: ["organization_id"] - isOneToOne: false - referencedRelation: "accounts" - referencedColumns: ["id"] + foreignKeyName: "account_organization_ids_organization_id_fkey"; + columns: ["organization_id"]; + isOneToOne: false; + referencedRelation: "accounts"; + referencedColumns: ["id"]; }, - ] - } + ]; + }; account_phone_numbers: { Row: { - account_id: string - id: string - phone_number: string - updated_at: string | null - } - Insert: { - account_id: string - id?: string - phone_number: string - updated_at?: string | null - } - Update: { - account_id?: string - id?: string - phone_number?: string - updated_at?: string | null - } + account_id: string; + id: string; + phone_number: string; + updated_at: string | null; + }; + Insert: { + account_id: string; + id?: string; + phone_number: string; + updated_at?: string | null; + }; + Update: { + account_id?: string; + id?: string; + phone_number?: string; + updated_at?: string | null; + }; Relationships: [ { - foreignKeyName: "account_phone_numbers_account_id_fkey" - columns: ["account_id"] - isOneToOne: false - referencedRelation: "accounts" - referencedColumns: ["id"] + foreignKeyName: "account_phone_numbers_account_id_fkey"; + columns: ["account_id"]; + isOneToOne: false; + referencedRelation: "accounts"; + referencedColumns: ["id"]; }, - ] - } + ]; + }; account_socials: { Row: { - account_id: string | null - id: string - social_id: string - } + account_id: string | null; + id: string; + social_id: string; + }; Insert: { - account_id?: string | null - id?: string - social_id?: string - } + account_id?: string | null; + id?: string; + social_id?: string; + }; Update: { - account_id?: string | null - id?: string - social_id?: string - } + account_id?: string | null; + id?: string; + social_id?: string; + }; Relationships: [ { - foreignKeyName: "account_socials_account_id_fkey" - columns: ["account_id"] - isOneToOne: false - referencedRelation: "accounts" - referencedColumns: ["id"] + foreignKeyName: "account_socials_account_id_fkey"; + columns: ["account_id"]; + isOneToOne: false; + referencedRelation: "accounts"; + referencedColumns: ["id"]; }, { - foreignKeyName: "account_socials_social_id_fkey" - columns: ["social_id"] - isOneToOne: false - referencedRelation: "socials" - referencedColumns: ["id"] + foreignKeyName: "account_socials_social_id_fkey"; + columns: ["social_id"]; + isOneToOne: false; + referencedRelation: "socials"; + referencedColumns: ["id"]; }, - ] - } + ]; + }; account_wallets: { Row: { - account_id: string - id: string - updated_at: string | null - wallet: string - } - Insert: { - account_id: string - id?: string - updated_at?: string | null - wallet: string - } - Update: { - account_id?: string - id?: string - updated_at?: string | null - wallet?: string - } + account_id: string; + id: string; + updated_at: string | null; + wallet: string; + }; + Insert: { + account_id: string; + id?: string; + updated_at?: string | null; + wallet: string; + }; + Update: { + account_id?: string; + id?: string; + updated_at?: string | null; + wallet?: string; + }; Relationships: [ { - foreignKeyName: "account_wallets_account_id_fkey" - columns: ["account_id"] - isOneToOne: false - referencedRelation: "accounts" - referencedColumns: ["id"] + foreignKeyName: "account_wallets_account_id_fkey"; + columns: ["account_id"]; + isOneToOne: false; + referencedRelation: "accounts"; + referencedColumns: ["id"]; }, - ] - } + ]; + }; account_workspace_ids: { Row: { - account_id: string | null - id: string - updated_at: string | null - workspace_id: string | null - } - Insert: { - account_id?: string | null - id?: string - updated_at?: string | null - workspace_id?: string | null - } - Update: { - account_id?: string | null - id?: string - updated_at?: string | null - workspace_id?: string | null - } + account_id: string | null; + id: string; + updated_at: string | null; + workspace_id: string | null; + }; + Insert: { + account_id?: string | null; + id?: string; + updated_at?: string | null; + workspace_id?: string | null; + }; + Update: { + account_id?: string | null; + id?: string; + updated_at?: string | null; + workspace_id?: string | null; + }; Relationships: [ { - foreignKeyName: "account_workspace_ids_account_id_fkey" - columns: ["account_id"] - isOneToOne: false - referencedRelation: "accounts" - referencedColumns: ["id"] + foreignKeyName: "account_workspace_ids_account_id_fkey"; + columns: ["account_id"]; + isOneToOne: false; + referencedRelation: "accounts"; + referencedColumns: ["id"]; }, { - foreignKeyName: "account_workspace_ids_workspace_id_fkey" - columns: ["workspace_id"] - isOneToOne: false - referencedRelation: "accounts" - referencedColumns: ["id"] + foreignKeyName: "account_workspace_ids_workspace_id_fkey"; + columns: ["workspace_id"]; + isOneToOne: false; + referencedRelation: "accounts"; + referencedColumns: ["id"]; }, - ] - } + ]; + }; accounts: { Row: { - id: string - name: string | null - timestamp: number | null - } - Insert: { - id?: string - name?: string | null - timestamp?: number | null - } - Update: { - id?: string - name?: string | null - timestamp?: number | null - } - Relationships: [] - } + id: string; + name: string | null; + timestamp: number | null; + }; + Insert: { + id?: string; + name?: string | null; + timestamp?: number | null; + }; + Update: { + id?: string; + name?: string | null; + timestamp?: number | null; + }; + Relationships: []; + }; accounts_memberships: { Row: { - account_id: string - account_role: string - created_at: string - created_by: string | null - updated_at: string - updated_by: string | null - user_id: string - } - Insert: { - account_id: string - account_role: string - created_at?: string - created_by?: string | null - updated_at?: string - updated_by?: string | null - user_id: string - } - Update: { - account_id?: string - account_role?: string - created_at?: string - created_by?: string | null - updated_at?: string - updated_by?: string | null - user_id?: string - } + account_id: string; + account_role: string; + created_at: string; + created_by: string | null; + updated_at: string; + updated_by: string | null; + user_id: string; + }; + Insert: { + account_id: string; + account_role: string; + created_at?: string; + created_by?: string | null; + updated_at?: string; + updated_by?: string | null; + user_id: string; + }; + Update: { + account_id?: string; + account_role?: string; + created_at?: string; + created_by?: string | null; + updated_at?: string; + updated_by?: string | null; + user_id?: string; + }; Relationships: [ { - foreignKeyName: "accounts_memberships_account_role_fkey" - columns: ["account_role"] - isOneToOne: false - referencedRelation: "roles" - referencedColumns: ["name"] + foreignKeyName: "accounts_memberships_account_role_fkey"; + columns: ["account_role"]; + isOneToOne: false; + referencedRelation: "roles"; + referencedColumns: ["name"]; }, - ] - } + ]; + }; admin_expenses: { Row: { - amount: number - category: string - created_at: string | null - created_by: string | null - id: string - is_active: boolean | null - item_name: string - updated_at: string | null - } - Insert: { - amount?: number - category: string - created_at?: string | null - created_by?: string | null - id?: string - is_active?: boolean | null - item_name: string - updated_at?: string | null - } - Update: { - amount?: number - category?: string - created_at?: string | null - created_by?: string | null - id?: string - is_active?: boolean | null - item_name?: string - updated_at?: string | null - } - Relationships: [] - } + amount: number; + category: string; + created_at: string | null; + created_by: string | null; + id: string; + is_active: boolean | null; + item_name: string; + updated_at: string | null; + }; + Insert: { + amount?: number; + category: string; + created_at?: string | null; + created_by?: string | null; + id?: string; + is_active?: boolean | null; + item_name: string; + updated_at?: string | null; + }; + Update: { + amount?: number; + category?: string; + created_at?: string | null; + created_by?: string | null; + id?: string; + is_active?: boolean | null; + item_name?: string; + updated_at?: string | null; + }; + Relationships: []; + }; admin_user_profiles: { Row: { - company: string | null - context_notes: string | null - created_at: string | null - email: string - id: string - job_title: string | null - last_contact_date: string | null - meeting_notes: string | null - observations: string | null - opportunities: string | null - pain_points: string | null - sentiment: string | null - tags: string[] | null - updated_at: string | null - } - Insert: { - company?: string | null - context_notes?: string | null - created_at?: string | null - email: string - id?: string - job_title?: string | null - last_contact_date?: string | null - meeting_notes?: string | null - observations?: string | null - opportunities?: string | null - pain_points?: string | null - sentiment?: string | null - tags?: string[] | null - updated_at?: string | null - } - Update: { - company?: string | null - context_notes?: string | null - created_at?: string | null - email?: string - id?: string - job_title?: string | null - last_contact_date?: string | null - meeting_notes?: string | null - observations?: string | null - opportunities?: string | null - pain_points?: string | null - sentiment?: string | null - tags?: string[] | null - updated_at?: string | null - } - Relationships: [] - } + company: string | null; + context_notes: string | null; + created_at: string | null; + email: string; + id: string; + job_title: string | null; + last_contact_date: string | null; + meeting_notes: string | null; + observations: string | null; + opportunities: string | null; + pain_points: string | null; + sentiment: string | null; + tags: string[] | null; + updated_at: string | null; + }; + Insert: { + company?: string | null; + context_notes?: string | null; + created_at?: string | null; + email: string; + id?: string; + job_title?: string | null; + last_contact_date?: string | null; + meeting_notes?: string | null; + observations?: string | null; + opportunities?: string | null; + pain_points?: string | null; + sentiment?: string | null; + tags?: string[] | null; + updated_at?: string | null; + }; + Update: { + company?: string | null; + context_notes?: string | null; + created_at?: string | null; + email?: string; + id?: string; + job_title?: string | null; + last_contact_date?: string | null; + meeting_notes?: string | null; + observations?: string | null; + opportunities?: string | null; + pain_points?: string | null; + sentiment?: string | null; + tags?: string[] | null; + updated_at?: string | null; + }; + Relationships: []; + }; agent_status: { Row: { - agent_id: string - id: string - progress: number | null - social_id: string - status: number | null - updated_at: string - } - Insert: { - agent_id?: string - id?: string - progress?: number | null - social_id: string - status?: number | null - updated_at?: string - } - Update: { - agent_id?: string - id?: string - progress?: number | null - social_id?: string - status?: number | null - updated_at?: string - } + agent_id: string; + id: string; + progress: number | null; + social_id: string; + status: number | null; + updated_at: string; + }; + Insert: { + agent_id?: string; + id?: string; + progress?: number | null; + social_id: string; + status?: number | null; + updated_at?: string; + }; + Update: { + agent_id?: string; + id?: string; + progress?: number | null; + social_id?: string; + status?: number | null; + updated_at?: string; + }; Relationships: [ { - foreignKeyName: "agent_status_agent_id_fkey" - columns: ["agent_id"] - isOneToOne: false - referencedRelation: "agents" - referencedColumns: ["id"] + foreignKeyName: "agent_status_agent_id_fkey"; + columns: ["agent_id"]; + isOneToOne: false; + referencedRelation: "agents"; + referencedColumns: ["id"]; }, { - foreignKeyName: "agent_status_social_id_fkey" - columns: ["social_id"] - isOneToOne: false - referencedRelation: "socials" - referencedColumns: ["id"] + foreignKeyName: "agent_status_social_id_fkey"; + columns: ["social_id"]; + isOneToOne: false; + referencedRelation: "socials"; + referencedColumns: ["id"]; }, - ] - } + ]; + }; agent_template_favorites: { Row: { - created_at: string | null - template_id: string - user_id: string - } + created_at: string | null; + template_id: string; + user_id: string; + }; Insert: { - created_at?: string | null - template_id: string - user_id: string - } + created_at?: string | null; + template_id: string; + user_id: string; + }; Update: { - created_at?: string | null - template_id?: string - user_id?: string - } + created_at?: string | null; + template_id?: string; + user_id?: string; + }; Relationships: [ { - foreignKeyName: "agent_template_favorites_template_id_fkey" - columns: ["template_id"] - isOneToOne: false - referencedRelation: "agent_templates" - referencedColumns: ["id"] + foreignKeyName: "agent_template_favorites_template_id_fkey"; + columns: ["template_id"]; + isOneToOne: false; + referencedRelation: "agent_templates"; + referencedColumns: ["id"]; }, { - foreignKeyName: "agent_template_favorites_user_id_fkey" - columns: ["user_id"] - isOneToOne: false - referencedRelation: "accounts" - referencedColumns: ["id"] + foreignKeyName: "agent_template_favorites_user_id_fkey"; + columns: ["user_id"]; + isOneToOne: false; + referencedRelation: "accounts"; + referencedColumns: ["id"]; }, - ] - } + ]; + }; agent_template_shares: { Row: { - created_at: string | null - template_id: string - user_id: string - } + created_at: string | null; + template_id: string; + user_id: string; + }; Insert: { - created_at?: string | null - template_id: string - user_id: string - } + created_at?: string | null; + template_id: string; + user_id: string; + }; Update: { - created_at?: string | null - template_id?: string - user_id?: string - } + created_at?: string | null; + template_id?: string; + user_id?: string; + }; Relationships: [ { - foreignKeyName: "agent_template_shares_template_id_fkey" - columns: ["template_id"] - isOneToOne: false - referencedRelation: "agent_templates" - referencedColumns: ["id"] + foreignKeyName: "agent_template_shares_template_id_fkey"; + columns: ["template_id"]; + isOneToOne: false; + referencedRelation: "agent_templates"; + referencedColumns: ["id"]; }, { - foreignKeyName: "agent_template_shares_user_id_fkey" - columns: ["user_id"] - isOneToOne: false - referencedRelation: "accounts" - referencedColumns: ["id"] + foreignKeyName: "agent_template_shares_user_id_fkey"; + columns: ["user_id"]; + isOneToOne: false; + referencedRelation: "accounts"; + referencedColumns: ["id"]; }, - ] - } + ]; + }; agent_templates: { Row: { - created_at: string - creator: string | null - description: string - favorites_count: number - id: string - is_private: boolean - prompt: string - tags: string[] - title: string - updated_at: string | null - } - Insert: { - created_at?: string - creator?: string | null - description: string - favorites_count?: number - id?: string - is_private?: boolean - prompt: string - tags?: string[] - title: string - updated_at?: string | null - } - Update: { - created_at?: string - creator?: string | null - description?: string - favorites_count?: number - id?: string - is_private?: boolean - prompt?: string - tags?: string[] - title?: string - updated_at?: string | null - } + created_at: string; + creator: string | null; + description: string; + favorites_count: number; + id: string; + is_private: boolean; + prompt: string; + tags: string[]; + title: string; + updated_at: string | null; + }; + Insert: { + created_at?: string; + creator?: string | null; + description: string; + favorites_count?: number; + id?: string; + is_private?: boolean; + prompt: string; + tags?: string[]; + title: string; + updated_at?: string | null; + }; + Update: { + created_at?: string; + creator?: string | null; + description?: string; + favorites_count?: number; + id?: string; + is_private?: boolean; + prompt?: string; + tags?: string[]; + title?: string; + updated_at?: string | null; + }; Relationships: [ { - foreignKeyName: "agent_templates_creator_fkey" - columns: ["creator"] - isOneToOne: false - referencedRelation: "accounts" - referencedColumns: ["id"] + foreignKeyName: "agent_templates_creator_fkey"; + columns: ["creator"]; + isOneToOne: false; + referencedRelation: "accounts"; + referencedColumns: ["id"]; }, - ] - } + ]; + }; agents: { Row: { - id: string - updated_at: string - } + id: string; + updated_at: string; + }; Insert: { - id?: string - updated_at?: string - } + id?: string; + updated_at?: string; + }; Update: { - id?: string - updated_at?: string - } - Relationships: [] - } + id?: string; + updated_at?: string; + }; + Relationships: []; + }; app_store_link_clicked: { Row: { - clientId: string | null - id: string | null - timestamp: number | null - } - Insert: { - clientId?: string | null - id?: string | null - timestamp?: number | null - } - Update: { - clientId?: string | null - id?: string | null - timestamp?: number | null - } - Relationships: [] - } + clientId: string | null; + id: string | null; + timestamp: number | null; + }; + Insert: { + clientId?: string | null; + id?: string | null; + timestamp?: number | null; + }; + Update: { + clientId?: string | null; + id?: string | null; + timestamp?: number | null; + }; + Relationships: []; + }; apple_login_button_clicked: { Row: { - campaignId: string | null - clientId: string | null - fanId: string | null - game: string | null - id: string | null - timestamp: number | null - } - Insert: { - campaignId?: string | null - clientId?: string | null - fanId?: string | null - game?: string | null - id?: string | null - timestamp?: number | null - } - Update: { - campaignId?: string | null - clientId?: string | null - fanId?: string | null - game?: string | null - id?: string | null - timestamp?: number | null - } + campaignId: string | null; + clientId: string | null; + fanId: string | null; + game: string | null; + id: string | null; + timestamp: number | null; + }; + Insert: { + campaignId?: string | null; + clientId?: string | null; + fanId?: string | null; + game?: string | null; + id?: string | null; + timestamp?: number | null; + }; + Update: { + campaignId?: string | null; + clientId?: string | null; + fanId?: string | null; + game?: string | null; + id?: string | null; + timestamp?: number | null; + }; Relationships: [ { - foreignKeyName: "apple_login_button_clicked_campaignId_fkey" - columns: ["campaignId"] - isOneToOne: false - referencedRelation: "campaigns" - referencedColumns: ["id"] + foreignKeyName: "apple_login_button_clicked_campaignId_fkey"; + columns: ["campaignId"]; + isOneToOne: false; + referencedRelation: "campaigns"; + referencedColumns: ["id"]; }, - ] - } + ]; + }; apple_music: { Row: { - fanId: string | null - game: string | null - id: string | null - syncid: string | null - syncId: string | null - timestamp: number | null - } - Insert: { - fanId?: string | null - game?: string | null - id?: string | null - syncid?: string | null - syncId?: string | null - timestamp?: number | null - } - Update: { - fanId?: string | null - game?: string | null - id?: string | null - syncid?: string | null - syncId?: string | null - timestamp?: number | null - } - Relationships: [] - } + fanId: string | null; + game: string | null; + id: string | null; + syncid: string | null; + syncId: string | null; + timestamp: number | null; + }; + Insert: { + fanId?: string | null; + game?: string | null; + id?: string | null; + syncid?: string | null; + syncId?: string | null; + timestamp?: number | null; + }; + Update: { + fanId?: string | null; + game?: string | null; + id?: string | null; + syncid?: string | null; + syncId?: string | null; + timestamp?: number | null; + }; + Relationships: []; + }; apple_play_button_clicked: { Row: { - appleId: string | null - campaignId: string | null - clientId: string | null - fanId: string | null - game: string | null - id: string - timestamp: number | null - } - Insert: { - appleId?: string | null - campaignId?: string | null - clientId?: string | null - fanId?: string | null - game?: string | null - id?: string - timestamp?: number | null - } - Update: { - appleId?: string | null - campaignId?: string | null - clientId?: string | null - fanId?: string | null - game?: string | null - id?: string - timestamp?: number | null - } + appleId: string | null; + campaignId: string | null; + clientId: string | null; + fanId: string | null; + game: string | null; + id: string; + timestamp: number | null; + }; + Insert: { + appleId?: string | null; + campaignId?: string | null; + clientId?: string | null; + fanId?: string | null; + game?: string | null; + id?: string; + timestamp?: number | null; + }; + Update: { + appleId?: string | null; + campaignId?: string | null; + clientId?: string | null; + fanId?: string | null; + game?: string | null; + id?: string; + timestamp?: number | null; + }; Relationships: [ { - foreignKeyName: "apple_play_button_clicked_campaignId_fkey" - columns: ["campaignId"] - isOneToOne: false - referencedRelation: "campaigns" - referencedColumns: ["id"] + foreignKeyName: "apple_play_button_clicked_campaignId_fkey"; + columns: ["campaignId"]; + isOneToOne: false; + referencedRelation: "campaigns"; + referencedColumns: ["id"]; }, - ] - } + ]; + }; artist_fan_segment: { Row: { - artist_social_id: string | null - fan_social_id: string | null - id: string - segment_name: string | null - updated_at: string - } - Insert: { - artist_social_id?: string | null - fan_social_id?: string | null - id?: string - segment_name?: string | null - updated_at?: string - } - Update: { - artist_social_id?: string | null - fan_social_id?: string | null - id?: string - segment_name?: string | null - updated_at?: string - } + artist_social_id: string | null; + fan_social_id: string | null; + id: string; + segment_name: string | null; + updated_at: string; + }; + Insert: { + artist_social_id?: string | null; + fan_social_id?: string | null; + id?: string; + segment_name?: string | null; + updated_at?: string; + }; + Update: { + artist_social_id?: string | null; + fan_social_id?: string | null; + id?: string; + segment_name?: string | null; + updated_at?: string; + }; Relationships: [ { - foreignKeyName: "artist_fan_segment_artist_social_id_fkey" - columns: ["artist_social_id"] - isOneToOne: false - referencedRelation: "socials" - referencedColumns: ["id"] + foreignKeyName: "artist_fan_segment_artist_social_id_fkey"; + columns: ["artist_social_id"]; + isOneToOne: false; + referencedRelation: "socials"; + referencedColumns: ["id"]; }, { - foreignKeyName: "artist_fan_segment_fan_social_id_fkey" - columns: ["fan_social_id"] - isOneToOne: false - referencedRelation: "socials" - referencedColumns: ["id"] + foreignKeyName: "artist_fan_segment_fan_social_id_fkey"; + columns: ["fan_social_id"]; + isOneToOne: false; + referencedRelation: "socials"; + referencedColumns: ["id"]; }, - ] - } + ]; + }; artist_organization_ids: { Row: { - artist_id: string - created_at: string | null - id: string - organization_id: string - updated_at: string | null - } - Insert: { - artist_id: string - created_at?: string | null - id?: string - organization_id: string - updated_at?: string | null - } - Update: { - artist_id?: string - created_at?: string | null - id?: string - organization_id?: string - updated_at?: string | null - } + artist_id: string; + created_at: string | null; + id: string; + organization_id: string; + updated_at: string | null; + }; + Insert: { + artist_id: string; + created_at?: string | null; + id?: string; + organization_id: string; + updated_at?: string | null; + }; + Update: { + artist_id?: string; + created_at?: string | null; + id?: string; + organization_id?: string; + updated_at?: string | null; + }; Relationships: [ { - foreignKeyName: "artist_organization_ids_artist_id_fkey" - columns: ["artist_id"] - isOneToOne: false - referencedRelation: "accounts" - referencedColumns: ["id"] + foreignKeyName: "artist_organization_ids_artist_id_fkey"; + columns: ["artist_id"]; + isOneToOne: false; + referencedRelation: "accounts"; + referencedColumns: ["id"]; }, { - foreignKeyName: "artist_organization_ids_organization_id_fkey" - columns: ["organization_id"] - isOneToOne: false - referencedRelation: "accounts" - referencedColumns: ["id"] + foreignKeyName: "artist_organization_ids_organization_id_fkey"; + columns: ["organization_id"]; + isOneToOne: false; + referencedRelation: "accounts"; + referencedColumns: ["id"]; }, - ] - } + ]; + }; artist_segments: { Row: { - artist_account_id: string - id: string - segment_id: string - updated_at: string | null - } - Insert: { - artist_account_id: string - id?: string - segment_id: string - updated_at?: string | null - } - Update: { - artist_account_id?: string - id?: string - segment_id?: string - updated_at?: string | null - } + artist_account_id: string; + id: string; + segment_id: string; + updated_at: string | null; + }; + Insert: { + artist_account_id: string; + id?: string; + segment_id: string; + updated_at?: string | null; + }; + Update: { + artist_account_id?: string; + id?: string; + segment_id?: string; + updated_at?: string | null; + }; Relationships: [ { - foreignKeyName: "artist_segments_artist_account_id_fkey" - columns: ["artist_account_id"] - isOneToOne: false - referencedRelation: "accounts" - referencedColumns: ["id"] + foreignKeyName: "artist_segments_artist_account_id_fkey"; + columns: ["artist_account_id"]; + isOneToOne: false; + referencedRelation: "accounts"; + referencedColumns: ["id"]; }, { - foreignKeyName: "artist_segments_segment_id_fkey" - columns: ["segment_id"] - isOneToOne: false - referencedRelation: "segments" - referencedColumns: ["id"] + foreignKeyName: "artist_segments_segment_id_fkey"; + columns: ["segment_id"]; + isOneToOne: false; + referencedRelation: "segments"; + referencedColumns: ["id"]; }, - ] - } + ]; + }; billing_customers: { Row: { - account_id: string - customer_id: string - email: string | null - id: number - provider: Database["public"]["Enums"]["billing_provider"] - } - Insert: { - account_id: string - customer_id: string - email?: string | null - id?: number - provider: Database["public"]["Enums"]["billing_provider"] - } - Update: { - account_id?: string - customer_id?: string - email?: string | null - id?: number - provider?: Database["public"]["Enums"]["billing_provider"] - } - Relationships: [] - } + account_id: string; + customer_id: string; + email: string | null; + id: number; + provider: Database["public"]["Enums"]["billing_provider"]; + }; + Insert: { + account_id: string; + customer_id: string; + email?: string | null; + id?: number; + provider: Database["public"]["Enums"]["billing_provider"]; + }; + Update: { + account_id?: string; + customer_id?: string; + email?: string | null; + id?: number; + provider?: Database["public"]["Enums"]["billing_provider"]; + }; + Relationships: []; + }; campaigns: { Row: { - artist_id: string | null - clientId: string | null - id: string - timestamp: number | null - } - Insert: { - artist_id?: string | null - clientId?: string | null - id?: string - timestamp?: number | null - } - Update: { - artist_id?: string | null - clientId?: string | null - id?: string - timestamp?: number | null - } + artist_id: string | null; + clientId: string | null; + id: string; + timestamp: number | null; + }; + Insert: { + artist_id?: string | null; + clientId?: string | null; + id?: string; + timestamp?: number | null; + }; + Update: { + artist_id?: string | null; + clientId?: string | null; + id?: string; + timestamp?: number | null; + }; Relationships: [ { - foreignKeyName: "campaigns_artist_id_fkey" - columns: ["artist_id"] - isOneToOne: false - referencedRelation: "accounts" - referencedColumns: ["id"] + foreignKeyName: "campaigns_artist_id_fkey"; + columns: ["artist_id"]; + isOneToOne: false; + referencedRelation: "accounts"; + referencedColumns: ["id"]; }, - ] - } + ]; + }; catalog_songs: { Row: { - catalog: string - created_at: string - id: string - song: string - updated_at: string - } - Insert: { - catalog: string - created_at?: string - id?: string - song: string - updated_at?: string - } - Update: { - catalog?: string - created_at?: string - id?: string - song?: string - updated_at?: string - } + catalog: string; + created_at: string; + id: string; + song: string; + updated_at: string; + }; + Insert: { + catalog: string; + created_at?: string; + id?: string; + song: string; + updated_at?: string; + }; + Update: { + catalog?: string; + created_at?: string; + id?: string; + song?: string; + updated_at?: string; + }; Relationships: [ { - foreignKeyName: "catalog_songs_catalog_fkey" - columns: ["catalog"] - isOneToOne: false - referencedRelation: "catalogs" - referencedColumns: ["id"] + foreignKeyName: "catalog_songs_catalog_fkey"; + columns: ["catalog"]; + isOneToOne: false; + referencedRelation: "catalogs"; + referencedColumns: ["id"]; }, { - foreignKeyName: "catalog_songs_song_fkey" - columns: ["song"] - isOneToOne: false - referencedRelation: "songs" - referencedColumns: ["isrc"] + foreignKeyName: "catalog_songs_song_fkey"; + columns: ["song"]; + isOneToOne: false; + referencedRelation: "songs"; + referencedColumns: ["isrc"]; }, - ] - } + ]; + }; catalogs: { Row: { - created_at: string - id: string - name: string - updated_at: string - } - Insert: { - created_at?: string - id?: string - name: string - updated_at?: string - } - Update: { - created_at?: string - id?: string - name?: string - updated_at?: string - } - Relationships: [] - } + created_at: string; + id: string; + name: string; + updated_at: string; + }; + Insert: { + created_at?: string; + id?: string; + name: string; + updated_at?: string; + }; + Update: { + created_at?: string; + id?: string; + name?: string; + updated_at?: string; + }; + Relationships: []; + }; config: { Row: { - billing_provider: Database["public"]["Enums"]["billing_provider"] - enable_account_billing: boolean - enable_team_account_billing: boolean - enable_team_accounts: boolean - } - Insert: { - billing_provider?: Database["public"]["Enums"]["billing_provider"] - enable_account_billing?: boolean - enable_team_account_billing?: boolean - enable_team_accounts?: boolean - } - Update: { - billing_provider?: Database["public"]["Enums"]["billing_provider"] - enable_account_billing?: boolean - enable_team_account_billing?: boolean - enable_team_accounts?: boolean - } - Relationships: [] - } + billing_provider: Database["public"]["Enums"]["billing_provider"]; + enable_account_billing: boolean; + enable_team_account_billing: boolean; + enable_team_accounts: boolean; + }; + Insert: { + billing_provider?: Database["public"]["Enums"]["billing_provider"]; + enable_account_billing?: boolean; + enable_team_account_billing?: boolean; + enable_team_accounts?: boolean; + }; + Update: { + billing_provider?: Database["public"]["Enums"]["billing_provider"]; + enable_account_billing?: boolean; + enable_team_account_billing?: boolean; + enable_team_accounts?: boolean; + }; + Relationships: []; + }; cookie_players: { Row: { - game: string | null - id: string | null - timestamp: number | null - uniquePlayerID: string | null - } - Insert: { - game?: string | null - id?: string | null - timestamp?: number | null - uniquePlayerID?: string | null - } - Update: { - game?: string | null - id?: string | null - timestamp?: number | null - uniquePlayerID?: string | null - } - Relationships: [] - } + game: string | null; + id: string | null; + timestamp: number | null; + uniquePlayerID: string | null; + }; + Insert: { + game?: string | null; + id?: string | null; + timestamp?: number | null; + uniquePlayerID?: string | null; + }; + Update: { + game?: string | null; + id?: string | null; + timestamp?: number | null; + uniquePlayerID?: string | null; + }; + Relationships: []; + }; credits_usage: { Row: { - account_id: string - id: number - remaining_credits: number - timestamp: string | null - } - Insert: { - account_id: string - id?: number - remaining_credits?: number - timestamp?: string | null - } - Update: { - account_id?: string - id?: number - remaining_credits?: number - timestamp?: string | null - } + account_id: string; + id: number; + remaining_credits: number; + timestamp: string | null; + }; + Insert: { + account_id: string; + id?: number; + remaining_credits?: number; + timestamp?: string | null; + }; + Update: { + account_id?: string; + id?: number; + remaining_credits?: number; + timestamp?: string | null; + }; Relationships: [ { - foreignKeyName: "credits_usage_account_id_fkey" - columns: ["account_id"] - isOneToOne: false - referencedRelation: "accounts" - referencedColumns: ["id"] + foreignKeyName: "credits_usage_account_id_fkey"; + columns: ["account_id"]; + isOneToOne: false; + referencedRelation: "accounts"; + referencedColumns: ["id"]; }, - ] - } + ]; + }; cta_redirect: { Row: { - clientId: string - id: number - timestamp: string | null - url: string | null - } - Insert: { - clientId: string - id?: number - timestamp?: string | null - url?: string | null - } - Update: { - clientId?: string - id?: number - timestamp?: string | null - url?: string | null - } - Relationships: [] - } + clientId: string; + id: number; + timestamp: string | null; + url: string | null; + }; + Insert: { + clientId: string; + id?: number; + timestamp?: string | null; + url?: string | null; + }; + Update: { + clientId?: string; + id?: number; + timestamp?: string | null; + url?: string | null; + }; + Relationships: []; + }; error_logs: { Row: { - account_id: string | null - created_at: string - error_message: string | null - error_timestamp: string | null - error_type: string | null - id: string - last_message: string | null - raw_message: string - room_id: string | null - stack_trace: string | null - telegram_message_id: number | null - tool_name: string | null - } - Insert: { - account_id?: string | null - created_at?: string - error_message?: string | null - error_timestamp?: string | null - error_type?: string | null - id?: string - last_message?: string | null - raw_message: string - room_id?: string | null - stack_trace?: string | null - telegram_message_id?: number | null - tool_name?: string | null - } - Update: { - account_id?: string | null - created_at?: string - error_message?: string | null - error_timestamp?: string | null - error_type?: string | null - id?: string - last_message?: string | null - raw_message?: string - room_id?: string | null - stack_trace?: string | null - telegram_message_id?: number | null - tool_name?: string | null - } + account_id: string | null; + created_at: string; + error_message: string | null; + error_timestamp: string | null; + error_type: string | null; + id: string; + last_message: string | null; + raw_message: string; + room_id: string | null; + stack_trace: string | null; + telegram_message_id: number | null; + tool_name: string | null; + }; + Insert: { + account_id?: string | null; + created_at?: string; + error_message?: string | null; + error_timestamp?: string | null; + error_type?: string | null; + id?: string; + last_message?: string | null; + raw_message: string; + room_id?: string | null; + stack_trace?: string | null; + telegram_message_id?: number | null; + tool_name?: string | null; + }; + Update: { + account_id?: string | null; + created_at?: string; + error_message?: string | null; + error_timestamp?: string | null; + error_type?: string | null; + id?: string; + last_message?: string | null; + raw_message?: string; + room_id?: string | null; + stack_trace?: string | null; + telegram_message_id?: number | null; + tool_name?: string | null; + }; Relationships: [ { - foreignKeyName: "error_logs_account_id_fkey" - columns: ["account_id"] - isOneToOne: false - referencedRelation: "accounts" - referencedColumns: ["id"] + foreignKeyName: "error_logs_account_id_fkey"; + columns: ["account_id"]; + isOneToOne: false; + referencedRelation: "accounts"; + referencedColumns: ["id"]; }, { - foreignKeyName: "error_logs_room_id_fkey" - columns: ["room_id"] - isOneToOne: false - referencedRelation: "rooms" - referencedColumns: ["id"] + foreignKeyName: "error_logs_room_id_fkey"; + columns: ["room_id"]; + isOneToOne: false; + referencedRelation: "rooms"; + referencedColumns: ["id"]; }, - ] - } + ]; + }; fan_segments: { Row: { - fan_social_id: string - id: string - segment_id: string - updated_at: string | null - } - Insert: { - fan_social_id: string - id?: string - segment_id: string - updated_at?: string | null - } - Update: { - fan_social_id?: string - id?: string - segment_id?: string - updated_at?: string | null - } + fan_social_id: string; + id: string; + segment_id: string; + updated_at: string | null; + }; + Insert: { + fan_social_id: string; + id?: string; + segment_id: string; + updated_at?: string | null; + }; + Update: { + fan_social_id?: string; + id?: string; + segment_id?: string; + updated_at?: string | null; + }; Relationships: [ { - foreignKeyName: "fan_segments_fan_social_id_fkey" - columns: ["fan_social_id"] - isOneToOne: false - referencedRelation: "socials" - referencedColumns: ["id"] + foreignKeyName: "fan_segments_fan_social_id_fkey"; + columns: ["fan_social_id"]; + isOneToOne: false; + referencedRelation: "socials"; + referencedColumns: ["id"]; }, { - foreignKeyName: "fan_segments_segment_id_fkey" - columns: ["segment_id"] - isOneToOne: false - referencedRelation: "segments" - referencedColumns: ["id"] + foreignKeyName: "fan_segments_segment_id_fkey"; + columns: ["segment_id"]; + isOneToOne: false; + referencedRelation: "segments"; + referencedColumns: ["id"]; }, - ] - } + ]; + }; fans: { Row: { - account_status: string | null - apple_token: string | null - campaign_id: string | null - campaign_interaction_count: number | null - campaignId: string | null - city: string | null - click_through_rate: number | null - clientId: string | null - consent_given: boolean | null - country: string | null - custom_tags: Json | null - discord_username: string | null - display_name: string | null - email: string | null - email_open_rate: number | null - engagement_level: string | null - episodes: Json | null - explicit_content_filter_enabled: boolean | null - explicit_content_filter_locked: boolean | null - "explicit_content.filter_enabled": boolean | null - "explicit_content.filter_locked": boolean | null - external_urls_spotify: string | null - "external_urls.spotify": string | null - facebook_profile_url: string | null - first_stream_date: string | null - followedArtists: Json | null - followers_total: number | null - "followers.href": string | null - "followers.total": number | null - gamification_points: number | null - genres: Json | null - heavyRotations: Json | null - href: string | null - id: string - images: Json | null - instagram_handle: string | null - last_campaign_interaction: string | null - last_login: string | null - last_purchase_date: string | null - last_stream_date: string | null - linkedin_profile_url: string | null - os_type: string | null - playlist: Json | null - preferences: Json | null - preferred_artists: Json | null - preferred_device: string | null - product: string | null - recentlyPlayed: Json | null - recommendations: Json | null - recommended_events: Json | null - reddit_username: string | null - saved_podcasts: Json | null - savedAlbums: Json | null - savedAudioBooks: Json | null - savedShows: Json | null - savedTracks: Json | null - social_shares: number | null - spotify_token: string | null - subscription_tier: string | null - testField: string | null - tiktok_handle: string | null - time_zone: string | null - timestamp: string | null - top_artists_long_term: Json | null - top_artists_medium_term: Json | null - top_tracks_long_term: Json | null - top_tracks_medium_term: Json | null - top_tracks_short_term: Json | null - topArtists: Json | null - topTracks: Json | null - total_spent: number | null - total_streams: number | null - twitter_handle: string | null - type: string | null - uri: string | null - youtube_channel_url: string | null - } - Insert: { - account_status?: string | null - apple_token?: string | null - campaign_id?: string | null - campaign_interaction_count?: number | null - campaignId?: string | null - city?: string | null - click_through_rate?: number | null - clientId?: string | null - consent_given?: boolean | null - country?: string | null - custom_tags?: Json | null - discord_username?: string | null - display_name?: string | null - email?: string | null - email_open_rate?: number | null - engagement_level?: string | null - episodes?: Json | null - explicit_content_filter_enabled?: boolean | null - explicit_content_filter_locked?: boolean | null - "explicit_content.filter_enabled"?: boolean | null - "explicit_content.filter_locked"?: boolean | null - external_urls_spotify?: string | null - "external_urls.spotify"?: string | null - facebook_profile_url?: string | null - first_stream_date?: string | null - followedArtists?: Json | null - followers_total?: number | null - "followers.href"?: string | null - "followers.total"?: number | null - gamification_points?: number | null - genres?: Json | null - heavyRotations?: Json | null - href?: string | null - id?: string - images?: Json | null - instagram_handle?: string | null - last_campaign_interaction?: string | null - last_login?: string | null - last_purchase_date?: string | null - last_stream_date?: string | null - linkedin_profile_url?: string | null - os_type?: string | null - playlist?: Json | null - preferences?: Json | null - preferred_artists?: Json | null - preferred_device?: string | null - product?: string | null - recentlyPlayed?: Json | null - recommendations?: Json | null - recommended_events?: Json | null - reddit_username?: string | null - saved_podcasts?: Json | null - savedAlbums?: Json | null - savedAudioBooks?: Json | null - savedShows?: Json | null - savedTracks?: Json | null - social_shares?: number | null - spotify_token?: string | null - subscription_tier?: string | null - testField?: string | null - tiktok_handle?: string | null - time_zone?: string | null - timestamp?: string | null - top_artists_long_term?: Json | null - top_artists_medium_term?: Json | null - top_tracks_long_term?: Json | null - top_tracks_medium_term?: Json | null - top_tracks_short_term?: Json | null - topArtists?: Json | null - topTracks?: Json | null - total_spent?: number | null - total_streams?: number | null - twitter_handle?: string | null - type?: string | null - uri?: string | null - youtube_channel_url?: string | null - } - Update: { - account_status?: string | null - apple_token?: string | null - campaign_id?: string | null - campaign_interaction_count?: number | null - campaignId?: string | null - city?: string | null - click_through_rate?: number | null - clientId?: string | null - consent_given?: boolean | null - country?: string | null - custom_tags?: Json | null - discord_username?: string | null - display_name?: string | null - email?: string | null - email_open_rate?: number | null - engagement_level?: string | null - episodes?: Json | null - explicit_content_filter_enabled?: boolean | null - explicit_content_filter_locked?: boolean | null - "explicit_content.filter_enabled"?: boolean | null - "explicit_content.filter_locked"?: boolean | null - external_urls_spotify?: string | null - "external_urls.spotify"?: string | null - facebook_profile_url?: string | null - first_stream_date?: string | null - followedArtists?: Json | null - followers_total?: number | null - "followers.href"?: string | null - "followers.total"?: number | null - gamification_points?: number | null - genres?: Json | null - heavyRotations?: Json | null - href?: string | null - id?: string - images?: Json | null - instagram_handle?: string | null - last_campaign_interaction?: string | null - last_login?: string | null - last_purchase_date?: string | null - last_stream_date?: string | null - linkedin_profile_url?: string | null - os_type?: string | null - playlist?: Json | null - preferences?: Json | null - preferred_artists?: Json | null - preferred_device?: string | null - product?: string | null - recentlyPlayed?: Json | null - recommendations?: Json | null - recommended_events?: Json | null - reddit_username?: string | null - saved_podcasts?: Json | null - savedAlbums?: Json | null - savedAudioBooks?: Json | null - savedShows?: Json | null - savedTracks?: Json | null - social_shares?: number | null - spotify_token?: string | null - subscription_tier?: string | null - testField?: string | null - tiktok_handle?: string | null - time_zone?: string | null - timestamp?: string | null - top_artists_long_term?: Json | null - top_artists_medium_term?: Json | null - top_tracks_long_term?: Json | null - top_tracks_medium_term?: Json | null - top_tracks_short_term?: Json | null - topArtists?: Json | null - topTracks?: Json | null - total_spent?: number | null - total_streams?: number | null - twitter_handle?: string | null - type?: string | null - uri?: string | null - youtube_channel_url?: string | null - } + account_status: string | null; + apple_token: string | null; + campaign_id: string | null; + campaign_interaction_count: number | null; + campaignId: string | null; + city: string | null; + click_through_rate: number | null; + clientId: string | null; + consent_given: boolean | null; + country: string | null; + custom_tags: Json | null; + discord_username: string | null; + display_name: string | null; + email: string | null; + email_open_rate: number | null; + engagement_level: string | null; + episodes: Json | null; + explicit_content_filter_enabled: boolean | null; + explicit_content_filter_locked: boolean | null; + "explicit_content.filter_enabled": boolean | null; + "explicit_content.filter_locked": boolean | null; + external_urls_spotify: string | null; + "external_urls.spotify": string | null; + facebook_profile_url: string | null; + first_stream_date: string | null; + followedArtists: Json | null; + followers_total: number | null; + "followers.href": string | null; + "followers.total": number | null; + gamification_points: number | null; + genres: Json | null; + heavyRotations: Json | null; + href: string | null; + id: string; + images: Json | null; + instagram_handle: string | null; + last_campaign_interaction: string | null; + last_login: string | null; + last_purchase_date: string | null; + last_stream_date: string | null; + linkedin_profile_url: string | null; + os_type: string | null; + playlist: Json | null; + preferences: Json | null; + preferred_artists: Json | null; + preferred_device: string | null; + product: string | null; + recentlyPlayed: Json | null; + recommendations: Json | null; + recommended_events: Json | null; + reddit_username: string | null; + saved_podcasts: Json | null; + savedAlbums: Json | null; + savedAudioBooks: Json | null; + savedShows: Json | null; + savedTracks: Json | null; + social_shares: number | null; + spotify_token: string | null; + subscription_tier: string | null; + testField: string | null; + tiktok_handle: string | null; + time_zone: string | null; + timestamp: string | null; + top_artists_long_term: Json | null; + top_artists_medium_term: Json | null; + top_tracks_long_term: Json | null; + top_tracks_medium_term: Json | null; + top_tracks_short_term: Json | null; + topArtists: Json | null; + topTracks: Json | null; + total_spent: number | null; + total_streams: number | null; + twitter_handle: string | null; + type: string | null; + uri: string | null; + youtube_channel_url: string | null; + }; + Insert: { + account_status?: string | null; + apple_token?: string | null; + campaign_id?: string | null; + campaign_interaction_count?: number | null; + campaignId?: string | null; + city?: string | null; + click_through_rate?: number | null; + clientId?: string | null; + consent_given?: boolean | null; + country?: string | null; + custom_tags?: Json | null; + discord_username?: string | null; + display_name?: string | null; + email?: string | null; + email_open_rate?: number | null; + engagement_level?: string | null; + episodes?: Json | null; + explicit_content_filter_enabled?: boolean | null; + explicit_content_filter_locked?: boolean | null; + "explicit_content.filter_enabled"?: boolean | null; + "explicit_content.filter_locked"?: boolean | null; + external_urls_spotify?: string | null; + "external_urls.spotify"?: string | null; + facebook_profile_url?: string | null; + first_stream_date?: string | null; + followedArtists?: Json | null; + followers_total?: number | null; + "followers.href"?: string | null; + "followers.total"?: number | null; + gamification_points?: number | null; + genres?: Json | null; + heavyRotations?: Json | null; + href?: string | null; + id?: string; + images?: Json | null; + instagram_handle?: string | null; + last_campaign_interaction?: string | null; + last_login?: string | null; + last_purchase_date?: string | null; + last_stream_date?: string | null; + linkedin_profile_url?: string | null; + os_type?: string | null; + playlist?: Json | null; + preferences?: Json | null; + preferred_artists?: Json | null; + preferred_device?: string | null; + product?: string | null; + recentlyPlayed?: Json | null; + recommendations?: Json | null; + recommended_events?: Json | null; + reddit_username?: string | null; + saved_podcasts?: Json | null; + savedAlbums?: Json | null; + savedAudioBooks?: Json | null; + savedShows?: Json | null; + savedTracks?: Json | null; + social_shares?: number | null; + spotify_token?: string | null; + subscription_tier?: string | null; + testField?: string | null; + tiktok_handle?: string | null; + time_zone?: string | null; + timestamp?: string | null; + top_artists_long_term?: Json | null; + top_artists_medium_term?: Json | null; + top_tracks_long_term?: Json | null; + top_tracks_medium_term?: Json | null; + top_tracks_short_term?: Json | null; + topArtists?: Json | null; + topTracks?: Json | null; + total_spent?: number | null; + total_streams?: number | null; + twitter_handle?: string | null; + type?: string | null; + uri?: string | null; + youtube_channel_url?: string | null; + }; + Update: { + account_status?: string | null; + apple_token?: string | null; + campaign_id?: string | null; + campaign_interaction_count?: number | null; + campaignId?: string | null; + city?: string | null; + click_through_rate?: number | null; + clientId?: string | null; + consent_given?: boolean | null; + country?: string | null; + custom_tags?: Json | null; + discord_username?: string | null; + display_name?: string | null; + email?: string | null; + email_open_rate?: number | null; + engagement_level?: string | null; + episodes?: Json | null; + explicit_content_filter_enabled?: boolean | null; + explicit_content_filter_locked?: boolean | null; + "explicit_content.filter_enabled"?: boolean | null; + "explicit_content.filter_locked"?: boolean | null; + external_urls_spotify?: string | null; + "external_urls.spotify"?: string | null; + facebook_profile_url?: string | null; + first_stream_date?: string | null; + followedArtists?: Json | null; + followers_total?: number | null; + "followers.href"?: string | null; + "followers.total"?: number | null; + gamification_points?: number | null; + genres?: Json | null; + heavyRotations?: Json | null; + href?: string | null; + id?: string; + images?: Json | null; + instagram_handle?: string | null; + last_campaign_interaction?: string | null; + last_login?: string | null; + last_purchase_date?: string | null; + last_stream_date?: string | null; + linkedin_profile_url?: string | null; + os_type?: string | null; + playlist?: Json | null; + preferences?: Json | null; + preferred_artists?: Json | null; + preferred_device?: string | null; + product?: string | null; + recentlyPlayed?: Json | null; + recommendations?: Json | null; + recommended_events?: Json | null; + reddit_username?: string | null; + saved_podcasts?: Json | null; + savedAlbums?: Json | null; + savedAudioBooks?: Json | null; + savedShows?: Json | null; + savedTracks?: Json | null; + social_shares?: number | null; + spotify_token?: string | null; + subscription_tier?: string | null; + testField?: string | null; + tiktok_handle?: string | null; + time_zone?: string | null; + timestamp?: string | null; + top_artists_long_term?: Json | null; + top_artists_medium_term?: Json | null; + top_tracks_long_term?: Json | null; + top_tracks_medium_term?: Json | null; + top_tracks_short_term?: Json | null; + topArtists?: Json | null; + topTracks?: Json | null; + total_spent?: number | null; + total_streams?: number | null; + twitter_handle?: string | null; + type?: string | null; + uri?: string | null; + youtube_channel_url?: string | null; + }; Relationships: [ { - foreignKeyName: "fans_campaignId_fkey" - columns: ["campaignId"] - isOneToOne: false - referencedRelation: "campaigns" - referencedColumns: ["id"] + foreignKeyName: "fans_campaignId_fkey"; + columns: ["campaignId"]; + isOneToOne: false; + referencedRelation: "campaigns"; + referencedColumns: ["id"]; }, - ] - } + ]; + }; files: { Row: { - artist_account_id: string - created_at: string - description: string | null - file_name: string - id: string - is_directory: boolean - mime_type: string | null - owner_account_id: string - size_bytes: number | null - storage_key: string - tags: string[] | null - updated_at: string - } - Insert: { - artist_account_id: string - created_at?: string - description?: string | null - file_name: string - id?: string - is_directory?: boolean - mime_type?: string | null - owner_account_id: string - size_bytes?: number | null - storage_key: string - tags?: string[] | null - updated_at?: string - } - Update: { - artist_account_id?: string - created_at?: string - description?: string | null - file_name?: string - id?: string - is_directory?: boolean - mime_type?: string | null - owner_account_id?: string - size_bytes?: number | null - storage_key?: string - tags?: string[] | null - updated_at?: string - } + artist_account_id: string; + created_at: string; + description: string | null; + file_name: string; + id: string; + is_directory: boolean; + mime_type: string | null; + owner_account_id: string; + size_bytes: number | null; + storage_key: string; + tags: string[] | null; + updated_at: string; + }; + Insert: { + artist_account_id: string; + created_at?: string; + description?: string | null; + file_name: string; + id?: string; + is_directory?: boolean; + mime_type?: string | null; + owner_account_id: string; + size_bytes?: number | null; + storage_key: string; + tags?: string[] | null; + updated_at?: string; + }; + Update: { + artist_account_id?: string; + created_at?: string; + description?: string | null; + file_name?: string; + id?: string; + is_directory?: boolean; + mime_type?: string | null; + owner_account_id?: string; + size_bytes?: number | null; + storage_key?: string; + tags?: string[] | null; + updated_at?: string; + }; Relationships: [ { - foreignKeyName: "files_artist_account_id_fkey" - columns: ["artist_account_id"] - isOneToOne: false - referencedRelation: "accounts" - referencedColumns: ["id"] + foreignKeyName: "files_artist_account_id_fkey"; + columns: ["artist_account_id"]; + isOneToOne: false; + referencedRelation: "accounts"; + referencedColumns: ["id"]; }, { - foreignKeyName: "files_owner_account_id_fkey" - columns: ["owner_account_id"] - isOneToOne: false - referencedRelation: "accounts" - referencedColumns: ["id"] + foreignKeyName: "files_owner_account_id_fkey"; + columns: ["owner_account_id"]; + isOneToOne: false; + referencedRelation: "accounts"; + referencedColumns: ["id"]; }, - ] - } + ]; + }; follows: { Row: { - game: string | null - id: string | null - timestamp: number | null - } - Insert: { - game?: string | null - id?: string | null - timestamp?: number | null - } - Update: { - game?: string | null - id?: string | null - timestamp?: number | null - } - Relationships: [] - } + game: string | null; + id: string | null; + timestamp: number | null; + }; + Insert: { + game?: string | null; + id?: string | null; + timestamp?: number | null; + }; + Update: { + game?: string | null; + id?: string | null; + timestamp?: number | null; + }; + Relationships: []; + }; founder_dashboard_chart_annotations: { Row: { - chart_type: string | null - created_at: string | null - event_date: string - event_description: string | null - id: string - } - Insert: { - chart_type?: string | null - created_at?: string | null - event_date: string - event_description?: string | null - id?: string - } - Update: { - chart_type?: string | null - created_at?: string | null - event_date?: string - event_description?: string | null - id?: string - } - Relationships: [] - } + chart_type: string | null; + created_at: string | null; + event_date: string; + event_description: string | null; + id: string; + }; + Insert: { + chart_type?: string | null; + created_at?: string | null; + event_date: string; + event_description?: string | null; + id?: string; + }; + Update: { + chart_type?: string | null; + created_at?: string | null; + event_date?: string; + event_description?: string | null; + id?: string; + }; + Relationships: []; + }; funnel_analytics: { Row: { - artist_id: string | null - handle: string | null - id: string - pilot_id: string | null - status: number | null - type: Database["public"]["Enums"]["social_type"] | null - updated_at: string - } - Insert: { - artist_id?: string | null - handle?: string | null - id?: string - pilot_id?: string | null - status?: number | null - type?: Database["public"]["Enums"]["social_type"] | null - updated_at?: string - } - Update: { - artist_id?: string | null - handle?: string | null - id?: string - pilot_id?: string | null - status?: number | null - type?: Database["public"]["Enums"]["social_type"] | null - updated_at?: string - } + artist_id: string | null; + handle: string | null; + id: string; + pilot_id: string | null; + status: number | null; + type: Database["public"]["Enums"]["social_type"] | null; + updated_at: string; + }; + Insert: { + artist_id?: string | null; + handle?: string | null; + id?: string; + pilot_id?: string | null; + status?: number | null; + type?: Database["public"]["Enums"]["social_type"] | null; + updated_at?: string; + }; + Update: { + artist_id?: string | null; + handle?: string | null; + id?: string; + pilot_id?: string | null; + status?: number | null; + type?: Database["public"]["Enums"]["social_type"] | null; + updated_at?: string; + }; Relationships: [ { - foreignKeyName: "funnel_analytics_artist_id_fkey" - columns: ["artist_id"] - isOneToOne: false - referencedRelation: "accounts" - referencedColumns: ["id"] + foreignKeyName: "funnel_analytics_artist_id_fkey"; + columns: ["artist_id"]; + isOneToOne: false; + referencedRelation: "accounts"; + referencedColumns: ["id"]; }, - ] - } + ]; + }; funnel_analytics_accounts: { Row: { - account_id: string | null - analysis_id: string | null - created_at: string - id: string - updated_at: string | null - } - Insert: { - account_id?: string | null - analysis_id?: string | null - created_at?: string - id?: string - updated_at?: string | null - } - Update: { - account_id?: string | null - analysis_id?: string | null - created_at?: string - id?: string - updated_at?: string | null - } + account_id: string | null; + analysis_id: string | null; + created_at: string; + id: string; + updated_at: string | null; + }; + Insert: { + account_id?: string | null; + analysis_id?: string | null; + created_at?: string; + id?: string; + updated_at?: string | null; + }; + Update: { + account_id?: string | null; + analysis_id?: string | null; + created_at?: string; + id?: string; + updated_at?: string | null; + }; Relationships: [ { - foreignKeyName: "account_funnel_analytics_account_id_fkey" - columns: ["account_id"] - isOneToOne: false - referencedRelation: "accounts" - referencedColumns: ["id"] + foreignKeyName: "account_funnel_analytics_account_id_fkey"; + columns: ["account_id"]; + isOneToOne: false; + referencedRelation: "accounts"; + referencedColumns: ["id"]; }, { - foreignKeyName: "account_funnel_analytics_analysis_id_fkey" - columns: ["analysis_id"] - isOneToOne: false - referencedRelation: "funnel_analytics" - referencedColumns: ["id"] + foreignKeyName: "account_funnel_analytics_analysis_id_fkey"; + columns: ["analysis_id"]; + isOneToOne: false; + referencedRelation: "funnel_analytics"; + referencedColumns: ["id"]; }, - ] - } + ]; + }; funnel_analytics_segments: { Row: { - analysis_id: string | null - created_at: string - icon: string | null - id: string - name: string | null - size: number | null - } - Insert: { - analysis_id?: string | null - created_at?: string - icon?: string | null - id?: string - name?: string | null - size?: number | null - } - Update: { - analysis_id?: string | null - created_at?: string - icon?: string | null - id?: string - name?: string | null - size?: number | null - } + analysis_id: string | null; + created_at: string; + icon: string | null; + id: string; + name: string | null; + size: number | null; + }; + Insert: { + analysis_id?: string | null; + created_at?: string; + icon?: string | null; + id?: string; + name?: string | null; + size?: number | null; + }; + Update: { + analysis_id?: string | null; + created_at?: string; + icon?: string | null; + id?: string; + name?: string | null; + size?: number | null; + }; Relationships: [ { - foreignKeyName: "funnel_analytics_segments_analysis_id_fkey" - columns: ["analysis_id"] - isOneToOne: false - referencedRelation: "funnel_analytics" - referencedColumns: ["id"] + foreignKeyName: "funnel_analytics_segments_analysis_id_fkey"; + columns: ["analysis_id"]; + isOneToOne: false; + referencedRelation: "funnel_analytics"; + referencedColumns: ["id"]; }, - ] - } + ]; + }; funnel_reports: { Row: { - id: string - next_steps: string | null - report: string | null - stack_unique_id: string | null - timestamp: string - type: Database["public"]["Enums"]["social_type"] | null - } - Insert: { - id?: string - next_steps?: string | null - report?: string | null - stack_unique_id?: string | null - timestamp?: string - type?: Database["public"]["Enums"]["social_type"] | null - } - Update: { - id?: string - next_steps?: string | null - report?: string | null - stack_unique_id?: string | null - timestamp?: string - type?: Database["public"]["Enums"]["social_type"] | null - } - Relationships: [] - } + id: string; + next_steps: string | null; + report: string | null; + stack_unique_id: string | null; + timestamp: string; + type: Database["public"]["Enums"]["social_type"] | null; + }; + Insert: { + id?: string; + next_steps?: string | null; + report?: string | null; + stack_unique_id?: string | null; + timestamp?: string; + type?: Database["public"]["Enums"]["social_type"] | null; + }; + Update: { + id?: string; + next_steps?: string | null; + report?: string | null; + stack_unique_id?: string | null; + timestamp?: string; + type?: Database["public"]["Enums"]["social_type"] | null; + }; + Relationships: []; + }; game_start: { Row: { - clientId: string | null - fanId: Json | null - game: string | null - id: string | null - timestamp: number | null - } - Insert: { - clientId?: string | null - fanId?: Json | null - game?: string | null - id?: string | null - timestamp?: number | null - } - Update: { - clientId?: string | null - fanId?: Json | null - game?: string | null - id?: string | null - timestamp?: number | null - } - Relationships: [] - } + clientId: string | null; + fanId: Json | null; + game: string | null; + id: string | null; + timestamp: number | null; + }; + Insert: { + clientId?: string | null; + fanId?: Json | null; + game?: string | null; + id?: string | null; + timestamp?: number | null; + }; + Update: { + clientId?: string | null; + fanId?: Json | null; + game?: string | null; + id?: string | null; + timestamp?: number | null; + }; + Relationships: []; + }; invitations: { Row: { - account_id: string - created_at: string - email: string - expires_at: string - id: number - invite_token: string - invited_by: string - role: string - updated_at: string - } - Insert: { - account_id: string - created_at?: string - email: string - expires_at?: string - id?: number - invite_token: string - invited_by: string - role: string - updated_at?: string - } - Update: { - account_id?: string - created_at?: string - email?: string - expires_at?: string - id?: number - invite_token?: string - invited_by?: string - role?: string - updated_at?: string - } + account_id: string; + created_at: string; + email: string; + expires_at: string; + id: number; + invite_token: string; + invited_by: string; + role: string; + updated_at: string; + }; + Insert: { + account_id: string; + created_at?: string; + email: string; + expires_at?: string; + id?: number; + invite_token: string; + invited_by: string; + role: string; + updated_at?: string; + }; + Update: { + account_id?: string; + created_at?: string; + email?: string; + expires_at?: string; + id?: number; + invite_token?: string; + invited_by?: string; + role?: string; + updated_at?: string; + }; Relationships: [ { - foreignKeyName: "invitations_role_fkey" - columns: ["role"] - isOneToOne: false - referencedRelation: "roles" - referencedColumns: ["name"] + foreignKeyName: "invitations_role_fkey"; + columns: ["role"]; + isOneToOne: false; + referencedRelation: "roles"; + referencedColumns: ["name"]; }, - ] - } + ]; + }; ios_redirect: { Row: { - clientId: string | null - fanId: string | null - id: string | null - timestamp: number | null - } - Insert: { - clientId?: string | null - fanId?: string | null - id?: string | null - timestamp?: number | null - } - Update: { - clientId?: string | null - fanId?: string | null - id?: string | null - timestamp?: number | null - } - Relationships: [] - } + clientId: string | null; + fanId: string | null; + id: string | null; + timestamp: number | null; + }; + Insert: { + clientId?: string | null; + fanId?: string | null; + id?: string | null; + timestamp?: number | null; + }; + Update: { + clientId?: string | null; + fanId?: string | null; + id?: string | null; + timestamp?: number | null; + }; + Relationships: []; + }; leaderboard: { Row: { - id: string | null - Name: string | null - Number: string | null - Score: string | null - Spotify: string | null - "Time._nanoseconds": string | null - "Time._seconds": string | null - } - Insert: { - id?: string | null - Name?: string | null - Number?: string | null - Score?: string | null - Spotify?: string | null - "Time._nanoseconds"?: string | null - "Time._seconds"?: string | null - } - Update: { - id?: string | null - Name?: string | null - Number?: string | null - Score?: string | null - Spotify?: string | null - "Time._nanoseconds"?: string | null - "Time._seconds"?: string | null - } - Relationships: [] - } + id: string | null; + Name: string | null; + Number: string | null; + Score: string | null; + Spotify: string | null; + "Time._nanoseconds": string | null; + "Time._seconds": string | null; + }; + Insert: { + id?: string | null; + Name?: string | null; + Number?: string | null; + Score?: string | null; + Spotify?: string | null; + "Time._nanoseconds"?: string | null; + "Time._seconds"?: string | null; + }; + Update: { + id?: string | null; + Name?: string | null; + Number?: string | null; + Score?: string | null; + Spotify?: string | null; + "Time._nanoseconds"?: string | null; + "Time._seconds"?: string | null; + }; + Relationships: []; + }; leaderboard_boogie: { Row: { - clientId: string | null - displayName: string | null - fanId: string | null - gameType: string | null - id: string | null - score: number | null - timestamp: string | null - } - Insert: { - clientId?: string | null - displayName?: string | null - fanId?: string | null - gameType?: string | null - id?: string | null - score?: number | null - timestamp?: string | null - } - Update: { - clientId?: string | null - displayName?: string | null - fanId?: string | null - gameType?: string | null - id?: string | null - score?: number | null - timestamp?: string | null - } - Relationships: [] - } + clientId: string | null; + displayName: string | null; + fanId: string | null; + gameType: string | null; + id: string | null; + score: number | null; + timestamp: string | null; + }; + Insert: { + clientId?: string | null; + displayName?: string | null; + fanId?: string | null; + gameType?: string | null; + id?: string | null; + score?: number | null; + timestamp?: string | null; + }; + Update: { + clientId?: string | null; + displayName?: string | null; + fanId?: string | null; + gameType?: string | null; + id?: string | null; + score?: number | null; + timestamp?: string | null; + }; + Relationships: []; + }; leaderboard_luh_tyler_3d: { Row: { - FanId: string | null - id: string | null - Score: string | null - ScorePerTime: string | null - Time: string | null - timestamp: string | null - UserName: string | null - } - Insert: { - FanId?: string | null - id?: string | null - Score?: string | null - ScorePerTime?: string | null - Time?: string | null - timestamp?: string | null - UserName?: string | null - } - Update: { - FanId?: string | null - id?: string | null - Score?: string | null - ScorePerTime?: string | null - Time?: string | null - timestamp?: string | null - UserName?: string | null - } - Relationships: [] - } + FanId: string | null; + id: string | null; + Score: string | null; + ScorePerTime: string | null; + Time: string | null; + timestamp: string | null; + UserName: string | null; + }; + Insert: { + FanId?: string | null; + id?: string | null; + Score?: string | null; + ScorePerTime?: string | null; + Time?: string | null; + timestamp?: string | null; + UserName?: string | null; + }; + Update: { + FanId?: string | null; + id?: string | null; + Score?: string | null; + ScorePerTime?: string | null; + Time?: string | null; + timestamp?: string | null; + UserName?: string | null; + }; + Relationships: []; + }; leaderboard_luv: { Row: { - f: string | null - id: string | null - } + f: string | null; + id: string | null; + }; Insert: { - f?: string | null - id?: string | null - } + f?: string | null; + id?: string | null; + }; Update: { - f?: string | null - id?: string | null - } - Relationships: [] - } + f?: string | null; + id?: string | null; + }; + Relationships: []; + }; memories: { Row: { - content: Json - id: string - room_id: string | null - updated_at: string - } - Insert: { - content: Json - id?: string - room_id?: string | null - updated_at?: string - } - Update: { - content?: Json - id?: string - room_id?: string | null - updated_at?: string - } + content: Json; + id: string; + room_id: string | null; + updated_at: string; + }; + Insert: { + content: Json; + id?: string; + room_id?: string | null; + updated_at?: string; + }; + Update: { + content?: Json; + id?: string; + room_id?: string | null; + updated_at?: string; + }; Relationships: [ { - foreignKeyName: "memories_room_id_fkey" - columns: ["room_id"] - isOneToOne: false - referencedRelation: "rooms" - referencedColumns: ["id"] + foreignKeyName: "memories_room_id_fkey"; + columns: ["room_id"]; + isOneToOne: false; + referencedRelation: "rooms"; + referencedColumns: ["id"]; }, - ] - } + ]; + }; notifications: { Row: { - account_id: string - body: string - channel: Database["public"]["Enums"]["notification_channel"] - created_at: string - dismissed: boolean - expires_at: string | null - id: number - link: string | null - type: Database["public"]["Enums"]["notification_type"] - } - Insert: { - account_id: string - body: string - channel?: Database["public"]["Enums"]["notification_channel"] - created_at?: string - dismissed?: boolean - expires_at?: string | null - id?: never - link?: string | null - type?: Database["public"]["Enums"]["notification_type"] - } - Update: { - account_id?: string - body?: string - channel?: Database["public"]["Enums"]["notification_channel"] - created_at?: string - dismissed?: boolean - expires_at?: string | null - id?: never - link?: string | null - type?: Database["public"]["Enums"]["notification_type"] - } - Relationships: [] - } + account_id: string; + body: string; + channel: Database["public"]["Enums"]["notification_channel"]; + created_at: string; + dismissed: boolean; + expires_at: string | null; + id: number; + link: string | null; + type: Database["public"]["Enums"]["notification_type"]; + }; + Insert: { + account_id: string; + body: string; + channel?: Database["public"]["Enums"]["notification_channel"]; + created_at?: string; + dismissed?: boolean; + expires_at?: string | null; + id?: never; + link?: string | null; + type?: Database["public"]["Enums"]["notification_type"]; + }; + Update: { + account_id?: string; + body?: string; + channel?: Database["public"]["Enums"]["notification_channel"]; + created_at?: string; + dismissed?: boolean; + expires_at?: string | null; + id?: never; + link?: string | null; + type?: Database["public"]["Enums"]["notification_type"]; + }; + Relationships: []; + }; order_items: { Row: { - created_at: string - id: string - order_id: string - price_amount: number | null - product_id: string - quantity: number - updated_at: string - variant_id: string - } - Insert: { - created_at?: string - id: string - order_id: string - price_amount?: number | null - product_id: string - quantity?: number - updated_at?: string - variant_id: string - } - Update: { - created_at?: string - id?: string - order_id?: string - price_amount?: number | null - product_id?: string - quantity?: number - updated_at?: string - variant_id?: string - } + created_at: string; + id: string; + order_id: string; + price_amount: number | null; + product_id: string; + quantity: number; + updated_at: string; + variant_id: string; + }; + Insert: { + created_at?: string; + id: string; + order_id: string; + price_amount?: number | null; + product_id: string; + quantity?: number; + updated_at?: string; + variant_id: string; + }; + Update: { + created_at?: string; + id?: string; + order_id?: string; + price_amount?: number | null; + product_id?: string; + quantity?: number; + updated_at?: string; + variant_id?: string; + }; Relationships: [ { - foreignKeyName: "order_items_order_id_fkey" - columns: ["order_id"] - isOneToOne: false - referencedRelation: "orders" - referencedColumns: ["id"] + foreignKeyName: "order_items_order_id_fkey"; + columns: ["order_id"]; + isOneToOne: false; + referencedRelation: "orders"; + referencedColumns: ["id"]; }, - ] - } + ]; + }; orders: { Row: { - account_id: string - billing_customer_id: number - billing_provider: Database["public"]["Enums"]["billing_provider"] - created_at: string - currency: string - id: string - status: Database["public"]["Enums"]["payment_status"] - total_amount: number - updated_at: string - } - Insert: { - account_id: string - billing_customer_id: number - billing_provider: Database["public"]["Enums"]["billing_provider"] - created_at?: string - currency: string - id: string - status: Database["public"]["Enums"]["payment_status"] - total_amount: number - updated_at?: string - } - Update: { - account_id?: string - billing_customer_id?: number - billing_provider?: Database["public"]["Enums"]["billing_provider"] - created_at?: string - currency?: string - id?: string - status?: Database["public"]["Enums"]["payment_status"] - total_amount?: number - updated_at?: string - } + account_id: string; + billing_customer_id: number; + billing_provider: Database["public"]["Enums"]["billing_provider"]; + created_at: string; + currency: string; + id: string; + status: Database["public"]["Enums"]["payment_status"]; + total_amount: number; + updated_at: string; + }; + Insert: { + account_id: string; + billing_customer_id: number; + billing_provider: Database["public"]["Enums"]["billing_provider"]; + created_at?: string; + currency: string; + id: string; + status: Database["public"]["Enums"]["payment_status"]; + total_amount: number; + updated_at?: string; + }; + Update: { + account_id?: string; + billing_customer_id?: number; + billing_provider?: Database["public"]["Enums"]["billing_provider"]; + created_at?: string; + currency?: string; + id?: string; + status?: Database["public"]["Enums"]["payment_status"]; + total_amount?: number; + updated_at?: string; + }; Relationships: [ { - foreignKeyName: "orders_billing_customer_id_fkey" - columns: ["billing_customer_id"] - isOneToOne: false - referencedRelation: "billing_customers" - referencedColumns: ["id"] + foreignKeyName: "orders_billing_customer_id_fkey"; + columns: ["billing_customer_id"]; + isOneToOne: false; + referencedRelation: "billing_customers"; + referencedColumns: ["id"]; }, - ] - } + ]; + }; organization_domains: { Row: { - created_at: string | null - domain: string - id: string - organization_id: string - } - Insert: { - created_at?: string | null - domain: string - id?: string - organization_id: string - } - Update: { - created_at?: string | null - domain?: string - id?: string - organization_id?: string - } + created_at: string | null; + domain: string; + id: string; + organization_id: string; + }; + Insert: { + created_at?: string | null; + domain: string; + id?: string; + organization_id: string; + }; + Update: { + created_at?: string | null; + domain?: string; + id?: string; + organization_id?: string; + }; Relationships: [ { - foreignKeyName: "organization_domains_organization_id_fkey" - columns: ["organization_id"] - isOneToOne: false - referencedRelation: "accounts" - referencedColumns: ["id"] + foreignKeyName: "organization_domains_organization_id_fkey"; + columns: ["organization_id"]; + isOneToOne: false; + referencedRelation: "accounts"; + referencedColumns: ["id"]; }, - ] - } + ]; + }; plans: { Row: { - name: string - tokens_quota: number - variant_id: string - } - Insert: { - name: string - tokens_quota: number - variant_id: string - } - Update: { - name?: string - tokens_quota?: number - variant_id?: string - } - Relationships: [] - } + name: string; + tokens_quota: number; + variant_id: string; + }; + Insert: { + name: string; + tokens_quota: number; + variant_id: string; + }; + Update: { + name?: string; + tokens_quota?: number; + variant_id?: string; + }; + Relationships: []; + }; popup_open: { Row: { - campaignId: string | null - clientId: string | null - fanId: string | null - game: string | null - id: string | null - timestamp: string | null - } - Insert: { - campaignId?: string | null - clientId?: string | null - fanId?: string | null - game?: string | null - id?: string | null - timestamp?: string | null - } - Update: { - campaignId?: string | null - clientId?: string | null - fanId?: string | null - game?: string | null - id?: string | null - timestamp?: string | null - } - Relationships: [] - } + campaignId: string | null; + clientId: string | null; + fanId: string | null; + game: string | null; + id: string | null; + timestamp: string | null; + }; + Insert: { + campaignId?: string | null; + clientId?: string | null; + fanId?: string | null; + game?: string | null; + id?: string | null; + timestamp?: string | null; + }; + Update: { + campaignId?: string | null; + clientId?: string | null; + fanId?: string | null; + game?: string | null; + id?: string | null; + timestamp?: string | null; + }; + Relationships: []; + }; post_comments: { Row: { - comment: string | null - commented_at: string - id: string - post_id: string | null - social_id: string | null - } - Insert: { - comment?: string | null - commented_at: string - id?: string - post_id?: string | null - social_id?: string | null - } - Update: { - comment?: string | null - commented_at?: string - id?: string - post_id?: string | null - social_id?: string | null - } + comment: string | null; + commented_at: string; + id: string; + post_id: string | null; + social_id: string | null; + }; + Insert: { + comment?: string | null; + commented_at: string; + id?: string; + post_id?: string | null; + social_id?: string | null; + }; + Update: { + comment?: string | null; + commented_at?: string; + id?: string; + post_id?: string | null; + social_id?: string | null; + }; Relationships: [ { - foreignKeyName: "post_comments_post_id_fkey" - columns: ["post_id"] - isOneToOne: false - referencedRelation: "posts" - referencedColumns: ["id"] + foreignKeyName: "post_comments_post_id_fkey"; + columns: ["post_id"]; + isOneToOne: false; + referencedRelation: "posts"; + referencedColumns: ["id"]; }, { - foreignKeyName: "post_comments_social_id_fkey" - columns: ["social_id"] - isOneToOne: false - referencedRelation: "socials" - referencedColumns: ["id"] + foreignKeyName: "post_comments_social_id_fkey"; + columns: ["social_id"]; + isOneToOne: false; + referencedRelation: "socials"; + referencedColumns: ["id"]; }, - ] - } + ]; + }; posts: { Row: { - id: string - post_url: string - updated_at: string - } - Insert: { - id?: string - post_url: string - updated_at?: string - } - Update: { - id?: string - post_url?: string - updated_at?: string - } - Relationships: [] - } + id: string; + post_url: string; + updated_at: string; + }; + Insert: { + id?: string; + post_url: string; + updated_at?: string; + }; + Update: { + id?: string; + post_url?: string; + updated_at?: string; + }; + Relationships: []; + }; presave: { Row: { - accessToken: string | null - fanId: string | null - "fanId.error.code": string | null - "fanId.error.name": string | null - id: string | null - presaveId: string | null - presaveReleaseDate: string | null - refreshToken: string | null - timestamp: number | null - } - Insert: { - accessToken?: string | null - fanId?: string | null - "fanId.error.code"?: string | null - "fanId.error.name"?: string | null - id?: string | null - presaveId?: string | null - presaveReleaseDate?: string | null - refreshToken?: string | null - timestamp?: number | null - } - Update: { - accessToken?: string | null - fanId?: string | null - "fanId.error.code"?: string | null - "fanId.error.name"?: string | null - id?: string | null - presaveId?: string | null - presaveReleaseDate?: string | null - refreshToken?: string | null - timestamp?: number | null - } - Relationships: [] - } + accessToken: string | null; + fanId: string | null; + "fanId.error.code": string | null; + "fanId.error.name": string | null; + id: string | null; + presaveId: string | null; + presaveReleaseDate: string | null; + refreshToken: string | null; + timestamp: number | null; + }; + Insert: { + accessToken?: string | null; + fanId?: string | null; + "fanId.error.code"?: string | null; + "fanId.error.name"?: string | null; + id?: string | null; + presaveId?: string | null; + presaveReleaseDate?: string | null; + refreshToken?: string | null; + timestamp?: number | null; + }; + Update: { + accessToken?: string | null; + fanId?: string | null; + "fanId.error.code"?: string | null; + "fanId.error.name"?: string | null; + id?: string | null; + presaveId?: string | null; + presaveReleaseDate?: string | null; + refreshToken?: string | null; + timestamp?: number | null; + }; + Relationships: []; + }; role_permissions: { Row: { - id: number - permission: Database["public"]["Enums"]["app_permissions"] - role: string - } + id: number; + permission: Database["public"]["Enums"]["app_permissions"]; + role: string; + }; Insert: { - id?: number - permission: Database["public"]["Enums"]["app_permissions"] - role: string - } + id?: number; + permission: Database["public"]["Enums"]["app_permissions"]; + role: string; + }; Update: { - id?: number - permission?: Database["public"]["Enums"]["app_permissions"] - role?: string - } + id?: number; + permission?: Database["public"]["Enums"]["app_permissions"]; + role?: string; + }; Relationships: [ { - foreignKeyName: "role_permissions_role_fkey" - columns: ["role"] - isOneToOne: false - referencedRelation: "roles" - referencedColumns: ["name"] + foreignKeyName: "role_permissions_role_fkey"; + columns: ["role"]; + isOneToOne: false; + referencedRelation: "roles"; + referencedColumns: ["name"]; }, - ] - } + ]; + }; roles: { Row: { - hierarchy_level: number - name: string - } + hierarchy_level: number; + name: string; + }; Insert: { - hierarchy_level: number - name: string - } + hierarchy_level: number; + name: string; + }; Update: { - hierarchy_level?: number - name?: string - } - Relationships: [] - } + hierarchy_level?: number; + name?: string; + }; + Relationships: []; + }; room_reports: { Row: { - id: string - report_id: string - room_id: string | null - } + id: string; + report_id: string; + room_id: string | null; + }; Insert: { - id?: string - report_id?: string - room_id?: string | null - } + id?: string; + report_id?: string; + room_id?: string | null; + }; Update: { - id?: string - report_id?: string - room_id?: string | null - } + id?: string; + report_id?: string; + room_id?: string | null; + }; Relationships: [ { - foreignKeyName: "room_reports_report_id_fkey" - columns: ["report_id"] - isOneToOne: false - referencedRelation: "segment_reports" - referencedColumns: ["id"] + foreignKeyName: "room_reports_report_id_fkey"; + columns: ["report_id"]; + isOneToOne: false; + referencedRelation: "segment_reports"; + referencedColumns: ["id"]; }, { - foreignKeyName: "room_reports_room_id_fkey" - columns: ["room_id"] - isOneToOne: false - referencedRelation: "rooms" - referencedColumns: ["id"] + foreignKeyName: "room_reports_room_id_fkey"; + columns: ["room_id"]; + isOneToOne: false; + referencedRelation: "rooms"; + referencedColumns: ["id"]; }, - ] - } + ]; + }; rooms: { Row: { - account_id: string | null - artist_id: string | null - id: string - topic: string | null - updated_at: string - } - Insert: { - account_id?: string | null - artist_id?: string | null - id?: string - topic?: string | null - updated_at?: string - } - Update: { - account_id?: string | null - artist_id?: string | null - id?: string - topic?: string | null - updated_at?: string - } + account_id: string | null; + artist_id: string | null; + id: string; + topic: string | null; + updated_at: string; + }; + Insert: { + account_id?: string | null; + artist_id?: string | null; + id?: string; + topic?: string | null; + updated_at?: string; + }; + Update: { + account_id?: string | null; + artist_id?: string | null; + id?: string; + topic?: string | null; + updated_at?: string; + }; Relationships: [ { - foreignKeyName: "rooms_artist_id_fkey" - columns: ["artist_id"] - isOneToOne: false - referencedRelation: "accounts" - referencedColumns: ["id"] + foreignKeyName: "rooms_artist_id_fkey"; + columns: ["artist_id"]; + isOneToOne: false; + referencedRelation: "accounts"; + referencedColumns: ["id"]; }, - ] - } + ]; + }; sales_pipeline_customers: { Row: { - activity_count: number | null - assigned_to: string | null - company_size: string | null - competitors: string[] | null - contact_email: string | null - contact_name: string | null - contact_phone: string | null - contacts: Json | null - conversion_stage: string | null - conversion_target_date: string | null - created_at: string | null - current_artists: number - current_mrr: number - custom_fields: Json | null - days_in_stage: number | null - domain: string | null - email: string | null - engagement_health: string | null - expected_close_date: string | null - external_ids: Json | null - id: string - industry: string | null - internal_owner: string | null - last_activity_date: string | null - last_activity_type: string | null - last_contact_date: string - logo_url: string | null - lost_reason: string | null - name: string - next_action: string | null - next_activity_date: string | null - next_activity_type: string | null - notes: string | null - order_index: number | null - organization: string | null - potential_artists: number - potential_mrr: number - priority: string | null - probability: number | null - recoupable_user_id: string | null - source: string | null - stage: string - stage_entered_at: string | null - tags: string[] | null - todos: Json | null - trial_end_date: string | null - trial_start_date: string | null - type: string | null - updated_at: string | null - use_case_type: string | null - website: string | null - weighted_mrr: number | null - win_reason: string | null - } - Insert: { - activity_count?: number | null - assigned_to?: string | null - company_size?: string | null - competitors?: string[] | null - contact_email?: string | null - contact_name?: string | null - contact_phone?: string | null - contacts?: Json | null - conversion_stage?: string | null - conversion_target_date?: string | null - created_at?: string | null - current_artists?: number - current_mrr?: number - custom_fields?: Json | null - days_in_stage?: number | null - domain?: string | null - email?: string | null - engagement_health?: string | null - expected_close_date?: string | null - external_ids?: Json | null - id?: string - industry?: string | null - internal_owner?: string | null - last_activity_date?: string | null - last_activity_type?: string | null - last_contact_date?: string - logo_url?: string | null - lost_reason?: string | null - name: string - next_action?: string | null - next_activity_date?: string | null - next_activity_type?: string | null - notes?: string | null - order_index?: number | null - organization?: string | null - potential_artists?: number - potential_mrr?: number - priority?: string | null - probability?: number | null - recoupable_user_id?: string | null - source?: string | null - stage: string - stage_entered_at?: string | null - tags?: string[] | null - todos?: Json | null - trial_end_date?: string | null - trial_start_date?: string | null - type?: string | null - updated_at?: string | null - use_case_type?: string | null - website?: string | null - weighted_mrr?: number | null - win_reason?: string | null - } - Update: { - activity_count?: number | null - assigned_to?: string | null - company_size?: string | null - competitors?: string[] | null - contact_email?: string | null - contact_name?: string | null - contact_phone?: string | null - contacts?: Json | null - conversion_stage?: string | null - conversion_target_date?: string | null - created_at?: string | null - current_artists?: number - current_mrr?: number - custom_fields?: Json | null - days_in_stage?: number | null - domain?: string | null - email?: string | null - engagement_health?: string | null - expected_close_date?: string | null - external_ids?: Json | null - id?: string - industry?: string | null - internal_owner?: string | null - last_activity_date?: string | null - last_activity_type?: string | null - last_contact_date?: string - logo_url?: string | null - lost_reason?: string | null - name?: string - next_action?: string | null - next_activity_date?: string | null - next_activity_type?: string | null - notes?: string | null - order_index?: number | null - organization?: string | null - potential_artists?: number - potential_mrr?: number - priority?: string | null - probability?: number | null - recoupable_user_id?: string | null - source?: string | null - stage?: string - stage_entered_at?: string | null - tags?: string[] | null - todos?: Json | null - trial_end_date?: string | null - trial_start_date?: string | null - type?: string | null - updated_at?: string | null - use_case_type?: string | null - website?: string | null - weighted_mrr?: number | null - win_reason?: string | null - } - Relationships: [] - } + activity_count: number | null; + assigned_to: string | null; + company_size: string | null; + competitors: string[] | null; + contact_email: string | null; + contact_name: string | null; + contact_phone: string | null; + contacts: Json | null; + conversion_stage: string | null; + conversion_target_date: string | null; + created_at: string | null; + current_artists: number; + current_mrr: number; + custom_fields: Json | null; + days_in_stage: number | null; + domain: string | null; + email: string | null; + engagement_health: string | null; + expected_close_date: string | null; + external_ids: Json | null; + id: string; + industry: string | null; + internal_owner: string | null; + last_activity_date: string | null; + last_activity_type: string | null; + last_contact_date: string; + logo_url: string | null; + lost_reason: string | null; + name: string; + next_action: string | null; + next_activity_date: string | null; + next_activity_type: string | null; + notes: string | null; + order_index: number | null; + organization: string | null; + potential_artists: number; + potential_mrr: number; + priority: string | null; + probability: number | null; + recoupable_user_id: string | null; + source: string | null; + stage: string; + stage_entered_at: string | null; + tags: string[] | null; + todos: Json | null; + trial_end_date: string | null; + trial_start_date: string | null; + type: string | null; + updated_at: string | null; + use_case_type: string | null; + website: string | null; + weighted_mrr: number | null; + win_reason: string | null; + }; + Insert: { + activity_count?: number | null; + assigned_to?: string | null; + company_size?: string | null; + competitors?: string[] | null; + contact_email?: string | null; + contact_name?: string | null; + contact_phone?: string | null; + contacts?: Json | null; + conversion_stage?: string | null; + conversion_target_date?: string | null; + created_at?: string | null; + current_artists?: number; + current_mrr?: number; + custom_fields?: Json | null; + days_in_stage?: number | null; + domain?: string | null; + email?: string | null; + engagement_health?: string | null; + expected_close_date?: string | null; + external_ids?: Json | null; + id?: string; + industry?: string | null; + internal_owner?: string | null; + last_activity_date?: string | null; + last_activity_type?: string | null; + last_contact_date?: string; + logo_url?: string | null; + lost_reason?: string | null; + name: string; + next_action?: string | null; + next_activity_date?: string | null; + next_activity_type?: string | null; + notes?: string | null; + order_index?: number | null; + organization?: string | null; + potential_artists?: number; + potential_mrr?: number; + priority?: string | null; + probability?: number | null; + recoupable_user_id?: string | null; + source?: string | null; + stage: string; + stage_entered_at?: string | null; + tags?: string[] | null; + todos?: Json | null; + trial_end_date?: string | null; + trial_start_date?: string | null; + type?: string | null; + updated_at?: string | null; + use_case_type?: string | null; + website?: string | null; + weighted_mrr?: number | null; + win_reason?: string | null; + }; + Update: { + activity_count?: number | null; + assigned_to?: string | null; + company_size?: string | null; + competitors?: string[] | null; + contact_email?: string | null; + contact_name?: string | null; + contact_phone?: string | null; + contacts?: Json | null; + conversion_stage?: string | null; + conversion_target_date?: string | null; + created_at?: string | null; + current_artists?: number; + current_mrr?: number; + custom_fields?: Json | null; + days_in_stage?: number | null; + domain?: string | null; + email?: string | null; + engagement_health?: string | null; + expected_close_date?: string | null; + external_ids?: Json | null; + id?: string; + industry?: string | null; + internal_owner?: string | null; + last_activity_date?: string | null; + last_activity_type?: string | null; + last_contact_date?: string; + logo_url?: string | null; + lost_reason?: string | null; + name?: string; + next_action?: string | null; + next_activity_date?: string | null; + next_activity_type?: string | null; + notes?: string | null; + order_index?: number | null; + organization?: string | null; + potential_artists?: number; + potential_mrr?: number; + priority?: string | null; + probability?: number | null; + recoupable_user_id?: string | null; + source?: string | null; + stage?: string; + stage_entered_at?: string | null; + tags?: string[] | null; + todos?: Json | null; + trial_end_date?: string | null; + trial_start_date?: string | null; + type?: string | null; + updated_at?: string | null; + use_case_type?: string | null; + website?: string | null; + weighted_mrr?: number | null; + win_reason?: string | null; + }; + Relationships: []; + }; save_track: { Row: { - game: string | null - id: string | null - timestamp: string | null - } - Insert: { - game?: string | null - id?: string | null - timestamp?: string | null - } - Update: { - game?: string | null - id?: string | null - timestamp?: string | null - } - Relationships: [] - } + game: string | null; + id: string | null; + timestamp: string | null; + }; + Insert: { + game?: string | null; + id?: string | null; + timestamp?: string | null; + }; + Update: { + game?: string | null; + id?: string | null; + timestamp?: string | null; + }; + Relationships: []; + }; scheduled_actions: { Row: { - account_id: string - artist_account_id: string - created_at: string | null - enabled: boolean | null - id: string - last_run: string | null - model: string | null - next_run: string | null - prompt: string - schedule: string - title: string - trigger_schedule_id: string | null - updated_at: string | null - } - Insert: { - account_id: string - artist_account_id: string - created_at?: string | null - enabled?: boolean | null - id?: string - last_run?: string | null - model?: string | null - next_run?: string | null - prompt: string - schedule: string - title: string - trigger_schedule_id?: string | null - updated_at?: string | null - } - Update: { - account_id?: string - artist_account_id?: string - created_at?: string | null - enabled?: boolean | null - id?: string - last_run?: string | null - model?: string | null - next_run?: string | null - prompt?: string - schedule?: string - title?: string - trigger_schedule_id?: string | null - updated_at?: string | null - } + account_id: string; + artist_account_id: string; + created_at: string | null; + enabled: boolean | null; + id: string; + last_run: string | null; + model: string | null; + next_run: string | null; + prompt: string; + schedule: string; + title: string; + trigger_schedule_id: string | null; + updated_at: string | null; + }; + Insert: { + account_id: string; + artist_account_id: string; + created_at?: string | null; + enabled?: boolean | null; + id?: string; + last_run?: string | null; + model?: string | null; + next_run?: string | null; + prompt: string; + schedule: string; + title: string; + trigger_schedule_id?: string | null; + updated_at?: string | null; + }; + Update: { + account_id?: string; + artist_account_id?: string; + created_at?: string | null; + enabled?: boolean | null; + id?: string; + last_run?: string | null; + model?: string | null; + next_run?: string | null; + prompt?: string; + schedule?: string; + title?: string; + trigger_schedule_id?: string | null; + updated_at?: string | null; + }; Relationships: [ { - foreignKeyName: "scheduled_actions_account_id_fkey" - columns: ["account_id"] - isOneToOne: false - referencedRelation: "accounts" - referencedColumns: ["id"] + foreignKeyName: "scheduled_actions_account_id_fkey"; + columns: ["account_id"]; + isOneToOne: false; + referencedRelation: "accounts"; + referencedColumns: ["id"]; }, { - foreignKeyName: "scheduled_actions_artist_account_id_fkey" - columns: ["artist_account_id"] - isOneToOne: false - referencedRelation: "accounts" - referencedColumns: ["id"] + foreignKeyName: "scheduled_actions_artist_account_id_fkey"; + columns: ["artist_account_id"]; + isOneToOne: false; + referencedRelation: "accounts"; + referencedColumns: ["id"]; }, - ] - } + ]; + }; segment_reports: { Row: { - artist_id: string | null - id: string - next_steps: string | null - report: string | null - updated_at: string | null - } - Insert: { - artist_id?: string | null - id?: string - next_steps?: string | null - report?: string | null - updated_at?: string | null - } - Update: { - artist_id?: string | null - id?: string - next_steps?: string | null - report?: string | null - updated_at?: string | null - } + artist_id: string | null; + id: string; + next_steps: string | null; + report: string | null; + updated_at: string | null; + }; + Insert: { + artist_id?: string | null; + id?: string; + next_steps?: string | null; + report?: string | null; + updated_at?: string | null; + }; + Update: { + artist_id?: string | null; + id?: string; + next_steps?: string | null; + report?: string | null; + updated_at?: string | null; + }; Relationships: [ { - foreignKeyName: "segment_reports_artist_id_fkey" - columns: ["artist_id"] - isOneToOne: false - referencedRelation: "accounts" - referencedColumns: ["id"] + foreignKeyName: "segment_reports_artist_id_fkey"; + columns: ["artist_id"]; + isOneToOne: false; + referencedRelation: "accounts"; + referencedColumns: ["id"]; }, - ] - } + ]; + }; segment_rooms: { Row: { - id: string - room_id: string - segment_id: string - updated_at: string - } - Insert: { - id?: string - room_id: string - segment_id: string - updated_at?: string - } - Update: { - id?: string - room_id?: string - segment_id?: string - updated_at?: string - } + id: string; + room_id: string; + segment_id: string; + updated_at: string; + }; + Insert: { + id?: string; + room_id: string; + segment_id: string; + updated_at?: string; + }; + Update: { + id?: string; + room_id?: string; + segment_id?: string; + updated_at?: string; + }; Relationships: [ { - foreignKeyName: "segment_rooms_room_id_fkey" - columns: ["room_id"] - isOneToOne: false - referencedRelation: "rooms" - referencedColumns: ["id"] + foreignKeyName: "segment_rooms_room_id_fkey"; + columns: ["room_id"]; + isOneToOne: false; + referencedRelation: "rooms"; + referencedColumns: ["id"]; }, { - foreignKeyName: "segment_rooms_segment_id_fkey" - columns: ["segment_id"] - isOneToOne: false - referencedRelation: "segments" - referencedColumns: ["id"] + foreignKeyName: "segment_rooms_segment_id_fkey"; + columns: ["segment_id"]; + isOneToOne: false; + referencedRelation: "segments"; + referencedColumns: ["id"]; }, - ] - } + ]; + }; segments: { Row: { - id: string - name: string - updated_at: string | null - } - Insert: { - id?: string - name: string - updated_at?: string | null - } - Update: { - id?: string - name?: string - updated_at?: string | null - } - Relationships: [] - } + id: string; + name: string; + updated_at: string | null; + }; + Insert: { + id?: string; + name: string; + updated_at?: string | null; + }; + Update: { + id?: string; + name?: string; + updated_at?: string | null; + }; + Relationships: []; + }; social_fans: { Row: { - artist_social_id: string - created_at: string - fan_social_id: string - id: string - latest_engagement: string | null - latest_engagement_id: string | null - updated_at: string - } - Insert: { - artist_social_id: string - created_at?: string - fan_social_id: string - id?: string - latest_engagement?: string | null - latest_engagement_id?: string | null - updated_at?: string - } - Update: { - artist_social_id?: string - created_at?: string - fan_social_id?: string - id?: string - latest_engagement?: string | null - latest_engagement_id?: string | null - updated_at?: string - } + artist_social_id: string; + created_at: string; + fan_social_id: string; + id: string; + latest_engagement: string | null; + latest_engagement_id: string | null; + updated_at: string; + }; + Insert: { + artist_social_id: string; + created_at?: string; + fan_social_id: string; + id?: string; + latest_engagement?: string | null; + latest_engagement_id?: string | null; + updated_at?: string; + }; + Update: { + artist_social_id?: string; + created_at?: string; + fan_social_id?: string; + id?: string; + latest_engagement?: string | null; + latest_engagement_id?: string | null; + updated_at?: string; + }; Relationships: [ { - foreignKeyName: "social_fans_artist_social_id_fkey" - columns: ["artist_social_id"] - isOneToOne: false - referencedRelation: "socials" - referencedColumns: ["id"] + foreignKeyName: "social_fans_artist_social_id_fkey"; + columns: ["artist_social_id"]; + isOneToOne: false; + referencedRelation: "socials"; + referencedColumns: ["id"]; }, { - foreignKeyName: "social_fans_fan_social_id_fkey" - columns: ["fan_social_id"] - isOneToOne: false - referencedRelation: "socials" - referencedColumns: ["id"] + foreignKeyName: "social_fans_fan_social_id_fkey"; + columns: ["fan_social_id"]; + isOneToOne: false; + referencedRelation: "socials"; + referencedColumns: ["id"]; }, { - foreignKeyName: "social_fans_latest_engagement_id_fkey" - columns: ["latest_engagement_id"] - isOneToOne: false - referencedRelation: "post_comments" - referencedColumns: ["id"] + foreignKeyName: "social_fans_latest_engagement_id_fkey"; + columns: ["latest_engagement_id"]; + isOneToOne: false; + referencedRelation: "post_comments"; + referencedColumns: ["id"]; }, - ] - } + ]; + }; social_posts: { Row: { - id: string - post_id: string | null - social_id: string | null - updated_at: string | null - } - Insert: { - id?: string - post_id?: string | null - social_id?: string | null - updated_at?: string | null - } - Update: { - id?: string - post_id?: string | null - social_id?: string | null - updated_at?: string | null - } + id: string; + post_id: string | null; + social_id: string | null; + updated_at: string | null; + }; + Insert: { + id?: string; + post_id?: string | null; + social_id?: string | null; + updated_at?: string | null; + }; + Update: { + id?: string; + post_id?: string | null; + social_id?: string | null; + updated_at?: string | null; + }; Relationships: [ { - foreignKeyName: "social_posts_post_id_fkey" - columns: ["post_id"] - isOneToOne: false - referencedRelation: "posts" - referencedColumns: ["id"] + foreignKeyName: "social_posts_post_id_fkey"; + columns: ["post_id"]; + isOneToOne: false; + referencedRelation: "posts"; + referencedColumns: ["id"]; }, { - foreignKeyName: "social_posts_social_id_fkey" - columns: ["social_id"] - isOneToOne: false - referencedRelation: "socials" - referencedColumns: ["id"] + foreignKeyName: "social_posts_social_id_fkey"; + columns: ["social_id"]; + isOneToOne: false; + referencedRelation: "socials"; + referencedColumns: ["id"]; }, - ] - } + ]; + }; social_spotify_albums: { Row: { - album_id: string | null - id: string - social_id: string | null - updated_at: string - } - Insert: { - album_id?: string | null - id?: string - social_id?: string | null - updated_at?: string - } - Update: { - album_id?: string | null - id?: string - social_id?: string | null - updated_at?: string - } + album_id: string | null; + id: string; + social_id: string | null; + updated_at: string; + }; + Insert: { + album_id?: string | null; + id?: string; + social_id?: string | null; + updated_at?: string; + }; + Update: { + album_id?: string | null; + id?: string; + social_id?: string | null; + updated_at?: string; + }; Relationships: [ { - foreignKeyName: "social_spotify_albums_album_id_fkey" - columns: ["album_id"] - isOneToOne: false - referencedRelation: "spotify_albums" - referencedColumns: ["id"] + foreignKeyName: "social_spotify_albums_album_id_fkey"; + columns: ["album_id"]; + isOneToOne: false; + referencedRelation: "spotify_albums"; + referencedColumns: ["id"]; }, { - foreignKeyName: "social_spotify_albums_social_id_fkey" - columns: ["social_id"] - isOneToOne: false - referencedRelation: "socials" - referencedColumns: ["id"] + foreignKeyName: "social_spotify_albums_social_id_fkey"; + columns: ["social_id"]; + isOneToOne: false; + referencedRelation: "socials"; + referencedColumns: ["id"]; }, - ] - } + ]; + }; social_spotify_tracks: { Row: { - id: string - social_id: string - track_id: string | null - updated_at: string | null - } - Insert: { - id?: string - social_id?: string - track_id?: string | null - updated_at?: string | null - } - Update: { - id?: string - social_id?: string - track_id?: string | null - updated_at?: string | null - } + id: string; + social_id: string; + track_id: string | null; + updated_at: string | null; + }; + Insert: { + id?: string; + social_id?: string; + track_id?: string | null; + updated_at?: string | null; + }; + Update: { + id?: string; + social_id?: string; + track_id?: string | null; + updated_at?: string | null; + }; Relationships: [ { - foreignKeyName: "social_spotify_tracks_social_id_fkey" - columns: ["social_id"] - isOneToOne: false - referencedRelation: "socials" - referencedColumns: ["id"] + foreignKeyName: "social_spotify_tracks_social_id_fkey"; + columns: ["social_id"]; + isOneToOne: false; + referencedRelation: "socials"; + referencedColumns: ["id"]; }, { - foreignKeyName: "social_spotify_tracks_track_id_fkey" - columns: ["track_id"] - isOneToOne: false - referencedRelation: "spotify_tracks" - referencedColumns: ["id"] + foreignKeyName: "social_spotify_tracks_track_id_fkey"; + columns: ["track_id"]; + isOneToOne: false; + referencedRelation: "spotify_tracks"; + referencedColumns: ["id"]; }, - ] - } + ]; + }; socials: { Row: { - avatar: string | null - bio: string | null - followerCount: number | null - followingCount: number | null - id: string - profile_url: string - region: string | null - updated_at: string - username: string - } - Insert: { - avatar?: string | null - bio?: string | null - followerCount?: number | null - followingCount?: number | null - id?: string - profile_url: string - region?: string | null - updated_at?: string - username: string - } - Update: { - avatar?: string | null - bio?: string | null - followerCount?: number | null - followingCount?: number | null - id?: string - profile_url?: string - region?: string | null - updated_at?: string - username?: string - } - Relationships: [] - } + avatar: string | null; + bio: string | null; + followerCount: number | null; + followingCount: number | null; + id: string; + profile_url: string; + region: string | null; + updated_at: string; + username: string; + }; + Insert: { + avatar?: string | null; + bio?: string | null; + followerCount?: number | null; + followingCount?: number | null; + id?: string; + profile_url: string; + region?: string | null; + updated_at?: string; + username: string; + }; + Update: { + avatar?: string | null; + bio?: string | null; + followerCount?: number | null; + followingCount?: number | null; + id?: string; + profile_url?: string; + region?: string | null; + updated_at?: string; + username?: string; + }; + Relationships: []; + }; song_artists: { Row: { - artist: string - created_at: string - id: string - song: string - updated_at: string - } - Insert: { - artist: string - created_at?: string - id?: string - song: string - updated_at?: string - } - Update: { - artist?: string - created_at?: string - id?: string - song?: string - updated_at?: string - } + artist: string; + created_at: string; + id: string; + song: string; + updated_at: string; + }; + Insert: { + artist: string; + created_at?: string; + id?: string; + song: string; + updated_at?: string; + }; + Update: { + artist?: string; + created_at?: string; + id?: string; + song?: string; + updated_at?: string; + }; Relationships: [ { - foreignKeyName: "song_artists_artist_fkey" - columns: ["artist"] - isOneToOne: false - referencedRelation: "accounts" - referencedColumns: ["id"] + foreignKeyName: "song_artists_artist_fkey"; + columns: ["artist"]; + isOneToOne: false; + referencedRelation: "accounts"; + referencedColumns: ["id"]; }, { - foreignKeyName: "song_artists_song_fkey" - columns: ["song"] - isOneToOne: false - referencedRelation: "songs" - referencedColumns: ["isrc"] + foreignKeyName: "song_artists_song_fkey"; + columns: ["song"]; + isOneToOne: false; + referencedRelation: "songs"; + referencedColumns: ["isrc"]; }, - ] - } + ]; + }; songs: { Row: { - album: string | null - isrc: string - name: string | null - notes: string | null - updated_at: string - } - Insert: { - album?: string | null - isrc: string - name?: string | null - notes?: string | null - updated_at?: string - } - Update: { - album?: string | null - isrc?: string - name?: string | null - notes?: string | null - updated_at?: string - } - Relationships: [] - } + album: string | null; + isrc: string; + name: string | null; + notes: string | null; + updated_at: string; + }; + Insert: { + album?: string | null; + isrc: string; + name?: string | null; + notes?: string | null; + updated_at?: string; + }; + Update: { + album?: string | null; + isrc?: string; + name?: string | null; + notes?: string | null; + updated_at?: string; + }; + Relationships: []; + }; spotify: { Row: { - clientId: string | null - country: string | null - display_name: string | null - email: string | null - "explicit_content.filter_enabled": string | null - "explicit_content.filter_locked": string | null - "external_urls.spotify": Json | null - fanId: string | null - "fanId.country": string | null - "fanId.display_name": string | null - "fanId.email": string | null - "fanId.explicit_content.filter_enabled": string | null - "fanId.explicit_content.filter_locked": string | null - "fanId.external_urls.spotify": string | null - "fanId.followers.total": string | null - "fanId.href": string | null - "fanId.id": string | null - "fanId.images": string | null - "fanId.isNewFan": string | null - "fanId.playlist": string | null - "fanId.presavedData.clientId": string | null - "fanId.presavedData.country": string | null - "fanId.presavedData.display_name": string | null - "fanId.presavedData.email": string | null - "fanId.presavedData.explicit_content.filter_enabled": string | null - "fanId.presavedData.explicit_content.filter_locked": string | null - "fanId.presavedData.external_urls.spotify": string | null - "fanId.presavedData.followers.total": string | null - "fanId.presavedData.href": string | null - "fanId.presavedData.id": string | null - "fanId.presavedData.images": string | null - "fanId.presavedData.playlist": string | null - "fanId.presavedData.product": string | null - "fanId.presavedData.recentlyPlayed": string | null - "fanId.presavedData.timestamp": string | null - "fanId.presavedData.type": string | null - "fanId.presavedData.uri": string | null - "fanId.product": string | null - "fanId.timestamp": string | null - "fanId.type": string | null - "fanId.uri": string | null - "followers.total": Json | null - game: string | null - href: string | null - id: string | null - images: Json | null - playlist: Json | null - product: string | null - syncId: string | null - timestamp: string | null - type: string | null - uri: string | null - } - Insert: { - clientId?: string | null - country?: string | null - display_name?: string | null - email?: string | null - "explicit_content.filter_enabled"?: string | null - "explicit_content.filter_locked"?: string | null - "external_urls.spotify"?: Json | null - fanId?: string | null - "fanId.country"?: string | null - "fanId.display_name"?: string | null - "fanId.email"?: string | null - "fanId.explicit_content.filter_enabled"?: string | null - "fanId.explicit_content.filter_locked"?: string | null - "fanId.external_urls.spotify"?: string | null - "fanId.followers.total"?: string | null - "fanId.href"?: string | null - "fanId.id"?: string | null - "fanId.images"?: string | null - "fanId.isNewFan"?: string | null - "fanId.playlist"?: string | null - "fanId.presavedData.clientId"?: string | null - "fanId.presavedData.country"?: string | null - "fanId.presavedData.display_name"?: string | null - "fanId.presavedData.email"?: string | null - "fanId.presavedData.explicit_content.filter_enabled"?: string | null - "fanId.presavedData.explicit_content.filter_locked"?: string | null - "fanId.presavedData.external_urls.spotify"?: string | null - "fanId.presavedData.followers.total"?: string | null - "fanId.presavedData.href"?: string | null - "fanId.presavedData.id"?: string | null - "fanId.presavedData.images"?: string | null - "fanId.presavedData.playlist"?: string | null - "fanId.presavedData.product"?: string | null - "fanId.presavedData.recentlyPlayed"?: string | null - "fanId.presavedData.timestamp"?: string | null - "fanId.presavedData.type"?: string | null - "fanId.presavedData.uri"?: string | null - "fanId.product"?: string | null - "fanId.timestamp"?: string | null - "fanId.type"?: string | null - "fanId.uri"?: string | null - "followers.total"?: Json | null - game?: string | null - href?: string | null - id?: string | null - images?: Json | null - playlist?: Json | null - product?: string | null - syncId?: string | null - timestamp?: string | null - type?: string | null - uri?: string | null - } - Update: { - clientId?: string | null - country?: string | null - display_name?: string | null - email?: string | null - "explicit_content.filter_enabled"?: string | null - "explicit_content.filter_locked"?: string | null - "external_urls.spotify"?: Json | null - fanId?: string | null - "fanId.country"?: string | null - "fanId.display_name"?: string | null - "fanId.email"?: string | null - "fanId.explicit_content.filter_enabled"?: string | null - "fanId.explicit_content.filter_locked"?: string | null - "fanId.external_urls.spotify"?: string | null - "fanId.followers.total"?: string | null - "fanId.href"?: string | null - "fanId.id"?: string | null - "fanId.images"?: string | null - "fanId.isNewFan"?: string | null - "fanId.playlist"?: string | null - "fanId.presavedData.clientId"?: string | null - "fanId.presavedData.country"?: string | null - "fanId.presavedData.display_name"?: string | null - "fanId.presavedData.email"?: string | null - "fanId.presavedData.explicit_content.filter_enabled"?: string | null - "fanId.presavedData.explicit_content.filter_locked"?: string | null - "fanId.presavedData.external_urls.spotify"?: string | null - "fanId.presavedData.followers.total"?: string | null - "fanId.presavedData.href"?: string | null - "fanId.presavedData.id"?: string | null - "fanId.presavedData.images"?: string | null - "fanId.presavedData.playlist"?: string | null - "fanId.presavedData.product"?: string | null - "fanId.presavedData.recentlyPlayed"?: string | null - "fanId.presavedData.timestamp"?: string | null - "fanId.presavedData.type"?: string | null - "fanId.presavedData.uri"?: string | null - "fanId.product"?: string | null - "fanId.timestamp"?: string | null - "fanId.type"?: string | null - "fanId.uri"?: string | null - "followers.total"?: Json | null - game?: string | null - href?: string | null - id?: string | null - images?: Json | null - playlist?: Json | null - product?: string | null - syncId?: string | null - timestamp?: string | null - type?: string | null - uri?: string | null - } - Relationships: [] - } + clientId: string | null; + country: string | null; + display_name: string | null; + email: string | null; + "explicit_content.filter_enabled": string | null; + "explicit_content.filter_locked": string | null; + "external_urls.spotify": Json | null; + fanId: string | null; + "fanId.country": string | null; + "fanId.display_name": string | null; + "fanId.email": string | null; + "fanId.explicit_content.filter_enabled": string | null; + "fanId.explicit_content.filter_locked": string | null; + "fanId.external_urls.spotify": string | null; + "fanId.followers.total": string | null; + "fanId.href": string | null; + "fanId.id": string | null; + "fanId.images": string | null; + "fanId.isNewFan": string | null; + "fanId.playlist": string | null; + "fanId.presavedData.clientId": string | null; + "fanId.presavedData.country": string | null; + "fanId.presavedData.display_name": string | null; + "fanId.presavedData.email": string | null; + "fanId.presavedData.explicit_content.filter_enabled": string | null; + "fanId.presavedData.explicit_content.filter_locked": string | null; + "fanId.presavedData.external_urls.spotify": string | null; + "fanId.presavedData.followers.total": string | null; + "fanId.presavedData.href": string | null; + "fanId.presavedData.id": string | null; + "fanId.presavedData.images": string | null; + "fanId.presavedData.playlist": string | null; + "fanId.presavedData.product": string | null; + "fanId.presavedData.recentlyPlayed": string | null; + "fanId.presavedData.timestamp": string | null; + "fanId.presavedData.type": string | null; + "fanId.presavedData.uri": string | null; + "fanId.product": string | null; + "fanId.timestamp": string | null; + "fanId.type": string | null; + "fanId.uri": string | null; + "followers.total": Json | null; + game: string | null; + href: string | null; + id: string | null; + images: Json | null; + playlist: Json | null; + product: string | null; + syncId: string | null; + timestamp: string | null; + type: string | null; + uri: string | null; + }; + Insert: { + clientId?: string | null; + country?: string | null; + display_name?: string | null; + email?: string | null; + "explicit_content.filter_enabled"?: string | null; + "explicit_content.filter_locked"?: string | null; + "external_urls.spotify"?: Json | null; + fanId?: string | null; + "fanId.country"?: string | null; + "fanId.display_name"?: string | null; + "fanId.email"?: string | null; + "fanId.explicit_content.filter_enabled"?: string | null; + "fanId.explicit_content.filter_locked"?: string | null; + "fanId.external_urls.spotify"?: string | null; + "fanId.followers.total"?: string | null; + "fanId.href"?: string | null; + "fanId.id"?: string | null; + "fanId.images"?: string | null; + "fanId.isNewFan"?: string | null; + "fanId.playlist"?: string | null; + "fanId.presavedData.clientId"?: string | null; + "fanId.presavedData.country"?: string | null; + "fanId.presavedData.display_name"?: string | null; + "fanId.presavedData.email"?: string | null; + "fanId.presavedData.explicit_content.filter_enabled"?: string | null; + "fanId.presavedData.explicit_content.filter_locked"?: string | null; + "fanId.presavedData.external_urls.spotify"?: string | null; + "fanId.presavedData.followers.total"?: string | null; + "fanId.presavedData.href"?: string | null; + "fanId.presavedData.id"?: string | null; + "fanId.presavedData.images"?: string | null; + "fanId.presavedData.playlist"?: string | null; + "fanId.presavedData.product"?: string | null; + "fanId.presavedData.recentlyPlayed"?: string | null; + "fanId.presavedData.timestamp"?: string | null; + "fanId.presavedData.type"?: string | null; + "fanId.presavedData.uri"?: string | null; + "fanId.product"?: string | null; + "fanId.timestamp"?: string | null; + "fanId.type"?: string | null; + "fanId.uri"?: string | null; + "followers.total"?: Json | null; + game?: string | null; + href?: string | null; + id?: string | null; + images?: Json | null; + playlist?: Json | null; + product?: string | null; + syncId?: string | null; + timestamp?: string | null; + type?: string | null; + uri?: string | null; + }; + Update: { + clientId?: string | null; + country?: string | null; + display_name?: string | null; + email?: string | null; + "explicit_content.filter_enabled"?: string | null; + "explicit_content.filter_locked"?: string | null; + "external_urls.spotify"?: Json | null; + fanId?: string | null; + "fanId.country"?: string | null; + "fanId.display_name"?: string | null; + "fanId.email"?: string | null; + "fanId.explicit_content.filter_enabled"?: string | null; + "fanId.explicit_content.filter_locked"?: string | null; + "fanId.external_urls.spotify"?: string | null; + "fanId.followers.total"?: string | null; + "fanId.href"?: string | null; + "fanId.id"?: string | null; + "fanId.images"?: string | null; + "fanId.isNewFan"?: string | null; + "fanId.playlist"?: string | null; + "fanId.presavedData.clientId"?: string | null; + "fanId.presavedData.country"?: string | null; + "fanId.presavedData.display_name"?: string | null; + "fanId.presavedData.email"?: string | null; + "fanId.presavedData.explicit_content.filter_enabled"?: string | null; + "fanId.presavedData.explicit_content.filter_locked"?: string | null; + "fanId.presavedData.external_urls.spotify"?: string | null; + "fanId.presavedData.followers.total"?: string | null; + "fanId.presavedData.href"?: string | null; + "fanId.presavedData.id"?: string | null; + "fanId.presavedData.images"?: string | null; + "fanId.presavedData.playlist"?: string | null; + "fanId.presavedData.product"?: string | null; + "fanId.presavedData.recentlyPlayed"?: string | null; + "fanId.presavedData.timestamp"?: string | null; + "fanId.presavedData.type"?: string | null; + "fanId.presavedData.uri"?: string | null; + "fanId.product"?: string | null; + "fanId.timestamp"?: string | null; + "fanId.type"?: string | null; + "fanId.uri"?: string | null; + "followers.total"?: Json | null; + game?: string | null; + href?: string | null; + id?: string | null; + images?: Json | null; + playlist?: Json | null; + product?: string | null; + syncId?: string | null; + timestamp?: string | null; + type?: string | null; + uri?: string | null; + }; + Relationships: []; + }; spotify_albums: { Row: { - id: string - name: string | null - release_date: string | null - updated_at: string - uri: string - } - Insert: { - id?: string - name?: string | null - release_date?: string | null - updated_at?: string - uri: string - } - Update: { - id?: string - name?: string | null - release_date?: string | null - updated_at?: string - uri?: string - } - Relationships: [] - } + id: string; + name: string | null; + release_date: string | null; + updated_at: string; + uri: string; + }; + Insert: { + id?: string; + name?: string | null; + release_date?: string | null; + updated_at?: string; + uri: string; + }; + Update: { + id?: string; + name?: string | null; + release_date?: string | null; + updated_at?: string; + uri?: string; + }; + Relationships: []; + }; spotify_analytics_albums: { Row: { - analysis_id: string | null - artist_name: string | null - created_at: string - id: string - name: string | null - release_date: number | null - uri: string | null - } - Insert: { - analysis_id?: string | null - artist_name?: string | null - created_at?: string - id?: string - name?: string | null - release_date?: number | null - uri?: string | null - } - Update: { - analysis_id?: string | null - artist_name?: string | null - created_at?: string - id?: string - name?: string | null - release_date?: number | null - uri?: string | null - } + analysis_id: string | null; + artist_name: string | null; + created_at: string; + id: string; + name: string | null; + release_date: number | null; + uri: string | null; + }; + Insert: { + analysis_id?: string | null; + artist_name?: string | null; + created_at?: string; + id?: string; + name?: string | null; + release_date?: number | null; + uri?: string | null; + }; + Update: { + analysis_id?: string | null; + artist_name?: string | null; + created_at?: string; + id?: string; + name?: string | null; + release_date?: number | null; + uri?: string | null; + }; Relationships: [ { - foreignKeyName: "spotify_analytics_albums_analysis_id_fkey" - columns: ["analysis_id"] - isOneToOne: false - referencedRelation: "funnel_analytics" - referencedColumns: ["id"] + foreignKeyName: "spotify_analytics_albums_analysis_id_fkey"; + columns: ["analysis_id"]; + isOneToOne: false; + referencedRelation: "funnel_analytics"; + referencedColumns: ["id"]; }, - ] - } + ]; + }; spotify_analytics_tracks: { Row: { - analysis_id: string | null - artist_name: string | null - created_at: string - id: string - name: string | null - popularity: number | null - uri: string | null - } - Insert: { - analysis_id?: string | null - artist_name?: string | null - created_at?: string - id?: string - name?: string | null - popularity?: number | null - uri?: string | null - } - Update: { - analysis_id?: string | null - artist_name?: string | null - created_at?: string - id?: string - name?: string | null - popularity?: number | null - uri?: string | null - } + analysis_id: string | null; + artist_name: string | null; + created_at: string; + id: string; + name: string | null; + popularity: number | null; + uri: string | null; + }; + Insert: { + analysis_id?: string | null; + artist_name?: string | null; + created_at?: string; + id?: string; + name?: string | null; + popularity?: number | null; + uri?: string | null; + }; + Update: { + analysis_id?: string | null; + artist_name?: string | null; + created_at?: string; + id?: string; + name?: string | null; + popularity?: number | null; + uri?: string | null; + }; Relationships: [ { - foreignKeyName: "spotify_analytics_tracks_analysis_id_fkey" - columns: ["analysis_id"] - isOneToOne: false - referencedRelation: "funnel_analytics" - referencedColumns: ["id"] + foreignKeyName: "spotify_analytics_tracks_analysis_id_fkey"; + columns: ["analysis_id"]; + isOneToOne: false; + referencedRelation: "funnel_analytics"; + referencedColumns: ["id"]; }, - ] - } + ]; + }; spotify_login_button_clicked: { Row: { - campaignId: string | null - clientId: string | null - fanId: string | null - game: string | null - id: string | null - timestamp: number | null - } - Insert: { - campaignId?: string | null - clientId?: string | null - fanId?: string | null - game?: string | null - id?: string | null - timestamp?: number | null - } - Update: { - campaignId?: string | null - clientId?: string | null - fanId?: string | null - game?: string | null - id?: string | null - timestamp?: number | null - } + campaignId: string | null; + clientId: string | null; + fanId: string | null; + game: string | null; + id: string | null; + timestamp: number | null; + }; + Insert: { + campaignId?: string | null; + clientId?: string | null; + fanId?: string | null; + game?: string | null; + id?: string | null; + timestamp?: number | null; + }; + Update: { + campaignId?: string | null; + clientId?: string | null; + fanId?: string | null; + game?: string | null; + id?: string | null; + timestamp?: number | null; + }; Relationships: [ { - foreignKeyName: "spotify_login_button_clicked_campaignId_fkey" - columns: ["campaignId"] - isOneToOne: false - referencedRelation: "campaigns" - referencedColumns: ["id"] + foreignKeyName: "spotify_login_button_clicked_campaignId_fkey"; + columns: ["campaignId"]; + isOneToOne: false; + referencedRelation: "campaigns"; + referencedColumns: ["id"]; }, - ] - } + ]; + }; spotify_play_button_clicked: { Row: { - campaignId: string | null - clientId: string | null - fanId: string | null - game: string | null - id: string - isPremium: boolean | null - timestamp: number | null - } - Insert: { - campaignId?: string | null - clientId?: string | null - fanId?: string | null - game?: string | null - id?: string - isPremium?: boolean | null - timestamp?: number | null - } - Update: { - campaignId?: string | null - clientId?: string | null - fanId?: string | null - game?: string | null - id?: string - isPremium?: boolean | null - timestamp?: number | null - } + campaignId: string | null; + clientId: string | null; + fanId: string | null; + game: string | null; + id: string; + isPremium: boolean | null; + timestamp: number | null; + }; + Insert: { + campaignId?: string | null; + clientId?: string | null; + fanId?: string | null; + game?: string | null; + id?: string; + isPremium?: boolean | null; + timestamp?: number | null; + }; + Update: { + campaignId?: string | null; + clientId?: string | null; + fanId?: string | null; + game?: string | null; + id?: string; + isPremium?: boolean | null; + timestamp?: number | null; + }; Relationships: [ { - foreignKeyName: "spotify_play_button_clicked_campaignId_fkey" - columns: ["campaignId"] - isOneToOne: false - referencedRelation: "campaigns" - referencedColumns: ["id"] + foreignKeyName: "spotify_play_button_clicked_campaignId_fkey"; + columns: ["campaignId"]; + isOneToOne: false; + referencedRelation: "campaigns"; + referencedColumns: ["id"]; }, { - foreignKeyName: "spotify_play_button_clicked_fanId_fkey" - columns: ["fanId"] - isOneToOne: false - referencedRelation: "fans" - referencedColumns: ["id"] + foreignKeyName: "spotify_play_button_clicked_fanId_fkey"; + columns: ["fanId"]; + isOneToOne: false; + referencedRelation: "fans"; + referencedColumns: ["id"]; }, - ] - } + ]; + }; spotify_tracks: { Row: { - id: string - name: string | null - popularity: number | null - updated_at: string - uri: string - } - Insert: { - id?: string - name?: string | null - popularity?: number | null - updated_at?: string - uri: string - } - Update: { - id?: string - name?: string | null - popularity?: number | null - updated_at?: string - uri?: string - } - Relationships: [] - } + id: string; + name: string | null; + popularity: number | null; + updated_at: string; + uri: string; + }; + Insert: { + id?: string; + name?: string | null; + popularity?: number | null; + updated_at?: string; + uri: string; + }; + Update: { + id?: string; + name?: string | null; + popularity?: number | null; + updated_at?: string; + uri?: string; + }; + Relationships: []; + }; subscription_items: { Row: { - created_at: string - id: string - interval: string - interval_count: number - price_amount: number | null - product_id: string - quantity: number - subscription_id: string - type: Database["public"]["Enums"]["subscription_item_type"] - updated_at: string - variant_id: string - } - Insert: { - created_at?: string - id: string - interval: string - interval_count: number - price_amount?: number | null - product_id: string - quantity?: number - subscription_id: string - type: Database["public"]["Enums"]["subscription_item_type"] - updated_at?: string - variant_id: string - } - Update: { - created_at?: string - id?: string - interval?: string - interval_count?: number - price_amount?: number | null - product_id?: string - quantity?: number - subscription_id?: string - type?: Database["public"]["Enums"]["subscription_item_type"] - updated_at?: string - variant_id?: string - } + created_at: string; + id: string; + interval: string; + interval_count: number; + price_amount: number | null; + product_id: string; + quantity: number; + subscription_id: string; + type: Database["public"]["Enums"]["subscription_item_type"]; + updated_at: string; + variant_id: string; + }; + Insert: { + created_at?: string; + id: string; + interval: string; + interval_count: number; + price_amount?: number | null; + product_id: string; + quantity?: number; + subscription_id: string; + type: Database["public"]["Enums"]["subscription_item_type"]; + updated_at?: string; + variant_id: string; + }; + Update: { + created_at?: string; + id?: string; + interval?: string; + interval_count?: number; + price_amount?: number | null; + product_id?: string; + quantity?: number; + subscription_id?: string; + type?: Database["public"]["Enums"]["subscription_item_type"]; + updated_at?: string; + variant_id?: string; + }; Relationships: [ { - foreignKeyName: "subscription_items_subscription_id_fkey" - columns: ["subscription_id"] - isOneToOne: false - referencedRelation: "subscriptions" - referencedColumns: ["id"] + foreignKeyName: "subscription_items_subscription_id_fkey"; + columns: ["subscription_id"]; + isOneToOne: false; + referencedRelation: "subscriptions"; + referencedColumns: ["id"]; }, - ] - } + ]; + }; subscriptions: { Row: { - account_id: string - active: boolean - billing_customer_id: number - billing_provider: Database["public"]["Enums"]["billing_provider"] - cancel_at_period_end: boolean - created_at: string - currency: string - id: string - period_ends_at: string - period_starts_at: string - status: Database["public"]["Enums"]["subscription_status"] - trial_ends_at: string | null - trial_starts_at: string | null - updated_at: string - } - Insert: { - account_id: string - active: boolean - billing_customer_id: number - billing_provider: Database["public"]["Enums"]["billing_provider"] - cancel_at_period_end: boolean - created_at?: string - currency: string - id: string - period_ends_at: string - period_starts_at: string - status: Database["public"]["Enums"]["subscription_status"] - trial_ends_at?: string | null - trial_starts_at?: string | null - updated_at?: string - } - Update: { - account_id?: string - active?: boolean - billing_customer_id?: number - billing_provider?: Database["public"]["Enums"]["billing_provider"] - cancel_at_period_end?: boolean - created_at?: string - currency?: string - id?: string - period_ends_at?: string - period_starts_at?: string - status?: Database["public"]["Enums"]["subscription_status"] - trial_ends_at?: string | null - trial_starts_at?: string | null - updated_at?: string - } + account_id: string; + active: boolean; + billing_customer_id: number; + billing_provider: Database["public"]["Enums"]["billing_provider"]; + cancel_at_period_end: boolean; + created_at: string; + currency: string; + id: string; + period_ends_at: string; + period_starts_at: string; + status: Database["public"]["Enums"]["subscription_status"]; + trial_ends_at: string | null; + trial_starts_at: string | null; + updated_at: string; + }; + Insert: { + account_id: string; + active: boolean; + billing_customer_id: number; + billing_provider: Database["public"]["Enums"]["billing_provider"]; + cancel_at_period_end: boolean; + created_at?: string; + currency: string; + id: string; + period_ends_at: string; + period_starts_at: string; + status: Database["public"]["Enums"]["subscription_status"]; + trial_ends_at?: string | null; + trial_starts_at?: string | null; + updated_at?: string; + }; + Update: { + account_id?: string; + active?: boolean; + billing_customer_id?: number; + billing_provider?: Database["public"]["Enums"]["billing_provider"]; + cancel_at_period_end?: boolean; + created_at?: string; + currency?: string; + id?: string; + period_ends_at?: string; + period_starts_at?: string; + status?: Database["public"]["Enums"]["subscription_status"]; + trial_ends_at?: string | null; + trial_starts_at?: string | null; + updated_at?: string; + }; Relationships: [ { - foreignKeyName: "subscriptions_billing_customer_id_fkey" - columns: ["billing_customer_id"] - isOneToOne: false - referencedRelation: "billing_customers" - referencedColumns: ["id"] + foreignKeyName: "subscriptions_billing_customer_id_fkey"; + columns: ["billing_customer_id"]; + isOneToOne: false; + referencedRelation: "billing_customers"; + referencedColumns: ["id"]; }, - ] - } + ]; + }; tasks: { Row: { - account_id: string - created_at: string - description: string | null - done: boolean - id: string - title: string - updated_at: string - } - Insert: { - account_id: string - created_at?: string - description?: string | null - done?: boolean - id?: string - title: string - updated_at?: string - } - Update: { - account_id?: string - created_at?: string - description?: string | null - done?: boolean - id?: string - title?: string - updated_at?: string - } - Relationships: [] - } + account_id: string; + created_at: string; + description: string | null; + done: boolean; + id: string; + title: string; + updated_at: string; + }; + Insert: { + account_id: string; + created_at?: string; + description?: string | null; + done?: boolean; + id?: string; + title: string; + updated_at?: string; + }; + Update: { + account_id?: string; + created_at?: string; + description?: string | null; + done?: boolean; + id?: string; + title?: string; + updated_at?: string; + }; + Relationships: []; + }; test_emails: { Row: { - created_at: string - email: string | null - id: number - } - Insert: { - created_at?: string - email?: string | null - id?: number - } - Update: { - created_at?: string - email?: string | null - id?: number - } - Relationships: [] - } + created_at: string; + email: string | null; + id: number; + }; + Insert: { + created_at?: string; + email?: string | null; + id?: number; + }; + Update: { + created_at?: string; + email?: string | null; + id?: number; + }; + Relationships: []; + }; youtube_tokens: { Row: { - access_token: string - artist_account_id: string - created_at: string - expires_at: string - id: string - refresh_token: string | null - updated_at: string - } - Insert: { - access_token: string - artist_account_id: string - created_at?: string - expires_at: string - id?: string - refresh_token?: string | null - updated_at?: string - } - Update: { - access_token?: string - artist_account_id?: string - created_at?: string - expires_at?: string - id?: string - refresh_token?: string | null - updated_at?: string - } + access_token: string; + artist_account_id: string; + created_at: string; + expires_at: string; + id: string; + refresh_token: string | null; + updated_at: string; + }; + Insert: { + access_token: string; + artist_account_id: string; + created_at?: string; + expires_at: string; + id?: string; + refresh_token?: string | null; + updated_at?: string; + }; + Update: { + access_token?: string; + artist_account_id?: string; + created_at?: string; + expires_at?: string; + id?: string; + refresh_token?: string | null; + updated_at?: string; + }; Relationships: [ { - foreignKeyName: "youtube_tokens_artist_account_id_fkey" - columns: ["artist_account_id"] - isOneToOne: true - referencedRelation: "accounts" - referencedColumns: ["id"] + foreignKeyName: "youtube_tokens_artist_account_id_fkey"; + columns: ["artist_account_id"]; + isOneToOne: true; + referencedRelation: "accounts"; + referencedColumns: ["id"]; }, - ] - } - } + ]; + }; + }; Views: { - [_ in never]: never - } + [_ in never]: never; + }; Functions: { accept_invitation: { - Args: { token: string; user_id: string } - Returns: string - } + Args: { token: string; user_id: string }; + Returns: string; + }; add_invitations_to_account: { Args: { - account_slug: string - invitations: Database["public"]["CompositeTypes"]["invitation"][] - } - Returns: Database["public"]["Tables"]["invitations"]["Row"][] - } + account_slug: string; + invitations: Database["public"]["CompositeTypes"]["invitation"][]; + }; + Returns: Database["public"]["Tables"]["invitations"]["Row"][]; + }; can_action_account_member: { - Args: { target_team_account_id: string; target_user_id: string } - Returns: boolean - } + Args: { target_team_account_id: string; target_user_id: string }; + Returns: boolean; + }; count_reports_by_day: { - Args: { end_date: string; start_date: string } + Args: { end_date: string; start_date: string }; Returns: { - count: number - date_key: string - }[] - } + count: number; + date_key: string; + }[]; + }; count_reports_by_month: { - Args: { end_date: string; start_date: string } + Args: { end_date: string; start_date: string }; Returns: { - count: number - date_key: string - }[] - } + count: number; + date_key: string; + }[]; + }; count_reports_by_week: { - Args: { end_date: string; start_date: string } + Args: { end_date: string; start_date: string }; Returns: { - count: number - date_key: string - }[] - } + count: number; + date_key: string; + }[]; + }; create_invitation: { - Args: { account_id: string; email: string; role: string } + Args: { account_id: string; email: string; role: string }; Returns: { - account_id: string - created_at: string - email: string - expires_at: string - id: number - invite_token: string - invited_by: string - role: string - updated_at: string - } + account_id: string; + created_at: string; + email: string; + expires_at: string; + id: number; + invite_token: string; + invited_by: string; + role: string; + updated_at: string; + }; SetofOptions: { - from: "*" - to: "invitations" - isOneToOne: true - isSetofReturn: false - } - } + from: "*"; + to: "invitations"; + isOneToOne: true; + isSetofReturn: false; + }; + }; deduct_credits: { - Args: { account_id: string; amount: number } - Returns: undefined - } - extract_domain: { Args: { email: string }; Returns: string } + Args: { account_id: string; amount: number }; + Returns: undefined; + }; + extract_domain: { Args: { email: string }; Returns: string }; get_account_invitations: { - Args: { account_slug: string } + Args: { account_slug: string }; Returns: { - account_id: string - created_at: string - email: string - expires_at: string - id: number - invited_by: string - inviter_email: string - inviter_name: string - role: string - updated_at: string - }[] - } + account_id: string; + created_at: string; + email: string; + expires_at: string; + id: number; + invited_by: string; + inviter_email: string; + inviter_name: string; + role: string; + updated_at: string; + }[]; + }; get_account_members: { - Args: { account_slug: string } + Args: { account_slug: string }; Returns: { - account_id: string - created_at: string - email: string - id: string - name: string - picture_url: string - primary_owner_user_id: string - role: string - role_hierarchy_level: number - updated_at: string - user_id: string - }[] - } + account_id: string; + created_at: string; + email: string; + id: string; + name: string; + picture_url: string; + primary_owner_user_id: string; + role: string; + role_hierarchy_level: number; + updated_at: string; + user_id: string; + }[]; + }; get_campaign: | { Args: { clientid: string }; Returns: Json } | { - Args: { artistid: string; campaignid: string; email: string } - Returns: Json - } + Args: { artistid: string; campaignid: string; email: string }; + Returns: Json; + }; get_campaign_fans: { - Args: { artistid: string; email: string } - Returns: Json - } - get_config: { Args: never; Returns: Json } + Args: { artistid: string; email: string }; + Returns: Json; + }; + get_config: { Args: never; Returns: Json }; get_fans_listening_top_songs: { - Args: { artistid: string; email: string } - Returns: Json - } + Args: { artistid: string; email: string }; + Returns: Json; + }; get_message_counts_by_user: | { - Args: { start_date: string } + Args: { start_date: string }; Returns: { - account_email: string - message_count: number - }[] + account_email: string; + message_count: number; + }[]; } | { - Args: { end_date: string; start_date: string } + Args: { end_date: string; start_date: string }; Returns: { - account_email: string - message_count: number - }[] - } + account_email: string; + message_count: number; + }[]; + }; get_rooms_created_by_user: { - Args: { start_date: string } + Args: { start_date: string }; Returns: { - account_email: string - rooms_created: number - }[] - } + account_email: string; + rooms_created: number; + }[]; + }; get_segment_reports_by_user: { - Args: { start_date: string } + Args: { start_date: string }; Returns: { - email: string - segment_report_count: number - }[] - } - get_upper_system_role: { Args: never; Returns: string } + email: string; + segment_report_count: number; + }[]; + }; + get_upper_system_role: { Args: never; Returns: string }; has_active_subscription: { - Args: { target_account_id: string } - Returns: boolean - } - has_credits: { Args: { account_id: string }; Returns: boolean } + Args: { target_account_id: string }; + Returns: boolean; + }; + has_credits: { Args: { account_id: string }; Returns: boolean }; has_more_elevated_role: { Args: { - role_name: string - target_account_id: string - target_user_id: string - } - Returns: boolean - } + role_name: string; + target_account_id: string; + target_user_id: string; + }; + Returns: boolean; + }; has_permission: { Args: { - account_id: string - permission_name: Database["public"]["Enums"]["app_permissions"] - user_id: string - } - Returns: boolean - } + account_id: string; + permission_name: Database["public"]["Enums"]["app_permissions"]; + user_id: string; + }; + Returns: boolean; + }; has_role_on_account: { - Args: { account_id: string; account_role?: string } - Returns: boolean - } + Args: { account_id: string; account_role?: string }; + Returns: boolean; + }; has_same_role_hierarchy_level: { Args: { - role_name: string - target_account_id: string - target_user_id: string - } - Returns: boolean - } - is_account_owner: { Args: { account_id: string }; Returns: boolean } + role_name: string; + target_account_id: string; + target_user_id: string; + }; + Returns: boolean; + }; + is_account_owner: { Args: { account_id: string }; Returns: boolean }; is_account_team_member: { - Args: { target_account_id: string } - Returns: boolean - } - is_set: { Args: { field_name: string }; Returns: boolean } + Args: { target_account_id: string }; + Returns: boolean; + }; + is_set: { Args: { field_name: string }; Returns: boolean }; is_team_member: { - Args: { account_id: string; user_id: string } - Returns: boolean - } + Args: { account_id: string; user_id: string }; + Returns: boolean; + }; team_account_workspace: { - Args: { account_slug: string } + Args: { account_slug: string }; Returns: { - id: string - name: string - permissions: Database["public"]["Enums"]["app_permissions"][] - picture_url: string - primary_owner_user_id: string - role: string - role_hierarchy_level: number - slug: string - subscription_status: Database["public"]["Enums"]["subscription_status"] - }[] - } + id: string; + name: string; + permissions: Database["public"]["Enums"]["app_permissions"][]; + picture_url: string; + primary_owner_user_id: string; + role: string; + role_hierarchy_level: number; + slug: string; + subscription_status: Database["public"]["Enums"]["subscription_status"]; + }[]; + }; transfer_team_account_ownership: { - Args: { new_owner_id: string; target_account_id: string } - Returns: undefined - } + Args: { new_owner_id: string; target_account_id: string }; + Returns: undefined; + }; upsert_order: { Args: { - billing_provider: Database["public"]["Enums"]["billing_provider"] - currency: string - line_items: Json - status: Database["public"]["Enums"]["payment_status"] - target_account_id: string - target_customer_id: string - target_order_id: string - total_amount: number - } + billing_provider: Database["public"]["Enums"]["billing_provider"]; + currency: string; + line_items: Json; + status: Database["public"]["Enums"]["payment_status"]; + target_account_id: string; + target_customer_id: string; + target_order_id: string; + total_amount: number; + }; Returns: { - account_id: string - billing_customer_id: number - billing_provider: Database["public"]["Enums"]["billing_provider"] - created_at: string - currency: string - id: string - status: Database["public"]["Enums"]["payment_status"] - total_amount: number - updated_at: string - } + account_id: string; + billing_customer_id: number; + billing_provider: Database["public"]["Enums"]["billing_provider"]; + created_at: string; + currency: string; + id: string; + status: Database["public"]["Enums"]["payment_status"]; + total_amount: number; + updated_at: string; + }; SetofOptions: { - from: "*" - to: "orders" - isOneToOne: true - isSetofReturn: false - } - } + from: "*"; + to: "orders"; + isOneToOne: true; + isSetofReturn: false; + }; + }; upsert_subscription: { Args: { - active: boolean - billing_provider: Database["public"]["Enums"]["billing_provider"] - cancel_at_period_end: boolean - currency: string - line_items: Json - period_ends_at: string - period_starts_at: string - status: Database["public"]["Enums"]["subscription_status"] - target_account_id: string - target_customer_id: string - target_subscription_id: string - trial_ends_at?: string - trial_starts_at?: string - } + active: boolean; + billing_provider: Database["public"]["Enums"]["billing_provider"]; + cancel_at_period_end: boolean; + currency: string; + line_items: Json; + period_ends_at: string; + period_starts_at: string; + status: Database["public"]["Enums"]["subscription_status"]; + target_account_id: string; + target_customer_id: string; + target_subscription_id: string; + trial_ends_at?: string; + trial_starts_at?: string; + }; Returns: { - account_id: string - active: boolean - billing_customer_id: number - billing_provider: Database["public"]["Enums"]["billing_provider"] - cancel_at_period_end: boolean - created_at: string - currency: string - id: string - period_ends_at: string - period_starts_at: string - status: Database["public"]["Enums"]["subscription_status"] - trial_ends_at: string | null - trial_starts_at: string | null - updated_at: string - } + account_id: string; + active: boolean; + billing_customer_id: number; + billing_provider: Database["public"]["Enums"]["billing_provider"]; + cancel_at_period_end: boolean; + created_at: string; + currency: string; + id: string; + period_ends_at: string; + period_starts_at: string; + status: Database["public"]["Enums"]["subscription_status"]; + trial_ends_at: string | null; + trial_starts_at: string | null; + updated_at: string; + }; SetofOptions: { - from: "*" - to: "subscriptions" - isOneToOne: true - isSetofReturn: false - } - } - } + from: "*"; + to: "subscriptions"; + isOneToOne: true; + isSetofReturn: false; + }; + }; + }; Enums: { app_permissions: | "roles.manage" @@ -3759,20 +3753,14 @@ export type Database = { | "members.manage" | "invites.manage" | "tasks.write" - | "tasks.delete" - billing_provider: "stripe" | "lemon-squeezy" | "paddle" - chat_role: "user" | "assistant" - notification_channel: "in_app" | "email" - notification_type: "info" | "warning" | "error" - payment_status: "pending" | "succeeded" | "failed" - social_type: - | "TIKTOK" - | "YOUTUBE" - | "INSTAGRAM" - | "TWITTER" - | "SPOTIFY" - | "APPLE" - subscription_item_type: "flat" | "per_seat" | "metered" + | "tasks.delete"; + billing_provider: "stripe" | "lemon-squeezy" | "paddle"; + chat_role: "user" | "assistant"; + notification_channel: "in_app" | "email"; + notification_type: "info" | "warning" | "error"; + payment_status: "pending" | "succeeded" | "failed"; + social_type: "TIKTOK" | "YOUTUBE" | "INSTAGRAM" | "TWITTER" | "SPOTIFY" | "APPLE"; + subscription_item_type: "flat" | "per_seat" | "metered"; subscription_status: | "active" | "trialing" @@ -3781,133 +3769,131 @@ export type Database = { | "unpaid" | "incomplete" | "incomplete_expired" - | "paused" - } + | "paused"; + }; CompositeTypes: { invitation: { - email: string | null - role: string | null - } - } - } -} + email: string | null; + role: string | null; + }; + }; + }; +}; -type DatabaseWithoutInternals = Omit +type DatabaseWithoutInternals = Omit; -type DefaultSchema = DatabaseWithoutInternals[Extract] +type DefaultSchema = DatabaseWithoutInternals[Extract]; export type Tables< DefaultSchemaTableNameOrOptions extends | keyof (DefaultSchema["Tables"] & DefaultSchema["Views"]) | { schema: keyof DatabaseWithoutInternals }, TableName extends DefaultSchemaTableNameOrOptions extends { - schema: keyof DatabaseWithoutInternals + schema: keyof DatabaseWithoutInternals; } ? keyof (DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Tables"] & DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Views"]) : never = never, > = DefaultSchemaTableNameOrOptions extends { - schema: keyof DatabaseWithoutInternals + schema: keyof DatabaseWithoutInternals; } ? (DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Tables"] & DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Views"])[TableName] extends { - Row: infer R + Row: infer R; } ? R : never - : DefaultSchemaTableNameOrOptions extends keyof (DefaultSchema["Tables"] & - DefaultSchema["Views"]) - ? (DefaultSchema["Tables"] & - DefaultSchema["Views"])[DefaultSchemaTableNameOrOptions] extends { - Row: infer R + : DefaultSchemaTableNameOrOptions extends keyof (DefaultSchema["Tables"] & DefaultSchema["Views"]) + ? (DefaultSchema["Tables"] & DefaultSchema["Views"])[DefaultSchemaTableNameOrOptions] extends { + Row: infer R; } ? R : never - : never + : never; export type TablesInsert< DefaultSchemaTableNameOrOptions extends | keyof DefaultSchema["Tables"] | { schema: keyof DatabaseWithoutInternals }, TableName extends DefaultSchemaTableNameOrOptions extends { - schema: keyof DatabaseWithoutInternals + schema: keyof DatabaseWithoutInternals; } ? keyof DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Tables"] : never = never, > = DefaultSchemaTableNameOrOptions extends { - schema: keyof DatabaseWithoutInternals + schema: keyof DatabaseWithoutInternals; } ? DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Tables"][TableName] extends { - Insert: infer I + Insert: infer I; } ? I : never : DefaultSchemaTableNameOrOptions extends keyof DefaultSchema["Tables"] ? DefaultSchema["Tables"][DefaultSchemaTableNameOrOptions] extends { - Insert: infer I + Insert: infer I; } ? I : never - : never + : never; export type TablesUpdate< DefaultSchemaTableNameOrOptions extends | keyof DefaultSchema["Tables"] | { schema: keyof DatabaseWithoutInternals }, TableName extends DefaultSchemaTableNameOrOptions extends { - schema: keyof DatabaseWithoutInternals + schema: keyof DatabaseWithoutInternals; } ? keyof DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Tables"] : never = never, > = DefaultSchemaTableNameOrOptions extends { - schema: keyof DatabaseWithoutInternals + schema: keyof DatabaseWithoutInternals; } ? DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions["schema"]]["Tables"][TableName] extends { - Update: infer U + Update: infer U; } ? U : never : DefaultSchemaTableNameOrOptions extends keyof DefaultSchema["Tables"] ? DefaultSchema["Tables"][DefaultSchemaTableNameOrOptions] extends { - Update: infer U + Update: infer U; } ? U : never - : never + : never; export type Enums< DefaultSchemaEnumNameOrOptions extends | keyof DefaultSchema["Enums"] | { schema: keyof DatabaseWithoutInternals }, EnumName extends DefaultSchemaEnumNameOrOptions extends { - schema: keyof DatabaseWithoutInternals + schema: keyof DatabaseWithoutInternals; } ? keyof DatabaseWithoutInternals[DefaultSchemaEnumNameOrOptions["schema"]]["Enums"] : never = never, > = DefaultSchemaEnumNameOrOptions extends { - schema: keyof DatabaseWithoutInternals + schema: keyof DatabaseWithoutInternals; } ? DatabaseWithoutInternals[DefaultSchemaEnumNameOrOptions["schema"]]["Enums"][EnumName] : DefaultSchemaEnumNameOrOptions extends keyof DefaultSchema["Enums"] ? DefaultSchema["Enums"][DefaultSchemaEnumNameOrOptions] - : never + : never; export type CompositeTypes< PublicCompositeTypeNameOrOptions extends | keyof DefaultSchema["CompositeTypes"] | { schema: keyof DatabaseWithoutInternals }, CompositeTypeName extends PublicCompositeTypeNameOrOptions extends { - schema: keyof DatabaseWithoutInternals + schema: keyof DatabaseWithoutInternals; } ? keyof DatabaseWithoutInternals[PublicCompositeTypeNameOrOptions["schema"]]["CompositeTypes"] : never = never, > = PublicCompositeTypeNameOrOptions extends { - schema: keyof DatabaseWithoutInternals + schema: keyof DatabaseWithoutInternals; } ? DatabaseWithoutInternals[PublicCompositeTypeNameOrOptions["schema"]]["CompositeTypes"][CompositeTypeName] : PublicCompositeTypeNameOrOptions extends keyof DefaultSchema["CompositeTypes"] ? DefaultSchema["CompositeTypes"][PublicCompositeTypeNameOrOptions] - : never + : never; export const Constants = { public: { @@ -3926,14 +3912,7 @@ export const Constants = { notification_channel: ["in_app", "email"], notification_type: ["info", "warning", "error"], payment_status: ["pending", "succeeded", "failed"], - social_type: [ - "TIKTOK", - "YOUTUBE", - "INSTAGRAM", - "TWITTER", - "SPOTIFY", - "APPLE", - ], + social_type: ["TIKTOK", "YOUTUBE", "INSTAGRAM", "TWITTER", "SPOTIFY", "APPLE"], subscription_item_type: ["flat", "per_seat", "metered"], subscription_status: [ "active", @@ -3947,4 +3926,4 @@ export const Constants = { ], }, }, -} as const +} as const; diff --git a/types/youtube.ts b/types/youtube.ts index 777feb4f9..7bd159677 100644 --- a/types/youtube.ts +++ b/types/youtube.ts @@ -1,10 +1,6 @@ // YouTube API Response Types -import type { - Tables, - TablesInsert, - TablesUpdate, -} from "@/types/database.types"; +import type { Tables, TablesInsert, TablesUpdate } from "@/types/database.types"; // Interface for detailed channel information from getYouTubeChannelInfo tool export interface YouTubeChannelInfoResult { @@ -317,4 +313,4 @@ export interface YoutubeStatus { }; isLoading: boolean; error: Error | null; -} \ No newline at end of file +} diff --git a/utils/artistHelpers.ts b/utils/artistHelpers.ts index 7e368d218..b33fad46b 100644 --- a/utils/artistHelpers.ts +++ b/utils/artistHelpers.ts @@ -24,4 +24,4 @@ export const sortArtistsAlphabetically = (artists: ArtistRecord[]) => { // Type guards export const isArtistSelected = (artist: ArtistRecord | null): artist is ArtistRecord => { return artist !== null; -}; \ No newline at end of file +}; diff --git a/utils/extractAccountIds.ts b/utils/extractAccountIds.ts index 6b404a0ab..f948f064e 100644 --- a/utils/extractAccountIds.ts +++ b/utils/extractAccountIds.ts @@ -1,6 +1,8 @@ /** * Extract owner and artist account IDs from storage key * Storage key format: files/{ownerAccountId}/{artistAccountId}/path/to/file + * + * @param storageKey */ export function extractAccountIds(storageKey: string) { const parts = storageKey.split("/"); @@ -9,4 +11,3 @@ export function extractAccountIds(storageKey: string) { artistAccountId: parts[2] || "", }; } - diff --git a/utils/getContentSizeBytes.ts b/utils/getContentSizeBytes.ts index ca1136390..afdadf8fa 100644 --- a/utils/getContentSizeBytes.ts +++ b/utils/getContentSizeBytes.ts @@ -1,7 +1,8 @@ /** * Calculate the size of text content in bytes + * + * @param content */ export function getContentSizeBytes(content: string): number { return new TextEncoder().encode(content).length; } - diff --git a/utils/getFileVisual.ts b/utils/getFileVisual.ts index 0b5c3a3c3..1b9290f81 100644 --- a/utils/getFileVisual.ts +++ b/utils/getFileVisual.ts @@ -1,5 +1,24 @@ -export type FileVisual = { icon: "plain" | "txt" | "yml" | "md" | "image" | "pdf" | "word" | "csv" | "json" | "audio" | "video"; color: string }; +export type FileVisual = { + icon: + | "plain" + | "txt" + | "yml" + | "md" + | "image" + | "pdf" + | "word" + | "csv" + | "json" + | "audio" + | "video"; + color: string; +}; +/** + * + * @param fileName + * @param mime + */ export default function getFileVisual(fileName: string, mime?: string | null): FileVisual { const lower = fileName.toLowerCase(); const type = mime || ""; @@ -36,5 +55,3 @@ export default function getFileVisual(fileName: string, mime?: string | null): F } return { icon: "plain", color: "text-muted-foreground" }; } - - diff --git a/utils/getMimeFromPath.ts b/utils/getMimeFromPath.ts index 841394c3d..b44baa3a9 100644 --- a/utils/getMimeFromPath.ts +++ b/utils/getMimeFromPath.ts @@ -15,4 +15,4 @@ export const getMimeFromPath = (path: string): string => { return (ext && mimeByExt[ext]) || "text/plain"; }; -export default getMimeFromPath; \ No newline at end of file +export default getMimeFromPath; diff --git a/utils/getRelativeStoragePath.ts b/utils/getRelativeStoragePath.ts index a79425426..b20aa0b65 100644 --- a/utils/getRelativeStoragePath.ts +++ b/utils/getRelativeStoragePath.ts @@ -1,8 +1,18 @@ -export default function getRelativeStoragePath(storageKey: string, ownerAccountId: string, artistAccountId: string, isDirectory?: boolean): string { +/** + * + * @param storageKey + * @param ownerAccountId + * @param artistAccountId + * @param isDirectory + */ +export default function getRelativeStoragePath( + storageKey: string, + ownerAccountId: string, + artistAccountId: string, + isDirectory?: boolean, +): string { const base = `files/${ownerAccountId}/${artistAccountId}/`; let rel = storageKey.startsWith(base) ? storageKey.slice(base.length) : storageKey; if (isDirectory && !rel.endsWith("/")) rel += "/"; return rel; } - - diff --git a/utils/isImagePath.ts b/utils/isImagePath.ts index d393c47c6..ecd489563 100644 --- a/utils/isImagePath.ts +++ b/utils/isImagePath.ts @@ -1,9 +1,7 @@ export const isImagePath = (value: string): boolean => { const lower = value.toLowerCase().split("?")[0]; const imageExtensions = [".png", ".jpg", ".jpeg", ".gif", ".webp", ".svg"] as const; - return imageExtensions.some((ext) => lower.endsWith(ext)); + return imageExtensions.some(ext => lower.endsWith(ext)); }; export default isImagePath; - - diff --git a/utils/isTextFile.ts b/utils/isTextFile.ts index 8e7fc2df4..fa19415a7 100644 --- a/utils/isTextFile.ts +++ b/utils/isTextFile.ts @@ -2,9 +2,10 @@ import { TEXT_EXTENSIONS } from "@/lib/consts/fileExtensions"; /** * Check if a file is a text file based on its name + * + * @param fileName */ export function isTextFile(fileName: string): boolean { const lowerName = fileName.toLowerCase(); - return TEXT_EXTENSIONS.some((ext) => lowerName.endsWith(ext)); + return TEXT_EXTENSIONS.some(ext => lowerName.endsWith(ext)); } - diff --git a/utils/isTextMimeType.ts b/utils/isTextMimeType.ts index 0b696d817..3ec0fb17a 100644 --- a/utils/isTextMimeType.ts +++ b/utils/isTextMimeType.ts @@ -11,5 +11,3 @@ export const isTextMimeType = (type?: string | null): boolean => { }; export default isTextMimeType; - - diff --git a/utils/isTextPath.ts b/utils/isTextPath.ts index cbe4ac600..52a9de0c2 100644 --- a/utils/isTextPath.ts +++ b/utils/isTextPath.ts @@ -2,7 +2,7 @@ import { TEXT_EXTENSIONS } from "@/lib/consts/fileExtensions"; export const isTextPath = (value: string): boolean => { const lower = value.toLowerCase().split("?")[0]; - return TEXT_EXTENSIONS.some((ext) => lower.endsWith(ext)); + return TEXT_EXTENSIONS.some(ext => lower.endsWith(ext)); }; -export default isTextPath; \ No newline at end of file +export default isTextPath; diff --git a/utils/isValidFileName.ts b/utils/isValidFileName.ts index 5ff4acd0c..5da6bcbaf 100644 --- a/utils/isValidFileName.ts +++ b/utils/isValidFileName.ts @@ -1,7 +1,8 @@ /** * Validates file name for rename operations * Prevents security issues and invalid characters - * @param fileName File name to validate + * + * @param fileName - File name to validate * @returns true if valid, false otherwise */ export function isValidFileName(fileName: string): boolean { @@ -53,4 +54,3 @@ export function isValidFileName(fileName: string): boolean { } export default isValidFileName; - diff --git a/utils/isValidFolderName.ts b/utils/isValidFolderName.ts index ff2c5a9c1..3bbbbfc98 100644 --- a/utils/isValidFolderName.ts +++ b/utils/isValidFolderName.ts @@ -1,3 +1,7 @@ +/** + * + * @param name + */ export default function isValidFolderName(name: string): boolean { if (!name) return false; if (name.length > 64) return false; @@ -5,5 +9,3 @@ export default function isValidFolderName(name: string): boolean { if (name.includes("..")) return false; return /^[\w .-]+$/.test(name); } - - diff --git a/utils/isValidPath.ts b/utils/isValidPath.ts index 4fda3e757..37c244b91 100644 --- a/utils/isValidPath.ts +++ b/utils/isValidPath.ts @@ -1,11 +1,12 @@ /** * Validates a directory path to prevent traversal attacks and security issues * More strict than storage key validation - for user-provided directory paths - * @param path Directory path to validate + * + * @param path - Directory path to validate * @returns true if path is safe to use, false otherwise */ export function isValidPath(path: string): boolean { - if (!path || typeof path !== 'string') { + if (!path || typeof path !== "string") { return false; } @@ -15,17 +16,17 @@ export function isValidPath(path: string): boolean { } // Reject directory traversal patterns - if (path.includes('..')) { + if (path.includes("..")) { return false; } // Reject current directory references - if (path.includes('./') || path === '.') { + if (path.includes("./") || path === ".") { return false; } // Reject backslashes (Windows path separators) - if (path.includes('\\')) { + if (path.includes("\\")) { return false; } @@ -36,7 +37,7 @@ export function isValidPath(path: string): boolean { // Reject absolute paths (should not start with /) // (This is redundant with normalization but adds defense in depth) - if (path.startsWith('/')) { + if (path.startsWith("/")) { return false; } @@ -44,4 +45,3 @@ export function isValidPath(path: string): boolean { } export default isValidPath; - diff --git a/utils/isValidStorageKey.ts b/utils/isValidStorageKey.ts index 2b11a7e14..2d87b3920 100644 --- a/utils/isValidStorageKey.ts +++ b/utils/isValidStorageKey.ts @@ -1,6 +1,8 @@ /** * Validates storage key format for Supabase storage * Prevents path traversal and other security issues + * + * @param key */ export function isValidStorageKey(key: string): boolean { if (!key || key.length > 1024) return false; @@ -9,4 +11,4 @@ export function isValidStorageKey(key: string): boolean { if (key.includes("\\")) return false; return true; } -export default isValidStorageKey; \ No newline at end of file +export default isValidStorageKey; diff --git a/utils/isValidUUID.ts b/utils/isValidUUID.ts index c98dc2180..ecc18c37f 100644 --- a/utils/isValidUUID.ts +++ b/utils/isValidUUID.ts @@ -1,10 +1,11 @@ /** * Validates UUID v4 format - * @param uuid String to validate as UUID + * + * @param uuid - String to validate as UUID * @returns true if valid UUID v4, false otherwise */ export function isValidUUID(uuid: string): boolean { - if (!uuid || typeof uuid !== 'string') { + if (!uuid || typeof uuid !== "string") { return false; } @@ -12,9 +13,8 @@ export function isValidUUID(uuid: string): boolean { // Version 4 has '4' in the version position // Variant has [89ab] in the variant position const uuidV4Regex = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i; - + return uuidV4Regex.test(uuid); } export default isValidUUID; -