Skip to content

Commit e475c7e

Browse files
committed
chore: add migrations
1 parent b6d9b20 commit e475c7e

31 files changed

+882
-665
lines changed

platforms/evoting-api/package.json

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,12 @@
88
"dev": "nodemon src/index.ts",
99
"build": "tsc",
1010
"typeorm": "typeorm-ts-node-commonjs",
11-
"typeorm:generate": "npm run typeorm migration:generate src/database/migrations/migration -- -d src/database/data-source.ts",
12-
"migration:run": "npm run typeorm migration:run -- -d src/database/data-source.ts",
13-
"migration:revert": "npm run typeorm migration:revert -- -d src/database/data-source.ts"
11+
"migration:generate": "typeorm-ts-node-commonjs migration:generate src/database/migrations/migration -d src/database/data-source.ts",
12+
"migration:run": "typeorm-ts-node-commonjs migration:run -d src/database/data-source.ts",
13+
"migration:revert": "typeorm-ts-node-commonjs migration:revert -d src/database/data-source.ts"
1414
},
1515
"dependencies": {
1616
"axios": "^1.6.7",
17-
"better-auth": "^1.3.4",
1817
"cors": "^2.8.5",
1918
"dotenv": "^16.4.5",
2019
"eventsource-polyfill": "^0.9.6",
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
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+
res.writeHead(200, {
19+
"Content-Type": "text/event-stream",
20+
"Cache-Control": "no-cache",
21+
Connection: "keep-alive",
22+
"Access-Control-Allow-Origin": "*",
23+
});
24+
25+
const handler = (data: any) => {
26+
res.write(`data: ${JSON.stringify(data)}\n\n`);
27+
};
28+
29+
this.eventEmitter.on(id, handler);
30+
31+
req.on("close", () => {
32+
this.eventEmitter.off(id, handler);
33+
res.end();
34+
});
35+
36+
req.on("error", (error) => {
37+
console.error("SSE Error:", error);
38+
this.eventEmitter.off(id, handler);
39+
res.end();
40+
});
41+
};
42+
43+
getOffer = async (req: Request, res: Response) => {
44+
const url = new URL(
45+
"/api/auth",
46+
process.env.PUBLIC_EVOTING_BASE_URL,
47+
).toString();
48+
const session = uuidv4();
49+
const offer = `w3ds://auth?redirect=${url}&session=${session}&platform=evoting`;
50+
res.json({ uri: offer });
51+
};
52+
53+
login = async (req: Request, res: Response) => {
54+
try {
55+
const { ename, session } = req.body;
56+
57+
if (!ename) {
58+
return res.status(400).json({ error: "ename is required" });
59+
}
60+
61+
const { user, token } =
62+
await this.userService.findOrCreateUser(ename);
63+
64+
const data = {
65+
user: {
66+
id: user.id,
67+
ename: user.ename,
68+
isVerified: user.isVerified,
69+
isPrivate: user.isPrivate,
70+
},
71+
token,
72+
};
73+
this.eventEmitter.emit(session, data);
74+
res.status(200).send();
75+
} catch (error) {
76+
console.error("Error during login:", error);
77+
res.status(500).json({ error: "Internal server error" });
78+
}
79+
};
80+
}
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
import { Request, Response } from "express";
2+
import { UserService } from "../services/UserService";
3+
4+
export class UserController {
5+
private userService: UserService;
6+
7+
constructor() {
8+
this.userService = new UserService();
9+
}
10+
11+
currentUser = async (req: Request, res: Response) => {
12+
try {
13+
if (!req.user) {
14+
return res.status(401).json({ error: "Authentication required" });
15+
}
16+
17+
const user = await this.userService.getUserById(req.user.id);
18+
if (!user) {
19+
return res.status(404).json({ error: "User not found" });
20+
}
21+
22+
res.json({
23+
id: user.id,
24+
ename: user.ename,
25+
name: user.name,
26+
handle: user.handle,
27+
description: user.description,
28+
avatarUrl: user.avatarUrl,
29+
bannerUrl: user.bannerUrl,
30+
isVerified: user.isVerified,
31+
isPrivate: user.isPrivate,
32+
email: user.email,
33+
emailVerified: user.emailVerified,
34+
createdAt: user.createdAt,
35+
updatedAt: user.updatedAt,
36+
});
37+
} catch (error) {
38+
console.error("Error getting current user:", error);
39+
res.status(500).json({ error: "Internal server error" });
40+
}
41+
};
42+
43+
search = async (req: Request, res: Response) => {
44+
try {
45+
const { q } = req.query;
46+
if (!q || typeof q !== "string") {
47+
return res.status(400).json({ error: "Query parameter 'q' is required" });
48+
}
49+
50+
const users = await this.userService.getAllUsers();
51+
const filteredUsers = users.filter(user =>
52+
user.name?.toLowerCase().includes(q.toLowerCase()) ||
53+
user.handle?.toLowerCase().includes(q.toLowerCase()) ||
54+
user.ename?.toLowerCase().includes(q.toLowerCase())
55+
);
56+
57+
res.json(filteredUsers.map(user => ({
58+
id: user.id,
59+
ename: user.ename,
60+
name: user.name,
61+
handle: user.handle,
62+
avatarUrl: user.avatarUrl,
63+
isVerified: user.isVerified,
64+
})));
65+
} catch (error) {
66+
console.error("Error searching users:", error);
67+
res.status(500).json({ error: "Internal server error" });
68+
}
69+
};
70+
71+
getProfileById = async (req: Request, res: Response) => {
72+
try {
73+
const { id } = req.params;
74+
const user = await this.userService.getUserById(id);
75+
76+
if (!user) {
77+
return res.status(404).json({ error: "User not found" });
78+
}
79+
80+
res.json({
81+
id: user.id,
82+
ename: user.ename,
83+
name: user.name,
84+
handle: user.handle,
85+
description: user.description,
86+
avatarUrl: user.avatarUrl,
87+
bannerUrl: user.bannerUrl,
88+
isVerified: user.isVerified,
89+
isPrivate: user.isPrivate,
90+
createdAt: user.createdAt,
91+
});
92+
} catch (error) {
93+
console.error("Error getting user profile:", error);
94+
res.status(500).json({ error: "Internal server error" });
95+
}
96+
};
97+
98+
updateProfile = async (req: Request, res: Response) => {
99+
try {
100+
if (!req.user) {
101+
return res.status(401).json({ error: "Authentication required" });
102+
}
103+
104+
const updates = req.body;
105+
const user = await this.userService.updateUser(req.user.id, updates);
106+
107+
if (!user) {
108+
return res.status(404).json({ error: "User not found" });
109+
}
110+
111+
res.json({
112+
id: user.id,
113+
ename: user.ename,
114+
name: user.name,
115+
handle: user.handle,
116+
description: user.description,
117+
avatarUrl: user.avatarUrl,
118+
bannerUrl: user.bannerUrl,
119+
isVerified: user.isVerified,
120+
isPrivate: user.isPrivate,
121+
email: user.email,
122+
emailVerified: user.emailVerified,
123+
createdAt: user.createdAt,
124+
updatedAt: user.updatedAt,
125+
});
126+
} catch (error) {
127+
console.error("Error updating user profile:", error);
128+
res.status(500).json({ error: "Internal server error" });
129+
}
130+
};
131+
}
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
import { Request, Response } from "express";
2+
import { Web3Adapter } from "../../../infrastructure/web3-adapter/src/index";
3+
import { AppDataSource } from "../database/data-source";
4+
import { User } from "../database/entities/User";
5+
import { Poll } from "../database/entities/Poll";
6+
import { Vote } from "../database/entities/Vote";
7+
import { MetaEnvelopeMap } from "../database/entities/MetaEnvelopeMap";
8+
9+
export class WebhookController {
10+
private adapter: Web3Adapter;
11+
private userRepository = AppDataSource.getRepository(User);
12+
private pollRepository = AppDataSource.getRepository(Poll);
13+
private voteRepository = AppDataSource.getRepository(Vote);
14+
private mappingRepository = AppDataSource.getRepository(MetaEnvelopeMap);
15+
16+
constructor(adapter: Web3Adapter) {
17+
this.adapter = adapter;
18+
}
19+
20+
handleWebhook = async (req: Request, res: Response) => {
21+
try {
22+
const { data, entityType } = req.body;
23+
24+
if (!data || !entityType) {
25+
return res.status(400).json({ error: "Missing data or entityType" });
26+
}
27+
28+
console.log(`Webhook received for ${entityType}:`, data);
29+
30+
const existingMapping = await this.mappingRepository.findOne({
31+
where: { globalId: data.id, entityType }
32+
});
33+
34+
if (existingMapping) {
35+
console.log(`Entity ${entityType} with global ID ${data.id} already exists locally`);
36+
return res.status(200).json({ message: "Entity already exists" });
37+
}
38+
39+
const localId = await this.createLocalEntity(entityType, data);
40+
41+
if (localId) {
42+
await this.mappingRepository.save({
43+
localId,
44+
globalId: data.id,
45+
entityType,
46+
platform: process.env.PUBLIC_EVOTING_BASE_URL || "evoting"
47+
});
48+
49+
console.log(`Created local ${entityType} with ID ${localId} for global ID ${data.id}`);
50+
}
51+
52+
res.status(200).json({ message: "Webhook processed successfully" });
53+
} catch (error) {
54+
console.error("Error processing webhook:", error);
55+
res.status(500).json({ error: "Internal server error" });
56+
}
57+
};
58+
59+
private async createLocalEntity(entityType: string, data: any): Promise<string | null> {
60+
try {
61+
switch (entityType) {
62+
case "User":
63+
const user = this.userRepository.create({
64+
ename: data.ename,
65+
name: data.name || data.ename,
66+
handle: data.handle || data.ename,
67+
description: data.description,
68+
avatarUrl: data.avatarUrl,
69+
bannerUrl: data.bannerUrl,
70+
isVerified: data.isVerified || false,
71+
isPrivate: data.isPrivate || false,
72+
email: data.email,
73+
emailVerified: data.emailVerified || false,
74+
});
75+
const savedUser = await this.userRepository.save(user);
76+
return savedUser.id;
77+
78+
case "Poll":
79+
const poll = this.pollRepository.create({
80+
title: data.title,
81+
mode: data.mode || "normal",
82+
visibility: data.visibility || "public",
83+
options: data.options || [],
84+
deadline: data.deadline ? new Date(data.deadline) : null,
85+
creatorId: data.creatorId,
86+
});
87+
const savedPoll = await this.pollRepository.save(poll);
88+
return savedPoll.id;
89+
90+
case "Vote":
91+
const vote = this.voteRepository.create({
92+
pollId: data.pollId,
93+
userId: data.userId,
94+
voterId: data.voterId,
95+
data: data.data,
96+
});
97+
const savedVote = await this.voteRepository.save(vote);
98+
return savedVote.id;
99+
100+
default:
101+
console.log(`Unknown entity type: ${entityType}`);
102+
return null;
103+
}
104+
} catch (error) {
105+
console.error(`Error creating local ${entityType}:`, error);
106+
return null;
107+
}
108+
}
109+
}
Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,22 @@
1-
// import "reflect-metadata";
1+
import "reflect-metadata";
22
import path from "node:path";
33
import { config } from "dotenv";
44
import { DataSource, type DataSourceOptions } from "typeorm";
5-
import { Account } from "./entities/Account";
6-
import { Session } from "./entities/Session";
75
import { User } from "./entities/User";
86
import { Verification } from "./entities/Verification";
9-
// import { PostgresSubscriber } from "../web3adapter/watchers/subscriber";
7+
import { Poll } from "./entities/Poll";
8+
import { Vote } from "./entities/Vote";
9+
import { MetaEnvelopeMap } from "./entities/MetaEnvelopeMap";
1010

1111
config({ path: path.resolve(__dirname, "../../../../.env") });
1212

1313
export const dataSourceOptions: DataSourceOptions = {
1414
type: "postgres",
1515
url: process.env.EVOTING_DATABASE_URL,
1616
synchronize: false,
17-
entities: [User, Session, Account, Verification],
18-
migrations: ["src/database/migrations/*.ts"],
19-
// logging: process.env.NODE_ENV === "development",
20-
// subscribers: [PostgresSubscriber],
17+
entities: [User, Verification, Poll, Vote, MetaEnvelopeMap],
18+
migrations: [path.join(__dirname, "migrations", "*.ts")],
19+
logging: process.env.NODE_ENV === "development",
2120
};
2221

23-
console.log(dataSourceOptions);
24-
2522
export const AppDataSource = new DataSource(dataSourceOptions);

0 commit comments

Comments
 (0)