chore: use strict compilation mode

This commit is contained in:
tdurieux
2023-02-13 13:38:57 +01:00
parent 3ab9b0c7a4
commit ec6098b3a1
33 changed files with 4007 additions and 2160 deletions
+2 -1
View File
@@ -10,6 +10,7 @@ import { tmpdir } from "os";
import * as gh from "parse-github-url"; import * as gh from "parse-github-url";
import * as inquirer from "inquirer"; import * as inquirer from "inquirer";
import server from "./src/server";
import config from "./config"; import config from "./config";
import GitHubDownload from "./src/source/GitHubDownload"; import GitHubDownload from "./src/source/GitHubDownload";
import Repository from "./src/Repository"; import Repository from "./src/Repository";
@@ -91,7 +92,7 @@ async function main() {
if (require.main === module) { if (require.main === module) {
if (process.argv[2] == "server") { if (process.argv[2] == "server") {
// start the server // start the server
require("./src/server").default(); server();
} else { } else {
// use the cli interface // use the cli interface
main(); main();
+5 -5
View File
@@ -68,11 +68,11 @@ const config: Config = {
"in", "in",
], ],
STORAGE: "filesystem", STORAGE: "filesystem",
S3_BUCKET: null, S3_BUCKET: process.env.S3_BUCKET,
S3_CLIENT_ID: null, S3_CLIENT_ID: process.env.S3_CLIENT_ID,
S3_CLIENT_SECRET: null, S3_CLIENT_SECRET: process.env.S3_CLIENT_SECRET,
S3_ENDPOINT: null, S3_ENDPOINT: process.env.S3_ENDPOINT,
S3_REGION: null, S3_REGION: process.env.S3_REGION,
}; };
for (let conf in process.env) { for (let conf in process.env) {
+3701 -1972
View File
File diff suppressed because it is too large Load Diff
+3 -3
View File
@@ -10,7 +10,7 @@
"start": "node --inspect=5858 -r ts-node/register ./index.ts", "start": "node --inspect=5858 -r ts-node/register ./index.ts",
"dev": "nodemon --transpile-only index.ts", "dev": "nodemon --transpile-only index.ts",
"migrateDB": "ts-node --transpile-only migrateDB.ts", "migrateDB": "ts-node --transpile-only migrateDB.ts",
"build": "tsc" "build": "rm -rf build && tsc"
}, },
"repository": { "repository": {
"type": "git", "type": "git",
@@ -64,7 +64,7 @@
"devDependencies": { "devDependencies": {
"@types/archiver": "^5.3.1", "@types/archiver": "^5.3.1",
"@types/compression": "^1.7.1", "@types/compression": "^1.7.1",
"@types/connect-redis": "^0.0.18", "@types/connect-redis": "^0.0.20",
"@types/express": "^4.17.14", "@types/express": "^4.17.14",
"@types/express-rate-limit": "^6.0.0", "@types/express-rate-limit": "^6.0.0",
"@types/express-session": "^1.17.5", "@types/express-session": "^1.17.5",
@@ -73,10 +73,10 @@
"@types/inquirer": "^8.0.0", "@types/inquirer": "^8.0.0",
"@types/marked": "^4.0.7", "@types/marked": "^4.0.7",
"@types/mime-types": "^2.1.0", "@types/mime-types": "^2.1.0",
"@types/node-schedule": "^2.1.0",
"@types/parse-github-url": "^1.0.0", "@types/parse-github-url": "^1.0.0",
"@types/passport": "^1.0.11", "@types/passport": "^1.0.11",
"@types/passport-github2": "^1.2.5", "@types/passport-github2": "^1.2.5",
"@types/rate-limit-redis": "^1.7.4",
"@types/tar-fs": "^2.0.1", "@types/tar-fs": "^2.0.1",
"@types/unzip-stream": "^0.3.1", "@types/unzip-stream": "^0.3.1",
"@types/xml-flow": "^1.0.1", "@types/xml-flow": "^1.0.1",
+11 -11
View File
@@ -2,7 +2,7 @@ import { join, basename } from "path";
import { Response } from "express"; import { Response } from "express";
import { Readable } from "stream"; import { Readable } from "stream";
import Repository from "./Repository"; import Repository from "./Repository";
import { TreeElement, TreeFile } from "./types"; import { Tree, TreeElement, TreeFile } from "./types";
import storage from "./storage"; import storage from "./storage";
import config from "../config"; import config from "../config";
import { anonymizePath, anonymizeStream } from "./anonymize-utils"; import { anonymizePath, anonymizeStream } from "./anonymize-utils";
@@ -13,7 +13,7 @@ import { handleError } from "./routes/route-utils";
* Represent a file in a anonymized repository * Represent a file in a anonymized repository
*/ */
export default class AnonymizedFile { export default class AnonymizedFile {
private _originalPath: string; private _originalPath: string | undefined;
private fileSize?: number; private fileSize?: number;
repository: Repository; repository: Repository;
@@ -59,7 +59,7 @@ export default class AnonymizedFile {
if (fileName == "") { if (fileName == "") {
continue; continue;
} }
if (!currentOriginal[fileName]) { if (!(currentOriginal as Tree)[fileName]) {
// anonymize all the file in the folder and check if there is one that match the current filename // anonymize all the file in the folder and check if there is one that match the current filename
const options = []; const options = [];
for (let originalFileName in currentOriginal) { for (let originalFileName in currentOriginal) {
@@ -74,7 +74,7 @@ export default class AnonymizedFile {
// if only one option we found the original filename // if only one option we found the original filename
if (options.length == 1) { if (options.length == 1) {
currentOriginalPath = join(currentOriginalPath, options[0]); currentOriginalPath = join(currentOriginalPath, options[0]);
currentOriginal = currentOriginal[options[0]]; currentOriginal = (currentOriginal as Tree)[options[0]];
} else if (options.length == 0) { } else if (options.length == 0) {
throw new AnonymousError("file_not_found", { throw new AnonymousError("file_not_found", {
object: this, object: this,
@@ -85,14 +85,14 @@ export default class AnonymizedFile {
if (!nextName) { if (!nextName) {
// if there is no next name we can't find the file and we return the first option // if there is no next name we can't find the file and we return the first option
currentOriginalPath = join(currentOriginalPath, options[0]); currentOriginalPath = join(currentOriginalPath, options[0]);
currentOriginal = currentOriginal[options[0]]; currentOriginal = (currentOriginal as Tree)[options[0]];
} }
let found = false; let found = false;
for (const option of options) { for (const option of options) {
const optionTree = currentOriginal[option]; const optionTree = (currentOriginal as Tree)[option];
if (optionTree.child) { if ((optionTree as Tree).child) {
const optionTreeChild = optionTree.child; const optionTreeChild = (optionTree as Tree).child;
if (optionTreeChild[nextName]) { if ((optionTreeChild as Tree)[nextName]) {
currentOriginalPath = join(currentOriginalPath, option); currentOriginalPath = join(currentOriginalPath, option);
currentOriginal = optionTreeChild; currentOriginal = optionTreeChild;
found = true; found = true;
@@ -103,12 +103,12 @@ export default class AnonymizedFile {
if (!found) { if (!found) {
// if we didn't find the next name we return the first option // if we didn't find the next name we return the first option
currentOriginalPath = join(currentOriginalPath, options[0]); currentOriginalPath = join(currentOriginalPath, options[0]);
currentOriginal = currentOriginal[options[0]]; currentOriginal = (currentOriginal as Tree)[options[0]];
} }
} }
} else { } else {
currentOriginalPath = join(currentOriginalPath, fileName); currentOriginalPath = join(currentOriginalPath, fileName);
currentOriginal = currentOriginal[fileName]; currentOriginal = (currentOriginal as Tree)[fileName];
} }
} }
+1 -1
View File
@@ -5,7 +5,7 @@ import { ConferenceStatus } from "./types";
export default class Conference { export default class Conference {
private _data: IConferenceDocument; private _data: IConferenceDocument;
private _repositories: Repository[] = null; private _repositories: Repository[] = [];
constructor(data: IConferenceDocument) { constructor(data: IConferenceDocument) {
this._data = data; this._data = data;
+18 -8
View File
@@ -56,22 +56,22 @@ export default class PullRequest {
this._model.pullRequest = { this._model.pullRequest = {
diff: diff.body, diff: diff.body,
title: prInfo.data.title, title: prInfo.data.title,
body: prInfo.data.body, body: prInfo.data.body || "",
creationDate: new Date(prInfo.data.created_at), creationDate: new Date(prInfo.data.created_at),
updatedDate: new Date(prInfo.data.updated_at), updatedDate: new Date(prInfo.data.updated_at),
draft: prInfo.data.draft, draft: prInfo.data.draft,
merged: prInfo.data.merged, merged: prInfo.data.merged,
mergedDate: prInfo.data.merged_at mergedDate: prInfo.data.merged_at
? new Date(prInfo.data.merged_at) ? new Date(prInfo.data.merged_at)
: null, : undefined,
state: prInfo.data.state, state: prInfo.data.state,
baseRepositoryFullName: prInfo.data.base.repo.full_name, baseRepositoryFullName: prInfo.data.base.repo.full_name,
headRepositoryFullName: prInfo.data.head.repo.full_name, headRepositoryFullName: prInfo.data.head.repo?.full_name,
comments: comments.data.map((comment) => ({ comments: comments.data.map((comment) => ({
body: comment.body, body: comment.body || "",
creationDate: new Date(comment.created_at), creationDate: new Date(comment.created_at),
updatedDate: new Date(comment.updated_at), updatedDate: new Date(comment.updated_at),
author: comment.user.login, author: comment.user?.login || "",
})), })),
}; };
} }
@@ -82,7 +82,8 @@ export default class PullRequest {
check() { check() {
if ( if (
this._model.options.expirationMode !== "never" && this._model.options.expirationMode !== "never" &&
this.status == "ready" this.status == "ready" &&
this._model.options.expirationDate
) { ) {
if (this._model.options.expirationDate <= new Date()) { if (this._model.options.expirationDate <= new Date()) {
this.expire(); this.expire();
@@ -187,7 +188,16 @@ export default class PullRequest {
if (status) this._model.status = status; if (status) this._model.status = status;
if (statusMessage) this._model.statusMessage = statusMessage; if (statusMessage) this._model.statusMessage = statusMessage;
// remove cache // remove cache
this._model.pullRequest = null; this._model.pullRequest.comments = [];
this._model.pullRequest.body = "";
this._model.pullRequest.title = "";
this._model.pullRequest.diff = "";
this._model.pullRequest.baseRepositoryFullName = "";
this._model.pullRequest.headRepositoryFullName = "";
this._model.pullRequest.merged = false;
this._model.pullRequest.mergedDate = undefined;
this._model.pullRequest.state = "closed";
this._model.pullRequest.draft = false;
return Promise.all([this._model.save()]); return Promise.all([this._model.save()]);
} }
@@ -222,7 +232,7 @@ export default class PullRequest {
output.body = anonymizeContent(this._model.pullRequest.body, this); output.body = anonymizeContent(this._model.pullRequest.body, this);
} }
if (this.options.comments) { if (this.options.comments) {
output.comments = this._model.pullRequest.comments.map((comment) => { output.comments = this._model.pullRequest.comments?.map((comment) => {
const o: any = {}; const o: any = {};
if (this.options.body) o.body = anonymizeContent(comment.body, this); if (this.options.body) o.body = anonymizeContent(comment.body, this);
if (this.options.username) if (this.options.username)
+16 -8
View File
@@ -1,7 +1,7 @@
import { join } from "path"; import { join } from "path";
import storage from "./storage"; import storage from "./storage";
import { RepositoryStatus, Source, Tree, TreeElement, TreeFile } from "./types"; import { RepositoryStatus, Source, Tree, TreeElement, TreeFile } from "./types";
import { Readable } from "stream"; import { Readable, Transform } from "stream";
import User from "./User"; import User from "./User";
import GitHubStream from "./source/GitHubStream"; import GitHubStream from "./source/GitHubStream";
import GitHubDownload from "./source/GitHubDownload"; import GitHubDownload from "./source/GitHubDownload";
@@ -14,7 +14,7 @@ import GitHubBase from "./source/GitHubBase";
import Conference from "./Conference"; import Conference from "./Conference";
import ConferenceModel from "./database/conference/conferences.model"; import ConferenceModel from "./database/conference/conferences.model";
import AnonymousError from "./AnonymousError"; import AnonymousError from "./AnonymousError";
import { downloadQueue } from "./queue"; import { downloadQueue, removeQueue } from "./queue";
import { isConnected } from "./database/database"; import { isConnected } from "./database/database";
import AnonymizedFile from "./AnonymizedFile"; import AnonymizedFile from "./AnonymizedFile";
import AnonymizedRepositoryModel from "./database/anonymizedRepositories/anonymizedRepositories.model"; import AnonymizedRepositoryModel from "./database/anonymizedRepositories/anonymizedRepositories.model";
@@ -36,7 +36,11 @@ function anonymizeTreeRecursive(
const output: Tree = {}; const output: Tree = {};
Object.getOwnPropertyNames(tree).forEach((file) => { Object.getOwnPropertyNames(tree).forEach((file) => {
const anonymizedPath = anonymizePath(file, terms); const anonymizedPath = anonymizePath(file, terms);
output[anonymizedPath] = anonymizeTreeRecursive(tree[file], terms, opt); output[anonymizedPath] = anonymizeTreeRecursive(
(tree as Tree)[file],
terms,
opt
);
}); });
return output; return output;
} }
@@ -98,6 +102,7 @@ export default class Repository {
const res = await AnonymizedRepositoryModel.findById(this._model._id, { const res = await AnonymizedRepositoryModel.findById(this._model._id, {
originalFiles: 1, originalFiles: 1,
}); });
if (!res) throw new AnonymousError("repository_not_found");
this.model.originalFiles = res.originalFiles; this.model.originalFiles = res.originalFiles;
} }
if ( if (
@@ -120,7 +125,8 @@ export default class Repository {
check() { check() {
if ( if (
this._model.options.expirationMode !== "never" && this._model.options.expirationMode !== "never" &&
this.status == "ready" this.status == "ready" &&
this._model.options.expirationDate
) { ) {
if (this._model.options.expirationDate <= new Date()) { if (this._model.options.expirationDate <= new Date()) {
this.expire(); this.expire();
@@ -164,7 +170,7 @@ export default class Repository {
repository: this, repository: this,
anonymizedPath: filename, anonymizedPath: filename,
}) })
) as Transformer, ),
}); });
} }
@@ -274,12 +280,14 @@ export default class Repository {
* Reset/delete the state of the repository * Reset/delete the state of the repository
*/ */
async resetSate(status?: RepositoryStatus, statusMessage?: string) { async resetSate(status?: RepositoryStatus, statusMessage?: string) {
const p = this.updateStatus(status, statusMessage); if (status) {
await this.updateStatus(status, statusMessage);
}
// remove attribute // remove attribute
this._model.size = { storage: 0, file: 0 }; this._model.size = { storage: 0, file: 0 };
this._model.originalFiles = null; this._model.originalFiles = undefined;
// remove cache // remove cache
return Promise.all([p, this.removeCache()]); await this.removeCache();
} }
/** /**
+1 -1
View File
@@ -32,7 +32,7 @@ export default class User {
return this._model.accessTokens.github; return this._model.accessTokens.github;
} }
get photo(): string { get photo(): string | undefined {
return this._model.photo; return this._model.photo;
} }
+2 -2
View File
@@ -10,7 +10,7 @@ const urlRegex =
/<?\b((https?|ftp|file):\/\/)[-A-Za-z0-9+&@#/%?=~_|!:,.;]+[-A-Za-z0-9+&@#/%=~_|]\b\/?>?/g; /<?\b((https?|ftp|file):\/\/)[-A-Za-z0-9+&@#/%?=~_|!:,.;]+[-A-Za-z0-9+&@#/%=~_|]\b\/?>?/g;
export function streamToString(stream: Readable): Promise<string> { export function streamToString(stream: Readable): Promise<string> {
const chunks = []; const chunks: Buffer[] = [];
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
stream.on("data", (chunk) => chunks.push(Buffer.from(chunk))); stream.on("data", (chunk) => chunks.push(Buffer.from(chunk)));
stream.on("error", (err) => reject(err)); stream.on("error", (err) => reject(err));
@@ -33,7 +33,7 @@ export function isTextFile(filePath: string, content: Buffer) {
export function anonymizeStream(file: AnonymizedFile) { export function anonymizeStream(file: AnonymizedFile) {
const ts = new Transform(); const ts = new Transform();
var chunks = [], var chunks: Buffer[] = [],
len = 0, len = 0,
pos = 0; pos = 0;
@@ -9,7 +9,7 @@ export interface IAnonymizedPullRequest {
anonymizeDate: Date; anonymizeDate: Date;
source: { source: {
pullRequestId: number; pullRequestId: number;
repositoryFullName?: string; repositoryFullName: string;
accessToken?: string; accessToken?: string;
}; };
owner: string; owner: string;
@@ -17,7 +17,7 @@ export interface IAnonymizedRepository {
}; };
owner: string; owner: string;
truckedFileList: boolean; truckedFileList: boolean;
originalFiles: Tree; originalFiles?: Tree;
conference: string; conference: string;
options: { options: {
terms: string[]; terms: string[];
+1
View File
@@ -13,6 +13,7 @@ export const database = mongoose.connection;
export let isConnected = false; export let isConnected = false;
export async function connect() { export async function connect() {
mongoose.set("strictQuery", false);
await mongoose.connect(MONGO_URL + "production", { await mongoose.connect(MONGO_URL + "production", {
authSource: "admin", authSource: "admin",
appName: "Anonymous GitHub Server", appName: "Anonymous GitHub Server",
+5 -1
View File
@@ -23,7 +23,11 @@ export default async function (job: SandboxedJob<Repository, void>) {
try { try {
await repo.anonymize(); await repo.anonymize();
} catch (error) { } catch (error) {
await repo.updateStatus(RepositoryStatus.ERROR, error.message); if (error instanceof Error) {
await repo.updateStatus(RepositoryStatus.ERROR, error.message);
} else if (typeof error === "string") {
await repo.updateStatus(RepositoryStatus.ERROR, error);
}
throw error; throw error;
} }
} catch (error) { } catch (error) {
+5 -1
View File
@@ -19,7 +19,11 @@ export default async function (job: SandboxedJob<Repository, void>) {
try { try {
await repo.remove(); await repo.remove();
} catch (error) { } catch (error) {
await repo.updateStatus(RepositoryStatus.ERROR, error.message); if (error instanceof Error) {
await repo.updateStatus(RepositoryStatus.ERROR, error.message);
} else if (typeof error === "string") {
await repo.updateStatus(RepositoryStatus.ERROR, error);
}
throw error; throw error;
} }
} catch (error) { } catch (error) {
+3 -15
View File
@@ -48,9 +48,6 @@ export function startWorker() {
autorun: true, autorun: true,
} }
); );
cacheWorker.on("error", async (error) => {
console.log(error);
});
cacheWorker.on("completed", async (job) => { cacheWorker.on("completed", async (job) => {
await job.remove(); await job.remove();
}); });
@@ -66,9 +63,6 @@ export function startWorker() {
autorun: true, autorun: true,
} }
); );
removeWorker.on("error", async (error) => {
console.log(error);
});
removeWorker.on("completed", async (job) => { removeWorker.on("completed", async (job) => {
await job.remove(); await job.remove();
}); });
@@ -88,18 +82,12 @@ export function startWorker() {
if (!downloadWorker.isRunning) downloadWorker.run(); if (!downloadWorker.isRunning) downloadWorker.run();
downloadWorker.on("active", async (job) => { downloadWorker.on("active", async (job) => {
console.log("active", job.data.repoId); console.log("[QUEUE] download repository start", job.data.repoId);
}); });
downloadWorker.on("completed", async (job) => { downloadWorker.on("completed", async (job) => {
console.log("completed", job.data.repoId); console.log("[QUEUE] download repository completed", job.data.repoId);
}); });
downloadWorker.on("failed", async (job) => { downloadWorker.on("failed", async (job) => {
console.log("failed", job.data.repoId); console.log("download repository failed", job.data.repoId);
});
downloadWorker.on("closing", async (error) => {
console.log("closing", error);
});
downloadWorker.on("error", async (error) => {
console.log(error);
}); });
} }
+1 -1
View File
@@ -118,7 +118,7 @@ router.get("/repos", async (req, res) => {
if (req.query.search) { if (req.query.search) {
query.push({ repoId: { $regex: req.query.search } }); query.push({ repoId: { $regex: req.query.search } });
} }
let status = []; const status: { status: string }[] = [];
query.push({ $or: status }); query.push({ $or: status });
if (ready) { if (ready) {
status.push({ status: "ready" }); status.push({ status: "ready" });
+22 -10
View File
@@ -4,6 +4,7 @@ import Conference from "../Conference";
import ConferenceModel from "../database/conference/conferences.model"; import ConferenceModel from "../database/conference/conferences.model";
import { ensureAuthenticated } from "./connection"; import { ensureAuthenticated } from "./connection";
import { handleError, getUser, isOwnerOrAdmin } from "./route-utils"; import { handleError, getUser, isOwnerOrAdmin } from "./route-utils";
import { IConferenceDocument } from "../database/conference/conferences.types";
const router = express.Router(); const router = express.Router();
@@ -66,7 +67,7 @@ router.get("/", async (req: express.Request, res: express.Response) => {
} }
}); });
function validateConferenceForm(conf) { function validateConferenceForm(conf: any) {
if (!conf.name) if (!conf.name)
throw new AnonymousError("conf_name_missing", { throw new AnonymousError("conf_name_missing", {
object: conf, object: conf,
@@ -148,11 +149,17 @@ router.post(
async (req: express.Request, res: express.Response) => { async (req: express.Request, res: express.Response) => {
try { try {
const user = await getUser(req); const user = await getUser(req);
let model = new ConferenceModel(); let model: IConferenceDocument = new ConferenceModel();
if (req.params.conferenceID) { if (req.params.conferenceID) {
model = await ConferenceModel.findOne({ const queryModel = await ConferenceModel.findOne({
conferenceID: req.params.conferenceID, conferenceID: req.params.conferenceID,
}); });
if (!queryModel) {
throw new AnonymousError("conference_not_found", {
httpStatus: 404,
});
}
model = queryModel;
isOwnerOrAdmin(model.owners, user); isOwnerOrAdmin(model.owners, user);
} }
validateConferenceForm(req.body); validateConferenceForm(req.body);
@@ -197,7 +204,10 @@ router.post(
res.send("ok"); res.send("ok");
} catch (error) { } catch (error) {
if (error.message?.indexOf(" duplicate key") > -1) { if (
error instanceof Error &&
error.message?.indexOf(" duplicate key") > -1
) {
return handleError( return handleError(
new AnonymousError("conf_id_used", { new AnonymousError("conf_id_used", {
object: req.params.conferenceID, object: req.params.conferenceID,
@@ -219,16 +229,18 @@ router.get(
conferenceID: req.params.conferenceID, conferenceID: req.params.conferenceID,
}); });
if (!data) if (!data)
throw new AnonymousError("conf_not_found", { throw new AnonymousError("conf_not_found", {
object: req.params.conferenceID, object: req.params.conferenceID,
httpStatus: 404, httpStatus: 404,
}); });
const user = await getUser(req); const user = await getUser(req);
const conference = new Conference(data); const conference = new Conference(data);
try { try {
isOwnerOrAdmin(conference.ownerIDs, user); isOwnerOrAdmin(conference.ownerIDs, user);
const o: any = conference.toJSON(); const o: any = conference.toJSON();
o.repositories = (await conference.repositories()).map((r) => r.toJSON()); o.repositories = (await conference.repositories()).map((r) =>
r.toJSON()
);
res.json(o); res.json(o);
} catch (error) { } catch (error) {
return res.json({ return res.json({
@@ -238,7 +250,7 @@ router.get(
startDate: conference.startDate, startDate: conference.startDate,
endDate: conference.endDate, endDate: conference.endDate,
options: conference.options, options: conference.options,
}) });
} }
} catch (error) { } catch (error) {
handleError(error, res, req); handleError(error, res, req);
+22 -20
View File
@@ -10,8 +10,6 @@ import config from "../../config";
import UserModel from "../database/users/users.model"; import UserModel from "../database/users/users.model";
import { IUserDocument } from "../database/users/users.types"; import { IUserDocument } from "../database/users/users.types";
const RedisStore = connectRedis(session);
export function ensureAuthenticated( export function ensureAuthenticated(
req: express.Request, req: express.Request,
res: express.Response, res: express.Response,
@@ -29,7 +27,7 @@ const verify = async (
profile: Profile, profile: Profile,
done: OAuth2Strategy.VerifyCallback done: OAuth2Strategy.VerifyCallback
): Promise<void> => { ): Promise<void> => {
let user: IUserDocument; let user: IUserDocument | null = null;
try { try {
user = await UserModel.findOne({ "externalIDs.github": profile.id }); user = await UserModel.findOne({ "externalIDs.github": profile.id });
if (user) { if (user) {
@@ -84,23 +82,27 @@ passport.deserializeUser((user: Express.User, done) => {
done(null, user); done(null, user);
}); });
const redisClient = createClient({ export function initSession() {
legacyMode: true, const RedisStore = connectRedis(session);
socket: { const redisClient = createClient({
port: config.REDIS_PORT, legacyMode: true,
host: config.REDIS_HOSTNAME, socket: {
}, port: config.REDIS_PORT,
}); host: config.REDIS_HOSTNAME,
redisClient.on("error", (err) => console.log("Redis Client Error", err)); },
redisClient.connect(); });
export const appSession = session({ redisClient.on("error", (err) => console.log("Redis Client Error", err));
secret: "keyboard cat", redisClient.connect();
store: new RedisStore({
client: redisClient, return session({
}), secret: "keyboard cat",
saveUninitialized: false, store: new RedisStore({
resave: false, client: redisClient,
}); }),
saveUninitialized: false,
resave: false,
});
}
export const router = express.Router(); export const router = express.Router();
+6 -3
View File
@@ -96,7 +96,7 @@ router.get(
} }
); );
function validateNewPullRequest(pullRequestUpdate): void { function validateNewPullRequest(pullRequestUpdate: any): void {
const validCharacters = /^[0-9a-zA-Z\-\_]+$/; const validCharacters = /^[0-9a-zA-Z\-\_]+$/;
if ( if (
!pullRequestUpdate.pullRequestId.match(validCharacters) || !pullRequestUpdate.pullRequestId.match(validCharacters) ||
@@ -151,7 +151,7 @@ function updatePullRequestModel(
expirationMode: pullRequestUpdate.options.expirationMode, expirationMode: pullRequestUpdate.options.expirationMode,
expirationDate: pullRequestUpdate.options.expirationDate expirationDate: pullRequestUpdate.options.expirationDate
? new Date(pullRequestUpdate.options.expirationDate) ? new Date(pullRequestUpdate.options.expirationDate)
: null, : undefined,
update: pullRequestUpdate.options.update, update: pullRequestUpdate.options.update,
image: pullRequestUpdate.options.image, image: pullRequestUpdate.options.image,
link: pullRequestUpdate.options.link, link: pullRequestUpdate.options.link,
@@ -220,7 +220,10 @@ router.post("/", async (req: express.Request, res: express.Response) => {
await pullRequest.anonymize(); await pullRequest.anonymize();
res.send(pullRequest.toJSON()); res.send(pullRequest.toJSON());
} catch (error) { } catch (error) {
if (error.message?.indexOf(" duplicate key") > -1) { if (
error instanceof Error &&
error.message.indexOf(" duplicate key") > -1
) {
return handleError( return handleError(
new AnonymousError("pullRequestId_already_used", { new AnonymousError("pullRequestId_already_used", {
httpStatus: 400, httpStatus: 400,
+36 -8
View File
@@ -70,6 +70,12 @@ router.post("/claim", async (req: express.Request, res: express.Response) => {
} }
const r = gh(req.body.repoUrl); const r = gh(req.body.repoUrl);
if (!r?.owner || !r?.name) {
throw new AnonymousError("repo_not_found", {
object: req.body,
httpStatus: 404,
});
}
const repo = await getRepositoryFromGitHub({ const repo = await getRepositoryFromGitHub({
owner: r.owner, owner: r.owner,
repo: r.name, repo: r.name,
@@ -259,7 +265,7 @@ router.get("/:repoId/", async (req: express.Request, res: express.Response) => {
} }
}); });
function validateNewRepo(repoUpdate): void { function validateNewRepo(repoUpdate: any): void {
const validCharacters = /^[0-9a-zA-Z\-\_]+$/; const validCharacters = /^[0-9a-zA-Z\-\_]+$/;
if ( if (
!repoUpdate.repoId.match(validCharacters) || !repoUpdate.repoId.match(validCharacters) ||
@@ -322,7 +328,7 @@ function updateRepoModel(
expirationMode: repoUpdate.options.expirationMode, expirationMode: repoUpdate.options.expirationMode,
expirationDate: repoUpdate.options.expirationDate expirationDate: repoUpdate.options.expirationDate
? new Date(repoUpdate.options.expirationDate) ? new Date(repoUpdate.options.expirationDate)
: null, : undefined,
update: repoUpdate.options.update, update: repoUpdate.options.update,
image: repoUpdate.options.image, image: repoUpdate.options.image,
pdf: repoUpdate.options.pdf, pdf: repoUpdate.options.pdf,
@@ -359,7 +365,7 @@ router.post(
updateRepoModel(repo.model, repoUpdate); updateRepoModel(repo.model, repoUpdate);
async function removeRepoFromConference(conferenceID) { const removeRepoFromConference = async (conferenceID: string) => {
const conf = await ConferenceModel.findOne({ const conf = await ConferenceModel.findOne({
conferenceID, conferenceID,
}); });
@@ -368,7 +374,7 @@ router.post(
if (r.length == 1) r[0].removeDate = new Date(); if (r.length == 1) r[0].removeDate = new Date();
await conf.save(); await conf.save();
} }
} };
if (!repoUpdate.conference) { if (!repoUpdate.conference) {
// remove conference // remove conference
if (repo.model.conference) { if (repo.model.conference) {
@@ -394,7 +400,7 @@ router.post(
if (f.length) { if (f.length) {
// the repository already referenced the conference // the repository already referenced the conference
f[0].addDate = new Date(); f[0].addDate = new Date();
f[0].removeDate = null; f[0].removeDate = undefined;
} else { } else {
conf.repositories.push({ conf.repositories.push({
id: repo.model.id, id: repo.model.id,
@@ -426,12 +432,25 @@ router.post("/", async (req: express.Request, res: express.Response) => {
validateNewRepo(repoUpdate); validateNewRepo(repoUpdate);
const r = gh(repoUpdate.fullName); const r = gh(repoUpdate.fullName);
if (!r?.owner || !r?.name) {
throw new AnonymousError("repo_not_found", {
object: req.body,
httpStatus: 404,
});
}
const repository = await getRepositoryFromGitHub({ const repository = await getRepositoryFromGitHub({
accessToken: user.accessToken, accessToken: user.accessToken,
owner: r.owner, owner: r.owner,
repo: r.name, repo: r.name,
}); });
if (!repository) {
throw new AnonymousError("repo_not_found", {
object: req.body,
httpStatus: 404,
});
}
const repo = new AnonymizedRepositoryModel(); const repo = new AnonymizedRepositoryModel();
repo.repoId = repoUpdate.repoId; repo.repoId = repoUpdate.repoId;
repo.anonymizeDate = new Date(); repo.anonymizeDate = new Date();
@@ -444,14 +463,20 @@ router.post("/", async (req: express.Request, res: express.Response) => {
if (repo.source.type == "GitHubDownload") { if (repo.source.type == "GitHubDownload") {
// details.size is in kilobytes // details.size is in kilobytes
if (repository.size > config.MAX_REPO_SIZE) { if (
repository.size === undefined ||
repository.size > config.MAX_REPO_SIZE
) {
throw new AnonymousError("invalid_mode", { throw new AnonymousError("invalid_mode", {
object: repository, object: repository,
httpStatus: 400, httpStatus: 400,
}); });
} }
} }
if (repository.size < config.AUTO_DOWNLOAD_REPO_SIZE) { if (
repository.size !== undefined &&
repository.size < config.AUTO_DOWNLOAD_REPO_SIZE
) {
repo.source.type = "GitHubDownload"; repo.source.type = "GitHubDownload";
} }
repo.conference = repoUpdate.conference; repo.conference = repoUpdate.conference;
@@ -488,7 +513,10 @@ router.post("/", async (req: express.Request, res: express.Response) => {
attempts: 3, attempts: 3,
}); });
} catch (error) { } catch (error) {
if (error.message?.indexOf(" duplicate key") > -1) { if (
error instanceof Error &&
error.message?.indexOf(" duplicate key") > -1
) {
return handleError( return handleError(
new AnonymousError("repoId_already_used", { new AnonymousError("repoId_already_used", {
httpStatus: 400, httpStatus: 400,
+2 -2
View File
@@ -44,7 +44,7 @@ export async function getRepo(
) { ) {
try { try {
const repo = await db.getRepository(req.params.repoId, { const repo = await db.getRepository(req.params.repoId, {
includeFiles: opt.includeFiles, includeFiles: opt.includeFiles === true,
}); });
if (opt.nocheck == true) { if (opt.nocheck == true) {
} else { } else {
@@ -79,7 +79,7 @@ function printError(error: any, req?: express.Request) {
io.notifyError(error, error.value); io.notifyError(error, error.value);
if (error instanceof AnonymousError) { if (error instanceof AnonymousError) {
let message = `[ERROR] ${error.toString()} ${error.stack let message = `[ERROR] ${error.toString()} ${error.stack
.split("\n")[1] ?.split("\n")[1]
.trim()}`; .trim()}`;
if (req) { if (req) {
message += ` ${req.originalUrl}`; message += ` ${req.originalUrl}`;
+4 -4
View File
@@ -4,7 +4,7 @@ import * as path from "path";
import AnonymizedFile from "../AnonymizedFile"; import AnonymizedFile from "../AnonymizedFile";
import GitHubDownload from "../source/GitHubDownload"; import GitHubDownload from "../source/GitHubDownload";
import AnonymousError from "../AnonymousError"; import AnonymousError from "../AnonymousError";
import { TreeElement } from "../types"; import { Tree, TreeElement } from "../types";
import * as marked from "marked"; import * as marked from "marked";
import { streamToString } from "../anonymize-utils"; import { streamToString } from "../anonymize-utils";
@@ -68,13 +68,13 @@ async function webView(req: express.Request, res: express.Response) {
if (fileName == "") { if (fileName == "") {
continue; continue;
} }
if (!currentAnonymized[fileName]) { if (!(currentAnonymized as Tree)[fileName]) {
throw new AnonymousError("file_not_found", { throw new AnonymousError("file_not_found", {
object: this, object: repo,
httpStatus: 404, httpStatus: 404,
}); });
} }
currentAnonymized = currentAnonymized[fileName]; currentAnonymized = (currentAnonymized as Tree)[fileName];
} }
let best_match = null; let best_match = null;
+12 -12
View File
@@ -1,16 +1,16 @@
import * as path from "path";
import * as ofs from "fs";
import { createClient } from "redis"; import { createClient } from "redis";
import { resolve, join } from "path";
import { existsSync } from "fs";
import rateLimit from "express-rate-limit"; import rateLimit from "express-rate-limit";
import * as slowDown from "express-slow-down"; import * as slowDown from "express-slow-down";
import RedisStore from "rate-limit-redis"; import RedisStore from "rate-limit-redis";
import * as express from "express"; import * as express from "express";
import * as compression from "compression"; import * as compression from "compression";
import * as db from "./database/database";
import config from "../config";
import * as passport from "passport"; import * as passport from "passport";
import * as connection from "./routes/connection"; import config from "../config";
import { connect } from "./database/database";
import { initSession, router as connectionRouter } from "./routes/connection";
import router from "./routes"; import router from "./routes";
import AnonymizedRepositoryModel from "./database/anonymizedRepositories/anonymizedRepositories.model"; import AnonymizedRepositoryModel from "./database/anonymizedRepositories/anonymizedRepositories.model";
import { conferenceStatusCheck, repositoryStatusCheck } from "./schedule"; import { conferenceStatusCheck, repositoryStatusCheck } from "./schedule";
@@ -31,7 +31,7 @@ function indexResponse(req: express.Request, res: express.Response) {
)}` )}`
); );
} }
res.sendFile(path.resolve(__dirname, "..", "public", "index.html")); res.sendFile(resolve("public", "index.html"));
} }
export default async function start() { export default async function start() {
@@ -45,7 +45,7 @@ export default async function start() {
app.get("/ip", (request, response) => response.send(request.ip)); app.get("/ip", (request, response) => response.send(request.ip));
// handle session and connection // handle session and connection
app.use(connection.appSession); app.use(initSession());
app.use(passport.initialize()); app.use(passport.initialize());
app.use(passport.session()); app.use(passport.session());
@@ -85,7 +85,7 @@ export default async function start() {
headers: true, headers: true,
}); });
app.use("/github", rate, speedLimiter, connection.router); app.use("/github", rate, speedLimiter, connectionRouter);
// api routes // api routes
const apiRouter = express.Router(); const apiRouter = express.Router();
@@ -102,8 +102,8 @@ export default async function start() {
apiRouter.use("/pr", speedLimiter, router.pullRequestPrivate); apiRouter.use("/pr", speedLimiter, router.pullRequestPrivate);
apiRouter.get("/message", async (_, res) => { apiRouter.get("/message", async (_, res) => {
if (ofs.existsSync("./message.txt")) { if (existsSync("./message.txt")) {
return res.sendFile(path.resolve(__dirname, "..", "message.txt")); return res.sendFile(resolve("message.txt"));
} }
res.sendStatus(404); res.sendStatus(404);
}); });
@@ -142,7 +142,7 @@ export default async function start() {
.get("/repository/:repoId/?*", indexResponse); .get("/repository/:repoId/?*", indexResponse);
app.use( app.use(
express.static(path.join(__dirname, "..", "public"), { express.static(join("public"), {
etag: true, etag: true,
lastModified: true, lastModified: true,
maxAge: 3600, // 1h maxAge: 3600, // 1h
@@ -155,7 +155,7 @@ export default async function start() {
conferenceStatusCheck(); conferenceStatusCheck();
repositoryStatusCheck(); repositoryStatusCheck();
await db.connect(); await connect();
app.listen(config.PORT); app.listen(config.PORT);
console.log("Database connected and Server started on port: " + config.PORT); console.log("Database connected and Server started on port: " + config.PORT);
} }
+7 -3
View File
@@ -11,7 +11,7 @@ export default abstract class GitHubBase {
type: "GitHubDownload" | "GitHubStream" | "Zip"; type: "GitHubDownload" | "GitHubStream" | "Zip";
githubRepository: GitHubRepository; githubRepository: GitHubRepository;
branch: Branch; branch: Branch;
accessToken: string; accessToken: string | undefined;
repository: Repository; repository: Repository;
constructor( constructor(
@@ -27,13 +27,17 @@ export default abstract class GitHubBase {
) { ) {
this.type = data.type; this.type = data.type;
this.accessToken = data.accessToken; this.accessToken = data.accessToken;
const branches = [];
if (data.branch && data.commit) {
branches.push({ commit: data.commit, name: data.branch });
}
this.githubRepository = new GitHubRepository({ this.githubRepository = new GitHubRepository({
name: data.repositoryName, name: data.repositoryName,
externalId: data.repositoryId, externalId: data.repositoryId,
branches: [{ commit: data.commit, name: data.branch }], branches,
}); });
this.repository = repository; this.repository = repository;
this.branch = { commit: data.commit, name: data.branch }; this.branch = branches[0];
} }
async getFileContent(file: AnonymizedFile): Promise<Readable> { async getFileContent(file: AnonymizedFile): Promise<Readable> {
+15 -15
View File
@@ -1,14 +1,14 @@
import { Octokit } from "@octokit/rest"; import { Octokit } from "@octokit/rest";
import config from "../../config";
import storage from "../storage";
import Repository from "../Repository";
import GitHubBase from "./GitHubBase";
import AnonymizedFile from "../AnonymizedFile";
import { RepositoryStatus, SourceBase } from "../types";
import got from "got"; import got from "got";
import { Readable } from "stream"; import { Readable } from "stream";
import { OctokitResponse } from "@octokit/types"; import { OctokitResponse } from "@octokit/types";
import config from "../../config";
import storage from "../storage";
import Repository from "../Repository";
import GitHubBase from "./GitHubBase";
import AnonymizedFile from "../AnonymizedFile";
import { RepositoryStatus, SourceBase } from "../types";
import AnonymousError from "../AnonymousError"; import AnonymousError from "../AnonymousError";
export default class GitHubDownload extends GitHubBase implements SourceBase { export default class GitHubDownload extends GitHubBase implements SourceBase {
@@ -56,7 +56,7 @@ export default class GitHubDownload extends GitHubBase implements SourceBase {
} }
response = await this._getZipUrl(token); response = await this._getZipUrl(token);
} catch (error) { } catch (error) {
if (error.status == 401 && config.GITHUB_TOKEN) { if ((error as any).status == 401 && config.GITHUB_TOKEN) {
try { try {
response = await this._getZipUrl(config.GITHUB_TOKEN); response = await this._getZipUrl(config.GITHUB_TOKEN);
} catch (error) { } catch (error) {
@@ -66,7 +66,7 @@ export default class GitHubDownload extends GitHubBase implements SourceBase {
); );
throw new AnonymousError("repo_not_accessible", { throw new AnonymousError("repo_not_accessible", {
httpStatus: 404, httpStatus: 404,
cause: error, cause: error as Error,
object: this.repository, object: this.repository,
}); });
} }
@@ -78,23 +78,23 @@ export default class GitHubDownload extends GitHubBase implements SourceBase {
throw new AnonymousError("repo_not_accessible", { throw new AnonymousError("repo_not_accessible", {
httpStatus: 404, httpStatus: 404,
object: this.repository, object: this.repository,
cause: error, cause: error as Error,
}); });
} }
} }
await this.repository.updateStatus(RepositoryStatus.DOWNLOAD); await this.repository.updateStatus(RepositoryStatus.DOWNLOAD);
const originalPath = this.repository.originalCachePath; const originalPath = this.repository.originalCachePath;
await storage.mk(originalPath); await storage.mk(originalPath);
let progress = null; let progress: { transferred: number } | undefined = undefined;
let progressTimeout; let progressTimeout;
let inDownload = true; let inDownload = true;
const that = this; const that = this;
async function updateProgress() { async function updateProgress() {
if (progress) { if (progress && that.repository.status) {
await that.repository.updateStatus( await that.repository.updateStatus(
that.repository.status, that.repository.status,
progress.transferred progress.transferred.toString()
); );
} }
if (inDownload) { if (inDownload) {
@@ -106,7 +106,7 @@ export default class GitHubDownload extends GitHubBase implements SourceBase {
try { try {
const downloadStream = got.stream(response.url); const downloadStream = got.stream(response.url);
downloadStream.addListener("downloadProgress", (p) => (progress = p)); downloadStream.addListener("downloadProgress", (p) => (progress = p));
await storage.extractZip(originalPath, downloadStream, null, this); await storage.extractZip(originalPath, downloadStream, undefined, this);
} catch (error) { } catch (error) {
await this.repository.updateStatus( await this.repository.updateStatus(
RepositoryStatus.ERROR, RepositoryStatus.ERROR,
@@ -114,7 +114,7 @@ export default class GitHubDownload extends GitHubBase implements SourceBase {
); );
throw new AnonymousError("unable_to_download", { throw new AnonymousError("unable_to_download", {
httpStatus: 500, httpStatus: 500,
cause: error, cause: error as Error,
object: this.repository, object: this.repository,
}); });
} finally { } finally {
+28 -13
View File
@@ -31,15 +31,15 @@ export class GitHubRepository {
return this._data; return this._data;
} }
public get fullName(): string { public get fullName(): string | undefined {
return this._data.name; return this._data.name;
} }
public get id(): string { public get id(): string | undefined {
return this._data.externalId; return this._data.externalId;
} }
public get size(): number { public get size(): number | undefined {
return this._data.size; return this._data.size;
} }
@@ -76,27 +76,30 @@ export class GitHubRepository {
{ $set: { branches } } { $set: { branches } }
); );
} else { } else {
this._data.branches = ( const q = await RepositoryModel.findOne({ externalId: this.id }).select(
await RepositoryModel.findOne({ externalId: this.id }).select( "branches"
"branches" );
) this._data.branches = q?.branches;
).branches;
} }
return this._data.branches; return this._data.branches || [];
} }
async readme(opt: { async readme(opt: {
branch?: string; branch?: string;
force?: boolean; force?: boolean;
accessToken?: string; accessToken?: string;
}): Promise<string> { }): Promise<string | undefined> {
if (!opt.branch) opt.branch = this._data.defaultBranch || "master"; if (!opt.branch) opt.branch = this._data.defaultBranch || "master";
const model = await RepositoryModel.findOne({ const model = await RepositoryModel.findOne({
externalId: this.id, externalId: this.id,
}).select("branches"); }).select("branches");
if (!model) {
throw new AnonymousError("repo_not_found", { httpStatus: 404 });
}
this._data.branches = await this.branches(opt); this._data.branches = await this.branches(opt);
model.branches = this._data.branches; model.branches = this._data.branches;
@@ -119,7 +122,7 @@ export class GitHubRepository {
} catch (error) { } catch (error) {
throw new AnonymousError("readme_not_available", { throw new AnonymousError("readme_not_available", {
httpStatus: 404, httpStatus: 404,
cause: error, cause: error as Error,
object: this, object: this,
}); });
} }
@@ -136,6 +139,12 @@ export class GitHubRepository {
} }
public get owner(): string { public get owner(): string {
if (!this.fullName) {
throw new AnonymousError("invalid_repo", {
httpStatus: 400,
object: this,
});
}
const repo = gh(this.fullName); const repo = gh(this.fullName);
if (!repo) { if (!repo) {
throw new AnonymousError("invalid_repo", { throw new AnonymousError("invalid_repo", {
@@ -147,6 +156,12 @@ export class GitHubRepository {
} }
public get repo(): string { public get repo(): string {
if (!this.fullName) {
throw new AnonymousError("invalid_repo", {
httpStatus: 400,
object: this,
});
}
const repo = gh(this.fullName); const repo = gh(this.fullName);
if (!repo) { if (!repo) {
throw new AnonymousError("invalid_repo", { throw new AnonymousError("invalid_repo", {
@@ -177,12 +192,12 @@ export async function getRepositoryFromGitHub(opt: {
).data; ).data;
} catch (error) { } catch (error) {
throw new AnonymousError("repo_not_found", { throw new AnonymousError("repo_not_found", {
httpStatus: error.status, httpStatus: (error as any).status,
object: { object: {
owner: opt.owner, owner: opt.owner,
repo: opt.repo, repo: opt.repo,
}, },
cause: error, cause: error as Error,
}); });
} }
if (!r) if (!r)
+21 -12
View File
@@ -30,11 +30,18 @@ export default class GitHubStream extends GitHubBase implements SourceBase {
auth: await this.getToken(), auth: await this.getToken(),
}); });
const file_sha = await file.sha();
if (!file_sha) {
throw new AnonymousError("file_not_accessible", {
httpStatus: 404,
object: file,
});
}
try { try {
const ghRes = await octokit.rest.git.getBlob({ const ghRes = await octokit.rest.git.getBlob({
owner: this.githubRepository.owner, owner: this.githubRepository.owner,
repo: this.githubRepository.repo, repo: this.githubRepository.repo,
file_sha: await file.sha(), file_sha,
}); });
if (!ghRes.data.content && ghRes.data.size != 0) { if (!ghRes.data.content && ghRes.data.size != 0) {
throw new AnonymousError("file_not_accessible", { throw new AnonymousError("file_not_accessible", {
@@ -57,16 +64,16 @@ export default class GitHubStream extends GitHubBase implements SourceBase {
await storage.write(file.originalCachePath, content, file, this); await storage.write(file.originalCachePath, content, file, this);
return stream.Readable.from(content); return stream.Readable.from(content);
} catch (error) { } catch (error) {
if (error.status === 404 || error.httpStatus === 404) { if ((error as any).status === 404 || (error as any).httpStatus === 404) {
throw new AnonymousError("file_not_found", { throw new AnonymousError("file_not_found", {
httpStatus: error.status, httpStatus: (error as any).status || (error as any).httpStatus,
cause: error, cause: error as Error,
object: file, object: file,
}); });
} }
throw new AnonymousError("file_too_big", { throw new AnonymousError("file_too_big", {
httpStatus: error.status, httpStatus: (error as any).status || (error as any).httpStatus,
cause: error, cause: error as Error,
object: file, object: file,
}); });
} }
@@ -92,7 +99,7 @@ export default class GitHubStream extends GitHubBase implements SourceBase {
count.request++; count.request++;
ghRes = await this.getGHTree(sha, { recursive: true }); ghRes = await this.getGHTree(sha, { recursive: true });
} catch (error) { } catch (error) {
if (error.status == 409) { if ((error as any).status == 409) {
// empty tree // empty tree
if (this.repository.status != RepositoryStatus.READY) if (this.repository.status != RepositoryStatus.READY)
await this.repository.updateStatus(RepositoryStatus.READY); await this.repository.updateStatus(RepositoryStatus.READY);
@@ -100,15 +107,17 @@ export default class GitHubStream extends GitHubBase implements SourceBase {
return { __: {} }; return { __: {} };
} else { } else {
console.log( console.log(
`[ERROR] getTree ${this.repository.repoId}@${sha}: ${error.message}` `[ERROR] getTree ${this.repository.repoId}@${sha}: ${
(error as Error).message
}`
); );
await this.repository.resetSate( await this.repository.resetSate(
RepositoryStatus.ERROR, RepositoryStatus.ERROR,
"repo_not_accessible" "repo_not_accessible"
); );
throw new AnonymousError("repo_not_accessible", { throw new AnonymousError("repo_not_accessible", {
httpStatus: error.status, httpStatus: (error as any).status,
cause: error, cause: error as Error,
object: { object: {
owner: this.githubRepository.owner, owner: this.githubRepository.owner,
repo: this.githubRepository.repo, repo: this.githubRepository.repo,
@@ -161,8 +170,8 @@ export default class GitHubStream extends GitHubBase implements SourceBase {
if (data.tree.length < 100 && count.request < 200) { if (data.tree.length < 100 && count.request < 200) {
const promises: Promise<any>[] = []; const promises: Promise<any>[] = [];
for (const file of data.tree) { for (const file of data.tree) {
const elementPath = path.join(parentPath, file.path); if (file.type == "tree" && file.path && file.sha) {
if (file.type == "tree") { const elementPath = path.join(parentPath, file.path);
promises.push( promises.push(
this.getTruncatedTree( this.getTruncatedTree(
file.sha, file.sha,
+3 -4
View File
@@ -1,8 +1,7 @@
import config from "../config"; import config from "../config";
import FileSystem from "./storage/FileSystem"; import FileSystem from "./storage/FileSystem";
import S3Storage from "./storage/S3"; import S3Storage from "./storage/S3";
import { StorageBase } from "./types";
const storage = config.STORAGE == "s3" ? new S3Storage() : new FileSystem(); export default (() => {
return config.STORAGE == "s3" ? new S3Storage() : new FileSystem();
export default storage as StorageBase; })();
+15 -5
View File
@@ -5,7 +5,7 @@ import * as fs from "fs";
import { Extract } from "unzip-stream"; import { Extract } from "unzip-stream";
import { join, basename, dirname } from "path"; import { join, basename, dirname } from "path";
import { Response } from "express"; import { Response } from "express";
import { Readable, pipeline } from "stream"; import { Readable, pipeline, Transform } from "stream";
import * as archiver from "archiver"; import * as archiver from "archiver";
import { promisify } from "util"; import { promisify } from "util";
import AnonymizedFile from "../AnonymizedFile"; import AnonymizedFile from "../AnonymizedFile";
@@ -31,7 +31,12 @@ export default class FileSystem implements StorageBase {
} }
/** @override */ /** @override */
async write(p: string, data: Buffer, file?: AnonymizedFile, source?: SourceBase): Promise<void> { async write(
p: string,
data: Buffer,
file?: AnonymizedFile,
source?: SourceBase
): Promise<void> {
if (!(await this.exists(dirname(p)))) { if (!(await this.exists(dirname(p)))) {
await fs.promises.mkdir(dirname(join(config.FOLDER, p)), { await fs.promises.mkdir(dirname(join(config.FOLDER, p)), {
recursive: true, recursive: true,
@@ -93,7 +98,12 @@ export default class FileSystem implements StorageBase {
} }
/** @override */ /** @override */
async extractZip(p: string, data: Readable, file?: AnonymizedFile, source?: SourceBase): Promise<void> { async extractZip(
p: string,
data: Readable,
file?: AnonymizedFile,
source?: SourceBase
): Promise<void> {
const pipe = promisify(pipeline); const pipe = promisify(pipeline);
return pipe( return pipe(
data, data,
@@ -114,10 +124,10 @@ export default class FileSystem implements StorageBase {
dir: string, dir: string,
opt?: { opt?: {
format?: "zip" | "tar"; format?: "zip" | "tar";
fileTransformer?; fileTransformer? (path: string): Transform;
} }
) { ) {
const archive = archiver(opt?.format, {}); const archive = archiver(opt?.format || "zip", {});
this.listFiles(dir, { this.listFiles(dir, {
onEntry: (file) => { onEntry: (file) => {
+26 -10
View File
@@ -1,7 +1,7 @@
import { SourceBase, StorageBase, Tree, TreeFile } from "../types"; import { SourceBase, StorageBase, Tree, TreeFile } from "../types";
import { S3 } from "aws-sdk"; import { S3 } from "aws-sdk";
import config from "../../config"; import config from "../../config";
import { pipeline, Readable } from "stream"; import { pipeline, Readable, Transform } from "stream";
import ArchiveStreamToS3 from "decompress-stream-to-s3"; import ArchiveStreamToS3 from "decompress-stream-to-s3";
import { Response } from "express"; import { Response } from "express";
import { lookup } from "mime-types"; import { lookup } from "mime-types";
@@ -35,6 +35,7 @@ export default class S3Storage implements StorageBase {
/** @override */ /** @override */
async exists(path: string): Promise<boolean> { async exists(path: string): Promise<boolean> {
if (!config.S3_BUCKET) throw new Error("S3_BUCKET not set");
try { try {
await this.client await this.client
.headObject({ .headObject({
@@ -50,6 +51,7 @@ export default class S3Storage implements StorageBase {
/** @override */ /** @override */
async mk(dir: string): Promise<void> { async mk(dir: string): Promise<void> {
if (!config.S3_BUCKET) throw new Error("S3_BUCKET not set");
if (dir && dir[dir.length - 1] != "/") dir = dir + "/"; if (dir && dir[dir.length - 1] != "/") dir = dir + "/";
await this.client await this.client
@@ -62,6 +64,7 @@ export default class S3Storage implements StorageBase {
/** @override */ /** @override */
async rm(dir: string): Promise<void> { async rm(dir: string): Promise<void> {
if (!config.S3_BUCKET) throw new Error("S3_BUCKET not set");
const data = await this.client const data = await this.client
.listObjectsV2({ .listObjectsV2({
Bucket: config.S3_BUCKET, Bucket: config.S3_BUCKET,
@@ -69,10 +72,15 @@ export default class S3Storage implements StorageBase {
}) })
.promise(); .promise();
const params = { Bucket: config.S3_BUCKET, Delete: { Objects: [] } }; const params = {
Bucket: config.S3_BUCKET,
Delete: { Objects: new Array<{ Key: string }>() },
};
data.Contents.forEach(function (content) { data.Contents?.forEach(function (content) {
params.Delete.Objects.push({ Key: content.Key }); if (content.Key) {
params.Delete.Objects.push({ Key: content.Key });
}
}); });
if (params.Delete.Objects.length == 0) { if (params.Delete.Objects.length == 0) {
@@ -88,6 +96,7 @@ export default class S3Storage implements StorageBase {
/** @override */ /** @override */
send(p: string, res: Response) { send(p: string, res: Response) {
if (!config.S3_BUCKET) throw new Error("S3_BUCKET not set");
const s = this.client const s = this.client
.getObject({ .getObject({
Bucket: config.S3_BUCKET, Bucket: config.S3_BUCKET,
@@ -95,7 +104,7 @@ export default class S3Storage implements StorageBase {
}) })
.on("error", (error) => { .on("error", (error) => {
try { try {
res.status(error.statusCode); res.status(error.statusCode || 500);
} catch (err) { } catch (err) {
console.error(err); console.error(err);
} }
@@ -114,6 +123,7 @@ export default class S3Storage implements StorageBase {
/** @override */ /** @override */
read(path: string): Readable { read(path: string): Readable {
if (!config.S3_BUCKET) throw new Error("S3_BUCKET not set");
return this.client return this.client
.getObject({ .getObject({
Bucket: config.S3_BUCKET, Bucket: config.S3_BUCKET,
@@ -129,6 +139,7 @@ export default class S3Storage implements StorageBase {
file?: AnonymizedFile, file?: AnonymizedFile,
source?: SourceBase source?: SourceBase
): Promise<void> { ): Promise<void> {
if (!config.S3_BUCKET) throw new Error("S3_BUCKET not set");
const params: S3.PutObjectRequest = { const params: S3.PutObjectRequest = {
Bucket: config.S3_BUCKET, Bucket: config.S3_BUCKET,
Key: path, Key: path,
@@ -144,6 +155,7 @@ export default class S3Storage implements StorageBase {
/** @override */ /** @override */
async listFiles(dir: string): Promise<Tree> { async listFiles(dir: string): Promise<Tree> {
if (!config.S3_BUCKET) throw new Error("S3_BUCKET not set");
if (dir && dir[dir.length - 1] != "/") dir = dir + "/"; if (dir && dir[dir.length - 1] != "/") dir = dir + "/";
const out: Tree = {}; const out: Tree = {};
const req = await this.client const req = await this.client
@@ -168,9 +180,11 @@ export default class S3Storage implements StorageBase {
current = current[p] as Tree; current = current[p] as Tree;
} }
const fileInfo: TreeFile = { size: f.Size || 0, sha: f.ETag }; if (f.ETag) {
const fileName = paths[paths.length - 1]; const fileInfo: TreeFile = { size: f.Size || 0, sha: f.ETag };
if (fileName) current[fileName] = fileInfo; const fileName = paths[paths.length - 1];
if (fileName) current[fileName] = fileInfo;
}
} }
return out; return out;
} }
@@ -185,6 +199,7 @@ export default class S3Storage implements StorageBase {
let toS3: ArchiveStreamToS3; let toS3: ArchiveStreamToS3;
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if (!config.S3_BUCKET) return reject("S3_BUCKET not set");
toS3 = new ArchiveStreamToS3({ toS3 = new ArchiveStreamToS3({
bucket: config.S3_BUCKET, bucket: config.S3_BUCKET,
prefix: p, prefix: p,
@@ -208,10 +223,11 @@ export default class S3Storage implements StorageBase {
dir: string, dir: string,
opt?: { opt?: {
format?: "zip" | "tar"; format?: "zip" | "tar";
fileTransformer?; fileTransformer?: (p: string) => Transform;
} }
) { ) {
const archive = archiver(opt?.format, {}); if (!config.S3_BUCKET) throw new Error("S3_BUCKET not set");
const archive = archiver(opt?.format || "zip", {});
if (dir && dir[dir.length - 1] != "/") dir = dir + "/"; if (dir && dir[dir.length - 1] != "/") dir = dir + "/";
const req = this.client.listObjectsV2({ const req = this.client.listObjectsV2({
Bucket: config.S3_BUCKET, Bucket: config.S3_BUCKET,
+5 -5
View File
@@ -4,7 +4,7 @@ import Zip from "./source/Zip";
import S3Storage from "./storage/S3"; import S3Storage from "./storage/S3";
import FileSystem from "./storage/FileSystem"; import FileSystem from "./storage/FileSystem";
import AnonymizedFile from "./AnonymizedFile"; import AnonymizedFile from "./AnonymizedFile";
import * as stream from "stream"; import { Transform, Readable } from "stream";
import * as archiver from "archiver"; import * as archiver from "archiver";
import { Response } from "express"; import { Response } from "express";
@@ -20,7 +20,7 @@ export interface SourceBase {
* Retrieve the fie content * Retrieve the fie content
* @param file the file of the content to retrieve * @param file the file of the content to retrieve
*/ */
getFileContent(file: AnonymizedFile): Promise<stream.Readable>; getFileContent(file: AnonymizedFile): Promise<Readable>;
/** /**
* Get all the files from a specific source * Get all the files from a specific source
@@ -50,7 +50,7 @@ export interface StorageBase {
* Read the content of a file * Read the content of a file
* @param path the path to the file * @param path the path to the file
*/ */
read(path: string): stream.Readable; read(path: string): Readable;
/** /**
* Write data to a file * Write data to a file
@@ -81,7 +81,7 @@ export interface StorageBase {
*/ */
extractZip( extractZip(
dir: string, dir: string,
tar: stream.Readable, tar: Readable,
file?: AnonymizedFile, file?: AnonymizedFile,
source?: SourceBase source?: SourceBase
): Promise<void>; ): Promise<void>;
@@ -107,7 +107,7 @@ export interface StorageBase {
/** /**
* Transformer to apply on the content of the file * Transformer to apply on the content of the file
*/ */
fileTransformer?: (p: any) => Transformer; fileTransformer?: (p: string) => Transform;
} }
): archiver.Archiver; ): archiver.Archiver;
+6 -2
View File
@@ -3,14 +3,18 @@
"transpileOnly": true "transpileOnly": true
}, },
"compilerOptions": { "compilerOptions": {
"types": ["node"],
"lib": ["es6"],
"target": "es6", "target": "es6",
"module": "commonjs", "module": "CommonJS",
"outDir": "build", "outDir": "build",
"removeComments": true, "removeComments": true,
"preserveConstEnums": true, "preserveConstEnums": true,
"forceConsistentCasingInFileNames": true, "forceConsistentCasingInFileNames": true,
"sourceMap": false, "sourceMap": false,
"skipLibCheck": true "skipLibCheck": true,
"strict": true,
"esModuleInterop": false
}, },
"include": ["src/**/*.ts", "index.ts", "cli.ts"], "include": ["src/**/*.ts", "index.ts", "cli.ts"],
"exclude": ["node_modules", ".vscode"] "exclude": ["node_modules", ".vscode"]