Skip to content

Commit baf31f7

Browse files
committed
charter: signing
1 parent da66cb4 commit baf31f7

File tree

5 files changed

+191
-33
lines changed

5 files changed

+191
-33
lines changed

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

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import { Request, Response } from "express";
22
import { GroupService } from "../services/GroupService";
3+
import { CharterSignatureService } from "../services/CharterSignatureService";
34

45
export class GroupController {
56
private groupService = new GroupService();
7+
private charterSignatureService = new CharterSignatureService();
68

79
async createGroup(req: Request, res: Response) {
810
try {
@@ -251,24 +253,30 @@ export class GroupController {
251253
}
252254
}
253255

254-
/**
255-
* Admin endpoint to ensure Cerberus monitoring is set up for all groups
256-
*/
257-
async ensureCerberusInAllGroups(req: Request, res: Response) {
256+
async getCharterSigningStatus(req: Request, res: Response) {
258257
try {
258+
const { id } = req.params;
259259
const userId = (req as any).user?.id;
260+
260261
if (!userId) {
261262
return res.status(401).json({ error: "Unauthorized" });
262263
}
263264

264-
// This is an admin-only operation - you might want to add more specific admin checks
265-
console.log(`User ${userId} requested to set up Cerberus monitoring for all groups`);
266-
267-
await this.groupService.ensureCerberusInAllGroups();
268-
269-
res.json({ message: "Cerberus monitoring has been set up for all groups with charter requirements" });
265+
const group = await this.groupService.getGroupById(id);
266+
if (!group) {
267+
return res.status(404).json({ error: "Group not found" });
268+
}
269+
270+
// Check if user is a participant in the group
271+
const isParticipant = group.participants?.some(p => p.id === userId);
272+
if (!isParticipant) {
273+
return res.status(403).json({ error: "Access denied - you must be a participant in this group" });
274+
}
275+
276+
const signingStatus = await this.charterSignatureService.getGroupSigningStatus(id);
277+
res.json(signingStatus);
270278
} catch (error) {
271-
console.error("Error setting up Cerberus monitoring for all groups:", error);
279+
console.error("Error getting charter signing status:", error);
272280
res.status(500).json({ error: "Internal server error" });
273281
}
274282
}

platforms/group-charter-manager-api/src/database/data-source.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { Group } from "./entities/Group";
66
import { Message } from "./entities/Message";
77
import { PostgresSubscriber } from "../web3adapter/watchers/subscriber";
88
import path from "path";
9+
import { CharterSignature } from "./entities/CharterSignature";
910

1011
config({ path: path.resolve(__dirname, "../../../../.env") });
1112

@@ -14,7 +15,7 @@ export const AppDataSource = new DataSource({
1415
url: process.env.GROUP_CHARTER_DATABASE_URL,
1516
synchronize: false,
1617
logging: process.env.NODE_ENV === "development",
17-
entities: [User, Group, Message],
18+
entities: [User, Group, Message, CharterSignature],
1819
migrations: ["src/database/migrations/*.ts"],
1920
subscribers: [PostgresSubscriber],
2021
});

platforms/group-charter-manager-api/src/index.ts

Lines changed: 70 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { UserController } from "./controllers/UserController";
77
import { GroupController } from "./controllers/GroupController";
88
import { WebhookController } from "./controllers/WebhookController";
99
import { AuthController } from "./controllers/AuthController";
10+
import { CharterSigningController } from "./controllers/CharterSigningController";
1011
import { authMiddleware, authGuard } from "./middleware/auth";
1112
import { adapter } from "./web3adapter";
1213
import path from "path";
@@ -20,6 +21,14 @@ const port = process.env.PORT || 3001;
2021
AppDataSource.initialize()
2122
.then(async () => {
2223
console.log("Database connection established");
24+
25+
// Initialize signing controller
26+
try {
27+
charterSigningController = new CharterSigningController();
28+
console.log("CharterSigningController initialized successfully");
29+
} catch (error) {
30+
console.error("Failed to initialize CharterSigningController:", error);
31+
}
2332
})
2433
.catch((error: any) => {
2534
console.error("Error during initialization:", error);
@@ -46,12 +55,40 @@ const userController = new UserController();
4655
const groupController = new GroupController();
4756
const webhookController = new WebhookController(adapter);
4857
const authController = new AuthController();
58+
let charterSigningController: CharterSigningController | null = null;
4959

5060
// Public routes (no auth required)
5161
app.get("/api/health", (req, res) => {
5262
res.json({ status: "ok", service: "group-charter-manager-api" });
5363
});
5464

65+
// Test endpoint to verify signing service is working
66+
app.get("/api/signing/test", (req, res) => {
67+
try {
68+
if (!charterSigningController) {
69+
return res.json({
70+
status: "error",
71+
error: "Signing service not ready",
72+
signing: "initializing"
73+
});
74+
}
75+
76+
const testResult = charterSigningController.testConnection();
77+
78+
res.json({
79+
status: "ok",
80+
message: "Signing service is working",
81+
signing: testResult ? "ready" : "error"
82+
});
83+
} catch (error) {
84+
res.json({
85+
status: "error",
86+
error: "Signing service test failed",
87+
signing: "error"
88+
});
89+
}
90+
});
91+
5592
// Auth routes (no auth required)
5693
app.get("/api/auth/offer", authController.getOffer.bind(authController));
5794
app.post("/api/auth", authController.login.bind(authController));
@@ -60,6 +97,22 @@ app.get("/api/auth/sessions/:id", authController.sseStream.bind(authController))
6097
// Webhook route (no auth required)
6198
app.post("/api/webhook", webhookController.handleWebhook.bind(webhookController));
6299

100+
// SSE endpoint for signing status (public - no auth required for real-time updates)
101+
app.get("/api/signing/sessions/:sessionId/status", (req, res) => {
102+
if (!charterSigningController) {
103+
return res.status(503).json({ error: "Signing service not ready" });
104+
}
105+
charterSigningController.getSigningSessionStatus(req, res);
106+
});
107+
108+
// Signing callback endpoint (public - called by eID Wallet)
109+
app.post("/api/signing/callback", (req, res) => {
110+
if (!charterSigningController) {
111+
return res.status(503).json({ error: "Signing service not ready" });
112+
}
113+
charterSigningController.handleSignedPayload(req, res);
114+
});
115+
63116
// Apply auth middleware to all routes below
64117
app.use(authMiddleware);
65118

@@ -83,16 +136,30 @@ app.delete("/api/groups/:id", authGuard, groupController.deleteGroup.bind(groupC
83136

84137
// Charter routes
85138
app.put("/api/groups/:id/charter", authGuard, groupController.updateCharter.bind(groupController));
139+
app.get("/api/groups/:id/charter/signing-status", authGuard, groupController.getCharterSigningStatus.bind(groupController));
140+
141+
// Signing routes (protected - require authentication)
142+
app.post("/api/signing/sessions", authGuard, (req, res) => {
143+
if (!charterSigningController) {
144+
return res.status(503).json({ error: "Signing service not ready" });
145+
}
146+
charterSigningController.createSigningSession(req, res);
147+
});
148+
149+
app.get("/api/signing/sessions/:sessionId", authGuard, (req, res) => {
150+
if (!charterSigningController) {
151+
return res.status(503).json({ error: "Signing service not ready" });
152+
}
153+
charterSigningController.getSigningSession(req, res);
154+
});
86155

87156
// Group participant routes
88157
app.get("/api/groups/:groupId", authGuard, groupController.getGroup.bind(groupController));
89158
app.post("/api/groups/:groupId/participants", authGuard, groupController.addParticipants.bind(groupController));
90159
app.delete("/api/groups/:groupId/participants/:userId", authGuard, groupController.removeParticipant.bind(groupController));
91160

92-
// Admin routes
93-
app.post("/api/admin/ensure-cerberus", authGuard, groupController.ensureCerberusInAllGroups.bind(groupController));
94-
95161
// Start server
96162
app.listen(port, () => {
97163
console.log(`Group Charter Manager API running on port ${port}`);
164+
console.log(`Signing service status: ${charterSigningController ? "ready" : "initializing"}`);
98165
});

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,18 @@ export class GroupService {
2626
}
2727

2828
async updateGroup(id: string, groupData: Partial<Group>): Promise<Group | null> {
29+
// If updating the charter, we need to mark all existing signatures as invalid
30+
// since the charter content has changed
31+
if (groupData.charter !== undefined) {
32+
// Get the current group to check if charter is being updated
33+
const currentGroup = await this.getGroupById(id);
34+
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`);
38+
}
39+
}
40+
2941
await this.groupRepository.update(id, groupData);
3042
return await this.getGroupById(id);
3143
}

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

Lines changed: 88 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
Users,
99
Save,
1010
X,
11+
CheckCircle,
1112
} from "lucide-react";
1213
import { Button } from "@/components/ui/button";
1314
import { Card, CardContent } from "@/components/ui/card";
@@ -20,6 +21,8 @@ import { apiClient } from "@/lib/apiClient";
2021
import Link from "next/link";
2122
import ReactMarkdown from "react-markdown";
2223
import remarkGfm from "remark-gfm";
24+
import { CharterSigningStatus } from "@/components/charter-signing-status";
25+
import { CharterSigningInterface } from "@/components/charter-signing-interface";
2326

2427
interface Group {
2528
id: string;
@@ -43,36 +46,60 @@ export default function CharterDetail({
4346
const [isEditing, setIsEditing] = useState(false);
4447
const [editCharter, setEditCharter] = useState("");
4548
const [isSaving, setIsSaving] = useState(false);
49+
const [showSigningInterface, setShowSigningInterface] = useState(false);
50+
const [signingStatus, setSigningStatus] = useState<any>(null);
51+
const [signingStatusLoading, setSigningStatusLoading] = useState(true);
4652

4753
const { id } = use(params);
4854
const { toast } = useToast();
4955
const { user } = useAuth();
5056

57+
const fetchGroup = async () => {
58+
try {
59+
setIsLoading(true);
60+
const response = await apiClient.get(`/api/groups/${id}`);
61+
setGroup(response.data);
62+
setEditCharter(response.data.charter || "");
63+
} catch (error) {
64+
console.error('Failed to fetch group:', error);
65+
toast({
66+
title: "Error",
67+
description: "Failed to load charter",
68+
variant: "destructive",
69+
});
70+
} finally {
71+
setIsLoading(false);
72+
}
73+
};
74+
75+
const fetchSigningStatus = async () => {
76+
if (!group?.charter) return;
77+
78+
try {
79+
setSigningStatusLoading(true);
80+
const response = await apiClient.get(`/api/groups/${id}/charter/signing-status`);
81+
setSigningStatus(response.data);
82+
} catch (error) {
83+
console.error('Failed to fetch signing status:', error);
84+
} finally {
85+
setSigningStatusLoading(false);
86+
}
87+
};
88+
5189
// Fetch group data on component mount
5290
useEffect(() => {
53-
const fetchGroup = async () => {
54-
try {
55-
setIsLoading(true);
56-
const response = await apiClient.get(`/api/groups/${id}`);
57-
setGroup(response.data);
58-
setEditCharter(response.data.charter || "");
59-
} catch (error) {
60-
console.error('Failed to fetch group:', error);
61-
toast({
62-
title: "Error",
63-
description: "Failed to load charter",
64-
variant: "destructive",
65-
});
66-
} finally {
67-
setIsLoading(false);
68-
}
69-
};
70-
7191
if (id) {
7292
fetchGroup();
7393
}
7494
}, [id, toast]);
7595

96+
// Fetch signing status when group changes
97+
useEffect(() => {
98+
if (group?.charter) {
99+
fetchSigningStatus();
100+
}
101+
}, [group?.charter]);
102+
76103
const handleEditStart = () => {
77104
setIsEditing(true);
78105
};
@@ -241,6 +268,23 @@ export default function CharterDetail({
241268
Edit Charter
242269
</Button>
243270
)}
271+
272+
{/* Sign Charter Button (only if current user hasn't signed) */}
273+
{group.charter && signingStatus && !signingStatusLoading && (
274+
(() => {
275+
const currentUser = signingStatus.participants.find((p: any) => p.id === user?.id);
276+
return currentUser && !currentUser.hasSigned;
277+
})() && (
278+
<Button
279+
onClick={() => setShowSigningInterface(true)}
280+
variant="outline"
281+
className="bg-green-600 text-white px-6 py-3 rounded-2xl font-medium hover:bg-green-700 transition-all duration-300 shadow-lg"
282+
>
283+
<CheckCircle className="mr-2" size={18} />
284+
Sign Charter
285+
</Button>
286+
)
287+
)}
244288
</>
245289
)}
246290
</div>
@@ -329,8 +373,34 @@ export default function CharterDetail({
329373
</CardContent>
330374
</Card>
331375
)}
376+
377+
{/* Charter Signing Status */}
378+
{group.charter && (
379+
<CharterSigningStatus
380+
groupId={group.id}
381+
charterContent={group.charter}
382+
/>
383+
)}
332384
</div>
333385
</div>
386+
387+
{/* Signing Interface Modal */}
388+
{showSigningInterface && (
389+
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
390+
<div className="bg-white rounded-lg p-6 max-w-md w-full mx-4">
391+
<CharterSigningInterface
392+
groupId={group.id}
393+
charterData={{ charter: group.charter }}
394+
onSigningComplete={(groupId) => {
395+
setShowSigningInterface(false);
396+
// Refresh the group data to show updated signing status
397+
fetchGroup();
398+
}}
399+
onCancel={() => setShowSigningInterface(false)}
400+
/>
401+
</div>
402+
</div>
403+
)}
334404
</div>
335405
);
336406
}

0 commit comments

Comments
 (0)