Spaces:
Runtime error
Runtime error
| "use server"; | |
| import { | |
| validatedActionWithAdminPermission, | |
| validatedActionWithUserManagePermission, | |
| } from "lib/action-utils"; | |
| import { headers } from "next/headers"; | |
| import { auth } from "auth/server"; | |
| import { | |
| UpdateUserDetailsSchema, | |
| DeleteUserSchema, | |
| UpdateUserPasswordSchema, | |
| UpdateUserActionState, | |
| DeleteUserActionState, | |
| UpdateUserPasswordActionState, | |
| } from "./validations"; | |
| import { getUser, getUserAccounts, updateUserDetails } from "lib/user/server"; | |
| import { getTranslations } from "next-intl/server"; | |
| import { logger } from "better-auth"; | |
| import { | |
| generateImageWithOpenAI, | |
| generateImageWithXAI, | |
| GeneratedImageResult, | |
| generateImageWithNanoBanana, | |
| } from "lib/ai/image/generate-image"; | |
| export const updateUserImageAction = validatedActionWithUserManagePermission( | |
| UpdateUserDetailsSchema.pick({ userId: true, image: true }), | |
| async ( | |
| data, | |
| userId, | |
| userSession, | |
| isOwnResource, | |
| ): Promise<UpdateUserActionState> => { | |
| const t = await getTranslations("User.Profile.common"); | |
| try { | |
| const { image } = data; | |
| if (isOwnResource) { | |
| await auth.api.updateUser({ | |
| returnHeaders: true, | |
| body: { image }, | |
| headers: await headers(), | |
| }); | |
| } else { | |
| await updateUserDetails( | |
| userId, | |
| userSession.user.name, | |
| userSession.user.email || "", | |
| image, | |
| ); | |
| } | |
| const user = await getUser(userId); | |
| if (!user) { | |
| return { | |
| success: false, | |
| message: t("userNotFound"), | |
| }; | |
| } | |
| return { | |
| success: true, | |
| message: "Profile photo updated successfully", | |
| user, | |
| currentUserUpdated: isOwnResource, | |
| }; | |
| } catch (error) { | |
| logger.error("Failed to update user image:", error); | |
| return { | |
| success: false, | |
| message: "Failed to update profile photo", | |
| }; | |
| } | |
| }, | |
| ); | |
| export const updateUserDetailsAction = validatedActionWithUserManagePermission( | |
| UpdateUserDetailsSchema, | |
| async ( | |
| data, | |
| userId, | |
| userSession, | |
| isOwnResource, | |
| _formData, | |
| ): Promise<UpdateUserActionState> => { | |
| const t = await getTranslations("User.Profile.common"); | |
| try { | |
| const { name, email, image } = data; | |
| const user = await getUser(userId); | |
| if (!user) { | |
| return { | |
| success: false, | |
| message: t("userNotFound"), | |
| }; | |
| } | |
| const isDifferentEmail = email && email !== userSession.user.email; | |
| const isDifferentName = name && name !== userSession.user.name; | |
| const isDifferentImage = image && image !== userSession.user.image; | |
| // this forces a session update for the current user, getting the latest data | |
| if (isOwnResource) { | |
| if (isDifferentName || isDifferentImage) { | |
| await auth.api.updateUser({ | |
| returnHeaders: true, | |
| body: { name, ...(image && { image }) }, | |
| headers: await headers(), | |
| }); | |
| } | |
| if (isDifferentEmail) { | |
| await auth.api.changeEmail({ | |
| returnHeaders: true, | |
| body: { newEmail: email }, | |
| headers: await headers(), | |
| }); | |
| } | |
| } else { | |
| await updateUserDetails(userId, name, email); | |
| } | |
| if (isDifferentEmail) user.email = email; | |
| if (isDifferentName) user.name = name; | |
| if (isDifferentImage) user.image = image; | |
| return { | |
| success: true, | |
| message: t("userDetailsUpdatedSuccessfully"), | |
| user, | |
| currentUserUpdated: isOwnResource, | |
| }; | |
| } catch (error) { | |
| logger.error("Failed to update user details:", error); | |
| return { | |
| success: false, | |
| message: t("failedToUpdateUserDetails"), | |
| }; | |
| } | |
| }, | |
| ); | |
| export const deleteUserAction = validatedActionWithAdminPermission( | |
| DeleteUserSchema, | |
| async (data, _formData, _userSession): Promise<DeleteUserActionState> => { | |
| const t = await getTranslations("Admin.UserDelete"); | |
| const { userId } = data; | |
| try { | |
| await auth.api.removeUser({ | |
| body: { userId }, | |
| headers: await headers(), | |
| }); | |
| } catch (error) { | |
| console.error("Failed to delete user:", error); | |
| return { | |
| success: false, | |
| message: t("failedToDeleteUser"), | |
| }; | |
| } | |
| return { | |
| success: true, | |
| message: t("userDeletedSuccessfully"), | |
| redirect: "/admin", | |
| }; | |
| }, | |
| ); | |
| export const updateUserPasswordAction = validatedActionWithUserManagePermission( | |
| UpdateUserPasswordSchema, | |
| async ( | |
| data, | |
| userId, | |
| _userSession, | |
| isOwnResource, | |
| _formData, | |
| ): Promise<UpdateUserPasswordActionState> => { | |
| const t = await getTranslations("User.Profile.common"); | |
| const { newPassword, currentPassword } = data; | |
| const { hasPassword } = await getUserAccounts(userId); | |
| if (!hasPassword) { | |
| return { | |
| success: false, | |
| message: t("userHasNoPasswordAccount"), | |
| }; | |
| } | |
| try { | |
| if (isOwnResource) { | |
| if (!currentPassword) { | |
| return { | |
| success: false, | |
| message: t("failedToUpdatePassword"), | |
| }; | |
| } | |
| await auth.api.changePassword({ | |
| body: { currentPassword, newPassword, revokeOtherSessions: true }, | |
| headers: await headers(), | |
| }); | |
| } else { | |
| await auth.api.setUserPassword({ | |
| body: { userId, newPassword }, | |
| headers: await headers(), | |
| }); | |
| await auth.api.revokeUserSessions({ | |
| body: { userId }, | |
| headers: await headers(), | |
| }); | |
| } | |
| return { | |
| success: true, | |
| message: t("passwordUpdatedSuccessfully"), | |
| }; | |
| } catch (_error) { | |
| console.error("Failed to update user password:", _error); | |
| return { | |
| success: false, | |
| message: t("failedToUpdatePassword"), | |
| }; | |
| } | |
| }, | |
| ); | |
| type ImageProvider = "openai" | "xai" | "google"; | |
| interface GenerateAvatarResult { | |
| success: boolean; | |
| base64?: string; | |
| mimeType?: string; | |
| error?: string; | |
| } | |
| /** | |
| * Server Action to generate avatar image using AI | |
| */ | |
| export async function generateAvatarImageAction( | |
| provider: ImageProvider, | |
| prompt: string, | |
| ): Promise<GenerateAvatarResult> { | |
| try { | |
| if (!prompt.trim()) { | |
| return { | |
| success: false, | |
| error: "Prompt is required", | |
| }; | |
| } | |
| // Wrap user prompt with avatar-specific instructions | |
| const enhancedPrompt = `You are tasked with creating a professional profile picture for a user. | |
| Requirements: | |
| - Portrait style with centered face | |
| - Clear, high-quality image suitable for profile/avatar use | |
| - Friendly and approachable expression | |
| - Professional yet personable appearance | |
| - Clean background that doesn't distract from the subject | |
| - Well-lit with good contrast | |
| User's request: | |
| "${prompt}" | |
| Generate a profile picture that fulfills the user's request while maintaining the professional portrait quality requirements above.`; | |
| let response: GeneratedImageResult; | |
| switch (provider) { | |
| case "openai": | |
| response = await generateImageWithOpenAI({ | |
| prompt: enhancedPrompt, | |
| }); | |
| break; | |
| case "xai": | |
| response = await generateImageWithXAI({ | |
| prompt: enhancedPrompt, | |
| }); | |
| break; | |
| case "google": | |
| response = await generateImageWithNanoBanana({ | |
| prompt: enhancedPrompt, | |
| }); | |
| break; | |
| default: | |
| return { | |
| success: false, | |
| error: "Invalid provider", | |
| }; | |
| } | |
| if (!response || response.images.length === 0) { | |
| return { | |
| success: false, | |
| error: "No image generated", | |
| }; | |
| } | |
| const image = response.images[0]; | |
| if (!image.base64) { | |
| return { | |
| success: false, | |
| error: "No image data received", | |
| }; | |
| } | |
| return { | |
| success: true, | |
| base64: image.base64, | |
| mimeType: image.mimeType || "image/png", | |
| }; | |
| } catch (error) { | |
| logger.error("Failed to generate avatar image:", error); | |
| return { | |
| success: false, | |
| error: | |
| error instanceof Error ? error.message : "Failed to generate image", | |
| }; | |
| } | |
| } | |