@promptbook/cli 0.104.0-1 → 0.104.0-10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/apps/agents-server/config.ts +1 -3
- package/apps/agents-server/next.config.ts +2 -2
- package/apps/agents-server/package.json +7 -3
- package/apps/agents-server/public/fonts/OpenMoji-color-cbdt.woff2 +0 -0
- package/apps/agents-server/public/swagger.json +115 -0
- package/apps/agents-server/scripts/generate-reserved-paths/generate-reserved-paths.ts +54 -0
- package/apps/agents-server/scripts/generate-reserved-paths/tsconfig.json +19 -0
- package/apps/agents-server/src/app/AddAgentButton.tsx +47 -21
- package/apps/agents-server/src/app/actions.ts +22 -5
- package/apps/agents-server/src/app/admin/browser-test/BrowserTestClient.tsx +211 -0
- package/apps/agents-server/src/app/admin/browser-test/page.tsx +13 -0
- package/apps/agents-server/src/app/admin/chat-feedback/ChatFeedbackClient.tsx +221 -274
- package/apps/agents-server/src/app/admin/chat-history/ChatHistoryClient.tsx +94 -137
- package/apps/agents-server/src/app/admin/messages/MessagesClient.tsx +294 -0
- package/apps/agents-server/src/app/admin/messages/page.tsx +13 -0
- package/apps/agents-server/src/app/admin/messages/send-email/SendEmailClient.tsx +104 -0
- package/apps/agents-server/src/app/admin/messages/send-email/actions.ts +35 -0
- package/apps/agents-server/src/app/admin/messages/send-email/page.tsx +13 -0
- package/apps/agents-server/src/app/admin/metadata/MetadataClient.tsx +23 -19
- package/apps/agents-server/src/app/agents/[agentName]/AgentChatWrapper.tsx +15 -1
- package/apps/agents-server/src/app/agents/[agentName]/AgentOptionsMenu.tsx +51 -9
- package/apps/agents-server/src/app/agents/[agentName]/AgentProfileChat.tsx +47 -4
- package/apps/agents-server/src/app/agents/[agentName]/AgentProfileWrapper.tsx +53 -11
- package/apps/agents-server/src/app/agents/[agentName]/_utils.ts +23 -3
- package/apps/agents-server/src/app/agents/[agentName]/agentLinks.tsx +8 -8
- package/apps/agents-server/src/app/agents/[agentName]/api/agents/route.ts +17 -26
- package/apps/agents-server/src/app/agents/[agentName]/api/book/route.ts +4 -2
- package/apps/agents-server/src/app/agents/[agentName]/api/chat/route.ts +20 -0
- package/apps/agents-server/src/app/agents/[agentName]/api/mcp/route.ts +6 -11
- package/apps/agents-server/src/app/agents/[agentName]/api/profile/route.ts +5 -1
- package/apps/agents-server/src/app/agents/[agentName]/api/voice/route.ts +5 -2
- package/apps/agents-server/src/app/agents/[agentName]/book/BookEditorWrapper.tsx +20 -16
- package/apps/agents-server/src/app/agents/[agentName]/book/page.tsx +15 -2
- package/apps/agents-server/src/app/agents/[agentName]/book+chat/page.tsx +15 -2
- package/apps/agents-server/src/app/agents/[agentName]/chat/page.tsx +12 -0
- package/apps/agents-server/src/app/agents/[agentName]/code/api/route.ts +68 -0
- package/apps/agents-server/src/app/agents/[agentName]/code/page.tsx +223 -0
- package/apps/agents-server/src/app/agents/[agentName]/generateAgentMetadata.ts +5 -0
- package/apps/agents-server/src/app/agents/[agentName]/history/actions.ts +2 -2
- package/apps/agents-server/src/app/agents/[agentName]/history/page.tsx +10 -3
- package/apps/agents-server/src/app/agents/[agentName]/images/default-avatar.png/getAgentDefaultAvatarPrompt.ts +31 -0
- package/apps/agents-server/src/app/agents/[agentName]/images/default-avatar.png/route.ts +194 -0
- package/apps/agents-server/src/app/agents/[agentName]/images/icon-256.png/route.tsx +14 -2
- package/apps/agents-server/src/app/agents/[agentName]/images/page.tsx +200 -0
- package/apps/agents-server/src/app/agents/[agentName]/images/screenshot-fullhd.png/route.tsx +4 -3
- package/apps/agents-server/src/app/agents/[agentName]/images/screenshot-phone.png/route.tsx +4 -3
- package/apps/agents-server/src/app/agents/[agentName]/integration/page.tsx +10 -3
- package/apps/agents-server/src/app/agents/[agentName]/links/page.tsx +11 -4
- package/apps/agents-server/src/app/agents/[agentName]/opengraph-image.tsx +11 -2
- package/apps/agents-server/src/app/agents/[agentName]/page.tsx +18 -10
- package/apps/agents-server/src/app/agents/[agentName]/system-message/page.tsx +100 -0
- package/apps/agents-server/src/app/api/admin-email/route.ts +12 -0
- package/apps/agents-server/src/app/api/agents/[agentName]/clone/route.ts +13 -14
- package/apps/agents-server/src/app/api/agents/[agentName]/restore/route.ts +20 -0
- package/apps/agents-server/src/app/api/agents/[agentName]/route.ts +43 -1
- package/apps/agents-server/src/app/api/agents/route.ts +28 -3
- package/apps/agents-server/src/app/api/api-tokens/route.ts +6 -7
- package/apps/agents-server/src/app/api/browser-test/act/route.ts +141 -0
- package/apps/agents-server/src/app/api/browser-test/screenshot/route.ts +30 -0
- package/apps/agents-server/src/app/api/browser-test/scroll-facebook/route.ts +62 -0
- package/apps/agents-server/src/app/api/docs/book.md/route.ts +61 -0
- package/apps/agents-server/src/app/api/emails/incoming/sendgrid/route.ts +48 -0
- package/apps/agents-server/src/app/api/federated-agents/route.ts +12 -0
- package/apps/agents-server/src/app/api/images/[filename]/route.ts +107 -0
- package/apps/agents-server/src/app/api/messages/route.ts +102 -0
- package/apps/agents-server/src/app/api/metadata/route.ts +5 -6
- package/apps/agents-server/src/app/api/upload/route.ts +128 -45
- package/apps/agents-server/src/app/docs/[docId]/page.tsx +2 -3
- package/apps/agents-server/src/app/docs/page.tsx +12 -12
- package/apps/agents-server/src/app/globals.css +140 -33
- package/apps/agents-server/src/app/humans.txt/route.ts +1 -1
- package/apps/agents-server/src/app/layout.tsx +27 -22
- package/apps/agents-server/src/app/page.tsx +54 -6
- package/apps/agents-server/src/app/recycle-bin/actions.ts +20 -14
- package/apps/agents-server/src/app/recycle-bin/page.tsx +27 -41
- package/apps/agents-server/src/app/robots.txt/route.ts +1 -1
- package/apps/agents-server/src/app/security.txt/route.ts +1 -1
- package/apps/agents-server/src/app/sitemap.xml/route.ts +9 -7
- package/apps/agents-server/src/app/swagger/page.tsx +14 -0
- package/apps/agents-server/src/components/AgentProfile/AgentProfile.tsx +41 -116
- package/apps/agents-server/src/components/AgentProfile/AgentProfileImage.tsx +92 -0
- package/apps/agents-server/src/components/AgentProfile/QrCodeModal.tsx +0 -1
- package/apps/agents-server/src/components/AgentProfile/useAgentBackground.ts +97 -0
- package/apps/agents-server/src/components/Auth/AuthControls.tsx +5 -4
- package/apps/agents-server/src/components/DeletedAgentBanner.tsx +26 -0
- package/apps/agents-server/src/components/DocsToolbar/DocsToolbar.tsx +38 -0
- package/apps/agents-server/src/components/DocumentationContent/DocumentationContent.tsx +11 -9
- package/apps/agents-server/src/components/Footer/Footer.tsx +5 -5
- package/apps/agents-server/src/components/ForgottenPasswordDialog/ForgottenPasswordDialog.tsx +61 -0
- package/apps/agents-server/src/components/Header/Header.tsx +114 -40
- package/apps/agents-server/src/components/Homepage/AgentCard.tsx +145 -23
- package/apps/agents-server/src/components/Homepage/AgentsList.tsx +93 -15
- package/apps/agents-server/src/components/Homepage/DeletedAgentsList.tsx +66 -0
- package/apps/agents-server/src/components/Homepage/ExternalAgentsSection.tsx +12 -3
- package/apps/agents-server/src/components/Homepage/ExternalAgentsSectionClient.tsx +19 -10
- package/apps/agents-server/src/components/LayoutWrapper/LayoutWrapper.tsx +3 -2
- package/apps/agents-server/src/components/LoginForm/LoginForm.tsx +50 -1
- package/apps/agents-server/src/components/NewAgentDialog/NewAgentDialog.tsx +88 -0
- package/apps/agents-server/src/components/NotFoundPage/NotFoundPage.tsx +7 -2
- package/apps/agents-server/src/components/OpenMojiIcon/OpenMojiIcon.tsx +16 -7
- package/apps/agents-server/src/components/PrintHeader/PrintHeader.tsx +4 -4
- package/apps/agents-server/src/components/RegisterUserDialog/RegisterUserDialog.tsx +61 -0
- package/apps/agents-server/src/components/VercelDeploymentCard/VercelDeploymentCard.tsx +2 -0
- package/apps/agents-server/src/components/_utils/generateMetaTxt.ts +12 -10
- package/apps/agents-server/src/components/_utils/headlessParam.tsx +7 -3
- package/apps/agents-server/src/database/$provideSupabaseForBrowser.ts +3 -3
- package/apps/agents-server/src/database/$provideSupabaseForServer.ts +1 -1
- package/apps/agents-server/src/database/$provideSupabaseForWorker.ts +3 -3
- package/apps/agents-server/src/database/metadataDefaults.ts +19 -1
- package/apps/agents-server/src/database/migrate.ts +34 -1
- package/apps/agents-server/src/database/migrations/2025-11-0001-initial-schema.sql +1 -3
- package/apps/agents-server/src/database/migrations/2025-11-0002-metadata-table.sql +1 -3
- package/apps/agents-server/src/database/migrations/2025-12-0240-agent-public-id.sql +3 -0
- package/apps/agents-server/src/database/migrations/2025-12-0360-agent-deleted-at.sql +1 -0
- package/apps/agents-server/src/database/migrations/2025-12-0370-image-table.sql +19 -0
- package/apps/agents-server/src/database/migrations/2025-12-0380-agent-visibility.sql +1 -0
- package/apps/agents-server/src/database/migrations/2025-12-0390-upload-tracking.sql +20 -0
- package/apps/agents-server/src/database/migrations/2025-12-0401-file-upload-status.sql +13 -0
- package/apps/agents-server/src/database/migrations/2025-12-0402-message-table.sql +42 -0
- package/apps/agents-server/src/database/migrations/2025-12-0403-generation-lock-table.sql +15 -0
- package/apps/agents-server/src/database/migrations/2025-12-0640-openai-assistant-cache.sql +12 -0
- package/apps/agents-server/src/database/migrations/2025-12-0820-agent-history-permanent-id.sql +29 -0
- package/apps/agents-server/src/database/schema.ts +231 -4
- package/apps/agents-server/src/generated/reservedPaths.ts +32 -0
- package/apps/agents-server/src/message-providers/email/_common/Email.ts +73 -0
- package/apps/agents-server/src/message-providers/email/_common/utils/TODO.txt +1 -0
- package/apps/agents-server/src/message-providers/email/_common/utils/parseEmailAddress.test.ts.todo +108 -0
- package/apps/agents-server/src/message-providers/email/_common/utils/parseEmailAddress.ts +62 -0
- package/apps/agents-server/src/message-providers/email/_common/utils/parseEmailAddresses.test.ts.todo +117 -0
- package/apps/agents-server/src/message-providers/email/_common/utils/parseEmailAddresses.ts +19 -0
- package/apps/agents-server/src/message-providers/email/_common/utils/stringifyEmailAddress.test.ts.todo +119 -0
- package/apps/agents-server/src/message-providers/email/_common/utils/stringifyEmailAddress.ts +19 -0
- package/apps/agents-server/src/message-providers/email/_common/utils/stringifyEmailAddresses.test.ts.todo +74 -0
- package/apps/agents-server/src/message-providers/email/_common/utils/stringifyEmailAddresses.ts +14 -0
- package/apps/agents-server/src/message-providers/email/sendgrid/SendgridMessageProvider.ts +44 -0
- package/apps/agents-server/src/message-providers/email/sendgrid/parseInboundSendgridEmail.ts +49 -0
- package/apps/agents-server/src/message-providers/email/zeptomail/ZeptomailMessageProvider.ts +51 -0
- package/apps/agents-server/src/message-providers/index.ts +13 -0
- package/apps/agents-server/src/message-providers/interfaces/MessageProvider.ts +11 -0
- package/apps/agents-server/src/middleware.ts +19 -23
- package/apps/agents-server/src/tools/$provideBrowserForServer.ts +32 -0
- package/apps/agents-server/src/tools/$provideCdnForServer.ts +7 -2
- package/apps/agents-server/src/utils/auth.ts +117 -17
- package/apps/agents-server/src/utils/cdn/classes/TrackedFilesStorage.ts +57 -0
- package/apps/agents-server/src/utils/cdn/classes/VercelBlobStorage.ts +4 -0
- package/apps/agents-server/src/utils/cdn/interfaces/IFilesStorage.ts +18 -0
- package/apps/agents-server/src/utils/content/extractBodyContentFromHtml.ts +19 -0
- package/apps/agents-server/src/utils/getUserIdFromRequest.ts +35 -0
- package/apps/agents-server/src/utils/handleChatCompletion.ts +65 -5
- package/apps/agents-server/src/utils/messages/sendMessage.ts +91 -0
- package/apps/agents-server/src/utils/messagesAdmin.ts +72 -0
- package/apps/agents-server/src/utils/normalization/filenameToPrompt.test.ts +36 -0
- package/apps/agents-server/src/utils/normalization/filenameToPrompt.ts +25 -0
- package/apps/agents-server/src/utils/validateApiKey.ts +7 -11
- package/esm/index.es.js +2890 -2737
- package/esm/index.es.js.map +1 -1
- package/esm/typings/servers.d.ts +8 -0
- package/esm/typings/src/_packages/core.index.d.ts +2 -0
- package/esm/typings/src/_packages/types.index.d.ts +10 -2
- package/esm/typings/src/book-2.0/agent-source/AgentBasicInformation.d.ts +6 -1
- package/esm/typings/src/book-2.0/agent-source/createAgentModelRequirements.d.ts +6 -6
- package/esm/typings/src/book-2.0/agent-source/createAgentModelRequirementsWithCommitments.closed.test.d.ts +1 -0
- package/esm/typings/src/book-2.0/utils/generatePlaceholderAgentProfileImageUrl.d.ts +3 -3
- package/esm/typings/src/book-components/Chat/Chat/ChatMessageItem.d.ts +5 -1
- package/esm/typings/src/book-components/Chat/Chat/ChatProps.d.ts +5 -0
- package/esm/typings/src/book-components/Chat/CodeBlock/CodeBlock.d.ts +13 -0
- package/esm/typings/src/book-components/Chat/MarkdownContent/MarkdownContent.d.ts +1 -0
- package/esm/typings/src/book-components/Chat/types/ChatMessage.d.ts +7 -11
- package/esm/typings/src/book-components/_common/Dropdown/Dropdown.d.ts +2 -2
- package/esm/typings/src/book-components/_common/MenuHoisting/MenuHoistingContext.d.ts +56 -0
- package/esm/typings/src/collection/agent-collection/constructors/agent-collection-in-supabase/AgentCollectionInSupabase.d.ts +21 -11
- package/esm/typings/src/collection/agent-collection/constructors/agent-collection-in-supabase/AgentsDatabaseSchema.d.ts +80 -14
- package/esm/typings/src/commitments/DICTIONARY/DICTIONARY.d.ts +46 -0
- package/esm/typings/src/commitments/index.d.ts +2 -1
- package/esm/typings/src/llm-providers/_multiple/MultipleLlmExecutionTools.d.ts +6 -2
- package/esm/typings/src/llm-providers/agent/AgentLlmExecutionTools.d.ts +1 -1
- package/esm/typings/src/llm-providers/ollama/OllamaExecutionTools.d.ts +1 -1
- package/esm/typings/src/llm-providers/openai/createOpenAiCompatibleExecutionTools.d.ts +1 -1
- package/esm/typings/src/llm-providers/remote/RemoteLlmExecutionTools.d.ts +1 -0
- package/esm/typings/src/types/Message.d.ts +49 -0
- package/esm/typings/src/types/ModelRequirements.d.ts +38 -14
- package/esm/typings/src/types/typeAliases.d.ts +23 -1
- package/esm/typings/src/utils/color/utils/colorToDataUrl.d.ts +2 -1
- package/esm/typings/src/utils/environment/$detectRuntimeEnvironment.d.ts +4 -4
- package/esm/typings/src/utils/environment/$isRunningInBrowser.d.ts +1 -1
- package/esm/typings/src/utils/environment/$isRunningInJest.d.ts +1 -1
- package/esm/typings/src/utils/environment/$isRunningInNode.d.ts +1 -1
- package/esm/typings/src/utils/environment/$isRunningInWebWorker.d.ts +1 -1
- package/esm/typings/src/utils/markdown/extractAllBlocksFromMarkdown.d.ts +2 -2
- package/esm/typings/src/utils/markdown/extractOneBlockFromMarkdown.d.ts +2 -2
- package/esm/typings/src/utils/random/$randomBase58.d.ts +12 -0
- package/esm/typings/src/version.d.ts +1 -1
- package/package.json +1 -1
- package/umd/index.umd.js +4018 -3865
- package/umd/index.umd.js.map +1 -1
- package/apps/agents-server/package-lock.json +0 -27
- package/apps/agents-server/public/fonts/download-font.js +0 -22
- package/apps/agents-server/src/components/PrintButton/PrintButton.tsx +0 -18
- package/esm/typings/src/book-2.0/utils/generateGravatarUrl.d.ts +0 -10
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { string_markdown } from '@promptbook-local/types';
|
|
2
|
+
import { simpleParser } from 'mailparser';
|
|
3
|
+
import TurndownService from 'turndown';
|
|
4
|
+
import { extractBodyContentFromHtml } from '../../../utils/content/extractBodyContentFromHtml';
|
|
5
|
+
import type { InboundEmail } from '../_common/Email';
|
|
6
|
+
import { parseEmailAddress } from '../_common/utils/parseEmailAddress';
|
|
7
|
+
import { parseEmailAddresses } from '../_common/utils/parseEmailAddresses';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Function parseInboundSendgridEmail will parse raw inbound email from Sendgrid and return Email object
|
|
11
|
+
*/
|
|
12
|
+
export async function parseInboundSendgridEmail(rawEmail: string): Promise<InboundEmail> {
|
|
13
|
+
const parsedEmail = await simpleParser(rawEmail);
|
|
14
|
+
|
|
15
|
+
const toArray = !Array.isArray(parsedEmail.to)
|
|
16
|
+
? parsedEmail.to === undefined
|
|
17
|
+
? []
|
|
18
|
+
: [parsedEmail.to]
|
|
19
|
+
: parsedEmail.to;
|
|
20
|
+
const to = toArray.flatMap((_) => parseEmailAddresses(_.text));
|
|
21
|
+
|
|
22
|
+
const ccArray = !Array.isArray(parsedEmail.cc)
|
|
23
|
+
? parsedEmail.cc === undefined
|
|
24
|
+
? []
|
|
25
|
+
: [parsedEmail.cc]
|
|
26
|
+
: parsedEmail.cc;
|
|
27
|
+
const cc = ccArray.flatMap((_) => parseEmailAddresses(_.text));
|
|
28
|
+
|
|
29
|
+
const turndownService = new TurndownService();
|
|
30
|
+
|
|
31
|
+
const content = (parsedEmail.html
|
|
32
|
+
? turndownService.turndown(extractBodyContentFromHtml(parsedEmail.html))
|
|
33
|
+
: parsedEmail.text || '') as string_markdown;
|
|
34
|
+
|
|
35
|
+
const email: InboundEmail = {
|
|
36
|
+
channel: 'EMAIL',
|
|
37
|
+
direction: 'INBOUND',
|
|
38
|
+
sender: parseEmailAddress(parsedEmail.from?.text || '').fullEmail,
|
|
39
|
+
recipients: to.map((_) => _.fullEmail),
|
|
40
|
+
cc,
|
|
41
|
+
subject: parsedEmail.subject || '',
|
|
42
|
+
content,
|
|
43
|
+
attachments: [
|
|
44
|
+
/* <- TODO: [📯] Parse attachments */
|
|
45
|
+
],
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
return email;
|
|
49
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { removeMarkdownFormatting } from '@promptbook-local/markdown-utils';
|
|
2
|
+
import type { really_any } from '@promptbook-local/types';
|
|
3
|
+
import { marked } from 'marked';
|
|
4
|
+
// @ts-expect-error: Zeptomail types are not resolving correctly
|
|
5
|
+
import { SendMailClient } from 'zeptomail';
|
|
6
|
+
import { MessageProvider } from '../../interfaces/MessageProvider';
|
|
7
|
+
import { OutboundEmail } from '../_common/Email';
|
|
8
|
+
|
|
9
|
+
export class ZeptomailMessageProvider implements MessageProvider {
|
|
10
|
+
constructor(private readonly apiKey: string) {}
|
|
11
|
+
|
|
12
|
+
public async send(message: OutboundEmail): Promise<really_any> {
|
|
13
|
+
try {
|
|
14
|
+
const client = new SendMailClient({ url: 'api.zeptomail.com/', token: this.apiKey });
|
|
15
|
+
|
|
16
|
+
const sender = message.sender as really_any;
|
|
17
|
+
const recipients = (Array.isArray(message.recipients) ? message.recipients : [message.recipients]).filter(
|
|
18
|
+
Boolean,
|
|
19
|
+
) as really_any[];
|
|
20
|
+
|
|
21
|
+
const textbody = removeMarkdownFormatting(message.content);
|
|
22
|
+
const htmlbody = await marked.parse(message.content);
|
|
23
|
+
|
|
24
|
+
const response = await client.sendMail({
|
|
25
|
+
from: {
|
|
26
|
+
address: sender.email || sender.baseEmail || sender,
|
|
27
|
+
name: sender.name || sender.fullName || undefined,
|
|
28
|
+
},
|
|
29
|
+
to: recipients.map((r) => ({
|
|
30
|
+
email_address: {
|
|
31
|
+
address: r.email || r.baseEmail || r,
|
|
32
|
+
name: r.name || r.fullName || undefined,
|
|
33
|
+
},
|
|
34
|
+
})),
|
|
35
|
+
subject: message.metadata?.subject || 'No Subject',
|
|
36
|
+
textbody,
|
|
37
|
+
htmlbody,
|
|
38
|
+
track_clicks: true,
|
|
39
|
+
track_opens: true,
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
return response;
|
|
43
|
+
} catch (raw: really_any) {
|
|
44
|
+
if (!('error' in raw)) {
|
|
45
|
+
throw raw;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
throw new Error(raw.error.message, raw.error.details);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { SendgridMessageProvider } from './email/sendgrid/SendgridMessageProvider';
|
|
2
|
+
import { ZeptomailMessageProvider } from './email/zeptomail/ZeptomailMessageProvider';
|
|
3
|
+
import { MessageProvider } from './interfaces/MessageProvider';
|
|
4
|
+
|
|
5
|
+
export const EMAIL_PROVIDERS: Record<string, MessageProvider> = {};
|
|
6
|
+
|
|
7
|
+
if (process.env.ZEPTOMAIL_API_KEY) {
|
|
8
|
+
EMAIL_PROVIDERS['ZEPTOMAIL'] = new ZeptomailMessageProvider(process.env.ZEPTOMAIL_API_KEY);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
if (process.env.SENDGRID_API_KEY) {
|
|
12
|
+
EMAIL_PROVIDERS['SENDGRID'] = new SendgridMessageProvider(process.env.SENDGRID_API_KEY);
|
|
13
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Message, really_any, string_email } from '@promptbook-local/types';
|
|
2
|
+
|
|
3
|
+
export type MessageProvider = {
|
|
4
|
+
/**
|
|
5
|
+
* Sends a message through the provider
|
|
6
|
+
*
|
|
7
|
+
* @param message The message to send
|
|
8
|
+
* @returns Raw response from the provider
|
|
9
|
+
*/
|
|
10
|
+
send(message: Message<string_email>): Promise<really_any>;
|
|
11
|
+
};
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import { TODO_any } from '@promptbook-local/types';
|
|
2
2
|
import { createClient } from '@supabase/supabase-js';
|
|
3
3
|
import { NextRequest, NextResponse } from 'next/server';
|
|
4
|
-
import { SERVERS
|
|
4
|
+
import { SERVERS } from '../config';
|
|
5
|
+
import { $getTableName } from './database/$getTableName';
|
|
6
|
+
import { RESERVED_PATHS } from './generated/reservedPaths';
|
|
5
7
|
import { isIpAllowed } from './utils/isIpAllowed';
|
|
6
8
|
|
|
7
|
-
// Note: Re-implementing normalizeTo_PascalCase to avoid importing from @promptbook-local/utils which might have Node.js dependencies
|
|
9
|
+
// Note: Re-implementing normalizeTo_PascalCase to avoid importing from @promptbook-local/utils which might have Node.js dependencies !!!!
|
|
8
10
|
function normalizeTo_PascalCase(text: string): string {
|
|
9
11
|
return text
|
|
10
|
-
.replace(/(?:^\w|[A-Z]|\b\w)/g, (word
|
|
12
|
+
.replace(/(?:^\w|[A-Z]|\b\w)/g, (word) => {
|
|
11
13
|
return word.toUpperCase();
|
|
12
14
|
})
|
|
13
15
|
.replace(/\s+/g, '');
|
|
@@ -33,8 +35,12 @@ export async function middleware(req: NextRequest) {
|
|
|
33
35
|
const host = req.headers.get('host');
|
|
34
36
|
|
|
35
37
|
if (host) {
|
|
38
|
+
/*
|
|
39
|
+
Note: [🐔] This code was commented out because results of it are unused
|
|
40
|
+
|
|
36
41
|
let tablePrefix = SUPABASE_TABLE_PREFIX;
|
|
37
42
|
|
|
43
|
+
|
|
38
44
|
if (SERVERS && SERVERS.length > 0) {
|
|
39
45
|
// Logic mirrored from src/tools/$provideServer.ts
|
|
40
46
|
if (SERVERS.some((server) => server === host)) {
|
|
@@ -44,6 +50,7 @@ export async function middleware(req: NextRequest) {
|
|
|
44
50
|
tablePrefix = `server_${serverName}_`;
|
|
45
51
|
}
|
|
46
52
|
}
|
|
53
|
+
*/
|
|
47
54
|
|
|
48
55
|
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL;
|
|
49
56
|
const supabaseKey = process.env.SUPABASE_SERVICE_ROLE_KEY || process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY;
|
|
@@ -58,7 +65,7 @@ export async function middleware(req: NextRequest) {
|
|
|
58
65
|
});
|
|
59
66
|
|
|
60
67
|
const { data } = await supabase
|
|
61
|
-
.from(
|
|
68
|
+
.from(await $getTableName(`Metadata`))
|
|
62
69
|
.select('value')
|
|
63
70
|
.eq('key', 'RESTRICT_IP')
|
|
64
71
|
.single();
|
|
@@ -82,6 +89,9 @@ export async function middleware(req: NextRequest) {
|
|
|
82
89
|
const token = authHeader.split(' ')[1];
|
|
83
90
|
|
|
84
91
|
if (token.startsWith('ptbk_')) {
|
|
92
|
+
/*
|
|
93
|
+
Note: [🐔] This code was commented out because results of it are unused
|
|
94
|
+
|
|
85
95
|
const host = req.headers.get('host');
|
|
86
96
|
let tablePrefix = SUPABASE_TABLE_PREFIX;
|
|
87
97
|
|
|
@@ -93,6 +103,7 @@ export async function middleware(req: NextRequest) {
|
|
|
93
103
|
tablePrefix = `server_${serverName}_`;
|
|
94
104
|
}
|
|
95
105
|
}
|
|
106
|
+
*/
|
|
96
107
|
|
|
97
108
|
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL;
|
|
98
109
|
const supabaseKey = process.env.SUPABASE_SERVICE_ROLE_KEY || process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY;
|
|
@@ -107,7 +118,7 @@ export async function middleware(req: NextRequest) {
|
|
|
107
118
|
});
|
|
108
119
|
|
|
109
120
|
const { data } = await supabase
|
|
110
|
-
.from(
|
|
121
|
+
.from(await $getTableName(`ApiTokens`))
|
|
111
122
|
.select('id')
|
|
112
123
|
.eq('token', token)
|
|
113
124
|
.eq('isRevoked', false)
|
|
@@ -186,22 +197,7 @@ export async function middleware(req: NextRequest) {
|
|
|
186
197
|
|
|
187
198
|
if (
|
|
188
199
|
potentialAgentName &&
|
|
189
|
-
!
|
|
190
|
-
'agents',
|
|
191
|
-
'api',
|
|
192
|
-
'admin',
|
|
193
|
-
'docs',
|
|
194
|
-
'test',
|
|
195
|
-
'embed',
|
|
196
|
-
'_next',
|
|
197
|
-
'manifest.webmanifest',
|
|
198
|
-
'sw.js',
|
|
199
|
-
'favicon.ico',
|
|
200
|
-
'sitemap.xml',
|
|
201
|
-
'robots.txt',
|
|
202
|
-
'security.txt',
|
|
203
|
-
'humans.txt',
|
|
204
|
-
].includes(potentialAgentName) &&
|
|
200
|
+
!RESERVED_PATHS.includes(potentialAgentName) &&
|
|
205
201
|
!potentialAgentName.startsWith('.') &&
|
|
206
202
|
// Note: Other static files are excluded by the matcher configuration below
|
|
207
203
|
true
|
|
@@ -243,7 +239,7 @@ export async function middleware(req: NextRequest) {
|
|
|
243
239
|
let serverName = serverHost;
|
|
244
240
|
serverName = serverName.replace(/\.ptbk\.io$/, '');
|
|
245
241
|
serverName = normalizeTo_PascalCase(serverName);
|
|
246
|
-
const prefix = `server_${serverName}_`;
|
|
242
|
+
// const prefix = `server_${serverName}_`;
|
|
247
243
|
|
|
248
244
|
// Search for agent with matching META LINK
|
|
249
245
|
// agentProfile->links is an array of strings
|
|
@@ -256,7 +252,7 @@ export async function middleware(req: NextRequest) {
|
|
|
256
252
|
|
|
257
253
|
try {
|
|
258
254
|
const { data } = await supabase
|
|
259
|
-
.from(
|
|
255
|
+
.from(await $getTableName(`Agent`))
|
|
260
256
|
.select('agentName')
|
|
261
257
|
.or(orFilter)
|
|
262
258
|
.limit(1)
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { locateChrome } from 'locate-app';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import { BrowserContext, chromium } from 'playwright';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Cache of browser instance
|
|
7
|
+
*
|
|
8
|
+
* @private internal cache for `$provideBrowserForServer`
|
|
9
|
+
*/
|
|
10
|
+
let browserInstance: BrowserContext | null = null;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* @@@
|
|
14
|
+
*/
|
|
15
|
+
export async function $provideBrowserForServer(): Promise<BrowserContext> {
|
|
16
|
+
if (browserInstance !== null && browserInstance.browser() && browserInstance.browser()!.isConnected()) {
|
|
17
|
+
return browserInstance;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
console.log('Launching new browser instance...');
|
|
21
|
+
browserInstance = await chromium.launchPersistentContext(
|
|
22
|
+
join(process.cwd(), '.promptbook', 'puppeteer', 'user-data'),
|
|
23
|
+
{
|
|
24
|
+
executablePath: await locateChrome(),
|
|
25
|
+
headless: false,
|
|
26
|
+
// defaultViewport: null,
|
|
27
|
+
// downloadsPath
|
|
28
|
+
},
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
return browserInstance;
|
|
32
|
+
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { $provideSupabaseForServer } from '../database/$provideSupabaseForServer';
|
|
2
|
+
import { TrackedFilesStorage } from '../utils/cdn/classes/TrackedFilesStorage';
|
|
1
3
|
import { VercelBlobStorage } from '../utils/cdn/classes/VercelBlobStorage';
|
|
2
4
|
import { IIFilesStorageWithCdn } from '../utils/cdn/interfaces/IFilesStorage';
|
|
3
5
|
|
|
@@ -9,16 +11,19 @@ import { IIFilesStorageWithCdn } from '../utils/cdn/interfaces/IFilesStorage';
|
|
|
9
11
|
let cdn: IIFilesStorageWithCdn | null = null;
|
|
10
12
|
|
|
11
13
|
/**
|
|
12
|
-
*
|
|
14
|
+
* @@@
|
|
13
15
|
*/
|
|
14
16
|
export function $provideCdnForServer(): IIFilesStorageWithCdn {
|
|
15
17
|
if (!cdn) {
|
|
16
|
-
|
|
18
|
+
const inner = new VercelBlobStorage({
|
|
17
19
|
token: process.env.VERCEL_BLOB_READ_WRITE_TOKEN!,
|
|
18
20
|
pathPrefix: process.env.NEXT_PUBLIC_CDN_PATH_PREFIX!,
|
|
19
21
|
cdnPublicUrl: new URL(process.env.NEXT_PUBLIC_CDN_PUBLIC_URL!),
|
|
20
22
|
});
|
|
21
23
|
|
|
24
|
+
const supabase = $provideSupabaseForServer();
|
|
25
|
+
cdn = new TrackedFilesStorage(inner, supabase);
|
|
26
|
+
|
|
22
27
|
/*
|
|
23
28
|
cdn = new DigitalOceanSpaces({
|
|
24
29
|
bucket: process.env.CDN_BUCKET!,
|
|
@@ -1,33 +1,133 @@
|
|
|
1
|
-
import { randomBytes, scrypt, timingSafeEqual } from 'crypto';
|
|
1
|
+
import { createHash, randomBytes, scrypt, timingSafeEqual } from 'crypto';
|
|
2
2
|
import { promisify } from 'util';
|
|
3
|
+
import { PASSWORD_SECURITY_CONFIG } from '../../../../security.config';
|
|
3
4
|
|
|
4
5
|
const scryptAsync = promisify(scrypt);
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* @param password The
|
|
8
|
+
* Validates password input to prevent edge cases and DoS attacks
|
|
9
|
+
*
|
|
10
|
+
* @param password The password to validate
|
|
11
|
+
* @throws Error if password is invalid
|
|
12
|
+
*/
|
|
13
|
+
function validatePasswordInput(password: string): void {
|
|
14
|
+
if (typeof password !== 'string') {
|
|
15
|
+
throw new Error('Password must be a string');
|
|
16
|
+
}
|
|
17
|
+
if (password.length === 0) {
|
|
18
|
+
throw new Error('Password cannot be empty');
|
|
19
|
+
}
|
|
20
|
+
if (password.length < PASSWORD_SECURITY_CONFIG.MIN_PASSWORD_LENGTH) {
|
|
21
|
+
throw new Error(`Password must be at least ${PASSWORD_SECURITY_CONFIG.MIN_PASSWORD_LENGTH} characters`);
|
|
22
|
+
}
|
|
23
|
+
// Note: No hard max limit - long passwords are compacted via compactPassword()
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Compacts a password for secure processing
|
|
28
|
+
*
|
|
29
|
+
* If the password is within MAX_PASSWORD_LENGTH, it is returned as-is.
|
|
30
|
+
* If longer, the password is split at MAX_PASSWORD_LENGTH and the second part
|
|
31
|
+
* is hashed with SHA256 before being appended to the first part.
|
|
32
|
+
*
|
|
33
|
+
* This prevents DoS attacks via extremely long passwords while still utilizing
|
|
34
|
+
* the full entropy of longer passwords.
|
|
35
|
+
*
|
|
36
|
+
* @param password The password to compact
|
|
37
|
+
* @returns The compacted password
|
|
38
|
+
*/
|
|
39
|
+
function compactPassword(password: string): string {
|
|
40
|
+
if (password.length <= PASSWORD_SECURITY_CONFIG.MAX_PASSWORD_LENGTH) {
|
|
41
|
+
return password;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const firstPart = password.slice(0, PASSWORD_SECURITY_CONFIG.MAX_PASSWORD_LENGTH);
|
|
45
|
+
const secondPart = password.slice(PASSWORD_SECURITY_CONFIG.MAX_PASSWORD_LENGTH);
|
|
46
|
+
|
|
47
|
+
// Hash the overflow part with SHA256 to bound its length while preserving entropy
|
|
48
|
+
const secondPartHash = createHash('sha256').update(secondPart, 'utf8').digest('hex');
|
|
49
|
+
|
|
50
|
+
return firstPart + secondPartHash;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Hashes a password using scrypt with secure parameters
|
|
55
|
+
*
|
|
56
|
+
* @param password The plain text password (minimum 8 characters, no maximum - long passwords are compacted)
|
|
10
57
|
* @returns The salt and hash formatted as "salt:hash"
|
|
58
|
+
* @throws Error if password validation fails
|
|
11
59
|
*/
|
|
12
60
|
export async function hashPassword(password: string): Promise<string> {
|
|
13
|
-
|
|
14
|
-
|
|
61
|
+
validatePasswordInput(password);
|
|
62
|
+
|
|
63
|
+
// Compact long passwords to prevent DoS while preserving entropy
|
|
64
|
+
const compactedPassword = compactPassword(password);
|
|
65
|
+
|
|
66
|
+
const salt = randomBytes(PASSWORD_SECURITY_CONFIG.SALT_LENGTH).toString('hex');
|
|
67
|
+
const derivedKey = (await scryptAsync(compactedPassword, salt, PASSWORD_SECURITY_CONFIG.KEY_LENGTH)) as Buffer;
|
|
68
|
+
|
|
69
|
+
// Clear password from memory as soon as possible (best effort)
|
|
70
|
+
// Note: JavaScript strings are immutable, so this is limited in effectiveness
|
|
15
71
|
return `${salt}:${derivedKey.toString('hex')}`;
|
|
16
72
|
}
|
|
17
73
|
|
|
18
74
|
/**
|
|
19
|
-
* Verifies a password against a stored hash
|
|
20
|
-
*
|
|
21
|
-
* @param password The plain text password
|
|
75
|
+
* Verifies a password against a stored hash using constant-time comparison
|
|
76
|
+
*
|
|
77
|
+
* @param password The plain text password to verify
|
|
22
78
|
* @param storedHash The stored hash in format "salt:hash"
|
|
23
|
-
* @returns True if the password matches
|
|
79
|
+
* @returns True if the password matches, false otherwise
|
|
24
80
|
*/
|
|
25
81
|
export async function verifyPassword(password: string, storedHash: string): Promise<boolean> {
|
|
26
|
-
|
|
27
|
-
if (
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
82
|
+
// Validate inputs
|
|
83
|
+
if (typeof password !== 'string' || typeof storedHash !== 'string') {
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (password.length === 0) {
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Compact long passwords the same way as during hashing
|
|
92
|
+
const compactedPassword = compactPassword(password);
|
|
93
|
+
|
|
94
|
+
const parts = storedHash.split(':');
|
|
95
|
+
if (parts.length !== 2) {
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const [salt, key] = parts;
|
|
100
|
+
if (!salt || !key) {
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Validate salt and key format (should be hex strings of expected length)
|
|
105
|
+
const expectedSaltLength = PASSWORD_SECURITY_CONFIG.SALT_LENGTH * 2; // hex encoding doubles length
|
|
106
|
+
const expectedKeyLength = PASSWORD_SECURITY_CONFIG.KEY_LENGTH * 2;
|
|
107
|
+
|
|
108
|
+
if (salt.length !== expectedSaltLength || key.length !== expectedKeyLength) {
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Validate hex format
|
|
113
|
+
const hexRegex = /^[0-9a-fA-F]+$/;
|
|
114
|
+
if (!hexRegex.test(salt) || !hexRegex.test(key)) {
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
try {
|
|
119
|
+
const derivedKey = (await scryptAsync(compactedPassword, salt, PASSWORD_SECURITY_CONFIG.KEY_LENGTH)) as Buffer;
|
|
120
|
+
const keyBuffer = Buffer.from(key, 'hex');
|
|
121
|
+
|
|
122
|
+
// Ensure buffers are same length before timing-safe comparison
|
|
123
|
+
// This should always be true given our validation, but defense in depth
|
|
124
|
+
if (derivedKey.length !== keyBuffer.length) {
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return timingSafeEqual(derivedKey, keyBuffer);
|
|
129
|
+
} catch {
|
|
130
|
+
// Any error during verification should return false, not leak information
|
|
131
|
+
return false;
|
|
132
|
+
}
|
|
33
133
|
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { SupabaseClient } from '@supabase/supabase-js';
|
|
2
|
+
import { $getTableName } from '../../../database/$getTableName';
|
|
3
|
+
import { AgentsServerDatabase } from '../../../database/schema';
|
|
4
|
+
import type { IFile, IIFilesStorageWithCdn } from '../interfaces/IFilesStorage';
|
|
5
|
+
|
|
6
|
+
export class TrackedFilesStorage implements IIFilesStorageWithCdn {
|
|
7
|
+
public constructor(
|
|
8
|
+
private readonly inner: IIFilesStorageWithCdn,
|
|
9
|
+
private readonly supabase: SupabaseClient<AgentsServerDatabase>,
|
|
10
|
+
) {}
|
|
11
|
+
|
|
12
|
+
public get cdnPublicUrl(): URL {
|
|
13
|
+
return this.inner.cdnPublicUrl;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
public get pathPrefix(): string | undefined {
|
|
17
|
+
return this.inner.pathPrefix;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
public getItemUrl(key: string): URL {
|
|
21
|
+
return this.inner.getItemUrl(key);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
public async getItem(key: string): Promise<IFile | null> {
|
|
25
|
+
return this.inner.getItem(key);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
public async removeItem(key: string): Promise<void> {
|
|
29
|
+
return this.inner.removeItem(key);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
public async setItem(key: string, file: IFile): Promise<void> {
|
|
33
|
+
console.log('!!! 0', { key, file });
|
|
34
|
+
|
|
35
|
+
await this.inner.setItem(key, file);
|
|
36
|
+
|
|
37
|
+
try {
|
|
38
|
+
const { userId, purpose } = file;
|
|
39
|
+
const cdnUrl = this.getItemUrl(key).href;
|
|
40
|
+
|
|
41
|
+
console.log('!!! 1', { userId, purpose, cdnUrl, key, file });
|
|
42
|
+
|
|
43
|
+
await this.supabase.from(await $getTableName('File')).insert({
|
|
44
|
+
userId: userId || null,
|
|
45
|
+
fileName: key,
|
|
46
|
+
fileSize: file.fileSize ?? file.data.length,
|
|
47
|
+
fileType: file.type,
|
|
48
|
+
cdnUrl,
|
|
49
|
+
purpose: purpose || 'UNKNOWN',
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
console.log('!!! 2', { userId, purpose, cdnUrl, key, file });
|
|
53
|
+
} catch (error) {
|
|
54
|
+
console.error('Failed to track upload:', error);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -14,6 +14,10 @@ export class VercelBlobStorage implements IIFilesStorageWithCdn {
|
|
|
14
14
|
return this.config.cdnPublicUrl;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
+
public get pathPrefix() {
|
|
18
|
+
return this.config.pathPrefix;
|
|
19
|
+
}
|
|
20
|
+
|
|
17
21
|
public constructor(private readonly config: IVercelBlobStorageConfig) {}
|
|
18
22
|
|
|
19
23
|
public getItemUrl(key: string): URL {
|
|
@@ -5,6 +5,23 @@ export type IFile = {
|
|
|
5
5
|
// Maybe TODO name: string_name;
|
|
6
6
|
type: string_mime_type;
|
|
7
7
|
data: Buffer;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* User who uploaded the file
|
|
11
|
+
*/
|
|
12
|
+
userId?: number;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Purpose of the upload (e.g. KNOWLEDGE, SERVER_FAVICON_URL)
|
|
16
|
+
*/
|
|
17
|
+
purpose?: string;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Size of the file in bytes
|
|
21
|
+
*
|
|
22
|
+
* Note: This is optional, if not provided, the size of the buffer is used
|
|
23
|
+
*/
|
|
24
|
+
fileSize?: number;
|
|
8
25
|
};
|
|
9
26
|
|
|
10
27
|
/**
|
|
@@ -17,6 +34,7 @@ export type IFilesStorage = Omit<IStorage<IFile>, 'length' | 'clear' | 'key'>;
|
|
|
17
34
|
*/
|
|
18
35
|
export type IIFilesStorageWithCdn = IFilesStorage & {
|
|
19
36
|
readonly cdnPublicUrl: URL;
|
|
37
|
+
readonly pathPrefix?: string;
|
|
20
38
|
getItemUrl(key: string): URL;
|
|
21
39
|
};
|
|
22
40
|
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { string_html } from '@promptbook-local/types';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Extract the first heading from HTML
|
|
5
|
+
*
|
|
6
|
+
* @param contentText HTML
|
|
7
|
+
* @returns heading
|
|
8
|
+
*/
|
|
9
|
+
export function extractBodyContentFromHtml(html: string_html): string_html {
|
|
10
|
+
// Note: Not using DOMParser, because it's overkill for this simple task
|
|
11
|
+
|
|
12
|
+
const match = html.match(/<body[^>]*>(?<bodyContent>[\s\S]*)<\/body>/s);
|
|
13
|
+
|
|
14
|
+
if (!match) {
|
|
15
|
+
return html;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return match.groups!.bodyContent!;
|
|
19
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { NextRequest } from 'next/server';
|
|
2
|
+
import { keepUnused } from '../../../../src/utils/organization/keepUnused';
|
|
3
|
+
import { $getTableName } from '../database/$getTableName';
|
|
4
|
+
import { $provideSupabaseForServer } from '../database/$provideSupabaseForServer';
|
|
5
|
+
import { getSession } from './session';
|
|
6
|
+
|
|
7
|
+
export async function getUserIdFromRequest(request: NextRequest): Promise<number | null> {
|
|
8
|
+
keepUnused(request); // Unused because we get user from session cookie for now
|
|
9
|
+
|
|
10
|
+
try {
|
|
11
|
+
// 1. Try to get user from session (cookie)
|
|
12
|
+
const session = await getSession();
|
|
13
|
+
if (session && session.username) {
|
|
14
|
+
const supabase = $provideSupabaseForServer();
|
|
15
|
+
const { data } = await supabase
|
|
16
|
+
.from(await $getTableName('User'))
|
|
17
|
+
.select('id')
|
|
18
|
+
.eq('username', session.username)
|
|
19
|
+
.single();
|
|
20
|
+
|
|
21
|
+
if (data) {
|
|
22
|
+
return data.id;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// 2. Try to get user from API key (Authorization header)
|
|
27
|
+
// TODO: [🧠] Implement linking API keys to users if needed
|
|
28
|
+
// const authHeader = request.headers.get('authorization');
|
|
29
|
+
// ...
|
|
30
|
+
} catch (error) {
|
|
31
|
+
console.error('Error getting user ID from request:', error);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return null;
|
|
35
|
+
}
|