From d5ac4a15468f568a27d2d8704559fd4ded7e36f3 Mon Sep 17 00:00:00 2001 From: Johan Nyman Date: Tue, 2 Jul 2024 12:51:06 +0200 Subject: [PATCH 1/7] fix: improve the info message at '/' and refactor --- .../http-server/packages/generic/package.json | 2 +- .../packages/generic/scripts/prebuild.js | 20 +++++++++++++++ apps/http-server/packages/generic/src/lib.ts | 6 +++++ .../packages/generic/src/server.ts | 25 ++++++++----------- .../generic/src/storage/fileStorage.ts | 4 +-- .../packages/generic/src/storage/storage.ts | 13 ++++++++++ 6 files changed, 53 insertions(+), 17 deletions(-) create mode 100644 apps/http-server/packages/generic/scripts/prebuild.js diff --git a/apps/http-server/packages/generic/package.json b/apps/http-server/packages/generic/package.json index 6c737e09..9346c0a5 100644 --- a/apps/http-server/packages/generic/package.json +++ b/apps/http-server/packages/generic/package.json @@ -5,7 +5,7 @@ "main": "dist/index.js", "types": "dist/index.d.ts", "scripts": { - "build": "yarn rimraf dist && yarn build:main", + "build": "yarn rimraf dist && node scripts/prebuild.js && yarn build:main", "build:main": "tsc -p tsconfig.json", "__test": "jest" }, diff --git a/apps/http-server/packages/generic/scripts/prebuild.js b/apps/http-server/packages/generic/scripts/prebuild.js new file mode 100644 index 00000000..78e4aaef --- /dev/null +++ b/apps/http-server/packages/generic/scripts/prebuild.js @@ -0,0 +1,20 @@ +const fs = require('fs').promises + +async function main() { + const packageJson = JSON.parse(await fs.readFile('package.json', 'utf8')) + let libStr = await fs.readFile('src/lib.ts', 'utf8') + + libStr = libStr.replace( + /export const PACKAGE_JSON_VERSION =.*/, + `export const PACKAGE_JSON_VERSION = '${packageJson.version}'` + ) + + await fs.writeFile('src/lib.ts', libStr, 'utf8') +} + +main().catch((e) => { + // eslint-disable-next-line no-console + console.error(e) + // eslint-disable-next-line no-process-exit + process.exit(1) +}) diff --git a/apps/http-server/packages/generic/src/lib.ts b/apps/http-server/packages/generic/src/lib.ts index 80eca911..1ed05861 100644 --- a/apps/http-server/packages/generic/src/lib.ts +++ b/apps/http-server/packages/generic/src/lib.ts @@ -23,3 +23,9 @@ export function valueOrFirst(text: string | string[] | undefined): string | unde return text } } + +/** + * The version of the package.json file + * Note: This variable is updated at build-time by scripts/prebuild.js + */ +export const PACKAGE_JSON_VERSION = '1.50.6' diff --git a/apps/http-server/packages/generic/src/server.ts b/apps/http-server/packages/generic/src/server.ts index 0f0d73bf..c7f5018d 100644 --- a/apps/http-server/packages/generic/src/server.ts +++ b/apps/http-server/packages/generic/src/server.ts @@ -8,9 +8,9 @@ import cors from '@koa/cors' import bodyParser from 'koa-bodyparser' import { HTTPServerConfig, LoggerInstance, stringifyError, first } from '@sofie-package-manager/api' -import { BadResponse, Storage } from './storage/storage' +import { BadResponse, Storage, isBadResponse } from './storage/storage' import { FileStorage } from './storage/fileStorage' -import { CTX, valueOrFirst } from './lib' +import { CTX, PACKAGE_JSON_VERSION, valueOrFirst } from './lib' import { parseFormData } from 'pechkin' const fsReadFile = promisify(fs.readFile) @@ -24,6 +24,8 @@ export class PackageProxyServer { private storage: Storage private logger: LoggerInstance + private startupTime = Date.now() + constructor(logger: LoggerInstance, private config: HTTPServerConfig) { this.logger = logger.category('PackageProxyServer') this.app.on('error', (err) => { @@ -123,17 +125,12 @@ export class PackageProxyServer { // Convenient pages: this.router.get('/', async (ctx) => { - let packageJson = { version: '0.0.0' } - try { - packageJson = JSON.parse( - await fsReadFile('../package.json', { - encoding: 'utf8', - }) - ) - } catch (err) { - // ignore + ctx.body = { + name: 'Package proxy server', + version: PACKAGE_JSON_VERSION, + uptime: Date.now() - this.startupTime, + info: this.storage.getInfo(), } - ctx.body = { name: 'Package proxy server', version: packageJson.version, info: this.storage.getInfo() } }) this.router.get('/uploadForm/:path+', async (ctx) => { // ctx.response.status = result.code @@ -165,10 +162,10 @@ export class PackageProxyServer { } }) } - private async handleStorage(ctx: CTX, storageFcn: () => Promise) { + private async handleStorage(ctx: CTX, storageFcn: () => Promise) { try { const result = await storageFcn() - if (result !== true) { + if (isBadResponse(result)) { ctx.response.status = result.code ctx.body = result.reason } diff --git a/apps/http-server/packages/generic/src/storage/fileStorage.ts b/apps/http-server/packages/generic/src/storage/fileStorage.ts index db8f8996..3416cd2a 100644 --- a/apps/http-server/packages/generic/src/storage/fileStorage.ts +++ b/apps/http-server/packages/generic/src/storage/fileStorage.ts @@ -5,7 +5,7 @@ import mime from 'mime-types' import prettyBytes from 'pretty-bytes' import { asyncPipe, CTX, CTXPost } from '../lib' import { HTTPServerConfig, LoggerInstance } from '@sofie-package-manager/api' -import { BadResponse, Storage } from './storage' +import { BadResponse, PackageInfo, Storage } from './storage' import { Readable } from 'stream' // Note: Explicit types here, due to that for some strange reason, promisify wont pass through the correct typings. @@ -39,7 +39,7 @@ export class FileStorage extends Storage { } getInfo(): string { - return this._basePath + return `basePath: "${this._basePath}", cleanFileAge: ${this.config.httpServer.cleanFileAge}` } async init(): Promise { diff --git a/apps/http-server/packages/generic/src/storage/storage.ts b/apps/http-server/packages/generic/src/storage/storage.ts index bbaee154..609b0302 100644 --- a/apps/http-server/packages/generic/src/storage/storage.ts +++ b/apps/http-server/packages/generic/src/storage/storage.ts @@ -18,3 +18,16 @@ export interface BadResponse { code: number reason: string } +export function isBadResponse(v: unknown): v is BadResponse { + return ( + typeof v === 'object' && + typeof (v as BadResponse).code === 'number' && + typeof (v as BadResponse).reason === 'string' + ) +} + +export type PackageInfo = { + path: string + size: string + modified: string +} From 1acfb0c21d0b4a0cbf76a959cff83ea2a8cdd102 Mon Sep 17 00:00:00 2001 From: Johan Nyman Date: Tue, 2 Jul 2024 12:51:43 +0200 Subject: [PATCH 2/7] fix: http-server: add '/list' endpoint, that displays a HTML page with a list of the packages --- .../packages/generic/src/server.ts | 51 ++++++++++++++++++- .../generic/src/storage/fileStorage.ts | 12 ++--- .../packages/generic/src/storage/storage.ts | 2 +- 3 files changed, 54 insertions(+), 11 deletions(-) diff --git a/apps/http-server/packages/generic/src/server.ts b/apps/http-server/packages/generic/src/server.ts index c7f5018d..653b2113 100644 --- a/apps/http-server/packages/generic/src/server.ts +++ b/apps/http-server/packages/generic/src/server.ts @@ -8,7 +8,7 @@ import cors from '@koa/cors' import bodyParser from 'koa-bodyparser' import { HTTPServerConfig, LoggerInstance, stringifyError, first } from '@sofie-package-manager/api' -import { BadResponse, Storage, isBadResponse } from './storage/storage' +import { BadResponse, PackageInfo, Storage, isBadResponse } from './storage/storage' import { FileStorage } from './storage/fileStorage' import { CTX, PACKAGE_JSON_VERSION, valueOrFirst } from './lib' import { parseFormData } from 'pechkin' @@ -90,6 +90,9 @@ export class PackageProxyServer { this.router.get('/packages', async (ctx) => { await this.handleStorage(ctx, async () => this.storage.listPackages(ctx)) }) + this.router.get('/list', async (ctx) => { + await this.handleStorageHTMLList(ctx, async () => this.storage.listPackages(ctx)) + }) this.router.get('/package/:path+', async (ctx) => { await this.handleStorage(ctx, async () => this.storage.getPackage(ctx.params.path, ctx)) }) @@ -175,4 +178,50 @@ export class PackageProxyServer { ctx.body = 'Internal server error' } } + private async handleStorageHTMLList( + ctx: CTX, + storageFcn: () => Promise<{ packages: PackageInfo[] } | BadResponse> + ) { + try { + const result = await storageFcn() + if (isBadResponse(result)) { + ctx.response.status = result.code + ctx.body = result.reason + } else { + const packages = result.packages + + ctx.set('Content-Type', 'text/html') + ctx.body = ` + + + + + +

Packages

+ +${packages + .map( + (pkg) => + ` + + + + ` + ) + .join('')} +
${pkg.path}${pkg.size}${pkg.modified}
+ +` + } + } catch (err) { + this.logger.error(`Error in handleStorage: ${stringifyError(err)} `) + ctx.response.status = 500 + ctx.body = 'Internal server error' + } + } } diff --git a/apps/http-server/packages/generic/src/storage/fileStorage.ts b/apps/http-server/packages/generic/src/storage/fileStorage.ts index 3416cd2a..83620e68 100644 --- a/apps/http-server/packages/generic/src/storage/fileStorage.ts +++ b/apps/http-server/packages/generic/src/storage/fileStorage.ts @@ -46,12 +46,7 @@ export class FileStorage extends Storage { await fsMkDir(this._basePath, { recursive: true }) } - async listPackages(ctx: CTX): Promise { - type PackageInfo = { - path: string - size: string - modified: string - } + async listPackages(ctx: CTX): Promise<{ packages: PackageInfo[] } | BadResponse> { const packages: PackageInfo[] = [] const getAllFiles = async (basePath: string, dirPath: string) => { @@ -84,9 +79,8 @@ export class FileStorage extends Storage { return 0 }) - ctx.body = { packages: packages } - - return true + ctx.body = { packages } + return { packages } } private async getFileInfo(paramPath: string): Promise< | { diff --git a/apps/http-server/packages/generic/src/storage/storage.ts b/apps/http-server/packages/generic/src/storage/storage.ts index 609b0302..1ae9632e 100644 --- a/apps/http-server/packages/generic/src/storage/storage.ts +++ b/apps/http-server/packages/generic/src/storage/storage.ts @@ -4,7 +4,7 @@ import { CTX, CTXPost } from '../lib' export abstract class Storage { abstract init(): Promise abstract getInfo(): string - abstract listPackages(ctx: CTX): Promise + abstract listPackages(ctx: CTX): Promise<{ packages: PackageInfo[] } | BadResponse> abstract headPackage(path: string, ctx: CTX): Promise abstract getPackage(path: string, ctx: CTX): Promise abstract postPackage( From 88326fd678a71b2936b94f5f8e0e26b88e886f38 Mon Sep 17 00:00:00 2001 From: Johan Nyman Date: Wed, 3 Jul 2024 11:42:17 +0200 Subject: [PATCH 3/7] chore: refactor http-server storage internal API so that the methods explicitly return their data instead of modifying ctx directly --- .../packages/generic/src/server.ts | 29 ++++--- .../generic/src/storage/fileStorage.ts | 81 +++++++++++-------- .../packages/generic/src/storage/storage.ts | 30 +++++-- 3 files changed, 91 insertions(+), 49 deletions(-) diff --git a/apps/http-server/packages/generic/src/server.ts b/apps/http-server/packages/generic/src/server.ts index 653b2113..f81ed7ff 100644 --- a/apps/http-server/packages/generic/src/server.ts +++ b/apps/http-server/packages/generic/src/server.ts @@ -8,7 +8,7 @@ import cors from '@koa/cors' import bodyParser from 'koa-bodyparser' import { HTTPServerConfig, LoggerInstance, stringifyError, first } from '@sofie-package-manager/api' -import { BadResponse, PackageInfo, Storage, isBadResponse } from './storage/storage' +import { BadResponse, PackageInfo, Sidecar, Storage, isBadResponse } from './storage/storage' import { FileStorage } from './storage/fileStorage' import { CTX, PACKAGE_JSON_VERSION, valueOrFirst } from './lib' import { parseFormData } from 'pechkin' @@ -88,16 +88,16 @@ export class PackageProxyServer { }) this.router.get('/packages', async (ctx) => { - await this.handleStorage(ctx, async () => this.storage.listPackages(ctx)) + await this.handleStorage(ctx, async () => this.storage.listPackages()) }) this.router.get('/list', async (ctx) => { - await this.handleStorageHTMLList(ctx, async () => this.storage.listPackages(ctx)) + await this.handleStorageHTMLList(ctx, async () => this.storage.listPackages()) }) this.router.get('/package/:path+', async (ctx) => { - await this.handleStorage(ctx, async () => this.storage.getPackage(ctx.params.path, ctx)) + await this.handleStorage(ctx, async () => this.storage.getPackage(ctx.params.path)) }) this.router.head('/package/:path+', async (ctx) => { - await this.handleStorage(ctx, async () => this.storage.headPackage(ctx.params.path, ctx)) + await this.handleStorage(ctx, async () => this.storage.headPackage(ctx.params.path)) }) this.router.post('/package/:path+', async (ctx) => { this.logger.debug(`POST ${ctx.request.URL}`) @@ -123,7 +123,7 @@ export class PackageProxyServer { }) this.router.delete('/package/:path+', async (ctx) => { this.logger.debug(`DELETE ${ctx.request.URL}`) - await this.handleStorage(ctx, async () => this.storage.deletePackage(ctx.params.path, ctx)) + await this.handleStorage(ctx, async () => this.storage.deletePackage(ctx.params.path)) }) // Convenient pages: @@ -165,12 +165,23 @@ export class PackageProxyServer { } }) } - private async handleStorage(ctx: CTX, storageFcn: () => Promise) { + private async handleStorage(ctx: CTX, storageFcn: () => Promise<{ sidecar: Sidecar; body?: any } | BadResponse>) { try { const result = await storageFcn() if (isBadResponse(result)) { ctx.response.status = result.code ctx.body = result.reason + } else { + ctx.response.status = result.sidecar.statusCode + if (result.sidecar.type !== undefined) ctx.type = result.sidecar.type + if (result.sidecar.length !== undefined) ctx.length = result.sidecar.length + if (result.sidecar.lastModified !== undefined) ctx.lastModified = result.sidecar.lastModified + + for (const [key, value] of Object.entries(result.sidecar.headers)) { + ctx.set(key, value) + } + + if (result.body) ctx.body = result.body } } catch (err) { this.logger.error(`Error in handleStorage: ${stringifyError(err)} `) @@ -180,7 +191,7 @@ export class PackageProxyServer { } private async handleStorageHTMLList( ctx: CTX, - storageFcn: () => Promise<{ packages: PackageInfo[] } | BadResponse> + storageFcn: () => Promise<{ body: { packages: PackageInfo[] } } | BadResponse> ) { try { const result = await storageFcn() @@ -188,7 +199,7 @@ export class PackageProxyServer { ctx.response.status = result.code ctx.body = result.reason } else { - const packages = result.packages + const packages = result.body.packages ctx.set('Content-Type', 'text/html') ctx.body = ` diff --git a/apps/http-server/packages/generic/src/storage/fileStorage.ts b/apps/http-server/packages/generic/src/storage/fileStorage.ts index 83620e68..fe1309be 100644 --- a/apps/http-server/packages/generic/src/storage/fileStorage.ts +++ b/apps/http-server/packages/generic/src/storage/fileStorage.ts @@ -3,9 +3,9 @@ import path from 'path' import { promisify } from 'util' import mime from 'mime-types' import prettyBytes from 'pretty-bytes' -import { asyncPipe, CTX, CTXPost } from '../lib' +import { asyncPipe, CTXPost } from '../lib' import { HTTPServerConfig, LoggerInstance } from '@sofie-package-manager/api' -import { BadResponse, PackageInfo, Storage } from './storage' +import { BadResponse, PackageInfo, Sidecar, Storage } from './storage' import { Readable } from 'stream' // Note: Explicit types here, due to that for some strange reason, promisify wont pass through the correct typings. @@ -46,7 +46,7 @@ export class FileStorage extends Storage { await fsMkDir(this._basePath, { recursive: true }) } - async listPackages(ctx: CTX): Promise<{ packages: PackageInfo[] } | BadResponse> { + async listPackages(): Promise<{ sidecar: Sidecar; body: { packages: PackageInfo[] } } | BadResponse> { const packages: PackageInfo[] = [] const getAllFiles = async (basePath: string, dirPath: string) => { @@ -79,8 +79,12 @@ export class FileStorage extends Storage { return 0 }) - ctx.body = { packages } - return { packages } + const sidecar: Sidecar = { + statusCode: 200, + headers: {}, + } + + return { sidecar, body: { packages } } } private async getFileInfo(paramPath: string): Promise< | { @@ -112,40 +116,42 @@ export class FileStorage extends Storage { lastModified: stat.mtime, } } - async headPackage(paramPath: string, ctx: CTX): Promise { + async headPackage(paramPath: string): Promise<{ sidecar: Sidecar } | BadResponse> { const fileInfo = await this.getFileInfo(paramPath) if (!fileInfo.found) { return { code: 404, reason: 'Package not found' } } - this.setHeaders(fileInfo, ctx) - - ctx.response.status = 204 - - ctx.body = undefined + const sidecar: Sidecar = { + statusCode: 204, + headers: {}, + } + this.updateSideCarWithFileInfo(sidecar, fileInfo) - return true + return { sidecar } } - async getPackage(paramPath: string, ctx: CTX): Promise { + async getPackage(paramPath: string): Promise<{ sidecar: Sidecar; body: any } | BadResponse> { const fileInfo = await this.getFileInfo(paramPath) if (!fileInfo.found) { return { code: 404, reason: 'Package not found' } } - - this.setHeaders(fileInfo, ctx) + const sidecar: Sidecar = { + statusCode: 200, + headers: {}, + } + this.updateSideCarWithFileInfo(sidecar, fileInfo) const readStream = fs.createReadStream(fileInfo.fullPath) - ctx.body = readStream - return true + return { sidecar, body: readStream } } async postPackage( paramPath: string, ctx: CTXPost, fileStreamOrText: string | Readable | undefined - ): Promise { + ): Promise<{ sidecar: Sidecar; body: any } | BadResponse> { const fullPath = path.join(this._basePath, paramPath) await fsMkDir(path.dirname(fullPath), { recursive: true }) @@ -158,25 +164,28 @@ export class FileStorage extends Storage { plainText = fileStreamOrText } + const sidecar: Sidecar = { + statusCode: 200, + headers: {}, + } + if (plainText) { // store plain text into file await fsWriteFile(fullPath, plainText) - ctx.body = { code: 201, message: `${exists ? 'Updated' : 'Inserted'} "${paramPath}"` } - ctx.response.status = 201 - return true + sidecar.statusCode = 201 + return { sidecar, body: { code: 201, message: `${exists ? 'Updated' : 'Inserted'} "${paramPath}"` } } } else if (fileStreamOrText && typeof fileStreamOrText !== 'string') { const fileStream = fileStreamOrText await asyncPipe(fileStream, fs.createWriteStream(fullPath)) - ctx.body = { code: 201, message: `${exists ? 'Updated' : 'Inserted'} "${paramPath}"` } - ctx.response.status = 201 - return true + sidecar.statusCode = 201 + return { sidecar, body: { code: 201, message: `${exists ? 'Updated' : 'Inserted'} "${paramPath}"` } } } else { return { code: 400, reason: 'No files provided' } } } - async deletePackage(paramPath: string, ctx: CTXPost): Promise { + async deletePackage(paramPath: string): Promise<{ sidecar: Sidecar; body: any } | BadResponse> { const fullPath = path.join(this._basePath, paramPath) if (!(await this.exists(fullPath))) { @@ -185,8 +194,12 @@ export class FileStorage extends Storage { await fsUnlink(fullPath) - ctx.body = { message: `Deleted "${paramPath}"` } - return true + const sidecar: Sidecar = { + statusCode: 200, + headers: {}, + } + + return { sidecar, body: { message: `Deleted "${paramPath}"` } } } private async exists(fullPath: string) { @@ -274,16 +287,16 @@ export class FileStorage extends Storage { * @param {CTX} ctx * @memberof FileStorage */ - private setHeaders(info: FileInfo, ctx: CTX) { - ctx.type = info.mimeType - ctx.length = info.length - ctx.lastModified = info.lastModified + private updateSideCarWithFileInfo(sidecar: Sidecar, info: FileInfo): void { + sidecar.type = info.mimeType + sidecar.length = info.length + sidecar.lastModified = info.lastModified // Check the config. 0 or -1 means it's disabled: if (this.config.httpServer.cleanFileAge >= 0) { - ctx.set( - 'Expires', - FileStorage.calculateExpiresTimestamp(info.lastModified, this.config.httpServer.cleanFileAge) + sidecar.headers['Expires'] = FileStorage.calculateExpiresTimestamp( + info.lastModified, + this.config.httpServer.cleanFileAge ) } } diff --git a/apps/http-server/packages/generic/src/storage/storage.ts b/apps/http-server/packages/generic/src/storage/storage.ts index 1ae9632e..9120af27 100644 --- a/apps/http-server/packages/generic/src/storage/storage.ts +++ b/apps/http-server/packages/generic/src/storage/storage.ts @@ -1,18 +1,26 @@ import { Readable } from 'stream' -import { CTX, CTXPost } from '../lib' +import { CTXPost } from '../lib' export abstract class Storage { abstract init(): Promise abstract getInfo(): string - abstract listPackages(ctx: CTX): Promise<{ packages: PackageInfo[] } | BadResponse> - abstract headPackage(path: string, ctx: CTX): Promise - abstract getPackage(path: string, ctx: CTX): Promise + abstract listPackages(): Promise< + | { + sidecar: Sidecar + body: { + packages: PackageInfo[] + } + } + | BadResponse + > + abstract headPackage(path: string): Promise<{ sidecar: Sidecar } | BadResponse> + abstract getPackage(path: string): Promise<{ sidecar: Sidecar; body: any } | BadResponse> abstract postPackage( path: string, ctx: CTXPost, fileStreamOrText: string | Readable | undefined - ): Promise - abstract deletePackage(path: string, ctx: CTXPost): Promise + ): Promise<{ sidecar: Sidecar; body: any } | BadResponse> + abstract deletePackage(path: string): Promise<{ sidecar: Sidecar; body: any } | BadResponse> } export interface BadResponse { code: number @@ -31,3 +39,13 @@ export type PackageInfo = { size: string modified: string } + +export interface Sidecar { + statusCode: number + type?: string + length?: number + lastModified?: Date + headers: { + [key: string]: string + } +} From c266c0591ce405b4682394ede87c6562305bb0b6 Mon Sep 17 00:00:00 2001 From: Johan Nyman Date: Wed, 3 Jul 2024 11:43:55 +0200 Subject: [PATCH 4/7] chore: move PACKAGE_JSON_VERSION to separate file --- apps/http-server/packages/generic/scripts/prebuild.js | 4 ++-- apps/http-server/packages/generic/src/lib.ts | 6 ------ apps/http-server/packages/generic/src/packageVersion.ts | 5 +++++ apps/http-server/packages/generic/src/server.ts | 3 ++- 4 files changed, 9 insertions(+), 9 deletions(-) create mode 100644 apps/http-server/packages/generic/src/packageVersion.ts diff --git a/apps/http-server/packages/generic/scripts/prebuild.js b/apps/http-server/packages/generic/scripts/prebuild.js index 78e4aaef..25acca85 100644 --- a/apps/http-server/packages/generic/scripts/prebuild.js +++ b/apps/http-server/packages/generic/scripts/prebuild.js @@ -2,14 +2,14 @@ const fs = require('fs').promises async function main() { const packageJson = JSON.parse(await fs.readFile('package.json', 'utf8')) - let libStr = await fs.readFile('src/lib.ts', 'utf8') + let libStr = await fs.readFile('src/packageVersion.ts', 'utf8') libStr = libStr.replace( /export const PACKAGE_JSON_VERSION =.*/, `export const PACKAGE_JSON_VERSION = '${packageJson.version}'` ) - await fs.writeFile('src/lib.ts', libStr, 'utf8') + await fs.writeFile('src/packageVersion.ts', libStr, 'utf8') } main().catch((e) => { diff --git a/apps/http-server/packages/generic/src/lib.ts b/apps/http-server/packages/generic/src/lib.ts index 1ed05861..80eca911 100644 --- a/apps/http-server/packages/generic/src/lib.ts +++ b/apps/http-server/packages/generic/src/lib.ts @@ -23,9 +23,3 @@ export function valueOrFirst(text: string | string[] | undefined): string | unde return text } } - -/** - * The version of the package.json file - * Note: This variable is updated at build-time by scripts/prebuild.js - */ -export const PACKAGE_JSON_VERSION = '1.50.6' diff --git a/apps/http-server/packages/generic/src/packageVersion.ts b/apps/http-server/packages/generic/src/packageVersion.ts new file mode 100644 index 00000000..41fd52a0 --- /dev/null +++ b/apps/http-server/packages/generic/src/packageVersion.ts @@ -0,0 +1,5 @@ +/** + * The version of the package.json file + * Note: This variable is updated at build-time by scripts/prebuild.js + */ +export const PACKAGE_JSON_VERSION = '1.50.6' diff --git a/apps/http-server/packages/generic/src/server.ts b/apps/http-server/packages/generic/src/server.ts index f81ed7ff..ce0ccf10 100644 --- a/apps/http-server/packages/generic/src/server.ts +++ b/apps/http-server/packages/generic/src/server.ts @@ -10,8 +10,9 @@ import bodyParser from 'koa-bodyparser' import { HTTPServerConfig, LoggerInstance, stringifyError, first } from '@sofie-package-manager/api' import { BadResponse, PackageInfo, Sidecar, Storage, isBadResponse } from './storage/storage' import { FileStorage } from './storage/fileStorage' -import { CTX, PACKAGE_JSON_VERSION, valueOrFirst } from './lib' +import { CTX, valueOrFirst } from './lib' import { parseFormData } from 'pechkin' +import { PACKAGE_JSON_VERSION } from './packageVersion' const fsReadFile = promisify(fs.readFile) From 79e67b4d1c189a643b6ac8ed8b2c190db2cecf84 Mon Sep 17 00:00:00 2001 From: Johan Nyman Date: Wed, 3 Jul 2024 11:45:44 +0200 Subject: [PATCH 5/7] fix: the path need to be relative to account for when the http-server is proxied --- apps/http-server/packages/generic/src/server.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/http-server/packages/generic/src/server.ts b/apps/http-server/packages/generic/src/server.ts index ce0ccf10..69a83875 100644 --- a/apps/http-server/packages/generic/src/server.ts +++ b/apps/http-server/packages/generic/src/server.ts @@ -220,7 +220,7 @@ ${packages .map( (pkg) => ` - ${pkg.path} + ${pkg.path} ${pkg.size} ${pkg.modified} ` From 92e496e579b9a32770b363cd4efe0086121b84bb Mon Sep 17 00:00:00 2001 From: Johan Nyman Date: Wed, 3 Jul 2024 13:32:10 +0200 Subject: [PATCH 6/7] chore: remove the packageVersion.ts file from git, it is auto-generated at build time --- apps/http-server/packages/generic/.gitignore | 1 + .../http-server/packages/generic/scripts/prebuild.js | 12 ++++++------ .../packages/generic/src/packageVersion.ts | 5 ----- apps/http-server/packages/generic/src/server.ts | 1 + 4 files changed, 8 insertions(+), 11 deletions(-) create mode 100644 apps/http-server/packages/generic/.gitignore delete mode 100644 apps/http-server/packages/generic/src/packageVersion.ts diff --git a/apps/http-server/packages/generic/.gitignore b/apps/http-server/packages/generic/.gitignore new file mode 100644 index 00000000..ec96dd6b --- /dev/null +++ b/apps/http-server/packages/generic/.gitignore @@ -0,0 +1 @@ +src/packageVersion.ts diff --git a/apps/http-server/packages/generic/scripts/prebuild.js b/apps/http-server/packages/generic/scripts/prebuild.js index 25acca85..8616d220 100644 --- a/apps/http-server/packages/generic/scripts/prebuild.js +++ b/apps/http-server/packages/generic/scripts/prebuild.js @@ -2,12 +2,12 @@ const fs = require('fs').promises async function main() { const packageJson = JSON.parse(await fs.readFile('package.json', 'utf8')) - let libStr = await fs.readFile('src/packageVersion.ts', 'utf8') - - libStr = libStr.replace( - /export const PACKAGE_JSON_VERSION =.*/, - `export const PACKAGE_JSON_VERSION = '${packageJson.version}'` - ) + const libStr = `// ****** This file is generated at build-time by scripts/prebuild.js ****** +/** + * The version of the package.json file + */ +export const PACKAGE_JSON_VERSION = '${packageJson.version}' +` await fs.writeFile('src/packageVersion.ts', libStr, 'utf8') } diff --git a/apps/http-server/packages/generic/src/packageVersion.ts b/apps/http-server/packages/generic/src/packageVersion.ts deleted file mode 100644 index 41fd52a0..00000000 --- a/apps/http-server/packages/generic/src/packageVersion.ts +++ /dev/null @@ -1,5 +0,0 @@ -/** - * The version of the package.json file - * Note: This variable is updated at build-time by scripts/prebuild.js - */ -export const PACKAGE_JSON_VERSION = '1.50.6' diff --git a/apps/http-server/packages/generic/src/server.ts b/apps/http-server/packages/generic/src/server.ts index 69a83875..64a2b50a 100644 --- a/apps/http-server/packages/generic/src/server.ts +++ b/apps/http-server/packages/generic/src/server.ts @@ -12,6 +12,7 @@ import { BadResponse, PackageInfo, Sidecar, Storage, isBadResponse } from './sto import { FileStorage } from './storage/fileStorage' import { CTX, valueOrFirst } from './lib' import { parseFormData } from 'pechkin' +// eslint-disable-next-line node/no-unpublished-import import { PACKAGE_JSON_VERSION } from './packageVersion' const fsReadFile = promisify(fs.readFile) From 73259d97c2fa26dd0580a4dca332875f8cbd9759 Mon Sep 17 00:00:00 2001 From: Johan Nyman Date: Wed, 3 Jul 2024 13:36:43 +0200 Subject: [PATCH 7/7] chore: refactor: rename Sidecar -> ResponseMeta --- .../packages/generic/src/server.ts | 20 +++--- .../generic/src/storage/fileStorage.ts | 61 +++++++++---------- .../packages/generic/src/storage/storage.ts | 14 ++--- 3 files changed, 47 insertions(+), 48 deletions(-) diff --git a/apps/http-server/packages/generic/src/server.ts b/apps/http-server/packages/generic/src/server.ts index 64a2b50a..8a94dcaa 100644 --- a/apps/http-server/packages/generic/src/server.ts +++ b/apps/http-server/packages/generic/src/server.ts @@ -8,7 +8,7 @@ import cors from '@koa/cors' import bodyParser from 'koa-bodyparser' import { HTTPServerConfig, LoggerInstance, stringifyError, first } from '@sofie-package-manager/api' -import { BadResponse, PackageInfo, Sidecar, Storage, isBadResponse } from './storage/storage' +import { BadResponse, PackageInfo, ResponseMeta, Storage, isBadResponse } from './storage/storage' import { FileStorage } from './storage/fileStorage' import { CTX, valueOrFirst } from './lib' import { parseFormData } from 'pechkin' @@ -167,20 +167,22 @@ export class PackageProxyServer { } }) } - private async handleStorage(ctx: CTX, storageFcn: () => Promise<{ sidecar: Sidecar; body?: any } | BadResponse>) { + private async handleStorage(ctx: CTX, storageFcn: () => Promise<{ meta: ResponseMeta; body?: any } | BadResponse>) { try { const result = await storageFcn() if (isBadResponse(result)) { ctx.response.status = result.code ctx.body = result.reason } else { - ctx.response.status = result.sidecar.statusCode - if (result.sidecar.type !== undefined) ctx.type = result.sidecar.type - if (result.sidecar.length !== undefined) ctx.length = result.sidecar.length - if (result.sidecar.lastModified !== undefined) ctx.lastModified = result.sidecar.lastModified - - for (const [key, value] of Object.entries(result.sidecar.headers)) { - ctx.set(key, value) + ctx.response.status = result.meta.statusCode + if (result.meta.type !== undefined) ctx.type = result.meta.type + if (result.meta.length !== undefined) ctx.length = result.meta.length + if (result.meta.lastModified !== undefined) ctx.lastModified = result.meta.lastModified + + if (result.meta.headers) { + for (const [key, value] of Object.entries(result.meta.headers)) { + ctx.set(key, value) + } } if (result.body) ctx.body = result.body diff --git a/apps/http-server/packages/generic/src/storage/fileStorage.ts b/apps/http-server/packages/generic/src/storage/fileStorage.ts index fe1309be..d3b46e90 100644 --- a/apps/http-server/packages/generic/src/storage/fileStorage.ts +++ b/apps/http-server/packages/generic/src/storage/fileStorage.ts @@ -5,7 +5,7 @@ import mime from 'mime-types' import prettyBytes from 'pretty-bytes' import { asyncPipe, CTXPost } from '../lib' import { HTTPServerConfig, LoggerInstance } from '@sofie-package-manager/api' -import { BadResponse, PackageInfo, Sidecar, Storage } from './storage' +import { BadResponse, PackageInfo, ResponseMeta, Storage } from './storage' import { Readable } from 'stream' // Note: Explicit types here, due to that for some strange reason, promisify wont pass through the correct typings. @@ -46,7 +46,7 @@ export class FileStorage extends Storage { await fsMkDir(this._basePath, { recursive: true }) } - async listPackages(): Promise<{ sidecar: Sidecar; body: { packages: PackageInfo[] } } | BadResponse> { + async listPackages(): Promise<{ meta: ResponseMeta; body: { packages: PackageInfo[] } } | BadResponse> { const packages: PackageInfo[] = [] const getAllFiles = async (basePath: string, dirPath: string) => { @@ -79,12 +79,11 @@ export class FileStorage extends Storage { return 0 }) - const sidecar: Sidecar = { + const meta: ResponseMeta = { statusCode: 200, - headers: {}, } - return { sidecar, body: { packages } } + return { meta, body: { packages } } } private async getFileInfo(paramPath: string): Promise< | { @@ -116,42 +115,40 @@ export class FileStorage extends Storage { lastModified: stat.mtime, } } - async headPackage(paramPath: string): Promise<{ sidecar: Sidecar } | BadResponse> { + async headPackage(paramPath: string): Promise<{ meta: ResponseMeta } | BadResponse> { const fileInfo = await this.getFileInfo(paramPath) if (!fileInfo.found) { return { code: 404, reason: 'Package not found' } } - const sidecar: Sidecar = { + const meta: ResponseMeta = { statusCode: 204, - headers: {}, } - this.updateSideCarWithFileInfo(sidecar, fileInfo) + this.updateMetaWithFileInfo(meta, fileInfo) - return { sidecar } + return { meta } } - async getPackage(paramPath: string): Promise<{ sidecar: Sidecar; body: any } | BadResponse> { + async getPackage(paramPath: string): Promise<{ meta: ResponseMeta; body: any } | BadResponse> { const fileInfo = await this.getFileInfo(paramPath) if (!fileInfo.found) { return { code: 404, reason: 'Package not found' } } - const sidecar: Sidecar = { + const meta: ResponseMeta = { statusCode: 200, - headers: {}, } - this.updateSideCarWithFileInfo(sidecar, fileInfo) + this.updateMetaWithFileInfo(meta, fileInfo) const readStream = fs.createReadStream(fileInfo.fullPath) - return { sidecar, body: readStream } + return { meta, body: readStream } } async postPackage( paramPath: string, ctx: CTXPost, fileStreamOrText: string | Readable | undefined - ): Promise<{ sidecar: Sidecar; body: any } | BadResponse> { + ): Promise<{ meta: ResponseMeta; body: any } | BadResponse> { const fullPath = path.join(this._basePath, paramPath) await fsMkDir(path.dirname(fullPath), { recursive: true }) @@ -164,28 +161,27 @@ export class FileStorage extends Storage { plainText = fileStreamOrText } - const sidecar: Sidecar = { + const meta: ResponseMeta = { statusCode: 200, - headers: {}, } if (plainText) { // store plain text into file await fsWriteFile(fullPath, plainText) - sidecar.statusCode = 201 - return { sidecar, body: { code: 201, message: `${exists ? 'Updated' : 'Inserted'} "${paramPath}"` } } + meta.statusCode = 201 + return { meta, body: { code: 201, message: `${exists ? 'Updated' : 'Inserted'} "${paramPath}"` } } } else if (fileStreamOrText && typeof fileStreamOrText !== 'string') { const fileStream = fileStreamOrText await asyncPipe(fileStream, fs.createWriteStream(fullPath)) - sidecar.statusCode = 201 - return { sidecar, body: { code: 201, message: `${exists ? 'Updated' : 'Inserted'} "${paramPath}"` } } + meta.statusCode = 201 + return { meta, body: { code: 201, message: `${exists ? 'Updated' : 'Inserted'} "${paramPath}"` } } } else { return { code: 400, reason: 'No files provided' } } } - async deletePackage(paramPath: string): Promise<{ sidecar: Sidecar; body: any } | BadResponse> { + async deletePackage(paramPath: string): Promise<{ meta: ResponseMeta; body: any } | BadResponse> { const fullPath = path.join(this._basePath, paramPath) if (!(await this.exists(fullPath))) { @@ -194,12 +190,11 @@ export class FileStorage extends Storage { await fsUnlink(fullPath) - const sidecar: Sidecar = { + const meta: ResponseMeta = { statusCode: 200, - headers: {}, } - return { sidecar, body: { message: `Deleted "${paramPath}"` } } + return { meta, body: { message: `Deleted "${paramPath}"` } } } private async exists(fullPath: string) { @@ -287,21 +282,23 @@ export class FileStorage extends Storage { * @param {CTX} ctx * @memberof FileStorage */ - private updateSideCarWithFileInfo(sidecar: Sidecar, info: FileInfo): void { - sidecar.type = info.mimeType - sidecar.length = info.length - sidecar.lastModified = info.lastModified + private updateMetaWithFileInfo(meta: ResponseMeta, info: FileInfo): void { + meta.type = info.mimeType + meta.length = info.length + meta.lastModified = info.lastModified + + if (!meta.headers) meta.headers = {} // Check the config. 0 or -1 means it's disabled: if (this.config.httpServer.cleanFileAge >= 0) { - sidecar.headers['Expires'] = FileStorage.calculateExpiresTimestamp( + meta.headers['Expires'] = FileStorage.calculateExpiresTimestamp( info.lastModified, this.config.httpServer.cleanFileAge ) } } /** - * Calculate the expiration timestamp, given a starting Date point and timespan duration + * Calculate the expiration timestamp, given a starting Date point and time-span duration * * @private * @static diff --git a/apps/http-server/packages/generic/src/storage/storage.ts b/apps/http-server/packages/generic/src/storage/storage.ts index 9120af27..b63e8626 100644 --- a/apps/http-server/packages/generic/src/storage/storage.ts +++ b/apps/http-server/packages/generic/src/storage/storage.ts @@ -6,21 +6,21 @@ export abstract class Storage { abstract getInfo(): string abstract listPackages(): Promise< | { - sidecar: Sidecar + meta: ResponseMeta body: { packages: PackageInfo[] } } | BadResponse > - abstract headPackage(path: string): Promise<{ sidecar: Sidecar } | BadResponse> - abstract getPackage(path: string): Promise<{ sidecar: Sidecar; body: any } | BadResponse> + abstract headPackage(path: string): Promise<{ meta: ResponseMeta } | BadResponse> + abstract getPackage(path: string): Promise<{ meta: ResponseMeta; body: any } | BadResponse> abstract postPackage( path: string, ctx: CTXPost, fileStreamOrText: string | Readable | undefined - ): Promise<{ sidecar: Sidecar; body: any } | BadResponse> - abstract deletePackage(path: string): Promise<{ sidecar: Sidecar; body: any } | BadResponse> + ): Promise<{ meta: ResponseMeta; body: any } | BadResponse> + abstract deletePackage(path: string): Promise<{ meta: ResponseMeta; body: any } | BadResponse> } export interface BadResponse { code: number @@ -40,12 +40,12 @@ export type PackageInfo = { modified: string } -export interface Sidecar { +export interface ResponseMeta { statusCode: number type?: string length?: number lastModified?: Date - headers: { + headers?: { [key: string]: string } }