@isoftdata/file-service
Advanced tools
@@ -1,2 +0,2 @@ | ||
| import { TransformFileOptions } from '../utilities/transform-image'; | ||
| import { TransformFileOptions } from '../utilities/transform-image.js'; | ||
| /** | ||
@@ -3,0 +3,0 @@ * This class represent a file object. |
@@ -1,13 +0,7 @@ | ||
| "use strict"; | ||
| var __importDefault = (this && this.__importDefault) || function (mod) { | ||
| return (mod && mod.__esModule) ? mod : { "default": mod }; | ||
| }; | ||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||
| exports.FileEntity = void 0; | ||
| const md5_1 = __importDefault(require("md5")); | ||
| const transform_image_1 = require("../utilities/transform-image"); | ||
| import getMd5 from 'md5'; | ||
| import { transformImage } from '../utilities/transform-image.js'; | ||
| /** | ||
| * This class represent a file object. | ||
| */ | ||
| class FileEntity { | ||
| export class FileEntity { | ||
| constructor(opts) { | ||
@@ -18,3 +12,3 @@ /** | ||
| this.transform = async (options) => new FileEntity({ | ||
| data: await (0, transform_image_1.transformImage)(this.data, options), | ||
| data: await transformImage(this.data, options), | ||
| encoding: this.encoding, | ||
@@ -28,8 +22,7 @@ mimetype: this.mimetype, | ||
| this.externalId = externalId; | ||
| this.md5 = md5 !== null && md5 !== void 0 ? md5 : (0, md5_1.default)(data); | ||
| this.md5 = md5 ?? getMd5(data); | ||
| this.mimetype = mimetype; | ||
| this.name = name; | ||
| this.size = size !== null && size !== void 0 ? size : data.byteLength; | ||
| this.size = size ?? data.byteLength; | ||
| } | ||
| } | ||
| exports.FileEntity = FileEntity; |
| import { Connection, PoolConnection } from 'mysql'; | ||
| import { FileEntity } from './FileEntity'; | ||
| import { IsoftFileType } from './types'; | ||
| import { FileEntity } from './FileEntity.js'; | ||
| import { IsoftFileType } from './types.js'; | ||
| /** | ||
@@ -5,0 +5,0 @@ * This class is a repository for file objects. This should be what is used to save and fetch `FileEntity` objects. |
+18
-26
@@ -1,20 +0,14 @@ | ||
| "use strict"; | ||
| var __importDefault = (this && this.__importDefault) || function (mod) { | ||
| return (mod && mod.__esModule) ? mod : { "default": mod }; | ||
| }; | ||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||
| exports.FileRepo = void 0; | ||
| const mysql_1 = require("mysql"); | ||
| const query_handler_1 = require("../utilities/query-handler"); | ||
| const stream_chunker_1 = __importDefault(require("stream-chunker")); | ||
| const stream_1 = require("stream"); | ||
| const promises_1 = require("stream/promises"); | ||
| import { raw } from 'mysql'; | ||
| import { handleQuery } from '../utilities/query-handler.js'; | ||
| import Chunker from 'stream-chunker'; | ||
| import { Readable, Writable } from 'stream'; | ||
| import { pipeline } from 'stream/promises'; | ||
| /** | ||
| * This class is a repository for file objects. This should be what is used to save and fetch `FileEntity` objects. | ||
| */ | ||
| class FileRepo { | ||
| export class FileRepo { | ||
| constructor(db) { | ||
| this.db = db; | ||
| this.createFileContainer = async (file, type) => { | ||
| const result = await (0, query_handler_1.handleQuery)({ | ||
| const result = await handleQuery({ | ||
| connection: this.db, | ||
@@ -25,5 +19,5 @@ sql: 'INSERT INTO `file` SET ?', | ||
| name: file.name, | ||
| created: (0, mysql_1.raw)('NOW()'), | ||
| updated: (0, mysql_1.raw)('NOW()'), | ||
| hash: (0, mysql_1.raw)(`UNHEX('${file.md5.toUpperCase()}')`), | ||
| created: raw('NOW()'), | ||
| updated: raw('NOW()'), | ||
| hash: raw(`UNHEX('${file.md5.toUpperCase()}')`), | ||
| type, | ||
@@ -36,3 +30,3 @@ }, | ||
| this.fetchFileChunkSize = async () => { | ||
| const rows = await (0, query_handler_1.handleQuery)({ | ||
| const rows = await handleQuery({ | ||
| connection: this.db, | ||
@@ -45,4 +39,3 @@ sql: "show variables like 'max_allowed_packet'", | ||
| this.fetchIdByDigest = async (hash) => { | ||
| var _a; | ||
| const results = await (0, query_handler_1.handleQuery)({ | ||
| const results = await handleQuery({ | ||
| connection: this.db, | ||
@@ -56,3 +49,3 @@ sql: 'SELECT `fileid` FROM `file` WHERE `hash` = UNHEX(?)', | ||
| const firstResult = results[0]; | ||
| return (_a = firstResult === null || firstResult === void 0 ? void 0 : firstResult.fileid) !== null && _a !== void 0 ? _a : null; | ||
| return firstResult?.fileid ?? null; | ||
| }; | ||
@@ -83,6 +76,6 @@ this.insert = async (file) => { | ||
| // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call | ||
| const chunkStream = new stream_chunker_1.default(chunkSize, { flush: true }); | ||
| const chunkStream = new Chunker(chunkSize, { flush: true }); | ||
| // db insertion function for use with writeStream | ||
| const inserter = async (chunk) => { | ||
| await (0, query_handler_1.handleQuery)({ | ||
| await handleQuery({ | ||
| connection: this.db, | ||
@@ -101,3 +94,3 @@ sql: 'INSERT INTO `filechunk` SET ?', | ||
| // A write stream that inserts into the filechunk table | ||
| const writeStream = new stream_1.Writable({ | ||
| const writeStream = new Writable({ | ||
| async write(chunk, encoding, next) { | ||
@@ -113,3 +106,3 @@ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument | ||
| // We only have a buffer at this point. We need a stream to use with pipeline | ||
| const sourceStream = stream_1.Readable.from(file.data); | ||
| const sourceStream = Readable.from(file.data); | ||
| // Stream the data into the database | ||
@@ -119,3 +112,3 @@ // eslint-disable-next-line @typescript-eslint/no-misused-promises, no-async-promise-executor | ||
| try { | ||
| await (0, promises_1.pipeline)(sourceStream, chunkStream, writeStream); | ||
| await pipeline(sourceStream, chunkStream, writeStream); | ||
| resolve(); | ||
@@ -131,3 +124,2 @@ } | ||
| } | ||
| exports.FileRepo = FileRepo; | ||
| FileRepo.getIsoftFileType = (mimetype) => { | ||
@@ -134,0 +126,0 @@ if (mimetype.startsWith('image/')) { |
@@ -1,2 +0,1 @@ | ||
| "use strict"; | ||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||
| export {}; |
@@ -1,4 +0,1 @@ | ||
| "use strict"; | ||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||
| exports.VerifierSelector = void 0; | ||
| /** | ||
@@ -16,3 +13,3 @@ * A verifier-selector string is a concatenation of two things: | ||
| */ | ||
| exports.VerifierSelector = { | ||
| export const VerifierSelector = { | ||
| /** | ||
@@ -33,2 +30,2 @@ * This method will parse a verifier-selector string into it's constituent parts. | ||
| }; | ||
| exports.default = exports.VerifierSelector; | ||
| export default VerifierSelector; |
@@ -1,2 +0,2 @@ | ||
| import { UploadResult } from '../app/types'; | ||
| import { UploadResult } from '../app/types.js'; | ||
| import { Readable } from 'stream'; | ||
@@ -3,0 +3,0 @@ export type ServiceOpts = { |
@@ -1,23 +0,19 @@ | ||
| "use strict"; | ||
| var __importDefault = (this && this.__importDefault) || function (mod) { | ||
| return (mod && mod.__esModule) ? mod : { "default": mod }; | ||
| }; | ||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||
| exports.FileServiceClient = void 0; | ||
| const form_data_1 = __importDefault(require("form-data")); | ||
| const axios_1 = __importDefault(require("axios")); | ||
| const stream_1 = require("stream"); | ||
| const crypto_1 = require("crypto"); | ||
| class FileServiceClient { | ||
| import FormData from 'form-data'; | ||
| import axios from 'axios'; | ||
| import { Readable } from 'stream'; | ||
| import { randomUUID } from 'crypto'; | ||
| export class FileServiceClient { | ||
| constructor(opts) { | ||
| this.addFile = (opts) => { | ||
| var _a; | ||
| if (!opts.stream || !(opts.stream instanceof stream_1.Readable)) | ||
| if (!opts.stream || !(opts.stream instanceof Readable)) | ||
| throw new Error('File uploads require a `stream` property of type `Readable`!'); | ||
| const uuid = (_a = opts.externalId) !== null && _a !== void 0 ? _a : (0, crypto_1.randomUUID)(); | ||
| this.filesToBeUploaded.push(Object.assign(Object.assign({}, opts), { externalId: uuid })); | ||
| const uuid = opts.externalId ?? randomUUID(); | ||
| this.filesToBeUploaded.push({ | ||
| ...opts, | ||
| externalId: uuid, | ||
| }); | ||
| return uuid; | ||
| }; | ||
| this.upload = async () => { | ||
| const data = new form_data_1.default(); | ||
| const data = new FormData(); | ||
| if (this.maxImageHeight) | ||
@@ -28,4 +24,3 @@ data.append('maxHeight', this.maxImageHeight); | ||
| this.filesToBeUploaded.forEach(file => { | ||
| var _a; | ||
| data.append((_a = file.externalId) !== null && _a !== void 0 ? _a : '', file.stream, file.name ? { filename: file.name } : {}); | ||
| data.append(file.externalId ?? '', file.stream, file.name ? { filename: file.name } : {}); | ||
| }); | ||
@@ -38,3 +33,3 @@ const config = { | ||
| }; | ||
| return (await axios_1.default.request(config)).data; | ||
| return (await axios.request(config)).data; | ||
| }; | ||
@@ -47,2 +42,1 @@ this.filesToBeUploaded = []; | ||
| } | ||
| exports.FileServiceClient = FileServiceClient; |
@@ -1,14 +0,7 @@ | ||
| "use strict"; | ||
| var __importDefault = (this && this.__importDefault) || function (mod) { | ||
| return (mod && mod.__esModule) ? mod : { "default": mod }; | ||
| }; | ||
| var _a; | ||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||
| exports.pool = void 0; | ||
| const mysql_1 = require("mysql"); | ||
| const fs_1 = __importDefault(require("fs")); | ||
| import { createPool } from 'mysql'; | ||
| import fs from 'fs'; | ||
| // Check if a password file exists. (Docker Secret) | ||
| let mysqlPasswordFromFile = ''; | ||
| try { | ||
| mysqlPasswordFromFile = fs_1.default.readFileSync('/run/secrets/mysql_password', 'utf8').toString().trim(); | ||
| mysqlPasswordFromFile = fs.readFileSync('/run/secrets/mysql_password', 'utf8').toString().trim(); | ||
| } | ||
@@ -20,3 +13,3 @@ catch (err) { | ||
| const user = process.env.MYSQL_USER; | ||
| const password = (_a = process.env.MYSQL_PASSWORD) !== null && _a !== void 0 ? _a : mysqlPasswordFromFile; | ||
| const password = process.env.MYSQL_PASSWORD ?? mysqlPasswordFromFile; | ||
| const database = process.env.MYSQL_DATABASE; | ||
@@ -27,3 +20,3 @@ if (!host || !user || !password || !database) { | ||
| // MySql | ||
| exports.pool = (0, mysql_1.createPool)({ host, user, password, database, multipleStatements: true }); | ||
| exports.default = exports.pool; | ||
| export const pool = createPool({ host, user, password, database, multipleStatements: true }); | ||
| export default pool; |
@@ -1,6 +0,6 @@ | ||
| export { registerFileServiceRoute } from './main'; | ||
| export { FailedUpload, SuccessfulUpload, UploadResult } from '../app/types'; | ||
| export { FileServiceClient, ServiceOpts, FileOpts } from '../client/FileServiceClient'; | ||
| export { FileEntity } from '../app/FileEntity'; | ||
| export { FileRepo } from '../app/FileRepo'; | ||
| export { saveFileChunk, SaveFileOptions } from './util'; | ||
| export { registerFileServiceRoute } from './main.js'; | ||
| export { FailedUpload, SuccessfulUpload, UploadResult } from '../app/types.js'; | ||
| export { FileServiceClient, ServiceOpts, FileOpts } from '../client/FileServiceClient.js'; | ||
| export { FileEntity } from '../app/FileEntity.js'; | ||
| export { FileRepo } from '../app/FileRepo.js'; | ||
| export { saveFileChunk, SaveFileOptions } from './util.js'; |
+5
-13
@@ -1,13 +0,5 @@ | ||
| "use strict"; | ||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||
| exports.saveFileChunk = exports.FileRepo = exports.FileEntity = exports.FileServiceClient = exports.registerFileServiceRoute = void 0; | ||
| var main_1 = require("./main"); | ||
| Object.defineProperty(exports, "registerFileServiceRoute", { enumerable: true, get: function () { return main_1.registerFileServiceRoute; } }); | ||
| var FileServiceClient_1 = require("../client/FileServiceClient"); | ||
| Object.defineProperty(exports, "FileServiceClient", { enumerable: true, get: function () { return FileServiceClient_1.FileServiceClient; } }); | ||
| var FileEntity_1 = require("../app/FileEntity"); | ||
| Object.defineProperty(exports, "FileEntity", { enumerable: true, get: function () { return FileEntity_1.FileEntity; } }); | ||
| var FileRepo_1 = require("../app/FileRepo"); | ||
| Object.defineProperty(exports, "FileRepo", { enumerable: true, get: function () { return FileRepo_1.FileRepo; } }); | ||
| var util_1 = require("./util"); | ||
| Object.defineProperty(exports, "saveFileChunk", { enumerable: true, get: function () { return util_1.saveFileChunk; } }); | ||
| export { registerFileServiceRoute } from './main.js'; | ||
| export { FileServiceClient } from '../client/FileServiceClient.js'; | ||
| export { FileEntity } from '../app/FileEntity.js'; | ||
| export { FileRepo } from '../app/FileRepo.js'; | ||
| export { saveFileChunk } from './util.js'; |
+25
-31
@@ -1,28 +0,22 @@ | ||
| "use strict"; | ||
| var __importDefault = (this && this.__importDefault) || function (mod) { | ||
| return (mod && mod.__esModule) ? mod : { "default": mod }; | ||
| }; | ||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||
| exports.registerFileServiceRoute = void 0; | ||
| /* eslint-disable @typescript-eslint/no-misused-promises */ | ||
| const transform_image_1 = require("../utilities/transform-image"); | ||
| const debug_1 = __importDefault(require("debug")); | ||
| const utility_db_1 = require("@isoftdata/utility-db"); | ||
| const VerifierSelector_1 = __importDefault(require("../app/VerifierSelector")); | ||
| const mime_types_1 = require("mime-types"); | ||
| const FileRepo_1 = require("../app/FileRepo"); | ||
| const util_1 = require("./util"); | ||
| const debug = (0, debug_1.default)('debug'); | ||
| const logError = (0, debug_1.default)('error'); | ||
| import { transformImage } from '../utilities/transform-image.js'; | ||
| import debugModule from 'debug'; | ||
| import { readFirst, getConnectionFromPool, isPool } from '@isoftdata/utility-db'; | ||
| import VerifierSelector from '../app/VerifierSelector.js'; | ||
| import { contentType } from 'mime-types'; | ||
| import { FileRepo } from '../app/FileRepo.js'; | ||
| import { buildFileEntityArray, buildResultArray } from './util.js'; | ||
| const debug = debugModule('debug'); | ||
| const logError = debugModule('error'); | ||
| // eslint-disable-next-line @typescript-eslint/require-await | ||
| const registerFileServiceRoute = async ({ webServer, connection: givenConnection, routePrefix = '/**', sharpOptions }) => { | ||
| export const registerFileServiceRoute = async ({ webServer, connection: givenConnection, routePrefix = '/**', sharpOptions }) => { | ||
| // eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
| webServer.post('/upload', async (req, res) => { | ||
| var _a; | ||
| debug('Starting response...'); | ||
| // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment | ||
| const { maxHeight, maxWidth } = (_a = req === null || req === void 0 ? void 0 : req.body) !== null && _a !== void 0 ? _a : {}; | ||
| const { maxHeight, maxWidth } = req?.body ?? {}; | ||
| let releaseConnectionWhenComplete = true; | ||
| let connection; | ||
| if ((0, utility_db_1.isPool)(givenConnection)) { | ||
| connection = await (0, utility_db_1.getConnectionFromPool)(givenConnection); | ||
| if (isPool(givenConnection)) { | ||
| connection = await getConnectionFromPool(givenConnection); | ||
| } | ||
@@ -37,7 +31,7 @@ else { | ||
| // eslint-disable-next-line @typescript-eslint/no-unsafe-argument | ||
| const fileRepo = new FileRepo_1.FileRepo(connection); | ||
| const fileRepo = new FileRepo(connection); | ||
| try { | ||
| const fileEntities = (0, util_1.buildFileEntityArray)(req.files); | ||
| const fileEntities = buildFileEntityArray(req.files); | ||
| // eslint-disable-next-line @typescript-eslint/no-unsafe-argument | ||
| const resultArray = await (0, util_1.buildResultArray)(fileEntities, maxHeight, maxWidth, fileRepo, sharpOptions); | ||
| const resultArray = await buildResultArray(fileEntities, maxHeight, maxWidth, fileRepo, sharpOptions); | ||
| return res.send(resultArray); | ||
@@ -57,2 +51,3 @@ } | ||
| // Register the route on the web server | ||
| // eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
| webServer.get(`:paramRoutePrefix(${routePrefix})/:path/:rest(*)`, async (req, res) => { | ||
@@ -63,4 +58,4 @@ debug('Starting response...'); | ||
| let connection; | ||
| if ((0, utility_db_1.isPool)(givenConnection)) { | ||
| connection = await (0, utility_db_1.getConnectionFromPool)(givenConnection); | ||
| if (isPool(givenConnection)) { | ||
| connection = await getConnectionFromPool(givenConnection); | ||
| } | ||
@@ -74,3 +69,3 @@ else { | ||
| try { | ||
| const verifierSelectorResponse = VerifierSelector_1.default.parse(path); | ||
| const verifierSelectorResponse = VerifierSelector.parse(path); | ||
| if (!verifierSelectorResponse) { | ||
@@ -83,3 +78,3 @@ debug('Invalid path (%o)', path); | ||
| // eslint-disable-next-line @typescript-eslint/no-unsafe-argument | ||
| const metadata = await (0, utility_db_1.readFirst)(connection, { | ||
| const metadata = await readFirst(connection, { | ||
| sql: 'SELECT `name`, `type`, LOWER(HEX(`hash`)) AS `md5sum` FROM `file` WHERE `fileid` = ?', | ||
@@ -104,3 +99,3 @@ values: [fileId], | ||
| // eslint-disable-next-line @typescript-eslint/no-unsafe-argument | ||
| const fileData = await (0, utility_db_1.readFirst)(connection, { | ||
| const fileData = await readFirst(connection, { | ||
| sql: 'SELECT f_get_attachment_data(?) AS data', | ||
@@ -115,6 +110,6 @@ values: [fileId], | ||
| if ((req.query.width || req.query.height) && metadata.type === 'Image') { | ||
| file = await (0, transform_image_1.transformImage)(file, Object.assign(Object.assign({}, req.query), { sharpOptions })); | ||
| file = await transformImage(file, { ...req.query, sharpOptions }); | ||
| } | ||
| // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call | ||
| const fileContentType = (0, mime_types_1.contentType)(name); | ||
| const fileContentType = contentType(name); | ||
| if (fileContentType) { | ||
@@ -140,2 +135,1 @@ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument | ||
| }; | ||
| exports.registerFileServiceRoute = registerFileServiceRoute; |
+8
-14
@@ -1,17 +0,11 @@ | ||
| "use strict"; | ||
| var __importDefault = (this && this.__importDefault) || function (mod) { | ||
| return (mod && mod.__esModule) ? mod : { "default": mod }; | ||
| }; | ||
| var _a; | ||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||
| const express_1 = __importDefault(require("express")); | ||
| const connection_pool_1 = __importDefault(require("../connection-pool")); | ||
| const main_1 = require("./main"); | ||
| const express_fileupload_1 = __importDefault(require("express-fileupload")); | ||
| const port = (_a = process.env.PORT) !== null && _a !== void 0 ? _a : 4000; | ||
| import express from 'express'; | ||
| import pool from '../connection-pool.js'; | ||
| import { registerFileServiceRoute } from './main.js'; | ||
| import fileUpload from 'express-fileupload'; | ||
| const port = process.env.PORT ?? 4000; | ||
| // Express server | ||
| const app = (0, express_1.default)(); | ||
| app.use((0, express_fileupload_1.default)()); | ||
| const app = express(); | ||
| app.use(fileUpload()); | ||
| //Add the file service to the router | ||
| void (0, main_1.registerFileServiceRoute)({ webServer: app, connection: connection_pool_1.default }); | ||
| void registerFileServiceRoute({ webServer: app, connection: pool }); | ||
| app.listen({ port }, () => console.log(`🚀 Server ready on port ${port}!`)); |
| import fileUpload from 'express-fileupload'; | ||
| import sharp from 'sharp'; | ||
| import { FileEntity } from '../app/FileEntity'; | ||
| import { FileRepo } from '../app/FileRepo'; | ||
| import { UploadResult } from '../app/types'; | ||
| import { FileEntity } from '../app/FileEntity.js'; | ||
| import { FileRepo } from '../app/FileRepo.js'; | ||
| import { UploadResult } from '../app/types.js'; | ||
| export interface SaveFileOptions { | ||
@@ -7,0 +7,0 @@ fileEntity: FileEntity; |
+52
-43
@@ -1,12 +0,6 @@ | ||
| "use strict"; | ||
| var __importDefault = (this && this.__importDefault) || function (mod) { | ||
| return (mod && mod.__esModule) ? mod : { "default": mod }; | ||
| }; | ||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||
| exports.saveFileChunk = exports.renameFile = exports.resizeImage = exports.buildResultArray = exports.buildFileEntity = exports.modifyResizeImage = exports.buildFileEntityArray = void 0; | ||
| const sharp_1 = __importDefault(require("sharp")); | ||
| const FileEntity_1 = require("../app/FileEntity"); | ||
| const transform_image_1 = require("../utilities/transform-image"); | ||
| const path_1 = require("path"); | ||
| const buildFileEntityArray = (filesObj) => { | ||
| import sharp from 'sharp'; | ||
| import { FileEntity } from '../app/FileEntity.js'; | ||
| import { transformImage } from '../utilities/transform-image.js'; | ||
| import { extname } from 'path'; | ||
| export const buildFileEntityArray = (filesObj) => { | ||
| //Currently just specific for express-fileupload, needs to be able to just take a generaic file buffer | ||
@@ -18,15 +12,19 @@ if (!filesObj) { | ||
| if (Array.isArray(file)) { | ||
| const subArr = file.map(f => new FileEntity_1.FileEntity(Object.assign(Object.assign({}, f), { externalId: id }))); | ||
| const subArr = file.map(f => new FileEntity({ | ||
| ...f, | ||
| externalId: id, | ||
| })); | ||
| return subArr; | ||
| } | ||
| return new FileEntity_1.FileEntity(Object.assign(Object.assign({}, file), { externalId: id })); | ||
| return new FileEntity({ | ||
| ...file, | ||
| externalId: id, | ||
| }); | ||
| }); | ||
| return fileEntities; | ||
| }; | ||
| exports.buildFileEntityArray = buildFileEntityArray; | ||
| const modifyResizeImage = async (e, maxHeight, maxWidth, fileEntity, modified, sharpOptions) => { | ||
| var _a, _b; | ||
| const imageMetadata = await (0, sharp_1.default)(e.data).metadata(); | ||
| if ((maxHeight && ((_a = imageMetadata.height) !== null && _a !== void 0 ? _a : 0)) > (maxHeight || maxWidth) && ((_b = imageMetadata.width) !== null && _b !== void 0 ? _b : 0) > maxWidth) { | ||
| fileEntity = await (0, exports.resizeImage)({ fileEntity, maxHeight, maxWidth, sharpOptions }); | ||
| export const modifyResizeImage = async (e, maxHeight, maxWidth, fileEntity, modified, sharpOptions) => { | ||
| const imageMetadata = await sharp(e.data).metadata(); | ||
| if ((maxHeight && (imageMetadata.height ?? 0)) > (maxHeight || maxWidth) && (imageMetadata.width ?? 0) > maxWidth) { | ||
| fileEntity = await resizeImage({ fileEntity, maxHeight, maxWidth, sharpOptions }); | ||
| modified = true; | ||
@@ -36,9 +34,13 @@ } | ||
| }; | ||
| exports.modifyResizeImage = modifyResizeImage; | ||
| const buildFileEntity = (e, buffer) => { | ||
| const fileEntity = new FileEntity_1.FileEntity(Object.assign(Object.assign({ data: buffer, encoding: e.encoding }, (e.externalId && { externalId: e.externalId })), { mimetype: e.mimetype, name: e.name })); | ||
| export const buildFileEntity = (e, buffer) => { | ||
| const fileEntity = new FileEntity({ | ||
| data: buffer, | ||
| encoding: e.encoding, | ||
| ...(e.externalId && { externalId: e.externalId }), | ||
| mimetype: e.mimetype, | ||
| name: e.name, | ||
| }); | ||
| return fileEntity; | ||
| }; | ||
| exports.buildFileEntity = buildFileEntity; | ||
| const buildResultArray = async (fileEntities, maxHeight, maxWidth, fileRepo, sharpOptions) => { | ||
| export const buildResultArray = async (fileEntities, maxHeight, maxWidth, fileRepo, sharpOptions) => { | ||
| const resultArray = []; | ||
@@ -51,3 +53,3 @@ for (const e of fileEntities) { | ||
| ; | ||
| ({ fileEntity, modified } = await (0, exports.modifyResizeImage)(e, maxHeight, maxWidth, fileEntity, modified, sharpOptions)); | ||
| ({ fileEntity, modified } = await modifyResizeImage(e, maxHeight, maxWidth, fileEntity, modified, sharpOptions)); | ||
| } | ||
@@ -59,7 +61,19 @@ if (!fileEntity) { | ||
| const id = await fileRepo.insert(fileEntity); | ||
| resultArray.push(Object.assign(Object.assign({ id }, (e.externalId && { externalId: e.externalId })), { modified, name: fileEntity.name, size: fileEntity.size, success: true })); | ||
| resultArray.push({ | ||
| id, | ||
| ...(e.externalId && { externalId: e.externalId }), | ||
| modified, | ||
| name: fileEntity.name, | ||
| size: fileEntity.size, | ||
| success: true, | ||
| }); | ||
| } | ||
| catch (error) { | ||
| console.error(error); | ||
| resultArray.push(Object.assign(Object.assign({ error }, (e.externalId && { externalId: e.externalId })), { name: e.name, success: false })); | ||
| resultArray.push({ | ||
| error, | ||
| ...(e.externalId && { externalId: e.externalId }), | ||
| name: e.name, | ||
| success: false, | ||
| }); | ||
| } | ||
@@ -69,8 +83,6 @@ } | ||
| }; | ||
| exports.buildResultArray = buildResultArray; | ||
| const resizeImage = async (input) => { | ||
| var _a, _b; | ||
| const newBuffer = await (0, transform_image_1.transformImage)(input.fileEntity.data, { | ||
| height: (_a = input.maxHeight) === null || _a === void 0 ? void 0 : _a.toString(), | ||
| width: (_b = input.maxWidth) === null || _b === void 0 ? void 0 : _b.toString(), | ||
| export const resizeImage = async (input) => { | ||
| const newBuffer = await transformImage(input.fileEntity.data, { | ||
| height: input.maxHeight?.toString(), | ||
| width: input.maxWidth?.toString(), | ||
| fit: input.fit ? input.fit : 'inside', | ||
@@ -80,23 +92,20 @@ withoutEnlargement: true, | ||
| }); | ||
| input.fileEntity = (0, exports.buildFileEntity)(input.fileEntity, newBuffer); | ||
| input.fileEntity = buildFileEntity(input.fileEntity, newBuffer); | ||
| return input.fileEntity; | ||
| }; | ||
| exports.resizeImage = resizeImage; | ||
| const renameFile = (input) => { | ||
| export const renameFile = (input) => { | ||
| const extension = input.fileSaveExtension; | ||
| return `${input.fileEntity.name.substring(0, input.fileEntity.name.lastIndexOf((0, path_1.extname)(input.fileEntity.name)))}.${extension}`; | ||
| return `${input.fileEntity.name.substring(0, input.fileEntity.name.lastIndexOf(extname(input.fileEntity.name)))}.${extension}`; | ||
| }; | ||
| exports.renameFile = renameFile; | ||
| const saveFileChunk = async (fileRepo, input) => { | ||
| export const saveFileChunk = async (fileRepo, input) => { | ||
| let fileEntityToSave; | ||
| // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing | ||
| if (input.fileEntity.mimetype.startsWith('image') && (input.maxHeight || input.maxWidth)) { | ||
| fileEntityToSave = await (0, exports.resizeImage)(input); | ||
| fileEntityToSave.name = (0, exports.renameFile)(input); | ||
| fileEntityToSave = await resizeImage(input); | ||
| fileEntityToSave.name = renameFile(input); | ||
| } | ||
| else { | ||
| fileEntityToSave = (0, exports.buildFileEntity)(input.fileEntity, input.fileEntity.data); | ||
| fileEntityToSave = buildFileEntity(input.fileEntity, input.fileEntity.data); | ||
| } | ||
| return fileRepo.insert(fileEntityToSave); | ||
| }; | ||
| exports.saveFileChunk = saveFileChunk; |
@@ -1,5 +0,2 @@ | ||
| "use strict"; | ||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||
| exports.handleQuery = void 0; | ||
| const handleQuery = (options) => new Promise((resolve, reject) => { | ||
| export const handleQuery = (options) => new Promise((resolve, reject) => { | ||
| options.connection.query(options.sql, options.values, (err, rows) => { | ||
@@ -15,2 +12,1 @@ if (err) { | ||
| }); | ||
| exports.handleQuery = handleQuery; |
@@ -1,11 +0,5 @@ | ||
| "use strict"; | ||
| var __importDefault = (this && this.__importDefault) || function (mod) { | ||
| return (mod && mod.__esModule) ? mod : { "default": mod }; | ||
| }; | ||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||
| exports.transformImage = void 0; | ||
| const debug_1 = __importDefault(require("debug")); | ||
| const sharp_1 = __importDefault(require("sharp")); | ||
| const debug = (0, debug_1.default)('files:transform'); | ||
| const transformImage = async (file, options) => { | ||
| import debugModule from 'debug'; | ||
| import sharp from 'sharp'; | ||
| const debug = debugModule('files:transform'); | ||
| export const transformImage = async (file, options) => { | ||
| const width = options.width ? (Number.isInteger(parseInt(options.width, 10)) ? parseInt(options.width, 10) : null) : null; | ||
@@ -19,11 +13,11 @@ const height = options.height ? (Number.isInteger(parseInt(options.height, 10)) ? parseInt(options.height, 10) : null) : null; | ||
| case 'contain': | ||
| return sharp_1.default.fit.contain; | ||
| return sharp.fit.contain; | ||
| case 'cover': | ||
| return sharp_1.default.fit.cover; | ||
| return sharp.fit.cover; | ||
| case 'fill': | ||
| return sharp_1.default.fit.fill; | ||
| return sharp.fit.fill; | ||
| case 'inside': | ||
| return sharp_1.default.fit.inside; | ||
| return sharp.fit.inside; | ||
| case 'outside': | ||
| return sharp_1.default.fit.outside; | ||
| return sharp.fit.outside; | ||
| default: | ||
@@ -46,3 +40,3 @@ return undefined; | ||
| debug('Beginning image processing...'); | ||
| const image = (0, sharp_1.default)(file, options.sharpOptions); | ||
| const image = sharp(file, options.sharpOptions); | ||
| // eslint-disable-next-line @typescript-eslint/await-thenable | ||
@@ -65,2 +59,1 @@ const transformedSharpImage = await image.rotate().resize(width, height, { fit, background, withoutEnlargement: options.withoutEnlargement }); | ||
| }; | ||
| exports.transformImage = transformImage; |
+11
-7
| { | ||
| "name": "@isoftdata/file-service", | ||
| "version": "4.0.8", | ||
| "version": "5.0.0", | ||
| "description": "A generic files service for any isoft platform.", | ||
| "main": "dist/entry/index.js", | ||
| "exports": { | ||
| ".": "./dist/entry/index.js" | ||
| }, | ||
| "type": "module", | ||
| "engines": { | ||
@@ -39,3 +42,3 @@ "node": ">=16.0.0" | ||
| "dependencies": { | ||
| "@isoftdata/utility-db": "^2.3.2", | ||
| "@isoftdata/utility-db": "^3.2.1", | ||
| "axios": "^1.7.7", | ||
@@ -49,3 +52,3 @@ "debug": "^4.1.1", | ||
| "mysql": "^2.17.1", | ||
| "sharp": "^0.32.6", | ||
| "sharp": "^0.33.5", | ||
| "stream-chunker": "^1.2.8" | ||
@@ -57,10 +60,11 @@ }, | ||
| "@types/debug": "^4.1.8", | ||
| "@types/express": "^4.17.13", | ||
| "@types/express": "^4.17.1", | ||
| "@types/express-fileupload": "^1.2.2", | ||
| "@types/md5": "^2.3.2", | ||
| "@types/mime-types": "^2.1.4", | ||
| "@types/mocha": "^10.0.1", | ||
| "@types/mysql": "^2.15.6", | ||
| "@types/node": "^20.5.0", | ||
| "@types/node": "^22.8.4", | ||
| "eslint-config-prettier": "^9.1.0", | ||
| "mocha": "^10.2.0", | ||
| "mocha": "^10.8.2", | ||
| "ts-node": "^10.7.0", | ||
@@ -67,0 +71,0 @@ "typescript": "^5.6.3" |
+21
-3
| # Project Background | ||
| This project can be using in an existing app that has an Express backend file server or it can act as a standalone file server. It's designed for use with standard ISoft-style-application MYSQL schema(SELECTs from `file` and `filechunk` tables). | ||
| # Installing | ||
| `npm i @isoftdata/file-service` | ||
| # Getting Started | ||
| > | ||
| > This project is designed with two use cases in mind: | ||
@@ -13,3 +16,5 @@ > 1. [For use in a project with an existing web server and database connection/pool](#foruseinaprojectwithanexistingwebserver) | ||
| ## For Use in a Project with an Existing Web Server | ||
| ### API | ||
| `@isoftdata/file-service` exports an object with a `registerFileServiceRoute` function on it. That function takes the an object with the following shape: | ||
@@ -54,2 +59,3 @@ | ||
| ## As a Standalone Web Server | ||
| 1. Clone this repository | ||
@@ -71,2 +77,3 @@ 2. Run `npm i` | ||
| ## Request Parameters | ||
| When making a request for an image, a few `GET` parameters are available(all optional): | ||
@@ -81,2 +88,3 @@ 1. `width` | ||
| ## Using the Client | ||
| The `FileServiceClient` class is exported by this module and can be used to upload images. Example usage: | ||
@@ -108,7 +116,8 @@ ```ts | ||
| ## What about authentication? | ||
| The file service doesn't have any concept or opinions about authentication requirements baked in. If you want to ensure that your files can't be accessed by someone that could guess the MD5 hash + fileid combo, we recommend you write an authentication checking middleware function that you register before the file service. Something like this: | ||
| ```js | ||
| const express = require('express') | ||
| const expressServer = express() | ||
| const cookieParser = require('cookie-parser') | ||
| import express from 'express' | ||
| import cookieParser from 'cookie-parser' | ||
| const app = express() | ||
| expressServer.use(cookieParser()) | ||
@@ -129,2 +138,3 @@ | ||
| ## What schema is required? | ||
| Some common ISoft schema is expected: | ||
@@ -140,7 +150,15 @@ | ||
| # Breaking changes | ||
| ## Version 3 | ||
| The only breaking change from version 2 to 3 was the requirement of Node 16 | ||
| ## Version 4 | ||
| Removed opinion of the File service that only images can be uploaded using the imported File Service functions. | ||
| *Chunker chunks files properly | ||
| ## Version 5 | ||
| Updated from CJS to ESM | ||
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
158
12.86%Yes
NaN37884
-11.58%14
7.69%728
-6.91%+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
Updated
Updated