aboutsummaryrefslogtreecommitdiff
path: root/packages/server/src
diff options
context:
space:
mode:
authorHampusM <hampus@hampusmat.com>2021-07-23 14:02:57 +0200
committerHampusM <hampus@hampusmat.com>2021-07-23 14:02:57 +0200
commit22e44b4ef5f74f7058c01ade82ce14aaf9214f18 (patch)
tree2f24834b6d45af6f0c94357eafef5523b797d0cd /packages/server/src
parent998d566194cf901a2c927851f9b87c8b577d21a9 (diff)
Cleaned and improved the backend
Put the initial Fastify stuff into a dedicated file, organized the types & made several improvements in the git http backend
Diffstat (limited to 'packages/server/src')
-rw-r--r--packages/server/src/api/v1/index.ts2
-rw-r--r--packages/server/src/api/v1/repo/branches.ts2
-rw-r--r--packages/server/src/api/v1/repo/index.ts2
-rw-r--r--packages/server/src/api/v1/repo/log.ts2
-rw-r--r--packages/server/src/app.ts250
-rw-r--r--packages/server/src/git/http.ts40
-rw-r--r--packages/server/src/server.ts59
-rw-r--r--packages/server/src/types/fastify.d.ts (renamed from packages/server/src/fastify_types.ts)0
-rw-r--r--packages/server/src/types/index.d.ts9
9 files changed, 189 insertions, 177 deletions
diff --git a/packages/server/src/api/v1/index.ts b/packages/server/src/api/v1/index.ts
index 169ba3a..e2e2104 100644
--- a/packages/server/src/api/v1/index.ts
+++ b/packages/server/src/api/v1/index.ts
@@ -1,6 +1,6 @@
import { FastifyInstance, FastifyPluginOptions } from "fastify";
import { Repository } from "../../git/repository";
-import { Route } from "../../fastify_types";
+import { Route } from "../../types/fastify";
import repo from "./repo";
import { verifyRepoName } from "../util";
import { Info as APIInfo, RepositorySummary as APIRepositorySummary, Repository as APIRepository } from "shared_types";
diff --git a/packages/server/src/api/v1/repo/branches.ts b/packages/server/src/api/v1/repo/branches.ts
index c01e858..4aa6665 100644
--- a/packages/server/src/api/v1/repo/branches.ts
+++ b/packages/server/src/api/v1/repo/branches.ts
@@ -1,6 +1,6 @@
import { FastifyInstance, FastifyPluginOptions } from "fastify";
import { Branch } from "../../../git/branch";
-import { Route } from "../../../fastify_types";
+import { Route } from "../../../types/fastify";
import { BranchSummary as APIBranchSummary, Branch as APIBranch } from "shared_types";
export default function(fastify: FastifyInstance, opts: FastifyPluginOptions, done: (err?: Error) => void): void {
diff --git a/packages/server/src/api/v1/repo/index.ts b/packages/server/src/api/v1/repo/index.ts
index e33484a..1fe08e9 100644
--- a/packages/server/src/api/v1/repo/index.ts
+++ b/packages/server/src/api/v1/repo/index.ts
@@ -1,4 +1,4 @@
-import { CoolFastifyRequest, Route } from "../../../fastify_types";
+import { CoolFastifyRequest, Route } from "../../../types/fastify";
import { FastifyInstance, FastifyPluginOptions } from "fastify";
import { Blob } from "../../../git/blob";
import { Repository } from "../../../git/repository";
diff --git a/packages/server/src/api/v1/repo/log.ts b/packages/server/src/api/v1/repo/log.ts
index fb876b8..24937ad 100644
--- a/packages/server/src/api/v1/repo/log.ts
+++ b/packages/server/src/api/v1/repo/log.ts
@@ -1,7 +1,7 @@
import { FastifyInstance, FastifyPluginOptions } from "fastify";
import { Commit } from "../../../git/commit";
import { Patch } from "../../../git/patch";
-import { Route } from "../../../fastify_types";
+import { Route } from "../../../types/fastify";
import { verifySHA } from "../../util";
import { LogCommit as APILogCommit, Patch as APIPatch, Commit as APICommit } from "shared_types";
diff --git a/packages/server/src/app.ts b/packages/server/src/app.ts
index ae220a9..71fe107 100644
--- a/packages/server/src/app.ts
+++ b/packages/server/src/app.ts
@@ -1,183 +1,123 @@
-import { readFileSync, readdirSync } from "fs";
import { Repository } from "./git/repository";
-import { Route } from "./fastify_types";
+import { Route } from "./types/fastify";
import { Tag } from "./git/tag";
import api from "./api/v1";
-import { exit } from "process";
-import { fastify as fastifyFactory } from "fastify";
+import { fastify as fastifyFactory, FastifyInstance } from "fastify";
import fastifyStatic from "fastify-static";
-import { join } from "path";
-import { load } from "js-yaml";
import { verifyRepoName } from "./api/util";
import { BaseError } from "./git/error";
+import { Settings } from "./types";
-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" ];
-
-// Make sure that all the required settings are present
-const settings_not_included = mandatory_settings.filter(x => !settings_keys.includes(x));
-if(settings_not_included.length !== 0) {
- console.log(`Error: settings.yml is missing ${(settings_not_included.length > 1) ? "keys" : "key"}:`);
- console.log(settings_not_included.join(", "));
- exit(1);
-}
-
-// Make sure that there's not an excessive amount of settings
-const mandatory_not_included = settings_keys.filter(x => !mandatory_settings.includes(x));
-if(mandatory_not_included.length !== 0) {
- console.log(`Error: settings.yml includes ${(mandatory_not_included.length > 1) ? "pointless keys" : "a pointless key"}:`);
- console.log(mandatory_not_included.join(", "));
- exit(1);
-}
-
-// Make sure that the base directory specified in the settings actually exists
-try {
- readdirSync(settings.base_dir);
-}
-catch {
- console.error(`Error: Tried opening the base directory. No such directory: ${settings.base_dir}`);
- exit(1);
-}
-
-const dist_dir = join(__dirname, "/../../client/dist");
-
-if(settings.production) {
- try {
- readdirSync(dist_dir);
- }
- catch {
- console.error("Error: Tried opening the dist directory but it doesn't exist.\nDid you accidentally turn on the production setting?");
- exit(1);
- }
-}
-
-const fastify = fastifyFactory();
-
-fastify.setErrorHandler((err, req, reply) => {
- console.log(err);
- reply.code(500).send("Internal server error!");
-});
+export default function buildApp(settings: Settings, dist_dir: string): FastifyInstance {
+ const fastify = fastifyFactory();
-fastify.setNotFoundHandler({}, function(req, reply) {
- reply.code(404).send("Page not found!");
-});
-
-if(settings.production) {
- fastify.register(fastifyStatic, { root: dist_dir });
+ fastify.setErrorHandler((err, req, reply) => {
+ console.log(err);
+ reply.code(500).send("Internal server error!");
+ });
- fastify.route({
- method: "GET",
- url: "/",
- handler: (req, reply) => {
- reply.sendFile("index.html");
- }
+ fastify.setNotFoundHandler({}, function(req, reply) {
+ reply.code(404).send("Page not found!");
});
-}
-fastify.addContentTypeParser("application/x-git-upload-pack-request", (req, payload, done) => done(null, payload));
+ if(settings.production) {
+ fastify.register(fastifyStatic, { root: dist_dir });
-fastify.register(api, { prefix: "/api/v1", config: { settings: settings } });
+ fastify.route({
+ method: "GET",
+ url: "/",
+ handler: (req, reply) => {
+ reply.sendFile("index.html");
+ }
+ });
+ }
-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");
+ fastify.addContentTypeParser("application/x-git-upload-pack-request", (req, payload, done) => done(null, payload));
- if(!verifyRepoName(req.params.repo)) {
- reply.code(400).send({ error: "Bad request" });
- return;
- }
+ fastify.register(api, { prefix: "/api/v1", config: { settings: settings } });
- if(!req.query.service) {
- reply.header("Content-Type", "text/plain");
- reply.code(403).send("Missing service query parameter\n");
- return;
+ 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");
+
+ if(!verifyRepoName(req.params.repo)) {
+ reply.code(400).send({ error: "Bad request" });
+ return;
+ }
+
+ if(!req.query.service) {
+ reply.header("Content-Type", "text/plain");
+ reply.code(403).send("Missing service query parameter\n");
+ return;
+ }
+
+ if(req.query.service !== "git-upload-pack") {
+ reply.header("Content-Type", "text/plain");
+ reply.code(403).send("Access denied!\n");
+ return;
+ }
+
+ if(Object.keys(req.query).length !== 1) {
+ reply.code(403).send("Too many query parameters!\n");
+ return;
+ }
+
+ const repository = await Repository.open(settings.base_dir, req.params.repo);
+ repository.HTTPconnect(req, reply);
}
+ });
- if(req.query.service !== "git-upload-pack") {
- reply.header("Content-Type", "text/plain");
- reply.code(403).send("Access denied!\n");
- return;
+ fastify.route<Route>({
+ method: "POST",
+ url: "/:repo([a-zA-Z0-9\\.\\-_]+)/git-upload-pack",
+ handler: async(req, reply) => {
+ if(!verifyRepoName(req.params.repo)) {
+ reply.code(400).send({ error: "Bad request" });
+ return;
+ }
+
+ const repository = await Repository.open(settings.base_dir, req.params.repo);
+ repository.HTTPconnect(req, reply);
}
+ });
- if(Object.keys(req.query).length !== 1) {
- reply.code(403).send("Too many query parameters!\n");
- return;
+ fastify.route({
+ method: "POST",
+ url: "/:repo([a-zA-Z0-9\\.\\-_]+)/git-receive-pack",
+ handler: (req, reply) => {
+ reply.header("Content-Type", "application/x-git-receive-pack-result");
+ reply.code(403).send("Access denied!");
}
+ });
- const repository = await Repository.open(settings.base_dir, req.params.repo);
- repository.HTTPconnect(req, reply);
- }
-});
-
-fastify.route<Route>({
- method: "POST",
- url: "/:repo([a-zA-Z0-9\\.\\-_]+)/git-upload-pack",
- handler: async(req, reply) => {
- if(!verifyRepoName(req.params.repo)) {
- reply.code(400).send({ error: "Bad request" });
- return;
- }
+ fastify.route<Route>({
+ method: "GET",
+ url: "/:repo([a-zA-Z0-9\\.\\-_]+)/refs/tags/:tag",
+ handler: async(req, reply) => {
+ if(!verifyRepoName(req.params.repo)) {
+ reply.code(400).send({ error: "Bad request" });
+ return;
+ }
- const repository = await Repository.open(settings.base_dir, req.params.repo);
- repository.HTTPconnect(req, reply);
- }
-});
-
-fastify.route({
- method: "POST",
- url: "/:repo([a-zA-Z0-9\\.\\-_]+)/git-receive-pack",
- handler: (req, reply) => {
- reply.header("Content-Type", "application/x-git-receive-pack-result");
- reply.code(403).send("Access denied!");
- }
-});
-
-fastify.route<Route>({
- method: "GET",
- url: "/:repo([a-zA-Z0-9\\.\\-_]+)/refs/tags/:tag",
- handler: async(req, reply) => {
- if(!verifyRepoName(req.params.repo)) {
- reply.code(400).send({ error: "Bad request" });
- return;
- }
+ const repository: Repository | BaseError = await Repository.open(settings.base_dir, req.params.repo).catch(err => err);
- const repository: Repository | BaseError = await Repository.open(settings.base_dir, req.params.repo).catch(err => err);
+ if(repository instanceof BaseError) {
+ reply.code(repository.code).send(repository.message);
+ return;
+ }
- if(repository instanceof BaseError) {
- reply.code(repository.code).send(repository.message);
- return;
- }
+ const tag = await Tag.lookup(repository, req.params.tag).catch(err => err);
- const tag = await Tag.lookup(repository, req.params.tag).catch(err => err);
+ if(tag instanceof BaseError) {
+ reply.code(tag.code).send(tag.message);
+ return;
+ }
- if(tag instanceof BaseError) {
- reply.code(tag.code).send(tag.message);
- return;
+ tag.downloadTarball(reply);
}
+ });
- tag.downloadTarball(reply);
- }
-});
-
-fastify.listen(settings.port, settings.host, (err: Error, addr: string) => {
- if(err) {
- console.error(err);
- exit(1);
- }
-
- console.log(`App is running on ${addr}`);
-}); \ No newline at end of file
+ return fastify;
+} \ No newline at end of file
diff --git a/packages/server/src/git/http.ts b/packages/server/src/git/http.ts
index 2d707cb..66d296c 100644
--- a/packages/server/src/git/http.ts
+++ b/packages/server/src/git/http.ts
@@ -1,18 +1,16 @@
import { FastifyReply, FastifyRequest } from "fastify";
-import { IncomingMessage } from "http";
import { Repository } from "./repository";
-import { Route } from "../fastify_types";
+import { Route } from "../types/fastify";
import { join } from "path";
import { spawn } from "child_process";
import { verifyGitRequest } from "../api/util";
export type RequestInfo = {
- repo: string,
url_path: string,
parsed_url: URL,
url_path_parts: string[],
is_discovery: boolean,
- service: string | null,
+ service: string,
content_type: string
}
@@ -20,21 +18,21 @@ export interface Request extends FastifyRequest {
params: Route["Params"],
}
-function getRequestInfo(req: Request): RequestInfo {
- const repo = req.params.repo + ".git";
- const url_path = req.url.replace(req.params.repo, repo);
+function getRequestInfo(req: Request, repository_name: string): RequestInfo {
+ const url_path = req.url.replace(req.params.repo, repository_name);
const parsed_url = new URL(`${req.protocol}://${req.hostname}${url_path}`);
const url_path_parts = parsed_url.pathname.split("/");
const is_discovery = (/\/info\/refs$/u).test(parsed_url.pathname);
- const service = is_discovery ? parsed_url.searchParams.get("service") : url_path_parts[url_path_parts.length - 1];
+ let service = is_discovery ? parsed_url.searchParams.get("service") : url_path_parts[url_path_parts.length - 1];
const content_type = `application/x-${service}-${is_discovery ? "advertisement" : "result"}`;
+ service = service || "";
+
return {
- repo,
url_path,
parsed_url,
is_discovery,
@@ -45,7 +43,7 @@ function getRequestInfo(req: Request): RequestInfo {
}
export function connect(repository: Repository, req: Request, reply: FastifyReply): void {
- const request_info = getRequestInfo(req);
+ const request_info = getRequestInfo(req, repository.name.short);
const valid_request = verifyGitRequest(request_info);
if(valid_request.success === false && valid_request.code) {
@@ -56,12 +54,12 @@ export function connect(repository: Repository, req: Request, reply: FastifyRepl
reply.raw.writeHead(200, { "Content-Type": request_info.content_type });
- const spawn_args = [ "--stateless-rpc", join(repository.base_dir, request_info.repo) ];
+ const spawn_args = [ "--stateless-rpc", join(repository.base_dir, repository.name.full) ];
if(request_info.is_discovery) {
spawn_args.push("--advertise-refs");
}
- const git_pack = spawn(<string>request_info.service, spawn_args);
+ const git_pack = spawn(request_info.service, spawn_args);
if(request_info.is_discovery) {
const s = "# service=" + request_info.service + "\n";
@@ -69,16 +67,22 @@ export function connect(repository: Repository, req: Request, reply: FastifyRepl
reply.raw.write(Buffer.from((Array(4 - n.length + 1).join("0") + n + s) + "0000"));
}
else {
- const request_body: IncomingMessage = req.raw;
-
- request_body.on("data", data => git_pack.stdin.write(data));
- request_body.on("close", () => git_pack.stdin.end());
+ const request_body = req.raw;
+
+ request_body.on("data", data => {
+ git_pack.stdin.write(data);
+ });
+ request_body.on("end", () => {
+ git_pack.stdin.end();
+ });
}
git_pack.on("error", err => console.log(err));
git_pack.stderr.on("data", (stderr: Buffer) => console.log(stderr.toString()));
- git_pack.stdout.on("data", data => reply.raw.write(data));
+ git_pack.stdout.on("data", data => {
+ reply.raw.write(data);
+ });
- git_pack.on("close", () => reply.raw.end());
+ git_pack.stdout.on("end", () => reply.raw.end());
} \ No newline at end of file
diff --git a/packages/server/src/server.ts b/packages/server/src/server.ts
new file mode 100644
index 0000000..15f902c
--- /dev/null
+++ b/packages/server/src/server.ts
@@ -0,0 +1,59 @@
+import { readFileSync, readdirSync } from "fs";
+import { join } from "path";
+import { load } from "js-yaml";
+import { exit } from "process";
+import { Settings } from "./types";
+import buildApp from "./app";
+
+const settings = load(readFileSync(join(__dirname, "/../../../settings.yml"), "utf8")) as Settings;
+const settings_keys = Object.keys(settings);
+
+const mandatory_settings = [ "host", "port", "dev_port", "title", "about", "base_dir", "production" ];
+
+// Make sure that all the required settings are present
+const settings_not_included = mandatory_settings.filter(x => !settings_keys.includes(x));
+if(settings_not_included.length !== 0) {
+ console.log(`Error: settings.yml is missing ${(settings_not_included.length > 1) ? "keys" : "key"}:`);
+ console.log(settings_not_included.join(", "));
+ exit(1);
+}
+
+// Make sure that there's not an excessive amount of settings
+const mandatory_not_included = settings_keys.filter(x => !mandatory_settings.includes(x));
+if(mandatory_not_included.length !== 0) {
+ console.log(`Error: settings.yml includes ${(mandatory_not_included.length > 1) ? "pointless keys" : "a pointless key"}:`);
+ console.log(mandatory_not_included.join(", "));
+ exit(1);
+}
+
+// Make sure that the base directory specified in the settings actually exists
+try {
+ readdirSync(settings.base_dir);
+}
+catch {
+ console.error(`Error: Tried opening the base directory. No such directory: ${settings.base_dir}`);
+ exit(1);
+}
+
+const dist_dir = join(__dirname, "/../../client/dist");
+
+if(settings.production) {
+ try {
+ readdirSync(dist_dir);
+ }
+ catch {
+ console.error("Error: Tried opening the dist directory but it doesn't exist.\nDid you accidentally turn on the production setting?");
+ exit(1);
+ }
+}
+
+const app = buildApp(settings, dist_dir);
+
+app.listen(settings.port, settings.host, (err: Error, addr: string) => {
+ if(err) {
+ console.error(err);
+ exit(1);
+ }
+
+ console.log(`App is running on ${addr}`);
+}); \ No newline at end of file
diff --git a/packages/server/src/fastify_types.ts b/packages/server/src/types/fastify.d.ts
index ebaaac2..ebaaac2 100644
--- a/packages/server/src/fastify_types.ts
+++ b/packages/server/src/types/fastify.d.ts
diff --git a/packages/server/src/types/index.d.ts b/packages/server/src/types/index.d.ts
new file mode 100644
index 0000000..beb5f49
--- /dev/null
+++ b/packages/server/src/types/index.d.ts
@@ -0,0 +1,9 @@
+export type Settings = {
+ host: string,
+ port: number,
+ dev_port: number,
+ title: string,
+ about: string,
+ base_dir: string,
+ production: boolean
+} \ No newline at end of file