diff options
author | HampusM <hampus@hampusmat.com> | 2021-07-23 14:02:57 +0200 |
---|---|---|
committer | HampusM <hampus@hampusmat.com> | 2021-07-23 14:02:57 +0200 |
commit | 22e44b4ef5f74f7058c01ade82ce14aaf9214f18 (patch) | |
tree | 2f24834b6d45af6f0c94357eafef5523b797d0cd /packages/server/src | |
parent | 998d566194cf901a2c927851f9b87c8b577d21a9 (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.ts | 2 | ||||
-rw-r--r-- | packages/server/src/api/v1/repo/branches.ts | 2 | ||||
-rw-r--r-- | packages/server/src/api/v1/repo/index.ts | 2 | ||||
-rw-r--r-- | packages/server/src/api/v1/repo/log.ts | 2 | ||||
-rw-r--r-- | packages/server/src/app.ts | 250 | ||||
-rw-r--r-- | packages/server/src/git/http.ts | 40 | ||||
-rw-r--r-- | packages/server/src/server.ts | 59 | ||||
-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.ts | 9 |
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 |