import crypto from 'crypto' import bodyParser from 'body-parser' import cookieParser from 'cookie-parser' import express, { Request, Response, Router } from 'express' import { createStatusRouter } from './middlewares' import { StatusCodes } from 'http-status-codes' import { createDatabase, createProblem, createSolution, getProblem, getProblems, getSolution, getSolutions, getUser, getUsers, getVisibleSolutions, updateSolution, } from './db/database' import { Id, isAdministrator, isStudent, Opaque, Problem, ProblemId, Solution as SolutionModel, SolutionId, UserId, } from '../shared/model' import { initialDatabaseValue } from './db/example-data' import { validateObjectKeys } from '../shared/utils' export async function createApiRouter() { type SessionId = Opaque const sessionStore: Record = {} const sessions = { createSession(userId: UserId) { const sid = crypto.randomBytes(10).toString('hex') as SessionId sessionStore[sid] = userId return sid }, getUserForSession(sid: SessionId) { return sessionStore[sid] ?? null }, } const db = createDatabase(process.env.DATABASE_PATH ?? './db.local.json', initialDatabaseValue) async function getRequestUser(req: Request) { const userId = sessions.getUserForSession(req.cookies.sid) if (!userId) { return null } console.log(userId) return await getUser(db, userId) } const r: Router = express.Router() r.use(bodyParser.json()) r.use(cookieParser()) r.use('/api/status', createStatusRouter()) // r.use('/api/ping', new PingRouter()) r.get('/api/current-user', async (req, res) => { res.json(await getRequestUser(req)) }) r.post('/api/login', async (req, res) => { const { id } = req.body const user = await getUser(db, id) if (!user) { res.sendStatus(StatusCodes.FORBIDDEN) return } res.cookie('sid', sessions.createSession(id), { maxAge: 1000 * 60 * 60 * 24 * 7 }) res.json({ status: 'ok' }) }) r.post('/api/logout', (req, res) => { res.cookie('sid', '', { expires: new Date() }) res.json({ status: 'ok' }) }) r.get('/api/users', async (req, res) => { const requestUser = await getRequestUser(req) if (!requestUser || !isAdministrator(requestUser.role)) { res.sendStatus(StatusCodes.UNAUTHORIZED) return } const users = await getUsers(db) res.json(users) }) r.get('/api/problems', async (req, res) => { type ProblemWithSolutionsCount = Problem & { solutionsCount?: number } const problems: ProblemWithSolutionsCount[] = await getProblems(db) const solutions = await getSolutions(db) const solutionCounts: Record = {} for (const s of solutions) { solutionCounts[s.forProblem] ||= 0 solutionCounts[s.forProblem]++ } for (const p of problems) { p.solutionsCount = solutionCounts[p.id] || 0 } res.json(problems) }) r.get('/api/problem/:id', async (req, res) => { res.json(await getProblem(db, req.params.id)) }) r.post('/api/problem', async (req, res) => { const user = await getRequestUser(req) if (!user) { res.sendStatus(StatusCodes.UNAUTHORIZED) return } if (user.role !== 'admin' && user.role !== 'moderator') { res.sendStatus(StatusCodes.UNAUTHORIZED) return } const id = await createProblem(db, { content: req.body.content, createdBy: user.id, }) res.json(id) }) r.get('/api/solutions', async (req, res) => { let queryUser = (req.query.user ?? null) as UserId | null let queryProblem = (req.query.problem ?? null) as ProblemId | null const requestUser = await getRequestUser(req) let solutions = await getSolutions(db) // se l'utente non è loggato o se non è un amministratore allora mostra solo le soluzioni "visibili" if (!requestUser || !isAdministrator(requestUser.role)) { solutions = solutions.filter(s => s.visible || (requestUser && s.sentBy === requestUser.id)) } // filtra rispetto agli utenti if (queryUser !== null) { solutions = solutions.filter(s => s.sentBy === queryUser) } // filtra rispetto ai problemi if (queryProblem !== null) { solutions = solutions.filter(s => s.forProblem === queryProblem) } res.json(solutions) }) r.get('/api/solution/:id', async (req, res) => { const user = await getRequestUser(req) const solution = await getSolution(db, req.params.id as SolutionId) // la soluzione deve esistere if (solution === null) { res.sendStatus(StatusCodes.NOT_FOUND) return } // uno studente può vedere solo soluzioni visibili o proprie soluzione if (!solution.visible && user && isStudent(user.role) && solution.sentBy !== user.id) { res.sendStatus(StatusCodes.UNAUTHORIZED) return } res.json(solution) }) r.post('/api/solution', async (req, res) => { const user = await getRequestUser(req) if (!user) { res.sendStatus(StatusCodes.UNAUTHORIZED) return } await createSolution(db, { sentBy: user.id, forProblem: req.body.forProblem, content: req.body.content, }) res.send({ status: 'ok' }) }) r.patch('/api/solution/:id', async (req, res) => { const id = req.params.id as SolutionId const user = await getRequestUser(req) // l'utente deve essere loggato if (!user) { res.sendStatus(StatusCodes.UNAUTHORIZED) return } const solution = await getSolution(db, id) // la soluzione deve esistere if (solution === null) { res.sendStatus(404) return } // uno studente non può modificare una soluzione di un altro utente if (user.role === 'student' && solution.sentBy !== user.id) { res.status(StatusCodes.UNAUTHORIZED) res.send(`a student can only modify its own solution`) return } // uno studente può modificare solo il campo "content" if (user.role === 'student' && !validateObjectKeys(req.body, ['content'])) { res.status(StatusCodes.UNAUTHORIZED) res.send(`a student can only modify the field "content"`) return } // un moderatore può modificare solo i campi "content", "visible", "status" if ( user.role === 'moderator' && !validateObjectKeys(req.body, ['content', 'status', 'visible']) ) { res.status(StatusCodes.UNAUTHORIZED) res.send(`a moderator can only modify the fields "content", "visible", "status"`) return } await updateSolution(db, id, req.body) res.json({ status: 'ok' }) }) r.get('/api/user/:id', async (req, res) => { const user = await getRequestUser(req) // intanto l'utente deve essere loggato if (!user) { res.sendStatus(StatusCodes.UNAUTHORIZED) return } // solo gli amministratori possono usare questa route if (!isAdministrator(user.role)) { res.sendStatus(StatusCodes.UNAUTHORIZED) return } const requestedUser = await getUser(db, req.params.id) // l'utente richiesto magari deve esistere if (!requestedUser) { res.sendStatus(404) return } res.json(requestedUser) }) return r }