Skip to content

Commit b384b54

Browse files
1. ajoute plus de notif : documents, subtask, tout ce qui et sur le proj
1 parent 73a1273 commit b384b54

File tree

4 files changed

+185
-31
lines changed

4 files changed

+185
-31
lines changed

src/app/(app)/projects/[id]/actions.ts

Lines changed: 142 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ export async function updateProjectAction(
7979
formData: FormData
8080
): Promise<{ error?: string; errors?: Record<string, string[] | undefined>; message?: string; project?: Project }> {
8181
const session = await auth();
82-
if (!session?.user?.uuid) {
82+
if (!session?.user?.uuid || !session.user.name) {
8383
console.error("[updateProjectAction] Authentication required. No session user UUID.");
8484
return { error: "Authentication required." };
8585
}
@@ -114,6 +114,25 @@ export async function updateProjectAction(
114114
if (!updatedProject) {
115115
return { error: 'Failed to update project or project not found.' };
116116
}
117+
118+
if (project.discordWebhookUrl && project.discordNotificationsEnabled && project.discordNotifySettings) {
119+
const changedFields = [];
120+
if (project.name !== updatedProject.name) changedFields.push(`Name: from "**${project.name}**" to "**${updatedProject.name}**"`);
121+
if (project.description !== updatedProject.description) changedFields.push("Description updated");
122+
if (changedFields.length > 0) {
123+
await sendDiscordNotification(project.discordWebhookUrl, {
124+
embeds: [{
125+
title: "🛠️ Project Details Updated",
126+
description: `**${session.user.name}** updated the project details:\n- ${changedFields.join('\n- ')}`,
127+
color: 15105570, // Orange
128+
timestamp: new Date().toISOString(),
129+
footer: { text: `Project: ${updatedProject.name}` }
130+
}]
131+
});
132+
}
133+
}
134+
135+
117136
return { message: 'Project updated successfully!', project: updatedProject };
118137
} catch (error: any) {
119138
console.error('Failed to update project (server action):', error);
@@ -276,7 +295,7 @@ export async function fetchProjectMembersAction(projectUuid: string | undefined)
276295

277296
export async function removeUserFromProjectAction(projectUuid: string, userUuidToRemove: string): Promise<{success?: boolean, error?: string, message?: string}> {
278297
const session = await auth();
279-
if (!session?.user?.uuid) {
298+
if (!session?.user?.uuid || !session.user.name) {
280299
console.error("[removeUserFromProjectAction] Authentication required. No session user UUID.");
281300
return { error: "Authentication required." };
282301
}
@@ -299,8 +318,19 @@ export async function removeUserFromProjectAction(projectUuid: string, userUuidT
299318
return { error: "Project owner cannot be removed. Transfer ownership first." };
300319
}
301320

302-
const success = await dbRemoveProjectMember(projectUuid, userUuidToRemove);
303-
if (success) {
321+
const removalResult = await dbRemoveProjectMember(projectUuid, userUuidToRemove);
322+
if (removalResult.success) {
323+
if (project.discordWebhookUrl && project.discordNotificationsEnabled && project.discordNotifyMembers) {
324+
await sendDiscordNotification(project.discordWebhookUrl, {
325+
embeds: [{
326+
title: "👤 Member Removed",
327+
description: `**${removalResult.userRemoved?.name || 'A user'}** was removed from the project by **${session.user.name}**.`,
328+
color: 15158332, // Red
329+
timestamp: new Date().toISOString(),
330+
footer: { text: `Project: ${project.name}` }
331+
}]
332+
});
333+
}
304334
return { success: true, message: "User removed from project successfully." };
305335
}
306336
return { error: "Failed to remove user from project." };
@@ -435,7 +465,7 @@ const UpdateTaskSchema = z.object({
435465

436466
export async function updateTaskAction(prevState: UpdateTaskFormState, formData: FormData): Promise<UpdateTaskFormState> {
437467
const session = await auth();
438-
if (!session?.user?.uuid) {
468+
if (!session?.user?.uuid || !session.user.name) {
439469
console.error("[updateTaskAction] Authentication required. No session user UUID.");
440470
return { error: "Authentication required." };
441471
}
@@ -468,6 +498,10 @@ export async function updateTaskAction(prevState: UpdateTaskFormState, formData:
468498

469499

470500
try {
501+
const project = await dbGetProjectByUuid(projectUuid);
502+
const oldTask = await dbGetTaskByUuid(taskUuid);
503+
if (!project || !oldTask) return { error: "Project or task not found." };
504+
471505
const userRole = await dbGetProjectMemberRole(projectUuid, session.user.uuid);
472506
console.log(`[updateTaskAction] User role check for project ${projectUuid} (user ${session.user.uuid}): ${userRole}`);
473507

@@ -500,6 +534,33 @@ export async function updateTaskAction(prevState: UpdateTaskFormState, formData:
500534
if (!updatedTask) {
501535
return { error: "Failed to update task."};
502536
}
537+
538+
if (project.discordWebhookUrl && project.discordNotificationsEnabled && project.discordNotifyTasks) {
539+
const changedFields: string[] = [];
540+
if (oldTask.status !== updatedTask.status) {
541+
changedFields.push(`Status changed from **${oldTask.status}** to **${updatedTask.status}**`);
542+
}
543+
if (oldTask.todoListMarkdown !== updatedTask.todoListMarkdown) {
544+
changedFields.push("Sub-tasks were modified.");
545+
}
546+
if (oldTask.title !== updatedTask.title) {
547+
changedFields.push(`Title changed to **${updatedTask.title}**`);
548+
}
549+
550+
if (changedFields.length > 0) {
551+
await sendDiscordNotification(project.discordWebhookUrl, {
552+
embeds: [{
553+
title: `🔄 Task Updated: ${updatedTask.title}`,
554+
description: `**${session.user.name}** updated a task:\n- ${changedFields.join('\n- ')}`,
555+
color: 3447003, // Blue
556+
timestamp: new Date().toISOString(),
557+
footer: { text: `Project: ${project.name}` }
558+
}]
559+
});
560+
}
561+
}
562+
563+
503564
return { message: "Task updated successfully!", updatedTask };
504565
} catch (error: any) {
505566
console.error("Error updating task:", error);
@@ -769,7 +830,7 @@ export interface ToggleProjectUrgencyFormState {
769830

770831
export async function toggleProjectUrgencyAction(prevState: ToggleProjectUrgencyFormState, formData: FormData): Promise<ToggleProjectUrgencyFormState> {
771832
const session = await auth();
772-
if (!session?.user?.uuid) {
833+
if (!session?.user?.uuid || !session.user.name) {
773834
console.error("[toggleProjectUrgencyAction] Authentication required. No session user UUID.");
774835
return { error: "Authentication required." };
775836
}
@@ -791,6 +852,19 @@ export async function toggleProjectUrgencyAction(prevState: ToggleProjectUrgency
791852
if (!updatedProject) {
792853
return { error: "Failed to update project urgency." };
793854
}
855+
856+
if (updatedProject.discordWebhookUrl && updatedProject.discordNotificationsEnabled && updatedProject.discordNotifySettings) {
857+
await sendDiscordNotification(updatedProject.discordWebhookUrl, {
858+
embeds: [{
859+
title: isUrgent ? "🔥 Project Marked as Urgent" : "Project No Longer Urgent",
860+
description: `**${session.user.name}** updated the project status.`,
861+
color: isUrgent ? 15158332 : 5763719,
862+
timestamp: new Date().toISOString(),
863+
footer: { text: `Project: ${updatedProject.name}` }
864+
}]
865+
});
866+
}
867+
794868
return { message: `Project urgency ${isUrgent ? 'set' : 'unset'}.`, project: updatedProject };
795869
} catch (error: any) {
796870
return { error: error.message || "An unexpected error occurred." };
@@ -805,7 +879,7 @@ export interface ToggleProjectVisibilityFormState {
805879

806880
export async function toggleProjectVisibilityAction(prevState: ToggleProjectVisibilityFormState, formData: FormData): Promise<ToggleProjectVisibilityFormState> {
807881
const session = await auth();
808-
if (!session?.user?.uuid) {
882+
if (!session?.user?.uuid || !session.user.name) {
809883
console.error("[toggleProjectVisibilityAction] Authentication required. No session user UUID.");
810884
return { error: "Authentication required." };
811885
}
@@ -833,6 +907,18 @@ export async function toggleProjectVisibilityAction(prevState: ToggleProjectVisi
833907
if (!updatedProjectInDb) {
834908
return { error: "Failed to update project visibility in FlowUp." };
835909
}
910+
if (updatedProjectInDb.discordWebhookUrl && updatedProjectInDb.discordNotificationsEnabled && updatedProjectInDb.discordNotifySettings) {
911+
await sendDiscordNotification(updatedProjectInDb.discordWebhookUrl, {
912+
embeds: [{
913+
title: isPrivate ? "🔒 Project is now Private" : "🌍 Project is now Public",
914+
description: `**${session.user.name}** updated the project's visibility.`,
915+
color: isPrivate ? 15105570 : 3447003,
916+
timestamp: new Date().toISOString(),
917+
footer: { text: `Project: ${updatedProjectInDb.name}` }
918+
}]
919+
});
920+
}
921+
836922

837923
if (updatedProjectInDb.githubRepoUrl && updatedProjectInDb.githubRepoName) {
838924
const oauthToken = await dbGetUserGithubOAuthToken(session.user.uuid);
@@ -957,7 +1043,7 @@ const CreateDocumentSchema = z.object({
9571043

9581044
export async function createDocumentAction(prevState: CreateDocumentFormState, formData: FormData): Promise<CreateDocumentFormState> {
9591045
const session = await auth();
960-
if (!session?.user?.uuid) return { error: "Authentication required." };
1046+
if (!session?.user?.uuid || !session.user.name) return { error: "Authentication required." };
9611047

9621048
const validatedFields = CreateDocumentSchema.safeParse({
9631049
projectUuid: formData.get('projectUuid'),
@@ -983,6 +1069,20 @@ export async function createDocumentAction(prevState: CreateDocumentFormState, f
9831069
fileType: 'markdown',
9841070
createdByUuid: session.user.uuid,
9851071
});
1072+
1073+
const project = await dbGetProjectByUuid(projectUuid);
1074+
if (project?.discordWebhookUrl && project.discordNotificationsEnabled && project.discordNotifyDocuments) {
1075+
await sendDiscordNotification(project.discordWebhookUrl, {
1076+
embeds: [{
1077+
title: "📄 New Document Created",
1078+
description: `**${session.user.name}** created a new document titled **${createdDocument.title}**.`,
1079+
color: 8421504, // Gray
1080+
timestamp: new Date().toISOString(),
1081+
footer: { text: `Project: ${project.name}` }
1082+
}]
1083+
});
1084+
}
1085+
9861086
return { message: "Document created successfully!", createdDocument };
9871087
} catch (error: any) {
9881088
console.error("Error creating document:", error);
@@ -1065,25 +1165,38 @@ export interface DeleteDocumentFormState {
10651165
}
10661166
export async function deleteDocumentAction(prevState: DeleteDocumentFormState, formData: FormData): Promise<DeleteDocumentFormState> {
10671167
const session = await auth();
1068-
if (!session?.user?.uuid) return { error: "Authentication required." };
1168+
if (!session?.user?.uuid || !session.user.name) return { error: "Authentication required." };
10691169

10701170
const documentUuid = formData.get('documentUuid') as string;
10711171
const projectUuid = formData.get('projectUuid') as string;
10721172

10731173
if (!documentUuid) return { error: "Document UUID is required."};
10741174

10751175
try {
1076-
if (projectUuid) {
1077-
const userRole = await dbGetProjectMemberRole(projectUuid, session.user.uuid);
1078-
if (!userRole || !['owner', 'co-owner', 'editor'].includes(userRole)) {
1079-
return { error: "You do not have permission to delete documents in this project." };
1080-
}
1081-
} else {
1082-
return { error: "Project context is required for permission check." };
1176+
const project = await dbGetProjectByUuid(projectUuid);
1177+
if (!project) return { error: "Project context not found." };
1178+
1179+
const userRole = await dbGetProjectMemberRole(projectUuid, session.user.uuid);
1180+
if (!userRole || !['owner', 'co-owner', 'editor'].includes(userRole)) {
1181+
return { error: "You do not have permission to delete documents in this project." };
10831182
}
1183+
1184+
const docToDelete = await getDocumentByUuid(documentUuid);
1185+
if (!docToDelete) return { error: "Document not found."};
10841186

10851187
const success = await dbDeleteDocument(documentUuid);
10861188
if (success) {
1189+
if (project.discordWebhookUrl && project.discordNotificationsEnabled && project.discordNotifyDocuments) {
1190+
await sendDiscordNotification(project.discordWebhookUrl, {
1191+
embeds: [{
1192+
title: "🗑️ Document Deleted",
1193+
description: `**${session.user.name}** deleted the document titled **${docToDelete.title}**.`,
1194+
color: 15158332, // Red
1195+
timestamp: new Date().toISOString(),
1196+
footer: { text: `Project: ${project.name}` }
1197+
}]
1198+
});
1199+
}
10871200
return { message: "Document deleted successfully." };
10881201
}
10891202
return { error: "Failed to delete document." };
@@ -1944,6 +2057,8 @@ const updateDiscordSettingsSchema = z.object({
19442057
discordNotifyTasks: z.enum(['true', 'false']).transform(v => v === 'true').optional(),
19452058
discordNotifyMembers: z.enum(['true', 'false']).transform(v => v === 'true').optional(),
19462059
discordNotifyAnnouncements: z.enum(['true', 'false']).transform(v => v === 'true').optional(),
2060+
discordNotifyDocuments: z.enum(['true', 'false']).transform(v => v === 'true').optional(),
2061+
discordNotifySettings: z.enum(['true', 'false']).transform(v => v === 'true').optional(),
19472062
});
19482063

19492064

@@ -1963,21 +2078,23 @@ export async function updateProjectDiscordSettingsAction(
19632078
discordNotifyTasks: formData.get('discordNotifyTasks'),
19642079
discordNotifyMembers: formData.get('discordNotifyMembers'),
19652080
discordNotifyAnnouncements: formData.get('discordNotifyAnnouncements'),
2081+
discordNotifyDocuments: formData.get('discordNotifyDocuments'),
2082+
discordNotifySettings: formData.get('discordNotifySettings'),
19662083
});
19672084

19682085
if (!validatedFields.success) {
19692086
return { error: "Invalid input: " + validatedFields.error.flatten().fieldErrors.discordWebhookUrl?.join(', ') };
19702087
}
19712088

1972-
const { projectUuid, discordWebhookUrl, discordNotificationsEnabled, discordNotifyTasks, discordNotifyMembers, discordNotifyAnnouncements } = validatedFields.data;
2089+
const { projectUuid, discordWebhookUrl, discordNotificationsEnabled, discordNotifyTasks, discordNotifyMembers, discordNotifyAnnouncements, discordNotifyDocuments, discordNotifySettings } = validatedFields.data;
19732090

19742091
try {
19752092
const userRole = await dbGetProjectMemberRole(projectUuid, session.user.uuid);
19762093
if (!userRole || !['owner', 'co-owner'].includes(userRole)) {
19772094
return { error: "You do not have permission to change Discord settings for this project." };
19782095
}
19792096

1980-
const updatedProject = await dbUpdateProjectDiscordSettings(projectUuid, discordWebhookUrl, discordNotificationsEnabled, discordNotifyTasks, discordNotifyMembers, discordNotifyAnnouncements);
2097+
const updatedProject = await dbUpdateProjectDiscordSettings(projectUuid, discordWebhookUrl, discordNotificationsEnabled, discordNotifyTasks, discordNotifyMembers, discordNotifyAnnouncements, discordNotifyDocuments, discordNotifySettings);
19812098

19822099
if (!updatedProject) {
19832100
return { error: "Failed to update project settings in the database." };
@@ -2088,17 +2205,21 @@ export async function setupGithubWebhookAction(
20882205

20892206
const updatedProject = await dbUpdateProjectWebhookDetails(project.uuid, newWebhook.id, webhookSecret);
20902207
if (!updatedProject) {
2091-
return { error: "Webhook created on GitHub, but failed to save details in FlowUp." };
2208+
// Best effort to clean up if DB write fails
2209+
await octokit.rest.repos.deleteWebhook({ owner, repo, hook_id: newWebhook.id });
2210+
return { error: "Webhook created on GitHub, but failed to save details in FlowUp. The webhook on GitHub has been removed." };
20922211
}
20932212

20942213
return { success: true, message: `Webhook (ID: ${newWebhook.id}) successfully created on GitHub!`, project: updatedProject };
20952214

20962215
} catch (error: any) {
20972216
console.error("Error setting up GitHub webhook:", error);
20982217
let errorMessage = error.message || "An unexpected error occurred.";
2099-
if (error.status === 422) { // Unprocessable Entity
2100-
if(error.message.includes("Hook already exists")) {
2218+
if (error.status === 422) {
2219+
if (error.message.includes("Hook already exists")) {
21012220
errorMessage = "A webhook for this URL already exists on the repository. Please check your repository settings on GitHub.";
2221+
} else if (error.message.includes("not supported because it isn't reachable")) {
2222+
errorMessage = "GitHub couldn't reach your webhook URL. If you are developing locally, your `localhost` is not accessible from the public internet. Please use a tunneling service like ngrok to expose your local server, then update your NEXT_PUBLIC_APP_URL in the .env file.";
21022223
} else {
21032224
errorMessage = `Could not create webhook (422): ${error.message}. Check permissions and configuration.`;
21042225
}

0 commit comments

Comments
 (0)