diff --git a/apps/backend/prisma/migrations/20250730164439_profile_image_key/migration.sql b/apps/backend/prisma/migrations/20250730164439_profile_image_key/migration.sql deleted file mode 100644 index c5d377254..000000000 --- a/apps/backend/prisma/migrations/20250730164439_profile_image_key/migration.sql +++ /dev/null @@ -1,8 +0,0 @@ --- AlterTable -ALTER TABLE "ProjectUser" ADD COLUMN "profileImageKey" TEXT; - --- AlterTable -ALTER TABLE "Team" ADD COLUMN "profileImageKey" TEXT; - --- AlterTable -ALTER TABLE "TeamMember" ADD COLUMN "profileImageKey" TEXT; diff --git a/apps/backend/prisma/schema.prisma b/apps/backend/prisma/schema.prisma index 5d8391fce..724127c95 100644 --- a/apps/backend/prisma/schema.prisma +++ b/apps/backend/prisma/schema.prisma @@ -78,12 +78,7 @@ model Team { clientMetadata Json? clientReadOnlyMetadata Json? serverMetadata Json? - - // if profileImageKey is set, we fetch the image from S3 - // otherwise if the profileImageUrl is set, we send the user to the URL. - // if neither is set, we return null. - profileImageKey String? - profileImageUrl String? + profileImageUrl String? teamMembers TeamMember[] projectApiKey ProjectApiKey[] @@ -105,13 +100,7 @@ model TeamMember { teamId String @db.Uuid // This will override the displayName of the user in this team. - displayName String? - - // This will override the profileImageUrl of the user in this team. - // if profileImageKey is set, we fetch the image from S3 - // otherwise if the profileImageUrl is set, we send the user to the URL. - // if neither is set, we return null. - profileImageKey String? + displayName String? profileImageUrl String? createdAt DateTime @default(now()) @@ -167,16 +156,11 @@ model ProjectUser { createdAt DateTime @default(now()) updatedAt DateTime @updatedAt - // if profileImageKey is set, we fetch the image from S3 - // otherwise if the profileImageUrl is set, we send the user to the URL. - // if neither is set, we return null. - profileImageKey String? - profileImageUrl String? - displayName String? serverMetadata Json? clientReadOnlyMetadata Json? clientMetadata Json? + profileImageUrl String? requiresTotpMfa Boolean @default(false) totpSecret Bytes? isAnonymous Boolean @default(false) diff --git a/apps/backend/src/app/api/latest/team-member-profiles/crud.tsx b/apps/backend/src/app/api/latest/team-member-profiles/crud.tsx index 96d6eeed4..76b313ca2 100644 --- a/apps/backend/src/app/api/latest/team-member-profiles/crud.tsx +++ b/apps/backend/src/app/api/latest/team-member-profiles/crud.tsx @@ -1,7 +1,6 @@ import { ensureTeamExists, ensureTeamMembershipExists, ensureUserExists, ensureUserTeamPermissionExists } from "@/lib/request-checks"; import { getPrismaClientForTenancy, retryTransaction } from "@/prisma-client"; import { createCrudHandlers } from "@/route-handlers/crud-handler"; -import { uploadAndGetImageUpdateInfo } from "@/s3"; import { Prisma } from "@prisma/client"; import { KnownErrors } from "@stackframe/stack-shared"; import { teamMemberProfilesCrud } from "@stackframe/stack-shared/dist/interface/crud/team-member-profiles"; @@ -150,7 +149,7 @@ export const teamMemberProfilesCrudHandlers = createLazyProxy(() => createCrudHa }, data: { displayName: data.display_name, - ...await uploadAndGetImageUpdateInfo(data.profile_image_url, "team-member-profile-images") + profileImageUrl: await uploadAndGetUrl(data.profile_image_url, "team-member-profile-images") }, include: fullInclude, }); diff --git a/apps/backend/src/app/api/latest/teams/crud.tsx b/apps/backend/src/app/api/latest/teams/crud.tsx index 5f3d7a4a1..4fd6a374d 100644 --- a/apps/backend/src/app/api/latest/teams/crud.tsx +++ b/apps/backend/src/app/api/latest/teams/crud.tsx @@ -2,7 +2,6 @@ import { ensureTeamExists, ensureTeamMembershipExists, ensureUserExists, ensureU import { sendTeamCreatedWebhook, sendTeamDeletedWebhook, sendTeamUpdatedWebhook } from "@/lib/webhooks"; import { getPrismaClientForTenancy, retryTransaction } from "@/prisma-client"; import { createCrudHandlers } from "@/route-handlers/crud-handler"; -import { getS3PublicUrl, uploadAndGetImageUpdateInfo } from "@/s3"; import { runAsynchronouslyAndWaitUntil } from "@/utils/vercel"; import { Prisma } from "@prisma/client"; import { KnownErrors } from "@stackframe/stack-shared"; @@ -18,7 +17,7 @@ export function teamPrismaToCrud(prisma: Prisma.TeamGetPayload<{}>) { return { id: prisma.teamId, display_name: prisma.displayName, - profile_image_url: prisma.profileImageKey ? getS3PublicUrl(prisma.profileImageKey) : prisma.profileImageUrl, + profile_image_url: prisma.profileImageUrl, created_at_millis: prisma.createdAt.getTime(), client_metadata: prisma.clientMetadata, client_read_only_metadata: prisma.clientReadOnlyMetadata, @@ -81,7 +80,7 @@ export const teamsCrudHandlers = createLazyProxy(() => createCrudHandlers(teamsC clientMetadata: data.client_metadata === null ? Prisma.JsonNull : data.client_metadata, clientReadOnlyMetadata: data.client_read_only_metadata === null ? Prisma.JsonNull : data.client_read_only_metadata, serverMetadata: data.server_metadata === null ? Prisma.JsonNull : data.server_metadata, - ...await uploadAndGetImageUpdateInfo(data.profile_image_url, "team-profile-images") + profileImageUrl: await uploadAndGetUrl(data.profile_image_url, "team-profile-images") }, }); @@ -165,7 +164,7 @@ export const teamsCrudHandlers = createLazyProxy(() => createCrudHandlers(teamsC clientMetadata: data.client_metadata === null ? Prisma.JsonNull : data.client_metadata, clientReadOnlyMetadata: data.client_read_only_metadata === null ? Prisma.JsonNull : data.client_read_only_metadata, serverMetadata: data.server_metadata === null ? Prisma.JsonNull : data.server_metadata, - ...await uploadAndGetImageUpdateInfo(data.profile_image_url, "team-profile-images") + profileImageUrl: await uploadAndGetUrl(data.profile_image_url, "team-profile-images") }, }); }); diff --git a/apps/backend/src/app/api/latest/users/crud.tsx b/apps/backend/src/app/api/latest/users/crud.tsx index a1aad6c65..8fef49022 100644 --- a/apps/backend/src/app/api/latest/users/crud.tsx +++ b/apps/backend/src/app/api/latest/users/crud.tsx @@ -7,7 +7,6 @@ import { PrismaTransaction } from "@/lib/types"; import { sendTeamMembershipDeletedWebhook, sendUserCreatedWebhook, sendUserDeletedWebhook, sendUserUpdatedWebhook } from "@/lib/webhooks"; import { RawQuery, getPrismaClientForSourceOfTruth, getPrismaClientForTenancy, getPrismaSchemaForSourceOfTruth, getPrismaSchemaForTenancy, globalPrismaClient, rawQuery, retryTransaction, sqlQuoteIdent } from "@/prisma-client"; import { createCrudHandlers } from "@/route-handlers/crud-handler"; -import { getS3PublicUrl, uploadAndGetImageUpdateInfo } from "@/s3"; import { log } from "@/utils/telemetry"; import { runAsynchronouslyAndWaitUntil } from "@/utils/vercel"; import { BooleanTrue, Prisma } from "@prisma/client"; @@ -66,7 +65,7 @@ export const userPrismaToCrud = ( primary_email: primaryEmailContactChannel?.value || null, primary_email_verified: !!primaryEmailContactChannel?.isVerified, primary_email_auth_enabled: !!primaryEmailContactChannel?.usedForAuth, - profile_image_url: prisma.profileImageKey ? getS3PublicUrl(prisma.profileImageKey) : prisma.profileImageUrl, + profile_image_url: prisma.profileImageUrl, signed_up_at_millis: prisma.createdAt.getTime(), client_metadata: prisma.clientMetadata, client_read_only_metadata: prisma.clientReadOnlyMetadata, @@ -305,7 +304,7 @@ export function getUserQuery(projectId: string, branchId: string, userId: string primary_email: primaryEmailContactChannel?.value || null, primary_email_verified: primaryEmailContactChannel?.isVerified || false, primary_email_auth_enabled: primaryEmailContactChannel?.usedForAuth === 'TRUE' ? true : false, - profile_image_url: row.profileImageKey ? getS3PublicUrl(row.profileImageKey) : row.profileImageUrl, + profile_image_url: row.profileImageUrl, signed_up_at_millis: new Date(row.createdAt + "Z").getTime(), client_metadata: row.clientMetadata, client_read_only_metadata: row.clientReadOnlyMetadata, @@ -324,7 +323,7 @@ export function getUserQuery(projectId: string, branchId: string, userId: string selected_team: row.SelectedTeamMember ? { id: row.SelectedTeamMember.Team.teamId, display_name: row.SelectedTeamMember.Team.displayName, - profile_image_url: row.SelectedTeamMember.Team.profileImageKey ? getS3PublicUrl(row.SelectedTeamMember.Team.profileImageKey) : row.SelectedTeamMember.Team.profileImageUrl, + profile_image_url: row.SelectedTeamMember.Team.profileImageUrl, created_at_millis: new Date(row.SelectedTeamMember.Team.createdAt + "Z").getTime(), client_metadata: row.SelectedTeamMember.Team.clientMetadata, client_read_only_metadata: row.SelectedTeamMember.Team.clientReadOnlyMetadata, @@ -489,7 +488,7 @@ export const usersCrudHandlers = createLazyProxy(() => createCrudHandlers(usersC serverMetadata: data.server_metadata === null ? Prisma.JsonNull : data.server_metadata, totpSecret: data.totp_secret_base64 == null ? data.totp_secret_base64 : Buffer.from(decodeBase64(data.totp_secret_base64)), isAnonymous: data.is_anonymous ?? false, - ...await uploadAndGetImageUpdateInfo(data.profile_image_url, "user-profile-images") + profileImageUrl: await uploadAndGetUrl(data.profile_image_url, "user-profile-images") }, include: userFullInclude, }); @@ -945,7 +944,7 @@ export const usersCrudHandlers = createLazyProxy(() => createCrudHandlers(usersC requiresTotpMfa: data.totp_secret_base64 === undefined ? undefined : (data.totp_secret_base64 !== null), totpSecret: data.totp_secret_base64 == null ? data.totp_secret_base64 : Buffer.from(decodeBase64(data.totp_secret_base64)), isAnonymous: data.is_anonymous ?? undefined, - ...await uploadAndGetImageUpdateInfo(data.profile_image_url, "user-profile-images") + profileImageUrl: await uploadAndGetUrl(data.profile_image_url, "user-profile-images") }, include: userFullInclude, }); diff --git a/apps/backend/src/s3.tsx b/apps/backend/src/s3.tsx index bd2613b48..adbc5d10c 100644 --- a/apps/backend/src/s3.tsx +++ b/apps/backend/src/s3.tsx @@ -78,36 +78,24 @@ export function checkImageString(input: string) { }; } -export async function uploadAndGetImageUpdateInfo( +export async function uploadAndGetUrl( input: string | null | undefined, folderName: 'user-profile-images' | 'team-profile-images' | 'team-member-profile-images' ) { - let profileImageKey: string | null | undefined = undefined; - let profileImageUrl: string | null | undefined = undefined; if (input) { const checkResult = checkImageString(input); if (checkResult.isBase64Image) { - const { key } = await uploadBase64Image({ input, folderName }); - profileImageKey = key; + const { url } = await uploadBase64Image({ input, folderName }); + return url; } else if (checkResult.isUrl) { - profileImageUrl = input; + return input; } else { throw new StatusError(StatusError.BadRequest, "Invalid profile image URL"); } - return { - profileImageKey, - profileImageUrl, - }; } else if (input === null) { - return { - profileImageKey: null, - profileImageUrl: null, - }; + return null; } else { - return { - profileImageKey: undefined, - profileImageUrl: undefined, - }; + return undefined; } }