mirror of
https://github.com/tdurieux/anonymous_github.git
synced 2026-07-06 21:57:57 +02:00
refactor: uncouple repository class & token
This commit is contained in:
@@ -4,18 +4,11 @@ import { Readable } from "stream";
|
|||||||
import { trace } from "@opentelemetry/api";
|
import { trace } from "@opentelemetry/api";
|
||||||
import Repository from "./Repository";
|
import Repository from "./Repository";
|
||||||
import { RepositoryStatus, Tree, TreeElement, TreeFile } from "./types";
|
import { RepositoryStatus, Tree, TreeElement, TreeFile } from "./types";
|
||||||
import storage from "./storage";
|
|
||||||
import config from "../config";
|
import config from "../config";
|
||||||
import {
|
import { anonymizePath, isTextFile } from "./anonymize-utils";
|
||||||
anonymizePath,
|
|
||||||
AnonymizeTransformer,
|
|
||||||
isTextFile,
|
|
||||||
} from "./anonymize-utils";
|
|
||||||
import AnonymousError from "./AnonymousError";
|
import AnonymousError from "./AnonymousError";
|
||||||
import { handleError } from "./routes/route-utils";
|
import { handleError } from "./routes/route-utils";
|
||||||
import { lookup } from "mime-types";
|
import { lookup } from "mime-types";
|
||||||
import { FILE_TYPE } from "./storage/Storage";
|
|
||||||
import GitHubBase from "./source/GitHubBase";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represent a file in a anonymized repository
|
* Represent a file in a anonymized repository
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ export default class AnonymousError extends CustomError {
|
|||||||
} else if (this.value instanceof User) {
|
} else if (this.value instanceof User) {
|
||||||
detail = `${this.value.username}`;
|
detail = `${this.value.username}`;
|
||||||
} else if (this.value instanceof GitHubBase) {
|
} else if (this.value instanceof GitHubBase) {
|
||||||
detail = `${this.value.githubRepository.fullName}`;
|
detail = `GHDownload ${this.value.data.repoId}`;
|
||||||
}
|
}
|
||||||
out += this.message;
|
out += this.message;
|
||||||
if (detail) {
|
if (detail) {
|
||||||
|
|||||||
@@ -0,0 +1,57 @@
|
|||||||
|
import { trace } from "@opentelemetry/api";
|
||||||
|
import Repository from "./Repository";
|
||||||
|
import { Octokit } from "@octokit/rest";
|
||||||
|
import UserModel from "./database/users/users.model";
|
||||||
|
import config from "../config";
|
||||||
|
|
||||||
|
export function octokit(token: string) {
|
||||||
|
return new Octokit({
|
||||||
|
auth: token,
|
||||||
|
request: {
|
||||||
|
fetch: fetch,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function checkToken(token: string) {
|
||||||
|
const oct = octokit(token);
|
||||||
|
try {
|
||||||
|
await oct.users.getAuthenticated();
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getToken(repository: Repository) {
|
||||||
|
const span = trace.getTracer("ano-file").startSpan("GHUtils.getToken");
|
||||||
|
span.setAttribute("repoId", repository.repoId);
|
||||||
|
try {
|
||||||
|
if (repository.model.source.accessToken) {
|
||||||
|
if (await checkToken(repository.model.source.accessToken)) {
|
||||||
|
return repository.model.source.accessToken;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!repository.owner.model.accessTokens?.github) {
|
||||||
|
const accessTokens = (
|
||||||
|
await UserModel.findById(repository.owner.id, {
|
||||||
|
accessTokens: 1,
|
||||||
|
})
|
||||||
|
)?.accessTokens;
|
||||||
|
if (accessTokens) {
|
||||||
|
repository.owner.model.accessTokens = accessTokens;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (repository.owner.model.accessTokens?.github) {
|
||||||
|
const check = await checkToken(
|
||||||
|
repository.owner.model.accessTokens?.github
|
||||||
|
);
|
||||||
|
if (check) {
|
||||||
|
return repository.owner.model.accessTokens?.github;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return config.GITHUB_TOKEN;
|
||||||
|
} finally {
|
||||||
|
span.end();
|
||||||
|
}
|
||||||
|
}
|
||||||
+20
-19
@@ -1,6 +1,5 @@
|
|||||||
import { RepositoryStatus, Source, Tree, TreeElement, TreeFile } from "./types";
|
import { RepositoryStatus } from "./types";
|
||||||
import User from "./User";
|
import User from "./User";
|
||||||
import { anonymizeContent, anonymizePath } from "./anonymize-utils";
|
|
||||||
import UserModel from "./database/users/users.model";
|
import UserModel from "./database/users/users.model";
|
||||||
import Conference from "./Conference";
|
import Conference from "./Conference";
|
||||||
import ConferenceModel from "./database/conference/conferences.model";
|
import ConferenceModel from "./database/conference/conferences.model";
|
||||||
@@ -8,7 +7,8 @@ import AnonymousError from "./AnonymousError";
|
|||||||
import { IAnonymizedPullRequestDocument } from "./database/anonymizedPullRequests/anonymizedPullRequests.types";
|
import { IAnonymizedPullRequestDocument } from "./database/anonymizedPullRequests/anonymizedPullRequests.types";
|
||||||
import config from "../config";
|
import config from "../config";
|
||||||
import got from "got";
|
import got from "got";
|
||||||
import GitHubBase from "./source/GitHubBase";
|
import { octokit } from "./GitHubUtils";
|
||||||
|
import { ContentAnonimizer } from "./anonymize-utils";
|
||||||
|
|
||||||
export default class PullRequest {
|
export default class PullRequest {
|
||||||
private _model: IAnonymizedPullRequestDocument;
|
private _model: IAnonymizedPullRequestDocument;
|
||||||
@@ -52,26 +52,23 @@ export default class PullRequest {
|
|||||||
"[INFO] Downloading pull request",
|
"[INFO] Downloading pull request",
|
||||||
this._model.source.pullRequestId
|
this._model.source.pullRequestId
|
||||||
);
|
);
|
||||||
const octokit = GitHubBase.octokit(await this.getToken());
|
const oct = octokit(await this.getToken());
|
||||||
|
|
||||||
const [owner, repo] = this._model.source.repositoryFullName.split("/");
|
const [owner, repo] = this._model.source.repositoryFullName.split("/");
|
||||||
const pull_number = this._model.source.pullRequestId;
|
const pull_number = this._model.source.pullRequestId;
|
||||||
|
|
||||||
const [prInfo, comments, diff] = await Promise.all([
|
const [prInfo, comments, diff] = await Promise.all([
|
||||||
octokit.rest.pulls.get({
|
oct.rest.pulls.get({
|
||||||
owner,
|
owner,
|
||||||
repo,
|
repo,
|
||||||
pull_number,
|
pull_number,
|
||||||
}),
|
}),
|
||||||
octokit.paginate(
|
oct.paginate("GET /repos/{owner}/{repo}/issues/{issue_number}/comments", {
|
||||||
"GET /repos/{owner}/{repo}/issues/{issue_number}/comments",
|
owner: owner,
|
||||||
{
|
repo: repo,
|
||||||
owner: owner,
|
issue_number: pull_number,
|
||||||
repo: repo,
|
per_page: 100,
|
||||||
issue_number: pull_number,
|
}),
|
||||||
per_page: 100,
|
|
||||||
}
|
|
||||||
),
|
|
||||||
got(`https://github.com/${owner}/${repo}/pull/${pull_number}.diff`),
|
got(`https://github.com/${owner}/${repo}/pull/${pull_number}.diff`),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@@ -250,18 +247,22 @@ export default class PullRequest {
|
|||||||
state: this._model.pullRequest.state,
|
state: this._model.pullRequest.state,
|
||||||
draft: this._model.pullRequest.draft,
|
draft: this._model.pullRequest.draft,
|
||||||
};
|
};
|
||||||
|
const anonymizer = new ContentAnonimizer({
|
||||||
|
...this.options,
|
||||||
|
repoId: this.pullRequestId,
|
||||||
|
});
|
||||||
if (this.options.title) {
|
if (this.options.title) {
|
||||||
output.title = anonymizeContent(this._model.pullRequest.title, this);
|
output.title = anonymizer.anonymize(this._model.pullRequest.title);
|
||||||
}
|
}
|
||||||
if (this.options.body) {
|
if (this.options.body) {
|
||||||
output.body = anonymizeContent(this._model.pullRequest.body, this);
|
output.body = anonymizer.anonymize(this._model.pullRequest.body);
|
||||||
}
|
}
|
||||||
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 = anonymizer.anonymize(comment.body);
|
||||||
if (this.options.username)
|
if (this.options.username)
|
||||||
o.author = anonymizeContent(comment.author, this);
|
o.author = anonymizer.anonymize(comment.author);
|
||||||
if (this.options.date) {
|
if (this.options.date) {
|
||||||
o.updatedDate = comment.updatedDate;
|
o.updatedDate = comment.updatedDate;
|
||||||
o.creationDate = comment.creationDate;
|
o.creationDate = comment.creationDate;
|
||||||
@@ -270,7 +271,7 @@ export default class PullRequest {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (this.options.diff) {
|
if (this.options.diff) {
|
||||||
output.diff = anonymizeContent(this._model.pullRequest.diff, this);
|
output.diff = anonymizer.anonymize(this._model.pullRequest.diff);
|
||||||
}
|
}
|
||||||
if (this.options.origin) {
|
if (this.options.origin) {
|
||||||
output.baseRepositoryFullName =
|
output.baseRepositoryFullName =
|
||||||
|
|||||||
+59
-56
@@ -1,5 +1,5 @@
|
|||||||
import storage from "./storage";
|
import storage from "./storage";
|
||||||
import { RepositoryStatus, Source, Tree, TreeElement, TreeFile } from "./types";
|
import { RepositoryStatus, Tree, TreeElement, TreeFile } from "./types";
|
||||||
import { Readable } from "stream";
|
import { Readable } from "stream";
|
||||||
import User from "./User";
|
import User from "./User";
|
||||||
import GitHubStream from "./source/GitHubStream";
|
import GitHubStream from "./source/GitHubStream";
|
||||||
@@ -16,9 +16,9 @@ import AnonymousError from "./AnonymousError";
|
|||||||
import { downloadQueue } from "./queue";
|
import { downloadQueue } from "./queue";
|
||||||
import { isConnected } from "./database/database";
|
import { isConnected } from "./database/database";
|
||||||
import AnonymizedRepositoryModel from "./database/anonymizedRepositories/anonymizedRepositories.model";
|
import AnonymizedRepositoryModel from "./database/anonymizedRepositories/anonymizedRepositories.model";
|
||||||
import { getRepositoryFromGitHub } from "./source/GitHubRepository";
|
import { GitHubRepository } from "./source/GitHubRepository";
|
||||||
import config from "../config";
|
|
||||||
import { trace } from "@opentelemetry/api";
|
import { trace } from "@opentelemetry/api";
|
||||||
|
import { getToken } from "./GitHubUtils";
|
||||||
|
|
||||||
function anonymizeTreeRecursive(
|
function anonymizeTreeRecursive(
|
||||||
tree: TreeElement,
|
tree: TreeElement,
|
||||||
@@ -48,38 +48,55 @@ function anonymizeTreeRecursive(
|
|||||||
|
|
||||||
export default class Repository {
|
export default class Repository {
|
||||||
private _model: IAnonymizedRepositoryDocument;
|
private _model: IAnonymizedRepositoryDocument;
|
||||||
source: Source;
|
|
||||||
owner: User;
|
owner: User;
|
||||||
|
|
||||||
constructor(data: IAnonymizedRepositoryDocument) {
|
constructor(data: IAnonymizedRepositoryDocument) {
|
||||||
this._model = data;
|
this._model = data;
|
||||||
switch (data.source.type) {
|
this.owner = new User(new UserModel({ _id: data.owner }));
|
||||||
|
this.owner = new User(new UserModel({ _id: data.owner }));
|
||||||
|
this.owner.model.isNew = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private checkedToken: boolean = false;
|
||||||
|
|
||||||
|
private async getToken() {
|
||||||
|
if (this.checkedToken) return this._model.source.accessToken as string;
|
||||||
|
const originalToken = this._model.source.accessToken;
|
||||||
|
const token = await getToken(this);
|
||||||
|
if (originalToken != token) {
|
||||||
|
this._model.source.accessToken = token;
|
||||||
|
await this._model.save();
|
||||||
|
}
|
||||||
|
this.checkedToken = true;
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
get source() {
|
||||||
|
switch (this.model.source.type) {
|
||||||
case "GitHubDownload":
|
case "GitHubDownload":
|
||||||
this.source = new GitHubDownload(data.source, this.repoId);
|
return new GitHubDownload({
|
||||||
break;
|
repoId: this.repoId,
|
||||||
|
commit: this.model.source.commit || "HEAD",
|
||||||
|
organization: "",
|
||||||
|
repoName: this.model.source.repositoryName || "",
|
||||||
|
getToken: () => this.getToken(),
|
||||||
|
});
|
||||||
case "GitHubStream":
|
case "GitHubStream":
|
||||||
this.source = new GitHubStream(data.source);
|
return new GitHubStream({
|
||||||
break;
|
repoId: this.repoId,
|
||||||
|
commit: this.model.source.commit || "HEAD",
|
||||||
|
organization: "",
|
||||||
|
repoName: this.model.source.repositoryName || "",
|
||||||
|
getToken: () => this.getToken(),
|
||||||
|
});
|
||||||
case "Zip":
|
case "Zip":
|
||||||
this.source = new Zip(data.source, this.repoId);
|
return new Zip(this.model.source, this.repoId);
|
||||||
break;
|
|
||||||
default:
|
default:
|
||||||
throw new AnonymousError("unsupported_source", {
|
throw new AnonymousError("unsupported_source", {
|
||||||
object: data.source.type,
|
object: this,
|
||||||
httpStatus: 400,
|
httpStatus: 400,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
this.owner = new User(new UserModel({ _id: data.owner }));
|
|
||||||
if (this.source instanceof GitHubBase) {
|
|
||||||
const originalToken = this._model.source.accessToken;
|
|
||||||
this.source.getToken(this.owner.id).then((token) => {
|
|
||||||
if (originalToken != token) {
|
|
||||||
this._model.source.accessToken = token;
|
|
||||||
this._model.save();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
this.owner.model.isNew = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -194,8 +211,8 @@ export default class Repository {
|
|||||||
image: this.options.image,
|
image: this.options.image,
|
||||||
link: this.options.link,
|
link: this.options.link,
|
||||||
repoId: this.repoId,
|
repoId: this.repoId,
|
||||||
repoName: (this.source as GitHubBase).githubRepository?.fullName,
|
repoName: this.model.source.repositoryName,
|
||||||
branchName: (this.source as GitHubBase).branch?.name || "main",
|
branchName: this.model.source.branch || "main",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -217,16 +234,17 @@ export default class Repository {
|
|||||||
) {
|
) {
|
||||||
// Only GitHubBase can be update for the moment
|
// Only GitHubBase can be update for the moment
|
||||||
if (this.source instanceof GitHubBase) {
|
if (this.source instanceof GitHubBase) {
|
||||||
const token = await this.source.getToken(this.owner.id);
|
const token = await this.getToken();
|
||||||
const branches = await this.source.githubRepository.branches({
|
const ghRepo = new GitHubRepository({});
|
||||||
|
const branches = await ghRepo.branches({
|
||||||
force: true,
|
force: true,
|
||||||
accessToken: token,
|
accessToken: token,
|
||||||
});
|
});
|
||||||
const branch = this.source.branch;
|
const branchName = this.model.source.branch || "main";
|
||||||
const newCommit = branches.filter((f) => f.name == branch.name)[0]
|
const newCommit = branches.filter((f) => f.name == branchName)[0]
|
||||||
?.commit;
|
?.commit;
|
||||||
if (
|
if (
|
||||||
branch.commit == newCommit &&
|
this.model.source.commit == newCommit &&
|
||||||
this.status == RepositoryStatus.READY
|
this.status == RepositoryStatus.READY
|
||||||
) {
|
) {
|
||||||
console.log(`[UPDATE] ${this._model.repoId} is up to date`);
|
console.log(`[UPDATE] ${this._model.repoId} is up to date`);
|
||||||
@@ -235,12 +253,9 @@ export default class Repository {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this._model.source.commit = newCommit;
|
this._model.source.commit = newCommit;
|
||||||
const commitInfo = await this.source.githubRepository.getCommitInfo(
|
const commitInfo = await ghRepo.getCommitInfo(newCommit, {
|
||||||
newCommit,
|
accessToken: token,
|
||||||
{
|
});
|
||||||
accessToken: token,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
if (
|
if (
|
||||||
commitInfo.commit?.author?.date ||
|
commitInfo.commit?.author?.date ||
|
||||||
commitInfo.commit?.committer?.date
|
commitInfo.commit?.committer?.date
|
||||||
@@ -249,11 +264,11 @@ export default class Repository {
|
|||||||
commitInfo.commit.committer?.date) as string;
|
commitInfo.commit.committer?.date) as string;
|
||||||
this._model.source.commitDate = new Date(d);
|
this._model.source.commitDate = new Date(d);
|
||||||
}
|
}
|
||||||
branch.commit = newCommit;
|
this.model.source.commit = newCommit;
|
||||||
|
|
||||||
if (!newCommit) {
|
if (!newCommit) {
|
||||||
console.error(
|
console.error(
|
||||||
`${branch.name} for ${this.source.githubRepository.fullName} is not found`
|
`${branchName} for ${this.model.source.repositoryName} is not found`
|
||||||
);
|
);
|
||||||
await this.updateStatus(RepositoryStatus.ERROR, "branch_not_found");
|
await this.updateStatus(RepositoryStatus.ERROR, "branch_not_found");
|
||||||
await this.resetSate();
|
await this.resetSate();
|
||||||
@@ -268,23 +283,6 @@ export default class Repository {
|
|||||||
`[UPDATE] ${this._model.repoId} will be updated to ${newCommit}`
|
`[UPDATE] ${this._model.repoId} will be updated to ${newCommit}`
|
||||||
);
|
);
|
||||||
|
|
||||||
if (this.source.type == "GitHubDownload") {
|
|
||||||
const repository = await getRepositoryFromGitHub({
|
|
||||||
accessToken: await this.source.getToken(this.owner.id),
|
|
||||||
owner: this.source.githubRepository.owner,
|
|
||||||
repo: this.source.githubRepository.repo,
|
|
||||||
});
|
|
||||||
if (
|
|
||||||
repository.size === undefined ||
|
|
||||||
repository.size > config.MAX_REPO_SIZE
|
|
||||||
) {
|
|
||||||
console.log(
|
|
||||||
`[UPDATE] ${this._model.repoId} will be streamed instead of downloaded`
|
|
||||||
);
|
|
||||||
this._model.source.type = "GitHubStream";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.resetSate(RepositoryStatus.PREPARING);
|
await this.resetSate(RepositoryStatus.PREPARING);
|
||||||
await downloadQueue.add(this.repoId, this, {
|
await downloadQueue.add(this.repoId, this, {
|
||||||
jobId: this.repoId,
|
jobId: this.repoId,
|
||||||
@@ -513,10 +511,15 @@ export default class Repository {
|
|||||||
anonymizeDate: this._model.anonymizeDate,
|
anonymizeDate: this._model.anonymizeDate,
|
||||||
status: this.status,
|
status: this.status,
|
||||||
statusMessage: this._model.statusMessage,
|
statusMessage: this._model.statusMessage,
|
||||||
source: this.source.toJSON(),
|
|
||||||
lastView: this._model.lastView,
|
lastView: this._model.lastView,
|
||||||
pageView: this._model.pageView,
|
pageView: this._model.pageView,
|
||||||
size: this.size,
|
size: this.size,
|
||||||
|
source: {
|
||||||
|
fullName: this.model.source.repositoryName,
|
||||||
|
commit: this.model.source.commit,
|
||||||
|
branch: this.model.source.branch,
|
||||||
|
type: this.model.source.type,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-3
@@ -6,7 +6,7 @@ import { GitHubRepository } from "./source/GitHubRepository";
|
|||||||
import PullRequest from "./PullRequest";
|
import PullRequest from "./PullRequest";
|
||||||
import AnonymizedPullRequestModel from "./database/anonymizedPullRequests/anonymizedPullRequests.model";
|
import AnonymizedPullRequestModel from "./database/anonymizedPullRequests/anonymizedPullRequests.model";
|
||||||
import { trace } from "@opentelemetry/api";
|
import { trace } from "@opentelemetry/api";
|
||||||
import GitHubBase from "./source/GitHubBase";
|
import { octokit } from "./GitHubUtils";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Model for a user
|
* Model for a user
|
||||||
@@ -66,9 +66,9 @@ export default class User {
|
|||||||
opt?.force === true
|
opt?.force === true
|
||||||
) {
|
) {
|
||||||
// get the list of repo from github
|
// get the list of repo from github
|
||||||
const octokit = GitHubBase.octokit(this.accessToken);
|
const oct = octokit(this.accessToken);
|
||||||
const repositories = (
|
const repositories = (
|
||||||
await octokit.paginate("GET /user/repos", {
|
await oct.paginate("GET /user/repos", {
|
||||||
visibility: "all",
|
visibility: "all",
|
||||||
sort: "pushed",
|
sort: "pushed",
|
||||||
per_page: 100,
|
per_page: 100,
|
||||||
|
|||||||
+35
-71
@@ -1,10 +1,8 @@
|
|||||||
import config from "../config";
|
import config from "../config";
|
||||||
import GitHubBase from "./source/GitHubBase";
|
|
||||||
import { isText } from "istextorbinary";
|
import { isText } from "istextorbinary";
|
||||||
import { basename } from "path";
|
import { basename } from "path";
|
||||||
import { Transform } from "stream";
|
import { Transform } from "stream";
|
||||||
import { Readable } from "stream";
|
import { Readable } from "stream";
|
||||||
import AnonymizedFile from "./AnonymizedFile";
|
|
||||||
import { trace } from "@opentelemetry/api";
|
import { trace } from "@opentelemetry/api";
|
||||||
|
|
||||||
const urlRegex =
|
const urlRegex =
|
||||||
@@ -33,16 +31,21 @@ export function isTextFile(filePath: string, content?: Buffer) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class AnonymizeTransformer extends Transform {
|
export class AnonymizeTransformer extends Transform {
|
||||||
public wasAnonimized = false;
|
|
||||||
public isText: boolean | null = null;
|
public isText: boolean | null = null;
|
||||||
|
anonimizer: ContentAnonimizer;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly opt: {
|
private readonly opt: {
|
||||||
filePath: string;
|
filePath: string;
|
||||||
} & ConstructorParameters<typeof ContentAnonimizer>[1]
|
} & ConstructorParameters<typeof ContentAnonimizer>[0]
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
this.isText = isTextFile(this.opt.filePath);
|
this.isText = isTextFile(this.opt.filePath);
|
||||||
|
this.anonimizer = new ContentAnonimizer(this.opt);
|
||||||
|
}
|
||||||
|
|
||||||
|
get wasAnonimized() {
|
||||||
|
return this.anonimizer.wasAnonymized;
|
||||||
}
|
}
|
||||||
|
|
||||||
_transform(chunk: Buffer, encoding: string, callback: () => void) {
|
_transform(chunk: Buffer, encoding: string, callback: () => void) {
|
||||||
@@ -55,11 +58,9 @@ export class AnonymizeTransformer extends Transform {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (this.isText) {
|
if (this.isText) {
|
||||||
const anonimizer = new ContentAnonimizer(chunk.toString(), this.opt);
|
const content = this.anonimizer.anonymize(chunk.toString());
|
||||||
anonimizer.anonymize();
|
if (this.anonimizer.wasAnonymized) {
|
||||||
if (anonimizer.wasAnonymized) {
|
chunk = Buffer.from(content);
|
||||||
this.wasAnonimized = true;
|
|
||||||
chunk = Buffer.from(anonimizer.content);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,25 +77,10 @@ export class AnonymizeTransformer extends Transform {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Anonymizationptions {
|
|
||||||
repoId?: string;
|
|
||||||
source?: {};
|
|
||||||
options: {
|
|
||||||
terms: string[];
|
|
||||||
image: boolean;
|
|
||||||
link: boolean;
|
|
||||||
pageSource?: {
|
|
||||||
branch: string;
|
|
||||||
path: string;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export class ContentAnonimizer {
|
export class ContentAnonimizer {
|
||||||
public wasAnonymized = false;
|
public wasAnonymized = false;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
public content: string,
|
|
||||||
readonly opt: {
|
readonly opt: {
|
||||||
image?: boolean;
|
image?: boolean;
|
||||||
link?: boolean;
|
link?: boolean;
|
||||||
@@ -105,12 +91,12 @@ export class ContentAnonimizer {
|
|||||||
}
|
}
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
private removeImage() {
|
private removeImage(content: string): string {
|
||||||
if (this.opt.image !== false) {
|
if (this.opt.image !== false) {
|
||||||
return;
|
return content;
|
||||||
}
|
}
|
||||||
// remove image in markdown
|
// remove image in markdown
|
||||||
this.content = this.content.replace(
|
return content.replace(
|
||||||
/!\[[^\]]*\]\((?<filename>.*?)(?=\"|\))(?<optionalpart>\".*\")?\)/g,
|
/!\[[^\]]*\]\((?<filename>.*?)(?=\"|\))(?<optionalpart>\".*\")?\)/g,
|
||||||
() => {
|
() => {
|
||||||
this.wasAnonymized = true;
|
this.wasAnonymized = true;
|
||||||
@@ -118,20 +104,20 @@ export class ContentAnonimizer {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
private removeLink() {
|
private removeLink(content: string): string {
|
||||||
if (this.opt.link !== false) {
|
if (this.opt.link !== false) {
|
||||||
return;
|
return content;
|
||||||
}
|
}
|
||||||
// remove image in markdown
|
// remove image in markdown
|
||||||
this.content = this.content.replace(urlRegex, () => {
|
return content.replace(urlRegex, () => {
|
||||||
this.wasAnonymized = true;
|
this.wasAnonymized = true;
|
||||||
return config.ANONYMIZATION_MASK;
|
return config.ANONYMIZATION_MASK;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private replaceGitHubSelfLinks() {
|
private replaceGitHubSelfLinks(content: string): string {
|
||||||
if (!this.opt.repoName || !this.opt.branchName) {
|
if (!this.opt.repoName || !this.opt.branchName) {
|
||||||
return;
|
return content;
|
||||||
}
|
}
|
||||||
const repoName = this.opt.repoName;
|
const repoName = this.opt.repoName;
|
||||||
const branchName = this.opt.branchName;
|
const branchName = this.opt.branchName;
|
||||||
@@ -140,28 +126,28 @@ export class ContentAnonimizer {
|
|||||||
this.wasAnonymized = true;
|
this.wasAnonymized = true;
|
||||||
return `https://${config.APP_HOSTNAME}/r/${this.opt.repoId}`;
|
return `https://${config.APP_HOSTNAME}/r/${this.opt.repoId}`;
|
||||||
};
|
};
|
||||||
this.content = this.content.replace(
|
content = content.replace(
|
||||||
new RegExp(
|
new RegExp(
|
||||||
`https://raw.githubusercontent.com/${repoName}/${branchName}\\b`,
|
`https://raw.githubusercontent.com/${repoName}/${branchName}\\b`,
|
||||||
"gi"
|
"gi"
|
||||||
),
|
),
|
||||||
replaceCallback
|
replaceCallback
|
||||||
);
|
);
|
||||||
this.content = this.content.replace(
|
content = content.replace(
|
||||||
new RegExp(`https://github.com/${repoName}/blob/${branchName}\\b`, "gi"),
|
new RegExp(`https://github.com/${repoName}/blob/${branchName}\\b`, "gi"),
|
||||||
replaceCallback
|
replaceCallback
|
||||||
);
|
);
|
||||||
this.content = this.content.replace(
|
content = content.replace(
|
||||||
new RegExp(`https://github.com/${repoName}/tree/${branchName}\\b`, "gi"),
|
new RegExp(`https://github.com/${repoName}/tree/${branchName}\\b`, "gi"),
|
||||||
replaceCallback
|
replaceCallback
|
||||||
);
|
);
|
||||||
this.content = this.content.replace(
|
return content.replace(
|
||||||
new RegExp(`https://github.com/${repoName}`, "gi"),
|
new RegExp(`https://github.com/${repoName}`, "gi"),
|
||||||
replaceCallback
|
replaceCallback
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private replaceTerms() {
|
private replaceTerms(content: string): string {
|
||||||
const terms = this.opt.terms || [];
|
const terms = this.opt.terms || [];
|
||||||
for (let i = 0; i < terms.length; i++) {
|
for (let i = 0; i < terms.length; i++) {
|
||||||
let term = terms[i];
|
let term = terms[i];
|
||||||
@@ -176,7 +162,7 @@ export class ContentAnonimizer {
|
|||||||
term = term.replace(/[-[\]{}()*+?.,\\^$|#]/g, "\\$&");
|
term = term.replace(/[-[\]{}()*+?.,\\^$|#]/g, "\\$&");
|
||||||
}
|
}
|
||||||
// remove whole url if it contains the term
|
// remove whole url if it contains the term
|
||||||
this.content = this.content.replace(urlRegex, (match) => {
|
content = content.replace(urlRegex, (match) => {
|
||||||
if (new RegExp(`\\b${term}\\b`, "gi").test(match)) {
|
if (new RegExp(`\\b${term}\\b`, "gi").test(match)) {
|
||||||
this.wasAnonymized = true;
|
this.wasAnonymized = true;
|
||||||
return mask;
|
return mask;
|
||||||
@@ -185,56 +171,34 @@ export class ContentAnonimizer {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// remove the term in the text
|
// remove the term in the text
|
||||||
this.content = this.content.replace(
|
return content.replace(new RegExp(`\\b${term}\\b`, "gi"), () => {
|
||||||
new RegExp(`\\b${term}\\b`, "gi"),
|
this.wasAnonymized = true;
|
||||||
() => {
|
return mask;
|
||||||
this.wasAnonymized = true;
|
});
|
||||||
return mask;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
return content;
|
||||||
}
|
}
|
||||||
|
|
||||||
anonymize() {
|
anonymize(content: string) {
|
||||||
const span = trace
|
const span = trace
|
||||||
.getTracer("ano-file")
|
.getTracer("ano-file")
|
||||||
.startSpan("ContentAnonimizer.anonymize");
|
.startSpan("ContentAnonimizer.anonymize");
|
||||||
try {
|
try {
|
||||||
this.removeImage();
|
content = this.removeImage(content);
|
||||||
span.addEvent("removeImage");
|
span.addEvent("removeImage");
|
||||||
this.removeLink();
|
content = this.removeLink(content);
|
||||||
span.addEvent("removeLink");
|
span.addEvent("removeLink");
|
||||||
this.replaceGitHubSelfLinks();
|
content = this.replaceGitHubSelfLinks(content);
|
||||||
span.addEvent("replaceGitHubSelfLinks");
|
span.addEvent("replaceGitHubSelfLinks");
|
||||||
this.replaceTerms();
|
content = this.replaceTerms(content);
|
||||||
span.addEvent("replaceTerms");
|
span.addEvent("replaceTerms");
|
||||||
return this.content;
|
return content;
|
||||||
} finally {
|
} finally {
|
||||||
span.end();
|
span.end();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function anonymizeContent(
|
|
||||||
content: string,
|
|
||||||
repository: Anonymizationptions
|
|
||||||
) {
|
|
||||||
let repoName: string | undefined;
|
|
||||||
let branchName: string | undefined;
|
|
||||||
if (repository.source instanceof GitHubBase) {
|
|
||||||
repoName = repository.source.githubRepository.fullName;
|
|
||||||
branchName = repository.source.branch.name;
|
|
||||||
}
|
|
||||||
return new ContentAnonimizer(content, {
|
|
||||||
repoId: repository.repoId,
|
|
||||||
image: repository.options.image,
|
|
||||||
link: repository.options.link,
|
|
||||||
terms: repository.options.terms,
|
|
||||||
repoName,
|
|
||||||
branchName,
|
|
||||||
}).anonymize();
|
|
||||||
}
|
|
||||||
|
|
||||||
export function anonymizePath(path: string, terms: string[]) {
|
export function anonymizePath(path: string, terms: string[]) {
|
||||||
return trace
|
return trace
|
||||||
.getTracer("ano-file")
|
.getTracer("ano-file")
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import Repository from "../Repository";
|
|||||||
import { getRepository as getRepositoryImport } from "../database/database";
|
import { getRepository as getRepositoryImport } from "../database/database";
|
||||||
import { RepositoryStatus } from "../types";
|
import { RepositoryStatus } from "../types";
|
||||||
import { trace } from "@opentelemetry/api";
|
import { trace } from "@opentelemetry/api";
|
||||||
import { Span } from "@opentelemetry/sdk-trace-node";
|
|
||||||
|
|
||||||
export default async function (job: SandboxedJob<Repository, void>) {
|
export default async function (job: SandboxedJob<Repository, void>) {
|
||||||
const {
|
const {
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import Repository from "../Repository";
|
|||||||
import User from "../User";
|
import User from "../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";
|
||||||
import RepositoryModel from "../database/repositories/repositories.model";
|
|
||||||
|
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
|
|||||||
@@ -5,9 +5,7 @@ import * as db from "../database/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 "../source/GitHubRepository";
|
||||||
import gh = require("parse-github-url");
|
import gh = require("parse-github-url");
|
||||||
import GitHubBase from "../source/GitHubBase";
|
|
||||||
import AnonymizedRepositoryModel from "../database/anonymizedRepositories/anonymizedRepositories.model";
|
import AnonymizedRepositoryModel from "../database/anonymizedRepositories/anonymizedRepositories.model";
|
||||||
import config from "../../config";
|
|
||||||
import { IAnonymizedRepositoryDocument } from "../database/anonymizedRepositories/anonymizedRepositories.types";
|
import { IAnonymizedRepositoryDocument } from "../database/anonymizedRepositories/anonymizedRepositories.types";
|
||||||
import Repository from "../Repository";
|
import Repository from "../Repository";
|
||||||
import UserModel from "../database/users/users.model";
|
import UserModel from "../database/users/users.model";
|
||||||
@@ -18,6 +16,7 @@ import RepositoryModel from "../database/repositories/repositories.model";
|
|||||||
import User from "../User";
|
import User from "../User";
|
||||||
import { RepositoryStatus } from "../types";
|
import { RepositoryStatus } from "../types";
|
||||||
import { IUserDocument } from "../database/users/users.types";
|
import { IUserDocument } from "../database/users/users.types";
|
||||||
|
import { checkToken } from "../GitHubUtils";
|
||||||
|
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
@@ -41,7 +40,7 @@ async function getTokenForAdmin(user: User, req: express.Request) {
|
|||||||
});
|
});
|
||||||
const user: IUserDocument = existingRepo?.owner as any;
|
const user: IUserDocument = existingRepo?.owner as any;
|
||||||
if (user instanceof UserModel) {
|
if (user instanceof UserModel) {
|
||||||
const check = await GitHubBase.checkToken(user.accessTokens.github);
|
const check = await checkToken(user.accessTokens.github);
|
||||||
if (check) {
|
if (check) {
|
||||||
return user.accessTokens.github;
|
return user.accessTokens.github;
|
||||||
}
|
}
|
||||||
@@ -100,7 +99,7 @@ router.post("/claim", async (req: express.Request, res: express.Response) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const dbRepo = await RepositoryModel.findById(
|
const dbRepo = await RepositoryModel.findById(
|
||||||
(repoConfig.source as GitHubBase).githubRepository.id
|
repoConfig.model.source.repositoryId
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!dbRepo || dbRepo.externalId != repo.id) {
|
if (!dbRepo || dbRepo.externalId != repo.id) {
|
||||||
|
|||||||
@@ -86,9 +86,9 @@ router.get(
|
|||||||
if (
|
if (
|
||||||
repo.status == "expired" &&
|
repo.status == "expired" &&
|
||||||
repo.options.expirationMode == "redirect" &&
|
repo.options.expirationMode == "redirect" &&
|
||||||
repo.source.url
|
repo.model.source.repositoryName
|
||||||
) {
|
) {
|
||||||
redirectURL = repo.source.url;
|
redirectURL = `https://github.com/${repo.model.source.repositoryName}`;
|
||||||
} else {
|
} else {
|
||||||
if (
|
if (
|
||||||
repo.status == "expired" ||
|
repo.status == "expired" ||
|
||||||
|
|||||||
@@ -51,9 +51,9 @@ export async function getRepo(
|
|||||||
if (
|
if (
|
||||||
repo.status == "expired" &&
|
repo.status == "expired" &&
|
||||||
repo.options.expirationMode == "redirect" &&
|
repo.options.expirationMode == "redirect" &&
|
||||||
repo.source.url
|
repo.model.source.repositoryId
|
||||||
) {
|
) {
|
||||||
res.redirect(repo.source.url);
|
res.redirect(`https://github.com/${repo.model.source.repositoryName}`);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ 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 "../AnonymizedFile";
|
||||||
import GitHubDownload from "../source/GitHubDownload";
|
|
||||||
import AnonymousError from "../AnonymousError";
|
import AnonymousError from "../AnonymousError";
|
||||||
import { Tree, TreeElement } from "../types";
|
import { Tree, TreeElement } from "../types";
|
||||||
import * as marked from "marked";
|
import * as marked from "marked";
|
||||||
@@ -36,10 +35,7 @@ async function webView(req: express.Request, res: express.Response) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (repo.options.pageSource?.branch != repo.model.source.branch) {
|
||||||
repo.options.pageSource?.branch !=
|
|
||||||
(repo.source as GitHubDownload).branch.name
|
|
||||||
) {
|
|
||||||
throw new AnonymousError("page_not_supported_on_different_branch", {
|
throw new AnonymousError("page_not_supported_on_different_branch", {
|
||||||
httpStatus: 400,
|
httpStatus: 400,
|
||||||
object: repo,
|
object: repo,
|
||||||
|
|||||||
+26
-94
@@ -1,39 +1,36 @@
|
|||||||
import { Octokit } from "@octokit/rest";
|
|
||||||
import { trace } from "@opentelemetry/api";
|
|
||||||
import { Readable } from "stream";
|
import { Readable } from "stream";
|
||||||
|
|
||||||
import AnonymizedFile from "../AnonymizedFile";
|
import AnonymizedFile from "../AnonymizedFile";
|
||||||
import { Branch, Tree } from "../types";
|
import { Tree } from "../types";
|
||||||
import { GitHubRepository } from "./GitHubRepository";
|
|
||||||
import config from "../../config";
|
|
||||||
import UserModel from "../database/users/users.model";
|
|
||||||
|
|
||||||
export default abstract class GitHubBase {
|
export interface GitHubBaseData {
|
||||||
|
getToken: () => string | Promise<string>;
|
||||||
|
repoId: string;
|
||||||
|
organization: string;
|
||||||
|
repoName: 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 {
|
||||||
abstract type: "GitHubDownload" | "GitHubStream" | "Zip";
|
abstract type: "GitHubDownload" | "GitHubStream" | "Zip";
|
||||||
githubRepository: GitHubRepository;
|
|
||||||
branch: Branch;
|
|
||||||
accessToken: string | undefined;
|
accessToken: string | undefined;
|
||||||
validToken: boolean = false;
|
|
||||||
|
|
||||||
constructor(data: {
|
constructor(readonly data: GitHubBaseData) {}
|
||||||
accessToken?: string;
|
|
||||||
commit?: string;
|
|
||||||
branch?: string;
|
|
||||||
repositoryId?: string;
|
|
||||||
repositoryName?: string;
|
|
||||||
}) {
|
|
||||||
this.accessToken = data.accessToken;
|
|
||||||
const branches = [];
|
|
||||||
if (data.branch && data.commit) {
|
|
||||||
branches.push({ commit: data.commit, name: data.branch });
|
|
||||||
}
|
|
||||||
this.githubRepository = new GitHubRepository({
|
|
||||||
name: data.repositoryName,
|
|
||||||
externalId: data.repositoryId,
|
|
||||||
branches,
|
|
||||||
});
|
|
||||||
this.branch = branches[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract getFileContent(
|
abstract getFileContent(
|
||||||
file: AnonymizedFile,
|
file: AnonymizedFile,
|
||||||
@@ -41,69 +38,4 @@ export default abstract class GitHubBase {
|
|||||||
): Promise<Readable>;
|
): Promise<Readable>;
|
||||||
|
|
||||||
abstract getFiles(progress?: (status: string) => void): Promise<Tree>;
|
abstract getFiles(progress?: (status: string) => void): Promise<Tree>;
|
||||||
|
|
||||||
static octokit(token: string) {
|
|
||||||
return new Octokit({
|
|
||||||
auth: token,
|
|
||||||
request: {
|
|
||||||
fetch: fetch,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
static async checkToken(token: string) {
|
|
||||||
const octokit = GitHubBase.octokit(token);
|
|
||||||
try {
|
|
||||||
await octokit.users.getAuthenticated();
|
|
||||||
return true;
|
|
||||||
} catch (error) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async getToken(ownerID?: any) {
|
|
||||||
const span = trace.getTracer("ano-file").startSpan("GHBase.getToken");
|
|
||||||
span.setAttribute("repoId", this.githubRepository.fullName || "");
|
|
||||||
try {
|
|
||||||
if (this.validToken) {
|
|
||||||
return this.accessToken as string;
|
|
||||||
}
|
|
||||||
if (ownerID) {
|
|
||||||
const user = await UserModel.findById(ownerID, {
|
|
||||||
accessTokens: 1,
|
|
||||||
});
|
|
||||||
if (user?.accessTokens.github) {
|
|
||||||
const check = await GitHubBase.checkToken(user.accessTokens.github);
|
|
||||||
if (check) {
|
|
||||||
this.accessToken = user.accessTokens.github;
|
|
||||||
this.validToken = true;
|
|
||||||
return this.accessToken;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (this.accessToken) {
|
|
||||||
if (await GitHubBase.checkToken(this.accessToken)) {
|
|
||||||
this.validToken = true;
|
|
||||||
return this.accessToken;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.accessToken = config.GITHUB_TOKEN;
|
|
||||||
return this.accessToken;
|
|
||||||
} finally {
|
|
||||||
span.end();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
get url() {
|
|
||||||
return "https://github.com/" + this.githubRepository.fullName;
|
|
||||||
}
|
|
||||||
|
|
||||||
toJSON(): any {
|
|
||||||
return {
|
|
||||||
type: this.type,
|
|
||||||
fullName: this.githubRepository.fullName?.toString(),
|
|
||||||
branch: this.branch?.name,
|
|
||||||
commit: this.branch?.commit,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,56 +3,45 @@ import { Readable } from "stream";
|
|||||||
import { OctokitResponse } from "@octokit/types";
|
import { OctokitResponse } from "@octokit/types";
|
||||||
|
|
||||||
import storage from "../storage";
|
import storage from "../storage";
|
||||||
import GitHubBase from "./GitHubBase";
|
import GitHubBase, { GitHubBaseData } from "./GitHubBase";
|
||||||
import AnonymizedFile from "../AnonymizedFile";
|
import AnonymizedFile from "../AnonymizedFile";
|
||||||
import { SourceBase } from "../types";
|
|
||||||
import AnonymousError from "../AnonymousError";
|
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";
|
||||||
|
|
||||||
export default class GitHubDownload extends GitHubBase implements SourceBase {
|
export default class GitHubDownload extends GitHubBase {
|
||||||
type: "GitHubDownload" | "GitHubStream" | "Zip" = "GitHubDownload";
|
type: "GitHubDownload" | "GitHubStream" | "Zip" = "GitHubDownload";
|
||||||
constructor(
|
constructor(data: GitHubBaseData) {
|
||||||
data: {
|
|
||||||
branch?: string;
|
|
||||||
commit?: string;
|
|
||||||
repositoryId?: string;
|
|
||||||
repositoryName?: string;
|
|
||||||
accessToken?: string;
|
|
||||||
},
|
|
||||||
readonly repoId: string
|
|
||||||
) {
|
|
||||||
super(data);
|
super(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _getZipUrl(
|
private async _getZipUrl(): Promise<OctokitResponse<unknown, 302>> {
|
||||||
auth: string
|
const oct = octokit(await this.data.getToken());
|
||||||
): Promise<OctokitResponse<unknown, 302>> {
|
return oct.rest.repos.downloadZipballArchive({
|
||||||
const octokit = GitHubBase.octokit(auth as string);
|
owner: this.data.organization,
|
||||||
return octokit.rest.repos.downloadZipballArchive({
|
repo: this.data.repoName,
|
||||||
owner: this.githubRepository.owner,
|
ref: this.data.commit || "HEAD",
|
||||||
repo: this.githubRepository.repo,
|
|
||||||
ref: this.branch?.commit || "HEAD",
|
|
||||||
method: "HEAD",
|
method: "HEAD",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async download(token: string, progress?: (status: string) => void) {
|
async download(progress?: (status: string) => void) {
|
||||||
const span = trace.getTracer("ano-file").startSpan("GHDownload.download");
|
const span = trace.getTracer("ano-file").startSpan("GHDownload.download");
|
||||||
span.setAttribute("repoId", this.githubRepository.fullName || "");
|
span.setAttribute("repoId", this.data.repoId);
|
||||||
try {
|
try {
|
||||||
let response: OctokitResponse<unknown, number>;
|
let response: OctokitResponse<unknown, number>;
|
||||||
try {
|
try {
|
||||||
response = await this._getZipUrl(token);
|
response = await this._getZipUrl();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
span.recordException(error as Error);
|
span.recordException(error as Error);
|
||||||
throw new AnonymousError("repo_not_accessible", {
|
throw new AnonymousError("repo_not_accessible", {
|
||||||
httpStatus: 404,
|
httpStatus: 404,
|
||||||
object: this.githubRepository,
|
object: this.data,
|
||||||
cause: error as Error,
|
cause: error as Error,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
await storage.mk(this.repoId);
|
await storage.mk(this.data.repoId);
|
||||||
let downloadProgress: { transferred: number } | undefined = undefined;
|
let downloadProgress: { transferred: number } | undefined = undefined;
|
||||||
let progressTimeout;
|
let progressTimeout;
|
||||||
let inDownload = true;
|
let inDownload = true;
|
||||||
@@ -73,18 +62,17 @@ export default class GitHubDownload extends GitHubBase implements SourceBase {
|
|||||||
downloadProgress = p;
|
downloadProgress = p;
|
||||||
});
|
});
|
||||||
await storage.extractZip(
|
await storage.extractZip(
|
||||||
this.repoId,
|
this.data.repoId,
|
||||||
"",
|
"",
|
||||||
downloadStream,
|
downloadStream,
|
||||||
undefined,
|
this.type
|
||||||
this
|
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
span.recordException(error as Error);
|
span.recordException(error as Error);
|
||||||
throw new AnonymousError("unable_to_download", {
|
throw new AnonymousError("unable_to_download", {
|
||||||
httpStatus: 500,
|
httpStatus: 500,
|
||||||
cause: error as Error,
|
cause: error as Error,
|
||||||
object: this.githubRepository,
|
object: this.data,
|
||||||
});
|
});
|
||||||
} finally {
|
} finally {
|
||||||
inDownload = false;
|
inDownload = false;
|
||||||
@@ -102,11 +90,11 @@ export default class GitHubDownload extends GitHubBase implements SourceBase {
|
|||||||
const span = trace
|
const span = trace
|
||||||
.getTracer("ano-file")
|
.getTracer("ano-file")
|
||||||
.startSpan("GHDownload.getFileContent");
|
.startSpan("GHDownload.getFileContent");
|
||||||
span.setAttribute("repoId", this.githubRepository.fullName || "");
|
span.setAttribute("repoId", file.repository.repoId);
|
||||||
try {
|
try {
|
||||||
const exists = await storage.exists(file.filePath);
|
const exists = await storage.exists(file.filePath);
|
||||||
if (exists === FILE_TYPE.FILE) {
|
if (exists === FILE_TYPE.FILE) {
|
||||||
return storage.read(this.repoId, file.filePath);
|
return storage.read(this.data.repoId, file.filePath);
|
||||||
} else if (exists === FILE_TYPE.FOLDER) {
|
} else if (exists === FILE_TYPE.FOLDER) {
|
||||||
throw new AnonymousError("folder_not_supported", {
|
throw new AnonymousError("folder_not_supported", {
|
||||||
httpStatus: 400,
|
httpStatus: 400,
|
||||||
@@ -117,20 +105,17 @@ export default class GitHubDownload extends GitHubBase implements SourceBase {
|
|||||||
await file.originalPath();
|
await file.originalPath();
|
||||||
|
|
||||||
// the cache is not ready, we need to download the repository
|
// the cache is not ready, we need to download the repository
|
||||||
await this.download(
|
await this.download(progress);
|
||||||
await this.getToken(file.repository.owner.id),
|
return storage.read(this.data.repoId, file.filePath);
|
||||||
progress
|
|
||||||
);
|
|
||||||
return storage.read(this.repoId, file.filePath);
|
|
||||||
} finally {
|
} finally {
|
||||||
span.end();
|
span.end();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async getFiles() {
|
async getFiles(progress?: (status: string) => void) {
|
||||||
if ((await storage.exists(this.repoId)) === FILE_TYPE.NOT_FOUND) {
|
if ((await storage.exists(this.data.repoId)) === FILE_TYPE.NOT_FOUND) {
|
||||||
await this.download(await this.getToken());
|
await this.download(progress);
|
||||||
}
|
}
|
||||||
return storage.listFiles(this.repoId);
|
return storage.listFiles(this.data.repoId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import RepositoryModel from "../database/repositories/repositories.model";
|
|||||||
import AnonymousError from "../AnonymousError";
|
import AnonymousError from "../AnonymousError";
|
||||||
import { isConnected } from "../database/database";
|
import { isConnected } from "../database/database";
|
||||||
import { trace } from "@opentelemetry/api";
|
import { trace } from "@opentelemetry/api";
|
||||||
import GitHubBase from "./GitHubBase";
|
import { octokit } from "../GitHubUtils";
|
||||||
|
|
||||||
export class GitHubRepository {
|
export class GitHubRepository {
|
||||||
private _data: Partial<{
|
private _data: Partial<{
|
||||||
@@ -49,7 +49,7 @@ export class GitHubRepository {
|
|||||||
async getCommitInfo(
|
async getCommitInfo(
|
||||||
sha: string,
|
sha: string,
|
||||||
opt: {
|
opt: {
|
||||||
accessToken?: string;
|
accessToken: string;
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
const span = trace
|
const span = trace
|
||||||
@@ -58,8 +58,8 @@ export class GitHubRepository {
|
|||||||
span.setAttribute("owner", this.owner);
|
span.setAttribute("owner", this.owner);
|
||||||
span.setAttribute("repo", this.repo);
|
span.setAttribute("repo", this.repo);
|
||||||
try {
|
try {
|
||||||
const octokit = GitHubBase.octokit(opt.accessToken as string);
|
const oct = octokit(opt.accessToken);
|
||||||
const commit = await octokit.repos.getCommit({
|
const commit = await oct.repos.getCommit({
|
||||||
owner: this.owner,
|
owner: this.owner,
|
||||||
repo: this.repo,
|
repo: this.repo,
|
||||||
ref: sha,
|
ref: sha,
|
||||||
@@ -71,7 +71,7 @@ export class GitHubRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async branches(opt: {
|
async branches(opt: {
|
||||||
accessToken?: string;
|
accessToken: string;
|
||||||
force?: boolean;
|
force?: boolean;
|
||||||
}): Promise<Branch[]> {
|
}): Promise<Branch[]> {
|
||||||
const span = trace.getTracer("ano-file").startSpan("GHRepository.branches");
|
const span = trace.getTracer("ano-file").startSpan("GHRepository.branches");
|
||||||
@@ -84,10 +84,10 @@ export class GitHubRepository {
|
|||||||
opt?.force === true
|
opt?.force === true
|
||||||
) {
|
) {
|
||||||
// get the list of repo from github
|
// get the list of repo from github
|
||||||
const octokit = GitHubBase.octokit(opt.accessToken as string);
|
const oct = octokit(opt.accessToken);
|
||||||
try {
|
try {
|
||||||
const branches = (
|
const branches = (
|
||||||
await octokit.paginate("GET /repos/{owner}/{repo}/branches", {
|
await oct.paginate("GET /repos/{owner}/{repo}/branches", {
|
||||||
owner: this.owner,
|
owner: this.owner,
|
||||||
repo: this.repo,
|
repo: this.repo,
|
||||||
per_page: 100,
|
per_page: 100,
|
||||||
@@ -132,7 +132,7 @@ export class GitHubRepository {
|
|||||||
async readme(opt: {
|
async readme(opt: {
|
||||||
branch?: string;
|
branch?: string;
|
||||||
force?: boolean;
|
force?: boolean;
|
||||||
accessToken?: string;
|
accessToken: string;
|
||||||
}): Promise<string | undefined> {
|
}): Promise<string | undefined> {
|
||||||
const span = trace.getTracer("ano-file").startSpan("GHRepository.readme");
|
const span = trace.getTracer("ano-file").startSpan("GHRepository.readme");
|
||||||
span.setAttribute("owner", this.owner);
|
span.setAttribute("owner", this.owner);
|
||||||
@@ -154,9 +154,9 @@ export class GitHubRepository {
|
|||||||
const selected = model.branches.filter((f) => f.name == opt.branch)[0];
|
const selected = model.branches.filter((f) => f.name == opt.branch)[0];
|
||||||
if (selected && (!selected.readme || opt?.force === true)) {
|
if (selected && (!selected.readme || opt?.force === true)) {
|
||||||
// get the list of repo from github
|
// get the list of repo from github
|
||||||
const octokit = GitHubBase.octokit(opt.accessToken as string);
|
const oct = octokit(opt.accessToken);
|
||||||
try {
|
try {
|
||||||
const ghRes = await octokit.repos.getReadme({
|
const ghRes = await oct.repos.getReadme({
|
||||||
owner: this.owner,
|
owner: this.owner,
|
||||||
repo: this.repo,
|
repo: this.repo,
|
||||||
ref: selected?.commit,
|
ref: selected?.commit,
|
||||||
@@ -239,11 +239,11 @@ export async function getRepositoryFromGitHub(opt: {
|
|||||||
if (opt.repo.indexOf(".git") > -1) {
|
if (opt.repo.indexOf(".git") > -1) {
|
||||||
opt.repo = opt.repo.replace(".git", "");
|
opt.repo = opt.repo.replace(".git", "");
|
||||||
}
|
}
|
||||||
const octokit = GitHubBase.octokit(opt.accessToken as string);
|
const oct = octokit(opt.accessToken);
|
||||||
let r: RestEndpointMethodTypes["repos"]["get"]["response"]["data"];
|
let r: RestEndpointMethodTypes["repos"]["get"]["response"]["data"];
|
||||||
try {
|
try {
|
||||||
r = (
|
r = (
|
||||||
await octokit.repos.get({
|
await oct.repos.get({
|
||||||
owner: opt.owner,
|
owner: opt.owner,
|
||||||
repo: opt.repo,
|
repo: opt.repo,
|
||||||
})
|
})
|
||||||
@@ -282,7 +282,7 @@ export async function getRepositoryFromGitHub(opt: {
|
|||||||
model.defaultBranch = r.default_branch;
|
model.defaultBranch = r.default_branch;
|
||||||
model.hasPage = r.has_pages;
|
model.hasPage = r.has_pages;
|
||||||
if (model.hasPage) {
|
if (model.hasPage) {
|
||||||
const ghPageRes = await octokit.repos.getPages({
|
const ghPageRes = await oct.repos.getPages({
|
||||||
owner: opt.owner,
|
owner: opt.owner,
|
||||||
repo: opt.repo,
|
repo: opt.repo,
|
||||||
});
|
});
|
||||||
|
|||||||
+28
-42
@@ -1,7 +1,7 @@
|
|||||||
import AnonymizedFile from "../AnonymizedFile";
|
import AnonymizedFile from "../AnonymizedFile";
|
||||||
import GitHubBase from "./GitHubBase";
|
import GitHubBase, { GitHubBaseData } from "./GitHubBase";
|
||||||
import storage from "../storage";
|
import storage from "../storage";
|
||||||
import { SourceBase, Tree } from "../types";
|
import { Tree } from "../types";
|
||||||
import * as path from "path";
|
import * as path from "path";
|
||||||
import got from "got";
|
import got from "got";
|
||||||
|
|
||||||
@@ -10,28 +10,23 @@ import AnonymousError from "../AnonymousError";
|
|||||||
import config from "../../config";
|
import config from "../../config";
|
||||||
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";
|
||||||
|
|
||||||
export default class GitHubStream extends GitHubBase implements SourceBase {
|
export default class GitHubStream extends GitHubBase {
|
||||||
type: "GitHubDownload" | "GitHubStream" | "Zip" = "GitHubStream";
|
type: "GitHubDownload" | "GitHubStream" | "Zip" = "GitHubStream";
|
||||||
|
|
||||||
constructor(data: {
|
constructor(data: GitHubBaseData) {
|
||||||
branch?: string;
|
|
||||||
commit?: string;
|
|
||||||
repositoryId?: string;
|
|
||||||
repositoryName?: string;
|
|
||||||
accessToken?: string;
|
|
||||||
}) {
|
|
||||||
super(data);
|
super(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
downloadFile(sha: string, token: string) {
|
downloadFile(token: string, sha: string) {
|
||||||
const span = trace.getTracer("ano-file").startSpan("GHStream.downloadFile");
|
const span = trace.getTracer("ano-file").startSpan("GHStream.downloadFile");
|
||||||
span.setAttribute("sha", sha);
|
span.setAttribute("sha", sha);
|
||||||
const octokit = GitHubBase.octokit(token);
|
const oct = octokit(token);
|
||||||
try {
|
try {
|
||||||
const { url } = octokit.rest.git.getBlob.endpoint({
|
const { url } = oct.rest.git.getBlob.endpoint({
|
||||||
owner: this.githubRepository.owner,
|
owner: this.data.organization,
|
||||||
repo: this.githubRepository.repo,
|
repo: this.data.repoName,
|
||||||
file_sha: sha,
|
file_sha: sha,
|
||||||
});
|
});
|
||||||
return got.stream(url, {
|
return got.stream(url, {
|
||||||
@@ -46,7 +41,7 @@ export default class GitHubStream extends GitHubBase implements SourceBase {
|
|||||||
// span.recordException(error as Error);
|
// span.recordException(error as Error);
|
||||||
throw new AnonymousError("repo_not_accessible", {
|
throw new AnonymousError("repo_not_accessible", {
|
||||||
httpStatus: 404,
|
httpStatus: 404,
|
||||||
object: this.githubRepository,
|
object: this.data,
|
||||||
cause: error as Error,
|
cause: error as Error,
|
||||||
});
|
});
|
||||||
} finally {
|
} finally {
|
||||||
@@ -88,8 +83,7 @@ export default class GitHubStream extends GitHubBase implements SourceBase {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const token = await this.getToken(file.repository.owner.id);
|
const content = this.downloadFile(await this.data.getToken(), file_sha);
|
||||||
const content = this.downloadFile(file_sha, token);
|
|
||||||
|
|
||||||
// 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());
|
||||||
@@ -98,8 +92,7 @@ export default class GitHubStream extends GitHubBase implements SourceBase {
|
|||||||
file.repository.repoId,
|
file.repository.repoId,
|
||||||
file.filePath,
|
file.filePath,
|
||||||
stream1,
|
stream1,
|
||||||
file,
|
this.type
|
||||||
this
|
|
||||||
);
|
);
|
||||||
return stream2;
|
return stream2;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -126,17 +119,15 @@ export default class GitHubStream extends GitHubBase implements SourceBase {
|
|||||||
|
|
||||||
async getFiles() {
|
async getFiles() {
|
||||||
const span = trace.getTracer("ano-file").startSpan("GHStream.getFiles");
|
const span = trace.getTracer("ano-file").startSpan("GHStream.getFiles");
|
||||||
span.setAttribute("repoName", this.githubRepository.fullName || "");
|
span.setAttribute("repoId", this.data.repoId);
|
||||||
try {
|
try {
|
||||||
let commit = this.branch?.commit;
|
return this.getTree(this.data.commit);
|
||||||
return this.getTree(await this.getToken(), commit);
|
|
||||||
} finally {
|
} finally {
|
||||||
span.end();
|
span.end();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async getTree(
|
private async getTree(
|
||||||
token: string,
|
|
||||||
sha: string,
|
sha: string,
|
||||||
truncatedTree: Tree = {},
|
truncatedTree: Tree = {},
|
||||||
parentPath: string = "",
|
parentPath: string = "",
|
||||||
@@ -146,13 +137,12 @@ export default class GitHubStream extends GitHubBase implements SourceBase {
|
|||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
const span = trace.getTracer("ano-file").startSpan("GHStream.getTree");
|
const span = trace.getTracer("ano-file").startSpan("GHStream.getTree");
|
||||||
span.setAttribute("repoName", this.githubRepository.fullName || "");
|
|
||||||
span.setAttribute("sha", sha);
|
span.setAttribute("sha", sha);
|
||||||
|
|
||||||
let ghRes: Awaited<ReturnType<typeof this.getGHTree>>;
|
let ghRes: Awaited<ReturnType<typeof this.getGHTree>>;
|
||||||
try {
|
try {
|
||||||
count.request++;
|
count.request++;
|
||||||
ghRes = await this.getGHTree(token, sha, { recursive: true });
|
ghRes = await this.getGHTree(sha, { recursive: true });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
span.recordException(error as Error);
|
span.recordException(error as Error);
|
||||||
@@ -165,8 +155,6 @@ export default class GitHubStream extends GitHubBase implements SourceBase {
|
|||||||
httpStatus: (error as any).status,
|
httpStatus: (error as any).status,
|
||||||
cause: error as Error,
|
cause: error as Error,
|
||||||
object: {
|
object: {
|
||||||
owner: this.githubRepository.owner,
|
|
||||||
repo: this.githubRepository.repo,
|
|
||||||
tree_sha: sha,
|
tree_sha: sha,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -178,24 +166,20 @@ export default class GitHubStream extends GitHubBase implements SourceBase {
|
|||||||
const tree = this.tree2Tree(ghRes.tree, truncatedTree, parentPath);
|
const tree = this.tree2Tree(ghRes.tree, truncatedTree, parentPath);
|
||||||
count.file += ghRes.tree.length;
|
count.file += ghRes.tree.length;
|
||||||
if (ghRes.truncated) {
|
if (ghRes.truncated) {
|
||||||
await this.getTruncatedTree(token, sha, tree, parentPath, count);
|
await this.getTruncatedTree(sha, tree, parentPath, count);
|
||||||
}
|
}
|
||||||
span.end();
|
span.end();
|
||||||
return tree;
|
return tree;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async getGHTree(
|
private async getGHTree(sha: string, opt = { recursive: true }) {
|
||||||
token: string,
|
|
||||||
sha: string,
|
|
||||||
opt = { recursive: true }
|
|
||||||
) {
|
|
||||||
const span = trace.getTracer("ano-file").startSpan("GHStream.getGHTree");
|
const span = trace.getTracer("ano-file").startSpan("GHStream.getGHTree");
|
||||||
span.setAttribute("sha", sha);
|
span.setAttribute("sha", sha);
|
||||||
try {
|
try {
|
||||||
const octokit = GitHubBase.octokit(token);
|
const oct = octokit(await this.data.getToken());
|
||||||
const ghRes = await octokit.git.getTree({
|
const ghRes = await oct.git.getTree({
|
||||||
owner: this.githubRepository.owner,
|
owner: this.data.organization,
|
||||||
repo: this.githubRepository.repo,
|
repo: this.data.repoName,
|
||||||
tree_sha: sha,
|
tree_sha: sha,
|
||||||
recursive: opt.recursive ? "1" : undefined,
|
recursive: opt.recursive ? "1" : undefined,
|
||||||
});
|
});
|
||||||
@@ -206,7 +190,6 @@ export default class GitHubStream extends GitHubBase implements SourceBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async getTruncatedTree(
|
private async getTruncatedTree(
|
||||||
token: string,
|
|
||||||
sha: string,
|
sha: string,
|
||||||
truncatedTree: Tree = {},
|
truncatedTree: Tree = {},
|
||||||
parentPath: string = "",
|
parentPath: string = "",
|
||||||
@@ -226,7 +209,9 @@ export default class GitHubStream extends GitHubBase implements SourceBase {
|
|||||||
let data = null;
|
let data = null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
data = await this.getGHTree(token, sha, { recursive: false });
|
data = await this.getGHTree(sha, {
|
||||||
|
recursive: false,
|
||||||
|
});
|
||||||
this.tree2Tree(data.tree, truncatedTree, parentPath);
|
this.tree2Tree(data.tree, truncatedTree, parentPath);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
span.recordException(error as Error);
|
span.recordException(error as Error);
|
||||||
@@ -241,7 +226,6 @@ export default class GitHubStream extends GitHubBase implements SourceBase {
|
|||||||
const elementPath = path.join(parentPath, file.path);
|
const elementPath = path.join(parentPath, file.path);
|
||||||
promises.push(
|
promises.push(
|
||||||
this.getTruncatedTree(
|
this.getTruncatedTree(
|
||||||
token,
|
|
||||||
file.sha,
|
file.sha,
|
||||||
truncatedTree,
|
truncatedTree,
|
||||||
elementPath,
|
elementPath,
|
||||||
@@ -254,7 +238,9 @@ export default class GitHubStream extends GitHubBase implements SourceBase {
|
|||||||
await Promise.all(promises);
|
await Promise.all(promises);
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
const data = await this.getGHTree(token, sha, { recursive: true });
|
const data = await this.getGHTree(sha, {
|
||||||
|
recursive: true,
|
||||||
|
});
|
||||||
this.tree2Tree(data.tree, truncatedTree, parentPath);
|
this.tree2Tree(data.tree, truncatedTree, parentPath);
|
||||||
if (data.truncated) {
|
if (data.truncated) {
|
||||||
// TODO: TRUNCATED
|
// TODO: TRUNCATED
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
import AnonymizedFile from "../AnonymizedFile";
|
import AnonymizedFile from "../AnonymizedFile";
|
||||||
import storage from "../storage";
|
import storage from "../storage";
|
||||||
import { SourceBase } from "../types";
|
|
||||||
import * as stream from "stream";
|
import * as stream from "stream";
|
||||||
|
import { SourceBase } from "./GitHubBase";
|
||||||
|
|
||||||
export default class Zip implements SourceBase {
|
export default class Zip implements SourceBase {
|
||||||
type = "Zip";
|
type = "Zip";
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { SourceBase, Tree } from "../types";
|
import { Tree } from "../types";
|
||||||
import config from "../../config";
|
import config from "../../config";
|
||||||
import { Stream } from "node:stream";
|
|
||||||
import * as fs from "fs";
|
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";
|
||||||
@@ -8,7 +7,6 @@ import { Response } from "express";
|
|||||||
import { Readable, pipeline, Transform } 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 { lookup } from "mime-types";
|
import { lookup } from "mime-types";
|
||||||
import { trace } from "@opentelemetry/api";
|
import { trace } from "@opentelemetry/api";
|
||||||
import StorageBase, { FILE_TYPE } from "./Storage";
|
import StorageBase, { FILE_TYPE } from "./Storage";
|
||||||
@@ -78,9 +76,7 @@ export default class FileSystem extends StorageBase {
|
|||||||
async write(
|
async write(
|
||||||
repoId: string,
|
repoId: string,
|
||||||
p: string,
|
p: string,
|
||||||
data: string | Readable,
|
data: string | Readable
|
||||||
file?: AnonymizedFile,
|
|
||||||
source?: SourceBase
|
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const span = trace.getTracer("ano-file").startSpan("fs.write");
|
const span = trace.getTracer("ano-file").startSpan("fs.write");
|
||||||
const fullPath = join(config.FOLDER, this.repoPath(repoId), p);
|
const fullPath = join(config.FOLDER, this.repoPath(repoId), p);
|
||||||
@@ -170,13 +166,7 @@ export default class FileSystem extends StorageBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** @override */
|
/** @override */
|
||||||
async extractZip(
|
async extractZip(repoId: string, p: string, data: Readable): Promise<void> {
|
||||||
repoId: string,
|
|
||||||
p: string,
|
|
||||||
data: Readable,
|
|
||||||
file?: AnonymizedFile,
|
|
||||||
source?: SourceBase
|
|
||||||
): Promise<void> {
|
|
||||||
const pipe = promisify(pipeline);
|
const pipe = promisify(pipeline);
|
||||||
const fullPath = join(config.FOLDER, this.repoPath(repoId), p);
|
const fullPath = join(config.FOLDER, this.repoPath(repoId), p);
|
||||||
return pipe(
|
return pipe(
|
||||||
|
|||||||
+6
-9
@@ -14,9 +14,8 @@ import { contentType } from "mime-types";
|
|||||||
import * as archiver from "archiver";
|
import * as archiver from "archiver";
|
||||||
import { trace } from "@opentelemetry/api";
|
import { trace } from "@opentelemetry/api";
|
||||||
import { dirname, basename, join } from "path";
|
import { dirname, basename, join } from "path";
|
||||||
import { SourceBase, Tree, TreeFile } from "../types";
|
import { Tree, TreeFile } from "../types";
|
||||||
import AnonymousError from "../AnonymousError";
|
import AnonymousError from "../AnonymousError";
|
||||||
import AnonymizedFile from "../AnonymizedFile";
|
|
||||||
import StorageBase, { FILE_TYPE } from "./Storage";
|
import StorageBase, { FILE_TYPE } from "./Storage";
|
||||||
|
|
||||||
export default class S3Storage extends StorageBase {
|
export default class S3Storage extends StorageBase {
|
||||||
@@ -205,8 +204,7 @@ export default class S3Storage extends StorageBase {
|
|||||||
repoId: string,
|
repoId: string,
|
||||||
path: string,
|
path: string,
|
||||||
data: string | Readable,
|
data: string | Readable,
|
||||||
file?: AnonymizedFile,
|
source?: string
|
||||||
source?: SourceBase
|
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const span = trace.getTracer("ano-file").startSpan("s3.rm");
|
const span = trace.getTracer("ano-file").startSpan("s3.rm");
|
||||||
span.setAttribute("repoId", repoId);
|
span.setAttribute("repoId", repoId);
|
||||||
@@ -221,7 +219,7 @@ export default class S3Storage extends StorageBase {
|
|||||||
ContentType: contentType(path).toString(),
|
ContentType: contentType(path).toString(),
|
||||||
};
|
};
|
||||||
if (source) {
|
if (source) {
|
||||||
params.Tagging = `source=${source.type}`;
|
params.Tagging = `source=${source}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const parallelUploads3 = new Upload({
|
const parallelUploads3 = new Upload({
|
||||||
@@ -289,8 +287,7 @@ export default class S3Storage extends StorageBase {
|
|||||||
repoId: string,
|
repoId: string,
|
||||||
path: string,
|
path: string,
|
||||||
data: Readable,
|
data: Readable,
|
||||||
file?: AnonymizedFile,
|
source?: string
|
||||||
source?: SourceBase
|
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
let toS3: ArchiveStreamToS3;
|
let toS3: ArchiveStreamToS3;
|
||||||
const span = trace.getTracer("ano-file").startSpan("s3.extractZip");
|
const span = trace.getTracer("ano-file").startSpan("s3.extractZip");
|
||||||
@@ -305,9 +302,9 @@ export default class S3Storage extends StorageBase {
|
|||||||
onEntry: (header) => {
|
onEntry: (header) => {
|
||||||
header.name = header.name.substring(header.name.indexOf("/") + 1);
|
header.name = header.name.substring(header.name.indexOf("/") + 1);
|
||||||
if (source) {
|
if (source) {
|
||||||
header.Tagging = `source=${source.type}`;
|
header.Tagging = `source=${source}`;
|
||||||
header.Metadata = {
|
header.Metadata = {
|
||||||
source: source.type,
|
source: source,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -3,8 +3,7 @@ import { Transform, Readable } from "stream";
|
|||||||
import * as archiver from "archiver";
|
import * as archiver from "archiver";
|
||||||
import { Response } from "express";
|
import { Response } from "express";
|
||||||
|
|
||||||
import AnonymizedFile from "../AnonymizedFile";
|
import { Tree } from "../types";
|
||||||
import { SourceBase, Tree } from "../types";
|
|
||||||
|
|
||||||
export enum FILE_TYPE {
|
export enum FILE_TYPE {
|
||||||
FILE = "file",
|
FILE = "file",
|
||||||
@@ -52,8 +51,7 @@ export default abstract class StorageBase {
|
|||||||
repoId: string,
|
repoId: string,
|
||||||
path: string,
|
path: string,
|
||||||
data: string | Readable,
|
data: string | Readable,
|
||||||
file?: AnonymizedFile,
|
source?: string
|
||||||
source?: SourceBase
|
|
||||||
): Promise<void>;
|
): Promise<void>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -73,8 +71,7 @@ export default abstract class StorageBase {
|
|||||||
repoId: string,
|
repoId: string,
|
||||||
dir: string,
|
dir: string,
|
||||||
tar: Readable,
|
tar: Readable,
|
||||||
file?: AnonymizedFile,
|
source?: string
|
||||||
source?: SourceBase
|
|
||||||
): Promise<void>;
|
): Promise<void>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -3,30 +3,6 @@ import GitHubStream from "./source/GitHubStream";
|
|||||||
import Zip from "./source/Zip";
|
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 { Readable } from "stream";
|
|
||||||
|
|
||||||
export interface SourceBase {
|
|
||||||
readonly type: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The url of the source
|
|
||||||
*/
|
|
||||||
url?: 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(): Promise<Tree>;
|
|
||||||
|
|
||||||
toJSON(): any;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type Source = GitHubDownload | GitHubStream | Zip;
|
export type Source = GitHubDownload | GitHubStream | Zip;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user