Skip to content

Commit 0abaeea

Browse files
committed
Apps backend changes
1 parent d09496d commit 0abaeea

5 files changed

Lines changed: 216 additions & 2 deletions

File tree

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
-- Migration to enable and pin apps based on usage conditions
2+
-- This migration updates both EnvironmentConfigOverride and Project tables
3+
4+
-- Update EnvironmentConfigOverride to enable apps
5+
-- Authentication: Always enabled
6+
UPDATE "EnvironmentConfigOverride"
7+
SET "config" = jsonb_set(
8+
COALESCE("config", '{}'::jsonb),
9+
'{apps.installed.authentication.enabled}',
10+
'true'::jsonb,
11+
true
12+
);
13+
14+
-- Emails: Always enabled
15+
UPDATE "EnvironmentConfigOverride"
16+
SET "config" = jsonb_set(
17+
COALESCE("config", '{}'::jsonb),
18+
'{apps.installed.emails.enabled}',
19+
'true'::jsonb,
20+
true
21+
);
22+
23+
-- Teams: Always enabled
24+
UPDATE "EnvironmentConfigOverride"
25+
SET "config" = jsonb_set(
26+
COALESCE("config", '{}'::jsonb),
27+
'{apps.installed.teams.enabled}',
28+
'true'::jsonb,
29+
true
30+
);
31+
32+
-- Webhooks: Always enabled
33+
UPDATE "EnvironmentConfigOverride"
34+
SET "config" = jsonb_set(
35+
COALESCE("config", '{}'::jsonb),
36+
'{apps.installed.webhooks.enabled}',
37+
'true'::jsonb,
38+
true
39+
);
40+
41+
-- Launch Checklist: Always enabled
42+
UPDATE "EnvironmentConfigOverride"
43+
SET "config" = jsonb_set(
44+
COALESCE("config", '{}'::jsonb),
45+
'{apps.installed.launch-checklist.enabled}',
46+
'true'::jsonb,
47+
true
48+
);
49+
50+
-- RBAC: Enable if at least one custom permission exists in the config
51+
UPDATE "EnvironmentConfigOverride" eco
52+
SET "config" = jsonb_set(
53+
COALESCE(eco."config", '{}'::jsonb),
54+
'{apps.installed.rbac.enabled}',
55+
'true'::jsonb,
56+
true
57+
);
58+
59+
-- API Keys: Enable if at least one API key exists for the project
60+
UPDATE "EnvironmentConfigOverride" eco
61+
SET "config" = jsonb_set(
62+
COALESCE(eco."config", '{}'::jsonb),
63+
'{apps.installed.api-keys.enabled}',
64+
'true'::jsonb,
65+
true
66+
)
67+
FROM "Tenancy" t
68+
WHERE eco."projectId" = t."projectId"
69+
AND eco."branchId" = t."branchId"
70+
AND EXISTS (
71+
SELECT 1 FROM "ProjectApiKey" pak
72+
WHERE pak."tenancyId" = t."id"
73+
);
74+
75+
-- Payments: Enable if Stripe account ID is available on the project
76+
UPDATE "EnvironmentConfigOverride" eco
77+
SET "config" = jsonb_set(
78+
COALESCE(eco."config", '{}'::jsonb),
79+
'{apps.installed.payments.enabled}',
80+
'true'::jsonb,
81+
true
82+
)
83+
FROM "Project" p
84+
WHERE eco."projectId" = p."id"
85+
AND p."stripeAccountId" IS NOT NULL;

apps/backend/src/lib/projects.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { getEnvVariable } from "@stackframe/stack-shared/dist/utils/env";
88
import { StackAssertionError } from "@stackframe/stack-shared/dist/utils/errors";
99
import { filterUndefined, typedFromEntries } from "@stackframe/stack-shared/dist/utils/objects";
1010
import { generateUuid } from "@stackframe/stack-shared/dist/utils/uuids";
11-
import { getPrismaClientForTenancy, RawQuery, globalPrismaClient, rawQuery, retryTransaction } from "../prisma-client";
11+
import { RawQuery, getPrismaClientForTenancy, globalPrismaClient, rawQuery, retryTransaction } from "../prisma-client";
1212
import { overrideEnvironmentConfigOverride, overrideProjectConfigOverride } from "./config";
1313
import { DEFAULT_BRANCH_ID, getSoleTenancyFromProjectBranch } from "./tenancies";
1414

@@ -223,6 +223,11 @@ export async function createOrUpdateProjectWithLegacyConfig(
223223
'rbac.defaultPermissions.teamMember': translateDefaultPermissions(dataOptions.team_member_default_permissions),
224224
'rbac.defaultPermissions.teamCreator': translateDefaultPermissions(dataOptions.team_creator_default_permissions),
225225
'rbac.defaultPermissions.signUp': translateDefaultPermissions(dataOptions.user_default_permissions),
226+
// ======================= apps =======================
227+
'apps.installed': {
228+
authentication: { enabled: true },
229+
emails: { enabled: true },
230+
},
226231
});
227232

228233
if (options.type === "create") {
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
export const ALL_APPS = {
2+
"authentication": {
3+
type: "app" as const,
4+
displayName: "Authentication",
5+
subtitle: "User sign-in and account management",
6+
tags: ["auth", "security"],
7+
},
8+
"teams": {
9+
type: "app" as const,
10+
displayName: "Teams",
11+
subtitle: "Team collaboration and management",
12+
tags: ["collaboration", "organization"],
13+
},
14+
"rbac": {
15+
type: "app" as const,
16+
displayName: "RBAC",
17+
subtitle: "Role-based access control and permissions",
18+
tags: ["security", "permissions"],
19+
},
20+
"api-keys": {
21+
type: "app" as const,
22+
displayName: "API Keys",
23+
subtitle: "API key generation and management",
24+
tags: ["api", "security"],
25+
},
26+
"payments": {
27+
type: "app" as const,
28+
displayName: "Payments",
29+
subtitle: "Payment processing and subscription management",
30+
tags: ["billing", "monetization"],
31+
},
32+
"emails": {
33+
type: "app" as const,
34+
displayName: "Emails",
35+
subtitle: "Email template configuration and management",
36+
tags: ["communication", "templates"],
37+
},
38+
"email-api": {
39+
type: "app" as const,
40+
displayName: "Email API",
41+
subtitle: "Programmatic email sending and delivery",
42+
tags: ["api", "communication"],
43+
},
44+
"data-vault": {
45+
type: "app" as const,
46+
displayName: "Data Vault",
47+
subtitle: "Secure storage for sensitive user data",
48+
tags: ["security", "storage"],
49+
},
50+
"workflows": {
51+
type: "app" as const,
52+
displayName: "Workflows",
53+
subtitle: "Automated business process orchestration",
54+
tags: ["automation", "processes"],
55+
},
56+
"webhooks": {
57+
type: "app" as const,
58+
displayName: "Webhooks",
59+
subtitle: "Real-time event notifications and integrations",
60+
tags: ["integration", "events"],
61+
},
62+
"tv-mode": {
63+
type: "app" as const,
64+
displayName: "TV mode",
65+
subtitle: "Dashboard display for large screens",
66+
tags: ["display", "monitoring"],
67+
},
68+
"launch-checklist": {
69+
type: "app" as const,
70+
displayName: "Launch Checklist",
71+
subtitle: "Pre-launch verification and readiness checks",
72+
tags: ["deployment", "verification"],
73+
},
74+
"catalyst": {
75+
type: "app" as const,
76+
displayName: "Catalyst",
77+
subtitle: "Project scaffolding and rapid development",
78+
tags: ["development", "tooling"],
79+
},
80+
"neon": {
81+
type: "integration" as const,
82+
displayName: "Neon",
83+
subtitle: "Serverless Postgres database integration",
84+
tags: ["database", "integration"],
85+
},
86+
"convex": {
87+
type: "integration" as const,
88+
displayName: "Convex",
89+
subtitle: "Real-time backend platform integration",
90+
tags: ["database", "integration", "realtime"],
91+
},
92+
} as const;

packages/stack-shared/src/config/schema.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// TODO: rename this file to spaghetti.ts because that's the kind of code here
22

33
import * as yup from "yup";
4+
import { ALL_APPS } from "../apps/apps-config";
45
import { DEFAULT_EMAIL_TEMPLATES, DEFAULT_EMAIL_THEMES, DEFAULT_EMAIL_THEME_ID } from "../helpers/emails";
56
import * as schemaFields from "../schema-fields";
67
import { offerSchema, userSpecifiedIdSchema, yupBoolean, yupDate, yupMixed, yupNever, yupNumber, yupObject, yupRecord, yupString, yupTuple, yupUnion } from "../schema-fields";
@@ -88,6 +89,32 @@ const branchApiKeysSchema = yupObject({
8889
});
8990
// --- END NEW API Keys Schema ---
9091

92+
// --- NEW Apps Schema ---
93+
const appIds = Object.keys(ALL_APPS) as (keyof typeof ALL_APPS)[];
94+
const branchAppsSchema = yupObject({
95+
installed: yupRecord(
96+
yupString().oneOf(appIds),
97+
yupObject({
98+
enabled: yupBoolean(),
99+
}),
100+
).test(
101+
'authentication-and-emails-enabled',
102+
'authentication and emails must be installed and enabled',
103+
function(value) {
104+
const hasAuthentication = value['authentication'].enabled === true;
105+
const hasEmails = value['emails'].enabled === true;
106+
if (!hasAuthentication || !hasEmails) {
107+
return this.createError({
108+
message: 'authentication and emails must be installed and enabled',
109+
path: this.path,
110+
});
111+
}
112+
return true;
113+
}
114+
),
115+
});
116+
// --- END NEW Apps Schema ---
117+
91118

92119
const branchAuthSchema = yupObject({
93120
allowSignUp: yupBoolean(),
@@ -165,6 +192,8 @@ export const branchConfigSchema = canNoLongerBeOverridden(projectConfigSchema, [
165192

166193
apiKeys: branchApiKeysSchema,
167194

195+
apps: branchAppsSchema,
196+
168197
domains: branchDomain,
169198

170199
auth: branchAuthSchema,
@@ -404,6 +433,10 @@ const organizationConfigDefaults = {
404433
},
405434
},
406435

436+
apps: {
437+
installed: typedFromEntries(appIds.map(appId => [appId, { enabled: false }])),
438+
},
439+
407440
teams: {
408441
createPersonalTeamOnSignUp: false,
409442
allowClientTeamCreation: false,

packages/template/src/lib/stack-app/projects/index.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ export type AdminProject = {
2727

2828
update(this: AdminProject, update: AdminProjectUpdateOptions): Promise<void>,
2929
delete(this: AdminProject): Promise<void>,
30-
transfer(this: AdminProject, user: CurrentUser, newTeamId: string): Promise<void>,
3130

3231
getConfig(this: AdminProject): Promise<CompleteConfig>,
3332
// NEXT_LINE_PLATFORM react-like

0 commit comments

Comments
 (0)