@superinterface/server 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +36 -0
- package/eslint.config.mjs +31 -0
- package/next.config.ts +7 -0
- package/package.json +176 -0
- package/prisma/Account.prisma +18 -0
- package/prisma/ApiKey.prisma +13 -0
- package/prisma/Assistant.prisma +32 -0
- package/prisma/AssistantHandler.prisma +12 -0
- package/prisma/Avatar.prisma +10 -0
- package/prisma/ClientToolHandler.prisma +13 -0
- package/prisma/CodeInterpreterTool.prisma +7 -0
- package/prisma/ComputerUseTool.prisma +12 -0
- package/prisma/CreateTaskHandler.prisma +10 -0
- package/prisma/DeleteTaskHandler.prisma +10 -0
- package/prisma/FileSearchTool.prisma +9 -0
- package/prisma/FirecrawlHandler.prisma +13 -0
- package/prisma/Function.prisma +13 -0
- package/prisma/Handler.prisma +19 -0
- package/prisma/HttpTransport.prisma +12 -0
- package/prisma/IconAvatar.prisma +8 -0
- package/prisma/ImageAvatar.prisma +8 -0
- package/prisma/ImageGenerationTool.prisma +11 -0
- package/prisma/InitialMessage.prisma +17 -0
- package/prisma/Invitation.prisma +15 -0
- package/prisma/ListTasksHandler.prisma +10 -0
- package/prisma/Log.prisma +21 -0
- package/prisma/McpServer.prisma +14 -0
- package/prisma/Message.prisma +30 -0
- package/prisma/ModelProvider.prisma +15 -0
- package/prisma/Organization.prisma +10 -0
- package/prisma/OrganizationApiKey.prisma +12 -0
- package/prisma/OrganizationInvitation.prisma +15 -0
- package/prisma/OrganizationUserRole.prisma +14 -0
- package/prisma/ReplicateHandler.prisma +14 -0
- package/prisma/RequestHandler.prisma +15 -0
- package/prisma/Run.prisma +36 -0
- package/prisma/RunStep.prisma +30 -0
- package/prisma/Session.prisma +7 -0
- package/prisma/SseTransport.prisma +12 -0
- package/prisma/StdioTransport.prisma +11 -0
- package/prisma/Task.prisma +15 -0
- package/prisma/Thread.prisma +20 -0
- package/prisma/Tool.prisma +13 -0
- package/prisma/UpdateTaskHandler.prisma +10 -0
- package/prisma/User.prisma +16 -0
- package/prisma/UserRole.prisma +14 -0
- package/prisma/VerificationToken.prisma +7 -0
- package/prisma/WebSearchTool.prisma +7 -0
- package/prisma/Workspace.prisma +14 -0
- package/prisma/enums/ApiKeyType.prisma +4 -0
- package/prisma/enums/AvatarType.prisma +5 -0
- package/prisma/enums/ClientToolHandlerType.prisma +3 -0
- package/prisma/enums/ComputerUseToolEnvironment.prisma +6 -0
- package/prisma/enums/FirecrawlHandlerType.prisma +6 -0
- package/prisma/enums/HandlerType.prisma +11 -0
- package/prisma/enums/IconAvatarName.prisma +14 -0
- package/prisma/enums/ImageGenerationToolOutputFormat.prisma +5 -0
- package/prisma/enums/ImageGenerationToolQuality.prisma +6 -0
- package/prisma/enums/ImageGenerationToolSize.prisma +6 -0
- package/prisma/enums/LogLevel.prisma +5 -0
- package/prisma/enums/LogRequestMethod.prisma +7 -0
- package/prisma/enums/LogRequestRoute.prisma +6 -0
- package/prisma/enums/MessageRole.prisma +4 -0
- package/prisma/enums/MessageStatus.prisma +5 -0
- package/prisma/enums/MethodType.prisma +7 -0
- package/prisma/enums/ModelProviderType.prisma +13 -0
- package/prisma/enums/OrganizationUserRoleType.prisma +3 -0
- package/prisma/enums/ReplicateHandlerType.prisma +3 -0
- package/prisma/enums/RunStatus.prisma +10 -0
- package/prisma/enums/RunStepStatus.prisma +7 -0
- package/prisma/enums/RunStepType.prisma +4 -0
- package/prisma/enums/StorageProviderType.prisma +7 -0
- package/prisma/enums/ToolType.prisma +7 -0
- package/prisma/enums/TransportType.prisma +5 -0
- package/prisma/enums/TruncationType.prisma +5 -0
- package/prisma/enums/UserRoleType.prisma +3 -0
- package/prisma/migrations/20251006235143_initial_setup/migration.sql +986 -0
- package/prisma/migrations/20251007163926_add_truncation_type/migration.sql +6 -0
- package/prisma/migrations/20251007190703_add_organizations/migration.sql +97 -0
- package/prisma/migrations/migration_lock.toml +3 -0
- package/prisma/schema.prisma +13 -0
- package/prisma.config.ts +6 -0
- package/scripts/cli.ts +84 -0
- package/scripts/commands/organizations/api-keys/create.ts +159 -0
- package/scripts/commands/organizations/create.ts +82 -0
- package/scripts/utils/env.ts +31 -0
- package/scripts/utils/errors.ts +6 -0
- package/src/app/api/api-keys/[apiKeyId]/route.ts +178 -0
- package/src/app/api/api-keys/route.ts +147 -0
- package/src/app/api/assistants/[assistantId]/functions/[functionId]/route.ts +245 -0
- package/src/app/api/assistants/[assistantId]/functions/route.ts +157 -0
- package/src/app/api/assistants/[assistantId]/initial-messages/route.ts +127 -0
- package/src/app/api/assistants/[assistantId]/mcp-servers/[mcpServerId]/route.ts +243 -0
- package/src/app/api/assistants/[assistantId]/mcp-servers/route.ts +163 -0
- package/src/app/api/assistants/[assistantId]/route.ts +336 -0
- package/src/app/api/assistants/route.ts +196 -0
- package/src/app/api/files/[fileId]/contents/route.ts +145 -0
- package/src/app/api/files/route.ts +117 -0
- package/src/app/api/messages/lib/getWorkspaceId.ts +12 -0
- package/src/app/api/messages/lib/initialMessagesResponse.ts +90 -0
- package/src/app/api/messages/lib/serializeThread.ts +22 -0
- package/src/app/api/messages/route.ts +710 -0
- package/src/app/api/providers/[modelProviderId]/assistants/route.ts +68 -0
- package/src/app/api/providers/[modelProviderId]/models/route.ts +68 -0
- package/src/app/api/providers/[modelProviderId]/route.ts +202 -0
- package/src/app/api/providers/route.ts +105 -0
- package/src/app/api/tasks/[taskId]/route.ts +213 -0
- package/src/app/api/tasks/callback/route.ts +280 -0
- package/src/app/api/tasks/route.ts +121 -0
- package/src/app/api/threads/runs/submit-client-tool-outputs/route.ts +54 -0
- package/src/app/api/workspaces/[workspaceId]/route.ts +137 -0
- package/src/app/api/workspaces/route.ts +139 -0
- package/src/app/layout.tsx +9 -0
- package/src/app/page.tsx +3 -0
- package/src/lib/apiKeys/formatApiKeyName.ts +13 -0
- package/src/lib/apiKeys/getApiKey.ts +25 -0
- package/src/lib/apiKeys/serializeApiKey.ts +11 -0
- package/src/lib/apiKeys/workspaceAccessWhere.ts +21 -0
- package/src/lib/assistants/assistantClientAdapter/buildGetOpenaiAssistant.ts +96 -0
- package/src/lib/assistants/assistantClientAdapter/index.ts +165 -0
- package/src/lib/assistants/formatName.ts +9 -0
- package/src/lib/assistants/serializeApiAssistant.ts +40 -0
- package/src/lib/assistants/serializeAssistant.ts +31 -0
- package/src/lib/assistants/storageAssistantId.ts +29 -0
- package/src/lib/avatars/defaultAvatar.ts +15 -0
- package/src/lib/avatars/serializeAvatar.ts +26 -0
- package/src/lib/cache/cacheHeaders.ts +5 -0
- package/src/lib/computerCalls/handleComputerCall/index.ts +173 -0
- package/src/lib/errors/index.ts +10 -0
- package/src/lib/errors/serializeError.ts +11 -0
- package/src/lib/functions/createFunction.ts +32 -0
- package/src/lib/functions/functionSchema.ts +201 -0
- package/src/lib/functions/handleFunction/getFunction.ts +43 -0
- package/src/lib/functions/handleFunction/handleAssistant.ts +399 -0
- package/src/lib/functions/handleFunction/handleClientTool.ts +127 -0
- package/src/lib/functions/handleFunction/handleFirecrawl.ts +212 -0
- package/src/lib/functions/handleFunction/handleReplicate.ts +109 -0
- package/src/lib/functions/handleFunction/handleRequest.ts +272 -0
- package/src/lib/functions/handleFunction/index.ts +474 -0
- package/src/lib/functions/handleFunction/tasks/handleCreateTask.ts +58 -0
- package/src/lib/functions/handleFunction/tasks/handleDeleteTask.ts +62 -0
- package/src/lib/functions/handleFunction/tasks/handleListTasks.ts +53 -0
- package/src/lib/functions/handleFunction/tasks/handleUpdateTask.ts +70 -0
- package/src/lib/functions/interpolateFunctionValue.ts +26 -0
- package/src/lib/functions/serializeApiFunction.ts +32 -0
- package/src/lib/functions/updateFunction.ts +42 -0
- package/src/lib/handlers/handlerPrismaInput.tsx +141 -0
- package/src/lib/handlers/serializeApiHandler.ts +94 -0
- package/src/lib/iconAvatars/serializeIconAvatar.ts +9 -0
- package/src/lib/imageAvatars/serializeImageAvatar.ts +9 -0
- package/src/lib/initialMessages/schema.ts +11 -0
- package/src/lib/initialMessages/serializeApiInitialMessage.ts +15 -0
- package/src/lib/initialMessages/updateInitialMessages.ts +33 -0
- package/src/lib/logs/createLog.ts +17 -0
- package/src/lib/mcpServers/closeMcpConnection.ts +16 -0
- package/src/lib/mcpServers/connectMcpServer.ts +117 -0
- package/src/lib/mcpServers/getToolCallMcpServer.ts +62 -0
- package/src/lib/mcpServers/headers.ts +113 -0
- package/src/lib/mcpServers/mcpServerSchema.ts +77 -0
- package/src/lib/mcpServers/serializeApiMcpServer.ts +51 -0
- package/src/lib/mcpServers/url.ts +98 -0
- package/src/lib/messages/content.ts +60 -0
- package/src/lib/messages/textContent.ts +13 -0
- package/src/lib/metadata/serializeMetadata.ts +34 -0
- package/src/lib/misc/isJSON.ts +9 -0
- package/src/lib/misc/merge/customizer.ts +8 -0
- package/src/lib/misc/merge/index.ts +6 -0
- package/src/lib/modelProviders/buildAzureOpenaiClientAdapter.ts +33 -0
- package/src/lib/modelProviders/buildOpenaiClient.ts +14 -0
- package/src/lib/modelProviders/buildOpenaiClientAdapter.ts +30 -0
- package/src/lib/modelProviders/clientAdapter.ts +121 -0
- package/src/lib/modelProviders/isModelProviderValid.ts +23 -0
- package/src/lib/modelProviders/modelProviderConfigs.ts +221 -0
- package/src/lib/modelProviders/serializeModelProvider.ts +19 -0
- package/src/lib/models/getModels.ts +27 -0
- package/src/lib/models/serializeApiModel.ts +5 -0
- package/src/lib/openai/getOpenaiAssistants.ts +30 -0
- package/src/lib/organizationApiKeys/getOrganizationApiKey.ts +23 -0
- package/src/lib/prisma/index.ts +29 -0
- package/src/lib/redis/index.ts +3 -0
- package/src/lib/runs/createRunOpts.ts +80 -0
- package/src/lib/storageProviders/getStorageProviderAssistants.ts +19 -0
- package/src/lib/storageProviders/getStorageProviderType.ts +21 -0
- package/src/lib/storageProviders/isOpenaiAssistantsStorageProvider.ts +8 -0
- package/src/lib/storageProviders/isResponsesStorageProvider.ts +8 -0
- package/src/lib/storageProviders/openaiAssistantsStorageProviderTypes.ts +6 -0
- package/src/lib/storageProviders/responsesStorageProviderTypes.ts +6 -0
- package/src/lib/storageProviders/serializeApiStorageProviderAssistant.ts +14 -0
- package/src/lib/tasks/cancelScheduledTask.ts +13 -0
- package/src/lib/tasks/getNextOccurrence.ts +77 -0
- package/src/lib/tasks/getTaskToolKey.ts +49 -0
- package/src/lib/tasks/parseTaskToolArgs.ts +101 -0
- package/src/lib/tasks/scheduleSchema.ts +34 -0
- package/src/lib/tasks/scheduleTask.ts +37 -0
- package/src/lib/tasks/schemas.ts +25 -0
- package/src/lib/tasks/serializeTask.ts +14 -0
- package/src/lib/tasks/validateSchedule.ts +23 -0
- package/src/lib/themes/defaultTheme.ts +7 -0
- package/src/lib/themes/serializeAccentColor.ts +9 -0
- package/src/lib/themes/serializeTheme/index.ts +25 -0
- package/src/lib/themes/serializeTheme/serializeScaling.ts +14 -0
- package/src/lib/threads/createThread/index.ts +185 -0
- package/src/lib/threads/createThread/initialMessages.ts +36 -0
- package/src/lib/threads/managedOpenaiThreadId.ts +72 -0
- package/src/lib/threads/storageThreadId.ts +49 -0
- package/src/lib/threads/validThreadId.ts +10 -0
- package/src/lib/toolCalls/handleToolCall.ts +121 -0
- package/src/lib/toolCalls/messagesToOutput.ts +8 -0
- package/src/lib/toolCalls/streamOutput.ts +106 -0
- package/src/lib/tools/tools/index.ts +316 -0
- package/src/lib/upstash/qstash.ts +5 -0
- package/src/lib/upstash/upstashWorkflowClient.ts +5 -0
- package/src/lib/workspaces/serializeApiWorkspace.ts +12 -0
- package/src/types/index.ts +133 -0
- package/tsconfig.build.json +13 -0
- package/tsconfig.json +27 -0
- package/types/prisma.d.ts +4 -0
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
-- CreateEnum
|
|
2
|
+
CREATE TYPE "OrganizationUserRoleType" AS ENUM ('ADMIN');
|
|
3
|
+
|
|
4
|
+
-- AlterTable
|
|
5
|
+
ALTER TABLE "Workspace" ADD COLUMN "organizationId" UUID;
|
|
6
|
+
|
|
7
|
+
-- CreateTable
|
|
8
|
+
CREATE TABLE "Organization" (
|
|
9
|
+
"id" UUID NOT NULL DEFAULT gen_random_uuid(),
|
|
10
|
+
"name" TEXT NOT NULL DEFAULT '',
|
|
11
|
+
"createdAt" TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
12
|
+
"updatedAt" TIMESTAMPTZ(6) NOT NULL,
|
|
13
|
+
|
|
14
|
+
CONSTRAINT "Organization_pkey" PRIMARY KEY ("id")
|
|
15
|
+
);
|
|
16
|
+
|
|
17
|
+
-- CreateTable
|
|
18
|
+
CREATE TABLE "OrganizationApiKey" (
|
|
19
|
+
"id" UUID NOT NULL DEFAULT gen_random_uuid(),
|
|
20
|
+
"name" TEXT NOT NULL DEFAULT '',
|
|
21
|
+
"value" UUID NOT NULL DEFAULT gen_random_uuid(),
|
|
22
|
+
"organizationId" UUID NOT NULL,
|
|
23
|
+
"createdAt" TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
24
|
+
"updatedAt" TIMESTAMPTZ(6) NOT NULL,
|
|
25
|
+
|
|
26
|
+
CONSTRAINT "OrganizationApiKey_pkey" PRIMARY KEY ("id")
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
-- CreateTable
|
|
30
|
+
CREATE TABLE "OrganizationInvitation" (
|
|
31
|
+
"id" UUID NOT NULL DEFAULT gen_random_uuid(),
|
|
32
|
+
"email" TEXT NOT NULL,
|
|
33
|
+
"roleType" "OrganizationUserRoleType" NOT NULL DEFAULT 'ADMIN',
|
|
34
|
+
"createdByUserId" TEXT NOT NULL,
|
|
35
|
+
"organizationId" UUID NOT NULL,
|
|
36
|
+
"createdAt" TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
37
|
+
"updatedAt" TIMESTAMPTZ(6) NOT NULL,
|
|
38
|
+
|
|
39
|
+
CONSTRAINT "OrganizationInvitation_pkey" PRIMARY KEY ("id")
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
-- CreateTable
|
|
43
|
+
CREATE TABLE "OrganizationUserRole" (
|
|
44
|
+
"id" UUID NOT NULL DEFAULT gen_random_uuid(),
|
|
45
|
+
"type" "OrganizationUserRoleType" NOT NULL DEFAULT 'ADMIN',
|
|
46
|
+
"userId" TEXT NOT NULL,
|
|
47
|
+
"organizationId" UUID NOT NULL,
|
|
48
|
+
"createdAt" TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
49
|
+
"updatedAt" TIMESTAMPTZ(6) NOT NULL,
|
|
50
|
+
|
|
51
|
+
CONSTRAINT "OrganizationUserRole_pkey" PRIMARY KEY ("id")
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
-- CreateIndex
|
|
55
|
+
CREATE UNIQUE INDEX "OrganizationApiKey_value_key" ON "OrganizationApiKey"("value");
|
|
56
|
+
|
|
57
|
+
-- CreateIndex
|
|
58
|
+
CREATE INDEX "OrganizationApiKey_value_idx" ON "OrganizationApiKey"("value");
|
|
59
|
+
|
|
60
|
+
-- CreateIndex
|
|
61
|
+
CREATE INDEX "OrganizationApiKey_organizationId_idx" ON "OrganizationApiKey"("organizationId");
|
|
62
|
+
|
|
63
|
+
-- CreateIndex
|
|
64
|
+
CREATE INDEX "OrganizationInvitation_createdByUserId_idx" ON "OrganizationInvitation"("createdByUserId");
|
|
65
|
+
|
|
66
|
+
-- CreateIndex
|
|
67
|
+
CREATE INDEX "OrganizationInvitation_organizationId_idx" ON "OrganizationInvitation"("organizationId");
|
|
68
|
+
|
|
69
|
+
-- CreateIndex
|
|
70
|
+
CREATE UNIQUE INDEX "OrganizationInvitation_organizationId_email_key" ON "OrganizationInvitation"("organizationId", "email");
|
|
71
|
+
|
|
72
|
+
-- CreateIndex
|
|
73
|
+
CREATE INDEX "OrganizationUserRole_userId_idx" ON "OrganizationUserRole"("userId");
|
|
74
|
+
|
|
75
|
+
-- CreateIndex
|
|
76
|
+
CREATE INDEX "OrganizationUserRole_organizationId_idx" ON "OrganizationUserRole"("organizationId");
|
|
77
|
+
|
|
78
|
+
-- CreateIndex
|
|
79
|
+
CREATE UNIQUE INDEX "OrganizationUserRole_organizationId_userId_key" ON "OrganizationUserRole"("organizationId", "userId");
|
|
80
|
+
|
|
81
|
+
-- AddForeignKey
|
|
82
|
+
ALTER TABLE "OrganizationApiKey" ADD CONSTRAINT "OrganizationApiKey_organizationId_fkey" FOREIGN KEY ("organizationId") REFERENCES "Organization"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
|
83
|
+
|
|
84
|
+
-- AddForeignKey
|
|
85
|
+
ALTER TABLE "OrganizationInvitation" ADD CONSTRAINT "OrganizationInvitation_createdByUserId_fkey" FOREIGN KEY ("createdByUserId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
|
86
|
+
|
|
87
|
+
-- AddForeignKey
|
|
88
|
+
ALTER TABLE "OrganizationInvitation" ADD CONSTRAINT "OrganizationInvitation_organizationId_fkey" FOREIGN KEY ("organizationId") REFERENCES "Organization"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
|
89
|
+
|
|
90
|
+
-- AddForeignKey
|
|
91
|
+
ALTER TABLE "OrganizationUserRole" ADD CONSTRAINT "OrganizationUserRole_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
|
92
|
+
|
|
93
|
+
-- AddForeignKey
|
|
94
|
+
ALTER TABLE "OrganizationUserRole" ADD CONSTRAINT "OrganizationUserRole_organizationId_fkey" FOREIGN KEY ("organizationId") REFERENCES "Organization"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
|
95
|
+
|
|
96
|
+
-- AddForeignKey
|
|
97
|
+
ALTER TABLE "Workspace" ADD CONSTRAINT "Workspace_organizationId_fkey" FOREIGN KEY ("organizationId") REFERENCES "Organization"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
package/prisma.config.ts
ADDED
package/scripts/cli.ts
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import process from 'node:process'
|
|
3
|
+
import type { PrismaClient } from '@prisma/client'
|
|
4
|
+
import { Command } from 'commander'
|
|
5
|
+
import { createOrganization } from './commands/organizations/create'
|
|
6
|
+
import { createOrganizationApiKey } from './commands/organizations/api-keys/create'
|
|
7
|
+
import { CliError } from './utils/errors'
|
|
8
|
+
import { ensureEnv, ensureDatabaseUrl } from './utils/env'
|
|
9
|
+
|
|
10
|
+
const program = new Command()
|
|
11
|
+
.name('superinterface-cli')
|
|
12
|
+
.description('Administrative helpers for @superinterface/server')
|
|
13
|
+
.showHelpAfterError('(add --help for additional information)')
|
|
14
|
+
|
|
15
|
+
type ActionOptions<Options> = Options & { prisma: PrismaClient }
|
|
16
|
+
|
|
17
|
+
const withAction = <Options extends Record<string, unknown>>(
|
|
18
|
+
action: (options: ActionOptions<Options>) => Promise<void>,
|
|
19
|
+
) => {
|
|
20
|
+
return async (options: Options) => {
|
|
21
|
+
let prisma: PrismaClient | undefined
|
|
22
|
+
|
|
23
|
+
try {
|
|
24
|
+
await ensureEnv()
|
|
25
|
+
ensureDatabaseUrl()
|
|
26
|
+
|
|
27
|
+
const prismaModule = await import('../src/lib/prisma')
|
|
28
|
+
prisma = prismaModule.prisma
|
|
29
|
+
|
|
30
|
+
const actionOptions = { ...options, prisma } as ActionOptions<Options>
|
|
31
|
+
|
|
32
|
+
await action(actionOptions)
|
|
33
|
+
} catch (error) {
|
|
34
|
+
if (error instanceof CliError) {
|
|
35
|
+
console.error(error.message)
|
|
36
|
+
if (error.cause) {
|
|
37
|
+
console.error(error.cause)
|
|
38
|
+
}
|
|
39
|
+
} else {
|
|
40
|
+
console.error(error)
|
|
41
|
+
}
|
|
42
|
+
process.exitCode = 1
|
|
43
|
+
} finally {
|
|
44
|
+
await prisma?.$disconnect()
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const organizations = program
|
|
50
|
+
.command('organizations')
|
|
51
|
+
.description('Manage organizations')
|
|
52
|
+
|
|
53
|
+
organizations
|
|
54
|
+
.command('create')
|
|
55
|
+
.description('Create a new organization')
|
|
56
|
+
.option('-n, --name <name>', 'Organization display name')
|
|
57
|
+
.action(withAction(createOrganization))
|
|
58
|
+
|
|
59
|
+
const organizationApiKeys = organizations
|
|
60
|
+
.command('api-keys')
|
|
61
|
+
.description('Manage organization API keys')
|
|
62
|
+
|
|
63
|
+
organizationApiKeys
|
|
64
|
+
.command('create')
|
|
65
|
+
.description('Create a new organization API key')
|
|
66
|
+
.option('-o, --organization-id <organizationId>', 'Organization UUID')
|
|
67
|
+
.option('-n, --name <name>', 'API key display name')
|
|
68
|
+
.action(withAction(createOrganizationApiKey))
|
|
69
|
+
|
|
70
|
+
if (process.argv.length <= 2) {
|
|
71
|
+
program.outputHelp()
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
program.parseAsync(process.argv).catch((error) => {
|
|
75
|
+
if (error instanceof CliError) {
|
|
76
|
+
console.error(error.message)
|
|
77
|
+
if (error.cause) {
|
|
78
|
+
console.error(error.cause)
|
|
79
|
+
}
|
|
80
|
+
} else {
|
|
81
|
+
console.error(error)
|
|
82
|
+
}
|
|
83
|
+
process.exitCode = 1
|
|
84
|
+
})
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import type { PrismaClient } from '@prisma/client'
|
|
2
|
+
import {
|
|
3
|
+
intro,
|
|
4
|
+
outro,
|
|
5
|
+
text,
|
|
6
|
+
note,
|
|
7
|
+
spinner as createSpinner,
|
|
8
|
+
cancel,
|
|
9
|
+
isCancel,
|
|
10
|
+
select,
|
|
11
|
+
} from '@clack/prompts'
|
|
12
|
+
import { z } from 'zod'
|
|
13
|
+
import { CliError } from '../../../utils/errors'
|
|
14
|
+
|
|
15
|
+
export type CreateOrganizationApiKeyOptions = {
|
|
16
|
+
prisma: PrismaClient
|
|
17
|
+
organizationId?: string
|
|
18
|
+
name?: string
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const organizationIdSchema = z
|
|
22
|
+
.string()
|
|
23
|
+
.trim()
|
|
24
|
+
.uuid('organization-id must be a valid UUID')
|
|
25
|
+
const keyNameSchema = z.string().trim().min(1, 'Name is required')
|
|
26
|
+
|
|
27
|
+
export const createOrganizationApiKey = async ({
|
|
28
|
+
prisma,
|
|
29
|
+
organizationId,
|
|
30
|
+
name,
|
|
31
|
+
}: CreateOrganizationApiKeyOptions) => {
|
|
32
|
+
intro('Create organization API key')
|
|
33
|
+
|
|
34
|
+
let targetOrganizationId = organizationId?.trim()
|
|
35
|
+
let organizationName: string | undefined
|
|
36
|
+
|
|
37
|
+
if (targetOrganizationId) {
|
|
38
|
+
const parsedOrgId = organizationIdSchema.safeParse(targetOrganizationId)
|
|
39
|
+
|
|
40
|
+
if (!parsedOrgId.success) {
|
|
41
|
+
cancel(parsedOrgId.error.issues[0]?.message ?? 'Invalid organization id')
|
|
42
|
+
return
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const organization = await prisma.organization.findUnique({
|
|
46
|
+
where: { id: parsedOrgId.data },
|
|
47
|
+
select: { id: true, name: true },
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
if (!organization) {
|
|
51
|
+
cancel('No organization found for the provided id')
|
|
52
|
+
return
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
targetOrganizationId = organization.id
|
|
56
|
+
organizationName = organization.name
|
|
57
|
+
} else {
|
|
58
|
+
const organizations = await prisma.organization.findMany({
|
|
59
|
+
orderBy: { createdAt: 'desc' },
|
|
60
|
+
select: { id: true, name: true },
|
|
61
|
+
take: 25,
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
if (organizations.length === 0) {
|
|
65
|
+
cancel('No organizations exist yet. Create one first.')
|
|
66
|
+
return
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const selection = await select({
|
|
70
|
+
message: 'Select organization',
|
|
71
|
+
options: organizations.map((org) => ({
|
|
72
|
+
value: org.id,
|
|
73
|
+
label: `${org.name || '(unnamed)'} · ${org.id}`,
|
|
74
|
+
})),
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
if (isCancel(selection)) {
|
|
78
|
+
cancel('No API key created')
|
|
79
|
+
return
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const selected = organizations.find((org) => org.id === selection)
|
|
83
|
+
|
|
84
|
+
if (!selected) {
|
|
85
|
+
cancel('No API key created')
|
|
86
|
+
return
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
targetOrganizationId = selected.id
|
|
90
|
+
organizationName = selected.name
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (!targetOrganizationId) {
|
|
94
|
+
cancel('No API key created')
|
|
95
|
+
return
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
let keyName = name?.trim()
|
|
99
|
+
|
|
100
|
+
if (!keyName) {
|
|
101
|
+
const input = await text({
|
|
102
|
+
message: 'API key name',
|
|
103
|
+
placeholder: 'Production key',
|
|
104
|
+
validate: (value) => {
|
|
105
|
+
const parsed = keyNameSchema.safeParse(value)
|
|
106
|
+
return parsed.success
|
|
107
|
+
? undefined
|
|
108
|
+
: (parsed.error.issues[0]?.message ?? 'Invalid name')
|
|
109
|
+
},
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
if (!input || isCancel(input)) {
|
|
113
|
+
cancel('No API key created')
|
|
114
|
+
return
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
keyName = input
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const parsedName = keyNameSchema.safeParse(keyName)
|
|
121
|
+
|
|
122
|
+
if (!parsedName.success) {
|
|
123
|
+
cancel(parsedName.error.issues[0]?.message ?? 'Name is required')
|
|
124
|
+
return
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const spinner = createSpinner()
|
|
128
|
+
spinner.start('Creating organization API key')
|
|
129
|
+
|
|
130
|
+
try {
|
|
131
|
+
const apiKey = await prisma.organizationApiKey.create({
|
|
132
|
+
data: {
|
|
133
|
+
organizationId: targetOrganizationId,
|
|
134
|
+
name: parsedName.data,
|
|
135
|
+
},
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
spinner.stop('Organization API key created')
|
|
139
|
+
|
|
140
|
+
note(
|
|
141
|
+
[
|
|
142
|
+
`id: ${apiKey.id}`,
|
|
143
|
+
`name: ${apiKey.name}`,
|
|
144
|
+
`organizationId: ${apiKey.organizationId}`,
|
|
145
|
+
`organizationName: ${organizationName ?? '(unnamed)'}`,
|
|
146
|
+
`value: ${apiKey.value}`,
|
|
147
|
+
`createdAt: ${apiKey.createdAt.toISOString()}`,
|
|
148
|
+
].join('\n'),
|
|
149
|
+
'Organization API Key',
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
outro('Done')
|
|
153
|
+
} catch (error) {
|
|
154
|
+
spinner.stop('Creation failed')
|
|
155
|
+
throw new CliError('Failed to create organization API key.', {
|
|
156
|
+
cause: error,
|
|
157
|
+
})
|
|
158
|
+
}
|
|
159
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import type { PrismaClient } from '@prisma/client'
|
|
2
|
+
import {
|
|
3
|
+
intro,
|
|
4
|
+
outro,
|
|
5
|
+
text,
|
|
6
|
+
note,
|
|
7
|
+
spinner as createSpinner,
|
|
8
|
+
cancel,
|
|
9
|
+
isCancel,
|
|
10
|
+
} from '@clack/prompts'
|
|
11
|
+
import { z } from 'zod'
|
|
12
|
+
import { CliError } from '../../utils/errors'
|
|
13
|
+
|
|
14
|
+
export type CreateOrganizationOptions = {
|
|
15
|
+
prisma: PrismaClient
|
|
16
|
+
name?: string
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const nameSchema = z.string().trim().min(1, 'Name is required')
|
|
20
|
+
|
|
21
|
+
export const createOrganization = async ({
|
|
22
|
+
name,
|
|
23
|
+
prisma,
|
|
24
|
+
}: CreateOrganizationOptions) => {
|
|
25
|
+
intro('Create organization')
|
|
26
|
+
|
|
27
|
+
let organizationName = name?.trim()
|
|
28
|
+
|
|
29
|
+
if (!organizationName) {
|
|
30
|
+
const input = await text({
|
|
31
|
+
message: 'Organization name',
|
|
32
|
+
placeholder: 'Acme Inc',
|
|
33
|
+
validate: (value) => {
|
|
34
|
+
const parsed = nameSchema.safeParse(value)
|
|
35
|
+
return parsed.success
|
|
36
|
+
? undefined
|
|
37
|
+
: (parsed.error.issues[0]?.message ?? 'Invalid name')
|
|
38
|
+
},
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
if (!input || isCancel(input)) {
|
|
42
|
+
cancel('No organization created')
|
|
43
|
+
return
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
organizationName = input
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const parsedName = nameSchema.safeParse(organizationName)
|
|
50
|
+
|
|
51
|
+
if (!parsedName.success) {
|
|
52
|
+
cancel(parsedName.error.issues[0]?.message ?? 'Name is required')
|
|
53
|
+
return
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const spinner = createSpinner()
|
|
57
|
+
spinner.start('Creating organization')
|
|
58
|
+
|
|
59
|
+
try {
|
|
60
|
+
const organization = await prisma.organization.create({
|
|
61
|
+
data: {
|
|
62
|
+
name: parsedName.data,
|
|
63
|
+
},
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
spinner.stop('Organization created')
|
|
67
|
+
|
|
68
|
+
note(
|
|
69
|
+
[
|
|
70
|
+
`id: ${organization.id}`,
|
|
71
|
+
`name: ${organization.name}`,
|
|
72
|
+
`createdAt: ${organization.createdAt.toISOString()}`,
|
|
73
|
+
].join('\n'),
|
|
74
|
+
'Organization',
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
outro('Done')
|
|
78
|
+
} catch (error) {
|
|
79
|
+
spinner.stop('Creation failed')
|
|
80
|
+
throw new CliError('Failed to create organization.', { cause: error })
|
|
81
|
+
}
|
|
82
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import process from 'node:process'
|
|
2
|
+
|
|
3
|
+
type ProcessWithLoadEnvFile = NodeJS.Process & {
|
|
4
|
+
loadEnvFile?: () => void
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
let envLoaded = false
|
|
8
|
+
|
|
9
|
+
export const ensureEnv = async () => {
|
|
10
|
+
if (envLoaded) return
|
|
11
|
+
|
|
12
|
+
const proc = process as ProcessWithLoadEnvFile
|
|
13
|
+
|
|
14
|
+
if (typeof proc.loadEnvFile === 'function') {
|
|
15
|
+
proc.loadEnvFile()
|
|
16
|
+
envLoaded = true
|
|
17
|
+
return
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const dotenv = await import('dotenv')
|
|
21
|
+
dotenv.config({ path: '.env' })
|
|
22
|
+
envLoaded = true
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export const ensureDatabaseUrl = () => {
|
|
26
|
+
if (!process.env.DATABASE_URL) {
|
|
27
|
+
throw new Error(
|
|
28
|
+
'DATABASE_URL environment variable is required to run this command.',
|
|
29
|
+
)
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { type NextRequest, NextResponse } from 'next/server'
|
|
2
|
+
import { ApiKeyType } from '@prisma/client'
|
|
3
|
+
import { headers } from 'next/headers'
|
|
4
|
+
import { cacheHeaders } from '@/lib/cache/cacheHeaders'
|
|
5
|
+
import { prisma } from '@/lib/prisma'
|
|
6
|
+
import { serializeApiKey } from '@/lib/apiKeys/serializeApiKey'
|
|
7
|
+
import { z } from 'zod'
|
|
8
|
+
import { getApiKey } from '@/lib/apiKeys/getApiKey'
|
|
9
|
+
import { validate } from 'uuid'
|
|
10
|
+
|
|
11
|
+
export const GET = async (
|
|
12
|
+
request: NextRequest,
|
|
13
|
+
props: {
|
|
14
|
+
params: Promise<{ apiKeyId: string }>
|
|
15
|
+
},
|
|
16
|
+
) => {
|
|
17
|
+
const params = await props.params
|
|
18
|
+
const { apiKeyId } = params
|
|
19
|
+
|
|
20
|
+
const headersList = await headers()
|
|
21
|
+
const authorization = headersList.get('authorization')
|
|
22
|
+
if (!authorization) {
|
|
23
|
+
return NextResponse.json(
|
|
24
|
+
{ error: 'No authorization header found' },
|
|
25
|
+
{ status: 400 },
|
|
26
|
+
)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const privateApiKey = await getApiKey({
|
|
30
|
+
type: ApiKeyType.PRIVATE,
|
|
31
|
+
authorization,
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
if (!privateApiKey) {
|
|
35
|
+
return NextResponse.json({ error: 'Invalid api key' }, { status: 400 })
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (!validate(apiKeyId)) {
|
|
39
|
+
return NextResponse.json({ error: 'Invalid api key id' }, { status: 400 })
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const apiKey = await prisma.apiKey.findFirst({
|
|
43
|
+
where: {
|
|
44
|
+
id: apiKeyId,
|
|
45
|
+
type: ApiKeyType.PUBLIC,
|
|
46
|
+
workspaceId: privateApiKey.workspaceId,
|
|
47
|
+
},
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
if (!apiKey) {
|
|
51
|
+
return NextResponse.json({ error: 'No api key found' }, { status: 400 })
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return NextResponse.json(
|
|
55
|
+
{ apiKey: serializeApiKey({ apiKey }) },
|
|
56
|
+
{ headers: cacheHeaders },
|
|
57
|
+
)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export const PATCH = async (
|
|
61
|
+
request: NextRequest,
|
|
62
|
+
props: { params: Promise<{ apiKeyId: string }> },
|
|
63
|
+
) => {
|
|
64
|
+
const params = await props.params
|
|
65
|
+
const { apiKeyId } = params
|
|
66
|
+
|
|
67
|
+
const headersList = await headers()
|
|
68
|
+
const authorization = headersList.get('authorization')
|
|
69
|
+
if (!authorization) {
|
|
70
|
+
return NextResponse.json(
|
|
71
|
+
{ error: 'No authorization header found' },
|
|
72
|
+
{ status: 400 },
|
|
73
|
+
)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const privateApiKey = await getApiKey({
|
|
77
|
+
authorization,
|
|
78
|
+
type: ApiKeyType.PRIVATE,
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
if (!privateApiKey) {
|
|
82
|
+
return NextResponse.json({ error: 'Invalid api key' }, { status: 400 })
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (!validate(apiKeyId)) {
|
|
86
|
+
return NextResponse.json({ error: 'Invalid api key id' }, { status: 400 })
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const body = await request.json().catch(() => null)
|
|
90
|
+
const schema = z.object({ name: z.string().min(1) })
|
|
91
|
+
const parsed = schema.safeParse(body)
|
|
92
|
+
|
|
93
|
+
if (!parsed.success) {
|
|
94
|
+
return NextResponse.json({ error: 'No name found' }, { status: 400 })
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const { name } = parsed.data
|
|
98
|
+
|
|
99
|
+
const existingApiKey = await prisma.apiKey.findFirst({
|
|
100
|
+
where: {
|
|
101
|
+
id: apiKeyId,
|
|
102
|
+
type: ApiKeyType.PUBLIC,
|
|
103
|
+
workspaceId: privateApiKey.workspaceId,
|
|
104
|
+
},
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
if (!existingApiKey) {
|
|
108
|
+
return NextResponse.json({ error: 'No api key found' }, { status: 400 })
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const apiKey = await prisma.apiKey.update({
|
|
112
|
+
where: { id: apiKeyId },
|
|
113
|
+
data: { name },
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
return NextResponse.json(
|
|
117
|
+
{ apiKey: serializeApiKey({ apiKey }) },
|
|
118
|
+
{ headers: cacheHeaders },
|
|
119
|
+
)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export const DELETE = async (
|
|
123
|
+
request: NextRequest,
|
|
124
|
+
props: { params: Promise<{ apiKeyId: string }> },
|
|
125
|
+
) => {
|
|
126
|
+
const params = await props.params
|
|
127
|
+
const { apiKeyId } = params
|
|
128
|
+
|
|
129
|
+
const headersList = await headers()
|
|
130
|
+
const authorization = headersList.get('authorization')
|
|
131
|
+
if (!authorization) {
|
|
132
|
+
return NextResponse.json(
|
|
133
|
+
{ error: 'No authorization header found' },
|
|
134
|
+
{ status: 400 },
|
|
135
|
+
)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const privateApiKey = await getApiKey({
|
|
139
|
+
type: ApiKeyType.PRIVATE,
|
|
140
|
+
authorization,
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
if (!privateApiKey) {
|
|
144
|
+
return NextResponse.json({ error: 'Invalid api key' }, { status: 400 })
|
|
145
|
+
}
|
|
146
|
+
if (!validate(apiKeyId)) {
|
|
147
|
+
return NextResponse.json({ error: 'Invalid api key id' }, { status: 400 })
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const apiKey = await prisma.apiKey.findFirst({
|
|
151
|
+
where: {
|
|
152
|
+
id: apiKeyId,
|
|
153
|
+
type: ApiKeyType.PUBLIC,
|
|
154
|
+
workspaceId: privateApiKey.workspaceId,
|
|
155
|
+
},
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
if (!apiKey) {
|
|
159
|
+
return NextResponse.json({ error: 'No api key found' }, { status: 400 })
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const deletedApiKey = await prisma.apiKey.delete({
|
|
163
|
+
where: { id: apiKey.id },
|
|
164
|
+
})
|
|
165
|
+
|
|
166
|
+
return NextResponse.json(
|
|
167
|
+
{ apiKey: serializeApiKey({ apiKey: deletedApiKey }) },
|
|
168
|
+
{ headers: cacheHeaders },
|
|
169
|
+
)
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export const OPTIONS = () =>
|
|
173
|
+
NextResponse.json(
|
|
174
|
+
{},
|
|
175
|
+
{
|
|
176
|
+
headers: cacheHeaders,
|
|
177
|
+
},
|
|
178
|
+
)
|