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:
Tanner
2023-07-27 16:14:26 -05:00
committed by GitHub
parent dd116d712f
commit 7e4f8d0fdb
6 changed files with 157 additions and 206 deletions

14
package-lock.json generated
View File

@@ -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",

View File

@@ -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",

View File

@@ -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
}

View File

@@ -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();

View File

@@ -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 {

View File

@@ -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,