mirror of
https://github.com/tdurieux/anonymous_github.git
synced 2026-07-06 13:47:59 +02:00
feat: introduce streamers that handle the stream and anonymization from github
This commit is contained in:
+1
-3
@@ -13,10 +13,8 @@ COPY healthcheck.js .
|
|||||||
|
|
||||||
COPY src ./src
|
COPY src ./src
|
||||||
COPY public ./public
|
COPY public ./public
|
||||||
COPY index.ts .
|
|
||||||
COPY config.ts .
|
|
||||||
|
|
||||||
RUN npm install && npm run build && npm cache clean --force
|
RUN npm install && npm run build && npm cache clean --force
|
||||||
COPY opentelemetry.js .
|
COPY opentelemetry.js .
|
||||||
|
|
||||||
CMD [ "node", "--require", "./opentelemetry.js", "./build/index.js"]
|
CMD [ "node", "--require", "./opentelemetry.js", "./build/server/index.js"]
|
||||||
+31
-14
@@ -5,17 +5,17 @@ services:
|
|||||||
build: .
|
build: .
|
||||||
restart: always
|
restart: always
|
||||||
image: tdurieux/anonymous_github:v2
|
image: tdurieux/anonymous_github:v2
|
||||||
deploy:
|
ports:
|
||||||
mode: replicated
|
- $EXPOSED_PORT:5000
|
||||||
replicas: 4
|
|
||||||
endpoint_mode: vip
|
|
||||||
env_file:
|
env_file:
|
||||||
- ./.env
|
- ./.env
|
||||||
volumes:
|
volumes:
|
||||||
- ./repositories:/app/build/repositories/
|
- ./repositories:/app/build/repositories/
|
||||||
environment:
|
environment:
|
||||||
|
- PORT=5000
|
||||||
- REDIS_HOSTNAME=redis
|
- REDIS_HOSTNAME=redis
|
||||||
- DB_HOSTNAME=mongodb
|
- DB_HOSTNAME=mongodb
|
||||||
|
- STREAMER_ENTRYPOINT=http://streamer:5000/
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test:
|
test:
|
||||||
- CMD
|
- CMD
|
||||||
@@ -27,22 +27,37 @@ services:
|
|||||||
links:
|
links:
|
||||||
- mongodb
|
- mongodb
|
||||||
- redis
|
- redis
|
||||||
- opentelemetry
|
- streamer
|
||||||
|
|
||||||
nginx:
|
streamer:
|
||||||
image: nginx:stable-alpine
|
build: .
|
||||||
|
restart: always
|
||||||
|
image: tdurieux/anonymous_github:v2
|
||||||
|
deploy:
|
||||||
|
mode: replicated
|
||||||
|
replicas: 4
|
||||||
|
endpoint_mode: vip
|
||||||
|
entrypoint: ["node", "./build/streamer/index.js"]
|
||||||
env_file:
|
env_file:
|
||||||
- ./.env
|
- ./.env
|
||||||
volumes:
|
volumes:
|
||||||
- ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
|
- ./repositories:/app/build/repositories/
|
||||||
depends_on:
|
environment:
|
||||||
- anonymous_github
|
- PORT=5000
|
||||||
ports:
|
healthcheck:
|
||||||
- "$EXPOSED_PORT:4000"
|
test:
|
||||||
|
- CMD
|
||||||
|
- node
|
||||||
|
- healthcheck.js
|
||||||
|
interval: 10s
|
||||||
|
timeout: 10s
|
||||||
|
retries: 5
|
||||||
|
|
||||||
redis:
|
redis:
|
||||||
image: "redis:alpine"
|
image: "redis:alpine"
|
||||||
restart: always
|
restart: always
|
||||||
|
ports:
|
||||||
|
- 127.0.0.1:6379:6379
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test:
|
test:
|
||||||
- CMD
|
- CMD
|
||||||
@@ -101,13 +116,15 @@ services:
|
|||||||
image: tiredofit/db-backup
|
image: tiredofit/db-backup
|
||||||
links:
|
links:
|
||||||
- mongodb
|
- mongodb
|
||||||
|
env_file:
|
||||||
|
- ./.env
|
||||||
volumes:
|
volumes:
|
||||||
- ./db_backups:/backup
|
- ./db_backups:/backup
|
||||||
environment:
|
environment:
|
||||||
- DB_TYPE=mongo
|
- DB_TYPE=mongo
|
||||||
- DB_HOST=mongodb
|
- DB_HOST=mongodb
|
||||||
- DB_DUMP_FREQ=60
|
- DB_DUMP_FREQ=120
|
||||||
- DB_CLEANUP_TIME=240
|
- DB_CLEANUP_TIME=500
|
||||||
- COMPRESSION=XZ
|
- COMPRESSION=XZ
|
||||||
- DB_USER=$DB_USERNAME
|
- DB_USER=$DB_USERNAME
|
||||||
- DB_PASS=$DB_PASSWORD
|
- DB_PASS=$DB_PASSWORD
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
import { config } from "dotenv";
|
|
||||||
config();
|
|
||||||
|
|
||||||
import server from "./src/server";
|
|
||||||
|
|
||||||
// start the server
|
|
||||||
server();
|
|
||||||
-15
@@ -1,15 +0,0 @@
|
|||||||
upstream backend {
|
|
||||||
server anonymous_github:5000;
|
|
||||||
}
|
|
||||||
|
|
||||||
server {
|
|
||||||
listen 4000;
|
|
||||||
|
|
||||||
resolver 127.0.0.11 valid=5s;
|
|
||||||
|
|
||||||
include /etc/nginx/mime.types;
|
|
||||||
|
|
||||||
location / {
|
|
||||||
proxy_pass http://backend/;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+3
-4
@@ -3,13 +3,12 @@
|
|||||||
"version": "2.2.0",
|
"version": "2.2.0",
|
||||||
"description": "Anonymise Github repositories for double-anonymous reviews",
|
"description": "Anonymise Github repositories for double-anonymous reviews",
|
||||||
"bin": {
|
"bin": {
|
||||||
"anonymous_github": "build/cli.js"
|
"anonymous_github": "build/cli/index.js"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "mocha --reporter spec",
|
"test": "mocha --reporter spec",
|
||||||
"start": "node --inspect=5858 -r ts-node/register ./index.ts",
|
"start": "node --inspect=5858 -r ts-node/register ./src/server/index.ts",
|
||||||
"dev": "nodemon --transpile-only index.ts",
|
"dev": "nodemon --transpile-only ./src/server/index.ts",
|
||||||
"migrateDB": "ts-node --transpile-only migrateDB.ts",
|
|
||||||
"build": "rm -rf build && tsc",
|
"build": "rm -rf build && tsc",
|
||||||
"knip": "knip"
|
"knip": "knip"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -10,12 +10,12 @@ import { join } from "path";
|
|||||||
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 server from "../server";
|
||||||
import config from "./config";
|
import config from "../config";
|
||||||
import GitHubDownload from "./src/source/GitHubDownload";
|
import GitHubDownload from "../core/source/GitHubDownload";
|
||||||
import Repository from "./src/Repository";
|
import Repository from "../core/Repository";
|
||||||
import AnonymizedRepositoryModel from "./src/database/anonymizedRepositories/anonymizedRepositories.model";
|
import AnonymizedRepositoryModel from "../core/model/anonymizedRepositories/anonymizedRepositories.model";
|
||||||
import { getRepositoryFromGitHub } from "./src/source/GitHubRepository";
|
import { getRepositoryFromGitHub } from "../core/source/GitHubRepository";
|
||||||
|
|
||||||
function generateRandomFileName(size: number) {
|
function generateRandomFileName(size: number) {
|
||||||
const characters =
|
const characters =
|
||||||
+13
-11
@@ -18,6 +18,7 @@ interface Config {
|
|||||||
* Allow to download repository and files
|
* Allow to download repository and files
|
||||||
*/
|
*/
|
||||||
ENABLE_DOWNLOAD: boolean;
|
ENABLE_DOWNLOAD: boolean;
|
||||||
|
STREAMER_ENTRYPOINT: string | null;
|
||||||
ANONYMIZATION_MASK: string;
|
ANONYMIZATION_MASK: string;
|
||||||
PORT: number;
|
PORT: number;
|
||||||
APP_HOSTNAME: string;
|
APP_HOSTNAME: string;
|
||||||
@@ -26,11 +27,11 @@ interface Config {
|
|||||||
DB_HOSTNAME: string;
|
DB_HOSTNAME: string;
|
||||||
FOLDER: string;
|
FOLDER: string;
|
||||||
additionalExtensions: string[];
|
additionalExtensions: string[];
|
||||||
S3_BUCKET?: string;
|
S3_BUCKET: string | null;
|
||||||
S3_CLIENT_ID?: string;
|
S3_CLIENT_ID: string | null;
|
||||||
S3_CLIENT_SECRET?: string;
|
S3_CLIENT_SECRET: string | null;
|
||||||
S3_ENDPOINT?: string;
|
S3_ENDPOINT: string | null;
|
||||||
S3_REGION?: string;
|
S3_REGION: string | null;
|
||||||
STORAGE: "filesystem" | "s3";
|
STORAGE: "filesystem" | "s3";
|
||||||
TRUST_PROXY: number;
|
TRUST_PROXY: number;
|
||||||
RATE_LIMIT: number;
|
RATE_LIMIT: number;
|
||||||
@@ -58,7 +59,7 @@ const config: Config = {
|
|||||||
DB_HOSTNAME: "mongodb",
|
DB_HOSTNAME: "mongodb",
|
||||||
REDIS_HOSTNAME: "redis",
|
REDIS_HOSTNAME: "redis",
|
||||||
REDIS_PORT: 6379,
|
REDIS_PORT: 6379,
|
||||||
FOLDER: resolve(__dirname, "repositories"),
|
FOLDER: resolve(__dirname, "..", "repositories"),
|
||||||
additionalExtensions: [
|
additionalExtensions: [
|
||||||
"license",
|
"license",
|
||||||
"dockerfile",
|
"dockerfile",
|
||||||
@@ -70,11 +71,12 @@ const config: Config = {
|
|||||||
"in",
|
"in",
|
||||||
],
|
],
|
||||||
STORAGE: "filesystem",
|
STORAGE: "filesystem",
|
||||||
S3_BUCKET: process.env.S3_BUCKET,
|
STREAMER_ENTRYPOINT: null,
|
||||||
S3_CLIENT_ID: process.env.S3_CLIENT_ID,
|
S3_BUCKET: null,
|
||||||
S3_CLIENT_SECRET: process.env.S3_CLIENT_SECRET,
|
S3_CLIENT_ID: null,
|
||||||
S3_ENDPOINT: process.env.S3_ENDPOINT,
|
S3_CLIENT_SECRET: null,
|
||||||
S3_REGION: process.env.S3_REGION,
|
S3_ENDPOINT: null,
|
||||||
|
S3_REGION: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
for (let conf in process.env) {
|
for (let conf in process.env) {
|
||||||
@@ -2,13 +2,15 @@ import { join, basename } from "path";
|
|||||||
import { Response } from "express";
|
import { Response } from "express";
|
||||||
import { Readable } from "stream";
|
import { Readable } from "stream";
|
||||||
import { trace } from "@opentelemetry/api";
|
import { trace } from "@opentelemetry/api";
|
||||||
|
import { lookup } from "mime-types";
|
||||||
|
|
||||||
import Repository from "./Repository";
|
import Repository from "./Repository";
|
||||||
import { RepositoryStatus, Tree, TreeElement, TreeFile } from "./types";
|
import { RepositoryStatus, Tree, TreeElement, TreeFile } from "./types";
|
||||||
import config from "../config";
|
import config from "../config";
|
||||||
import { anonymizePath, isTextFile } from "./anonymize-utils";
|
import { anonymizePath, isTextFile } from "./anonymize-utils";
|
||||||
import AnonymousError from "./AnonymousError";
|
import AnonymousError from "./AnonymousError";
|
||||||
import { handleError } from "./routes/route-utils";
|
import { handleError } from "../server/routes/route-utils";
|
||||||
import { lookup } from "mime-types";
|
import got from "got";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represent a file in a anonymized repository
|
* Represent a file in a anonymized repository
|
||||||
@@ -197,7 +199,7 @@ export default class AnonymizedFile {
|
|||||||
httpStatus: 403,
|
httpStatus: 403,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const out = await this.repository.source?.getFileContent(this);
|
const content = await this.repository.source?.getFileContent(this);
|
||||||
if (
|
if (
|
||||||
!this.repository.model.isReseted ||
|
!this.repository.model.isReseted ||
|
||||||
this.repository.status != RepositoryStatus.READY
|
this.repository.status != RepositoryStatus.READY
|
||||||
@@ -205,7 +207,7 @@ export default class AnonymizedFile {
|
|||||||
this.repository.model.isReseted = false;
|
this.repository.model.isReseted = false;
|
||||||
await this.repository.updateStatus(RepositoryStatus.READY);
|
await this.repository.updateStatus(RepositoryStatus.READY);
|
||||||
}
|
}
|
||||||
return out;
|
return content;
|
||||||
} finally {
|
} finally {
|
||||||
span.end();
|
span.end();
|
||||||
}
|
}
|
||||||
@@ -213,19 +215,32 @@ export default class AnonymizedFile {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async anonymizedContent() {
|
async anonymizedContent() {
|
||||||
return trace
|
const span = trace.getTracer("ano-file").startSpan("Repository.conference");
|
||||||
.getTracer("ano-file")
|
span.setAttribute("anonymizedPath", this.anonymizedPath);
|
||||||
.startActiveSpan("anonymizedContent", async (span) => {
|
const anonymizer = this.repository.generateAnonymizeTransformer(
|
||||||
span.setAttribute("anonymizedPath", this.anonymizedPath);
|
this.anonymizedPath
|
||||||
const content = await this.content();
|
);
|
||||||
return content
|
if (!config.STREAMER_ENTRYPOINT) {
|
||||||
.pipe(
|
// collect the content locally
|
||||||
this.repository.generateAnonymizeTransformer(this.anonymizedPath)
|
const content = await this.content();
|
||||||
)
|
return content.pipe(anonymizer).on("close", () => {
|
||||||
.on("close", () => {
|
span.end();
|
||||||
span.end();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
// use the streamer service
|
||||||
|
return got.stream(join(config.STREAMER_ENTRYPOINT, "api"), {
|
||||||
|
method: "POST",
|
||||||
|
json: {
|
||||||
|
token: await this.repository.getToken(),
|
||||||
|
repoFullName: this.repository.model.source.repositoryName,
|
||||||
|
commit: this.repository.model.source.commit,
|
||||||
|
branch: this.repository.model.source.branch,
|
||||||
|
repoId: this.repository.repoId,
|
||||||
|
filePath: this.filePath,
|
||||||
|
sha: await this.sha(),
|
||||||
|
anonymizerOptions: anonymizer.opt,
|
||||||
|
},
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
get filePath() {
|
get filePath() {
|
||||||
@@ -243,6 +258,9 @@ export default class AnonymizedFile {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async send(res: Response): Promise<void> {
|
async send(res: Response): Promise<void> {
|
||||||
|
const anonymizer = this.repository.generateAnonymizeTransformer(
|
||||||
|
this.anonymizedPath
|
||||||
|
);
|
||||||
return trace
|
return trace
|
||||||
.getTracer("ano-file")
|
.getTracer("ano-file")
|
||||||
.startActiveSpan("AnonymizedFile.send", async (span) => {
|
.startActiveSpan("AnonymizedFile.send", async (span) => {
|
||||||
@@ -250,6 +268,39 @@ export default class AnonymizedFile {
|
|||||||
span.setAttribute("anonymizedPath", this.anonymizedPath);
|
span.setAttribute("anonymizedPath", this.anonymizedPath);
|
||||||
return new Promise<void>(async (resolve, reject) => {
|
return new Promise<void>(async (resolve, reject) => {
|
||||||
try {
|
try {
|
||||||
|
if (config.STREAMER_ENTRYPOINT) {
|
||||||
|
// use the streamer service
|
||||||
|
got
|
||||||
|
.stream(join(config.STREAMER_ENTRYPOINT, "api"), {
|
||||||
|
method: "POST",
|
||||||
|
json: {
|
||||||
|
token: await this.repository.getToken(),
|
||||||
|
repoFullName: this.repository.model.source.repositoryName,
|
||||||
|
commit: this.repository.model.source.commit,
|
||||||
|
branch: this.repository.model.source.branch,
|
||||||
|
repoId: this.repository.repoId,
|
||||||
|
filePath: this.filePath,
|
||||||
|
sha: await this.sha(),
|
||||||
|
anonymizerOptions: anonymizer.opt,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.on("error", () => {
|
||||||
|
handleError(
|
||||||
|
new AnonymousError("file_not_found", {
|
||||||
|
object: this,
|
||||||
|
httpStatus: 404,
|
||||||
|
}),
|
||||||
|
res
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.pipe(res)
|
||||||
|
.on("close", () => {
|
||||||
|
span.end();
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const mime = lookup(this.anonymizedPath);
|
const mime = lookup(this.anonymizedPath);
|
||||||
if (mime && this.extension() != "ts") {
|
if (mime && this.extension() != "ts") {
|
||||||
res.contentType(mime);
|
res.contentType(mime);
|
||||||
@@ -257,9 +308,6 @@ export default class AnonymizedFile {
|
|||||||
res.contentType("text/plain");
|
res.contentType("text/plain");
|
||||||
}
|
}
|
||||||
res.header("Accept-Ranges", "none");
|
res.header("Accept-Ranges", "none");
|
||||||
const anonymizer = this.repository.generateAnonymizeTransformer(
|
|
||||||
this.anonymizedPath
|
|
||||||
);
|
|
||||||
anonymizer.once("transform", (data) => {
|
anonymizer.once("transform", (data) => {
|
||||||
if (!mime && data.isText) {
|
if (!mime && data.isText) {
|
||||||
res.contentType("text/plain");
|
res.contentType("text/plain");
|
||||||
@@ -269,26 +317,27 @@ export default class AnonymizedFile {
|
|||||||
res.header("Content-Length", this.fileSize.toString());
|
res.header("Content-Length", this.fileSize.toString());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const content = await this.content();
|
const content = await this.content();
|
||||||
|
function handleStreamError(error: Error) {
|
||||||
|
if (!content.closed && !content.destroyed) {
|
||||||
|
content.destroy();
|
||||||
|
}
|
||||||
|
span.recordException(error);
|
||||||
|
span.end();
|
||||||
|
reject(error);
|
||||||
|
// handleError(error, res);
|
||||||
|
}
|
||||||
content
|
content
|
||||||
|
.on("error", handleStreamError)
|
||||||
.pipe(anonymizer)
|
.pipe(anonymizer)
|
||||||
.pipe(res)
|
.pipe(res)
|
||||||
|
.on("error", handleStreamError)
|
||||||
.on("close", () => {
|
.on("close", () => {
|
||||||
if (!content.closed && !content.destroyed) {
|
if (!content.closed && !content.destroyed) {
|
||||||
content.destroy();
|
content.destroy();
|
||||||
}
|
}
|
||||||
span.end();
|
span.end();
|
||||||
resolve();
|
resolve();
|
||||||
})
|
|
||||||
.on("error", (error) => {
|
|
||||||
if (!content.closed && !content.destroyed) {
|
|
||||||
content.destroy();
|
|
||||||
}
|
|
||||||
span.recordException(error);
|
|
||||||
span.end();
|
|
||||||
reject(error);
|
|
||||||
handleError(error, res);
|
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
handleError(error, res);
|
handleError(error, res);
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import AnonymizedRepositoryModel from "./database/anonymizedRepositories/anonymizedRepositories.model";
|
import AnonymizedRepositoryModel from "./model/anonymizedRepositories/anonymizedRepositories.model";
|
||||||
import { IConferenceDocument } from "./database/conference/conferences.types";
|
import { IConferenceDocument } from "./model/conference/conferences.types";
|
||||||
import Repository from "./Repository";
|
import Repository from "./Repository";
|
||||||
import { ConferenceStatus } from "./types";
|
import { ConferenceStatus } from "./types";
|
||||||
|
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
import { trace } from "@opentelemetry/api";
|
import { trace } from "@opentelemetry/api";
|
||||||
import Repository from "./Repository";
|
|
||||||
import { Octokit } from "@octokit/rest";
|
import { Octokit } from "@octokit/rest";
|
||||||
import UserModel from "./database/users/users.model";
|
|
||||||
|
import Repository from "./Repository";
|
||||||
|
import UserModel from "./model/users/users.model";
|
||||||
import config from "../config";
|
import config from "../config";
|
||||||
|
|
||||||
export function octokit(token: string) {
|
export function octokit(token: string) {
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
import { RepositoryStatus } from "./types";
|
import { RepositoryStatus } from "./types";
|
||||||
import User from "./User";
|
import User from "./User";
|
||||||
import UserModel from "./database/users/users.model";
|
import UserModel from "./model/users/users.model";
|
||||||
import Conference from "./Conference";
|
import Conference from "./Conference";
|
||||||
import ConferenceModel from "./database/conference/conferences.model";
|
import ConferenceModel from "./model/conference/conferences.model";
|
||||||
import AnonymousError from "./AnonymousError";
|
import AnonymousError from "./AnonymousError";
|
||||||
import { IAnonymizedPullRequestDocument } from "./database/anonymizedPullRequests/anonymizedPullRequests.types";
|
import { IAnonymizedPullRequestDocument } from "./model/anonymizedPullRequests/anonymizedPullRequests.types";
|
||||||
import config from "../config";
|
import config from "../config";
|
||||||
import got from "got";
|
import got from "got";
|
||||||
import { octokit } from "./GitHubUtils";
|
import { octokit } from "./GitHubUtils";
|
||||||
@@ -6,16 +6,16 @@ import GitHubStream from "./source/GitHubStream";
|
|||||||
import GitHubDownload from "./source/GitHubDownload";
|
import GitHubDownload from "./source/GitHubDownload";
|
||||||
import Zip from "./source/Zip";
|
import Zip from "./source/Zip";
|
||||||
import { anonymizePath } from "./anonymize-utils";
|
import { anonymizePath } from "./anonymize-utils";
|
||||||
import UserModel from "./database/users/users.model";
|
import UserModel from "./model/users/users.model";
|
||||||
import { IAnonymizedRepositoryDocument } from "./database/anonymizedRepositories/anonymizedRepositories.types";
|
import { IAnonymizedRepositoryDocument } from "./model/anonymizedRepositories/anonymizedRepositories.types";
|
||||||
import { AnonymizeTransformer } from "./anonymize-utils";
|
import { AnonymizeTransformer } from "./anonymize-utils";
|
||||||
import GitHubBase from "./source/GitHubBase";
|
import GitHubBase from "./source/GitHubBase";
|
||||||
import Conference from "./Conference";
|
import Conference from "./Conference";
|
||||||
import ConferenceModel from "./database/conference/conferences.model";
|
import ConferenceModel from "./model/conference/conferences.model";
|
||||||
import AnonymousError from "./AnonymousError";
|
import AnonymousError from "./AnonymousError";
|
||||||
import { downloadQueue } from "./queue";
|
import { downloadQueue } from "../queue";
|
||||||
import { isConnected } from "./database/database";
|
import { isConnected } from "../server/database";
|
||||||
import AnonymizedRepositoryModel from "./database/anonymizedRepositories/anonymizedRepositories.model";
|
import AnonymizedRepositoryModel from "./model/anonymizedRepositories/anonymizedRepositories.model";
|
||||||
import { GitHubRepository } from "./source/GitHubRepository";
|
import { GitHubRepository } from "./source/GitHubRepository";
|
||||||
import { trace } from "@opentelemetry/api";
|
import { trace } from "@opentelemetry/api";
|
||||||
import { getToken } from "./GitHubUtils";
|
import { getToken } from "./GitHubUtils";
|
||||||
@@ -59,7 +59,7 @@ export default class Repository {
|
|||||||
|
|
||||||
private checkedToken: boolean = false;
|
private checkedToken: boolean = false;
|
||||||
|
|
||||||
private async getToken() {
|
async getToken() {
|
||||||
if (this.checkedToken) return this._model.source.accessToken as string;
|
if (this.checkedToken) return this._model.source.accessToken as string;
|
||||||
const originalToken = this._model.source.accessToken;
|
const originalToken = this._model.source.accessToken;
|
||||||
const token = await getToken(this);
|
const token = await getToken(this);
|
||||||
@@ -1,11 +1,12 @@
|
|||||||
import AnonymizedRepositoryModel from "./database/anonymizedRepositories/anonymizedRepositories.model";
|
import { trace } from "@opentelemetry/api";
|
||||||
import RepositoryModel from "./database/repositories/repositories.model";
|
|
||||||
import { IUserDocument } from "./database/users/users.types";
|
import AnonymizedRepositoryModel from "./model/anonymizedRepositories/anonymizedRepositories.model";
|
||||||
|
import RepositoryModel from "./model/repositories/repositories.model";
|
||||||
|
import { IUserDocument } from "./model/users/users.types";
|
||||||
import Repository from "./Repository";
|
import Repository from "./Repository";
|
||||||
import { GitHubRepository } from "./source/GitHubRepository";
|
import { GitHubRepository } from "./source/GitHubRepository";
|
||||||
import PullRequest from "./PullRequest";
|
import PullRequest from "./PullRequest";
|
||||||
import AnonymizedPullRequestModel from "./database/anonymizedPullRequests/anonymizedPullRequests.model";
|
import AnonymizedPullRequestModel from "./model/anonymizedPullRequests/anonymizedPullRequests.model";
|
||||||
import { trace } from "@opentelemetry/api";
|
|
||||||
import { octokit } from "./GitHubUtils";
|
import { octokit } from "./GitHubUtils";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
import config from "../config";
|
|
||||||
import { isText } from "istextorbinary";
|
|
||||||
import { basename } from "path";
|
import { basename } from "path";
|
||||||
import { Transform } from "stream";
|
import { Transform, Readable } from "stream";
|
||||||
import { Readable } from "stream";
|
import { isText } from "istextorbinary";
|
||||||
import { trace } from "@opentelemetry/api";
|
import { trace } from "@opentelemetry/api";
|
||||||
|
|
||||||
|
import config from "../config";
|
||||||
|
|
||||||
const urlRegex =
|
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;
|
||||||
|
|
||||||
@@ -35,7 +35,7 @@ export class AnonymizeTransformer extends Transform {
|
|||||||
anonimizer: ContentAnonimizer;
|
anonimizer: ContentAnonimizer;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly opt: {
|
readonly opt: {
|
||||||
filePath: string;
|
filePath: string;
|
||||||
} & ConstructorParameters<typeof ContentAnonimizer>[0]
|
} & ConstructorParameters<typeof ContentAnonimizer>[0]
|
||||||
) {
|
) {
|
||||||
@@ -56,7 +56,6 @@ export class AnonymizeTransformer extends Transform {
|
|||||||
if (this.isText === null) {
|
if (this.isText === null) {
|
||||||
this.isText = isTextFile(this.opt.filePath, chunk);
|
this.isText = isTextFile(this.opt.filePath, chunk);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.isText) {
|
if (this.isText) {
|
||||||
const content = this.anonimizer.anonymize(chunk.toString());
|
const content = this.anonimizer.anonymize(chunk.toString());
|
||||||
if (this.anonimizer.wasAnonymized) {
|
if (this.anonimizer.wasAnonymized) {
|
||||||
@@ -171,7 +170,7 @@ export class ContentAnonimizer {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// remove the term in the text
|
// remove the term in the text
|
||||||
return content.replace(new RegExp(`\\b${term}\\b`, "gi"), () => {
|
content = content.replace(new RegExp(`\\b${term}\\b`, "gi"), () => {
|
||||||
this.wasAnonymized = true;
|
this.wasAnonymized = true;
|
||||||
return mask;
|
return mask;
|
||||||
});
|
});
|
||||||
@@ -2,6 +2,7 @@ import { Readable } from "stream";
|
|||||||
|
|
||||||
import AnonymizedFile from "../AnonymizedFile";
|
import AnonymizedFile from "../AnonymizedFile";
|
||||||
import { Tree } from "../types";
|
import { Tree } from "../types";
|
||||||
|
import { SourceBase } from "./Source";
|
||||||
|
|
||||||
export interface GitHubBaseData {
|
export interface GitHubBaseData {
|
||||||
getToken: () => string | Promise<string>;
|
getToken: () => string | Promise<string>;
|
||||||
@@ -11,21 +12,6 @@ export interface GitHubBaseData {
|
|||||||
commit: string;
|
commit: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SourceBase {
|
|
||||||
readonly type: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve the fie content
|
|
||||||
* @param file the file of the content to retrieve
|
|
||||||
*/
|
|
||||||
getFileContent(file: AnonymizedFile): Promise<Readable>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get all the files from a specific source
|
|
||||||
*/
|
|
||||||
getFiles(progress?: (status: string) => void): Promise<Tree>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default abstract class GitHubBase implements SourceBase {
|
export default abstract class GitHubBase implements SourceBase {
|
||||||
abstract type: "GitHubDownload" | "GitHubStream" | "Zip";
|
abstract type: "GitHubDownload" | "GitHubStream" | "Zip";
|
||||||
accessToken: string | undefined;
|
accessToken: string | undefined;
|
||||||
@@ -4,11 +4,11 @@ import { OctokitResponse } from "@octokit/types";
|
|||||||
|
|
||||||
import storage from "../storage";
|
import storage from "../storage";
|
||||||
import GitHubBase, { GitHubBaseData } from "./GitHubBase";
|
import GitHubBase, { GitHubBaseData } from "./GitHubBase";
|
||||||
import AnonymizedFile from "../AnonymizedFile";
|
|
||||||
import AnonymousError from "../AnonymousError";
|
|
||||||
import { trace } from "@opentelemetry/api";
|
import { trace } from "@opentelemetry/api";
|
||||||
import { FILE_TYPE } from "../storage/Storage";
|
import { FILE_TYPE } from "../storage/Storage";
|
||||||
import { octokit } from "../GitHubUtils";
|
import { octokit } from "../GitHubUtils";
|
||||||
|
import AnonymousError from "../AnonymousError";
|
||||||
|
import AnonymizedFile from "../AnonymizedFile";
|
||||||
|
|
||||||
export default class GitHubDownload extends GitHubBase {
|
export default class GitHubDownload extends GitHubBase {
|
||||||
type: "GitHubDownload" | "GitHubStream" | "Zip" = "GitHubDownload";
|
type: "GitHubDownload" | "GitHubStream" | "Zip" = "GitHubDownload";
|
||||||
@@ -1,12 +1,13 @@
|
|||||||
import { Branch } from "../types";
|
import { Branch } from "../types";
|
||||||
import * as gh from "parse-github-url";
|
import * as gh from "parse-github-url";
|
||||||
import { IRepositoryDocument } from "../database/repositories/repositories.types";
|
|
||||||
import { RestEndpointMethodTypes } from "@octokit/rest";
|
import { RestEndpointMethodTypes } from "@octokit/rest";
|
||||||
import RepositoryModel from "../database/repositories/repositories.model";
|
|
||||||
import AnonymousError from "../AnonymousError";
|
|
||||||
import { isConnected } from "../database/database";
|
|
||||||
import { trace } from "@opentelemetry/api";
|
import { trace } from "@opentelemetry/api";
|
||||||
|
|
||||||
|
import AnonymousError from "../AnonymousError";
|
||||||
|
import { isConnected } from "../../server/database";
|
||||||
import { octokit } from "../GitHubUtils";
|
import { octokit } from "../GitHubUtils";
|
||||||
|
import { IRepositoryDocument } from "../model/repositories/repositories.types";
|
||||||
|
import RepositoryModel from "../model/repositories/repositories.model";
|
||||||
|
|
||||||
export class GitHubRepository {
|
export class GitHubRepository {
|
||||||
private _data: Partial<{
|
private _data: Partial<{
|
||||||
@@ -82,36 +82,24 @@ export default class GitHubStream extends GitHubBase {
|
|||||||
object: file,
|
object: file,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
try {
|
const content = this.downloadFile(await this.data.getToken(), file_sha);
|
||||||
const content = this.downloadFile(await this.data.getToken(), file_sha);
|
|
||||||
|
|
||||||
// duplicate the stream to write it to the storage
|
// duplicate the stream to write it to the storage
|
||||||
const stream1 = content.pipe(new stream.PassThrough());
|
const stream1 = content.pipe(new stream.PassThrough());
|
||||||
const stream2 = content.pipe(new stream.PassThrough());
|
const stream2 = content.pipe(new stream.PassThrough());
|
||||||
storage.write(
|
|
||||||
file.repository.repoId,
|
content.on("error", (error) => {
|
||||||
file.filePath,
|
error = new AnonymousError("file_not_found", {
|
||||||
stream1,
|
|
||||||
this.type
|
|
||||||
);
|
|
||||||
return stream2;
|
|
||||||
} catch (error) {
|
|
||||||
if (
|
|
||||||
(error as any).status === 404 ||
|
|
||||||
(error as any).httpStatus === 404
|
|
||||||
) {
|
|
||||||
throw new AnonymousError("file_not_found", {
|
|
||||||
httpStatus: (error as any).status || (error as any).httpStatus,
|
|
||||||
cause: error as Error,
|
|
||||||
object: file,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
throw new AnonymousError("file_too_big", {
|
|
||||||
httpStatus: (error as any).status || (error as any).httpStatus,
|
httpStatus: (error as any).status || (error as any).httpStatus,
|
||||||
cause: error as Error,
|
cause: error as Error,
|
||||||
object: file,
|
object: file,
|
||||||
});
|
});
|
||||||
}
|
stream1.emit("error", error);
|
||||||
|
stream2.emit("error", error);
|
||||||
|
});
|
||||||
|
|
||||||
|
storage.write(file.repository.repoId, file.filePath, stream1, this.type);
|
||||||
|
return stream2;
|
||||||
} finally {
|
} finally {
|
||||||
span.end();
|
span.end();
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
import { Readable } from "stream";
|
||||||
|
|
||||||
|
import AnonymizedFile from "../AnonymizedFile";
|
||||||
|
import { Tree } from "../types";
|
||||||
|
import GitHubDownload from "./GitHubDownload";
|
||||||
|
import GitHubStream from "./GitHubStream";
|
||||||
|
import Zip from "./Zip";
|
||||||
|
|
||||||
|
export type Source = GitHubDownload | GitHubStream | Zip;
|
||||||
|
|
||||||
|
export interface SourceBase {
|
||||||
|
readonly type: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the fie content
|
||||||
|
* @param file the file of the content to retrieve
|
||||||
|
*/
|
||||||
|
getFileContent(file: AnonymizedFile): Promise<Readable>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all the files from a specific source
|
||||||
|
*/
|
||||||
|
getFiles(progress?: (status: string) => void): Promise<Tree>;
|
||||||
|
}
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
|
import * as stream from "stream";
|
||||||
|
|
||||||
import AnonymizedFile from "../AnonymizedFile";
|
import AnonymizedFile from "../AnonymizedFile";
|
||||||
import storage from "../storage";
|
import storage from "../storage";
|
||||||
import * as stream from "stream";
|
import { SourceBase } from "./Source";
|
||||||
import { SourceBase } from "./GitHubBase";
|
|
||||||
|
|
||||||
export default class Zip implements SourceBase {
|
export default class Zip implements SourceBase {
|
||||||
type = "Zip";
|
type = "Zip";
|
||||||
@@ -83,7 +83,15 @@ export default class FileSystem extends StorageBase {
|
|||||||
span.setAttribute("path", fullPath);
|
span.setAttribute("path", fullPath);
|
||||||
try {
|
try {
|
||||||
await this.mk(repoId, dirname(p));
|
await this.mk(repoId, dirname(p));
|
||||||
|
if (data instanceof Readable) {
|
||||||
|
data.on("error", (err) => {
|
||||||
|
this.rm(repoId, p);
|
||||||
|
});
|
||||||
|
}
|
||||||
return await fs.promises.writeFile(fullPath, data, "utf-8");
|
return await fs.promises.writeFile(fullPath, data, "utf-8");
|
||||||
|
} catch (err: any) {
|
||||||
|
span.recordException(err);
|
||||||
|
// throw err;
|
||||||
} finally {
|
} finally {
|
||||||
span.end();
|
span.end();
|
||||||
}
|
}
|
||||||
@@ -32,6 +32,8 @@ export default class S3Storage extends StorageBase {
|
|||||||
private client(timeout = 10000) {
|
private client(timeout = 10000) {
|
||||||
if (!config.S3_CLIENT_ID) throw new Error("S3_CLIENT_ID not set");
|
if (!config.S3_CLIENT_ID) throw new Error("S3_CLIENT_ID not set");
|
||||||
if (!config.S3_CLIENT_SECRET) throw new Error("S3_CLIENT_SECRET not set");
|
if (!config.S3_CLIENT_SECRET) throw new Error("S3_CLIENT_SECRET not set");
|
||||||
|
if (!config.S3_REGION) throw new Error("S3_REGION not set");
|
||||||
|
if (!config.S3_ENDPOINT) throw new Error("S3_ENDPOINT not set");
|
||||||
return new S3({
|
return new S3({
|
||||||
credentials: {
|
credentials: {
|
||||||
accessKeyId: config.S3_CLIENT_ID,
|
accessKeyId: config.S3_CLIENT_ID,
|
||||||
@@ -212,6 +214,14 @@ export default class S3Storage extends StorageBase {
|
|||||||
try {
|
try {
|
||||||
if (!config.S3_BUCKET) throw new Error("S3_BUCKET not set");
|
if (!config.S3_BUCKET) throw new Error("S3_BUCKET not set");
|
||||||
|
|
||||||
|
if (data instanceof Readable) {
|
||||||
|
data.on("error", (err) => {
|
||||||
|
console.error(`[ERROR] S3 write ${path}`, err);
|
||||||
|
span.recordException(err as Error);
|
||||||
|
this.rm(repoId, path);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const params: PutObjectCommandInput = {
|
const params: PutObjectCommandInput = {
|
||||||
Bucket: config.S3_BUCKET,
|
Bucket: config.S3_BUCKET,
|
||||||
Key: join(this.repoPath(repoId), path),
|
Key: join(this.repoPath(repoId), path),
|
||||||
@@ -229,7 +239,6 @@ export default class S3Storage extends StorageBase {
|
|||||||
});
|
});
|
||||||
|
|
||||||
await parallelUploads3.done();
|
await parallelUploads3.done();
|
||||||
return;
|
|
||||||
} finally {
|
} finally {
|
||||||
span.end();
|
span.end();
|
||||||
}
|
}
|
||||||
@@ -4,6 +4,10 @@ import * as archiver from "archiver";
|
|||||||
import { Response } from "express";
|
import { Response } from "express";
|
||||||
|
|
||||||
import { Tree } from "../types";
|
import { Tree } from "../types";
|
||||||
|
import S3Storage from "./S3";
|
||||||
|
import FileSystem from "./FileSystem";
|
||||||
|
|
||||||
|
export type Storage = S3Storage | FileSystem;
|
||||||
|
|
||||||
export enum FILE_TYPE {
|
export enum FILE_TYPE {
|
||||||
FILE = "file",
|
FILE = "file",
|
||||||
@@ -1,13 +1,3 @@
|
|||||||
import GitHubDownload from "./source/GitHubDownload";
|
|
||||||
import GitHubStream from "./source/GitHubStream";
|
|
||||||
import Zip from "./source/Zip";
|
|
||||||
import S3Storage from "./storage/S3";
|
|
||||||
import FileSystem from "./storage/FileSystem";
|
|
||||||
|
|
||||||
export type Source = GitHubDownload | GitHubStream | Zip;
|
|
||||||
|
|
||||||
export type Storage = S3Storage | FileSystem;
|
|
||||||
|
|
||||||
export interface Branch {
|
export interface Branch {
|
||||||
name: string;
|
name: string;
|
||||||
commit: string;
|
commit: string;
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Queue, Worker } from "bullmq";
|
import { Queue, Worker } from "bullmq";
|
||||||
import config from "../config";
|
import config from "../config";
|
||||||
import Repository from "./Repository";
|
import Repository from "../core/Repository";
|
||||||
import * as path from "path";
|
import * as path from "path";
|
||||||
|
|
||||||
export let cacheQueue: Queue<Repository>;
|
export let cacheQueue: Queue<Repository>;
|
||||||
@@ -37,7 +37,7 @@ export function startWorker() {
|
|||||||
});
|
});
|
||||||
const cacheWorker = new Worker<Repository>(
|
const cacheWorker = new Worker<Repository>(
|
||||||
cacheQueue.name,
|
cacheQueue.name,
|
||||||
path.resolve("build/src/processes/removeCache.js"),
|
path.resolve("build/queue/processes/removeCache.js"),
|
||||||
{
|
{
|
||||||
concurrency: 5,
|
concurrency: 5,
|
||||||
connection,
|
connection,
|
||||||
@@ -49,7 +49,7 @@ export function startWorker() {
|
|||||||
});
|
});
|
||||||
const removeWorker = new Worker<Repository>(
|
const removeWorker = new Worker<Repository>(
|
||||||
removeQueue.name,
|
removeQueue.name,
|
||||||
path.resolve("build/src/processes/removeRepository.js"),
|
path.resolve("build/queue/processes/removeRepository.js"),
|
||||||
{
|
{
|
||||||
concurrency: 5,
|
concurrency: 5,
|
||||||
connection,
|
connection,
|
||||||
@@ -62,7 +62,7 @@ export function startWorker() {
|
|||||||
|
|
||||||
const downloadWorker = new Worker<Repository>(
|
const downloadWorker = new Worker<Repository>(
|
||||||
downloadQueue.name,
|
downloadQueue.name,
|
||||||
path.resolve("build/src/processes/downloadRepository.js"),
|
path.resolve("build/queue/processes/downloadRepository.js"),
|
||||||
{
|
{
|
||||||
concurrency: 3,
|
concurrency: 3,
|
||||||
connection,
|
connection,
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
|
import { Exception, trace } from "@opentelemetry/api";
|
||||||
import { SandboxedJob } from "bullmq";
|
import { SandboxedJob } from "bullmq";
|
||||||
import { config } from "dotenv";
|
import { config } from "dotenv";
|
||||||
config();
|
config();
|
||||||
import Repository from "../Repository";
|
import Repository from "../../core/Repository";
|
||||||
import { getRepository as getRepositoryImport } from "../database/database";
|
import { getRepository as getRepositoryImport } from "../../server/database";
|
||||||
import { RepositoryStatus } from "../types";
|
import { RepositoryStatus } from "../../core/types";
|
||||||
import { Exception, trace } from "@opentelemetry/api";
|
|
||||||
|
|
||||||
export default async function (job: SandboxedJob<Repository, void>) {
|
export default async function (job: SandboxedJob<Repository, void>) {
|
||||||
const {
|
const {
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import { SandboxedJob } from "bullmq";
|
|
||||||
import Repository from "../Repository";
|
|
||||||
import { getRepository as getRepositoryImport } from "../database/database";
|
|
||||||
import { Exception, trace } from "@opentelemetry/api";
|
import { Exception, trace } from "@opentelemetry/api";
|
||||||
|
import { SandboxedJob } from "bullmq";
|
||||||
|
import Repository from "../../core/Repository";
|
||||||
|
import { getRepository as getRepositoryImport } from "../../server/database";
|
||||||
|
|
||||||
export default async function (job: SandboxedJob<Repository, void>) {
|
export default async function (job: SandboxedJob<Repository, void>) {
|
||||||
const {
|
const {
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
import { SandboxedJob } from "bullmq";
|
|
||||||
import Repository from "../Repository";
|
|
||||||
import { getRepository as getRepositoryImport } from "../database/database";
|
|
||||||
import { RepositoryStatus } from "../types";
|
|
||||||
import { trace } from "@opentelemetry/api";
|
import { trace } from "@opentelemetry/api";
|
||||||
|
import { SandboxedJob } from "bullmq";
|
||||||
|
import Repository from "../../core/Repository";
|
||||||
|
import { getRepository as getRepositoryImport } from "../../server/database";
|
||||||
|
import { RepositoryStatus } from "../../core/types";
|
||||||
|
|
||||||
export default async function (job: SandboxedJob<Repository, void>) {
|
export default async function (job: SandboxedJob<Repository, void>) {
|
||||||
const {
|
const {
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
import mongoose, { ConnectOptions } from "mongoose";
|
import mongoose, { ConnectOptions } from "mongoose";
|
||||||
import Repository from "../Repository";
|
import Repository from "../core/Repository";
|
||||||
import config from "../../config";
|
import config from "../config";
|
||||||
import AnonymizedRepositoryModel from "./anonymizedRepositories/anonymizedRepositories.model";
|
import AnonymizedRepositoryModel from "../core/model/anonymizedRepositories/anonymizedRepositories.model";
|
||||||
import AnonymousError from "../AnonymousError";
|
import AnonymousError from "../core/AnonymousError";
|
||||||
import AnonymizedPullRequestModel from "./anonymizedPullRequests/anonymizedPullRequests.model";
|
import AnonymizedPullRequestModel from "../core/model/anonymizedPullRequests/anonymizedPullRequests.model";
|
||||||
import PullRequest from "../PullRequest";
|
import PullRequest from "../core/PullRequest";
|
||||||
|
|
||||||
const MONGO_URL = `mongodb://${config.DB_USERNAME}:${config.DB_PASSWORD}@${config.DB_HOSTNAME}:27017/`;
|
const MONGO_URL = `mongodb://${config.DB_USERNAME}:${config.DB_PASSWORD}@${config.DB_HOSTNAME}:27017/`;
|
||||||
|
|
||||||
@@ -1,3 +1,6 @@
|
|||||||
|
import { config as dotenv } from "dotenv";
|
||||||
|
dotenv();
|
||||||
|
|
||||||
import { createClient } from "redis";
|
import { createClient } from "redis";
|
||||||
import { resolve, join } from "path";
|
import { resolve, join } from "path";
|
||||||
import { existsSync } from "fs";
|
import { existsSync } from "fs";
|
||||||
@@ -7,16 +10,15 @@ 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 passport from "passport";
|
import * as passport from "passport";
|
||||||
|
import { connect } from "./database";
|
||||||
import config from "../config";
|
|
||||||
import { connect } from "./database/database";
|
|
||||||
import { initSession, router as connectionRouter } from "./routes/connection";
|
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 "../core/model/anonymizedRepositories/anonymizedRepositories.model";
|
||||||
import { conferenceStatusCheck, repositoryStatusCheck } from "./schedule";
|
import { conferenceStatusCheck, repositoryStatusCheck } from "./schedule";
|
||||||
import { startWorker } from "./queue";
|
import { startWorker } from "../queue";
|
||||||
import AnonymizedPullRequestModel from "./database/anonymizedPullRequests/anonymizedPullRequests.model";
|
import AnonymizedPullRequestModel from "../core/model/anonymizedPullRequests/anonymizedPullRequests.model";
|
||||||
import { getUser } from "./routes/route-utils";
|
import { getUser } from "./routes/route-utils";
|
||||||
|
import config from "../config";
|
||||||
|
|
||||||
function indexResponse(req: express.Request, res: express.Response) {
|
function indexResponse(req: express.Request, res: express.Response) {
|
||||||
if (
|
if (
|
||||||
@@ -214,3 +216,5 @@ export default async function start() {
|
|||||||
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
start();
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
import { Queue } from "bullmq";
|
import { Queue } from "bullmq";
|
||||||
import * as express from "express";
|
import * as express from "express";
|
||||||
import AnonymousError from "../AnonymousError";
|
import AnonymousError from "../../core/AnonymousError";
|
||||||
import AnonymizedRepositoryModel from "../database/anonymizedRepositories/anonymizedRepositories.model";
|
import AnonymizedRepositoryModel from "../../core/model/anonymizedRepositories/anonymizedRepositories.model";
|
||||||
import ConferenceModel from "../database/conference/conferences.model";
|
import ConferenceModel from "../../core/model/conference/conferences.model";
|
||||||
import UserModel from "../database/users/users.model";
|
import UserModel from "../../core/model/users/users.model";
|
||||||
import { cacheQueue, downloadQueue, removeQueue } from "../queue";
|
import { cacheQueue, downloadQueue, removeQueue } from "../../queue";
|
||||||
import Repository from "../Repository";
|
import Repository from "../../core/Repository";
|
||||||
import User from "../User";
|
import User from "../../core/User";
|
||||||
import { ensureAuthenticated } from "./connection";
|
import { ensureAuthenticated } from "./connection";
|
||||||
import { handleError, getUser, isOwnerOrAdmin, getRepo } from "./route-utils";
|
import { handleError, getUser, isOwnerOrAdmin, getRepo } from "./route-utils";
|
||||||
|
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
import * as express from "express";
|
import * as express from "express";
|
||||||
import AnonymousError from "../AnonymousError";
|
import AnonymousError from "../../core/AnonymousError";
|
||||||
import Conference from "../Conference";
|
import Conference from "../../core/Conference";
|
||||||
import ConferenceModel from "../database/conference/conferences.model";
|
import ConferenceModel from "../../core/model/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";
|
import { IConferenceDocument } from "../../core/model/conference/conferences.types";
|
||||||
|
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
@@ -7,9 +7,9 @@ import { Profile, Strategy } from "passport-github2";
|
|||||||
import * as express from "express";
|
import * as express from "express";
|
||||||
|
|
||||||
import config from "../../config";
|
import config from "../../config";
|
||||||
import UserModel from "../database/users/users.model";
|
import UserModel from "../../core/model/users/users.model";
|
||||||
import { IUserDocument } from "../database/users/users.types";
|
import { IUserDocument } from "../../core/model/users/users.types";
|
||||||
import AnonymousError from "../AnonymousError";
|
import AnonymousError from "../../core/AnonymousError";
|
||||||
|
|
||||||
export function ensureAuthenticated(
|
export function ensureAuthenticated(
|
||||||
req: express.Request,
|
req: express.Request,
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import * as express from "express";
|
import * as express from "express";
|
||||||
import AnonymizedFile from "../AnonymizedFile";
|
import AnonymizedFile from "../../core/AnonymizedFile";
|
||||||
import AnonymousError from "../AnonymousError";
|
import AnonymousError from "../../core/AnonymousError";
|
||||||
import { getRepo, handleError } from "./route-utils";
|
import { getRepo, handleError } from "./route-utils";
|
||||||
|
|
||||||
export const router = express.Router();
|
export const router = express.Router();
|
||||||
@@ -49,7 +49,7 @@ router.get(
|
|||||||
// cache the file for 5min
|
// cache the file for 5min
|
||||||
res.header("Cache-Control", "max-age=300");
|
res.header("Cache-Control", "max-age=300");
|
||||||
await repo.countView();
|
await repo.countView();
|
||||||
f.send(res);
|
await f.send(res);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return handleError(error, res, req);
|
return handleError(error, res, req);
|
||||||
}
|
}
|
||||||
@@ -7,11 +7,11 @@ import {
|
|||||||
handleError,
|
handleError,
|
||||||
isOwnerOrAdmin,
|
isOwnerOrAdmin,
|
||||||
} from "./route-utils";
|
} from "./route-utils";
|
||||||
import AnonymousError from "../AnonymousError";
|
import AnonymousError from "../../core/AnonymousError";
|
||||||
import { IAnonymizedPullRequestDocument } from "../database/anonymizedPullRequests/anonymizedPullRequests.types";
|
import { IAnonymizedPullRequestDocument } from "../../core/model/anonymizedPullRequests/anonymizedPullRequests.types";
|
||||||
import PullRequest from "../PullRequest";
|
import PullRequest from "../../core/PullRequest";
|
||||||
import AnonymizedPullRequestModel from "../database/anonymizedPullRequests/anonymizedPullRequests.model";
|
import AnonymizedPullRequestModel from "../../core/model/anonymizedPullRequests/anonymizedPullRequests.model";
|
||||||
import { RepositoryStatus } from "../types";
|
import { RepositoryStatus } from "../../core/types";
|
||||||
|
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import * as express from "express";
|
import * as express from "express";
|
||||||
|
|
||||||
import { getPullRequest, handleError } from "./route-utils";
|
import { getPullRequest, handleError } from "./route-utils";
|
||||||
import AnonymousError from "../AnonymousError";
|
import AnonymousError from "../../core/AnonymousError";
|
||||||
|
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
@@ -1,22 +1,22 @@
|
|||||||
import * as express from "express";
|
import * as express from "express";
|
||||||
import { ensureAuthenticated } from "./connection";
|
import { ensureAuthenticated } from "./connection";
|
||||||
|
|
||||||
import * as db from "../database/database";
|
import * as db from "../database";
|
||||||
import { getRepo, getUser, handleError, isOwnerOrAdmin } from "./route-utils";
|
import { getRepo, getUser, handleError, isOwnerOrAdmin } from "./route-utils";
|
||||||
import { getRepositoryFromGitHub } from "../source/GitHubRepository";
|
import { getRepositoryFromGitHub } from "../../core/source/GitHubRepository";
|
||||||
import gh = require("parse-github-url");
|
import gh = require("parse-github-url");
|
||||||
import AnonymizedRepositoryModel from "../database/anonymizedRepositories/anonymizedRepositories.model";
|
import AnonymizedRepositoryModel from "../../core/model/anonymizedRepositories/anonymizedRepositories.model";
|
||||||
import { IAnonymizedRepositoryDocument } from "../database/anonymizedRepositories/anonymizedRepositories.types";
|
import { IAnonymizedRepositoryDocument } from "../../core/model/anonymizedRepositories/anonymizedRepositories.types";
|
||||||
import Repository from "../Repository";
|
import Repository from "../../core/Repository";
|
||||||
import UserModel from "../database/users/users.model";
|
import UserModel from "../../core/model/users/users.model";
|
||||||
import ConferenceModel from "../database/conference/conferences.model";
|
import ConferenceModel from "../../core/model/conference/conferences.model";
|
||||||
import AnonymousError from "../AnonymousError";
|
import AnonymousError from "../../core/AnonymousError";
|
||||||
import { downloadQueue, removeQueue } from "../queue";
|
import { downloadQueue, removeQueue } from "../../queue";
|
||||||
import RepositoryModel from "../database/repositories/repositories.model";
|
import RepositoryModel from "../../core/model/repositories/repositories.model";
|
||||||
import User from "../User";
|
import User from "../../core/User";
|
||||||
import { RepositoryStatus } from "../types";
|
import { RepositoryStatus } from "../../core/types";
|
||||||
import { IUserDocument } from "../database/users/users.types";
|
import { IUserDocument } from "../../core/model/users/users.types";
|
||||||
import { checkToken } from "../GitHubUtils";
|
import { checkToken } from "../../core/GitHubUtils";
|
||||||
|
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
@@ -4,9 +4,9 @@ import * as stream from "stream";
|
|||||||
import config from "../../config";
|
import config from "../../config";
|
||||||
|
|
||||||
import { getRepo, handleError } from "./route-utils";
|
import { getRepo, handleError } from "./route-utils";
|
||||||
import AnonymousError from "../AnonymousError";
|
import AnonymousError from "../../core/AnonymousError";
|
||||||
import { downloadQueue } from "../queue";
|
import { downloadQueue } from "../../queue";
|
||||||
import { RepositoryStatus } from "../types";
|
import { RepositoryStatus } from "../../core/types";
|
||||||
|
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
@@ -1,8 +1,9 @@
|
|||||||
import * as express from "express";
|
import * as express from "express";
|
||||||
import AnonymousError from "../AnonymousError";
|
import AnonymousError from "../../core/AnonymousError";
|
||||||
import * as db from "../database/database";
|
import * as db from "../database";
|
||||||
import UserModel from "../database/users/users.model";
|
import UserModel from "../../core/model/users/users.model";
|
||||||
import User from "../User";
|
import User from "../../core/User";
|
||||||
|
import { HTTPError } from "got";
|
||||||
|
|
||||||
export async function getPullRequest(
|
export async function getPullRequest(
|
||||||
req: express.Request,
|
req: express.Request,
|
||||||
@@ -85,6 +86,11 @@ function printError(error: any, req?: express.Request) {
|
|||||||
if (req.originalUrl === "/api/repo/undefined/options") return;
|
if (req.originalUrl === "/api/repo/undefined/options") return;
|
||||||
}
|
}
|
||||||
console.error(message);
|
console.error(message);
|
||||||
|
} else if (error instanceof HTTPError) {
|
||||||
|
let message = `[ERROR] HTTP.${
|
||||||
|
error.code
|
||||||
|
} ${error.message.toString()} ${error.stack?.split("\n")[1].trim()}`;
|
||||||
|
console.error(message);
|
||||||
} else if (error instanceof Error) {
|
} else if (error instanceof Error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
} else {
|
} else {
|
||||||
@@ -107,7 +113,10 @@ export function handleError(
|
|||||||
status = error.httpStatus;
|
status = error.httpStatus;
|
||||||
} else if (error.$metadata?.httpStatusCode) {
|
} else if (error.$metadata?.httpStatusCode) {
|
||||||
status = error.$metadata.httpStatusCode;
|
status = error.$metadata.httpStatusCode;
|
||||||
} else if (message && message.indexOf("not_found") > -1) {
|
} else if (
|
||||||
|
message &&
|
||||||
|
(message.indexOf("not_found") > -1 || message.indexOf("(Not Found)") > -1)
|
||||||
|
) {
|
||||||
status = 404;
|
status = 404;
|
||||||
} else if (message && message.indexOf("not_connected") > -1) {
|
} else if (message && message.indexOf("not_connected") > -1) {
|
||||||
status = 401;
|
status = 401;
|
||||||
@@ -2,8 +2,8 @@ import * as express from "express";
|
|||||||
import config from "../../config";
|
import config from "../../config";
|
||||||
import { ensureAuthenticated } from "./connection";
|
import { ensureAuthenticated } from "./connection";
|
||||||
import { handleError, getUser, isOwnerOrAdmin } from "./route-utils";
|
import { handleError, getUser, isOwnerOrAdmin } from "./route-utils";
|
||||||
import UserModel from "../database/users/users.model";
|
import UserModel from "../../core/model/users/users.model";
|
||||||
import User from "../User";
|
import User from "../../core/User";
|
||||||
|
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
import * as express from "express";
|
import * as express from "express";
|
||||||
import { getRepo, handleError } from "./route-utils";
|
import { getRepo, handleError } from "./route-utils";
|
||||||
import * as path from "path";
|
import * as path from "path";
|
||||||
import AnonymizedFile from "../AnonymizedFile";
|
import AnonymizedFile from "../../core/AnonymizedFile";
|
||||||
import AnonymousError from "../AnonymousError";
|
import AnonymousError from "../../core/AnonymousError";
|
||||||
import { Tree, TreeElement } from "../types";
|
import { Tree, TreeElement } from "../../core/types";
|
||||||
import * as marked from "marked";
|
import * as marked from "marked";
|
||||||
import { streamToString } from "../anonymize-utils";
|
import { streamToString } from "../../core/anonymize-utils";
|
||||||
|
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
import * as schedule from "node-schedule";
|
import * as schedule from "node-schedule";
|
||||||
import Conference from "./Conference";
|
import Conference from "../core/Conference";
|
||||||
import AnonymizedRepositoryModel from "./database/anonymizedRepositories/anonymizedRepositories.model";
|
import AnonymizedRepositoryModel from "../core/model/anonymizedRepositories/anonymizedRepositories.model";
|
||||||
import ConferenceModel from "./database/conference/conferences.model";
|
import ConferenceModel from "../core/model/conference/conferences.model";
|
||||||
import Repository from "./Repository";
|
import Repository from "../core/Repository";
|
||||||
|
|
||||||
export function conferenceStatusCheck() {
|
export function conferenceStatusCheck() {
|
||||||
// check every 6 hours the status of the conferences
|
// check every 6 hours the status of the conferences
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import { config as dotenv } from "dotenv";
|
||||||
|
dotenv();
|
||||||
|
|
||||||
|
import * as express from "express";
|
||||||
|
import * as compression from "compression";
|
||||||
|
|
||||||
|
import config from "../config";
|
||||||
|
import router from "./route";
|
||||||
|
import { handleError } from "../server/routes/route-utils";
|
||||||
|
import AnonymousError from "../core/AnonymousError";
|
||||||
|
|
||||||
|
const app = express();
|
||||||
|
app.use(express.json());
|
||||||
|
|
||||||
|
app.use(compression());
|
||||||
|
|
||||||
|
app.use("/api", router);
|
||||||
|
|
||||||
|
app.all("*", (req, res) => {
|
||||||
|
handleError(
|
||||||
|
new AnonymousError("file_not_found", {
|
||||||
|
httpStatus: 404,
|
||||||
|
object: req.originalUrl,
|
||||||
|
}),
|
||||||
|
res,
|
||||||
|
req
|
||||||
|
);
|
||||||
|
});
|
||||||
|
app.listen(config.PORT, () => {
|
||||||
|
console.log(`Server started on http://streamer:${config.PORT}`);
|
||||||
|
});
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
import * as express from "express";
|
||||||
|
import GitHubStream from "../core/source/GitHubStream";
|
||||||
|
import { AnonymizeTransformer, isTextFile } from "../core/anonymize-utils";
|
||||||
|
import { handleError } from "../server/routes/route-utils";
|
||||||
|
import { contentType } from "mime-types";
|
||||||
|
|
||||||
|
export const router = express.Router();
|
||||||
|
|
||||||
|
router.post("/", async (req: express.Request, res: express.Response) => {
|
||||||
|
req.body = req.body || {};
|
||||||
|
const token: string = req.body.token;
|
||||||
|
const repoFullName = req.body.repoFullName.split("/");
|
||||||
|
const repoId = req.body.repoId;
|
||||||
|
const branch = req.body.branch;
|
||||||
|
const fileSha = req.body.sha;
|
||||||
|
const commit = req.body.commit;
|
||||||
|
const filePath = req.body.filePath;
|
||||||
|
const anonymizerOptions = req.body.anonymizerOptions;
|
||||||
|
const anonymizer = new AnonymizeTransformer(anonymizerOptions);
|
||||||
|
|
||||||
|
const source = new GitHubStream({
|
||||||
|
repoId,
|
||||||
|
organization: repoFullName[0],
|
||||||
|
repoName: repoFullName[1],
|
||||||
|
commit: commit,
|
||||||
|
getToken: () => token,
|
||||||
|
});
|
||||||
|
const content = source.downloadFile(token, fileSha);
|
||||||
|
try {
|
||||||
|
const mime = contentType(filePath);
|
||||||
|
if (mime && !filePath.endsWith(".ts")) {
|
||||||
|
res.contentType(mime);
|
||||||
|
} else if (isTextFile(filePath)) {
|
||||||
|
res.contentType("text/plain");
|
||||||
|
}
|
||||||
|
res.header("Accept-Ranges", "none");
|
||||||
|
anonymizer.once("transform", (data) => {
|
||||||
|
if (!mime && data.isText) {
|
||||||
|
res.contentType("text/plain");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
function handleStreamError(error: Error) {
|
||||||
|
if (!content.closed && !content.destroyed) {
|
||||||
|
content.destroy();
|
||||||
|
}
|
||||||
|
handleError(error, res);
|
||||||
|
}
|
||||||
|
content
|
||||||
|
.on("error", handleStreamError)
|
||||||
|
.pipe(anonymizer)
|
||||||
|
.pipe(res)
|
||||||
|
.on("error", handleStreamError)
|
||||||
|
.on("close", () => {
|
||||||
|
if (!content.closed && !content.destroyed) {
|
||||||
|
content.destroy();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
handleError(error, res);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default router;
|
||||||
+1
-1
@@ -17,6 +17,6 @@
|
|||||||
"esModuleInterop": false,
|
"esModuleInterop": false,
|
||||||
"incremental": true
|
"incremental": true
|
||||||
},
|
},
|
||||||
"include": ["src/**/*.ts", "index.ts", "cli.ts"],
|
"include": ["src/**/*.ts", "index.ts", "src/cli/index.ts"],
|
||||||
"exclude": ["node_modules", ".vscode"]
|
"exclude": ["node_modules", ".vscode"]
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user