diff --git a/apps/backend/src/app/api/latest/internal/config-overrides/crud.tsx b/apps/backend/src/app/api/latest/internal/configs/overrides/crud.tsx similarity index 58% rename from apps/backend/src/app/api/latest/internal/config-overrides/crud.tsx rename to apps/backend/src/app/api/latest/internal/configs/overrides/crud.tsx index 110a83744..1cf9919c6 100644 --- a/apps/backend/src/app/api/latest/internal/config-overrides/crud.tsx +++ b/apps/backend/src/app/api/latest/internal/configs/overrides/crud.tsx @@ -1,33 +1,20 @@ import { getRenderedEnvironmentConfigQuery, overrideEnvironmentConfigOverride } from "@/lib/config"; import { globalPrismaClient, rawQuery } from "@/prisma-client"; import { createCrudHandlers } from "@/route-handlers/crud-handler"; -import { configOverridesCrud } from "@stackframe/stack-shared/dist/interface/crud/config-overrides"; +import { configOverrideCrud } from "@stackframe/stack-shared/dist/interface/crud/config"; import { yupObject, yupString } from "@stackframe/stack-shared/dist/schema-fields"; import { StatusError } from "@stackframe/stack-shared/dist/utils/errors"; import { createLazyProxy } from "@stackframe/stack-shared/dist/utils/proxies"; -export const configOverridesCrudHandlers = createLazyProxy(() => createCrudHandlers(configOverridesCrud, { +export const configOverridesCrudHandlers = createLazyProxy(() => createCrudHandlers(configOverrideCrud, { paramsSchema: yupObject({ emailId: yupString().optional(), }), - onRead: async ({ auth }) => { - return { - id: auth.project.id, - branch_id: auth.tenancy.branchId, - organization_id: auth.tenancy.organization?.id, - project_id: auth.project.id, - config: JSON.stringify(auth.tenancy.config), - }; - }, onUpdate: async ({ auth, data }) => { - if (auth.tenancy.organization) { - throw new StatusError(StatusError.BadRequest, 'Organizational config overrides are not yet supported'); - } - - if (data.config) { + if (data.configOverrideString) { let parsedConfig; try { - parsedConfig = JSON.parse(data.config); + parsedConfig = JSON.parse(data.configOverrideString); } catch (e) { if (e instanceof SyntaxError) { throw new StatusError(StatusError.BadRequest, 'Invalid config JSON'); @@ -47,12 +34,7 @@ export const configOverridesCrudHandlers = createLazyProxy(() => createCrudHandl })); return { - id: auth.project.id, - branch_id: auth.tenancy.branchId, - // @ts-expect-error: remove this once we support organizational config overrides - organization_id: auth.tenancy.organization?.id, - project_id: auth.project.id, - config: JSON.stringify(updatedConfig), + configOverrideString: JSON.stringify(updatedConfig), }; }, })); diff --git a/apps/backend/src/app/api/latest/internal/config-overrides/route.tsx b/apps/backend/src/app/api/latest/internal/configs/route.tsx similarity index 66% rename from apps/backend/src/app/api/latest/internal/config-overrides/route.tsx rename to apps/backend/src/app/api/latest/internal/configs/route.tsx index dad8752ac..5969d812e 100644 --- a/apps/backend/src/app/api/latest/internal/config-overrides/route.tsx +++ b/apps/backend/src/app/api/latest/internal/configs/route.tsx @@ -1,4 +1,4 @@ -import { configOverridesCrudHandlers } from "./crud"; +import { configOverridesCrudHandlers } from "./overrides/crud"; export const GET = configOverridesCrudHandlers.readHandler; export const PATCH = configOverridesCrudHandlers.updateHandler; diff --git a/packages/stack-shared/src/interface/admin-interface.ts b/packages/stack-shared/src/interface/admin-interface.ts index 87a4b03a0..96d563136 100644 --- a/packages/stack-shared/src/interface/admin-interface.ts +++ b/packages/stack-shared/src/interface/admin-interface.ts @@ -1,6 +1,6 @@ import { InternalSession } from "../sessions"; import { filterUndefined, typedFromEntries } from "../utils/objects"; -import { ConfigOverridesCrud } from "./crud/config-overrides"; +import { ConfigCrud, ConfigOverrideCrud } from "./crud/config"; import { InternalEmailsCrud } from "./crud/emails"; import { InternalApiKeysCrud } from "./crud/internal-api-keys"; import { ProjectPermissionDefinitionsCrud } from "./crud/project-permissions"; @@ -445,17 +445,17 @@ export class StackAdminInterface extends StackServerInterface { return await response.json(); } - async getConfigOverrides(): Promise { + async getConfig(): Promise { const response = await this.sendAdminRequest( - `/internal/config-overrides`, + `/internal/config`, { method: "GET" }, null, ); return await response.json(); } - async updateConfigOverrides(data: { config: any }): Promise { - const legacyConfig = data.config; + async updateConfig(data: { configOverride: any }): Promise { + const legacyConfig = data.configOverride; const configOverrideOverride = filterUndefined({ // ======================= auth ======================= diff --git a/packages/stack-shared/src/interface/crud/config-overrides.ts b/packages/stack-shared/src/interface/crud/config-overrides.ts deleted file mode 100644 index 208f1f799..000000000 --- a/packages/stack-shared/src/interface/crud/config-overrides.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { CrudTypeOf, createCrud } from "../../crud"; -import * as schemaFields from "../../schema-fields"; -import { yupObject } from "../../schema-fields"; - -export const configOverridesCrudAdminReadSchema = yupObject({ - project_id: schemaFields.yupString().defined(), - branch_id: schemaFields.yupString().defined(), - organization_id: schemaFields.yupString().optional(), - id: schemaFields.yupString().defined(), - config: schemaFields.yupString().defined(), -}).defined(); - -export const configOverridesCrudAdminUpdateSchema = yupObject({ - config: schemaFields.yupString().optional(), -}).defined(); - -export const configOverridesCrud = createCrud({ - adminReadSchema: configOverridesCrudAdminReadSchema, - adminUpdateSchema: configOverridesCrudAdminUpdateSchema, - docs: { - adminRead: { - summary: 'Get the current config overrides', - description: 'Get the current config overrides with the specified project id and branch id', - tags: ['Config'], - }, - adminUpdate: { - summary: 'Update the current config overrides', - description: 'Update the current config overrides with the specified project id and branch id', - tags: ['Config'], - }, - }, -}); -export type ConfigOverridesCrud = CrudTypeOf; diff --git a/packages/stack-shared/src/interface/crud/config.ts b/packages/stack-shared/src/interface/crud/config.ts new file mode 100644 index 000000000..f9cbe3657 --- /dev/null +++ b/packages/stack-shared/src/interface/crud/config.ts @@ -0,0 +1,38 @@ +import { CrudTypeOf, createCrud } from "../../crud"; +import * as schemaFields from "../../schema-fields"; +import { yupObject } from "../../schema-fields"; + +export const configOverrideCrudAdminReadSchema = yupObject({}).defined(); + +export const configOverrideCrudAdminUpdateSchema = yupObject({ + configOverrideString: schemaFields.yupString().optional(), +}).defined(); + +export const configOverrideCrud = createCrud({ + adminReadSchema: configOverrideCrudAdminReadSchema, + adminUpdateSchema: configOverrideCrudAdminUpdateSchema, + docs: { + adminUpdate: { + summary: 'Update the config', + description: 'Update the config for a project and branch with an override', + tags: ['Config'], + }, + }, +}); +export type ConfigOverrideCrud = CrudTypeOf; + +export const configCrudAdminReadSchema = yupObject({ + configString: schemaFields.yupString().defined(), +}).defined(); + +export const configCrud = createCrud({ + adminReadSchema: configCrudAdminReadSchema, + docs: { + adminRead: { + summary: 'Get the config', + description: 'Get the config for a project and branch', + tags: ['Config'], + }, + }, +}); +export type ConfigCrud = CrudTypeOf; diff --git a/packages/template/src/lib/stack-app/apps/implementations/admin-app-impl.ts b/packages/template/src/lib/stack-app/apps/implementations/admin-app-impl.ts index e06dc5699..e8c4263fa 100644 --- a/packages/template/src/lib/stack-app/apps/implementations/admin-app-impl.ts +++ b/packages/template/src/lib/stack-app/apps/implementations/admin-app-impl.ts @@ -5,7 +5,7 @@ import { EmailTemplateCrud } from "@stackframe/stack-shared/dist/interface/crud/ import { InternalApiKeysCrud } from "@stackframe/stack-shared/dist/interface/crud/internal-api-keys"; import { ProjectsCrud } from "@stackframe/stack-shared/dist/interface/crud/projects"; import { StackAssertionError, throwErr } from "@stackframe/stack-shared/dist/utils/errors"; -import { filterUndefined, pick } from "@stackframe/stack-shared/dist/utils/objects"; +import { pick } from "@stackframe/stack-shared/dist/utils/objects"; import { Result } from "@stackframe/stack-shared/dist/utils/results"; import { useMemo } from "react"; // THIS_LINE_PLATFORM react-like import { AdminSentEmail } from "../.."; @@ -18,9 +18,9 @@ import { StackAdminApp, StackAdminAppConstructorOptions } from "../interfaces/ad import { clientVersion, createCache, getBaseUrl, getDefaultProjectId, getDefaultPublishableClientKey, getDefaultSecretServerKey, getDefaultSuperSecretAdminKey } from "./common"; import { _StackServerAppImplIncomplete } from "./server-app-impl"; -import { OrganizationRenderedConfig } from "@stackframe/stack-shared/dist/config/schema"; +import { EnvironmentConfigOverrideOverride, OrganizationRenderedConfig } from "@stackframe/stack-shared/dist/config/schema"; import { ChatContent } from "@stackframe/stack-shared/dist/interface/admin-interface"; -import { ConfigOverridesCrud } from "@stackframe/stack-shared/dist/interface/crud/config-overrides"; +import { ConfigCrud } from "@stackframe/stack-shared/dist/interface/crud/config"; import { useAsyncCache } from "./common"; // THIS_LINE_PLATFORM react-like export class _StackAdminAppImplIncomplete extends _StackServerAppImplIncomplete implements StackAdminApp @@ -59,7 +59,7 @@ export class _StackAdminAppImplIncomplete { - return await this._interface.getConfigOverrides(); + return await this._interface.getConfig(); }); constructor(options: StackAdminAppConstructorOptions) { @@ -87,8 +87,8 @@ export class _StackAdminAppImplIncomplete Promise): AdminOwnedProject { @@ -157,21 +157,20 @@ export class _StackAdminAppImplIncomplete app._adminConfigFromCrud(crud), [crud]); + const config = useAsyncCache(app._configOverridesCache, [], "useConfig()"); + return useMemo(() => app._adminConfigFromCrud(config), [config]); }, // END_PLATFORM + async updateConfig(configOverride: EnvironmentConfigOverrideOverride) { + await app._interface.updateConfig({ configOverride }); + }, async update(update: AdminProjectUpdateOptions) { const updateOptions = adminProjectUpdateOptionsToCrud(update); - await app._interface.updateProject(filterUndefined({ - ...updateOptions, - config: undefined, - })); - await app._interface.updateConfigOverrides({ config: updateOptions.config }); + await app._interface.updateProject(updateOptions); await onRefresh(); }, async delete() { diff --git a/packages/template/src/lib/stack-app/projects/index.ts b/packages/template/src/lib/stack-app/projects/index.ts index 922eea012..3042b5e05 100644 --- a/packages/template/src/lib/stack-app/projects/index.ts +++ b/packages/template/src/lib/stack-app/projects/index.ts @@ -1,7 +1,7 @@ import { ProductionModeError } from "@stackframe/stack-shared/dist/helpers/production-mode"; import { AdminUserProjectsCrud, ProjectsCrud } from "@stackframe/stack-shared/dist/interface/crud/projects"; -import { OrganizationRenderedConfig } from "@stackframe/stack-shared/dist/config/schema"; +import { EnvironmentConfigOverrideOverride, OrganizationRenderedConfig } from "@stackframe/stack-shared/dist/config/schema"; import { StackAdminApp } from "../apps/interfaces/admin-app"; import { AdminProjectConfig, AdminProjectConfigUpdateOptions, ProjectConfig } from "../project-configs"; @@ -26,6 +26,7 @@ export type AdminProject = { getConfig(this: AdminProject): Promise, // NEXT_LINE_PLATFORM react-like useConfig(this: AdminProject): OrganizationRenderedConfig, + updateConfig(this: AdminProject, config: EnvironmentConfigOverrideOverride): Promise, getProductionModeErrors(this: AdminProject): Promise, // NEXT_LINE_PLATFORM react-like