mirror of
https://github.com/different-ai/openwork
synced 2026-04-25 17:15:34 +02:00
feat(den): use Better Auth active org context (#1485)
* feat(den): use Better Auth active org context * fix(app): switch Better Auth org only on explicit actions * refactor(den): flatten active org resource routes --------- Co-authored-by: src-opn <src-opn@users.noreply.github.com>
This commit is contained in:
19
ee/apps/den-api/src/active-organization.ts
Normal file
19
ee/apps/den-api/src/active-organization.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { asc, eq } from "@openwork-ee/den-db/drizzle"
|
||||
import { MemberTable } from "@openwork-ee/den-db/schema"
|
||||
import { normalizeDenTypeId } from "@openwork-ee/utils/typeid"
|
||||
import { db } from "./db.js"
|
||||
|
||||
export async function getInitialActiveOrganizationIdForUser(userId: string) {
|
||||
const normalizedUserId = normalizeDenTypeId("user", userId)
|
||||
|
||||
const rows = await db
|
||||
.select({
|
||||
organizationId: MemberTable.organizationId,
|
||||
})
|
||||
.from(MemberTable)
|
||||
.where(eq(MemberTable.userId, normalizedUserId))
|
||||
.orderBy(asc(MemberTable.createdAt))
|
||||
.limit(1)
|
||||
|
||||
return rows[0]?.organizationId ?? null
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
import { getInitialActiveOrganizationIdForUser } from "./active-organization.js";
|
||||
import { db } from "./db.js";
|
||||
import { env } from "./env.js";
|
||||
import {
|
||||
@@ -77,6 +78,22 @@ export const auth = betterAuth({
|
||||
provider: "mysql",
|
||||
schema,
|
||||
}),
|
||||
databaseHooks: {
|
||||
session: {
|
||||
create: {
|
||||
before: async (session) => {
|
||||
const activeOrganizationId = await getInitialActiveOrganizationIdForUser(session.userId);
|
||||
|
||||
return {
|
||||
data: {
|
||||
...session,
|
||||
activeOrganizationId,
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
advanced: {
|
||||
ipAddress: {
|
||||
ipAddressHeaders: ["x-forwarded-for", "x-real-ip", "cf-connecting-ip"],
|
||||
|
||||
@@ -1,45 +1,62 @@
|
||||
import { normalizeDenTypeId } from "@openwork-ee/utils/typeid"
|
||||
import type { MiddlewareHandler } from "hono"
|
||||
import { isScopedApiKeyForOrganization } from "../api-keys.js"
|
||||
import { getOrganizationContextForUser, type OrganizationContext } from "../orgs.js"
|
||||
import { getApiKeyScopedOrganizationId, isScopedApiKeyForOrganization } from "../api-keys.js"
|
||||
import { getOrganizationContextForUser, resolveUserOrganizations, type OrganizationContext } from "../orgs.js"
|
||||
import type { AuthContextVariables } from "../session.js"
|
||||
import type { UserOrganizationsContext } from "./user-organizations.js"
|
||||
|
||||
export type OrganizationContextVariables = {
|
||||
organizationContext: OrganizationContext
|
||||
}
|
||||
|
||||
export const resolveOrganizationContextMiddleware: MiddlewareHandler<{
|
||||
Variables: AuthContextVariables & Partial<OrganizationContextVariables>
|
||||
Variables: AuthContextVariables & Partial<OrganizationContextVariables> & Partial<UserOrganizationsContext>
|
||||
}> = async (c, next) => {
|
||||
const user = c.get("user")
|
||||
if (!user?.id) {
|
||||
return c.json({ error: "unauthorized" }, 401) as never
|
||||
}
|
||||
|
||||
const params = (c.req as { valid: (target: "param") => { orgId?: string } }).valid("param")
|
||||
const organizationIdRaw = params.orgId?.trim()
|
||||
if (!organizationIdRaw) {
|
||||
return c.json({ error: "organization_id_required" }, 400) as never
|
||||
const apiKey = c.get("apiKey")
|
||||
const scopedOrganizationId = getApiKeyScopedOrganizationId(apiKey)
|
||||
|
||||
let organizationId = c.get("activeOrganizationId") ?? null
|
||||
let organizationSlug = c.get("activeOrganizationSlug") ?? null
|
||||
|
||||
if (!organizationId) {
|
||||
const resolved = await resolveUserOrganizations({
|
||||
activeOrganizationId: scopedOrganizationId ?? c.get("session")?.activeOrganizationId ?? null,
|
||||
userId: normalizeDenTypeId("user", user.id),
|
||||
})
|
||||
|
||||
const scopedOrgs = scopedOrganizationId
|
||||
? resolved.orgs.filter((org) => org.id === scopedOrganizationId)
|
||||
: resolved.orgs
|
||||
|
||||
organizationId = scopedOrganizationId ? scopedOrgs[0]?.id ?? null : resolved.activeOrgId
|
||||
organizationSlug = scopedOrganizationId ? scopedOrgs[0]?.slug ?? null : resolved.activeOrgSlug
|
||||
|
||||
c.set("userOrganizations", scopedOrgs)
|
||||
c.set("activeOrganizationId", organizationId)
|
||||
c.set("activeOrganizationSlug", organizationSlug)
|
||||
}
|
||||
|
||||
let organizationId
|
||||
try {
|
||||
organizationId = normalizeDenTypeId("organization", organizationIdRaw)
|
||||
} catch {
|
||||
if (!organizationId) {
|
||||
return c.json({ error: "organization_not_found" }, 404) as never
|
||||
}
|
||||
|
||||
const normalizedOrganizationId = normalizeDenTypeId("organization", organizationId)
|
||||
|
||||
const context = await getOrganizationContextForUser({
|
||||
userId: normalizeDenTypeId("user", user.id),
|
||||
organizationId,
|
||||
organizationId: normalizedOrganizationId,
|
||||
})
|
||||
|
||||
if (!context) {
|
||||
return c.json({ error: "organization_not_found" }, 404) as never
|
||||
}
|
||||
|
||||
const apiKey = c.get("apiKey")
|
||||
if (apiKey && !isScopedApiKeyForOrganization({ apiKey, organizationId })) {
|
||||
if (apiKey && !isScopedApiKeyForOrganization({ apiKey, organizationId: normalizedOrganizationId })) {
|
||||
return c.json({
|
||||
error: "forbidden",
|
||||
message: "This API key is scoped to a different organization.",
|
||||
@@ -54,5 +71,7 @@ export const resolveOrganizationContextMiddleware: MiddlewareHandler<{
|
||||
}
|
||||
|
||||
c.set("organizationContext", context)
|
||||
c.set("activeOrganizationId", context.organization.id)
|
||||
c.set("activeOrganizationSlug", context.organization.slug)
|
||||
await next()
|
||||
}
|
||||
|
||||
@@ -30,13 +30,13 @@ export const resolveUserOrganizationsMiddleware: MiddlewareHandler<{
|
||||
? resolved.orgs.filter((org) => org.id === scopedOrganizationId)
|
||||
: resolved.orgs
|
||||
|
||||
const activeOrganizationId = scopedOrganizationId ? scopedOrgs[0]?.id ?? null : resolved.activeOrgId
|
||||
const activeOrganizationSlug = scopedOrganizationId
|
||||
? scopedOrgs[0]?.slug ?? null
|
||||
: resolved.activeOrgSlug
|
||||
|
||||
c.set("userOrganizations", scopedOrgs)
|
||||
c.set("activeOrganizationId", scopedOrganizationId ? scopedOrgs[0]?.id ?? null : resolved.activeOrgId)
|
||||
c.set(
|
||||
"activeOrganizationSlug",
|
||||
scopedOrganizationId
|
||||
? scopedOrgs[0]?.slug ?? null
|
||||
: resolved.activeOrgSlug,
|
||||
)
|
||||
c.set("activeOrganizationId", activeOrganizationId)
|
||||
c.set("activeOrganizationSlug", activeOrganizationSlug)
|
||||
await next()
|
||||
}
|
||||
|
||||
@@ -12,6 +12,16 @@ This folder owns organization-facing Den API routes.
|
||||
- `templates.ts`: shared template CRUD
|
||||
- `shared.ts`: shared route-local helpers, param schemas, and guard helpers
|
||||
|
||||
## Active organization model
|
||||
|
||||
- `POST /api/auth/organization/set-active` is the only Better Auth endpoint that should switch the user's active org explicitly.
|
||||
- New sessions should get an initial `activeOrganizationId` from Better Auth session creation hooks in `src/auth.ts`.
|
||||
- `GET /v1/org` returns the active organization from the current session, including a nested `organization.owner` object plus the current member and team context.
|
||||
- `POST /v1/org` creates a new organization and switches the session to it. `PATCH /v1/org` updates the active organization.
|
||||
- Active-org scoped resources should prefer top-level routes like `/v1/templates`, `/v1/skills`, `/v1/teams`, `/v1/roles`, `/v1/api-keys`, `/v1/llm-providers`, and plugin-system `/v1/...` routes. They should not require `:orgId` or `:orgSlug` in the path.
|
||||
- Routes under `/v1/orgs/**` are reserved for cross-org flows that are not tied to the active workspace yet, such as invitation preview/accept.
|
||||
- If a client needs to change workspaces, it should call Better Auth set-active first, then use the active-org scoped `/v1/...` resource routes.
|
||||
|
||||
## Middleware expectations
|
||||
|
||||
- `requireUserMiddleware`: the route requires a signed-in user
|
||||
|
||||
@@ -12,7 +12,7 @@ import { jsonValidator, paramValidator, requireUserMiddleware, resolveOrganizati
|
||||
import { denTypeIdSchema } from "../../openapi.js"
|
||||
import { auth } from "../../auth.js"
|
||||
import type { OrgRouteVariables } from "./shared.js"
|
||||
import { ensureApiKeyManager, idParamSchema, orgIdParamSchema } from "./shared.js"
|
||||
import { ensureApiKeyManager, idParamSchema } from "./shared.js"
|
||||
|
||||
const createOrganizationApiKeySchema = z.object({
|
||||
name: z.string().trim().min(2).max(64),
|
||||
@@ -92,12 +92,12 @@ const createOrganizationApiKeyResponseSchema = z.object({
|
||||
key: z.string().min(1),
|
||||
}).meta({ ref: "CreateOrganizationApiKeyResponse" })
|
||||
|
||||
const apiKeyIdParamSchema = orgIdParamSchema.extend(idParamSchema("apiKeyId").shape)
|
||||
const apiKeyIdParamSchema = idParamSchema("apiKeyId")
|
||||
const hideApiKeyGenerationRoute = () => process.env.NODE_ENV === "production"
|
||||
|
||||
export function registerOrgApiKeyRoutes<T extends { Variables: OrgRouteVariables }>(app: Hono<T>) {
|
||||
app.get(
|
||||
"/v1/orgs/:orgId/api-keys",
|
||||
"/v1/api-keys",
|
||||
describeRoute({
|
||||
tags: ["API Keys"],
|
||||
summary: "List organization API keys",
|
||||
@@ -147,7 +147,6 @@ export function registerOrgApiKeyRoutes<T extends { Variables: OrgRouteVariables
|
||||
},
|
||||
}),
|
||||
requireUserMiddleware,
|
||||
paramValidator(orgIdParamSchema),
|
||||
resolveOrganizationContextMiddleware,
|
||||
async (c) => {
|
||||
const access = ensureApiKeyManager(c)
|
||||
@@ -162,7 +161,7 @@ export function registerOrgApiKeyRoutes<T extends { Variables: OrgRouteVariables
|
||||
)
|
||||
|
||||
app.post(
|
||||
"/v1/orgs/:orgId/api-keys",
|
||||
"/v1/api-keys",
|
||||
describeRoute({
|
||||
tags: ["API Keys"],
|
||||
summary: "Create an organization API key",
|
||||
@@ -213,7 +212,6 @@ export function registerOrgApiKeyRoutes<T extends { Variables: OrgRouteVariables
|
||||
},
|
||||
}),
|
||||
requireUserMiddleware,
|
||||
paramValidator(orgIdParamSchema),
|
||||
resolveOrganizationContextMiddleware,
|
||||
jsonValidator(createOrganizationApiKeySchema),
|
||||
async (c) => {
|
||||
@@ -259,7 +257,7 @@ export function registerOrgApiKeyRoutes<T extends { Variables: OrgRouteVariables
|
||||
)
|
||||
|
||||
app.delete(
|
||||
"/v1/orgs/:orgId/api-keys/:apiKeyId",
|
||||
"/v1/api-keys/:apiKeyId",
|
||||
describeRoute({
|
||||
tags: ["API Keys"],
|
||||
hide: true,
|
||||
|
||||
@@ -1,18 +1,19 @@
|
||||
import { eq } from "@openwork-ee/den-db/drizzle"
|
||||
import { OrganizationTable } from "@openwork-ee/den-db/schema"
|
||||
import { normalizeDenTypeId } from "@openwork-ee/utils/typeid"
|
||||
import { normalizeDenTypeId, type DenTypeId } from "@openwork-ee/utils/typeid"
|
||||
import type { Hono } from "hono"
|
||||
import { describeRoute } from "hono-openapi"
|
||||
import { z } from "zod"
|
||||
import { auth } from "../../auth.js"
|
||||
import { requireCloudWorkerAccess } from "../../billing/polar.js"
|
||||
import { db } from "../../db.js"
|
||||
import { env } from "../../env.js"
|
||||
import { jsonValidator, paramValidator, queryValidator, requireUserMiddleware, resolveMemberTeamsMiddleware, resolveOrganizationContextMiddleware } from "../../middleware/index.js"
|
||||
import { jsonValidator, queryValidator, requireUserMiddleware, resolveMemberTeamsMiddleware, resolveOrganizationContextMiddleware } from "../../middleware/index.js"
|
||||
import { denTypeIdSchema, forbiddenSchema, invalidRequestSchema, jsonResponse, notFoundSchema, unauthorizedSchema } from "../../openapi.js"
|
||||
import { acceptInvitationForUser, createOrganizationForUser, getInvitationPreview, setSessionActiveOrganization, updateOrganizationName } from "../../orgs.js"
|
||||
import { getRequiredUserEmail } from "../../user.js"
|
||||
import type { OrgRouteVariables } from "./shared.js"
|
||||
import { ensureOwner, orgIdParamSchema } from "./shared.js"
|
||||
import { ensureOwner } from "./shared.js"
|
||||
|
||||
const createOrganizationSchema = z.object({
|
||||
name: z.string().trim().min(2).max(120),
|
||||
@@ -34,6 +35,14 @@ const organizationResponseSchema = z.object({
|
||||
organization: z.object({}).passthrough().nullable(),
|
||||
}).meta({ ref: "OrganizationResponse" })
|
||||
|
||||
const organizationOwnerSchema = z.object({
|
||||
memberId: denTypeIdSchema("member"),
|
||||
userId: denTypeIdSchema("user"),
|
||||
name: z.string().nullable(),
|
||||
email: z.string().email().nullable(),
|
||||
image: z.string().nullable().optional(),
|
||||
}).meta({ ref: "OrganizationOwner" })
|
||||
|
||||
const paymentRequiredSchema = z.object({
|
||||
error: z.literal("payment_required"),
|
||||
message: z.string(),
|
||||
@@ -54,6 +63,10 @@ const invitationAcceptedResponseSchema = z.object({
|
||||
}).meta({ ref: "InvitationAcceptedResponse" })
|
||||
|
||||
const organizationContextResponseSchema = z.object({
|
||||
organization: z.object({
|
||||
owner: organizationOwnerSchema.nullable().optional(),
|
||||
}).passthrough(),
|
||||
currentMember: z.object({}).passthrough(),
|
||||
currentMemberTeams: z.array(z.object({}).passthrough()),
|
||||
}).passthrough().meta({ ref: "OrganizationContextResponse" })
|
||||
|
||||
@@ -73,9 +86,30 @@ function getStoredSessionId(session: { id?: string | null } | null) {
|
||||
}
|
||||
}
|
||||
|
||||
async function setRequestActiveOrganization(
|
||||
c: {
|
||||
get: (key: "session") => { id?: string | null } | null
|
||||
req: { raw: Request }
|
||||
},
|
||||
organizationId: DenTypeId<"organization"> | null,
|
||||
) {
|
||||
try {
|
||||
await auth.api.setActiveOrganization({
|
||||
body: { organizationId },
|
||||
headers: c.req.raw.headers,
|
||||
})
|
||||
return
|
||||
} catch {}
|
||||
|
||||
const sessionId = getStoredSessionId(c.get("session"))
|
||||
if (sessionId) {
|
||||
await setSessionActiveOrganization(sessionId, organizationId)
|
||||
}
|
||||
}
|
||||
|
||||
export function registerOrgCoreRoutes<T extends { Variables: OrgRouteVariables }>(app: Hono<T>) {
|
||||
app.post(
|
||||
"/v1/orgs",
|
||||
"/v1/org",
|
||||
describeRoute({
|
||||
tags: ["Organizations"],
|
||||
hide: true,
|
||||
@@ -100,7 +134,6 @@ export function registerOrgCoreRoutes<T extends { Variables: OrgRouteVariables }
|
||||
}
|
||||
|
||||
const user = c.get("user")
|
||||
const session = c.get("session")
|
||||
const input = c.req.valid("json")
|
||||
const email = getRequiredUserEmail(user)
|
||||
|
||||
@@ -131,10 +164,7 @@ export function registerOrgCoreRoutes<T extends { Variables: OrgRouteVariables }
|
||||
name: input.name,
|
||||
})
|
||||
|
||||
const sessionId = getStoredSessionId(session)
|
||||
if (sessionId) {
|
||||
await setSessionActiveOrganization(sessionId, organizationId)
|
||||
}
|
||||
await setRequestActiveOrganization(c, organizationId)
|
||||
|
||||
const organization = await db
|
||||
.select()
|
||||
@@ -196,7 +226,6 @@ export function registerOrgCoreRoutes<T extends { Variables: OrgRouteVariables }
|
||||
}
|
||||
|
||||
const user = c.get("user")
|
||||
const session = c.get("session")
|
||||
const input = c.req.valid("json")
|
||||
const email = getRequiredUserEmail(user)
|
||||
|
||||
@@ -214,10 +243,7 @@ export function registerOrgCoreRoutes<T extends { Variables: OrgRouteVariables }
|
||||
return c.json({ error: "invitation_not_found" }, 404)
|
||||
}
|
||||
|
||||
const sessionId = getStoredSessionId(session)
|
||||
if (sessionId) {
|
||||
await setSessionActiveOrganization(sessionId, accepted.member.organizationId)
|
||||
}
|
||||
await setRequestActiveOrganization(c, accepted.member.organizationId)
|
||||
|
||||
const orgRows = await db
|
||||
.select({ slug: OrganizationTable.slug })
|
||||
@@ -235,7 +261,7 @@ export function registerOrgCoreRoutes<T extends { Variables: OrgRouteVariables }
|
||||
)
|
||||
|
||||
app.patch(
|
||||
"/v1/orgs/:orgId",
|
||||
"/v1/org",
|
||||
describeRoute({
|
||||
tags: ["Organizations"],
|
||||
summary: "Update organization",
|
||||
@@ -249,7 +275,6 @@ export function registerOrgCoreRoutes<T extends { Variables: OrgRouteVariables }
|
||||
},
|
||||
}),
|
||||
requireUserMiddleware,
|
||||
paramValidator(orgIdParamSchema),
|
||||
resolveOrganizationContextMiddleware,
|
||||
jsonValidator(updateOrganizationSchema),
|
||||
async (c) => {
|
||||
@@ -275,25 +300,38 @@ export function registerOrgCoreRoutes<T extends { Variables: OrgRouteVariables }
|
||||
)
|
||||
|
||||
app.get(
|
||||
"/v1/orgs/:orgId/context",
|
||||
"/v1/org",
|
||||
describeRoute({
|
||||
tags: ["Organizations"],
|
||||
summary: "Get organization context",
|
||||
description: "Returns the resolved organization context for a specific org, including the current member record and their team memberships.",
|
||||
summary: "Get active organization",
|
||||
description: "Returns the active organization from the current session, including its owner, the current member record, and their team memberships.",
|
||||
responses: {
|
||||
200: jsonResponse("Organization context returned successfully.", organizationContextResponseSchema),
|
||||
400: jsonResponse("The organization context path parameters were invalid.", invalidRequestSchema),
|
||||
401: jsonResponse("The caller must be signed in to load organization context.", unauthorizedSchema),
|
||||
404: jsonResponse("The organization could not be found.", notFoundSchema),
|
||||
},
|
||||
}),
|
||||
requireUserMiddleware,
|
||||
paramValidator(orgIdParamSchema),
|
||||
resolveOrganizationContextMiddleware,
|
||||
resolveMemberTeamsMiddleware,
|
||||
(c) => {
|
||||
const payload = c.get("organizationContext")
|
||||
const owner = payload.members.find((member: typeof payload.members[number]) => member.isOwner) ?? null
|
||||
|
||||
return c.json({
|
||||
...c.get("organizationContext"),
|
||||
...payload,
|
||||
organization: {
|
||||
...payload.organization,
|
||||
owner: owner
|
||||
? {
|
||||
memberId: owner.id,
|
||||
userId: owner.user.id,
|
||||
name: owner.user.name,
|
||||
email: owner.user.email,
|
||||
image: owner.user.image,
|
||||
}
|
||||
: null,
|
||||
},
|
||||
currentMemberTeams: c.get("memberTeams") ?? [],
|
||||
})
|
||||
},
|
||||
|
||||
@@ -11,7 +11,7 @@ import { denTypeIdSchema, forbiddenSchema, invalidRequestSchema, jsonResponse, n
|
||||
import { getOrganizationLimitStatus } from "../../organization-limits.js"
|
||||
import { listAssignableRoles } from "../../orgs.js"
|
||||
import type { OrgRouteVariables } from "./shared.js"
|
||||
import { buildInvitationLink, createInvitationId, ensureInviteManager, idParamSchema, normalizeRoleName, orgIdParamSchema } from "./shared.js"
|
||||
import { buildInvitationLink, createInvitationId, ensureInviteManager, idParamSchema, normalizeRoleName } from "./shared.js"
|
||||
|
||||
const inviteMemberSchema = z.object({
|
||||
email: z.string().email(),
|
||||
@@ -33,11 +33,11 @@ const invitationEmailFailedSchema = z.object({
|
||||
}).meta({ ref: "InvitationEmailFailedError" })
|
||||
|
||||
type InvitationId = typeof InvitationTable.$inferSelect.id
|
||||
const orgInvitationParamsSchema = orgIdParamSchema.extend(idParamSchema("invitationId", "invitation").shape)
|
||||
const orgInvitationParamsSchema = idParamSchema("invitationId", "invitation")
|
||||
|
||||
export function registerOrgInvitationRoutes<T extends { Variables: OrgRouteVariables }>(app: Hono<T>) {
|
||||
app.post(
|
||||
"/v1/orgs/:orgId/invitations",
|
||||
"/v1/invitations",
|
||||
describeRoute({
|
||||
tags: ["Invitations"],
|
||||
summary: "Create organization invitation",
|
||||
@@ -53,7 +53,6 @@ export function registerOrgInvitationRoutes<T extends { Variables: OrgRouteVaria
|
||||
},
|
||||
}),
|
||||
requireUserMiddleware,
|
||||
paramValidator(orgIdParamSchema),
|
||||
resolveOrganizationContextMiddleware,
|
||||
jsonValidator(inviteMemberSchema),
|
||||
async (c) => {
|
||||
@@ -173,7 +172,7 @@ export function registerOrgInvitationRoutes<T extends { Variables: OrgRouteVaria
|
||||
)
|
||||
|
||||
app.post(
|
||||
"/v1/orgs/:orgId/invitations/:invitationId/cancel",
|
||||
"/v1/invitations/:invitationId/cancel",
|
||||
describeRoute({
|
||||
tags: ["Invitations"],
|
||||
summary: "Cancel organization invitation",
|
||||
|
||||
@@ -23,7 +23,7 @@ import { getModelsDevProvider, listModelsDevProviders } from "../../llm/models-d
|
||||
import type { MemberTeamsContext } from "../../middleware/member-teams.js"
|
||||
import { denTypeIdSchema, emptyResponse, forbiddenSchema, invalidRequestSchema, jsonResponse, notFoundSchema, unauthorizedSchema } from "../../openapi.js"
|
||||
import type { OrgRouteVariables } from "./shared.js"
|
||||
import { idParamSchema, memberHasRole, orgIdParamSchema } from "./shared.js"
|
||||
import { idParamSchema, memberHasRole } from "./shared.js"
|
||||
|
||||
type JsonRecord = Record<string, unknown>
|
||||
type LlmProviderId = typeof LlmProviderTable.$inferSelect.id
|
||||
@@ -38,11 +38,11 @@ type RouteFailure = {
|
||||
message?: string
|
||||
}
|
||||
|
||||
const providerCatalogParamsSchema = orgIdParamSchema.extend({
|
||||
const providerCatalogParamsSchema = z.object({
|
||||
providerId: z.string().trim().min(1).max(255),
|
||||
})
|
||||
|
||||
const orgLlmProviderParamsSchema = orgIdParamSchema.extend(idParamSchema("llmProviderId", "llmProvider").shape)
|
||||
const orgLlmProviderParamsSchema = idParamSchema("llmProviderId", "llmProvider")
|
||||
|
||||
const customModelSchema = z.object({
|
||||
id: z.string().trim().min(1).max(255),
|
||||
@@ -480,7 +480,7 @@ async function loadLlmProviders(input: {
|
||||
|
||||
export function registerOrgLlmProviderRoutes<T extends { Variables: OrgRouteVariables & Partial<MemberTeamsContext> }>(app: Hono<T>) {
|
||||
app.get(
|
||||
"/v1/orgs/:orgId/llm-provider-catalog",
|
||||
"/v1/llm-provider-catalog",
|
||||
describeRoute({
|
||||
tags: ["LLM Providers"],
|
||||
summary: "List LLM provider catalog",
|
||||
@@ -493,7 +493,6 @@ export function registerOrgLlmProviderRoutes<T extends { Variables: OrgRouteVari
|
||||
},
|
||||
}),
|
||||
requireUserMiddleware,
|
||||
paramValidator(orgIdParamSchema),
|
||||
resolveOrganizationContextMiddleware,
|
||||
async (c) => {
|
||||
try {
|
||||
@@ -509,7 +508,7 @@ export function registerOrgLlmProviderRoutes<T extends { Variables: OrgRouteVari
|
||||
)
|
||||
|
||||
app.get(
|
||||
"/v1/orgs/:orgId/llm-provider-catalog/:providerId",
|
||||
"/v1/llm-provider-catalog/:providerId",
|
||||
describeRoute({
|
||||
tags: ["LLM Providers"],
|
||||
summary: "Get LLM provider catalog entry",
|
||||
@@ -556,7 +555,7 @@ export function registerOrgLlmProviderRoutes<T extends { Variables: OrgRouteVari
|
||||
)
|
||||
|
||||
app.get(
|
||||
"/v1/orgs/:orgId/llm-providers",
|
||||
"/v1/llm-providers",
|
||||
describeRoute({
|
||||
tags: ["LLM Providers"],
|
||||
summary: "List organization LLM providers",
|
||||
@@ -568,7 +567,6 @@ export function registerOrgLlmProviderRoutes<T extends { Variables: OrgRouteVari
|
||||
},
|
||||
}),
|
||||
requireUserMiddleware,
|
||||
paramValidator(orgIdParamSchema),
|
||||
resolveOrganizationContextMiddleware,
|
||||
resolveMemberTeamsMiddleware,
|
||||
async (c) => {
|
||||
@@ -592,7 +590,7 @@ export function registerOrgLlmProviderRoutes<T extends { Variables: OrgRouteVari
|
||||
)
|
||||
|
||||
app.get(
|
||||
"/v1/orgs/:orgId/llm-providers/:llmProviderId/connect",
|
||||
"/v1/llm-providers/:llmProviderId/connect",
|
||||
describeRoute({
|
||||
tags: ["LLM Providers"],
|
||||
summary: "Get LLM provider connect payload",
|
||||
@@ -669,7 +667,7 @@ export function registerOrgLlmProviderRoutes<T extends { Variables: OrgRouteVari
|
||||
)
|
||||
|
||||
app.post(
|
||||
"/v1/orgs/:orgId/llm-providers",
|
||||
"/v1/llm-providers",
|
||||
describeRoute({
|
||||
tags: ["LLM Providers"],
|
||||
summary: "Create organization LLM provider",
|
||||
@@ -682,7 +680,6 @@ export function registerOrgLlmProviderRoutes<T extends { Variables: OrgRouteVari
|
||||
},
|
||||
}),
|
||||
requireUserMiddleware,
|
||||
paramValidator(orgIdParamSchema),
|
||||
resolveOrganizationContextMiddleware,
|
||||
jsonValidator(llmProviderWriteSchema),
|
||||
async (c) => {
|
||||
@@ -781,7 +778,7 @@ export function registerOrgLlmProviderRoutes<T extends { Variables: OrgRouteVari
|
||||
)
|
||||
|
||||
app.patch(
|
||||
"/v1/orgs/:orgId/llm-providers/:llmProviderId",
|
||||
"/v1/llm-providers/:llmProviderId",
|
||||
describeRoute({
|
||||
tags: ["LLM Providers"],
|
||||
summary: "Update organization LLM provider",
|
||||
@@ -917,7 +914,7 @@ export function registerOrgLlmProviderRoutes<T extends { Variables: OrgRouteVari
|
||||
)
|
||||
|
||||
app.delete(
|
||||
"/v1/orgs/:orgId/llm-providers/:llmProviderId",
|
||||
"/v1/llm-providers/:llmProviderId",
|
||||
describeRoute({
|
||||
tags: ["LLM Providers"],
|
||||
summary: "Delete organization LLM provider",
|
||||
@@ -973,7 +970,7 @@ export function registerOrgLlmProviderRoutes<T extends { Variables: OrgRouteVari
|
||||
)
|
||||
|
||||
app.delete(
|
||||
"/v1/orgs/:orgId/llm-providers/:llmProviderId/access/:accessId",
|
||||
"/v1/llm-providers/:llmProviderId/access/:accessId",
|
||||
describeRoute({
|
||||
tags: ["LLM Providers"],
|
||||
summary: "Remove LLM provider access grant",
|
||||
|
||||
@@ -9,18 +9,18 @@ import { jsonValidator, paramValidator, requireUserMiddleware, resolveOrganizati
|
||||
import { emptyResponse, forbiddenSchema, invalidRequestSchema, jsonResponse, notFoundSchema, successSchema, unauthorizedSchema } from "../../openapi.js"
|
||||
import { listAssignableRoles, removeOrganizationMember, roleIncludesOwner } from "../../orgs.js"
|
||||
import type { OrgRouteVariables } from "./shared.js"
|
||||
import { ensureOwner, idParamSchema, normalizeRoleName, orgIdParamSchema } from "./shared.js"
|
||||
import { ensureOwner, idParamSchema, normalizeRoleName } from "./shared.js"
|
||||
|
||||
const updateMemberRoleSchema = z.object({
|
||||
role: z.string().trim().min(1).max(64),
|
||||
})
|
||||
|
||||
type MemberId = typeof MemberTable.$inferSelect.id
|
||||
const orgMemberParamsSchema = orgIdParamSchema.extend(idParamSchema("memberId", "member").shape)
|
||||
const orgMemberParamsSchema = idParamSchema("memberId", "member")
|
||||
|
||||
export function registerOrgMemberRoutes<T extends { Variables: OrgRouteVariables }>(app: Hono<T>) {
|
||||
app.post(
|
||||
"/v1/orgs/:orgId/members/:memberId/role",
|
||||
"/v1/members/:memberId/role",
|
||||
describeRoute({
|
||||
tags: ["Members"],
|
||||
summary: "Update member role",
|
||||
@@ -81,7 +81,7 @@ export function registerOrgMemberRoutes<T extends { Variables: OrgRouteVariables
|
||||
)
|
||||
|
||||
app.delete(
|
||||
"/v1/orgs/:orgId/members/:memberId",
|
||||
"/v1/members/:memberId",
|
||||
describeRoute({
|
||||
tags: ["Members"],
|
||||
summary: "Remove organization member",
|
||||
|
||||
@@ -87,7 +87,6 @@ import {
|
||||
pluginUpdateSchema,
|
||||
resourceAccessGrantWriteSchema,
|
||||
} from "./schemas.js"
|
||||
import { orgIdParamSchema } from "../shared.js"
|
||||
|
||||
type EndpointMethod = "DELETE" | "GET" | "PATCH" | "POST"
|
||||
type EndpointAudience = "admin" | "public_webhook"
|
||||
@@ -120,7 +119,7 @@ type DeferredEndpointContract = {
|
||||
tag: EndpointTag
|
||||
}
|
||||
|
||||
const orgBasePath = "/v1/orgs/:orgId"
|
||||
const orgBasePath = "/v1"
|
||||
|
||||
export const pluginArchRoutePaths = {
|
||||
configObjects: `${orgBasePath}/config-objects`,
|
||||
@@ -191,7 +190,7 @@ export const pluginArchEndpointContracts: Record<string, EndpointContract> = {
|
||||
description: "List current config object projections with search and connector filters.",
|
||||
method: "GET",
|
||||
path: pluginArchRoutePaths.configObjects,
|
||||
request: { params: orgIdParamSchema, query: configObjectListQuerySchema },
|
||||
request: { query: configObjectListQuerySchema },
|
||||
response: { description: "Current config object rows.", schema: configObjectListResponseSchema, status: 200 },
|
||||
tag: "Config Objects",
|
||||
},
|
||||
@@ -209,7 +208,7 @@ export const pluginArchEndpointContracts: Record<string, EndpointContract> = {
|
||||
description: "Create a cloud or imported config object and optionally attach it to plugins.",
|
||||
method: "POST",
|
||||
path: pluginArchRoutePaths.configObjects,
|
||||
request: { body: configObjectCreateSchema, params: orgIdParamSchema },
|
||||
request: { body: configObjectCreateSchema },
|
||||
response: { description: "Config object created successfully.", schema: configObjectMutationResponseSchema, status: 201 },
|
||||
tag: "Config Objects",
|
||||
},
|
||||
@@ -335,7 +334,7 @@ export const pluginArchEndpointContracts: Record<string, EndpointContract> = {
|
||||
description: "List accessible plugins for the organization.",
|
||||
method: "GET",
|
||||
path: pluginArchRoutePaths.plugins,
|
||||
request: { params: orgIdParamSchema, query: pluginListQuerySchema },
|
||||
request: { query: pluginListQuerySchema },
|
||||
response: { description: "Plugin list.", schema: pluginListResponseSchema, status: 200 },
|
||||
tag: "Plugins",
|
||||
},
|
||||
@@ -353,7 +352,7 @@ export const pluginArchEndpointContracts: Record<string, EndpointContract> = {
|
||||
description: "Create a private-by-default plugin.",
|
||||
method: "POST",
|
||||
path: pluginArchRoutePaths.plugins,
|
||||
request: { body: pluginCreateSchema, params: orgIdParamSchema },
|
||||
request: { body: pluginCreateSchema },
|
||||
response: { description: "Plugin created successfully.", schema: pluginMutationResponseSchema, status: 201 },
|
||||
tag: "Plugins",
|
||||
},
|
||||
@@ -452,7 +451,7 @@ export const pluginArchEndpointContracts: Record<string, EndpointContract> = {
|
||||
description: "List accessible marketplaces for the organization.",
|
||||
method: "GET",
|
||||
path: pluginArchRoutePaths.marketplaces,
|
||||
request: { params: orgIdParamSchema, query: marketplaceListQuerySchema },
|
||||
request: { query: marketplaceListQuerySchema },
|
||||
response: { description: "Marketplace list.", schema: marketplaceListResponseSchema, status: 200 },
|
||||
tag: "Marketplaces",
|
||||
},
|
||||
@@ -470,7 +469,7 @@ export const pluginArchEndpointContracts: Record<string, EndpointContract> = {
|
||||
description: "Create a private-by-default marketplace.",
|
||||
method: "POST",
|
||||
path: pluginArchRoutePaths.marketplaces,
|
||||
request: { body: marketplaceCreateSchema, params: orgIdParamSchema },
|
||||
request: { body: marketplaceCreateSchema },
|
||||
response: { description: "Marketplace created successfully.", schema: marketplaceMutationResponseSchema, status: 201 },
|
||||
tag: "Marketplaces",
|
||||
},
|
||||
@@ -560,7 +559,7 @@ export const pluginArchEndpointContracts: Record<string, EndpointContract> = {
|
||||
description: "List connector accounts such as GitHub App installations available to the org.",
|
||||
method: "GET",
|
||||
path: pluginArchRoutePaths.connectorAccounts,
|
||||
request: { params: orgIdParamSchema, query: connectorAccountListQuerySchema },
|
||||
request: { query: connectorAccountListQuerySchema },
|
||||
response: { description: "Connector account list.", schema: connectorAccountListResponseSchema, status: 200 },
|
||||
tag: "Connectors",
|
||||
},
|
||||
@@ -569,7 +568,7 @@ export const pluginArchEndpointContracts: Record<string, EndpointContract> = {
|
||||
description: "Create a reusable connector account record.",
|
||||
method: "POST",
|
||||
path: pluginArchRoutePaths.connectorAccounts,
|
||||
request: { body: connectorAccountCreateSchema, params: orgIdParamSchema },
|
||||
request: { body: connectorAccountCreateSchema },
|
||||
response: { description: "Connector account created successfully.", schema: connectorAccountMutationResponseSchema, status: 201 },
|
||||
tag: "Connectors",
|
||||
},
|
||||
@@ -596,7 +595,7 @@ export const pluginArchEndpointContracts: Record<string, EndpointContract> = {
|
||||
description: "List configured connector instances for the org.",
|
||||
method: "GET",
|
||||
path: pluginArchRoutePaths.connectorInstances,
|
||||
request: { params: orgIdParamSchema, query: connectorInstanceListQuerySchema },
|
||||
request: { query: connectorInstanceListQuerySchema },
|
||||
response: { description: "Connector instance list.", schema: connectorInstanceListResponseSchema, status: 200 },
|
||||
tag: "Connectors",
|
||||
},
|
||||
@@ -605,7 +604,7 @@ export const pluginArchEndpointContracts: Record<string, EndpointContract> = {
|
||||
description: "Create a connector instance backed by one connector account.",
|
||||
method: "POST",
|
||||
path: pluginArchRoutePaths.connectorInstances,
|
||||
request: { body: connectorInstanceCreateSchema, params: orgIdParamSchema },
|
||||
request: { body: connectorInstanceCreateSchema },
|
||||
response: { description: "Connector instance created successfully.", schema: connectorInstanceMutationResponseSchema, status: 201 },
|
||||
tag: "Connectors",
|
||||
},
|
||||
@@ -767,7 +766,7 @@ export const pluginArchEndpointContracts: Record<string, EndpointContract> = {
|
||||
description: "List connector sync events for inspection and debugging.",
|
||||
method: "GET",
|
||||
path: pluginArchRoutePaths.connectorSyncEvents,
|
||||
request: { params: orgIdParamSchema, query: connectorSyncEventListQuerySchema },
|
||||
request: { query: connectorSyncEventListQuerySchema },
|
||||
response: { description: "Connector sync event list.", schema: connectorSyncEventListResponseSchema, status: 200 },
|
||||
tag: "Connectors",
|
||||
},
|
||||
@@ -794,7 +793,7 @@ export const pluginArchEndpointContracts: Record<string, EndpointContract> = {
|
||||
description: "Create the GitHub connector account, instance, target, and initial mappings in one setup flow.",
|
||||
method: "POST",
|
||||
path: pluginArchRoutePaths.githubSetup,
|
||||
request: { body: githubConnectorSetupSchema, params: orgIdParamSchema },
|
||||
request: { body: githubConnectorSetupSchema },
|
||||
response: { description: "GitHub connector setup created successfully.", schema: githubSetupResponseSchema, status: 201 },
|
||||
tag: "GitHub",
|
||||
},
|
||||
@@ -803,7 +802,7 @@ export const pluginArchEndpointContracts: Record<string, EndpointContract> = {
|
||||
description: "Persist a GitHub App installation as a reusable connector account.",
|
||||
method: "POST",
|
||||
path: pluginArchRoutePaths.githubAccounts,
|
||||
request: { body: githubConnectorAccountCreateSchema, params: orgIdParamSchema },
|
||||
request: { body: githubConnectorAccountCreateSchema },
|
||||
response: { description: "GitHub connector account created successfully.", schema: connectorAccountMutationResponseSchema, status: 201 },
|
||||
tag: "GitHub",
|
||||
},
|
||||
@@ -821,7 +820,7 @@ export const pluginArchEndpointContracts: Record<string, EndpointContract> = {
|
||||
description: "Validate one GitHub repository-branch target before persisting it.",
|
||||
method: "POST",
|
||||
path: pluginArchRoutePaths.githubValidateTarget,
|
||||
request: { body: githubValidateTargetSchema, params: orgIdParamSchema },
|
||||
request: { body: githubValidateTargetSchema },
|
||||
response: { description: "GitHub target validation result.", schema: githubValidateTargetResponseSchema, status: 200 },
|
||||
tag: "GitHub",
|
||||
},
|
||||
|
||||
@@ -200,7 +200,6 @@ export function registerPluginArchRoutes<T extends { Variables: OrgRouteVariable
|
||||
app,
|
||||
"get",
|
||||
pluginArchRoutePaths.configObjects,
|
||||
paramValidator(configObjectParamsSchema.pick({ orgId: true })),
|
||||
queryValidator(configObjectListQuerySchema),
|
||||
describeRoute({
|
||||
tags: ["Config Objects"],
|
||||
@@ -233,7 +232,6 @@ export function registerPluginArchRoutes<T extends { Variables: OrgRouteVariable
|
||||
app,
|
||||
"post",
|
||||
pluginArchRoutePaths.configObjects,
|
||||
paramValidator(configObjectParamsSchema.pick({ orgId: true })),
|
||||
jsonValidator(configObjectCreateSchema),
|
||||
describeRoute({
|
||||
tags: ["Config Objects"],
|
||||
@@ -549,7 +547,6 @@ export function registerPluginArchRoutes<T extends { Variables: OrgRouteVariable
|
||||
})
|
||||
|
||||
withPluginArchOrgContext(app, "get", pluginArchRoutePaths.plugins,
|
||||
paramValidator(pluginParamsSchema.pick({ orgId: true })),
|
||||
queryValidator(pluginListQuerySchema),
|
||||
describeRoute({
|
||||
tags: ["Plugins"],
|
||||
@@ -567,7 +564,6 @@ export function registerPluginArchRoutes<T extends { Variables: OrgRouteVariable
|
||||
})
|
||||
|
||||
withPluginArchOrgContext(app, "post", pluginArchRoutePaths.plugins,
|
||||
paramValidator(pluginParamsSchema.pick({ orgId: true })),
|
||||
jsonValidator(pluginCreateSchema),
|
||||
describeRoute({
|
||||
tags: ["Plugins"],
|
||||
@@ -828,7 +824,6 @@ export function registerPluginArchRoutes<T extends { Variables: OrgRouteVariable
|
||||
})
|
||||
|
||||
withPluginArchOrgContext(app, "get", pluginArchRoutePaths.marketplaces,
|
||||
paramValidator(marketplaceParamsSchema.pick({ orgId: true })),
|
||||
queryValidator(marketplaceListQuerySchema),
|
||||
describeRoute({
|
||||
tags: ["Marketplaces"],
|
||||
@@ -846,7 +841,6 @@ export function registerPluginArchRoutes<T extends { Variables: OrgRouteVariable
|
||||
})
|
||||
|
||||
withPluginArchOrgContext(app, "post", pluginArchRoutePaths.marketplaces,
|
||||
paramValidator(marketplaceParamsSchema.pick({ orgId: true })),
|
||||
jsonValidator(marketplaceCreateSchema),
|
||||
describeRoute({
|
||||
tags: ["Marketplaces"],
|
||||
@@ -1085,7 +1079,6 @@ export function registerPluginArchRoutes<T extends { Variables: OrgRouteVariable
|
||||
})
|
||||
|
||||
withPluginArchOrgContext(app, "get", pluginArchRoutePaths.connectorAccounts,
|
||||
paramValidator(connectorAccountParamsSchema.pick({ orgId: true })),
|
||||
queryValidator(connectorAccountListQuerySchema),
|
||||
describeRoute({
|
||||
tags: ["Connectors"],
|
||||
@@ -1103,7 +1096,6 @@ export function registerPluginArchRoutes<T extends { Variables: OrgRouteVariable
|
||||
})
|
||||
|
||||
withPluginArchOrgContext(app, "post", pluginArchRoutePaths.connectorAccounts,
|
||||
paramValidator(connectorAccountParamsSchema.pick({ orgId: true })),
|
||||
jsonValidator(connectorAccountCreateSchema),
|
||||
describeRoute({
|
||||
tags: ["Connectors"],
|
||||
@@ -1176,7 +1168,6 @@ export function registerPluginArchRoutes<T extends { Variables: OrgRouteVariable
|
||||
})
|
||||
|
||||
withPluginArchOrgContext(app, "get", pluginArchRoutePaths.connectorInstances,
|
||||
paramValidator(connectorInstanceParamsSchema.pick({ orgId: true })),
|
||||
queryValidator(connectorInstanceListQuerySchema),
|
||||
describeRoute({
|
||||
tags: ["Connectors"],
|
||||
@@ -1194,7 +1185,6 @@ export function registerPluginArchRoutes<T extends { Variables: OrgRouteVariable
|
||||
})
|
||||
|
||||
withPluginArchOrgContext(app, "post", pluginArchRoutePaths.connectorInstances,
|
||||
paramValidator(connectorInstanceParamsSchema.pick({ orgId: true })),
|
||||
jsonValidator(connectorInstanceCreateSchema),
|
||||
describeRoute({
|
||||
tags: ["Connectors"],
|
||||
@@ -1577,7 +1567,6 @@ export function registerPluginArchRoutes<T extends { Variables: OrgRouteVariable
|
||||
})
|
||||
|
||||
withPluginArchOrgContext(app, "get", pluginArchRoutePaths.connectorSyncEvents,
|
||||
paramValidator(connectorSyncEventParamsSchema.pick({ orgId: true })),
|
||||
queryValidator(connectorSyncEventListQuerySchema),
|
||||
describeRoute({
|
||||
tags: ["Connectors"],
|
||||
@@ -1639,7 +1628,6 @@ export function registerPluginArchRoutes<T extends { Variables: OrgRouteVariable
|
||||
})
|
||||
|
||||
withPluginArchOrgContext(app, "post", pluginArchRoutePaths.githubAccounts,
|
||||
paramValidator(connectorAccountParamsSchema.pick({ orgId: true })),
|
||||
jsonValidator(githubConnectorAccountCreateSchema),
|
||||
describeRoute({
|
||||
tags: ["GitHub"],
|
||||
@@ -1664,7 +1652,6 @@ export function registerPluginArchRoutes<T extends { Variables: OrgRouteVariable
|
||||
})
|
||||
|
||||
withPluginArchOrgContext(app, "post", pluginArchRoutePaths.githubSetup,
|
||||
paramValidator(connectorAccountParamsSchema.pick({ orgId: true })),
|
||||
jsonValidator(githubConnectorSetupSchema),
|
||||
describeRoute({
|
||||
tags: ["GitHub"],
|
||||
@@ -1713,7 +1700,6 @@ export function registerPluginArchRoutes<T extends { Variables: OrgRouteVariable
|
||||
})
|
||||
|
||||
withPluginArchOrgContext(app, "post", pluginArchRoutePaths.githubValidateTarget,
|
||||
paramValidator(connectorAccountParamsSchema.pick({ orgId: true })),
|
||||
jsonValidator(githubValidateTargetSchema),
|
||||
describeRoute({
|
||||
tags: ["GitHub"],
|
||||
|
||||
@@ -17,7 +17,7 @@ import {
|
||||
} from "@openwork-ee/den-db/schema"
|
||||
import { z } from "zod"
|
||||
import { denTypeIdSchema } from "../../../openapi.js"
|
||||
import { idParamSchema, orgIdParamSchema } from "../shared.js"
|
||||
import { idParamSchema } from "../shared.js"
|
||||
|
||||
const cursorSchema = z.string().trim().min(1).max(255)
|
||||
const jsonObjectSchema = z.object({}).passthrough()
|
||||
@@ -132,21 +132,21 @@ export const githubRepositoryListQuerySchema = pluginArchPaginationQuerySchema.e
|
||||
q: z.string().trim().min(1).max(255).optional(),
|
||||
})
|
||||
|
||||
export const configObjectParamsSchema = orgIdParamSchema.extend(idParamSchema("configObjectId", "configObject").shape)
|
||||
export const configObjectParamsSchema = idParamSchema("configObjectId", "configObject")
|
||||
export const configObjectVersionParamsSchema = configObjectParamsSchema.extend(idParamSchema("versionId", "configObjectVersion").shape)
|
||||
export const configObjectAccessGrantParamsSchema = configObjectParamsSchema.extend(idParamSchema("grantId", "configObjectAccessGrant").shape)
|
||||
export const pluginParamsSchema = orgIdParamSchema.extend(idParamSchema("pluginId", "plugin").shape)
|
||||
export const pluginParamsSchema = idParamSchema("pluginId", "plugin")
|
||||
export const pluginConfigObjectParamsSchema = pluginParamsSchema.extend(idParamSchema("configObjectId", "configObject").shape)
|
||||
export const pluginAccessGrantParamsSchema = pluginParamsSchema.extend(idParamSchema("grantId", "pluginAccessGrant").shape)
|
||||
export const marketplaceParamsSchema = orgIdParamSchema.extend(idParamSchema("marketplaceId", "marketplace").shape)
|
||||
export const marketplaceParamsSchema = idParamSchema("marketplaceId", "marketplace")
|
||||
export const marketplacePluginParamsSchema = marketplaceParamsSchema.extend(idParamSchema("pluginId", "plugin").shape)
|
||||
export const marketplaceAccessGrantParamsSchema = marketplaceParamsSchema.extend(idParamSchema("grantId", "marketplaceAccessGrant").shape)
|
||||
export const connectorAccountParamsSchema = orgIdParamSchema.extend(idParamSchema("connectorAccountId", "connectorAccount").shape)
|
||||
export const connectorInstanceParamsSchema = orgIdParamSchema.extend(idParamSchema("connectorInstanceId", "connectorInstance").shape)
|
||||
export const connectorAccountParamsSchema = idParamSchema("connectorAccountId", "connectorAccount")
|
||||
export const connectorInstanceParamsSchema = idParamSchema("connectorInstanceId", "connectorInstance")
|
||||
export const connectorInstanceAccessGrantParamsSchema = connectorInstanceParamsSchema.extend(idParamSchema("grantId", "connectorInstanceAccessGrant").shape)
|
||||
export const connectorTargetParamsSchema = orgIdParamSchema.extend(idParamSchema("connectorTargetId", "connectorTarget").shape)
|
||||
export const connectorMappingParamsSchema = orgIdParamSchema.extend(idParamSchema("connectorMappingId", "connectorMapping").shape)
|
||||
export const connectorSyncEventParamsSchema = orgIdParamSchema.extend(idParamSchema("connectorSyncEventId", "connectorSyncEvent").shape)
|
||||
export const connectorTargetParamsSchema = idParamSchema("connectorTargetId", "connectorTarget")
|
||||
export const connectorMappingParamsSchema = idParamSchema("connectorMappingId", "connectorMapping")
|
||||
export const connectorSyncEventParamsSchema = idParamSchema("connectorSyncEventId", "connectorSyncEvent")
|
||||
|
||||
export const connectorAccountRepositoryParamsSchema = connectorAccountParamsSchema
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ import { jsonValidator, paramValidator, requireUserMiddleware, resolveOrganizati
|
||||
import { emptyResponse, forbiddenSchema, invalidRequestSchema, jsonResponse, notFoundSchema, successSchema, unauthorizedSchema } from "../../openapi.js"
|
||||
import { serializePermissionRecord } from "../../orgs.js"
|
||||
import type { OrgRouteVariables } from "./shared.js"
|
||||
import { createRoleId, ensureOwner, idParamSchema, normalizeRoleName, orgIdParamSchema, replaceRoleValue, splitRoles } from "./shared.js"
|
||||
import { createRoleId, ensureOwner, idParamSchema, normalizeRoleName, replaceRoleValue, splitRoles } from "./shared.js"
|
||||
|
||||
const permissionSchema = z.record(z.string(), z.array(z.string()))
|
||||
|
||||
@@ -24,11 +24,11 @@ const updateRoleSchema = z.object({
|
||||
})
|
||||
|
||||
type OrganizationRoleId = typeof OrganizationRoleTable.$inferSelect.id
|
||||
const orgRoleParamsSchema = orgIdParamSchema.extend(idParamSchema("roleId", "organizationRole").shape)
|
||||
const orgRoleParamsSchema = idParamSchema("roleId", "organizationRole")
|
||||
|
||||
export function registerOrgRoleRoutes<T extends { Variables: OrgRouteVariables }>(app: Hono<T>) {
|
||||
app.post(
|
||||
"/v1/orgs/:orgId/roles",
|
||||
"/v1/roles",
|
||||
describeRoute({
|
||||
tags: ["Roles"],
|
||||
summary: "Create organization role",
|
||||
@@ -42,7 +42,6 @@ export function registerOrgRoleRoutes<T extends { Variables: OrgRouteVariables }
|
||||
},
|
||||
}),
|
||||
requireUserMiddleware,
|
||||
paramValidator(orgIdParamSchema),
|
||||
resolveOrganizationContextMiddleware,
|
||||
jsonValidator(createRoleSchema),
|
||||
async (c) => {
|
||||
@@ -81,7 +80,7 @@ export function registerOrgRoleRoutes<T extends { Variables: OrgRouteVariables }
|
||||
)
|
||||
|
||||
app.patch(
|
||||
"/v1/orgs/:orgId/roles/:roleId",
|
||||
"/v1/roles/:roleId",
|
||||
describeRoute({
|
||||
tags: ["Roles"],
|
||||
summary: "Update organization role",
|
||||
@@ -188,7 +187,7 @@ export function registerOrgRoleRoutes<T extends { Variables: OrgRouteVariables }
|
||||
)
|
||||
|
||||
app.delete(
|
||||
"/v1/orgs/:orgId/roles/:roleId",
|
||||
"/v1/roles/:roleId",
|
||||
describeRoute({
|
||||
tags: ["Roles"],
|
||||
summary: "Delete organization role",
|
||||
|
||||
@@ -11,10 +11,6 @@ export type OrgRouteVariables =
|
||||
& Partial<OrganizationContextVariables>
|
||||
& Partial<MemberTeamsContext>
|
||||
|
||||
export const orgIdParamSchema = z.object({
|
||||
orgId: denTypeIdSchema("organization"),
|
||||
})
|
||||
|
||||
export function idParamSchema<K extends string>(key: K, typeName?: DenTypeIdName) {
|
||||
if (!typeName) {
|
||||
return z.object({
|
||||
|
||||
@@ -24,7 +24,7 @@ import {
|
||||
import type { MemberTeamsContext } from "../../middleware/member-teams.js"
|
||||
import { denTypeIdSchema, emptyResponse, forbiddenSchema, invalidRequestSchema, jsonResponse, notFoundSchema, successSchema, unauthorizedSchema } from "../../openapi.js"
|
||||
import type { OrgRouteVariables } from "./shared.js"
|
||||
import { idParamSchema, memberHasRole, orgIdParamSchema } from "./shared.js"
|
||||
import { idParamSchema, memberHasRole } from "./shared.js"
|
||||
|
||||
const skillTextSchema = z.string().superRefine((value, ctx) => {
|
||||
if (!value.trim()) {
|
||||
@@ -105,8 +105,8 @@ type MemberId = typeof MemberTable.$inferSelect.id
|
||||
type SkillRow = typeof SkillTable.$inferSelect
|
||||
type SkillHubRow = typeof SkillHubTable.$inferSelect
|
||||
|
||||
const orgSkillHubParamsSchema = orgIdParamSchema.extend(idParamSchema("skillHubId", "skillHub").shape)
|
||||
const orgSkillParamsSchema = orgIdParamSchema.extend(idParamSchema("skillId", "skill").shape)
|
||||
const orgSkillHubParamsSchema = idParamSchema("skillHubId", "skillHub")
|
||||
const orgSkillParamsSchema = idParamSchema("skillId", "skill")
|
||||
const orgSkillHubSkillParamsSchema = orgSkillHubParamsSchema.extend(idParamSchema("skillId", "skill").shape)
|
||||
const orgSkillHubAccessParamsSchema = orgSkillHubParamsSchema.extend(idParamSchema("accessId", "skillHubMember").shape)
|
||||
|
||||
@@ -263,7 +263,7 @@ function canViewSkill(input: {
|
||||
|
||||
export function registerOrgSkillRoutes<T extends { Variables: OrgRouteVariables & Partial<MemberTeamsContext> }>(app: Hono<T>) {
|
||||
app.post(
|
||||
"/v1/orgs/:orgId/skills",
|
||||
"/v1/skills",
|
||||
describeRoute({
|
||||
tags: ["Skills"],
|
||||
summary: "Create skill",
|
||||
@@ -275,7 +275,6 @@ export function registerOrgSkillRoutes<T extends { Variables: OrgRouteVariables
|
||||
},
|
||||
}),
|
||||
requireUserMiddleware,
|
||||
paramValidator(orgIdParamSchema),
|
||||
resolveOrganizationContextMiddleware,
|
||||
jsonValidator(createSkillSchema),
|
||||
async (c) => {
|
||||
@@ -314,7 +313,7 @@ export function registerOrgSkillRoutes<T extends { Variables: OrgRouteVariables
|
||||
)
|
||||
|
||||
app.get(
|
||||
"/v1/orgs/:orgId/skills",
|
||||
"/v1/skills",
|
||||
describeRoute({
|
||||
tags: ["Skills"],
|
||||
summary: "List skills",
|
||||
@@ -326,7 +325,6 @@ export function registerOrgSkillRoutes<T extends { Variables: OrgRouteVariables
|
||||
},
|
||||
}),
|
||||
requireUserMiddleware,
|
||||
paramValidator(orgIdParamSchema),
|
||||
resolveOrganizationContextMiddleware,
|
||||
resolveMemberTeamsMiddleware,
|
||||
async (c) => {
|
||||
@@ -360,7 +358,7 @@ export function registerOrgSkillRoutes<T extends { Variables: OrgRouteVariables
|
||||
)
|
||||
|
||||
app.delete(
|
||||
"/v1/orgs/:orgId/skills/:skillId",
|
||||
"/v1/skills/:skillId",
|
||||
describeRoute({
|
||||
tags: ["Skills"],
|
||||
summary: "Delete skill",
|
||||
@@ -412,7 +410,7 @@ export function registerOrgSkillRoutes<T extends { Variables: OrgRouteVariables
|
||||
)
|
||||
|
||||
app.patch(
|
||||
"/v1/orgs/:orgId/skills/:skillId",
|
||||
"/v1/skills/:skillId",
|
||||
describeRoute({
|
||||
tags: ["Skills"],
|
||||
summary: "Update skill",
|
||||
@@ -486,7 +484,7 @@ export function registerOrgSkillRoutes<T extends { Variables: OrgRouteVariables
|
||||
)
|
||||
|
||||
app.post(
|
||||
"/v1/orgs/:orgId/skill-hubs",
|
||||
"/v1/skill-hubs",
|
||||
describeRoute({
|
||||
tags: ["Skill Hubs"],
|
||||
summary: "Create skill hub",
|
||||
@@ -498,7 +496,6 @@ export function registerOrgSkillRoutes<T extends { Variables: OrgRouteVariables
|
||||
},
|
||||
}),
|
||||
requireUserMiddleware,
|
||||
paramValidator(orgIdParamSchema),
|
||||
resolveOrganizationContextMiddleware,
|
||||
jsonValidator(createSkillHubSchema),
|
||||
async (c) => {
|
||||
@@ -542,7 +539,7 @@ export function registerOrgSkillRoutes<T extends { Variables: OrgRouteVariables
|
||||
)
|
||||
|
||||
app.get(
|
||||
"/v1/orgs/:orgId/skill-hubs",
|
||||
"/v1/skill-hubs",
|
||||
describeRoute({
|
||||
tags: ["Skill Hubs"],
|
||||
summary: "List skill hubs",
|
||||
@@ -554,7 +551,6 @@ export function registerOrgSkillRoutes<T extends { Variables: OrgRouteVariables
|
||||
},
|
||||
}),
|
||||
requireUserMiddleware,
|
||||
paramValidator(orgIdParamSchema),
|
||||
resolveOrganizationContextMiddleware,
|
||||
resolveMemberTeamsMiddleware,
|
||||
async (c) => {
|
||||
@@ -698,7 +694,7 @@ export function registerOrgSkillRoutes<T extends { Variables: OrgRouteVariables
|
||||
)
|
||||
|
||||
app.patch(
|
||||
"/v1/orgs/:orgId/skill-hubs/:skillHubId",
|
||||
"/v1/skill-hubs/:skillHubId",
|
||||
describeRoute({
|
||||
tags: ["Skill Hubs"],
|
||||
summary: "Update skill hub",
|
||||
@@ -767,7 +763,7 @@ export function registerOrgSkillRoutes<T extends { Variables: OrgRouteVariables
|
||||
)
|
||||
|
||||
app.delete(
|
||||
"/v1/orgs/:orgId/skill-hubs/:skillHubId",
|
||||
"/v1/skill-hubs/:skillHubId",
|
||||
describeRoute({
|
||||
tags: ["Skill Hubs"],
|
||||
summary: "Delete skill hub",
|
||||
@@ -820,7 +816,7 @@ export function registerOrgSkillRoutes<T extends { Variables: OrgRouteVariables
|
||||
)
|
||||
|
||||
app.post(
|
||||
"/v1/orgs/:orgId/skill-hubs/:skillHubId/skills",
|
||||
"/v1/skill-hubs/:skillHubId/skills",
|
||||
describeRoute({
|
||||
tags: ["Skill Hubs"],
|
||||
summary: "Add skill to skill hub",
|
||||
@@ -908,7 +904,7 @@ export function registerOrgSkillRoutes<T extends { Variables: OrgRouteVariables
|
||||
)
|
||||
|
||||
app.delete(
|
||||
"/v1/orgs/:orgId/skill-hubs/:skillHubId/skills/:skillId",
|
||||
"/v1/skill-hubs/:skillHubId/skills/:skillId",
|
||||
describeRoute({
|
||||
tags: ["Skill Hubs"],
|
||||
summary: "Remove skill from skill hub",
|
||||
@@ -971,7 +967,7 @@ export function registerOrgSkillRoutes<T extends { Variables: OrgRouteVariables
|
||||
)
|
||||
|
||||
app.post(
|
||||
"/v1/orgs/:orgId/skill-hubs/:skillHubId/access",
|
||||
"/v1/skill-hubs/:skillHubId/access",
|
||||
describeRoute({
|
||||
tags: ["Skill Hubs"],
|
||||
summary: "Grant skill hub access",
|
||||
@@ -1082,7 +1078,7 @@ export function registerOrgSkillRoutes<T extends { Variables: OrgRouteVariables
|
||||
)
|
||||
|
||||
app.delete(
|
||||
"/v1/orgs/:orgId/skill-hubs/:skillHubId/access/:accessId",
|
||||
"/v1/skill-hubs/:skillHubId/access/:accessId",
|
||||
describeRoute({
|
||||
tags: ["Skill Hubs"],
|
||||
summary: "Revoke skill hub access",
|
||||
|
||||
@@ -21,7 +21,6 @@ import type { OrgRouteVariables } from "./shared.js"
|
||||
import {
|
||||
ensureTeamManager,
|
||||
idParamSchema,
|
||||
orgIdParamSchema,
|
||||
} from "./shared.js"
|
||||
|
||||
const createTeamSchema = z.object({
|
||||
@@ -45,7 +44,7 @@ const updateTeamSchema = z.object({
|
||||
type TeamId = typeof TeamTable.$inferSelect.id
|
||||
type MemberId = typeof MemberTable.$inferSelect.id
|
||||
|
||||
const orgTeamParamsSchema = orgIdParamSchema.extend(idParamSchema("teamId", "team").shape)
|
||||
const orgTeamParamsSchema = idParamSchema("teamId", "team")
|
||||
|
||||
const teamResponseSchema = z.object({
|
||||
team: z.object({
|
||||
@@ -85,7 +84,7 @@ async function ensureMembersBelongToOrganization(input: {
|
||||
|
||||
export function registerOrgTeamRoutes<T extends { Variables: OrgRouteVariables }>(app: Hono<T>) {
|
||||
app.post(
|
||||
"/v1/orgs/:orgId/teams",
|
||||
"/v1/teams",
|
||||
describeRoute({
|
||||
tags: ["Teams"],
|
||||
summary: "Create team",
|
||||
@@ -99,7 +98,6 @@ export function registerOrgTeamRoutes<T extends { Variables: OrgRouteVariables }
|
||||
},
|
||||
}),
|
||||
requireUserMiddleware,
|
||||
paramValidator(orgIdParamSchema),
|
||||
resolveOrganizationContextMiddleware,
|
||||
jsonValidator(createTeamSchema),
|
||||
async (c) => {
|
||||
@@ -174,7 +172,7 @@ export function registerOrgTeamRoutes<T extends { Variables: OrgRouteVariables }
|
||||
)
|
||||
|
||||
app.patch(
|
||||
"/v1/orgs/:orgId/teams/:teamId",
|
||||
"/v1/teams/:teamId",
|
||||
describeRoute({
|
||||
tags: ["Teams"],
|
||||
summary: "Update team",
|
||||
@@ -278,7 +276,7 @@ export function registerOrgTeamRoutes<T extends { Variables: OrgRouteVariables }
|
||||
)
|
||||
|
||||
app.delete(
|
||||
"/v1/orgs/:orgId/teams/:teamId",
|
||||
"/v1/teams/:teamId",
|
||||
describeRoute({
|
||||
tags: ["Teams"],
|
||||
summary: "Delete team",
|
||||
|
||||
@@ -8,7 +8,7 @@ import { db } from "../../db.js"
|
||||
import { jsonValidator, paramValidator, requireUserMiddleware, resolveOrganizationContextMiddleware } from "../../middleware/index.js"
|
||||
import { denTypeIdSchema, emptyResponse, forbiddenSchema, invalidRequestSchema, jsonResponse, notFoundSchema, unauthorizedSchema } from "../../openapi.js"
|
||||
import type { OrgRouteVariables } from "./shared.js"
|
||||
import { idParamSchema, orgIdParamSchema, parseTemplateJson } from "./shared.js"
|
||||
import { idParamSchema, parseTemplateJson } from "./shared.js"
|
||||
|
||||
const createTemplateSchema = z.object({
|
||||
name: z.string().trim().min(1).max(255),
|
||||
@@ -41,11 +41,11 @@ const templateListResponseSchema = z.object({
|
||||
}).meta({ ref: "TemplateListResponse" })
|
||||
|
||||
type TemplateSharingId = typeof TempTemplateSharingTable.$inferSelect.id
|
||||
const orgTemplateParamsSchema = orgIdParamSchema.extend(idParamSchema("templateId", "tempTemplateSharing").shape)
|
||||
const orgTemplateParamsSchema = idParamSchema("templateId", "tempTemplateSharing")
|
||||
|
||||
export function registerOrgTemplateRoutes<T extends { Variables: OrgRouteVariables }>(app: Hono<T>) {
|
||||
app.post(
|
||||
"/v1/orgs/:orgId/templates",
|
||||
"/v1/templates",
|
||||
describeRoute({
|
||||
tags: ["Templates"],
|
||||
summary: "Create shared template",
|
||||
@@ -58,7 +58,6 @@ export function registerOrgTemplateRoutes<T extends { Variables: OrgRouteVariabl
|
||||
},
|
||||
}),
|
||||
requireUserMiddleware,
|
||||
paramValidator(orgIdParamSchema),
|
||||
resolveOrganizationContextMiddleware,
|
||||
jsonValidator(createTemplateSchema),
|
||||
async (c) => {
|
||||
@@ -101,7 +100,7 @@ export function registerOrgTemplateRoutes<T extends { Variables: OrgRouteVariabl
|
||||
)
|
||||
|
||||
app.get(
|
||||
"/v1/orgs/:orgId/templates",
|
||||
"/v1/templates",
|
||||
describeRoute({
|
||||
tags: ["Templates"],
|
||||
summary: "List shared templates",
|
||||
@@ -114,7 +113,6 @@ export function registerOrgTemplateRoutes<T extends { Variables: OrgRouteVariabl
|
||||
},
|
||||
}),
|
||||
requireUserMiddleware,
|
||||
paramValidator(orgIdParamSchema),
|
||||
resolveOrganizationContextMiddleware,
|
||||
async (c) => {
|
||||
const payload = c.get("organizationContext")
|
||||
@@ -168,7 +166,7 @@ export function registerOrgTemplateRoutes<T extends { Variables: OrgRouteVariabl
|
||||
)
|
||||
|
||||
app.delete(
|
||||
"/v1/orgs/:orgId/templates/:templateId",
|
||||
"/v1/templates/:templateId",
|
||||
describeRoute({
|
||||
tags: ["Templates"],
|
||||
summary: "Delete shared template",
|
||||
|
||||
@@ -153,7 +153,7 @@ export function OrganizationScreen() {
|
||||
setCreateBusy(true);
|
||||
setCreateError(null);
|
||||
try {
|
||||
const { response, payload } = await requestJson("/v1/orgs", {
|
||||
const { response, payload } = await requestJson("/v1/org", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({ name: trimmed }),
|
||||
});
|
||||
|
||||
@@ -109,6 +109,13 @@ export type DenOrgContext = {
|
||||
metadata: string | null;
|
||||
createdAt: string | null;
|
||||
updatedAt: string | null;
|
||||
owner: {
|
||||
memberId: string;
|
||||
userId: string;
|
||||
name: string | null;
|
||||
email: string | null;
|
||||
image: string | null;
|
||||
} | null;
|
||||
};
|
||||
currentMember: {
|
||||
id: string;
|
||||
@@ -196,95 +203,95 @@ export function formatRoleLabel(role: string): string {
|
||||
.join(" ");
|
||||
}
|
||||
|
||||
export function getOrgDashboardRoute(orgSlug: string): string {
|
||||
return `/o/${encodeURIComponent(orgSlug)}/dashboard`;
|
||||
export function getOrgDashboardRoute(_orgSlug?: string | null): string {
|
||||
return "/dashboard";
|
||||
}
|
||||
|
||||
export function getJoinOrgRoute(invitationId: string): string {
|
||||
return `/join-org?invite=${encodeURIComponent(invitationId)}`;
|
||||
}
|
||||
|
||||
export function getManageMembersRoute(orgSlug: string): string {
|
||||
export function getManageMembersRoute(orgSlug?: string | null): string {
|
||||
return `${getOrgDashboardRoute(orgSlug)}/manage-members`;
|
||||
}
|
||||
|
||||
export function getMembersRoute(orgSlug: string): string {
|
||||
export function getMembersRoute(orgSlug?: string | null): string {
|
||||
return `${getOrgDashboardRoute(orgSlug)}/members`;
|
||||
}
|
||||
|
||||
export function getSharedSetupsRoute(orgSlug: string): string {
|
||||
export function getSharedSetupsRoute(orgSlug?: string | null): string {
|
||||
return `${getOrgDashboardRoute(orgSlug)}/shared-setups`;
|
||||
}
|
||||
|
||||
export function getBackgroundAgentsRoute(orgSlug: string): string {
|
||||
export function getBackgroundAgentsRoute(orgSlug?: string | null): string {
|
||||
return `${getOrgDashboardRoute(orgSlug)}/background-agents`;
|
||||
}
|
||||
|
||||
export function getCustomLlmProvidersRoute(orgSlug: string): string {
|
||||
export function getCustomLlmProvidersRoute(orgSlug?: string | null): string {
|
||||
return `${getOrgDashboardRoute(orgSlug)}/custom-llm-providers`;
|
||||
}
|
||||
|
||||
export function getLlmProvidersRoute(orgSlug: string): string {
|
||||
export function getLlmProvidersRoute(orgSlug?: string | null): string {
|
||||
return getCustomLlmProvidersRoute(orgSlug);
|
||||
}
|
||||
|
||||
export function getLlmProviderRoute(orgSlug: string, llmProviderId: string): string {
|
||||
export function getLlmProviderRoute(orgSlug: string | null | undefined, llmProviderId: string): string {
|
||||
return `${getLlmProvidersRoute(orgSlug)}/${encodeURIComponent(llmProviderId)}`;
|
||||
}
|
||||
|
||||
export function getEditLlmProviderRoute(orgSlug: string, llmProviderId: string): string {
|
||||
export function getEditLlmProviderRoute(orgSlug: string | null | undefined, llmProviderId: string): string {
|
||||
return `${getLlmProviderRoute(orgSlug, llmProviderId)}/edit`;
|
||||
}
|
||||
|
||||
export function getNewLlmProviderRoute(orgSlug: string): string {
|
||||
export function getNewLlmProviderRoute(orgSlug?: string | null): string {
|
||||
return `${getLlmProvidersRoute(orgSlug)}/new`;
|
||||
}
|
||||
|
||||
export function getBillingRoute(orgSlug: string): string {
|
||||
export function getBillingRoute(orgSlug?: string | null): string {
|
||||
return `${getOrgDashboardRoute(orgSlug)}/billing`;
|
||||
}
|
||||
|
||||
export function getApiKeysRoute(orgSlug: string): string {
|
||||
export function getApiKeysRoute(orgSlug?: string | null): string {
|
||||
return `${getOrgDashboardRoute(orgSlug)}/api-keys`;
|
||||
}
|
||||
|
||||
export function getSkillHubsRoute(orgSlug: string): string {
|
||||
export function getSkillHubsRoute(orgSlug?: string | null): string {
|
||||
return `${getOrgDashboardRoute(orgSlug)}/skill-hubs`;
|
||||
}
|
||||
|
||||
export function getSkillHubRoute(orgSlug: string, skillHubId: string): string {
|
||||
export function getSkillHubRoute(orgSlug: string | null | undefined, skillHubId: string): string {
|
||||
return `${getSkillHubsRoute(orgSlug)}/${encodeURIComponent(skillHubId)}`;
|
||||
}
|
||||
|
||||
export function getEditSkillHubRoute(orgSlug: string, skillHubId: string): string {
|
||||
export function getEditSkillHubRoute(orgSlug: string | null | undefined, skillHubId: string): string {
|
||||
return `${getSkillHubRoute(orgSlug, skillHubId)}/edit`;
|
||||
}
|
||||
|
||||
export function getNewSkillHubRoute(orgSlug: string): string {
|
||||
export function getNewSkillHubRoute(orgSlug?: string | null): string {
|
||||
return `${getSkillHubsRoute(orgSlug)}/new`;
|
||||
}
|
||||
|
||||
export function getSkillDetailRoute(orgSlug: string, skillId: string): string {
|
||||
export function getSkillDetailRoute(orgSlug: string | null | undefined, skillId: string): string {
|
||||
return `${getSkillHubsRoute(orgSlug)}/skills/${encodeURIComponent(skillId)}`;
|
||||
}
|
||||
|
||||
export function getEditSkillRoute(orgSlug: string, skillId: string): string {
|
||||
export function getEditSkillRoute(orgSlug: string | null | undefined, skillId: string): string {
|
||||
return `${getSkillDetailRoute(orgSlug, skillId)}/edit`;
|
||||
}
|
||||
|
||||
export function getNewSkillRoute(orgSlug: string): string {
|
||||
export function getNewSkillRoute(orgSlug?: string | null): string {
|
||||
return `${getSkillHubsRoute(orgSlug)}/skills/new`;
|
||||
}
|
||||
|
||||
export function getPluginsRoute(orgSlug: string): string {
|
||||
export function getPluginsRoute(orgSlug?: string | null): string {
|
||||
return `${getOrgDashboardRoute(orgSlug)}/plugins`;
|
||||
}
|
||||
|
||||
export function getPluginRoute(orgSlug: string, pluginId: string): string {
|
||||
export function getPluginRoute(orgSlug: string | null | undefined, pluginId: string): string {
|
||||
return `${getPluginsRoute(orgSlug)}/${encodeURIComponent(pluginId)}`;
|
||||
}
|
||||
|
||||
export function getIntegrationsRoute(orgSlug: string): string {
|
||||
export function getIntegrationsRoute(orgSlug?: string | null): string {
|
||||
return `${getOrgDashboardRoute(orgSlug)}/integrations`;
|
||||
}
|
||||
|
||||
@@ -346,6 +353,9 @@ export function parseOrgContextPayload(payload: unknown): DenOrgContext | null {
|
||||
const organizationId = asString(organization.id);
|
||||
const organizationName = asString(organization.name);
|
||||
const organizationSlug = asString(organization.slug);
|
||||
const organizationOwner = isRecord(organization.owner) ? organization.owner : null;
|
||||
const organizationOwnerMemberId = organizationOwner ? asString(organizationOwner.memberId) : null;
|
||||
const organizationOwnerUserId = organizationOwner ? asString(organizationOwner.userId) : null;
|
||||
const currentMemberId = asString(currentMember.id);
|
||||
const currentMemberUserId = asString(currentMember.userId);
|
||||
const currentMemberRole = asString(currentMember.role);
|
||||
@@ -498,6 +508,15 @@ export function parseOrgContextPayload(payload: unknown): DenOrgContext | null {
|
||||
metadata: asString(organization.metadata),
|
||||
createdAt: asIsoString(organization.createdAt),
|
||||
updatedAt: asIsoString(organization.updatedAt),
|
||||
owner: organizationOwner && organizationOwnerMemberId && organizationOwnerUserId
|
||||
? {
|
||||
memberId: organizationOwnerMemberId,
|
||||
userId: organizationOwnerUserId,
|
||||
name: asString(organizationOwner.name),
|
||||
email: asString(organizationOwner.email),
|
||||
image: asString(organizationOwner.image),
|
||||
}
|
||||
: null,
|
||||
},
|
||||
currentMember: {
|
||||
id: currentMemberId,
|
||||
|
||||
1
ee/apps/den-web/app/(den)/dashboard/api-keys/page.tsx
Normal file
1
ee/apps/den-web/app/(den)/dashboard/api-keys/page.tsx
Normal file
@@ -0,0 +1 @@
|
||||
export { default } from "../../o/[orgSlug]/dashboard/api-keys/page";
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from "../../o/[orgSlug]/dashboard/background-agents/page";
|
||||
1
ee/apps/den-web/app/(den)/dashboard/billing/page.tsx
Normal file
1
ee/apps/den-web/app/(den)/dashboard/billing/page.tsx
Normal file
@@ -0,0 +1 @@
|
||||
export { default } from "../../o/[orgSlug]/dashboard/billing/page";
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from "../../../../o/[orgSlug]/dashboard/custom-llm-providers/[llmProviderId]/edit/page";
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from "../../../o/[orgSlug]/dashboard/custom-llm-providers/[llmProviderId]/page";
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from "../../../o/[orgSlug]/dashboard/custom-llm-providers/new/page";
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from "../../o/[orgSlug]/dashboard/custom-llm-providers/page";
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from "../../o/[orgSlug]/dashboard/integrations/page";
|
||||
17
ee/apps/den-web/app/(den)/dashboard/layout.tsx
Normal file
17
ee/apps/den-web/app/(den)/dashboard/layout.tsx
Normal file
@@ -0,0 +1,17 @@
|
||||
import { OrgDashboardShell } from "../o/[orgSlug]/dashboard/_components/org-dashboard-shell";
|
||||
import { OrgDashboardProvider } from "../o/[orgSlug]/dashboard/_providers/org-dashboard-provider";
|
||||
import { DashboardQueryClientProvider } from "../o/[orgSlug]/dashboard/_providers/query-client-provider";
|
||||
|
||||
export default function DashboardLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<DashboardQueryClientProvider>
|
||||
<OrgDashboardProvider>
|
||||
<OrgDashboardShell>{children}</OrgDashboardShell>
|
||||
</OrgDashboardProvider>
|
||||
</DashboardQueryClientProvider>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from "../../o/[orgSlug]/dashboard/manage-members/page";
|
||||
1
ee/apps/den-web/app/(den)/dashboard/members/page.tsx
Normal file
1
ee/apps/den-web/app/(den)/dashboard/members/page.tsx
Normal file
@@ -0,0 +1 @@
|
||||
export { default } from "../../o/[orgSlug]/dashboard/members/page";
|
||||
@@ -1,5 +1 @@
|
||||
import { DashboardRedirectScreen } from "../_components/dashboard-redirect-screen";
|
||||
|
||||
export default function DashboardPage() {
|
||||
return <DashboardRedirectScreen />;
|
||||
}
|
||||
export { default } from "../o/[orgSlug]/dashboard/page";
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from "../../../o/[orgSlug]/dashboard/plugins/[pluginId]/page";
|
||||
1
ee/apps/den-web/app/(den)/dashboard/plugins/page.tsx
Normal file
1
ee/apps/den-web/app/(den)/dashboard/plugins/page.tsx
Normal file
@@ -0,0 +1 @@
|
||||
export { default } from "../../o/[orgSlug]/dashboard/plugins/page";
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from "../../o/[orgSlug]/dashboard/shared-setups/page";
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from "../../../../o/[orgSlug]/dashboard/skill-hubs/[skillHubId]/edit/page";
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from "../../../o/[orgSlug]/dashboard/skill-hubs/[skillHubId]/page";
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from "../../../o/[orgSlug]/dashboard/skill-hubs/new/page";
|
||||
1
ee/apps/den-web/app/(den)/dashboard/skill-hubs/page.tsx
Normal file
1
ee/apps/den-web/app/(den)/dashboard/skill-hubs/page.tsx
Normal file
@@ -0,0 +1 @@
|
||||
export { default } from "../../o/[orgSlug]/dashboard/skill-hubs/page";
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from "../../../../../o/[orgSlug]/dashboard/skill-hubs/skills/[skillId]/edit/page";
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from "../../../../o/[orgSlug]/dashboard/skill-hubs/skills/[skillId]/page";
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from "../../../../o/[orgSlug]/dashboard/skill-hubs/skills/new/page";
|
||||
@@ -83,7 +83,7 @@ export function ApiKeysScreen() {
|
||||
setError(null);
|
||||
try {
|
||||
const { response, payload } = await requestJson(
|
||||
`/v1/orgs/${encodeURIComponent(orgId)}/api-keys`,
|
||||
`/v1/api-keys`,
|
||||
{ method: "GET" },
|
||||
12000,
|
||||
);
|
||||
@@ -135,7 +135,7 @@ export function ApiKeysScreen() {
|
||||
setCopied(false);
|
||||
try {
|
||||
const { response, payload } = await requestJson(
|
||||
`/v1/orgs/${encodeURIComponent(orgId)}/api-keys`,
|
||||
`/v1/api-keys`,
|
||||
{
|
||||
method: "POST",
|
||||
body: JSON.stringify({ name }),
|
||||
@@ -203,7 +203,7 @@ export function ApiKeysScreen() {
|
||||
setError(null);
|
||||
try {
|
||||
const { response, payload } = await requestJson(
|
||||
`/v1/orgs/${encodeURIComponent(orgId)}/api-keys/${encodeURIComponent(apiKey.id)}`,
|
||||
`/v1/api-keys/${encodeURIComponent(apiKey.id)}`,
|
||||
{ method: "DELETE" },
|
||||
12000,
|
||||
);
|
||||
|
||||
@@ -34,7 +34,7 @@ import {
|
||||
} from "../../../../_lib/den-flow";
|
||||
import { buildDenFeedbackUrl } from "../../../../_lib/feedback";
|
||||
import { useDenFlow } from "../../../../_providers/den-flow-provider";
|
||||
import { getSharedSetupsRoute } from "../../../../_lib/den-org";
|
||||
import { getBackgroundAgentsRoute, getSharedSetupsRoute } from "../../../../_lib/den-org";
|
||||
import { useOrgDashboard } from "../_providers/org-dashboard-provider";
|
||||
|
||||
type ConnectionDetails = {
|
||||
@@ -317,7 +317,7 @@ export function BackgroundAgentsScreen() {
|
||||
renameBusyWorkerId,
|
||||
} = useDenFlow();
|
||||
const feedbackHref = buildDenFeedbackUrl({
|
||||
pathname: `/o/${orgSlug}/dashboard/background-agents`,
|
||||
pathname: getBackgroundAgentsRoute(orgSlug),
|
||||
orgSlug,
|
||||
topic: "workspace-limits",
|
||||
});
|
||||
|
||||
@@ -353,7 +353,7 @@ export function buildEditableCustomProviderText(provider: DenLlmProvider) {
|
||||
}
|
||||
|
||||
export async function requestLlmProviderCatalog(orgId: string) {
|
||||
const { response, payload } = await requestJson(`/v1/orgs/${encodeURIComponent(orgId)}/llm-provider-catalog`, { method: "GET" }, 20000);
|
||||
const { response, payload } = await requestJson(`/v1/llm-provider-catalog`, { method: "GET" }, 20000);
|
||||
if (!response.ok) {
|
||||
throw new Error(getErrorMessage(payload, `Failed to load the provider catalog (${response.status}).`));
|
||||
}
|
||||
@@ -365,7 +365,7 @@ export async function requestLlmProviderCatalog(orgId: string) {
|
||||
|
||||
export async function requestLlmProviderCatalogDetail(orgId: string, providerId: string) {
|
||||
const { response, payload } = await requestJson(
|
||||
`/v1/orgs/${encodeURIComponent(orgId)}/llm-provider-catalog/${encodeURIComponent(providerId)}`,
|
||||
`/v1/llm-provider-catalog/${encodeURIComponent(providerId)}`,
|
||||
{ method: "GET" },
|
||||
20000,
|
||||
);
|
||||
@@ -401,7 +401,7 @@ export function useOrgLlmProviders(orgId: string | null) {
|
||||
setBusy(true);
|
||||
setError(null);
|
||||
try {
|
||||
const { response, payload } = await requestJson(`/v1/orgs/${encodeURIComponent(orgId)}/llm-providers`, { method: "GET" }, 15000);
|
||||
const { response, payload } = await requestJson(`/v1/llm-providers`, { method: "GET" }, 15000);
|
||||
if (!response.ok) {
|
||||
throw new Error(getErrorMessage(payload, `Failed to load providers (${response.status}).`));
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ export function LlmProviderDetailScreen({
|
||||
setDeleteError(null);
|
||||
try {
|
||||
const { response, payload } = await requestJson(
|
||||
`/v1/orgs/${encodeURIComponent(orgId)}/llm-providers/${encodeURIComponent(provider.id)}`,
|
||||
`/v1/llm-providers/${encodeURIComponent(provider.id)}`,
|
||||
{ method: "DELETE" },
|
||||
12000,
|
||||
);
|
||||
|
||||
@@ -298,8 +298,8 @@ export function LlmProviderEditorScreen({
|
||||
}
|
||||
|
||||
const path = provider
|
||||
? `/v1/orgs/${encodeURIComponent(orgId)}/llm-providers/${encodeURIComponent(provider.id)}`
|
||||
: `/v1/orgs/${encodeURIComponent(orgId)}/llm-providers`;
|
||||
? `/v1/llm-providers/${encodeURIComponent(provider.id)}`
|
||||
: `/v1/llm-providers`;
|
||||
const method = provider ? "PATCH" : "POST";
|
||||
|
||||
const { response, payload } = await requestJson(
|
||||
|
||||
@@ -87,7 +87,7 @@ export function useOrgTemplates(orgId: string | null) {
|
||||
}
|
||||
|
||||
const { response, payload } = await requestJson(
|
||||
`/v1/orgs/${encodeURIComponent(orgId)}/templates`,
|
||||
`/v1/templates`,
|
||||
{ method: "GET" },
|
||||
12000,
|
||||
);
|
||||
|
||||
@@ -95,7 +95,7 @@ export function SkillEditorScreen({ skillId }: { skillId?: string }) {
|
||||
const shared = visibility === "private" ? null : visibility;
|
||||
if (skillId) {
|
||||
const { response, payload } = await requestJson(
|
||||
`/v1/orgs/${encodeURIComponent(orgId)}/skills/${encodeURIComponent(skillId)}`,
|
||||
`/v1/skills/${encodeURIComponent(skillId)}`,
|
||||
{ method: "PATCH", body: JSON.stringify({ skillText, shared }) },
|
||||
12000,
|
||||
);
|
||||
@@ -103,7 +103,7 @@ export function SkillEditorScreen({ skillId }: { skillId?: string }) {
|
||||
router.push(getSkillDetailRoute(orgSlug, skillId));
|
||||
} else {
|
||||
const { response, payload } = await requestJson(
|
||||
`/v1/orgs/${encodeURIComponent(orgId)}/skills`,
|
||||
`/v1/skills`,
|
||||
{ method: "POST", body: JSON.stringify({ skillText, shared }) },
|
||||
12000,
|
||||
);
|
||||
|
||||
@@ -363,8 +363,8 @@ export function useOrgSkillLibrary(orgId: string | null) {
|
||||
setError(null);
|
||||
try {
|
||||
const [skillsResult, skillHubsResult] = await Promise.all([
|
||||
requestJson(`/v1/orgs/${encodeURIComponent(orgId)}/skills`, { method: "GET" }, 12000),
|
||||
requestJson(`/v1/orgs/${encodeURIComponent(orgId)}/skill-hubs`, { method: "GET" }, 12000),
|
||||
requestJson(`/v1/skills`, { method: "GET" }, 12000),
|
||||
requestJson(`/v1/skill-hubs`, { method: "GET" }, 12000),
|
||||
]);
|
||||
|
||||
if (!skillsResult.response.ok) {
|
||||
|
||||
@@ -130,7 +130,7 @@ export function SkillHubEditorScreen({ skillHubId }: { skillHubId?: string }) {
|
||||
|
||||
if (!nextSkillHubId) {
|
||||
const { response, payload } = await requestJson(
|
||||
`/v1/orgs/${encodeURIComponent(orgId)}/skill-hubs`,
|
||||
`/v1/skill-hubs`,
|
||||
{
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
@@ -172,7 +172,7 @@ export function SkillHubEditorScreen({ skillHubId }: { skillHubId?: string }) {
|
||||
(skillHub.description ?? "") !== description.trim())
|
||||
) {
|
||||
const { response, payload } = await requestJson(
|
||||
`/v1/orgs/${encodeURIComponent(orgId)}/skill-hubs/${encodeURIComponent(nextSkillHubId)}`,
|
||||
`/v1/skill-hubs/${encodeURIComponent(nextSkillHubId)}`,
|
||||
{
|
||||
method: "PATCH",
|
||||
body: JSON.stringify({
|
||||
@@ -209,7 +209,7 @@ export function SkillHubEditorScreen({ skillHubId }: { skillHubId?: string }) {
|
||||
await Promise.all(
|
||||
teamIdsToAdd.map(async (teamId) => {
|
||||
const { response, payload } = await requestJson(
|
||||
`/v1/orgs/${encodeURIComponent(orgId)}/skill-hubs/${encodeURIComponent(nextSkillHubId)}/access`,
|
||||
`/v1/skill-hubs/${encodeURIComponent(nextSkillHubId)}/access`,
|
||||
{
|
||||
method: "POST",
|
||||
body: JSON.stringify({ teamId }),
|
||||
@@ -231,7 +231,7 @@ export function SkillHubEditorScreen({ skillHubId }: { skillHubId?: string }) {
|
||||
await Promise.all(
|
||||
teamAccessIdsToRemove.map(async (accessId) => {
|
||||
const { response, payload } = await requestJson(
|
||||
`/v1/orgs/${encodeURIComponent(orgId)}/skill-hubs/${encodeURIComponent(nextSkillHubId)}/access/${encodeURIComponent(accessId)}`,
|
||||
`/v1/skill-hubs/${encodeURIComponent(nextSkillHubId)}/access/${encodeURIComponent(accessId)}`,
|
||||
{ method: "DELETE" },
|
||||
12000,
|
||||
);
|
||||
@@ -250,7 +250,7 @@ export function SkillHubEditorScreen({ skillHubId }: { skillHubId?: string }) {
|
||||
await Promise.all(
|
||||
skillIdsToAdd.map(async (entry) => {
|
||||
const { response, payload } = await requestJson(
|
||||
`/v1/orgs/${encodeURIComponent(orgId)}/skill-hubs/${encodeURIComponent(nextSkillHubId)}/skills`,
|
||||
`/v1/skill-hubs/${encodeURIComponent(nextSkillHubId)}/skills`,
|
||||
{
|
||||
method: "POST",
|
||||
body: JSON.stringify({ skillId: entry }),
|
||||
@@ -272,7 +272,7 @@ export function SkillHubEditorScreen({ skillHubId }: { skillHubId?: string }) {
|
||||
await Promise.all(
|
||||
skillIdsToRemove.map(async (entry) => {
|
||||
const { response, payload } = await requestJson(
|
||||
`/v1/orgs/${encodeURIComponent(orgId)}/skill-hubs/${encodeURIComponent(nextSkillHubId)}/skills/${encodeURIComponent(entry)}`,
|
||||
`/v1/skill-hubs/${encodeURIComponent(nextSkillHubId)}/skills/${encodeURIComponent(entry)}`,
|
||||
{ method: "DELETE" },
|
||||
12000,
|
||||
);
|
||||
|
||||
@@ -100,7 +100,7 @@ export function SharedSetupsScreen() {
|
||||
}
|
||||
|
||||
const { response, payload } = await requestJson(
|
||||
`/v1/orgs/${encodeURIComponent(orgId)}/templates/${encodeURIComponent(templateId)}`,
|
||||
`/v1/templates/${encodeURIComponent(templateId)}`,
|
||||
{ method: "DELETE" },
|
||||
12000,
|
||||
);
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
useMemo,
|
||||
useState,
|
||||
} from "react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { usePathname, useRouter } from "next/navigation";
|
||||
import { useDenFlow } from "../../../../_providers/den-flow-provider";
|
||||
import { getErrorMessage, getOrgLimitError, requestJson } from "../../../../_lib/den-flow";
|
||||
import {
|
||||
@@ -19,7 +19,7 @@ import {
|
||||
} from "../../../../_lib/den-org";
|
||||
|
||||
type OrgDashboardContextValue = {
|
||||
orgSlug: string;
|
||||
orgSlug: string | null;
|
||||
orgId: string | null;
|
||||
orgDirectory: DenOrgSummary[];
|
||||
activeOrg: DenOrgSummary | null;
|
||||
@@ -49,10 +49,11 @@ export function OrgDashboardProvider({
|
||||
orgSlug,
|
||||
children,
|
||||
}: {
|
||||
orgSlug: string;
|
||||
orgSlug?: string | null;
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
const router = useRouter();
|
||||
const pathname = usePathname();
|
||||
const { user, sessionHydrated, signOut, refreshWorkers, workersLoadedOnce } = useDenFlow();
|
||||
const [orgDirectory, setOrgDirectory] = useState<DenOrgSummary[]>([]);
|
||||
const [orgContext, setOrgContext] = useState<DenOrgContext | null>(null);
|
||||
@@ -61,18 +62,20 @@ export function OrgDashboardProvider({
|
||||
const [mutationBusy, setMutationBusy] = useState<string | null>(null);
|
||||
|
||||
const activeOrg = useMemo(
|
||||
() => orgDirectory.find((entry) => entry.slug === orgSlug) ?? orgDirectory.find((entry) => entry.isActive) ?? null,
|
||||
() =>
|
||||
(orgSlug ? orgDirectory.find((entry) => entry.slug === orgSlug) : null) ??
|
||||
orgDirectory.find((entry) => entry.isActive) ??
|
||||
orgDirectory[0] ??
|
||||
null,
|
||||
[orgDirectory, orgSlug],
|
||||
);
|
||||
|
||||
const activeOrgId = activeOrg?.id ?? orgContext?.organization.id ?? null;
|
||||
|
||||
function getRequiredActiveOrgId() {
|
||||
function ensureActiveOrganizationSelected() {
|
||||
if (!activeOrgId) {
|
||||
throw new Error("Organization not found.");
|
||||
}
|
||||
|
||||
return activeOrgId;
|
||||
}
|
||||
|
||||
async function loadOrgDirectory() {
|
||||
@@ -81,11 +84,26 @@ export function OrgDashboardProvider({
|
||||
throw new Error(getErrorMessage(payload, `Failed to load organizations (${response.status}).`));
|
||||
}
|
||||
|
||||
return parseOrgListPayload(payload).orgs;
|
||||
return parseOrgListPayload(payload);
|
||||
}
|
||||
|
||||
async function loadOrgContext(targetOrgId: string) {
|
||||
const { response, payload } = await requestJson(`/v1/orgs/${encodeURIComponent(targetOrgId)}/context`, { method: "GET" }, 12000);
|
||||
async function setActiveOrganization(input: { organizationId?: string | null; organizationSlug?: string | null }) {
|
||||
const { response, payload } = await requestJson(
|
||||
"/api/auth/organization/set-active",
|
||||
{
|
||||
method: "POST",
|
||||
body: JSON.stringify(input),
|
||||
},
|
||||
12000,
|
||||
);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(getErrorMessage(payload, `Failed to switch organization (${response.status}).`));
|
||||
}
|
||||
}
|
||||
|
||||
async function loadOrgContext() {
|
||||
const { response, payload } = await requestJson("/v1/org", { method: "GET" }, 12000);
|
||||
if (!response.ok) {
|
||||
throw new Error(getErrorMessage(payload, `Failed to load organization (${response.status}).`));
|
||||
}
|
||||
@@ -110,16 +128,25 @@ export function OrgDashboardProvider({
|
||||
setOrgError(null);
|
||||
|
||||
try {
|
||||
const directory = await loadOrgDirectory();
|
||||
const targetOrg = directory.find((entry) => entry.slug === orgSlug) ?? null;
|
||||
let directoryPayload = await loadOrgDirectory();
|
||||
const fallbackOrg = directoryPayload.orgs.find((entry) => entry.isActive) ?? directoryPayload.orgs[0] ?? null;
|
||||
const targetOrg = (orgSlug ? directoryPayload.orgs.find((entry) => entry.slug === orgSlug) : null) ?? fallbackOrg;
|
||||
|
||||
if (!targetOrg) {
|
||||
throw new Error("Organization not found.");
|
||||
setOrgDirectory([]);
|
||||
setOrgContext(null);
|
||||
router.replace("/organization");
|
||||
return;
|
||||
}
|
||||
|
||||
const context = await loadOrgContext(targetOrg.id);
|
||||
if (!targetOrg.isActive) {
|
||||
await setActiveOrganization({ organizationId: targetOrg.id });
|
||||
directoryPayload = await loadOrgDirectory();
|
||||
}
|
||||
|
||||
setOrgDirectory(directory.map((entry) => ({ ...entry, isActive: entry.id === context.organization.id })));
|
||||
const context = await loadOrgContext();
|
||||
|
||||
setOrgDirectory(directoryPayload.orgs.map((entry) => ({ ...entry, isActive: entry.id === context.organization.id })));
|
||||
setOrgContext(context);
|
||||
await refreshWorkers({ keepSelection: false, quiet: workersLoadedOnce });
|
||||
} catch (error) {
|
||||
@@ -150,7 +177,7 @@ export function OrgDashboardProvider({
|
||||
setOrgError(null);
|
||||
try {
|
||||
const { response, payload } = await requestJson(
|
||||
"/v1/orgs",
|
||||
"/v1/org",
|
||||
{
|
||||
method: "POST",
|
||||
body: JSON.stringify({ name: trimmed }),
|
||||
@@ -183,7 +210,34 @@ export function OrgDashboardProvider({
|
||||
}
|
||||
|
||||
function switchOrganization(nextSlug: string) {
|
||||
router.push(getOrgDashboardRoute(nextSlug));
|
||||
const targetOrg = orgDirectory.find((entry) => entry.slug === nextSlug) ?? null;
|
||||
if (!targetOrg) {
|
||||
return;
|
||||
}
|
||||
|
||||
void (async () => {
|
||||
setMutationBusy("switch-organization");
|
||||
setOrgError(null);
|
||||
|
||||
try {
|
||||
await setActiveOrganization({ organizationId: targetOrg.id });
|
||||
const context = await loadOrgContext();
|
||||
setOrgDirectory((current) => current.map((entry) => ({ ...entry, isActive: entry.id === context.organization.id })));
|
||||
setOrgContext(context);
|
||||
await refreshWorkers({ keepSelection: false, quiet: workersLoadedOnce });
|
||||
|
||||
if (orgSlug && pathname.startsWith("/o/")) {
|
||||
router.replace(getOrgDashboardRoute(nextSlug));
|
||||
return;
|
||||
}
|
||||
|
||||
router.refresh();
|
||||
} catch (error) {
|
||||
setOrgError(error instanceof Error ? error.message : "Failed to switch organization.");
|
||||
} finally {
|
||||
setMutationBusy(null);
|
||||
}
|
||||
})();
|
||||
}
|
||||
|
||||
async function updateOrganizationName(name: string) {
|
||||
@@ -193,8 +247,9 @@ export function OrgDashboardProvider({
|
||||
}
|
||||
|
||||
await runMutation("update-organization-name", async () => {
|
||||
ensureActiveOrganizationSelected();
|
||||
const { response, payload } = await requestJson(
|
||||
`/v1/orgs/${encodeURIComponent(getRequiredActiveOrgId())}`,
|
||||
"/v1/org",
|
||||
{
|
||||
method: "PATCH",
|
||||
body: JSON.stringify({ name: trimmed }),
|
||||
@@ -210,8 +265,9 @@ export function OrgDashboardProvider({
|
||||
|
||||
async function inviteMember(input: { email: string; role: string }) {
|
||||
await runMutation("invite-member", async () => {
|
||||
ensureActiveOrganizationSelected();
|
||||
const { response, payload } = await requestJson(
|
||||
`/v1/orgs/${encodeURIComponent(getRequiredActiveOrgId())}/invitations`,
|
||||
"/v1/invitations",
|
||||
{
|
||||
method: "POST",
|
||||
body: JSON.stringify(input),
|
||||
@@ -231,8 +287,9 @@ export function OrgDashboardProvider({
|
||||
|
||||
async function cancelInvitation(invitationId: string) {
|
||||
await runMutation("cancel-invitation", async () => {
|
||||
ensureActiveOrganizationSelected();
|
||||
const { response, payload } = await requestJson(
|
||||
`/v1/orgs/${encodeURIComponent(getRequiredActiveOrgId())}/invitations/${encodeURIComponent(invitationId)}/cancel`,
|
||||
`/v1/invitations/${encodeURIComponent(invitationId)}/cancel`,
|
||||
{ method: "POST", body: JSON.stringify({}) },
|
||||
12000,
|
||||
);
|
||||
@@ -245,8 +302,9 @@ export function OrgDashboardProvider({
|
||||
|
||||
async function updateMemberRole(memberId: string, role: string) {
|
||||
await runMutation("update-member-role", async () => {
|
||||
ensureActiveOrganizationSelected();
|
||||
const { response, payload } = await requestJson(
|
||||
`/v1/orgs/${encodeURIComponent(getRequiredActiveOrgId())}/members/${encodeURIComponent(memberId)}/role`,
|
||||
`/v1/members/${encodeURIComponent(memberId)}/role`,
|
||||
{
|
||||
method: "POST",
|
||||
body: JSON.stringify({ role }),
|
||||
@@ -262,8 +320,9 @@ export function OrgDashboardProvider({
|
||||
|
||||
async function removeMember(memberId: string) {
|
||||
await runMutation("remove-member", async () => {
|
||||
ensureActiveOrganizationSelected();
|
||||
const { response, payload } = await requestJson(
|
||||
`/v1/orgs/${encodeURIComponent(getRequiredActiveOrgId())}/members/${encodeURIComponent(memberId)}`,
|
||||
`/v1/members/${encodeURIComponent(memberId)}`,
|
||||
{ method: "DELETE" },
|
||||
12000,
|
||||
);
|
||||
@@ -276,8 +335,9 @@ export function OrgDashboardProvider({
|
||||
|
||||
async function createRole(input: { roleName: string; permission: Record<string, string[]> }) {
|
||||
await runMutation("create-role", async () => {
|
||||
ensureActiveOrganizationSelected();
|
||||
const { response, payload } = await requestJson(
|
||||
`/v1/orgs/${encodeURIComponent(getRequiredActiveOrgId())}/roles`,
|
||||
"/v1/roles",
|
||||
{
|
||||
method: "POST",
|
||||
body: JSON.stringify(input),
|
||||
@@ -293,8 +353,9 @@ export function OrgDashboardProvider({
|
||||
|
||||
async function createTeam(input: { name: string; memberIds: string[] }) {
|
||||
await runMutation("create-team", async () => {
|
||||
ensureActiveOrganizationSelected();
|
||||
const { response, payload } = await requestJson(
|
||||
`/v1/orgs/${encodeURIComponent(getRequiredActiveOrgId())}/teams`,
|
||||
"/v1/teams",
|
||||
{
|
||||
method: "POST",
|
||||
body: JSON.stringify(input),
|
||||
@@ -310,8 +371,9 @@ export function OrgDashboardProvider({
|
||||
|
||||
async function updateTeam(teamId: string, input: { name?: string; memberIds?: string[] }) {
|
||||
await runMutation("update-team", async () => {
|
||||
ensureActiveOrganizationSelected();
|
||||
const { response, payload } = await requestJson(
|
||||
`/v1/orgs/${encodeURIComponent(getRequiredActiveOrgId())}/teams/${encodeURIComponent(teamId)}`,
|
||||
`/v1/teams/${encodeURIComponent(teamId)}`,
|
||||
{
|
||||
method: "PATCH",
|
||||
body: JSON.stringify(input),
|
||||
@@ -327,8 +389,9 @@ export function OrgDashboardProvider({
|
||||
|
||||
async function deleteTeam(teamId: string) {
|
||||
await runMutation("delete-team", async () => {
|
||||
ensureActiveOrganizationSelected();
|
||||
const { response, payload } = await requestJson(
|
||||
`/v1/orgs/${encodeURIComponent(getRequiredActiveOrgId())}/teams/${encodeURIComponent(teamId)}`,
|
||||
`/v1/teams/${encodeURIComponent(teamId)}`,
|
||||
{ method: "DELETE" },
|
||||
12000,
|
||||
);
|
||||
@@ -341,8 +404,9 @@ export function OrgDashboardProvider({
|
||||
|
||||
async function updateRole(roleId: string, input: { roleName?: string; permission?: Record<string, string[]> }) {
|
||||
await runMutation("update-role", async () => {
|
||||
ensureActiveOrganizationSelected();
|
||||
const { response, payload } = await requestJson(
|
||||
`/v1/orgs/${encodeURIComponent(getRequiredActiveOrgId())}/roles/${encodeURIComponent(roleId)}`,
|
||||
`/v1/roles/${encodeURIComponent(roleId)}`,
|
||||
{
|
||||
method: "PATCH",
|
||||
body: JSON.stringify(input),
|
||||
@@ -358,8 +422,9 @@ export function OrgDashboardProvider({
|
||||
|
||||
async function deleteRole(roleId: string) {
|
||||
await runMutation("delete-role", async () => {
|
||||
ensureActiveOrganizationSelected();
|
||||
const { response, payload } = await requestJson(
|
||||
`/v1/orgs/${encodeURIComponent(getRequiredActiveOrgId())}/roles/${encodeURIComponent(roleId)}`,
|
||||
`/v1/roles/${encodeURIComponent(roleId)}`,
|
||||
{ method: "DELETE" },
|
||||
12000,
|
||||
);
|
||||
@@ -385,7 +450,7 @@ export function OrgDashboardProvider({
|
||||
}, [orgSlug, router, sessionHydrated, user?.id]);
|
||||
|
||||
const value: OrgDashboardContextValue = {
|
||||
orgSlug,
|
||||
orgSlug: activeOrg?.slug ?? orgSlug ?? null,
|
||||
orgId: activeOrgId,
|
||||
orgDirectory,
|
||||
activeOrg,
|
||||
|
||||
@@ -1,11 +1,5 @@
|
||||
import { redirect } from "next/navigation";
|
||||
|
||||
export default async function ManageMembersRedirectPage({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ orgSlug: string }>;
|
||||
}) {
|
||||
const { orgSlug } = await params;
|
||||
|
||||
redirect(`/o/${encodeURIComponent(orgSlug)}/dashboard/members`);
|
||||
export default function ManageMembersRedirectPage() {
|
||||
redirect("/dashboard/members");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user