Skip to content

Commit 50b9c27

Browse files
authored
Merge pull request #49 from WildCodeSchool/US4-modifier-et-supprimer-des-groupes
Us4 modifier et supprimer des groupes
2 parents 51ff3fd + c4e551d commit 50b9c27

34 files changed

+1418
-325
lines changed

backend/src/entities/GroupMember.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,16 +43,23 @@ export class GroupMember extends BaseEntity {
4343
@Field()
4444
lastTempstampVu: Date;
4545

46+
@Field(() => User)
4647
@ManyToOne(
4748
() => User,
4849
(user) => user.groupMember,
50+
{
51+
onDelete: "CASCADE",
52+
},
4953
)
5054
@JoinColumn({ name: "userId" })
5155
user: User;
5256

5357
@ManyToOne(
5458
() => Group,
5559
(group) => group.groupMember,
60+
{
61+
onDelete: "CASCADE",
62+
},
5663
)
5764
@JoinColumn({ name: "groupId" })
5865
group: Group;

backend/src/entities/Message.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,9 @@ export class Message extends BaseEntity {
5151
@ManyToOne(
5252
() => Group,
5353
(group) => group.messages,
54+
{
55+
onDelete: "CASCADE",
56+
},
5457
)
5558
@Field(() => Group)
5659
group: Group;

backend/src/resolvers/GroupResolver.ts

Lines changed: 201 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,14 @@ import {
1212
Root,
1313
UseMiddleware,
1414
} from "type-graphql";
15+
1516
import Group from "../entities/Group";
1617
import { GroupMember } from "../entities/GroupMember";
1718
import { Message } from "../entities/Message";
1819
import User from "../entities/User";
1920
import { getVariableEnv } from "../lib/envManager/envManager";
2021
import { RoleMiddleware } from "../middleware/RoleMiddleware";
22+
import { addMembersToGroup, removeMembersFromGroup } from "../services/groupMemberService";
2123
import type { ContextType } from "../types/context";
2224

2325
@InputType()
@@ -49,6 +51,36 @@ class AddFundsInput {
4951
amount!: number;
5052
}
5153

54+
@InputType()
55+
class UpdateGroupInput {
56+
@Field()
57+
name!: string;
58+
59+
@Field()
60+
event_type!: string;
61+
62+
@Field()
63+
piggy_bank!: number;
64+
65+
@Field()
66+
deadline!: Date;
67+
68+
@Field(() => [String], { nullable: true })
69+
users?: string[];
70+
71+
@Field({ nullable: true })
72+
user_beneficiary?: string;
73+
}
74+
75+
@InputType()
76+
class RemoveMembersInput {
77+
@Field(() => [Number], { nullable: true })
78+
userIds?: number[];
79+
80+
@Field(() => [String], { nullable: true })
81+
userEmails?: string[];
82+
}
83+
5284
@ObjectType()
5385
export class MyGroupsResponse {
5486
@Field(() => [Group])
@@ -72,7 +104,13 @@ export default class GroupResolver {
72104
user: { id: ctx.user?.id },
73105
},
74106
},
75-
relations: { groupMember: true, user_admin: true, user_beneficiary: true },
107+
relations: {
108+
groupMember: {
109+
user: true, // 👈 THIS is the key
110+
},
111+
user_admin: true,
112+
user_beneficiary: true,
113+
},
76114
order: { id: "DESC" },
77115
});
78116

@@ -83,10 +121,27 @@ export default class GroupResolver {
83121
return { groups, groupToken };
84122
}
85123

124+
@Query(() => Group)
125+
async getGroupById(@Arg("id") id: number) {
126+
const group = await Group.findOne({
127+
where: { id: id },
128+
relations: {
129+
user_admin: true,
130+
user_beneficiary: true,
131+
groupMember: {
132+
user: true,
133+
},
134+
},
135+
});
136+
if (!group) throw new Error("Groupe non trouvé");
137+
return group;
138+
}
139+
86140
@FieldResolver(() => [GroupMember])
87141
async groupMember(@Root() group: Group) {
88142
const groupMembers = await GroupMember.find({
89143
where: { groupId: group.id },
144+
relations: { user: true },
90145
});
91146

92147
return groupMembers || []; // >>> not null
@@ -195,4 +250,149 @@ export default class GroupResolver {
195250

196251
return group;
197252
}
253+
254+
@UseMiddleware(RoleMiddleware())
255+
@Mutation(() => Group)
256+
async updateGroup(@Arg("id") id: number, @Arg("data") data: UpdateGroupInput) {
257+
const group = await Group.findOne({ where: { id: id } });
258+
if (!group) throw new Error("Groupe non trouvé");
259+
group.name = data.name;
260+
group.event_type = data.event_type;
261+
group.piggy_bank = data.piggy_bank;
262+
group.deadline = data.deadline;
263+
await group.save();
264+
265+
if (data.users?.length) {
266+
await addMembersToGroup({
267+
userEmails: data.users,
268+
groupId: group.id,
269+
});
270+
}
271+
272+
return group;
273+
}
274+
275+
@UseMiddleware(RoleMiddleware())
276+
@Mutation(() => String)
277+
async deleteGroup(@Arg("id") id: number, @Ctx() ctx: ContextType): Promise<string> {
278+
if (!ctx.user) {
279+
throw new Error("Utilisateur non connecté");
280+
}
281+
282+
const group = await Group.findOne({
283+
where: {
284+
id,
285+
user_admin: { id: ctx.user.id },
286+
},
287+
relations: { user_admin: true },
288+
});
289+
290+
if (!group) {
291+
throw new Error("Groupe introuvable ou accès refusé");
292+
}
293+
294+
if (!group.user_admin || group.user_admin.id !== ctx.user.id) {
295+
throw new Error("Il faut être administrateur du groupe pour pouvoir le supprimer");
296+
}
297+
298+
try {
299+
await Group.remove(group);
300+
return "Le groupe a été supprimé";
301+
} catch (err) {
302+
console.error("deleteGroup error:", err);
303+
throw new Error("Une erreur est survenue lors de la suppression du groupe");
304+
}
305+
}
306+
307+
@UseMiddleware(RoleMiddleware())
308+
@Mutation(() => String)
309+
async removeMembersFromGroup(
310+
@Arg("groupId", () => Number) groupId: number,
311+
@Arg("data") data: RemoveMembersInput,
312+
@Ctx() ctx: ContextType,
313+
): Promise<string> {
314+
if (!ctx.user) {
315+
throw new Error("Utilisateur non connecté");
316+
}
317+
318+
// Ensure at least one input is provided
319+
if ((!data.userIds || data.userIds.length === 0) && (!data.userEmails || data.userEmails.length === 0)) {
320+
throw new Error("Vous devez fournir au moins un identifiant utilisateur ou un email");
321+
}
322+
323+
const group = await Group.findOne({
324+
where: {
325+
id: groupId,
326+
},
327+
relations: { user_admin: true },
328+
});
329+
330+
if (!group) {
331+
throw new Error("Groupe introuvable ou accès refusé");
332+
}
333+
334+
const currentUserId = ctx.user.id;
335+
const isAdmin = ctx.user.id === group.user_admin.id;
336+
337+
// Convert emails to user IDs if provided
338+
let userIdsToRemove: number[] = [];
339+
340+
if (data.userEmails && data.userEmails.length > 0) {
341+
await Promise.all(
342+
data.userEmails.map(async (email) => {
343+
const user = await User.findOne({ where: { email } });
344+
if (user) {
345+
userIdsToRemove.push(user.id);
346+
}
347+
}),
348+
);
349+
}
350+
351+
// Add direct user IDs if provided
352+
if (data.userIds && data.userIds.length > 0) {
353+
userIdsToRemove.push(...data.userIds);
354+
}
355+
356+
// Remove duplicates
357+
userIdsToRemove = [...new Set(userIdsToRemove)];
358+
359+
if (userIdsToRemove.length === 0) {
360+
throw new Error("Aucun utilisateur valide trouvé");
361+
}
362+
363+
// Admin cannot remove itself from a group
364+
if (isAdmin && userIdsToRemove.includes(group.user_admin.id)) {
365+
throw new Error("L'administrateur du groupe ne peut pas être supprimé. Supprimez plutôt le groupe");
366+
}
367+
368+
// Remove myself from a group
369+
if (!isAdmin && userIdsToRemove.length === 1 && userIdsToRemove[0] === currentUserId) {
370+
try {
371+
await removeMembersFromGroup({
372+
userIds: [currentUserId],
373+
groupId,
374+
});
375+
return "Succès! Vous ne faites plus partie du groupe!";
376+
} catch (err) {
377+
console.error("removeMembersFromGroup error:", err);
378+
throw new Error("Une erreur est survenue, nous n'avons pas pu vous supprimer du groupe");
379+
}
380+
}
381+
382+
// Admin removes users from a group
383+
if (isAdmin && userIdsToRemove.length > 0 && !userIdsToRemove.includes(group.user_admin.id)) {
384+
try {
385+
await removeMembersFromGroup({
386+
userIds: userIdsToRemove,
387+
groupId,
388+
});
389+
return "Succès! Les utilisateurs ont été supprimés du groupe!";
390+
} catch (err) {
391+
console.error("removeMembersFromGroup error:", err);
392+
throw new Error("Une erreur est survenue, nous n'avons pas pu supprimer les utilisateurs du groupe");
393+
}
394+
}
395+
396+
throw new Error("Vous n'avez pas les permissions nécessaires pour effectuer cette action");
397+
}
198398
}

backend/src/resolvers/MessageResolver.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ export default class MessageResolver {
131131

132132
if (!userId) throw new Error("Utilisateur non authentifié");
133133

134-
const group = await GroupMember.findOne({ where: { user: { id: userId }, group: { id: groupId } } });
134+
const group = await GroupMember.findOne({ where: { userId: userId, groupId: groupId } });
135135
// verifier que l'utilisateur fait bien partie du groupe
136136
if (!group) throw new Error("Groupe non trouvé");
137137

backend/src/services/groupMemberService.ts

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@ type AddMembersInput = {
77
groupId: number;
88
};
99

10+
type RemoveMembersInput = {
11+
userIds: number[];
12+
groupId: number;
13+
};
14+
1015
/**
1116
* Add users to a group from a list of emails.
1217
* - Silently skips emails that do not match a user.
@@ -17,7 +22,8 @@ export async function addMembersToGroup({ userEmails, groupId }: AddMembersInput
1722
await Promise.all(
1823
userEmails.map(async (userEmail) => {
1924
const userToAdd = await User.findOne({ where: { email: userEmail } });
20-
if (!userToAdd) {
25+
const userPending = await PendingInvitation.findOne({ where: { userEmail: userEmail } });
26+
if (!userToAdd && !userPending) {
2127
// if the user does not exist in the db, add it to the pendinginvitationList
2228
const pendingInvitation = PendingInvitation.create({
2329
userEmail: userEmail,
@@ -29,11 +35,33 @@ export async function addMembersToGroup({ userEmails, groupId }: AddMembersInput
2935
}
3036

3137
const groupMember = GroupMember.create({
32-
userId: userToAdd.id,
38+
userId: userToAdd?.id,
3339
groupId,
3440
});
3541

3642
await groupMember.save();
3743
}),
3844
);
3945
}
46+
47+
export async function removeMembersFromGroup({ userIds, groupId }: RemoveMembersInput) {
48+
if (!userIds || userIds.length === 0) return;
49+
50+
// Remove all group members
51+
await Promise.all(
52+
userIds.map(async (userId) => {
53+
const groupMember = await GroupMember.findOne({
54+
where: { userId: userId, groupId: groupId },
55+
});
56+
57+
if (!groupMember) return;
58+
59+
try {
60+
await GroupMember.remove(groupMember);
61+
} catch (err) {
62+
console.error("removeMembersFromGroup error:", err);
63+
throw new Error("Une erreur est survenue lors de la suppression de l'utilisateur du groupe");
64+
}
65+
}),
66+
);
67+
}

0 commit comments

Comments
 (0)