aboutsummaryrefslogtreecommitdiff
path: root/packages
diff options
context:
space:
mode:
Diffstat (limited to 'packages')
-rw-r--r--packages/server/.eslintrc.js6
-rw-r--r--packages/server/src/api/git.ts242
-rw-r--r--packages/server/src/api/git_types.d.ts88
-rw-r--r--packages/server/src/api/util.ts13
-rw-r--r--packages/server/src/api/v1/index.ts18
-rw-r--r--packages/server/src/api/v1/repo.ts108
-rw-r--r--packages/server/src/api/v1/repo/branches.ts40
-rw-r--r--packages/server/src/api/v1/repo/index.ts48
-rw-r--r--packages/server/src/api/v1/repo/log.ts40
-rw-r--r--packages/server/src/app.ts45
-rw-r--r--packages/server/src/fastify_types.ts13
11 files changed, 355 insertions, 306 deletions
diff --git a/packages/server/.eslintrc.js b/packages/server/.eslintrc.js
index b9ebf8b..fb8ecfa 100644
--- a/packages/server/.eslintrc.js
+++ b/packages/server/.eslintrc.js
@@ -4,7 +4,7 @@ module.exports = {
es2021: true,
node: true
},
- extends: [ "standard" ],
+ extends: [ "standard", "plugin:@typescript-eslint/recommended" ],
parser: "@typescript-eslint/parser",
parserOptions: {
ecmaVersion: 12
@@ -106,13 +106,13 @@ module.exports = {
"max-params": [ "error", 6 ],
"max-statements": "off",
"max-statements-per-line": "error",
- "multiline-comment-style": "error",
+ "multiline-comment-style": [ "error", "bare-block" ],
"new-cap": "error",
"new-parens": "error",
"newline-per-chained-call": "error",
"no-alert": "error",
"no-array-constructor": "error",
- "no-await-in-loop": "error",
+ "no-await-in-loop": "off",
"no-bitwise": "error",
"no-caller": "error",
"no-confusing-arrow": "error",
diff --git a/packages/server/src/api/git.ts b/packages/server/src/api/git.ts
index 70e9fbd..d32a299 100644
--- a/packages/server/src/api/git.ts
+++ b/packages/server/src/api/git.ts
@@ -1,82 +1,16 @@
+import { Branch, Hunk, Hunks, LatestCommit, LogCommit, Patch, PatchHeaderData, RequestInfo, ShortBranch, ShortCommit, ShortRepository, ShortTag, ShortTreeEntry, Tree } from "./git_types";
import { Commit, ConvenientHunk, ConvenientPatch, Object, Oid, Repository, Revwalk, Tag, TreeEntry } from "nodegit";
import { FastifyReply, FastifyRequest } from "fastify";
+import { Pack, pack } from "tar-stream";
import { join, parse } from "path";
import { readFile, readdir } from "fs";
import { IncomingMessage } from "http";
+import { Route } from "../fastify_types";
import { URL } from "whatwg-url";
+import { createGzip } from "zlib";
+import { pipeline } from "stream";
import { spawn } from "child_process";
import { verifyGitRequest } from "./util";
-import { pack } from "tar-stream";
-import { pipeline } from "stream";
-import { createGzip } from "zlib";
-
-interface Hunk {
- new_start: number,
- new_lines_cnt: number,
- old_start: number,
- old_lines_cnt: number,
- new_lines: number[],
- deleted_lines: number[],
- hunk: string
-}
-interface Patch {
- from: string,
- to: string,
- additions: number,
- deletions: number,
- too_large: boolean,
- hunks: Hunk[] | null
-}
-export interface RequestInfo {
- repo: string,
- url_path: string,
- parsed_url: URL,
- url_path_parts: string[],
- is_discovery: boolean,
- service: string | null,
- content_type: string
-}
-interface TreeEntryLatestCommit {
- id: string | null,
- message: string | null,
- date: number | null
-}
-interface ShortTreeEntry {
- name: string,
- id: string,
- type: "blob" | "tree",
- latest_commit: TreeEntryLatestCommit
-}
-interface ShortRepository {
- name: string,
- description: string,
- owner: string,
- last_updated: number
-}
-type CommitAuthor = {
- name: string,
- email: string
-}
-type LogCommit = {
- id: string,
- author: CommitAuthor,
- date: number,
- message: string,
- insertions: number,
- deletions: number,
- files_changed: number
-}
-type ShortCommit = {
- id: string,
- author: CommitAuthor,
- message: string,
- date: number,
- patches: Patch[]
-}
-interface Hunks {
- prev: null | number,
- hunks: Hunk[]
-}
function addRepoDirSuffix(repo_name: string) {
return repo_name.endsWith(".git") ? repo_name : `${repo_name}.git`;
@@ -104,12 +38,6 @@ function getHunkContent(hunk: string[]) {
}
function getPatchHeaderData(patch_headers: string[], all_patches: string[]) {
- interface PatchHeaderData {
- indexes: number[],
- lengths: number[],
- last: number | null
- };
-
return patch_headers.reduce((patch_header_data, line, index) => {
// The start of a patch header
if((/^diff --git/u).test(line)) {
@@ -162,11 +90,13 @@ function getPatch(patch: ConvenientPatch, too_large: boolean, hunks?: Hunk[]): P
};
}
-function getRequestInfo(req: FastifyRequest): RequestInfo {
- const params: any = req.params;
+interface Request extends FastifyRequest {
+ params: Route["Params"],
+}
- const repo = params.repo + ".git";
- const url_path = req.url.replace(params.repo, repo);
+function getRequestInfo(req: Request): RequestInfo {
+ const repo = req.params.repo + ".git";
+ const url_path = req.url.replace(req.params.repo, repo);
const parsed_url = new URL(`${req.protocol}://${req.hostname}${url_path}`);
const url_path_parts = parsed_url.pathname.split("/");
@@ -217,7 +147,7 @@ async function getTreeEntryLastCommit(repo: Repository, tree_entry: TreeEntry) {
return result;
});
- }, Promise.resolve(<TreeEntryLatestCommit>{ id: null, message: null, date: null }));
+ }, Promise.resolve(<LatestCommit>{ id: null, message: null, date: null }));
}
function readDirectory(directory: string) {
@@ -232,6 +162,39 @@ function readDirectory(directory: string) {
});
}
+async function addArchiveEntries(entries: TreeEntry[], repo_name: string, archive: Pack) {
+ for(const tree_entry of entries) {
+ if(tree_entry.isBlob()) {
+ const blob = await tree_entry.getBlob();
+ archive.entry({ name: `${repo_name}/${tree_entry.path()}` }, blob.content().toString());
+ }
+ else if(tree_entry.isTree()) {
+ const tree = await tree_entry.getTree();
+ addArchiveEntries(tree.entries(), repo_name, archive);
+ }
+ }
+}
+
+function getTreeEntries(repo: Repository, entries: TreeEntry[]) {
+ return entries.reduce((acc, entry) => {
+ return acc.then(result => {
+ return getTreeEntryLastCommit(repo, entry).then(last_commit => {
+ result.push({
+ name: parse(entry.path()).base,
+ id: entry.sha(),
+ type: entry.isBlob() ? "blob" : "tree",
+ latest_commit: {
+ id: last_commit.id,
+ message: last_commit.message,
+ date: last_commit.date
+ }
+ });
+ return result;
+ });
+ });
+ }, Promise.resolve(<ShortTreeEntry[]>[]));
+}
+
export class GitAPI {
base_dir: string;
@@ -239,7 +202,7 @@ export class GitAPI {
this.base_dir = base_dir;
}
- async getLog(repo_name: string) {
+ async getLog(repo_name: string): Promise<LogCommit[]> {
const full_repo_name = addRepoDirSuffix(repo_name);
const repo = await Repository.openBare(`${this.base_dir}/${full_repo_name}`);
@@ -262,7 +225,7 @@ export class GitAPI {
}));
}
- async getRepositoryLastCommit(repo_name: string) {
+ async getRepositoryLastCommit(repo_name: string): Promise<number> {
const full_repo_name = addRepoDirSuffix(repo_name);
const repo = await Repository.openBare(`${this.base_dir}/${full_repo_name}`);
@@ -271,20 +234,20 @@ export class GitAPI {
return master_commit.time();
}
- getRepositoryFile(repo_name: string, file: string) {
- return new Promise<string>(resolve => {
+ getRepositoryFile(repo_name: string, file: string): Promise<string | null> {
+ return new Promise(resolve => {
const full_repo_name = addRepoDirSuffix(repo_name);
readFile(`${this.base_dir}/${full_repo_name}/${file}`, (err, content) => {
- if(!err) {
- resolve(content.toString().replace(/\n/gu, ""));
+ if(err) {
+ resolve(null);
return;
}
- resolve("");
+ resolve(content.toString().replace(/\n/gu, ""));
});
});
}
- async getRepositories() {
+ async getRepositories(): Promise<ShortRepository[] | null> {
const dir_content = await readDirectory(this.base_dir);
if(dir_content.length === 0) {
@@ -323,7 +286,6 @@ export class GitAPI {
const patch_start = patch_header_data.indexes[patch_index] + patch_header_data.lengths[patch_index];
const patch_end = (typeof patch_header_data.indexes[patch_index + 1] === "undefined") ? all_patches.length - 1 : patch_header_data.indexes[patch_index + 1];
const patch_content = all_patches.slice(patch_start, patch_end);
-
const line_lengths = patch_content.map(line => line.length).reduce((result, length) => result + length);
if(patch_content.length > 5000 || line_lengths > 5000) {
@@ -362,7 +324,7 @@ export class GitAPI {
};
}
- connectToGitHTTPBackend(req: FastifyRequest, reply: FastifyReply) {
+ connectToGitHTTPBackend(req: Request, reply: FastifyReply): void {
const request_info = getRequestInfo(req);
const valid_request = verifyGitRequest(request_info);
@@ -401,58 +363,33 @@ export class GitAPI {
git_pack.on("close", () => reply.raw.end());
}
- async getTree(repo_name: string, tree_path: string) {
+ async getTree(repo_name: string, tree_path: string | null): Promise<Tree | null> {
const full_repo_name = addRepoDirSuffix(repo_name);
const repo = await Repository.openBare(`${this.base_dir}/${full_repo_name}`);
const master_commit = await repo.getMasterCommit();
const tree = await master_commit.getTree();
- let entries = [];
if(tree_path) {
- try {
- const path_entry = await tree.getEntry(tree_path);
-
- if(path_entry.isBlob()) {
- return { type: "blob", content: (await path_entry.getBlob()).content().toString() };
- }
+ const path_entry = await tree.getEntry(tree_path)
+ .catch(() => null);
- entries = (await path_entry.getTree()).entries();
+ if(!path_entry) {
+ return null;
}
- catch(err) {
- if(err.errno === -3) {
- return null;
- }
- throw(err);
+
+ if(path_entry.isBlob()) {
+ return { type: "blob", content: (await path_entry.getBlob()).content().toString() };
}
- }
- else {
- entries = tree.entries();
+
+ const path_entry_tree = await path_entry.getTree();
+ return { type: "tree", content: await getTreeEntries(repo, path_entry_tree.entries()) };
}
- return {
- type: "tree",
- tree: await entries.reduce((acc, entry) => {
- return acc.then(result => {
- return getTreeEntryLastCommit(repo, entry).then(last_commit => {
- result.push({
- name: parse(entry.path()).base,
- id: entry.sha(),
- type: entry.isBlob() ? "blob" : "tree",
- latest_commit: {
- id: last_commit.id,
- message: last_commit.message,
- date: last_commit.date
- }
- });
- return result;
- });
- });
- }, Promise.resolve(<ShortTreeEntry[]>[]))
- };
+ return { type: "tree", content: await getTreeEntries(repo, tree.entries()) };
}
- async doesObjectExist(repo_name: string, id: string) {
+ async doesObjectExist(repo_name: string, id: string): Promise<boolean> {
const full_repo_name = addRepoDirSuffix(repo_name);
const repo = await Repository.openBare(`${this.base_dir}/${full_repo_name}`);
@@ -461,19 +398,19 @@ export class GitAPI {
.catch(() => false);
}
- async doesReadmeExist(repo_name: string) {
+ async doesReadmeExist(repo_name: string): Promise<boolean> {
const full_repo_name = addRepoDirSuffix(repo_name);
const repo = await Repository.openBare(`${this.base_dir}/${full_repo_name}`);
const master_commit = await repo.getMasterCommit();
const tree = await master_commit.getTree();
- const readme = await tree.getEntry("README.md").catch(() => null);
-
- return Boolean(readme);
+ return tree.getEntry("README.md").catch(() => null)
+ .then(() => true)
+ .catch(() => false);
}
- async getBranches(repo_name: string) {
+ async getBranches(repo_name: string): Promise<ShortBranch[]> {
const full_repo_name = addRepoDirSuffix(repo_name);
const repo = await Repository.openBare(`${this.base_dir}/${full_repo_name}`);
@@ -483,11 +420,11 @@ export class GitAPI {
return {
id: ref.target().tostrS(),
name: ref.shorthand()
- }
+ };
});
}
- async getBranch(repo_name: string, branch_id: string) {
+ async getBranch(repo_name: string, branch_id: string): Promise<Branch | null> {
const full_repo_name = addRepoDirSuffix(repo_name);
const repo = await Repository.openBare(`${this.base_dir}/${full_repo_name}`);
@@ -502,6 +439,7 @@ export class GitAPI {
const latest_commit = await repo.getBranchCommit(branch);
return {
+ id: branch.target().tostrS(),
name: branch.shorthand(),
latest_commit: {
id: latest_commit.sha(),
@@ -511,7 +449,7 @@ export class GitAPI {
};
}
- async getTags(repo_name: string) {
+ async getTags(repo_name: string): Promise<ShortTag[]> {
const full_repo_name = addRepoDirSuffix(repo_name);
const repo = await Repository.openBare(`${this.base_dir}/${full_repo_name}`);
@@ -544,16 +482,8 @@ export class GitAPI {
return;
}
- let tree;
-
- try {
- const commit = await Commit.lookup(repo, (await reference.peel(Object.TYPE.COMMIT)).id())
- tree = await commit.getTree()
- }
- catch {
- reply.code(500).send("Internal server error!");
- return;
- }
+ const commit = await Commit.lookup(repo, (await reference.peel(Object.TYPE.COMMIT)).id());
+ const tree = await commit.getTree();
const archive = pack();
const gzip = createGzip();
@@ -570,23 +500,11 @@ export class GitAPI {
gzip.on("error", () => reply.raw.end());
archive.on("error", () => reply.raw.end());
- async function addArchiveEntries(entries: TreeEntry[]) {
- for(const tree_entry of entries) {
- if(tree_entry.isBlob()) {
- const blob = await tree_entry.getBlob();
- archive.entry({ name: `${repo_name}/${tree_entry.path()}` }, blob.content().toString());
- }
- else if(tree_entry.isTree()) {
- await addArchiveEntries((await tree_entry.getTree()).entries());
- }
- }
- }
-
- addArchiveEntries(tree.entries())
+ addArchiveEntries(tree.entries(), repo_name, archive)
.then(() => archive.finalize())
.catch(() => {
archive.finalize();
reply.raw.end();
});
}
-}; \ No newline at end of file
+} \ No newline at end of file
diff --git a/packages/server/src/api/git_types.d.ts b/packages/server/src/api/git_types.d.ts
new file mode 100644
index 0000000..ae110ff
--- /dev/null
+++ b/packages/server/src/api/git_types.d.ts
@@ -0,0 +1,88 @@
+export interface Hunk {
+ new_start: number,
+ new_lines_cnt: number,
+ old_start: number,
+ old_lines_cnt: number,
+ new_lines: number[],
+ deleted_lines: number[],
+ hunk: string
+}
+export interface Patch {
+ from: string,
+ to: string,
+ additions: number,
+ deletions: number,
+ too_large: boolean,
+ hunks: Hunk[] | null
+}
+export interface RequestInfo {
+ repo: string,
+ url_path: string,
+ parsed_url: URL,
+ url_path_parts: string[],
+ is_discovery: boolean,
+ service: string | null,
+ content_type: string
+}
+export interface LatestCommit {
+ id: string | null,
+ message: string | null,
+ date: number | null
+}
+export interface ShortTreeEntry {
+ name: string,
+ id: string,
+ type: "blob" | "tree",
+ latest_commit: LatestCommit
+}
+export type Tree = {
+ type: "blob" | "tree",
+ content: string | ShortTreeEntry[]
+}
+export interface ShortRepository {
+ name: string,
+ description: string | null,
+ owner: string | null,
+ last_updated: number
+}
+export type Author = {
+ name: string,
+ email: string
+}
+export type LogCommit = {
+ id: string,
+ author: Author,
+ date: number,
+ message: string,
+ insertions: number,
+ deletions: number,
+ files_changed: number
+}
+export type ShortCommit = {
+ id: string,
+ author: Author,
+ message: string,
+ date: number,
+ patches: Patch[]
+}
+export interface Hunks {
+ prev: null | number,
+ hunks: Hunk[]
+}
+export interface ShortBranch {
+ id: string,
+ name: string
+}
+export interface Branch extends ShortBranch {
+ latest_commit: LatestCommit
+}
+export type ShortTag = {
+ name: string,
+ date: number,
+ author: Author
+}
+export type PatchHeaderData = {
+ indexes: number[],
+ lengths: number[],
+ last: number | null
+} \ No newline at end of file
diff --git a/packages/server/src/api/util.ts b/packages/server/src/api/util.ts
index 1cc135f..b05ebb7 100644
--- a/packages/server/src/api/util.ts
+++ b/packages/server/src/api/util.ts
@@ -1,15 +1,16 @@
-import { GitAPI, RequestInfo } from "./git";
+import { GitAPI } from "./git";
+import { RequestInfo } from "./git_types";
import { readdir } from "fs";
type VerificationResultType = "SUCCESS" | "NOT_FOUND" | "INVALID" | "ACCESS_DENIED";
export class VerificationResult {
constructor(result: VerificationResultType, subject?: string) {
- this.success = result === "SUCCESS" ? true : false;
+ this.success = result === "SUCCESS";
if(result !== "SUCCESS") {
const verification_error_types = {
- NOT_FOUND: { code: 404, message: `${ String(subject?.substr(0, 1).toUpperCase()) + subject?.substr(1)} not found!` },
+ NOT_FOUND: { code: 404, message: `${String(subject?.substr(0, 1).toUpperCase()) + subject?.substr(1)} not found!` },
INVALID: { code: 403, message: `Invalid ${subject}` },
ACCESS_DENIED: { code: 403, message: "Access denied!" }
};
@@ -24,7 +25,7 @@ export class VerificationResult {
message: string | null = null;
}
-export function verifyRepoName(base_dir: string, repo_name: string) {
+export function verifyRepoName(base_dir: string, repo_name: string): Promise<VerificationResult> {
return new Promise<VerificationResult>(resolve => {
console.log(repo_name);
const is_valid_repo_name = (/^[a-zA-Z0-9.\-_]+$/u).test(repo_name);
@@ -50,7 +51,7 @@ export function verifyRepoName(base_dir: string, repo_name: string) {
});
}
-export async function verifySHA(git: GitAPI, repo_name: string, sha: string) {
+export async function verifySHA(git: GitAPI, repo_name: string, sha: string): Promise<VerificationResult> {
if(!(/^[a-fA-F0-9]+$/u).test(sha)) {
return new VerificationResult("INVALID", "sha");
}
@@ -64,7 +65,7 @@ export async function verifySHA(git: GitAPI, repo_name: string, sha: string) {
return new VerificationResult("SUCCESS");
}
-export function verifyGitRequest(request_info: RequestInfo) {
+export function verifyGitRequest(request_info: RequestInfo): VerificationResult {
if((/\.\/|\.\./u).test(request_info.parsed_url.pathname)) {
return new VerificationResult("INVALID", "path");
}
diff --git a/packages/server/src/api/v1/index.ts b/packages/server/src/api/v1/index.ts
index 31ab24f..00652a8 100644
--- a/packages/server/src/api/v1/index.ts
+++ b/packages/server/src/api/v1/index.ts
@@ -1,9 +1,10 @@
import { FastifyInstance, FastifyPluginOptions } from "fastify";
-import { verifyRepoName } from "../util";
import { GitAPI } from "../git";
+import { Route } from "../../fastify_types";
import repo from "./repo";
+import { verifyRepoName } from "../util";
-export default function(fastify: FastifyInstance, opts: FastifyPluginOptions, done: any) {
+export default function(fastify: FastifyInstance, opts: FastifyPluginOptions, done: (err?: Error) => void): void {
const git = new GitAPI(opts.config.settings.base_dir);
fastify.setErrorHandler((err, req, reply) => {
@@ -12,7 +13,7 @@ export default function(fastify: FastifyInstance, opts: FastifyPluginOptions, do
});
fastify.setNotFoundHandler((req, reply) => {
reply.code(404).send({ error: "Endpoint not found!" });
- })
+ });
fastify.route({
method: "GET",
@@ -31,23 +32,22 @@ export default function(fastify: FastifyInstance, opts: FastifyPluginOptions, do
}
});
- fastify.route({
+ fastify.route<Route>({
method: "GET",
url: "/repos/:repo",
handler: async(req, reply) => {
- const params: any = req.params;
- const repo_verification = await verifyRepoName(opts.config.settings.base_dir, params.repo);
+ const repo_verification = await verifyRepoName(opts.config.settings.base_dir, req.params.repo);
if(repo_verification.success === false && repo_verification.code) {
reply.code(repo_verification.code).send({ error: repo_verification.message });
}
- const desc = await git.getRepositoryFile(params.repo, "description");
+ const desc = await git.getRepositoryFile(req.params.repo, "description");
- reply.send({ data: { name: params.repo, description: desc, has_readme: await git.doesReadmeExist(params.repo) } });
+ reply.send({ data: { name: req.params.repo, description: desc, has_readme: await git.doesReadmeExist(req.params.repo) } });
}
});
fastify.register(repo, { prefix: "/repos/:repo", config: { git: git, settings: opts.config.settings } });
done();
-}; \ No newline at end of file
+} \ No newline at end of file
diff --git a/packages/server/src/api/v1/repo.ts b/packages/server/src/api/v1/repo.ts
deleted file mode 100644
index 6ef2923..0000000
--- a/packages/server/src/api/v1/repo.ts
+++ /dev/null
@@ -1,108 +0,0 @@
-import { FastifyInstance, FastifyPluginOptions } from "fastify";
-import { verifySHA, verifyRepoName } from "../util";
-
-export default function (fastify: FastifyInstance, opts: FastifyPluginOptions, done: any) {
- const git = opts.config.git;
-
- fastify.addHook("onRequest", async(req, reply) => {
- const params: any = req.params;
- const repo_verification = await verifyRepoName(opts.config.settings.base_dir, params.repo);
- if(repo_verification.success === false && repo_verification.code) {
- reply.code(repo_verification.code).send({ error: repo_verification.message });
- }
- });
-
- fastify.route({
- method: "GET",
- url: "/log",
- handler: async(req, reply) => {
- const log = await git.getLog((<any>req.params).repo);
-
- if(log.length === 0) {
- reply.code(500).send({ error: "Internal server error!" });
- return;
- }
-
- reply.send({ data: log });
- }
- });
-
- fastify.route({
- method: "GET",
- url: "/log/:commit",
- handler: async(req, reply) => {
- const params: any = req.params;
- const commit_verification = await verifySHA(git, params.repo, params.commit);
- if(commit_verification.success === false && commit_verification.code) {
- reply.code(commit_verification.code).send({ error: commit_verification.message });
- }
-
- const commit = await git.getCommit(params.repo, params.commit);
-
- reply.send({ data: commit });
- }
- });
-
- fastify.route({
- method: "GET",
- url: "/tree",
- handler: async(req, reply) => {
- const params: any = req.params;
- const query: any = req.query;
-
- const tree_path = (query.length !== 0 && query.path) ? query.path : null;
-
- const tree = await git.getTree(params.repo, tree_path);
-
- if(!tree) {
- reply.code(404).send({ error: "Path not found" });
- }
-
- reply.send({ data: tree });
- }
- });
-
- fastify.route({
- method: "GET",
- url: "/branches",
- handler: async(req, reply) => {
- const params: any = req.params;
- const branches = await git.getBranches(params.repo);
-
- reply.send({ data: branches });
- }
- });
-
- fastify.route({
- method: "GET",
- url: "/branches/:branch",
- handler: async(req, reply) => {
- const params: any = req.params;
- const branch_verification = await verifySHA(git, params.repo, params.branch);
- if(branch_verification.success === false && branch_verification.code) {
- reply.code(branch_verification.code).send({ error: branch_verification.message });
- }
-
- const branch = await git.getBranch(params.repo, params.branch);
-
- if(!branch) {
- reply.code(404).send({ error: "Branch not found!" });
- return;
- }
-
- reply.send({ data: branch });
- }
- });
-
- fastify.route({
- method: "GET",
- url: "/tags",
- handler: async(req, reply) => {
- const params: any = req.params;
- const refs = await git.getTags(params.repo);
- reply.send({ data: refs });
- }
- });
-
- done();
-} \ No newline at end of file
diff --git a/packages/server/src/api/v1/repo/branches.ts b/packages/server/src/api/v1/repo/branches.ts
new file mode 100644
index 0000000..fd8056a
--- /dev/null
+++ b/packages/server/src/api/v1/repo/branches.ts
@@ -0,0 +1,40 @@
+import { FastifyInstance, FastifyPluginOptions } from "fastify";
+import { GitAPI } from "../../git";
+import { Route } from "../../../fastify_types";
+import { verifySHA } from "../../util";
+
+export default function(fastify: FastifyInstance, opts: FastifyPluginOptions, done: (err?: Error) => void): void {
+ const git: GitAPI = opts.config.git;
+
+ fastify.route<Route>({
+ method: "GET",
+ url: "/branches",
+ handler: async(req, reply) => {
+ const branches = await git.getBranches(req.params.repo);
+
+ reply.send({ data: branches });
+ }
+ });
+
+ fastify.route<Route>({
+ method: "GET",
+ url: "/branches/:branch",
+ handler: async(req, reply) => {
+ const branch_verification = await verifySHA(git, req.params.repo, req.params.branch);
+ if(branch_verification.success === false && branch_verification.code) {
+ reply.code(branch_verification.code).send({ error: branch_verification.message });
+ }
+
+ const branch = await git.getBranch(req.params.repo, req.params.branch);
+
+ if(!branch) {
+ reply.code(404).send({ error: "Branch not found!" });
+ return;
+ }
+
+ reply.send({ data: branch });
+ }
+ });
+
+ done();
+} \ No newline at end of file
diff --git a/packages/server/src/api/v1/repo/index.ts b/packages/server/src/api/v1/repo/index.ts
new file mode 100644
index 0000000..86e5d10
--- /dev/null
+++ b/packages/server/src/api/v1/repo/index.ts
@@ -0,0 +1,48 @@
+import { FastifyInstance, FastifyPluginOptions, FastifyRequest } from "fastify";
+import { GitAPI } from "../../git";
+import { Route } from "../../../fastify_types";
+import branches from "./branches";
+import log from "./log";
+import { verifyRepoName } from "../../util";
+
+export default function(fastify: FastifyInstance, opts: FastifyPluginOptions, done: (err?: Error) => void): void {
+ const git: GitAPI = opts.config.git;
+
+ fastify.addHook("onRequest", async(req: FastifyRequest<Route>, reply) => {
+ const repo_verification = await verifyRepoName(opts.config.settings.base_dir, req.params.repo);
+ if(repo_verification.success === false && repo_verification.code) {
+ reply.code(repo_verification.code).send({ error: repo_verification.message });
+ }
+ });
+
+ fastify.register(log, { config: { git: git } });
+ fastify.register(branches, { config: { git: git } });
+
+ fastify.route<Route>({
+ method: "GET",
+ url: "/tree",
+ handler: async(req, reply) => {
+ const tree_path = (Object.keys(req.query).length !== 0 && req.query.path) ? req.query.path : null;
+
+ const tree = await git.getTree(req.params.repo, tree_path);
+
+ if(!tree) {
+ reply.code(404).send({ error: "Path not found" });
+ return;
+ }
+
+ reply.send({ data: tree });
+ }
+ });
+
+ fastify.route<Route>({
+ method: "GET",
+ url: "/tags",
+ handler: async(req, reply) => {
+ const refs = await git.getTags(req.params.repo);
+ reply.send({ data: refs });
+ }
+ });
+
+ done();
+} \ No newline at end of file
diff --git a/packages/server/src/api/v1/repo/log.ts b/packages/server/src/api/v1/repo/log.ts
new file mode 100644
index 0000000..f692b00
--- /dev/null
+++ b/packages/server/src/api/v1/repo/log.ts
@@ -0,0 +1,40 @@
+import { FastifyInstance, FastifyPluginOptions } from "fastify";
+import { GitAPI } from "../../git";
+import { Route } from "../../../fastify_types";
+import { verifySHA } from "../../util";
+
+export default function(fastify: FastifyInstance, opts: FastifyPluginOptions, done: (err?: Error) => void): void {
+ const git: GitAPI = opts.config.git;
+
+ fastify.route<Route>({
+ method: "GET",
+ url: "/log",
+ handler: async(req, reply) => {
+ const log = await git.getLog(req.params.repo);
+
+ if(log.length === 0) {
+ reply.code(500).send({ error: "Internal server error!" });
+ return;
+ }
+
+ reply.send({ data: log });
+ }
+ });
+
+ fastify.route<Route>({
+ method: "GET",
+ url: "/log/:commit",
+ handler: async(req, reply) => {
+ const commit_verification = await verifySHA(git, req.params.repo, req.params.commit);
+ if(commit_verification.success === false && commit_verification.code) {
+ reply.code(commit_verification.code).send({ error: commit_verification.message });
+ }
+
+ const commit = await git.getCommit(req.params.repo, req.params.commit);
+
+ reply.send({ data: commit });
+ }
+ });
+
+ done();
+} \ No newline at end of file
diff --git a/packages/server/src/app.ts b/packages/server/src/app.ts
index 3cb1c07..e8200d7 100644
--- a/packages/server/src/app.ts
+++ b/packages/server/src/app.ts
@@ -1,13 +1,25 @@
import { readFileSync, readdirSync } from "fs";
import { GitAPI } from "./api/git";
+import { Route } from "./fastify_types";
import api from "./api/v1";
import { exit } from "process";
import { fastify as fastifyFactory } from "fastify";
+import fastifyStatic from "fastify-static";
import { join } from "path";
import { load } from "js-yaml";
import { verifyRepoName } from "./api/util";
-const settings = <any>load(readFileSync(join(__dirname, "/../../../settings.yml"), "utf8"));
+type Settings = {
+ host: string,
+ port: number,
+ dev_port: number,
+ title: string,
+ about: string,
+ base_dir: string,
+ production: boolean
+}
+
+const settings = <Settings>load(readFileSync(join(__dirname, "/../../../settings.yml"), "utf8"));
const settings_keys = Object.keys(settings);
const mandatory_settings = [ "host", "port", "dev_port", "title", "about", "base_dir", "production" ];
@@ -57,12 +69,14 @@ fastify.setNotFoundHandler({}, function(req, reply) {
});
if(settings.production) {
- fastify.register(require("fastify-static"), { root: dist_dir });
+ fastify.register(fastifyStatic, { root: dist_dir });
fastify.route({
method: "GET",
url: "/",
- handler: (req, reply: any) => reply.sendFile("index.html")
+ handler: (req, reply) => {
+ reply.sendFile("index.html");
+ }
});
}
@@ -70,31 +84,26 @@ fastify.addContentTypeParser("application/x-git-upload-pack-request", (req, payl
fastify.register(api, { prefix: "/api/v1", config: { settings: settings } });
-interface Query {
- [key: string]: string
-}
-
-fastify.route({
+fastify.route<Route>({
method: "GET",
url: "/:repo([a-zA-Z0-9\\.\\-_]+)/info/refs",
handler: async(req, reply) => {
reply.header("Content-Type", "application/x-git-upload-pack-advertisement");
- const repo_verification = await verifyRepoName(settings.base_dir, (<any>req).params.repo);
+ const repo_verification = await verifyRepoName(settings.base_dir, req.params.repo);
if(repo_verification.success === false && repo_verification.code) {
reply.code(repo_verification.code).send(repo_verification.message);
}
- const query: Query = <any>req.query;
- if(!query.service) {
+ if(!req.query.service) {
reply.code(403).send("Missing service query parameter\n");
return;
}
- else if(query.service !== "git-upload-pack") {
+ else if(req.query.service !== "git-upload-pack") {
reply.code(403).send("Access denied!\n");
return;
}
- else if(Object.keys(query).length !== 1) {
+ else if(Object.keys(req.query).length !== 1) {
reply.code(403).send("Too many query parameters!\n");
return;
}
@@ -103,11 +112,12 @@ fastify.route({
}
});
-fastify.route({
+fastify.route<Route>({
method: "POST",
url: "/:repo([a-zA-Z0-9\\.\\-_]+)/git-upload-pack",
handler: async(req, reply) => {
- const repo_verification = await verifyRepoName(settings.base_dir, (<any>req).params.repo);
+ const repo_verification = await verifyRepoName(settings.base_dir, req.params.repo);
+
if(repo_verification.success === false && repo_verification.code) {
reply.header("Content-Type", "application/x-git-upload-pack-result");
reply.code(repo_verification.code).send(repo_verification.message);
@@ -117,12 +127,11 @@ fastify.route({
}
});
-fastify.route({
+fastify.route<Route>({
method: "GET",
url: "/:repo([a-zA-Z0-9\\.\\-_]+)/refs/tags/:tag",
handler: (req, reply) => {
- const params: any = req.params;
- git.downloadTagArchive(params.repo, params.tag, reply);
+ git.downloadTagArchive(req.params.repo, req.params.tag, reply);
}
});
diff --git a/packages/server/src/fastify_types.ts b/packages/server/src/fastify_types.ts
new file mode 100644
index 0000000..986fdf2
--- /dev/null
+++ b/packages/server/src/fastify_types.ts
@@ -0,0 +1,13 @@
+import { ReplyGenericInterface } from "fastify/types/reply";
+import { RequestGenericInterface } from "fastify";
+
+export interface Request extends RequestGenericInterface {
+ Params: {
+ [key: string]: string
+ },
+ Querystring: {
+ [key: string]: string
+ }
+}
+
+export interface Route extends Request, ReplyGenericInterface {} \ No newline at end of file