import * as express from "express"; import { ensureAuthenticated } from "./connection"; import { getGist, getUser, handleError, isOwnerOrAdmin, extendExpirationDate, } from "./route-utils"; import AnonymousError from "../../core/AnonymousError"; import { IAnonymizedGistDocument } from "../../core/model/anonymizedGists/anonymizedGists.types"; import Gist from "../../core/Gist"; import AnonymizedGistModel from "../../core/model/anonymizedGists/anonymizedGists.model"; import { RepositoryStatus } from "../../core/types"; const router = express.Router(); // user needs to be connected for all user API router.use(ensureAuthenticated); // refresh gist router.post( "/:gistId/refresh", async (req: express.Request, res: express.Response) => { try { const gist = await getGist(req, res, { nocheck: true }); if (!gist) return; const user = await getUser(req); isOwnerOrAdmin([gist.owner.id], user); await gist.updateIfNeeded({ force: true }); res.json({ status: gist.status }); } catch (error) { handleError(error, res, req); } } ); // extend the expiration of a gist (default +6 months) and bring it back online // if it had expired router.post( "/:gistId/extend", async (req: express.Request, res: express.Response) => { try { const gist = await getGist(req, res, { nocheck: true }); if (!gist) return; if ( gist.status == RepositoryStatus.PREPARING || gist.status == RepositoryStatus.REMOVING || gist.status == RepositoryStatus.EXPIRING || gist.status == RepositoryStatus.REMOVED ) { throw new AnonymousError("invalid_status", { object: gist, httpStatus: 409, }); } const user = await getUser(req); isOwnerOrAdmin([gist.owner.id], user); const newExpiration = extendExpirationDate(gist.model.options.expirationDate); gist.model.options.expirationDate = newExpiration; await AnonymizedGistModel.updateOne( { _id: gist.model._id }, { $set: { "options.expirationDate": newExpiration } } ).exec(); await gist.updateIfNeeded({ force: true }); res.json({ status: gist.status, expirationDate: newExpiration }); } catch (error) { handleError(error, res, req); } } ); // delete a gist router.delete( "/:gistId/", async (req: express.Request, res: express.Response) => { const gist = await getGist(req, res, { nocheck: true }); if (!gist) return; try { if (gist.status == "removed") throw new AnonymousError("is_removed", { object: req.params.gistId, httpStatus: 410, }); const user = await getUser(req); isOwnerOrAdmin([gist.owner.id], user); await gist.remove(); return res.json({ status: gist.status }); } catch (error) { handleError(error, res, req); } } ); // fetch GitHub gist details (used by anonymize form) router.get( "/source/:gistId", async (req: express.Request, res: express.Response) => { const user = await getUser(req); try { const gist = new Gist( new AnonymizedGistModel({ owner: user.id, source: { gistId: req.params.gistId, }, }) ); gist.owner = user; await gist.download(); res.json(gist.toJSON()); } catch (error) { handleError(error, res, req); } } ); // get gist information router.get( "/:gistId/", async (req: express.Request, res: express.Response) => { try { const gist = await getGist(req, res, { nocheck: true }); if (!gist) return; const user = await getUser(req); isOwnerOrAdmin([gist.owner.id], user); res.json(gist.toJSON()); } catch (error) { handleError(error, res, req); } } ); // eslint-disable-next-line @typescript-eslint/no-explicit-any function validateNewGist(gistUpdate: any): void { const validCharacters = /^[0-9a-zA-Z\-_]+$/; if ( !gistUpdate.gistId || !gistUpdate.gistId.match(validCharacters) || gistUpdate.gistId.length < 3 ) { throw new AnonymousError("invalid_gistId", { object: gistUpdate, httpStatus: 400, }); } if (!gistUpdate.source || !gistUpdate.source.gistId) { throw new AnonymousError("gistId_not_specified", { object: gistUpdate, httpStatus: 400, }); } if (!gistUpdate.options) { throw new AnonymousError("options_not_provided", { object: gistUpdate, httpStatus: 400, }); } if (!Array.isArray(gistUpdate.terms)) { throw new AnonymousError("invalid_terms_format", { object: gistUpdate, httpStatus: 400, }); } } function updateGistModel( model: IAnonymizedGistDocument, // eslint-disable-next-line @typescript-eslint/no-explicit-any gistUpdate: any ) { model.options = { terms: gistUpdate.terms, expirationMode: gistUpdate.options.expirationMode, expirationDate: gistUpdate.options.expirationDate ? new Date(gistUpdate.options.expirationDate) : undefined, update: gistUpdate.options.update, image: gistUpdate.options.image, link: gistUpdate.options.link, body: gistUpdate.options.body, title: gistUpdate.options.title, username: gistUpdate.options.username, origin: gistUpdate.options.origin, content: gistUpdate.options.content, comments: gistUpdate.options.comments, date: gistUpdate.options.date, }; } // update a gist router.post( "/:gistId/", async (req: express.Request, res: express.Response) => { try { const gist = await getGist(req, res, { nocheck: true }); if (!gist) return; const user = await getUser(req); isOwnerOrAdmin([gist.owner.id], user); const gistUpdate = req.body; validateNewGist(gistUpdate); gist.model.anonymizeDate = new Date(); updateGistModel(gist.model, gistUpdate); gist.model.conference = gistUpdate.conference; await AnonymizedGistModel.updateOne( { _id: gist.model._id }, { $set: { options: gist.model.options, conference: gist.model.conference, anonymizeDate: gist.model.anonymizeDate, }, } ).exec(); await gist.updateStatus(RepositoryStatus.PREPARING); await gist.updateIfNeeded({ force: true }); res.json(gist.toJSON()); } catch (error) { return handleError(error, res, req); } } ); // add gist router.post("/", async (req: express.Request, res: express.Response) => { const user = await getUser(req); const gistUpdate = req.body; try { validateNewGist(gistUpdate); const gist = new Gist( new AnonymizedGistModel({ owner: user.id, options: gistUpdate.options, }) ); gist.model.gistId = gistUpdate.gistId; gist.model.anonymizeDate = new Date(); gist.model.owner = user.id; updateGistModel(gist.model, gistUpdate); gist.source.accessToken = user.accessToken; gist.source.gistId = gistUpdate.source.gistId; gist.model.conference = gistUpdate.conference; await gist.model.save(); await gist.anonymize(); res.send(gist.toJSON()); } catch (error) { if ( error instanceof Error && error.message.indexOf(" duplicate key") > -1 ) { return handleError( new AnonymousError("gistId_already_used", { httpStatus: 400, cause: error, object: gistUpdate, }), res, req ); } return handleError(error, res, req); } }); export default router;