Skip to content

Commit ec34377

Browse files
committed
fix: crud perms charter
1 parent 6943d74 commit ec34377

File tree

6 files changed

+122
-69
lines changed

6 files changed

+122
-69
lines changed

platforms/group-charter-manager-api/src/controllers/GroupController.ts

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -102,10 +102,11 @@ export class GroupController {
102102
return res.status(404).json({ error: "Group not found" });
103103
}
104104

105-
// Check if user is a participant in the group
106-
const isParticipant = group.participants?.some(p => p.id === userId);
107-
if (!isParticipant) {
108-
return res.status(403).json({ error: "Access denied - you must be a participant in this group" });
105+
// Check if user is an admin or owner of the group
106+
const isOwner = group.owner === userId;
107+
const isAdmin = group.admins?.includes(userId);
108+
if (!isOwner && !isAdmin) {
109+
return res.status(403).json({ error: "Access denied - only admins can edit the charter" });
109110
}
110111

111112
const { charter } = req.body;
@@ -125,6 +126,25 @@ export class GroupController {
125126
async deleteGroup(req: Request, res: Response) {
126127
try {
127128
const { id } = req.params;
129+
const userId = (req as any).user?.id;
130+
131+
if (!userId) {
132+
return res.status(401).json({ error: "Unauthorized" });
133+
}
134+
135+
const group = await this.groupService.getGroupById(id);
136+
if (!group) {
137+
return res.status(404).json({ error: "Group not found" });
138+
}
139+
140+
// Check if user is owner or admin
141+
const isOwner = group.owner === userId;
142+
const isAdmin = group.admins?.includes(userId);
143+
144+
if (!isOwner && !isAdmin) {
145+
return res.status(403).json({ error: "Access denied - only admins can delete groups" });
146+
}
147+
128148
const success = await this.groupService.deleteGroup(id);
129149

130150
if (!success) {

platforms/group-charter-manager-api/src/services/CharterSignatureService.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,4 +130,13 @@ export class CharterSignatureService {
130130
}
131131
});
132132
}
133+
134+
// Delete all signatures for a group (when charter content changes)
135+
async deleteAllSignaturesForGroup(groupId: string): Promise<void> {
136+
const result = await this.signatureRepository.delete({
137+
groupId
138+
});
139+
140+
console.log(`Deleted ${result.affected || 0} signatures for group ${groupId} due to charter content change`);
141+
}
133142
}

platforms/group-charter-manager-api/src/services/GroupService.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@ import { AppDataSource } from "../database/data-source";
22
import { Group } from "../database/entities/Group";
33
import { User } from "../database/entities/User";
44
import { MessageService } from "./MessageService";
5+
import { CharterSignatureService } from "./CharterSignatureService";
56

67
export class GroupService {
78
public groupRepository = AppDataSource.getRepository(Group);
89
private userRepository = AppDataSource.getRepository(User);
910
private messageService = new MessageService();
11+
private charterSignatureService = new CharterSignatureService();
1012

1113
async createGroup(groupData: Partial<Group>): Promise<Group> {
1214
const group = this.groupRepository.create(groupData);
@@ -26,15 +28,15 @@ export class GroupService {
2628
}
2729

2830
async updateGroup(id: string, groupData: Partial<Group>): Promise<Group | null> {
29-
// If updating the charter, we need to mark all existing signatures as invalid
31+
// If updating the charter, we need to delete all existing signatures
3032
// since the charter content has changed
3133
if (groupData.charter !== undefined) {
3234
// Get the current group to check if charter is being updated
3335
const currentGroup = await this.getGroupById(id);
3436
if (currentGroup && currentGroup.charter !== groupData.charter) {
35-
// Charter content has changed, so all existing signatures are now invalid
36-
// This will be handled by the CharterSignatureService when checking signing status
37-
console.log(`Charter updated for group ${id}, existing signatures are now invalid`);
37+
// Charter content has changed, so delete all existing signatures
38+
console.log(`Charter updated for group ${id}, deleting all existing signatures`);
39+
await this.charterSignatureService.deleteAllSignaturesForGroup(id);
3840
}
3941
}
4042

platforms/group-charter-manager/src/app/charter/[id]/page.tsx

Lines changed: 70 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -152,8 +152,8 @@ export default function CharterDetail({
152152
isEditing
153153
});
154154

155-
// Temporary: Always show edit button for testing
156-
const showEditButton = true; // canEdit;
155+
// Only show edit button for admins
156+
const showEditButton = canEdit;
157157

158158
if (isLoading) {
159159
return (
@@ -258,7 +258,7 @@ export default function CharterDetail({
258258
</>
259259
) : (
260260
<>
261-
{/* Edit Charter Button (only for owner or admin) */}
261+
{/* Edit Charter Button (only for admins) */}
262262
{showEditButton && (
263263
<Button
264264
onClick={handleEditStart}
@@ -285,6 +285,23 @@ export default function CharterDetail({
285285
</Button>
286286
)
287287
)}
288+
289+
{/* Disabled button for users who have already signed */}
290+
{group.charter && signingStatus && !signingStatusLoading && (
291+
(() => {
292+
const currentUser = signingStatus.participants.find((p: any) => p.id === user?.id);
293+
return currentUser && currentUser.hasSigned;
294+
})() && (
295+
<Button
296+
disabled
297+
variant="outline"
298+
className="bg-green-50 text-green-600 border-green-600 cursor-not-allowed px-6 py-3 rounded-2xl font-medium"
299+
>
300+
<CheckCircle className="mr-2" size={18} />
301+
Charter Signed
302+
</Button>
303+
)
304+
)}
288305
</>
289306
)}
290307
</div>
@@ -318,12 +335,30 @@ export default function CharterDetail({
318335
</ReactMarkdown>
319336
</div>
320337
) : (
321-
<p className="text-gray-500 italic">
322-
No charter content has been set for this group.
323-
</p>
338+
<div>
339+
<p className="text-gray-500 italic">
340+
No charter content has been set for this group.
341+
</p>
342+
{!canEdit && (
343+
<div className="mt-3 p-3 bg-blue-50 border border-blue-200 rounded-lg">
344+
<p className="text-sm text-blue-700">
345+
💡 Only admins can create the charter. Contact a group admin to get started.
346+
</p>
347+
</div>
348+
)}
349+
</div>
324350
)}
325351
</div>
326352
)}
353+
354+
{/* Info message for non-admin users */}
355+
{!canEdit && group.charter && (
356+
<div className="mt-4 p-3 bg-blue-50 border border-blue-200 rounded-lg">
357+
<p className="text-sm text-blue-700">
358+
💡 Only admins can edit the charter. Contact a group admin if you need changes.
359+
</p>
360+
</div>
361+
)}
327362
</div>
328363
</CardContent>
329364
</Card>
@@ -338,13 +373,16 @@ export default function CharterDetail({
338373
Group Information
339374
</h3>
340375
<div className="space-y-3 text-sm">
341-
<div>
342-
<span className="text-gray-600">Owner:</span>
343-
<span className="ml-2 font-medium">{group.owner}</span>
344-
</div>
345376
<div>
346377
<span className="text-gray-600">Admins:</span>
347-
<span className="ml-2 font-medium">{group.admins?.length || 0}</span>
378+
<span className="ml-2 font-medium">
379+
{group.participants && group.admins ? (
380+
group.participants
381+
.filter(participant => group.admins.includes(participant.id))
382+
.map(participant => participant.name || participant.ename || 'Unknown')
383+
.join(', ')
384+
) : 'None'}
385+
</span>
348386
</div>
349387
<div>
350388
<span className="text-gray-600">Members:</span>
@@ -393,10 +431,29 @@ export default function CharterDetail({
393431
charterData={{ charter: group.charter }}
394432
onSigningComplete={(groupId) => {
395433
setShowSigningInterface(false);
396-
// Refresh the group data to show updated signing status
397-
fetchGroup();
434+
// Immediately update signing status without full refresh
435+
fetchSigningStatus();
398436
}}
399437
onCancel={() => setShowSigningInterface(false)}
438+
onSigningStatusUpdate={(participantId, hasSigned) => {
439+
// Immediately update the local signing status
440+
if (signingStatus) {
441+
setSigningStatus(prev => {
442+
if (!prev) return prev;
443+
444+
const updatedParticipants = prev.participants.map(participant =>
445+
participant.id === participantId
446+
? { ...participant, hasSigned }
447+
: participant
448+
);
449+
450+
return {
451+
...prev,
452+
participants: updatedParticipants
453+
};
454+
});
455+
}
456+
}}
400457
/>
401458
</div>
402459
</div>

platforms/group-charter-manager/src/components/charter-signing-interface.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,10 @@ interface CharterSigningInterfaceProps {
1616
charterData: any;
1717
onSigningComplete: (groupId: string) => void;
1818
onCancel: () => void;
19+
onSigningStatusUpdate?: (participantId: string, hasSigned: boolean) => void;
1920
}
2021

21-
export function CharterSigningInterface({ groupId, charterData, onSigningComplete, onCancel }: CharterSigningInterfaceProps) {
22+
export function CharterSigningInterface({ groupId, charterData, onSigningComplete, onCancel, onSigningStatusUpdate }: CharterSigningInterfaceProps) {
2223
const [sessionId, setSessionId] = useState<string | null>(null);
2324
const [qrData, setQrData] = useState<string | null>(null);
2425
const [status, setStatus] = useState<"pending" | "connecting" | "signed" | "expired" | "error">("pending");
@@ -90,6 +91,11 @@ export function CharterSigningInterface({ groupId, charterData, onSigningComplet
9091
if (data.type === "signed" && data.status === "completed") {
9192
setStatus("signed");
9293

94+
// Immediately update the parent component's signing status
95+
if (onSigningStatusUpdate && user?.id) {
96+
onSigningStatusUpdate(user.id, true);
97+
}
98+
9399
toast({
94100
title: "Charter Signed!",
95101
description: "Your charter has been successfully signed",

platforms/group-charter-manager/src/components/charter-signing-status.tsx

Lines changed: 6 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,8 @@ import { useState, useEffect } from "react";
44
import { CheckCircle, Circle, AlertTriangle } from "lucide-react";
55
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
66
import { Badge } from "@/components/ui/badge";
7-
import { Button } from "@/components/ui/button";
87
import { apiClient } from "@/lib/apiClient";
98
import { useToast } from "@/hooks/use-toast";
10-
import { CharterSigningInterface } from "./charter-signing-interface";
119

1210
interface CharterSigningStatusProps {
1311
groupId: string;
@@ -31,7 +29,6 @@ interface SigningStatus {
3129
export function CharterSigningStatus({ groupId, charterContent }: CharterSigningStatusProps) {
3230
const [signingStatus, setSigningStatus] = useState<SigningStatus | null>(null);
3331
const [loading, setLoading] = useState(true);
34-
const [showSigningInterface, setShowSigningInterface] = useState(false);
3532
const { toast } = useToast();
3633

3734
useEffect(() => {
@@ -55,21 +52,13 @@ export function CharterSigningStatus({ groupId, charterContent }: CharterSigning
5552
}
5653
};
5754

58-
const handleSigningComplete = async (groupId: string) => {
59-
toast({
60-
title: "Success",
61-
description: "Charter signed successfully!",
62-
});
63-
setShowSigningInterface(false);
64-
// Refresh the signing status
65-
await fetchSigningStatus();
66-
};
55+
6756

6857
if (loading) {
6958
return (
7059
<Card className="bg-white/70 backdrop-blur-xs rounded-3xl soft-shadow">
7160
<CardHeader>
72-
<CardTitle>Users</CardTitle>
61+
<CardTitle>Members</CardTitle>
7362
</CardHeader>
7463
<CardContent>
7564
<div className="animate-pulse space-y-3">
@@ -86,7 +75,7 @@ export function CharterSigningStatus({ groupId, charterContent }: CharterSigning
8675
return (
8776
<Card className="bg-white/70 backdrop-blur-xs rounded-3xl soft-shadow">
8877
<CardHeader>
89-
<CardTitle>Users</CardTitle>
78+
<CardTitle>Members</CardTitle>
9079
</CardHeader>
9180
<CardContent>
9281
<p className="text-gray-500">Unable to load user information</p>
@@ -95,15 +84,13 @@ export function CharterSigningStatus({ groupId, charterContent }: CharterSigning
9584
);
9685
}
9786

98-
const signedCount = signingStatus.participants.filter(p => p.hasSigned).length;
99-
const totalCount = signingStatus.participants.length;
100-
const allSigned = signedCount === totalCount;
87+
10188

10289
return (
10390
<>
10491
<Card className="bg-white/70 backdrop-blur-xs rounded-3xl soft-shadow">
10592
<CardHeader>
106-
<CardTitle>Users</CardTitle>
93+
<CardTitle>Members</CardTitle>
10794
</CardHeader>
10895
<CardContent>
10996
<div className="space-y-3">
@@ -144,37 +131,9 @@ export function CharterSigningStatus({ groupId, charterContent }: CharterSigning
144131
))}
145132
</div>
146133

147-
{signingStatus.signatures.length > 0 && (
148-
<div className="mt-6 pt-6 border-t">
149-
<h4 className="font-medium text-sm text-gray-700 mb-3">Recent Signatures</h4>
150-
<div className="space-y-2">
151-
{signingStatus.signatures.slice(-5).map((signature) => (
152-
<div key={signature.id} className="flex items-center gap-2 text-sm text-gray-600">
153-
<CheckCircle className="h-4 w-4 text-green-600" />
154-
<span>
155-
{signature.user?.name || signature.user?.ename || 'Unknown User'}
156-
signed on {new Date(signature.createdAt).toLocaleDateString()}
157-
</span>
158-
</div>
159-
))}
160-
</div>
161-
</div>
162-
)}
134+
163135
</CardContent>
164136
</Card>
165-
166-
{showSigningInterface && (
167-
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
168-
<div className="bg-white rounded-lg p-6 max-w-md w-full mx-4">
169-
<CharterSigningInterface
170-
groupId={groupId}
171-
charterData={{ charter: charterContent }}
172-
onSigningComplete={handleSigningComplete}
173-
onCancel={() => setShowSigningInterface(false)}
174-
/>
175-
</div>
176-
</div>
177-
)}
178137
</>
179138
);
180139
}

0 commit comments

Comments
 (0)