This commit is contained in:
Zai Shi 2025-07-31 15:01:42 -07:00
parent 5a05e017d7
commit c76d55fafc
7 changed files with 64 additions and 77 deletions

View File

@ -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),
};
},
}));

View File

@ -1,4 +1,4 @@
import { configOverridesCrudHandlers } from "./crud";
import { configOverridesCrudHandlers } from "./overrides/crud";
export const GET = configOverridesCrudHandlers.readHandler;
export const PATCH = configOverridesCrudHandlers.updateHandler;

View File

@ -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<ConfigOverridesCrud["Admin"]["Read"]> {
async getConfig(): Promise<ConfigCrud["Admin"]["Read"]> {
const response = await this.sendAdminRequest(
`/internal/config-overrides`,
`/internal/config`,
{ method: "GET" },
null,
);
return await response.json();
}
async updateConfigOverrides(data: { config: any }): Promise<ConfigOverridesCrud["Admin"]["Read"]> {
const legacyConfig = data.config;
async updateConfig(data: { configOverride: any }): Promise<ConfigOverrideCrud["Admin"]["Read"]> {
const legacyConfig = data.configOverride;
const configOverrideOverride = filterUndefined({
// ======================= auth =======================

View File

@ -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<typeof configOverridesCrud>;

View File

@ -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<typeof configOverrideCrud>;
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<typeof configCrud>;

View File

@ -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<HasTokenStore extends boolean, ProjectId extends string> extends _StackServerAppImplIncomplete<HasTokenStore, ProjectId> implements StackAdminApp<HasTokenStore, ProjectId>
@ -59,7 +59,7 @@ export class _StackAdminAppImplIncomplete<HasTokenStore extends boolean, Project
return await this._interface.renderEmailPreview({ themeId, themeTsxSource, templateId, templateTsxSource });
});
private readonly _configOverridesCache = createCache(async () => {
return await this._interface.getConfigOverrides();
return await this._interface.getConfig();
});
constructor(options: StackAdminAppConstructorOptions<HasTokenStore, ProjectId>) {
@ -87,8 +87,8 @@ export class _StackAdminAppImplIncomplete<HasTokenStore extends boolean, Project
});
}
_adminConfigFromCrud(data: ConfigOverridesCrud['Admin']['Read']): OrganizationRenderedConfig {
return JSON.parse(data.config);
_adminConfigFromCrud(data: ConfigCrud['Admin']['Read']): OrganizationRenderedConfig {
return JSON.parse(data.configString);
}
_adminOwnedProjectFromCrud(data: ProjectsCrud['Admin']['Read'], onRefresh: () => Promise<void>): AdminOwnedProject {
@ -157,21 +157,20 @@ export class _StackAdminAppImplIncomplete<HasTokenStore extends boolean, Project
userDefaultPermissions: data.config.user_default_permissions,
},
async getConfig() {
return app._adminConfigFromCrud(await app._interface.getConfigOverrides());
return app._adminConfigFromCrud(await app._interface.getConfig());
},
// IF_PLATFORM react-like
useConfig() {
const crud = useAsyncCache(app._configOverridesCache, [], "useConfig()");
return useMemo(() => 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() {

View File

@ -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<OrganizationRenderedConfig>,
// NEXT_LINE_PLATFORM react-like
useConfig(this: AdminProject): OrganizationRenderedConfig,
updateConfig(this: AdminProject, config: EnvironmentConfigOverrideOverride): Promise<void>,
getProductionModeErrors(this: AdminProject): Promise<ProductionModeError[]>,
// NEXT_LINE_PLATFORM react-like