Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
117 commits
Select commit Hold shift + click to select a range
0f9b372
add tenancy crud
fomalhautb Jul 24, 2025
a9ceab2
remove old config
fomalhautb Jul 24, 2025
6390832
fix validateRedirectUrl
fomalhautb Jul 24, 2025
1f02532
fix tenancy config
fomalhautb Jul 24, 2025
588019d
fix
fomalhautb Jul 24, 2025
86466a9
better error handling
fomalhautb Jul 24, 2025
3d38936
fix
fomalhautb Jul 24, 2025
506ff7d
fix
fomalhautb Jul 24, 2025
5d7bcc0
fix
fomalhautb Jul 24, 2025
3626c5c
fix
fomalhautb Jul 24, 2025
76f593a
fix
fomalhautb Jul 24, 2025
0b00d8c
remove environment-config
fomalhautb Jul 24, 2025
62b7775
Update apps/backend/src/app/api/latest/integrations/neon/oauth-provid…
fomalhautb Jul 24, 2025
0bcafcd
Update apps/backend/src/app/api/latest/auth/otp/send-sign-in-code/rou…
fomalhautb Jul 24, 2025
15f73de
Update apps/backend/prisma/seed.ts
fomalhautb Jul 24, 2025
8c18c7f
Merge branch 'dev' into remove-old-config
fomalhautb Jul 24, 2025
073ab15
fix
fomalhautb Jul 24, 2025
bc069b0
fix tests
fomalhautb Jul 24, 2025
9ceb0ac
added config override crud
fomalhautb Jul 25, 2025
1afbf06
rename
fomalhautb Jul 25, 2025
c18cae3
Refactor updateConfigOverrides to handle legacy config structure and …
fomalhautb Jul 25, 2025
0387792
fix bugs
fomalhautb Jul 25, 2025
6458c54
add use config
fomalhautb Jul 28, 2025
408e387
added todos
fomalhautb Jul 28, 2025
41d8a4c
tests
fomalhautb Jul 28, 2025
e2cb2ab
tests
fomalhautb Jul 28, 2025
dabc619
tests
fomalhautb Jul 29, 2025
32afeb2
more tests
fomalhautb Jul 29, 2025
4e25c86
init
fomalhautb Jul 29, 2025
13bb68e
s3 image
fomalhautb Jul 30, 2025
0a3e088
fix size
fomalhautb Jul 30, 2025
1b9d1b0
updated image profile url
fomalhautb Jul 30, 2025
3a6c548
Merge branch 'dev' into s3
fomalhautb Jul 30, 2025
431a3a4
env vars
fomalhautb Jul 30, 2025
f61670a
fix package.json
fomalhautb Jul 30, 2025
cb7d262
fix tests
fomalhautb Jul 30, 2025
7a648b2
fix tests
fomalhautb Jul 30, 2025
1fd77f6
add to emulator
fomalhautb Jul 30, 2025
f5d6656
Merge branch 'dev' into s3
fomalhautb Jul 30, 2025
b2722a4
dynamic import
fomalhautb Jul 30, 2025
e4a6b04
Merge branch 's3' of github.com:stackframe-projects/stack into s3
fomalhautb Jul 30, 2025
73d90ca
Merge branch 'dev' into s3
fomalhautb Jul 30, 2025
c883965
make s3 optional
fomalhautb Jul 30, 2025
e929f74
docs
fomalhautb Jul 30, 2025
48debe3
remove test route
fomalhautb Jul 30, 2025
814b66d
Update apps/backend/.env
fomalhautb Jul 30, 2025
517b4dd
improve error handling
fomalhautb Jul 30, 2025
0d4fd41
Merge branch 's3' of github.com:stackframe-projects/stack into s3
fomalhautb Jul 30, 2025
3a2def7
comments
fomalhautb Jul 30, 2025
c856d00
Update S3 configuration checks and regex for base64 image validation
fomalhautb Jul 30, 2025
64b34b1
fix import error
fomalhautb Jul 30, 2025
3369c3a
fix import
fomalhautb Jul 30, 2025
af5fbd0
fix syntax error
fomalhautb Jul 30, 2025
6d83076
fix types
fomalhautb Jul 30, 2025
4ed5fe9
Merge branch 'config-json-crud' into s3
fomalhautb Jul 30, 2025
6f658fd
add logo
fomalhautb Jul 30, 2025
6172952
fix
fomalhautb Jul 30, 2025
d2413f3
fix types
fomalhautb Jul 31, 2025
7f78755
fix types
fomalhautb Jul 31, 2025
7ed74d0
Merge branch 's3' into project-logo
fomalhautb Jul 31, 2025
405a63f
Merge branch 'dev' into remove-old-config
fomalhautb Jul 31, 2025
1904feb
Merge branch 'dev' into remove-old-config
fomalhautb Jul 31, 2025
079df58
fix types
fomalhautb Jul 31, 2025
9fa42c7
Improve error handling for missing provider type in OAuth configuration
fomalhautb Jul 31, 2025
577eaf3
fix tests
fomalhautb Jul 31, 2025
ae04ae2
fix tests
fomalhautb Jul 31, 2025
40a6c7a
Merge branch 'remove-old-config' into project-config-to-json
fomalhautb Jul 31, 2025
60ced05
Merge branch 'dev' into project-config-to-json
fomalhautb Jul 31, 2025
5a05e01
fix
fomalhautb Jul 31, 2025
9e5a049
Update apps/e2e/tests/backend/endpoints/api/v1/internal/config-overri…
fomalhautb Jul 31, 2025
c76d55f
fix
fomalhautb Jul 31, 2025
40db4e2
Merge branch 'project-config-to-json' of github.com:stackframe-projec…
fomalhautb Jul 31, 2025
7862a3e
Refactor config overrides CRUD handlers by simplifying paramsSchema a…
fomalhautb Jul 31, 2025
ee330cb
Refactor config CRUD handlers to standardize naming conventions for c…
fomalhautb Jul 31, 2025
b3b581c
fix tests
fomalhautb Jul 31, 2025
5d45170
Merge branch 'dev' into project-config-to-json
fomalhautb Jul 31, 2025
1d52910
fix
fomalhautb Jul 31, 2025
677de8a
fix
fomalhautb Jul 31, 2025
44eecf6
Merge branch 'dev' into project-config-to-json
fomalhautb Jul 31, 2025
db50e41
fix
fomalhautb Jul 31, 2025
48bb875
add tests
fomalhautb Jul 31, 2025
eb5a173
Merge branch 'dev' into project-config-to-json
fomalhautb Jul 31, 2025
8e581f6
remove logging
fomalhautb Jul 31, 2025
dee426f
remove word
fomalhautb Jul 31, 2025
3d20abc
rename endpoints
fomalhautb Aug 1, 2025
31d8c37
Merge branch 'project-config-to-json' into s3
fomalhautb Aug 1, 2025
52cfaf8
fix
fomalhautb Aug 1, 2025
a205566
removed unused
fomalhautb Aug 1, 2025
a6c69bb
Merge branch 'dev' into s3
fomalhautb Aug 1, 2025
f103ce8
Merge branch 's3' of github.com:stackframe-projects/stack into s3
fomalhautb Aug 1, 2025
c324aa2
remove unused
fomalhautb Aug 1, 2025
c480271
fix import
fomalhautb Aug 1, 2025
95db583
8120 -> 8121
fomalhautb Aug 1, 2025
f7b1d20
fix test
fomalhautb Aug 1, 2025
6cfc7b7
improve auto-migration test
fomalhautb Aug 1, 2025
d518eb0
refactor auto-migration test to simplify success count validation
fomalhautb Aug 1, 2025
901f0b5
fix
fomalhautb Aug 1, 2025
7914568
Merge branch 's3' into project-logo
fomalhautb Aug 1, 2025
cada633
Merge branch 'dev' into project-logo
fomalhautb Aug 1, 2025
317529b
add logo
fomalhautb Aug 1, 2025
e6d2839
fix
fomalhautb Aug 1, 2025
3e314cd
logo uploader
fomalhautb Aug 1, 2025
1610f90
Merge branch 'dev' into project-logo
fomalhautb Aug 1, 2025
72379f5
fix bug
fomalhautb Aug 1, 2025
c0f963a
Update apps/dashboard/src/components/logo-upload.tsx
fomalhautb Aug 1, 2025
76a4a1e
Update apps/dashboard/src/components/logo-upload.tsx
fomalhautb Aug 1, 2025
51d4060
Update packages/stack-shared/src/schema-fields.ts
fomalhautb Aug 1, 2025
533d5fb
fix tests
fomalhautb Aug 1, 2025
1b0cdbf
fix tests
fomalhautb Aug 2, 2025
986f02f
Merge dev into project-logo
N2D4 Aug 2, 2025
b41c1ec
Merge dev into project-logo
N2D4 Aug 3, 2025
6de0da8
Merge dev into project-logo
N2D4 Aug 5, 2025
835e151
Merge dev into project-logo
N2D4 Aug 7, 2025
8a0c8a7
Merge dev into project-logo
N2D4 Aug 8, 2025
9d7656b
Merge dev into project-logo
N2D4 Aug 9, 2025
f7f6c38
Update apps/dashboard/src/app/(main)/(protected)/projects/[projectId]…
fomalhautb Aug 19, 2025
d950e13
Merge branch 'dev' into project-logo
fomalhautb Aug 19, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
-- AlterTable
ALTER TABLE "Project" ADD COLUMN "logoUrl" TEXT;

-- AlterTable
ALTER TABLE "Project" ADD COLUMN "fullLogoUrl" TEXT;
2 changes: 2 additions & 0 deletions apps/backend/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ model Project {
displayName String
description String @default("")
isProductionMode Boolean
logoUrl String?
fullLogoUrl String?

projectConfigOverride Json?

Expand Down
2 changes: 1 addition & 1 deletion apps/backend/src/lib/images.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export async function parseBase64Image(input: string, options: {
maxHeight?: number,
allowTypes?: string[],
} = {
maxBytes: 1024 * 300,
maxBytes: 1_000_000, // 1MB
maxWidth: 4096,
maxHeight: 4096,
allowTypes: ['image/jpeg', 'image/png', 'image/webp'],
Expand Down
17 changes: 17 additions & 0 deletions apps/backend/src/lib/projects.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { uploadAndGetUrl } from "@/s3";
import { Prisma } from "@prisma/client";
import { KnownErrors } from "@stackframe/stack-shared";
import { CompleteConfig, EnvironmentConfigOverrideOverride, ProjectConfigOverrideOverride } from "@stackframe/stack-shared/dist/config/schema";
Expand Down Expand Up @@ -48,6 +49,8 @@ export function getProjectQuery(projectId: string): RawQuery<Promise<Omit<Projec
id: row.id,
display_name: row.displayName,
description: row.description,
logo_url: row.logoUrl,
full_logo_url: row.fullLogoUrl,
created_at_millis: new Date(row.createdAt + "Z").getTime(),
is_production_mode: row.isProductionMode,
};
Expand Down Expand Up @@ -76,6 +79,16 @@ export async function createOrUpdateProjectWithLegacyConfig(
data: ProjectsCrud["Admin"]["Update"],
})
) {
let logoUrl: string | null | undefined;
if (options.data.logo_url !== undefined) {
logoUrl = await uploadAndGetUrl(options.data.logo_url, "project-logos");
}

let fullLogoUrl: string | null | undefined;
if (options.data.full_logo_url !== undefined) {
fullLogoUrl = await uploadAndGetUrl(options.data.full_logo_url, "project-logos");
}

const [projectId, branchId] = await retryTransaction(globalPrismaClient, async (tx) => {
let project: Prisma.ProjectGetPayload<{}>;
let branchId: string;
Expand All @@ -87,6 +100,8 @@ export async function createOrUpdateProjectWithLegacyConfig(
displayName: options.data.display_name,
description: options.data.description ?? "",
isProductionMode: options.data.is_production_mode ?? false,
logoUrl,
fullLogoUrl,
},
});

Expand Down Expand Up @@ -117,6 +132,8 @@ export async function createOrUpdateProjectWithLegacyConfig(
displayName: options.data.display_name,
description: options.data.description === null ? "" : options.data.description,
isProductionMode: options.data.is_production_mode,
logoUrl,
fullLogoUrl,
},
});
branchId = options.branchId;
Expand Down
5 changes: 2 additions & 3 deletions apps/backend/src/s3.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export function getS3PublicUrl(key: string): string {

async function uploadBase64Image({
input,
maxBytes = 1024 * 300,
maxBytes = 1_000_000, // 1MB
folderName,
}: {
input: string,
Expand Down Expand Up @@ -85,7 +85,7 @@ export function checkImageString(input: string) {

export async function uploadAndGetUrl(
input: string | null | undefined,
folderName: 'user-profile-images' | 'team-profile-images' | 'team-member-profile-images'
folderName: 'user-profile-images' | 'team-profile-images' | 'team-member-profile-images' | 'project-logos'
) {
if (input) {
const checkResult = checkImageString(input);
Expand All @@ -97,7 +97,6 @@ export async function uploadAndGetUrl(
} else {
throw new StatusError(StatusError.BadRequest, "Invalid profile image URL");
}

} else if (input === null) {
return null;
} else {
Expand Down
1 change: 1 addition & 0 deletions apps/dashboard/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
"@tanstack/react-table": "^8.20.5",
"@vercel/analytics": "^1.2.2",
"@vercel/speed-insights": "^1.0.12",
"browser-image-compression": "^2.0.2",
"canvas-confetti": "^1.9.2",
"clsx": "^2.0.0",
"dotenv-cli": "^7.3.0",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"use client";
import { InputField } from "@/components/form-fields";
import { StyledLink } from "@/components/link";
import { LogoUpload } from "@/components/logo-upload";
import { FormSettingCard, SettingCard, SettingSwitch, SettingText } from "@/components/settings";
import { getPublicEnvVar } from '@/lib/env';
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger, ActionDialog, Alert, Button, Typography } from "@stackframe/stack-ui";
Expand Down Expand Up @@ -63,6 +64,32 @@ export default function PageClient() {
)}
/>

<SettingCard title="Project Logo">
<LogoUpload
label="Logo"
value={project.logoUrl}
onValueChange={async (logoUrl) => {
await project.update({ logoUrl });
}}
description="Upload a logo for your project. Recommended size: 200x200px"
type="logo"
/>

<LogoUpload
label="Full Logo"
value={project.fullLogoUrl}
onValueChange={async (fullLogoUrl) => {
await project.update({ fullLogoUrl });
}}
description="Upload a full logo with text. Recommended size: At least 100px tall, landscape format"
type="full-logo"
/>

<Typography variant="secondary" type="footnote">
Logo images will be displayed in your application (e.g. login page) and emails. The logo should be a square image, while the full logo can include text and be wider.
</Typography>
</SettingCard>

<SettingCard
title="API Key Settings"
description="Configure which types of API keys are allowed in your project."
Expand Down
135 changes: 135 additions & 0 deletions apps/dashboard/src/components/logo-upload.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
"use client";

import { fileToBase64 } from '@stackframe/stack-shared/dist/utils/base64';
import { runAsynchronouslyWithAlert } from '@stackframe/stack-shared/dist/utils/promises';
import { Button, cn, Typography } from '@stackframe/stack-ui';
import imageCompression from 'browser-image-compression';
import { Upload, X } from 'lucide-react';
import { useState } from 'react';

export async function checkImageUrl(url: string) {
try {
const res = await fetch(url, { method: 'HEAD' });
const buff = await res.blob();
return buff.type.startsWith('image/');
} catch (e) {
return false;
}
}

export function LogoUpload(props: {
label: string,
value?: string | null,
onValueChange: (value: string | null) => void | Promise<void>,
description?: string,
acceptedTypes?: string[],
type: 'logo' | 'full-logo',
}) {
const [uploading, setUploading] = useState(false);
const [error, setError] = useState<string | null>(null);

function upload() {
const input = document.createElement('input');
input.type = 'file';
input.accept = props.acceptedTypes?.join(',') || 'image/*';

input.onchange = (e) => {
const file = (e.target as HTMLInputElement).files?.[0];
if (!file) return;

setUploading(true);
setError(null);

runAsynchronouslyWithAlert(async () => {
try {
// Compress the image first
const compressedFile = await imageCompression(file, {
maxSizeMB: 1,
maxWidthOrHeight: 800,
useWebWorker: true,
fileType: file.type.startsWith('image/svg') ? file.type : 'image/jpeg',
});

const base64Url = await fileToBase64(compressedFile);

if (await checkImageUrl(base64Url)) {
await props.onValueChange(base64Url);
setError(null);
} else {
setError('Invalid image format');
}
} catch (err) {
setError('Failed to process image');
console.error('Logo upload error:', err);
} finally {
setUploading(false);
input.remove();
}
});
};

input.click();
}

async function remove() {
setError(null);
await props.onValueChange(null);
}

const logoContainerClasses = props.type === 'full-logo'
? "relative h-16 w-48 rounded border overflow-hidden bg-muted"
: "relative h-16 w-16 rounded border overflow-hidden bg-muted";

const placeholderContainerClasses = props.type === 'full-logo'
? "h-16 w-48 rounded border-2 border-dashed border-muted-foreground/25 flex items-center justify-center bg-muted/50"
: "h-16 w-16 rounded border-2 border-dashed border-muted-foreground/25 flex items-center justify-center bg-muted/50";

return (
<div className="flex flex-col gap-2">
<Typography variant="secondary" type="label">
{props.label}
</Typography>

<div className="flex items-center gap-3">
{props.value ? (
<div className="flex items-center gap-3">
<div className={logoContainerClasses}>
{/* eslint-disable-next-line @next/next/no-img-element */}
<img
src={props.value}
alt={props.label}
className="h-full w-full object-contain"
/>
</div>
<Button
variant="outline"
size="sm"
onClick={remove}
disabled={uploading}
>
<X className="h-4 w-4 mr-2" />
Remove
</Button>
</div>
) : (
<div className="flex flex-col items-center gap-4">
<div className={cn(placeholderContainerClasses, "cursor-pointer", uploading && "opacity-50 pointer-events-none")} onClick={uploading ? undefined : upload}>
<Upload className="h-6 w-6 text-muted-foreground" />
</div>
{props.description && (
<Typography variant="secondary" type="footnote" className="mt-2">
{props.description}
</Typography>
)}
</div>
)}
</div>

{error && (
<Typography variant="destructive" type="footnote">
{error}
</Typography>
)}
</div>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,10 @@ it("should be able to provision a new project if client details are correct", as
"created_at_millis": <stripped field 'created_at_millis'>,
"description": "Project created by an external integration",
"display_name": "Test project",
"full_logo_url": null,
"id": "<stripped UUID>",
"is_production_mode": false,
"logo_url": null,
},
"headers": Headers { <some fields may have been hidden> },
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,8 +126,10 @@ it("lists oauth providers", async ({ expect }) => {
"created_at_millis": <stripped field 'created_at_millis'>,
"description": "",
"display_name": "New Project",
"full_logo_url": null,
"id": "<stripped UUID>",
"is_production_mode": false,
"logo_url": null,
},
"headers": Headers { <some fields may have been hidden> },
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,10 @@ it("get project details", async ({ expect }) => {
"created_at_millis": <stripped field 'created_at_millis'>,
"description": "",
"display_name": "New Project",
"full_logo_url": null,
"id": "<stripped UUID>",
"is_production_mode": false,
"logo_url": null,
},
"headers": Headers { <some fields may have been hidden> },
}
Expand Down Expand Up @@ -91,8 +93,10 @@ it("creates and updates the basic project information of a project", async ({ ex
"created_at_millis": <stripped field 'created_at_millis'>,
"description": "Updated description",
"display_name": "Updated Project",
"full_logo_url": null,
"id": "<stripped UUID>",
"is_production_mode": true,
"logo_url": null,
},
"headers": Headers { <some fields may have been hidden> },
}
Expand Down Expand Up @@ -159,8 +163,10 @@ it("creates and updates the email config of a project", async ({ expect }) => {
"created_at_millis": <stripped field 'created_at_millis'>,
"description": "",
"display_name": "New Project",
"full_logo_url": null,
"id": "<stripped UUID>",
"is_production_mode": false,
"logo_url": null,
},
"headers": Headers { <some fields may have been hidden> },
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,10 @@ it("should be able to provision a new project if neon client details are correct
"created_at_millis": <stripped field 'created_at_millis'>,
"description": "Created with Neon",
"display_name": "Test project",
"full_logo_url": null,
"id": "<stripped UUID>",
"is_production_mode": false,
"logo_url": null,
},
"headers": Headers { <some fields may have been hidden> },
}
Expand Down
Loading