返回 presentation-ai
theme-actions.ts
根目录 / src / app / _actions / presentation / theme-actions.ts
1 "use server";
2
3 import * as z from "zod";
4
5 import { presentationThemeStyleDataSchema } from "@/lib/presentation/theme-schema";
6 import { auth } from "@/server/auth";
7 import { db } from "@/server/db";
8
9 // Schema for creating/updating a theme
10 const themeSchema = z.object({
11 name: z.string().min(1).max(50),
12 description: z.string().optional(),
13 themeData: presentationThemeStyleDataSchema,
14 logoUrl: z.string().optional(),
15 isPublic: z.boolean().optional().default(false),
16 });
17
18 export type ThemeFormData = z.infer<typeof themeSchema>;
19
20 // Create a new custom theme
21 export async function createCustomTheme(formData: ThemeFormData) {
22 try {
23 const session = await auth();
24 if (!session?.user) {
25 return {
26 success: false,
27 message: "You must be signed in to create a theme",
28 };
29 }
30
31 const validatedData = themeSchema.parse(formData);
32
33 const newTheme = await db.presentationTheme.create({
34 data: {
35 name: validatedData.name,
36 description: validatedData.description,
37 themeData: validatedData.themeData,
38 logoUrl: validatedData.logoUrl,
39 isPublic: false,
40 userId: session.user.id,
41 },
42 });
43
44 return {
45 success: true,
46 themeId: newTheme.id,
47 message: "Theme created successfully",
48 };
49 } catch (error) {
50 console.error("Failed to create custom theme:", error);
51
52 // Log the actual error but return a generic message
53 if (error instanceof z.ZodError) {
54 return {
55 success: false,
56 message: "Invalid theme data. Please check your inputs and try again.",
57 };
58 } else if (error instanceof Error && error.message.includes("Prisma")) {
59 return {
60 success: false,
61 message: "Database error. Please try again later.",
62 };
63 } else {
64 return {
65 success: false,
66 message: "Something went wrong. Please try again later.",
67 };
68 }
69 }
70 }
71
72 // Update an existing custom theme
73 export async function updateCustomTheme(
74 themeId: string,
75 formData: ThemeFormData,
76 ) {
77 try {
78 const session = await auth();
79 if (!session?.user) {
80 return {
81 success: false,
82 message: "You must be signed in to update a theme",
83 };
84 }
85
86 const validatedData = themeSchema.parse(formData);
87
88 // Verify ownership
89 const existingTheme = await db.presentationTheme.findUnique({
90 where: { id: themeId },
91 });
92
93 if (!existingTheme) {
94 return { success: false, message: "Theme not found" };
95 }
96
97 if (existingTheme.userId !== session.user.id) {
98 return { success: false, message: "Not authorized to update this theme" };
99 }
100
101 await db.presentationTheme.update({
102 where: { id: themeId },
103 data: {
104 name: validatedData.name,
105 description: validatedData.description,
106 themeData: validatedData.themeData,
107 logoUrl: validatedData.logoUrl,
108 isPublic: false,
109 updatedAt: new Date(),
110 },
111 });
112
113 return {
114 success: true,
115 message: "Theme updated successfully",
116 };
117 } catch (error) {
118 console.error("Failed to update custom theme:", error);
119
120 // Log the actual error but return a generic message
121 if (error instanceof z.ZodError) {
122 return {
123 success: false,
124 message: "Invalid theme data. Please check your inputs and try again.",
125 };
126 } else if (error instanceof Error && error.message.includes("Prisma")) {
127 return {
128 success: false,
129 message: "Database error. Please try again later.",
130 };
131 } else {
132 return {
133 success: false,
134 message: "Something went wrong. Please try again later.",
135 };
136 }
137 }
138 }
139
140 // Update a system theme in place. Only application admins can change seeded themes.
141 export async function updateAdminPresentationTheme(
142 themeId: string,
143 formData: ThemeFormData,
144 ) {
145 try {
146 const session = await auth();
147 if (!session?.user?.isAdmin) {
148 return {
149 success: false,
150 message: "Not authorized to update system themes",
151 };
152 }
153
154 const validatedData = themeSchema.parse(formData);
155
156 const existingTheme = await db.presentationTheme.findUnique({
157 where: { id: themeId },
158 select: { isAdmin: true },
159 });
160
161 if (!existingTheme) {
162 return { success: false, message: "Theme not found" };
163 }
164
165 if (!existingTheme.isAdmin) {
166 return {
167 success: false,
168 message: "This action can only update system themes",
169 };
170 }
171
172 await db.presentationTheme.update({
173 where: { id: themeId },
174 data: {
175 name: validatedData.name,
176 description: validatedData.description,
177 themeData: validatedData.themeData,
178 logoUrl: validatedData.logoUrl,
179 isPublic: false,
180 updatedAt: new Date(),
181 },
182 });
183
184 return {
185 success: true,
186 message: "System theme updated successfully",
187 };
188 } catch (error) {
189 console.error("Failed to update system theme:", error);
190
191 if (error instanceof z.ZodError) {
192 return {
193 success: false,
194 message: "Invalid theme data. Please check your inputs and try again.",
195 };
196 } else if (error instanceof Error && error.message.includes("Prisma")) {
197 return {
198 success: false,
199 message: "Database error. Please try again later.",
200 };
201 } else {
202 return {
203 success: false,
204 message: "Something went wrong. Please try again later.",
205 };
206 }
207 }
208 }
209
210 // Get system themes that are stored in the database
211 export async function getSystemPresentationThemes() {
212 try {
213 const themes = await db.presentationTheme.findMany({
214 where: {
215 isAdmin: true,
216 },
217 orderBy: {
218 createdAt: "asc",
219 },
220 });
221
222 return {
223 success: true,
224 themes,
225 };
226 } catch (error) {
227 console.error("Failed to fetch system themes:", error);
228 return {
229 success: false,
230 message:
231 "Unable to load system themes at this time. Please try again later.",
232 themes: [],
233 };
234 }
235 }
236
237 // Get all custom themes for the current user
238 export async function getUserCustomThemes() {
239 try {
240 const session = await auth();
241 if (!session?.user) {
242 return {
243 success: false,
244 message: "You must be signed in to view your themes",
245 themes: [],
246 };
247 }
248
249 const themes = await db.presentationTheme.findMany({
250 where: {
251 userId: session.user.id,
252 },
253 orderBy: {
254 createdAt: "desc",
255 },
256 });
257
258 return {
259 success: true,
260 themes,
261 };
262 } catch (error) {
263 console.error("Failed to fetch custom themes:", error);
264 return {
265 success: false,
266 message: "Unable to load themes at this time. Please try again later.",
267 themes: [],
268 };
269 }
270 }
271
272 // Get all public themes, including like counts and user engagement flags
273 export async function getPublicCustomThemes() {
274 try {
275 const session = await auth();
276 const userId = session?.user?.id ?? null;
277
278 const themes = await db.presentationTheme.findMany({
279 where: {
280 isPublic: true,
281 isAdmin: false,
282 },
283 orderBy: {
284 createdAt: "desc",
285 },
286 include: {
287 user: {
288 select: {
289 name: true,
290 },
291 },
292 _count: {
293 select: {
294 presentationThemeLikes: true,
295 },
296 },
297 presentationThemeLikes: userId
298 ? {
299 where: {
300 userId,
301 },
302 }
303 : undefined,
304 favoritePresentationThemes: userId
305 ? {
306 where: {
307 userId,
308 },
309 }
310 : undefined,
311 },
312 });
313
314 const shapedThemes = themes.map((theme) => ({
315 ...theme,
316 name: theme.name,
317 likeCount: theme._count.presentationThemeLikes,
318 isLiked: !!theme.presentationThemeLikes?.length,
319 isFavorite: !!theme.favoritePresentationThemes?.length,
320 }));
321
322 return {
323 success: true,
324 themes: shapedThemes,
325 };
326 } catch (error) {
327 console.error("Failed to fetch public themes:", error);
328 return {
329 success: false,
330 message:
331 "Unable to load public themes at this time. Please try again later.",
332 themes: [],
333 };
334 }
335 }
336
337 // Get a single theme by ID
338 export async function getCustomThemeById(themeId: string) {
339 try {
340 const theme = await db.presentationTheme.findUnique({
341 where: { id: themeId },
342 include: {
343 user: {
344 select: {
345 name: true,
346 },
347 },
348 },
349 });
350
351 if (!theme) {
352 return { success: false, message: "Theme not found" };
353 }
354
355 return {
356 success: true,
357 theme,
358 };
359 } catch (error) {
360 console.error("Failed to fetch theme:", error);
361 return {
362 success: false,
363 message: "Unable to load the theme at this time. Please try again later.",
364 };
365 }
366 }
367
367 lines TYPESCRIPT