mirror of
https://github.com/tdurieux/anonymous_github.git
synced 2026-07-01 03:25:30 +02:00
feat: add support for pull requests (#156)
This commit is contained in:
@@ -1,3 +1,5 @@
|
||||
import pullRequestPrivate from "./pullRequest-private";
|
||||
import pullRequestPublic from "./pullRequest-public";
|
||||
import repositoryPrivate from "./repository-private";
|
||||
import repositoryPublic from "./repository-public";
|
||||
import conference from "./conference";
|
||||
@@ -8,6 +10,8 @@ import option from "./option";
|
||||
import admin from "./admin";
|
||||
|
||||
export default {
|
||||
pullRequestPrivate,
|
||||
pullRequestPublic,
|
||||
repositoryPrivate,
|
||||
repositoryPublic,
|
||||
file,
|
||||
|
||||
@@ -0,0 +1,245 @@
|
||||
import * as express from "express";
|
||||
import { ensureAuthenticated } from "./connection";
|
||||
|
||||
import {
|
||||
getPullRequest,
|
||||
getUser,
|
||||
handleError,
|
||||
isOwnerOrAdmin,
|
||||
} from "./route-utils";
|
||||
import AnonymousError from "../AnonymousError";
|
||||
import { IAnonymizedPullRequestDocument } from "../database/anonymizedPullRequests/anonymizedPullRequests.types";
|
||||
import PullRequest from "../PullRequest";
|
||||
import AnonymizedPullRequestModel from "../database/anonymizedPullRequests/anonymizedPullRequests.model";
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
// user needs to be connected for all user API
|
||||
router.use(ensureAuthenticated);
|
||||
|
||||
// refresh pullRequest
|
||||
router.post(
|
||||
"/:pullRequestId/refresh",
|
||||
async (req: express.Request, res: express.Response) => {
|
||||
try {
|
||||
const pullRequest = await getPullRequest(req, res, { nocheck: true });
|
||||
if (!pullRequest) return;
|
||||
|
||||
if (
|
||||
pullRequest.status == "preparing" ||
|
||||
pullRequest.status == "removing" ||
|
||||
pullRequest.status == "expiring"
|
||||
)
|
||||
return;
|
||||
|
||||
const user = await getUser(req);
|
||||
isOwnerOrAdmin([pullRequest.owner.id], user);
|
||||
await pullRequest.anonymize()
|
||||
res.json({ status: pullRequest.status });
|
||||
} catch (error) {
|
||||
handleError(error, res, req);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// delete a pullRequest
|
||||
router.delete(
|
||||
"/:pullRequestId/",
|
||||
async (req: express.Request, res: express.Response) => {
|
||||
const pullRequest = await getPullRequest(req, res, { nocheck: true });
|
||||
if (!pullRequest) return;
|
||||
try {
|
||||
if (pullRequest.status == "removed")
|
||||
throw new AnonymousError("is_removed", {
|
||||
object: req.params.pullRequestId,
|
||||
httpStatus: 410,
|
||||
});
|
||||
const user = await getUser(req);
|
||||
isOwnerOrAdmin([pullRequest.owner.id], user);
|
||||
await pullRequest.remove();
|
||||
return res.json({ status: pullRequest.status });
|
||||
} catch (error) {
|
||||
handleError(error, res, req);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
router.get(
|
||||
"/:owner/:repository/:pullRequestId",
|
||||
async (req: express.Request, res: express.Response) => {
|
||||
const user = await getUser(req);
|
||||
try {
|
||||
const pullRequest = new PullRequest(
|
||||
new AnonymizedPullRequestModel({
|
||||
owner: user.id,
|
||||
source: {
|
||||
pullRequestId: parseInt(req.params.pullRequestId),
|
||||
repositoryFullName: `${req.params.owner}/${req.params.repository}`,
|
||||
},
|
||||
})
|
||||
);
|
||||
await pullRequest.download();
|
||||
res.json(pullRequest.toJSON());
|
||||
} catch (error) {
|
||||
handleError(error, res, req);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// get pullRequest information
|
||||
router.get(
|
||||
"/:pullRequestId/",
|
||||
async (req: express.Request, res: express.Response) => {
|
||||
try {
|
||||
const pullRequest = await getPullRequest(req, res, { nocheck: true });
|
||||
if (!pullRequest) return;
|
||||
|
||||
const user = await getUser(req);
|
||||
isOwnerOrAdmin([pullRequest.owner.id], user);
|
||||
res.json(pullRequest.toJSON());
|
||||
} catch (error) {
|
||||
handleError(error, res, req);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
function validateNewPullRequest(pullRequestUpdate): void {
|
||||
const validCharacters = /^[0-9a-zA-Z\-\_]+$/;
|
||||
if (
|
||||
!pullRequestUpdate.pullRequestId.match(validCharacters) ||
|
||||
pullRequestUpdate.pullRequestId.length < 3
|
||||
) {
|
||||
throw new AnonymousError("invalid_pullRequestId", {
|
||||
object: pullRequestUpdate,
|
||||
httpStatus: 400,
|
||||
});
|
||||
}
|
||||
if (!pullRequestUpdate.source.repositoryFullName) {
|
||||
throw new AnonymousError("repository_not_specified", {
|
||||
object: pullRequestUpdate,
|
||||
httpStatus: 400,
|
||||
});
|
||||
}
|
||||
if (!pullRequestUpdate.source.pullRequestId) {
|
||||
throw new AnonymousError("pullRequestId_not_specified", {
|
||||
object: pullRequestUpdate,
|
||||
httpStatus: 400,
|
||||
});
|
||||
}
|
||||
if (
|
||||
parseInt(pullRequestUpdate.source.pullRequestId) !=
|
||||
pullRequestUpdate.source.pullRequestId
|
||||
) {
|
||||
throw new AnonymousError("pullRequestId_is_not_a_number", {
|
||||
object: pullRequestUpdate,
|
||||
httpStatus: 400,
|
||||
});
|
||||
}
|
||||
if (!pullRequestUpdate.options) {
|
||||
throw new AnonymousError("options_not_provided", {
|
||||
object: pullRequestUpdate,
|
||||
httpStatus: 400,
|
||||
});
|
||||
}
|
||||
if (!Array.isArray(pullRequestUpdate.terms)) {
|
||||
throw new AnonymousError("invalid_terms_format", {
|
||||
object: pullRequestUpdate,
|
||||
httpStatus: 400,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function updatePullRequestModel(
|
||||
model: IAnonymizedPullRequestDocument,
|
||||
pullRequestUpdate: any
|
||||
) {
|
||||
model.options = {
|
||||
terms: pullRequestUpdate.terms,
|
||||
expirationMode: pullRequestUpdate.options.expirationMode,
|
||||
expirationDate: pullRequestUpdate.options.expirationDate
|
||||
? new Date(pullRequestUpdate.options.expirationDate)
|
||||
: null,
|
||||
update: pullRequestUpdate.options.update,
|
||||
image: pullRequestUpdate.options.image,
|
||||
link: pullRequestUpdate.options.link,
|
||||
body: pullRequestUpdate.options.body,
|
||||
title: pullRequestUpdate.options.title,
|
||||
username: pullRequestUpdate.options.username,
|
||||
origin: pullRequestUpdate.options.origin,
|
||||
diff: pullRequestUpdate.options.diff,
|
||||
comments: pullRequestUpdate.options.comments,
|
||||
date: pullRequestUpdate.options.date,
|
||||
};
|
||||
}
|
||||
|
||||
// update a pullRequest
|
||||
router.post(
|
||||
"/:pullRequestId/",
|
||||
async (req: express.Request, res: express.Response) => {
|
||||
try {
|
||||
const pullRequest = await getPullRequest(req, res, { nocheck: true });
|
||||
if (!pullRequest) return;
|
||||
const user = await getUser(req);
|
||||
|
||||
isOwnerOrAdmin([pullRequest.owner.id], user);
|
||||
const pullRequestUpdate = req.body;
|
||||
validateNewPullRequest(pullRequestUpdate);
|
||||
pullRequest.model.anonymizeDate = new Date();
|
||||
|
||||
updatePullRequestModel(pullRequest.model, pullRequestUpdate);
|
||||
// TODO handle conference
|
||||
pullRequest.model.conference = pullRequestUpdate.conference;
|
||||
await pullRequest.updateIfNeeded({ force: true });
|
||||
res.json(pullRequest.toJSON());
|
||||
} catch (error) {
|
||||
return handleError(error, res, req);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// add pullRequest
|
||||
router.post("/", async (req: express.Request, res: express.Response) => {
|
||||
const user = await getUser(req);
|
||||
const pullRequestUpdate = req.body;
|
||||
|
||||
try {
|
||||
validateNewPullRequest(pullRequestUpdate);
|
||||
|
||||
const pullRequest = new PullRequest(
|
||||
new AnonymizedPullRequestModel({
|
||||
owner: user.id,
|
||||
options: pullRequestUpdate.options,
|
||||
})
|
||||
);
|
||||
|
||||
pullRequest.model.pullRequestId = pullRequestUpdate.pullRequestId;
|
||||
pullRequest.model.anonymizeDate = new Date();
|
||||
pullRequest.model.owner = user.id;
|
||||
|
||||
updatePullRequestModel(pullRequest.model, pullRequestUpdate);
|
||||
pullRequest.source.accessToken = user.accessToken;
|
||||
pullRequest.source.pullRequestId = pullRequestUpdate.source.pullRequestId;
|
||||
pullRequest.source.repositoryFullName =
|
||||
pullRequestUpdate.source.repositoryFullName;
|
||||
|
||||
pullRequest.conference = pullRequestUpdate.conference;
|
||||
|
||||
await pullRequest.anonymize()
|
||||
res.send(pullRequest.toJSON());
|
||||
} catch (error) {
|
||||
if (error.message?.indexOf(" duplicate key") > -1) {
|
||||
return handleError(
|
||||
new AnonymousError("pullRequestId_already_used", {
|
||||
httpStatus: 400,
|
||||
cause: error,
|
||||
object: pullRequestUpdate,
|
||||
}),
|
||||
res,
|
||||
req
|
||||
);
|
||||
}
|
||||
return handleError(error, res, req);
|
||||
}
|
||||
});
|
||||
|
||||
export default router;
|
||||
@@ -0,0 +1,84 @@
|
||||
import * as express from "express";
|
||||
|
||||
import { getPullRequest, handleError } from "./route-utils";
|
||||
import AnonymousError from "../AnonymousError";
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
router.get(
|
||||
"/:pullRequestId/options",
|
||||
async (req: express.Request, res: express.Response) => {
|
||||
try {
|
||||
res.header("Cache-Control", "no-cache");
|
||||
const pr = await getPullRequest(req, res, { nocheck: true });
|
||||
if (!pr) return;
|
||||
let redirectURL = null;
|
||||
if (pr.status == "expired" && pr.options.expirationMode == "redirect") {
|
||||
redirectURL = `https://github.com/${pr.source.repositoryFullName}/pull/${pr.source.pullRequestId}`;
|
||||
} else {
|
||||
if (
|
||||
pr.status == "expired" ||
|
||||
pr.status == "expiring" ||
|
||||
pr.status == "removing" ||
|
||||
pr.status == "removed"
|
||||
) {
|
||||
throw new AnonymousError("pull_request_expired", {
|
||||
object: pr,
|
||||
httpStatus: 410,
|
||||
});
|
||||
}
|
||||
|
||||
const fiveMinuteAgo = new Date();
|
||||
fiveMinuteAgo.setMinutes(fiveMinuteAgo.getMinutes() - 5);
|
||||
if (pr.status != "ready") {
|
||||
if (
|
||||
pr.model.statusDate < fiveMinuteAgo
|
||||
// && repo.status != "preparing"
|
||||
) {
|
||||
await pr.updateIfNeeded({ force: true });
|
||||
}
|
||||
if (pr.status == "error") {
|
||||
throw new AnonymousError(
|
||||
pr.model.statusMessage
|
||||
? pr.model.statusMessage
|
||||
: "pull_request_not_available",
|
||||
{
|
||||
object: pr,
|
||||
httpStatus: 500,
|
||||
}
|
||||
);
|
||||
}
|
||||
throw new AnonymousError("pull_request_not_ready", {
|
||||
httpStatus: 404,
|
||||
object: pr,
|
||||
});
|
||||
}
|
||||
|
||||
await pr.updateIfNeeded();
|
||||
}
|
||||
|
||||
res.json({
|
||||
url: redirectURL,
|
||||
lastUpdateDate: pr.model.statusDate,
|
||||
});
|
||||
} catch (error) {
|
||||
handleError(error, res, req);
|
||||
}
|
||||
}
|
||||
);
|
||||
router.get(
|
||||
"/:pullRequestId/content",
|
||||
async (req: express.Request, res: express.Response) => {
|
||||
const pullRequest = await getPullRequest(req, res);
|
||||
if (!pullRequest) return;
|
||||
try {
|
||||
await pullRequest.countView();
|
||||
res.header("Cache-Control", "no-cache");
|
||||
res.json(pullRequest.content());
|
||||
} catch (error) {
|
||||
handleError(error, res, req);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
export default router;
|
||||
@@ -60,11 +60,10 @@ router.get(
|
||||
router.get(
|
||||
"/:repoId/files",
|
||||
async (req: express.Request, res: express.Response) => {
|
||||
res.header("Cache-Control", "no-cache");
|
||||
const repo = await getRepo(req, res);
|
||||
if (!repo) return;
|
||||
try {
|
||||
res.header("Cache-Control", "no-cache");
|
||||
|
||||
res.json(await repo.anonymizedFiles({ includeSha: false }));
|
||||
} catch (error) {
|
||||
handleError(error, res, req);
|
||||
@@ -76,6 +75,7 @@ router.get(
|
||||
"/:repoId/options",
|
||||
async (req: express.Request, res: express.Response) => {
|
||||
try {
|
||||
res.header("Cache-Control", "no-cache");
|
||||
const repo = await getRepo(req, res, { nocheck: true });
|
||||
if (!repo) return;
|
||||
let redirectURL = null;
|
||||
@@ -146,7 +146,6 @@ router.get(
|
||||
download = true;
|
||||
}
|
||||
|
||||
res.header("Cache-Control", "no-cache");
|
||||
res.json({
|
||||
url: redirectURL,
|
||||
download,
|
||||
|
||||
@@ -5,6 +5,35 @@ import UserModel from "../database/users/users.model";
|
||||
import User from "../User";
|
||||
import * as io from "@pm2/io";
|
||||
|
||||
export async function getPullRequest(
|
||||
req: express.Request,
|
||||
res: express.Response,
|
||||
opt?: { nocheck?: boolean }
|
||||
) {
|
||||
try {
|
||||
const pullRequest = await db.getPullRequest(req.params.pullRequestId);
|
||||
if (opt?.nocheck == true) {
|
||||
} else {
|
||||
// redirect if the repository is expired
|
||||
if (
|
||||
pullRequest.status == "expired" &&
|
||||
pullRequest.options.expirationMode == "redirect"
|
||||
) {
|
||||
res.redirect(
|
||||
`http://github.com/${pullRequest.source.repositoryFullName}/pull/${pullRequest.source.pullRequestId}`
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
pullRequest.check();
|
||||
}
|
||||
return pullRequest;
|
||||
} catch (error) {
|
||||
handleError(error, res, req);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export async function getRepo(
|
||||
req: express.Request,
|
||||
res: express.Response,
|
||||
@@ -50,7 +79,7 @@ function printError(error: any, req?: express.Request) {
|
||||
if (req) {
|
||||
message += ` ${req.originalUrl}`;
|
||||
// ignore common error
|
||||
if (req.originalUrl === '/api/repo/undefined/options') return
|
||||
if (req.originalUrl === "/api/repo/undefined/options") return;
|
||||
}
|
||||
console.error(message);
|
||||
} else if (error instanceof Error) {
|
||||
|
||||
@@ -97,6 +97,21 @@ router.get(
|
||||
}
|
||||
}
|
||||
);
|
||||
router.get(
|
||||
"/anonymized_pull_requests",
|
||||
async (req: express.Request, res: express.Response) => {
|
||||
try {
|
||||
const user = await getUser(req);
|
||||
res.json(
|
||||
(await user.getPullRequests()).map((x) => {
|
||||
return x.toJSON();
|
||||
})
|
||||
);
|
||||
} catch (error) {
|
||||
handleError(error, res, req);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
router.get(
|
||||
"/all_repositories",
|
||||
|
||||
@@ -6,7 +6,7 @@ import GitHubDownload from "../source/GitHubDownload";
|
||||
import AnonymousError from "../AnonymousError";
|
||||
import { TreeElement } from "../types";
|
||||
import * as marked from "marked";
|
||||
import { anonymizeContent, streamToString } from "../anonymize-utils";
|
||||
import { streamToString } from "../anonymize-utils";
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
@@ -103,7 +103,7 @@ async function webView(req: express.Request, res: express.Response) {
|
||||
}
|
||||
if ((await f.extension()) == "md") {
|
||||
const content = await streamToString(await f.anonymizedContent());
|
||||
res.send(marked.marked(content));
|
||||
res.contentType("html").send(marked.marked(content));
|
||||
} else {
|
||||
f.send(res);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user