mirror of
https://github.com/Discreetly/server.git
synced 2026-01-10 04:57:57 -05:00
Express routes (#15)
* WIP - Starting to refactor routes for prisma * feat(prisma) remove roomId from ClaimCodes model refactor(prisma) seed claim codes using ClaimCodeManager refactor(server) initialize ClaimCodeManager feat(server) add claimcodes endpoint refactor(server) claim code in /join endpoint * chore(claimcodes) updated claim code system * chore(schema): added rate limiting and epoch tracking * feature(endpoints) /join /logclaimcodes refactor(schema) Rooms Claimcodes many-to-many --------- Co-authored-by: AtHeartEngineer <atheartengineer@gmail.com>
This commit is contained in:
14
package-lock.json
generated
14
package-lock.json
generated
@@ -14,7 +14,7 @@
|
||||
"atob": "^2.1.2",
|
||||
"body-parser": "^1.20.2",
|
||||
"cors": "^2.8.5",
|
||||
"discreetly-claimcodes": "^1.0.7",
|
||||
"discreetly-claimcodes": "^1.1.3",
|
||||
"discreetly-interfaces": "^0.1.5",
|
||||
"dotenv": "^16.3.1",
|
||||
"express": "^4.18.2",
|
||||
@@ -1315,9 +1315,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/discreetly-claimcodes": {
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/discreetly-claimcodes/-/discreetly-claimcodes-1.0.7.tgz",
|
||||
"integrity": "sha512-on8ZS7W1WlsY+J9hQmTU2IO18HQ2/pXbztaSCkJaqlpgMDjb2l2Q0MqGQtwshTtv9dfcoIuzeRzuXSk4T69NJg=="
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/discreetly-claimcodes/-/discreetly-claimcodes-1.1.3.tgz",
|
||||
"integrity": "sha512-2QnlhYUPIGLl11XgxIxl6ZKIJZoS2T1ABIHbqjBbec0YYQ2qfWZ7JWH53OZm0mqeO8Dbjon5zK3YNoGiuYJ1Gg=="
|
||||
},
|
||||
"node_modules/discreetly-interfaces": {
|
||||
"version": "0.1.5",
|
||||
@@ -4610,9 +4610,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"discreetly-claimcodes": {
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/discreetly-claimcodes/-/discreetly-claimcodes-1.0.7.tgz",
|
||||
"integrity": "sha512-on8ZS7W1WlsY+J9hQmTU2IO18HQ2/pXbztaSCkJaqlpgMDjb2l2Q0MqGQtwshTtv9dfcoIuzeRzuXSk4T69NJg=="
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/discreetly-claimcodes/-/discreetly-claimcodes-1.1.3.tgz",
|
||||
"integrity": "sha512-2QnlhYUPIGLl11XgxIxl6ZKIJZoS2T1ABIHbqjBbec0YYQ2qfWZ7JWH53OZm0mqeO8Dbjon5zK3YNoGiuYJ1Gg=="
|
||||
},
|
||||
"discreetly-interfaces": {
|
||||
"version": "0.1.5",
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
"atob": "^2.1.2",
|
||||
"body-parser": "^1.20.2",
|
||||
"cors": "^2.8.5",
|
||||
"discreetly-claimcodes": "^1.0.7",
|
||||
"discreetly-claimcodes": "^1.1.3",
|
||||
"discreetly-interfaces": "^0.1.5",
|
||||
"dotenv": "^16.3.1",
|
||||
"express": "^4.18.2",
|
||||
|
||||
@@ -10,29 +10,25 @@ datasource db {
|
||||
url = env("DATABASE_URL")
|
||||
}
|
||||
|
||||
model Groups {
|
||||
id String @id @default(auto()) @map("_id") @db.ObjectId
|
||||
groupId String @unique
|
||||
name String?
|
||||
rooms Rooms[]
|
||||
}
|
||||
|
||||
model Rooms {
|
||||
id String @id @default(auto()) @map("_id") @db.ObjectId
|
||||
roomId String @unique
|
||||
name String
|
||||
groupId String
|
||||
group Groups @relation(fields: [groupId], references: [groupId])
|
||||
identities String[] @default([])
|
||||
messages Messages[]
|
||||
claimCodes ClaimCodes[]
|
||||
id String @id @default(auto()) @map("_id") @db.ObjectId
|
||||
roomId String @unique
|
||||
name String
|
||||
rateLimit Int @default(1000) // epoch length in ms
|
||||
userMessageLimit Int @default(1) // per epoch
|
||||
identities String[] @default([])
|
||||
epochs Epoch[]
|
||||
messages Messages[]
|
||||
claimCodes ClaimCodes[] @relation(fields: [claimCodeIds], references: [id])
|
||||
claimCodeIds String[] @default([]) @db.ObjectId
|
||||
}
|
||||
|
||||
model ClaimCodes {
|
||||
id String @id @default(auto()) @map("_id") @db.ObjectId
|
||||
roomId String @unique
|
||||
claimcode String @unique
|
||||
room Rooms @relation(fields: [roomId], references: [roomId])
|
||||
id String @id @default(auto()) @map("_id") @db.ObjectId
|
||||
claimcode String @unique
|
||||
claimed Boolean @default(false)
|
||||
roomIds String[] @default([]) @db.ObjectId
|
||||
rooms Rooms[] @relation(fields: [roomIds], references: [id])
|
||||
}
|
||||
|
||||
model Messages {
|
||||
@@ -41,5 +37,15 @@ model Messages {
|
||||
timeStamp DateTime @default(now())
|
||||
roomId String
|
||||
room Rooms @relation(fields: [roomId], references: [roomId])
|
||||
identity String
|
||||
proof String
|
||||
epoch Epoch? @relation(fields: [epochId], references: [id])
|
||||
epochId String? @db.ObjectId
|
||||
}
|
||||
|
||||
model Epoch {
|
||||
id String @id @default(auto()) @map("_id") @db.ObjectId
|
||||
epoch Int
|
||||
messages Messages[]
|
||||
rooms Rooms? @relation(fields: [roomsId], references: [id])
|
||||
roomsId String? @db.ObjectId
|
||||
}
|
||||
|
||||
@@ -1,24 +1,53 @@
|
||||
import { PrismaClient } from '@prisma/client'
|
||||
import { genId } from 'discreetly-interfaces';
|
||||
import { generateClaimCodes } from 'discreetly-claimcodes';
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
const idc = genId(0n, "First User").toString();
|
||||
|
||||
|
||||
|
||||
|
||||
const idc = genId(0n, "First User").toString();
|
||||
const idc2 = genId(0n, "Second User").toString();
|
||||
const claimCodes = generateClaimCodes(10);
|
||||
// console.log(claimCodes);
|
||||
let codeArr: any[] = [];
|
||||
claimCodes.forEach(code => {
|
||||
codeArr.push({ claimcode: code.code })
|
||||
})
|
||||
|
||||
const seedData = {
|
||||
where: {
|
||||
roomId: genId(0n, "First Room").toString()
|
||||
},
|
||||
update: {},
|
||||
create: {
|
||||
roomId: genId(0n, "First Room").toString(),
|
||||
name: "First Room",
|
||||
identities: [idc, idc2],
|
||||
claimCodes: {
|
||||
create: codeArr
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
async function main() {
|
||||
const groupOne = await prisma.groups.upsert({
|
||||
where: { groupId: genId(0n, "Discreetly Test").toString() },
|
||||
|
||||
await prisma.rooms.upsert(seedData)
|
||||
await prisma.rooms.upsert({
|
||||
where: {
|
||||
roomId: genId(0n, "Room Two").toString()
|
||||
},
|
||||
update: {},
|
||||
create: {
|
||||
groupId: genId(0n, "Discreetly Test").toString(),
|
||||
name: "Discreetly Test",
|
||||
rooms: {
|
||||
create: {
|
||||
roomId: genId(0n, "First Room").toString(),
|
||||
name: "First Room",
|
||||
identities: [idc]
|
||||
}
|
||||
roomId: genId(0n, "Room Two").toString(),
|
||||
name: "Room Two",
|
||||
identities: [idc],
|
||||
claimCodes: {
|
||||
create: codeArr
|
||||
}
|
||||
}
|
||||
})
|
||||
console.log(seedData);
|
||||
}
|
||||
main();
|
||||
|
||||
247
src/server.ts
247
src/server.ts
@@ -2,16 +2,14 @@ import express from 'express';
|
||||
import { Server } from 'http';
|
||||
import { Server as SocketIOServer, Socket } from 'socket.io';
|
||||
import cors from 'cors';
|
||||
import { createClient } from 'redis';
|
||||
import Prisma from 'prisma';
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
import { MongoClient, ServerApiVersion } from 'mongodb'
|
||||
import { serverConfig, rooms as defaultRooms, rooms } from './config/rooms.js';
|
||||
import type { MessageI, RoomI, RoomGroupI } from 'discreetly-interfaces';
|
||||
import { type MessageI, type RoomI, type RoomGroupI, genId } from 'discreetly-interfaces';
|
||||
import verifyProof from './verifier.js';
|
||||
import { ClaimCodeManager } from 'discreetly-claimcodes';
|
||||
import { generateClaimCodes } from 'discreetly-claimcodes';
|
||||
import type { ClaimCodeStatus } from 'discreetly-claimcodes';
|
||||
import { pp, addIdentityToRoom, createGroup, createRoom, findGroupById, findRoomById } from './utils.js';
|
||||
import { pp } from './utils.js';
|
||||
import { faker } from '@faker-js/faker';
|
||||
|
||||
// HTTP is to get info from the server about configuration, rooms, etc
|
||||
@@ -52,60 +50,6 @@ const prisma = new PrismaClient();
|
||||
console.log("Prisma connected");
|
||||
|
||||
|
||||
// if (!process.env.REDIS_URL) {
|
||||
// console.log('Connecting to redis at localhost');
|
||||
// redisClient = createClient();
|
||||
// TESTING = true;
|
||||
// } else {
|
||||
// console.log('Connecting to redis at: ' + process.env.REDIS_URL);
|
||||
// console.log(process.env.REDIS_TLS_URL);
|
||||
// redisClient = createClient({
|
||||
// url: process.env.REDIS_URL,
|
||||
// socket: {
|
||||
// tls: true,
|
||||
// rejectUnauthorized: false
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
// redisClient.connect().then(() => pp('Redis Connected'));
|
||||
|
||||
|
||||
|
||||
|
||||
// redisClient.get('rooms').then((rooms) => {
|
||||
// rooms = JSON.parse(rooms);
|
||||
// if (rooms) {
|
||||
// loadedRooms = rooms as unknown as RoomGroupI[];
|
||||
// } else {
|
||||
// loadedRooms = defaultRooms;
|
||||
// redisClient.set('rooms', JSON.stringify(loadedRooms));
|
||||
// }
|
||||
// });
|
||||
|
||||
// let ccm: ClaimCodeManager;
|
||||
|
||||
// redisClient.get('ccm').then((cc) => {
|
||||
// TESTGROUPID = BigInt(loadedRooms[0].id);
|
||||
// if (!cc) {
|
||||
// ccm = new ClaimCodeManager();
|
||||
// ccm.generateClaimCodeSet(10, TESTGROUPID, 'TEST');
|
||||
// const ccs = ccm.getClaimCodeSets();
|
||||
// redisClient.set('ccm', JSON.stringify(ccs));
|
||||
// } else {
|
||||
// ccm = new ClaimCodeManager(JSON.parse(cc));
|
||||
|
||||
// if (ccm.getUsedCount(TESTGROUPID).unusedCount < 5) {
|
||||
// ccm.generateClaimCodeSet(10, TESTGROUPID, 'TEST');
|
||||
// const ccs = ccm.getClaimCodeSets();
|
||||
|
||||
// redisClient.set('ccm', JSON.stringify(ccs));
|
||||
// }
|
||||
// }
|
||||
// const ccs = ccm.getClaimCodeSets();
|
||||
// });
|
||||
|
||||
// redisClient.on('error', (err) => pp('Redis Client Error: ' + err, 'error'));
|
||||
|
||||
io.on('connection', (socket: Socket) => {
|
||||
pp('SocketIO: a user connected', 'debug');
|
||||
|
||||
@@ -140,124 +84,104 @@ app.use(
|
||||
})
|
||||
);
|
||||
|
||||
|
||||
app.get(['/', '/api'], (req, res) => {
|
||||
pp('Express: fetching server info');
|
||||
res.json(serverConfig);
|
||||
});
|
||||
|
||||
app.get('/groups', async (req, res) => {
|
||||
const groups = await prisma.groups.findMany({
|
||||
include: {
|
||||
rooms: true
|
||||
app.get('/logclaimcodes', async (req, res) => {
|
||||
pp('Express: fetching claim codes');
|
||||
const claimCodes = await prisma.claimCodes.findMany();
|
||||
res.status(200).json(claimCodes);
|
||||
})
|
||||
|
||||
|
||||
app.get('/identities', async (req, res) => {
|
||||
pp(String("Express: fetching all identities"))
|
||||
const identities = await prisma.rooms.findMany({
|
||||
select: {
|
||||
name: true,
|
||||
roomId: true,
|
||||
identities: true
|
||||
}
|
||||
});
|
||||
res.status(200).json(groups);
|
||||
})
|
||||
res.status(200).json(identities);
|
||||
})
|
||||
|
||||
|
||||
app.get('/api/rooms', async (req, res) => {
|
||||
pp(String("Express: fetching all rooms"))
|
||||
const rooms = await prisma.rooms.findMany();
|
||||
res.status(200).json(rooms);
|
||||
});
|
||||
|
||||
app.get('/api/rooms', (req, res) => {
|
||||
pp('Express: fetching rooms');
|
||||
res.json(loadedRooms);
|
||||
});
|
||||
|
||||
app.get('/api/rooms/:id', (req, res) => {
|
||||
app.get('/api/rooms/:id', async (req, res) => {
|
||||
// TODO This should return the room info for the given room ID
|
||||
pp(String('Express: fetching room info for ' + req.params.id));
|
||||
const room = loadedRooms
|
||||
.flatMap((rooms) => rooms.rooms)
|
||||
.filter((room) => room.id === req.params.id);
|
||||
res.json(room);
|
||||
const room = await prisma.rooms.findUnique({
|
||||
where: {
|
||||
roomId: req.params.id
|
||||
}
|
||||
});
|
||||
res.status(200).json(room);
|
||||
});
|
||||
|
||||
// TODO api endpoint that creates new rooms and generates invite codes for them
|
||||
// app.post('/join', (req, res) => {
|
||||
// const data = req.body;
|
||||
// const { code, idc } = data;
|
||||
// pp('Express[/join]: claiming code:' + code);
|
||||
// const result: ClaimCodeStatus = ccm.claimCode(code);
|
||||
// const groupID = result.groupID;
|
||||
// if (result.status === 'CLAIMED') {
|
||||
// let claimedRooms = [];
|
||||
// let alreadyAddedRooms = [];
|
||||
// loadedRooms.forEach((group) => {
|
||||
// if (group.id == groupID) {
|
||||
// group.rooms.forEach((room: RoomI) => {
|
||||
// let { status, roomGroups } = addIdentityToRoom(BigInt(room.id), BigInt(idc), loadedRooms);
|
||||
// loadedRooms = roomGroups;
|
||||
// redisClient.set('rooms', JSON.stringify(loadedRooms));
|
||||
// if (status) {
|
||||
// claimedRooms.push(room);
|
||||
// } else {
|
||||
// alreadyAddedRooms.push(room);
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
// });
|
||||
// let r = [...claimedRooms, ...alreadyAddedRooms];
|
||||
|
||||
// if (claimedRooms.length > 0) {
|
||||
// res.status(200).json({ status: 'valid', rooms: r });
|
||||
// } else if (alreadyAddedRooms.length > 0) {
|
||||
// res.status(200).json({ status: 'already-added', rooms: r });
|
||||
// } else {
|
||||
// res.status(451).json({ status: 'invalid' });
|
||||
// }
|
||||
|
||||
// // the DB should be updated after we successfully send a response
|
||||
// redisClient.set('ccm', JSON.stringify(ccm.getClaimCodeSets()));
|
||||
// } else {
|
||||
// res.status(451).json({ status: 'invalid' });
|
||||
// }
|
||||
// });
|
||||
|
||||
// TODO we are going to need endpoints that take a password that will be in a .env file to generate new roomGroups, rooms, and claim codes
|
||||
// app.post('/group/add', (req, res) => {
|
||||
// const data = req.body;
|
||||
// const { password, groupName, roomNames, codes } = data;
|
||||
// if (password === process.env.PASSWORD) {
|
||||
// const result = createGroup(groupName, roomNames, loadedRooms);
|
||||
// loadedRooms = result.roomGroup;
|
||||
// redisClient.set('rooms', JSON.stringify(loadedRooms));
|
||||
// if (codes.generate) {
|
||||
// codes.amount = codes.amount || 10;
|
||||
// ccm.generateClaimCodeSet(codes.amount, result.groupId, groupName);
|
||||
// const ccs = ccm.getClaimCodeSets();
|
||||
// redisClient.set('ccm', JSON.stringify(ccs));
|
||||
// }
|
||||
// res.status(201).json({ status: `Created group ${groupName}`, loadedRooms });
|
||||
// }
|
||||
// });
|
||||
|
||||
app.post('/room/add', (req, res) => {
|
||||
app.post('/join', async (req, res) => {
|
||||
const data = req.body;
|
||||
const { password, groupId, roomName } = data;
|
||||
const { code, idc } = data;
|
||||
pp('Express[/join]: claiming code:' + code);
|
||||
const codeStatus = await prisma.claimCodes.findUnique({
|
||||
where: {
|
||||
claimcode: code
|
||||
}
|
||||
})
|
||||
if (codeStatus.claimed === false) {
|
||||
const claimCode = await prisma.claimCodes.update({
|
||||
where: {
|
||||
claimcode: code
|
||||
},
|
||||
data: {
|
||||
claimed: true
|
||||
}
|
||||
})
|
||||
const roomIds = claimCode["roomIds"].map((room) => room);
|
||||
const updatedRooms = await prisma.rooms.updateMany({
|
||||
where: {
|
||||
roomId: {
|
||||
in: roomIds
|
||||
}
|
||||
},
|
||||
data: {
|
||||
identities: {
|
||||
push: idc
|
||||
}
|
||||
}
|
||||
})
|
||||
res.status(200).json(updatedRooms);
|
||||
} else {
|
||||
res.status(400).json({ message: "Claim code already used" })
|
||||
}
|
||||
});
|
||||
// TODO api endpoint that creates new rooms and generates invite codes for them
|
||||
|
||||
|
||||
app.post('/room/add', async (req, res) => {
|
||||
const data = req.body;
|
||||
const { password, roomName } = data;
|
||||
if (password === process.env.PASSWORD) {
|
||||
const roomGroups = createRoom(groupId, roomName, loadedRooms);
|
||||
loadedRooms = roomGroups;
|
||||
redisClient.set('rooms', JSON.stringify(loadedRooms));
|
||||
res.status(201).json({ status: `Created room ${roomName}`, loadedRooms });
|
||||
const newRoom = await prisma.rooms.create({
|
||||
data: {
|
||||
roomId: genId(BigInt(999), roomName).toString(),
|
||||
name: roomName
|
||||
}
|
||||
})
|
||||
res.status(200).json(newRoom)
|
||||
}
|
||||
});
|
||||
|
||||
// app.post('/group/createcode', (req, res) => {
|
||||
// const data = req.body;
|
||||
// let { password, groupId, amount } = data;
|
||||
// if (password === process.env.PASSWORD) {
|
||||
// amount = amount || 10;
|
||||
// console.log(loadedRooms, groupId);
|
||||
// const group = findGroupById(loadedRooms, groupId);
|
||||
// const ccs = ccm.generateClaimCodeSet(amount, groupId, group.name);
|
||||
// redisClient.set('ccm', JSON.stringify(ccs));
|
||||
// res.status(201).json({ stats: `Created ${amount} codes for ${group.name}`, ccm });
|
||||
// }
|
||||
// });
|
||||
|
||||
// app.get('/logclaimcodes', (req, res) => {
|
||||
// pp('-----CLAIMCODES-----', 'debug');
|
||||
// pp(ccm.getClaimCodeSet(TESTGROUPID));
|
||||
// pp('-----ENDOFCODES-----', 'debug');
|
||||
// res.status(200).json({ status: 'ok' });
|
||||
// });
|
||||
|
||||
|
||||
|
||||
app.listen(HTTP_PORT, () => {
|
||||
pp(`Express Http Server is running at port ${HTTP_PORT}`);
|
||||
@@ -267,11 +191,6 @@ socket_server.listen(SOCKET_PORT, () => {
|
||||
pp(`SocketIO Server is running at port ${SOCKET_PORT}`);
|
||||
});
|
||||
|
||||
// Disconnect from redis on exit
|
||||
// process.on('SIGINT', () => {
|
||||
// pp('disconnecting redis');
|
||||
// redisClient.disconnect().then(process.exit());
|
||||
// });
|
||||
|
||||
if (TESTING) {
|
||||
class randomMessagePicker {
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
import { createClient } from 'redis';
|
||||
import type { RoomI, RoomGroupI, ServerI } from 'discreetly-interfaces';
|
||||
import { genId } from 'discreetly-interfaces';
|
||||
|
||||
// const redisClient = createClient();
|
||||
// redisClient.connect();
|
||||
|
||||
export function findRoomById(
|
||||
roomGroups,
|
||||
|
||||
Reference in New Issue
Block a user