Skip to content

Commit f34f161

Browse files
committed
feat: charter CRUD
1 parent 11c3965 commit f34f161

File tree

31 files changed

+4246
-1425
lines changed

31 files changed

+4246
-1425
lines changed

platforms/blabsy-w3ds-auth-api/src/web3adapter/mappings/chat.mapping.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
"localToUniversalMap": {
77
"name": "name",
88
"type": "type",
9+
"admins": "user(admins),admins",
910
"participants": "user(participants[]),participantIds",
1011
"lastMessage": "lastMessageId",
1112
"createdAt": "__date(__calc(createdAt._seconds * 1000)),createdAt",
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import { Request, Response } from "express";
2+
import { v4 as uuidv4 } from "uuid";
3+
import { UserService } from "../services/UserService";
4+
import { EventEmitter } from "events";
5+
6+
export class AuthController {
7+
private userService: UserService;
8+
private eventEmitter: EventEmitter;
9+
10+
constructor() {
11+
this.userService = new UserService();
12+
this.eventEmitter = new EventEmitter();
13+
}
14+
15+
sseStream = async (req: Request, res: Response) => {
16+
const { id } = req.params;
17+
18+
// Set headers for SSE
19+
res.writeHead(200, {
20+
"Content-Type": "text/event-stream",
21+
"Cache-Control": "no-cache",
22+
Connection: "keep-alive",
23+
"Access-Control-Allow-Origin": "*",
24+
});
25+
26+
const handler = (data: any) => {
27+
res.write(`data: ${JSON.stringify(data)}\n\n`);
28+
};
29+
30+
this.eventEmitter.on(id, handler);
31+
32+
// Handle client disconnect
33+
req.on("close", () => {
34+
this.eventEmitter.off(id, handler);
35+
res.end();
36+
});
37+
38+
req.on("error", (error) => {
39+
console.error("SSE Error:", error);
40+
this.eventEmitter.off(id, handler);
41+
res.end();
42+
});
43+
};
44+
45+
getOffer = async (req: Request, res: Response) => {
46+
const url = new URL(
47+
"/api/auth",
48+
process.env.PUBLIC_GROUP_CHARTER_BASE_URL,
49+
).toString();
50+
const session = uuidv4();
51+
const offer = `w3ds://auth?redirect=${url}&session=${session}&platform=GroupCharter`;
52+
res.json({ uri: offer });
53+
};
54+
55+
login = async (req: Request, res: Response) => {
56+
try {
57+
const { ename, session } = req.body;
58+
59+
if (!ename) {
60+
return res.status(400).json({ error: "ename is required" });
61+
}
62+
63+
const { user, token } =
64+
await this.userService.findOrCreateUser(ename);
65+
66+
const data = {
67+
user: {
68+
id: user.id,
69+
ename: user.ename,
70+
isVerified: user.isVerified,
71+
isPrivate: user.isPrivate,
72+
},
73+
token,
74+
};
75+
this.eventEmitter.emit(session, data);
76+
res.status(200).send();
77+
} catch (error) {
78+
console.error("Error during login:", error);
79+
res.status(500).json({ error: "Internal server error" });
80+
}
81+
};
82+
}

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

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,26 @@ export class GroupController {
5454
async updateGroup(req: Request, res: Response) {
5555
try {
5656
const { id } = req.params;
57-
const groupData = req.body;
57+
const userId = (req as any).user?.id;
58+
59+
if (!userId) {
60+
return res.status(401).json({ error: "Unauthorized" });
61+
}
62+
63+
const group = await this.groupService.getGroupById(id);
64+
if (!group) {
65+
return res.status(404).json({ error: "Group not found" });
66+
}
67+
68+
// Check if user is owner or admin
69+
const isOwner = group.owner === userId;
70+
const isAdmin = group.admins?.includes(userId);
5871

72+
if (!isOwner && !isAdmin) {
73+
return res.status(403).json({ error: "Access denied" });
74+
}
75+
76+
const groupData = req.body;
5977
const updatedGroup = await this.groupService.updateGroup(id, groupData);
6078
if (!updatedGroup) {
6179
return res.status(404).json({ error: "Group not found" });
@@ -68,6 +86,40 @@ export class GroupController {
6886
}
6987
}
7088

89+
async updateCharter(req: Request, res: Response) {
90+
try {
91+
const { id } = req.params;
92+
const userId = (req as any).user?.id;
93+
94+
if (!userId) {
95+
return res.status(401).json({ error: "Unauthorized" });
96+
}
97+
98+
const group = await this.groupService.getGroupById(id);
99+
if (!group) {
100+
return res.status(404).json({ error: "Group not found" });
101+
}
102+
103+
// Check if user is a participant in the group
104+
const isParticipant = group.participants?.some(p => p.id === userId);
105+
if (!isParticipant) {
106+
return res.status(403).json({ error: "Access denied - you must be a participant in this group" });
107+
}
108+
109+
const { charter } = req.body;
110+
const updatedGroup = await this.groupService.updateGroup(id, { charter });
111+
112+
if (!updatedGroup) {
113+
return res.status(404).json({ error: "Group not found" });
114+
}
115+
116+
res.json(updatedGroup);
117+
} catch (error) {
118+
console.error("Error updating charter:", error);
119+
res.status(500).json({ error: "Internal server error" });
120+
}
121+
}
122+
71123
async deleteGroup(req: Request, res: Response) {
72124
try {
73125
const { id } = req.params;

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

Lines changed: 47 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,34 +19,54 @@ export class WebhookController {
1919

2020
handleWebhook = async (req: Request, res: Response) => {
2121
try {
22-
console.log("here")
22+
console.log("Webhook received:", {
23+
schemaId: req.body.schemaId,
24+
globalId: req.body.id,
25+
tableName: req.body.data?.tableName
26+
});
27+
2328
if (process.env.ANCHR_URL) {
2429
axios.post(
2530
new URL("group-charter-manager", process.env.ANCHR_URL).toString(),
2631
req.body
2732
);
2833
}
34+
2935
const schemaId = req.body.schemaId;
3036
const globalId = req.body.id;
3137
const mapping = Object.values(this.adapter.mapping).find(
3238
(m) => m.schemaId === schemaId
3339
);
34-
console.log(mapping, this.adapter.mapping, schemaId)
40+
41+
console.log("Found mapping:", mapping?.tableName);
42+
console.log("Available mappings:", Object.keys(this.adapter.mapping));
43+
44+
if (!mapping) {
45+
console.error("No mapping found for schemaId:", schemaId);
46+
throw new Error("No mapping found");
47+
}
48+
49+
// Check if this globalId is already locked (being processed)
50+
if (this.adapter.lockedIds.includes(globalId)) {
51+
console.log("GlobalId already locked, skipping:", globalId);
52+
return res.status(200).send();
53+
}
54+
3555
this.adapter.addToLockedIds(globalId);
3656

37-
if (!mapping) throw new Error();
3857
const local = await this.adapter.fromGlobal({
3958
data: req.body.data,
4059
mapping,
4160
});
4261

4362
let localId = await this.adapter.mappingDb.getLocalId(globalId);
63+
console.log("Local ID for globalId", globalId, ":", localId);
4464

4565
if (mapping.tableName === "users") {
4666
if (localId) {
4767
const user = await this.userService.getUserById(localId);
4868
if (!user) throw new Error();
49-
69+
5070
for (const key of Object.keys(local.data)) {
5171
// @ts-ignore
5272
user[key] = local.data[key];
@@ -70,7 +90,7 @@ export class WebhookController {
7090
isVerified: req.body.data.isVerified,
7191
isPrivate: req.body.data.isPrivate,
7292
});
73-
93+
7494
await this.adapter.mappingDb.storeMapping({
7595
localId: user.id,
7696
globalId: req.body.id,
@@ -79,57 +99,74 @@ export class WebhookController {
7999
this.adapter.addToLockedIds(globalId);
80100
}
81101
} else if (mapping.tableName === "groups") {
102+
console.log("Processing group with data:", local.data);
103+
82104
let participants: User[] = [];
83105
if (
84106
local.data.participants &&
85107
Array.isArray(local.data.participants)
86108
) {
87-
console.log(local);
109+
console.log("Processing participants:", local.data.participants);
88110
const participantPromises = local.data.participants.map(
89111
async (ref: string) => {
90112
if (ref && typeof ref === "string") {
91113
const userId = ref.split("(")[1].split(")")[0];
114+
console.log("Extracted userId:", userId);
92115
return await this.userService.getUserById(userId);
93116
}
94117
return null;
95118
}
96119
);
120+
97121
participants = (
98122
await Promise.all(participantPromises)
99123
).filter((user): user is User => user !== null);
124+
console.log("Found participants:", participants.length);
100125
}
101126

127+
let admins = local?.data?.admins as string[] ?? []
128+
admins = admins.map((a) => a.includes("(") ? a.split("(")[1].split(")")[0]: a)
129+
102130
if (localId) {
131+
console.log("Updating existing group with localId:", localId);
103132
const group = await this.groupService.getGroupById(localId);
104-
if (!group) return res.status(500).send();
133+
if (!group) {
134+
console.error("Group not found for localId:", localId);
135+
return res.status(500).send();
136+
}
105137

106138
group.name = local.data.name as string;
107139
group.description = local.data.description as string;
108140
group.owner = local.data.owner as string;
109-
group.admins = local.data.admins as string[];
141+
group.admins = admins;
110142
group.participants = participants;
111143

112144
this.adapter.addToLockedIds(localId);
113145
await this.groupService.groupRepository.save(group);
146+
console.log("Updated group:", group.id);
114147
} else {
148+
console.log("Creating new group");
115149
const group = await this.groupService.createGroup({
116150
name: local.data.name as string,
117151
description: local.data.description as string,
118152
owner: local.data.owner as string,
119-
admins: local.data.admins as string[],
153+
admins,
120154
participants: participants,
121155
});
122156

157+
console.log("Created group with ID:", group.id);
158+
console.log(group)
123159
this.adapter.addToLockedIds(group.id);
124160
await this.adapter.mappingDb.storeMapping({
125161
localId: group.id,
126162
globalId: req.body.id,
127163
});
164+
console.log("Stored mapping for group:", group.id, "->", req.body.id);
128165
}
129166
}
130167
res.status(200).send();
131168
} catch (e) {
132-
console.error(e);
169+
console.error("Webhook error:", e);
133170
res.status(500).send();
134171
}
135172
};

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ export const AppDataSource = new DataSource({
1313
url: process.env.GROUP_CHARTER_DATABASE_URL,
1414
synchronize: false,
1515
logging: process.env.NODE_ENV === "development",
16-
entities: [User, Group, ],
16+
entities: [User, Group ],
1717
migrations: ["src/database/migrations/*.ts"],
1818
subscribers: [PostgresSubscriber],
1919
});
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import {
2+
Entity,
3+
PrimaryGeneratedColumn,
4+
Column,
5+
CreateDateColumn,
6+
UpdateDateColumn,
7+
ManyToOne,
8+
JoinColumn,
9+
} from "typeorm";
10+
import { Group } from "./Group";
11+
import { User } from "./User";
12+
13+
@Entity("charters")
14+
export class Charter {
15+
@PrimaryGeneratedColumn("uuid")
16+
id!: string;
17+
18+
@Column({ nullable: true })
19+
name!: string;
20+
21+
@Column({ type: "text", nullable: true })
22+
content!: string; // This will store markdown content
23+
24+
@Column({ default: false })
25+
isActive!: boolean;
26+
27+
@Column({ nullable: true })
28+
groupId!: string;
29+
30+
@ManyToOne(() => Group, { nullable: true })
31+
@JoinColumn({ name: "groupId" })
32+
group!: Group;
33+
34+
@Column({ nullable: true })
35+
ownerId!: string;
36+
37+
@ManyToOne(() => User)
38+
@JoinColumn({ name: "ownerId" })
39+
owner!: User;
40+
41+
@CreateDateColumn()
42+
createdAt!: Date;
43+
44+
@UpdateDateColumn()
45+
updatedAt!: Date;
46+
}

platforms/group-charter-manager-api/src/database/entities/Group.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import {
77
ManyToMany,
88
JoinTable,
99
} from "typeorm";
10-
import { User } from "./User";
1110

1211
@Entity()
1312
export class Group {
@@ -26,13 +25,16 @@ export class Group {
2625
@Column("simple-array", { nullable: true })
2726
admins!: string[];
2827

29-
@ManyToMany(() => User)
28+
@Column({ type: "text", nullable: true })
29+
charter!: string; // Markdown content for the group charter
30+
31+
@ManyToMany("User")
3032
@JoinTable({
3133
name: "group_participants",
3234
joinColumn: { name: "group_id", referencedColumnName: "id" },
3335
inverseJoinColumn: { name: "user_id", referencedColumnName: "id" }
3436
})
35-
participants!: User[];
37+
participants!: any[];
3638

3739
@CreateDateColumn()
3840
createdAt!: Date;

0 commit comments

Comments
 (0)