This commit is contained in:
Zai Shi 2025-08-01 09:42:44 -07:00
parent 31d8c37a4e
commit 52cfaf8c83
6 changed files with 18 additions and 57 deletions

View File

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

View File

@ -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)

View File

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

View File

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

View File

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

View File

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