Package Updates
This commit is contained in:
18
.prettierrc.js
Normal file
18
.prettierrc.js
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
const config = {
|
||||||
|
printWidth: 120,
|
||||||
|
useTabs: false,
|
||||||
|
tabWidth: 2,
|
||||||
|
trailingComma: "none",
|
||||||
|
semi: true,
|
||||||
|
singleQuote: false,
|
||||||
|
bracketSpacing: true,
|
||||||
|
arrowParens: "always",
|
||||||
|
jsxSingleQuote: false,
|
||||||
|
bracketSameLine: false,
|
||||||
|
endOfLine: "lf"
|
||||||
|
// importOrder: ["^@core/(.*)$", "^@server/(.*)$", "^@ui/(.*)$", "^[./]"],
|
||||||
|
// importOrderSeparation: true,
|
||||||
|
// importOrderSortSpecifiers: true
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = config;
|
||||||
14
Dockerfile
14
Dockerfile
@@ -1,4 +1,4 @@
|
|||||||
FROM node:16
|
FROM node:20
|
||||||
|
|
||||||
# Create app directory
|
# Create app directory
|
||||||
WORKDIR /usr/src/app
|
WORKDIR /usr/src/app
|
||||||
@@ -28,9 +28,9 @@ RUN apt install -y libwebp-dev
|
|||||||
# Install HEIF support (libheic-dev Package does not exist on 16.04)
|
# Install HEIF support (libheic-dev Package does not exist on 16.04)
|
||||||
RUN apt-get -y install libde265-dev
|
RUN apt-get -y install libde265-dev
|
||||||
RUN apt-get -y install pkg-config m4 libtool automake autoconf
|
RUN apt-get -y install pkg-config m4 libtool automake autoconf
|
||||||
RUN wget https://github.com/strukturag/libheif/archive/v1.14.1.tar.gz
|
RUN wget https://github.com/strukturag/libheif/archive/v1.18.0.tar.gz
|
||||||
RUN tar -xvf v1.14.1.tar.gz
|
RUN tar -xvf v1.18.0.tar.gz
|
||||||
WORKDIR /usr/src/app/libheif-1.14.1/
|
WORKDIR /usr/src/app/libheif-1.18.0/
|
||||||
RUN ./autogen.sh
|
RUN ./autogen.sh
|
||||||
RUN ./configure
|
RUN ./configure
|
||||||
RUN make
|
RUN make
|
||||||
@@ -46,9 +46,9 @@ RUN apt-get -y install wget && apt-get install -y ruby-full && ruby -v
|
|||||||
# RUN apt-get install imagemagick -y
|
# RUN apt-get install imagemagick -y
|
||||||
|
|
||||||
# # Install ImageMagick with WEBP and HEIC support
|
# # Install ImageMagick with WEBP and HEIC support
|
||||||
RUN wget https://download.imagemagick.org/archive/releases/ImageMagick-7.1.1-13.tar.xz
|
RUN wget https://download.imagemagick.org/archive/releases/ImageMagick-7.1.1-35.tar.xz
|
||||||
RUN tar -xvf ImageMagick-7.1.1-13.tar.xz
|
RUN tar -xvf ImageMagick-7.1.1-35.tar.xz
|
||||||
WORKDIR /usr/src/app/ImageMagick-7.1.1-13/
|
WORKDIR /usr/src/app/ImageMagick-7.1.1-35/
|
||||||
RUN ./configure --with-heic=yes --with-webp=yes
|
RUN ./configure --with-heic=yes --with-webp=yes
|
||||||
RUN make
|
RUN make
|
||||||
RUN make install
|
RUN make install
|
||||||
|
|||||||
@@ -1,10 +1,6 @@
|
|||||||
import { Request, Response, NextFunction } from "express";
|
import { Request, Response, NextFunction } from "express";
|
||||||
|
|
||||||
export default function BillRequestValidator(
|
export default function BillRequestValidator(req: Request, res: Response, next: NextFunction) {
|
||||||
req: Request,
|
|
||||||
res: Response,
|
|
||||||
next: NextFunction
|
|
||||||
) {
|
|
||||||
const vendorid: string = (req.body.vendorid || "").trim();
|
const vendorid: string = (req.body.vendorid || "").trim();
|
||||||
const invoice_number: string = (req.body.invoice_number || "").trim();
|
const invoice_number: string = (req.body.invoice_number || "").trim();
|
||||||
const jobid: string = (req.body.jobid || "").trim();
|
const jobid: string = (req.body.jobid || "").trim();
|
||||||
|
|||||||
@@ -22,17 +22,10 @@ export async function BillsListMedia(req: Request, res: Response) {
|
|||||||
if (req.files) {
|
if (req.files) {
|
||||||
ret = await Promise.all(
|
ret = await Promise.all(
|
||||||
(req.files as Express.Multer.File[]).map(async (file) => {
|
(req.files as Express.Multer.File[]).map(async (file) => {
|
||||||
const relativeFilePath: string = path.join(
|
const relativeFilePath: string = path.join(PathToRoBillsFolder(jobid), file.filename);
|
||||||
PathToRoBillsFolder(jobid),
|
|
||||||
file.filename
|
|
||||||
);
|
|
||||||
|
|
||||||
const relativeThumbPath: string = await GenerateThumbnail(
|
const relativeThumbPath: string = await GenerateThumbnail(relativeFilePath);
|
||||||
relativeFilePath
|
const type: core.FileTypeResult | undefined = await ft.fileTypeFromFile(relativeFilePath);
|
||||||
);
|
|
||||||
const type: core.FileTypeResult | undefined = await ft.fromFile(
|
|
||||||
relativeFilePath
|
|
||||||
);
|
|
||||||
return {
|
return {
|
||||||
type,
|
type,
|
||||||
size: file.size,
|
size: file.size,
|
||||||
@@ -41,72 +34,57 @@ export async function BillsListMedia(req: Request, res: Response) {
|
|||||||
FolderPaths.JobsFolder,
|
FolderPaths.JobsFolder,
|
||||||
jobid,
|
jobid,
|
||||||
FolderPaths.BillsSubDir,
|
FolderPaths.BillsSubDir,
|
||||||
file.filename,
|
file.filename
|
||||||
]),
|
]),
|
||||||
thumbnail: GenerateUrl([
|
thumbnail: GenerateUrl([
|
||||||
FolderPaths.StaticPath,
|
FolderPaths.StaticPath,
|
||||||
FolderPaths.JobsFolder,
|
FolderPaths.JobsFolder,
|
||||||
jobid,
|
jobid,
|
||||||
FolderPaths.BillsSubDir,
|
FolderPaths.BillsSubDir,
|
||||||
relativeThumbPath,
|
relativeThumbPath
|
||||||
]),
|
]),
|
||||||
thumbnailHeight: 250,
|
thumbnailHeight: 250,
|
||||||
thumbnailWidth: 250,
|
thumbnailWidth: 250,
|
||||||
filename: file.filename,
|
filename: file.filename,
|
||||||
relativeFilePath,
|
relativeFilePath
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
let filesList: fs.Dirent[] = (
|
let filesList: fs.Dirent[] = (
|
||||||
await fs.readdir(PathToRoBillsFolder(jobid), {
|
await fs.readdir(PathToRoBillsFolder(jobid), {
|
||||||
withFileTypes: true,
|
withFileTypes: true
|
||||||
})
|
})
|
||||||
).filter(
|
).filter(
|
||||||
(f) =>
|
(f) =>
|
||||||
f.isFile() &&
|
f.isFile() &&
|
||||||
!/(^|\/)\.[^\/\.]/g.test(f.name) &&
|
!/(^|\/)\.[^\/\.]/g.test(f.name) &&
|
||||||
(invoice_number !== ""
|
(invoice_number !== "" ? f.name.toLowerCase().includes(invoice_number.toLowerCase()) : true) &&
|
||||||
? f.name.toLowerCase().includes(invoice_number.toLowerCase())
|
|
||||||
: true) &&
|
|
||||||
ListableChecker(f)
|
ListableChecker(f)
|
||||||
);
|
);
|
||||||
|
|
||||||
ret = await Promise.all(
|
ret = await Promise.all(
|
||||||
filesList.map(async (file) => {
|
filesList.map(async (file) => {
|
||||||
const relativeFilePath: string = path.join(
|
const relativeFilePath: string = path.join(PathToRoBillsFolder(jobid), file.name);
|
||||||
PathToRoBillsFolder(jobid),
|
|
||||||
file.name
|
|
||||||
);
|
|
||||||
|
|
||||||
const relativeThumbPath: string = await GenerateThumbnail(
|
const relativeThumbPath: string = await GenerateThumbnail(relativeFilePath);
|
||||||
relativeFilePath
|
const type: core.FileTypeResult | undefined = await ft.fileTypeFromFile(relativeFilePath);
|
||||||
);
|
|
||||||
const type: core.FileTypeResult | undefined = await ft.fromFile(
|
|
||||||
relativeFilePath
|
|
||||||
);
|
|
||||||
const fileSize = await fs.stat(relativeFilePath);
|
const fileSize = await fs.stat(relativeFilePath);
|
||||||
return {
|
return {
|
||||||
type,
|
type,
|
||||||
size: fileSize.size,
|
size: fileSize.size,
|
||||||
src: GenerateUrl([
|
src: GenerateUrl([FolderPaths.StaticPath, FolderPaths.JobsFolder, jobid, FolderPaths.BillsSubDir, file.name]),
|
||||||
FolderPaths.StaticPath,
|
|
||||||
FolderPaths.JobsFolder,
|
|
||||||
jobid,
|
|
||||||
FolderPaths.BillsSubDir,
|
|
||||||
file.name,
|
|
||||||
]),
|
|
||||||
thumbnail: GenerateUrl([
|
thumbnail: GenerateUrl([
|
||||||
FolderPaths.StaticPath,
|
FolderPaths.StaticPath,
|
||||||
FolderPaths.JobsFolder,
|
FolderPaths.JobsFolder,
|
||||||
jobid,
|
jobid,
|
||||||
FolderPaths.BillsSubDir,
|
FolderPaths.BillsSubDir,
|
||||||
relativeThumbPath,
|
relativeThumbPath
|
||||||
]),
|
]),
|
||||||
thumbnailHeight: 250,
|
thumbnailHeight: 250,
|
||||||
thumbnailWidth: 250,
|
thumbnailWidth: 250,
|
||||||
filename: file.name,
|
filename: file.name,
|
||||||
relativeFilePath,
|
relativeFilePath
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -5,18 +5,13 @@ import multer from "multer";
|
|||||||
import path, { resolve } from "path";
|
import path, { resolve } from "path";
|
||||||
import { logger } from "../server";
|
import { logger } from "../server";
|
||||||
import GenerateThumbnail from "../util/generateThumbnail";
|
import GenerateThumbnail from "../util/generateThumbnail";
|
||||||
import generateUniqueFilename, {
|
import generateUniqueFilename, { generateUniqueBillFilename } from "../util/generateUniqueFilename";
|
||||||
generateUniqueBillFilename,
|
|
||||||
} from "../util/generateUniqueFilename";
|
|
||||||
import { ConvertHeicFiles } from "../util/heicConverter";
|
import { ConvertHeicFiles } from "../util/heicConverter";
|
||||||
import {
|
import { PathToRoBillsFolder, PathToVendorBillsFile } from "../util/pathGenerators";
|
||||||
PathToRoBillsFolder,
|
|
||||||
PathToVendorBillsFile,
|
|
||||||
} from "../util/pathGenerators";
|
|
||||||
import { BillsListMedia } from "./billsListMedia";
|
import { BillsListMedia } from "./billsListMedia";
|
||||||
|
|
||||||
dotenv.config({
|
dotenv.config({
|
||||||
path: resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`),
|
path: resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`)
|
||||||
});
|
});
|
||||||
|
|
||||||
export const BillsMediaUploadMulter = multer({
|
export const BillsMediaUploadMulter = multer({
|
||||||
@@ -25,27 +20,20 @@ export const BillsMediaUploadMulter = multer({
|
|||||||
const jobid: string = (req.body.jobid || "").trim();
|
const jobid: string = (req.body.jobid || "").trim();
|
||||||
const DestinationFolder: string = PathToRoBillsFolder(jobid);
|
const DestinationFolder: string = PathToRoBillsFolder(jobid);
|
||||||
fs.ensureDirSync(DestinationFolder);
|
fs.ensureDirSync(DestinationFolder);
|
||||||
cb(
|
cb(jobid === "" || jobid === null ? new Error("Job ID not specified.") : null, DestinationFolder);
|
||||||
jobid === "" || jobid === null
|
|
||||||
? new Error("Job ID not specified.")
|
|
||||||
: null,
|
|
||||||
DestinationFolder
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
filename: function (req, file, cb) {
|
filename: function (req, file, cb) {
|
||||||
logger.info("Uploading file: ", {
|
logger.info("Uploading file: ", {
|
||||||
file: path.basename(file.originalname),
|
file: path.basename(file.originalname)
|
||||||
});
|
});
|
||||||
const invoice_number: string = (req.body.invoice_number || "").trim();
|
const invoice_number: string = (req.body.invoice_number || "").trim();
|
||||||
|
|
||||||
cb(
|
cb(
|
||||||
invoice_number === "" || invoice_number === null
|
invoice_number === "" || invoice_number === null ? new Error("Invoice number not specified.") : null,
|
||||||
? new Error("Invoice number not specified.")
|
|
||||||
: null,
|
|
||||||
generateUniqueBillFilename(file, invoice_number)
|
generateUniqueBillFilename(file, invoice_number)
|
||||||
);
|
);
|
||||||
},
|
}
|
||||||
}),
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
export async function BillsUploadMedia(req: Request, res: Response) {
|
export async function BillsUploadMedia(req: Request, res: Response) {
|
||||||
@@ -53,7 +41,7 @@ export async function BillsUploadMedia(req: Request, res: Response) {
|
|||||||
if (!req.files) {
|
if (!req.files) {
|
||||||
res.send({
|
res.send({
|
||||||
status: false,
|
status: false,
|
||||||
message: "No file uploaded",
|
message: "No file uploaded"
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
await ConvertHeicFiles(req.files as Express.Multer.File[]);
|
await ConvertHeicFiles(req.files as Express.Multer.File[]);
|
||||||
@@ -76,10 +64,7 @@ export async function BillsUploadMedia(req: Request, res: Response) {
|
|||||||
|
|
||||||
copyQueue.push(
|
copyQueue.push(
|
||||||
(async () => {
|
(async () => {
|
||||||
const target: string = path.join(
|
const target: string = path.join(PathToVendorBillsFile(vendorid), file.filename);
|
||||||
PathToVendorBillsFile(vendorid),
|
|
||||||
file.filename
|
|
||||||
);
|
|
||||||
await fs.ensureDir(path.dirname(target));
|
await fs.ensureDir(path.dirname(target));
|
||||||
await fs.copyFile(file.path, target);
|
await fs.copyFile(file.path, target);
|
||||||
})()
|
})()
|
||||||
@@ -93,7 +78,7 @@ export async function BillsUploadMedia(req: Request, res: Response) {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error("Error while uploading Bill Media", {
|
logger.error("Error while uploading Bill Media", {
|
||||||
files: req.files,
|
files: req.files,
|
||||||
error,
|
error
|
||||||
});
|
});
|
||||||
res.status(500).send(error);
|
res.status(500).send(error);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,6 @@ module.exports = [
|
|||||||
script: "dist/server.js",
|
script: "dist/server.js",
|
||||||
name: "MediaServer",
|
name: "MediaServer",
|
||||||
exec_mode: "cluster",
|
exec_mode: "cluster",
|
||||||
instances: 0,
|
instances: 0
|
||||||
},
|
}
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -1,10 +1,6 @@
|
|||||||
import { Request, Response, NextFunction } from "express";
|
import { Request, Response, NextFunction } from "express";
|
||||||
|
|
||||||
export default function ValidateJobBasedRequest(
|
export default function ValidateJobBasedRequest(req: Request, res: Response, next: NextFunction) {
|
||||||
req: Request,
|
|
||||||
res: Response,
|
|
||||||
next: NextFunction
|
|
||||||
) {
|
|
||||||
const jobid: string = (req.body.jobid || "").trim();
|
const jobid: string = (req.body.jobid || "").trim();
|
||||||
if (jobid === "") {
|
if (jobid === "") {
|
||||||
res.status(400).json({ error: "No RO Number has been specified." });
|
res.status(400).json({ error: "No RO Number has been specified." });
|
||||||
|
|||||||
@@ -5,11 +5,7 @@ import { logger } from "../server";
|
|||||||
import MediaFile from "../util/interfaces/MediaFile";
|
import MediaFile from "../util/interfaces/MediaFile";
|
||||||
import ListableChecker from "../util/listableChecker";
|
import ListableChecker from "../util/listableChecker";
|
||||||
import { PathToRoBillsFolder, PathToRoFolder } from "../util/pathGenerators";
|
import { PathToRoBillsFolder, PathToRoFolder } from "../util/pathGenerators";
|
||||||
import {
|
import { BillsRelativeFilePath, FolderPaths, JobRelativeFilePath } from "../util/serverInit";
|
||||||
BillsRelativeFilePath,
|
|
||||||
FolderPaths,
|
|
||||||
JobRelativeFilePath,
|
|
||||||
} from "../util/serverInit";
|
|
||||||
|
|
||||||
export async function JobsDeleteMedia(req: Request, res: Response) {
|
export async function JobsDeleteMedia(req: Request, res: Response) {
|
||||||
const jobid: string = (req.body.jobid || "").trim();
|
const jobid: string = (req.body.jobid || "").trim();
|
||||||
@@ -21,12 +17,12 @@ export async function JobsDeleteMedia(req: Request, res: Response) {
|
|||||||
// Setup lists for both file locations
|
// Setup lists for both file locations
|
||||||
const jobFileList: fs.Dirent[] = (
|
const jobFileList: fs.Dirent[] = (
|
||||||
await fs.readdir(PathToRoFolder(jobid), {
|
await fs.readdir(PathToRoFolder(jobid), {
|
||||||
withFileTypes: true,
|
withFileTypes: true
|
||||||
})
|
})
|
||||||
).filter((f) => f.isFile() && ListableChecker(f));
|
).filter((f) => f.isFile() && ListableChecker(f));
|
||||||
const billFileList: fs.Dirent[] = (
|
const billFileList: fs.Dirent[] = (
|
||||||
await fs.readdir(PathToRoBillsFolder(jobid), {
|
await fs.readdir(PathToRoBillsFolder(jobid), {
|
||||||
withFileTypes: true,
|
withFileTypes: true
|
||||||
})
|
})
|
||||||
).filter((f) => f.isFile() && ListableChecker(f));
|
).filter((f) => f.isFile() && ListableChecker(f));
|
||||||
|
|
||||||
@@ -37,12 +33,7 @@ export async function JobsDeleteMedia(req: Request, res: Response) {
|
|||||||
// File is in the set of requested files.
|
// File is in the set of requested files.
|
||||||
await fs.remove(JobRelativeFilePath(jobid, file.name));
|
await fs.remove(JobRelativeFilePath(jobid, file.name));
|
||||||
await fs.remove(
|
await fs.remove(
|
||||||
path.join(
|
path.join(FolderPaths.Jobs, jobid, FolderPaths.ThumbsSubDir, file.name.replace(/\.[^/.]+$/, ".png"))
|
||||||
FolderPaths.Jobs,
|
|
||||||
jobid,
|
|
||||||
FolderPaths.ThumbsSubDir,
|
|
||||||
file.name.replace(/\.[^/.]+$/, ".png")
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -22,12 +22,12 @@ export async function jobsDownloadMedia(req: Request, res: Response) {
|
|||||||
|
|
||||||
const jobFileList: fs.Dirent[] = (
|
const jobFileList: fs.Dirent[] = (
|
||||||
await fs.readdir(PathToRoFolder(jobid), {
|
await fs.readdir(PathToRoFolder(jobid), {
|
||||||
withFileTypes: true,
|
withFileTypes: true
|
||||||
})
|
})
|
||||||
).filter((f) => f.isFile() && ListableChecker(f));
|
).filter((f) => f.isFile() && ListableChecker(f));
|
||||||
const billFileList: fs.Dirent[] = (
|
const billFileList: fs.Dirent[] = (
|
||||||
await fs.readdir(PathToRoBillsFolder(jobid), {
|
await fs.readdir(PathToRoBillsFolder(jobid), {
|
||||||
withFileTypes: true,
|
withFileTypes: true
|
||||||
})
|
})
|
||||||
).filter((f) => f.isFile() && ListableChecker(f));
|
).filter((f) => f.isFile() && ListableChecker(f));
|
||||||
|
|
||||||
@@ -36,18 +36,14 @@ export async function jobsDownloadMedia(req: Request, res: Response) {
|
|||||||
await Promise.all(
|
await Promise.all(
|
||||||
jobFileList.map(async (file) => {
|
jobFileList.map(async (file) => {
|
||||||
//Do something async
|
//Do something async
|
||||||
const fileOnDisk: Buffer = await fs.readFile(
|
const fileOnDisk: Buffer = await fs.readFile(JobRelativeFilePath(jobid, file.name));
|
||||||
JobRelativeFilePath(jobid, file.name)
|
|
||||||
);
|
|
||||||
zip.file(path.parse(path.basename(file.name)).base, fileOnDisk);
|
zip.file(path.parse(path.basename(file.name)).base, fileOnDisk);
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
billFileList.map(async (file) => {
|
billFileList.map(async (file) => {
|
||||||
//Do something async
|
//Do something async
|
||||||
const fileOnDisk: Buffer = await fs.readFile(
|
const fileOnDisk: Buffer = await fs.readFile(BillsRelativeFilePath(jobid, file.name));
|
||||||
BillsRelativeFilePath(jobid, file.name)
|
|
||||||
);
|
|
||||||
zip.file(path.parse(path.basename(file.name)).base, fileOnDisk);
|
zip.file(path.parse(path.basename(file.name)).base, fileOnDisk);
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
@@ -57,23 +53,19 @@ export async function jobsDownloadMedia(req: Request, res: Response) {
|
|||||||
jobFileList.map(async (file) => {
|
jobFileList.map(async (file) => {
|
||||||
if (files.includes(path.parse(path.basename(file.name)).base)) {
|
if (files.includes(path.parse(path.basename(file.name)).base)) {
|
||||||
// File is in the set of requested files.
|
// File is in the set of requested files.
|
||||||
const fileOnDisk: Buffer = await fs.readFile(
|
const fileOnDisk: Buffer = await fs.readFile(JobRelativeFilePath(jobid, file.name));
|
||||||
JobRelativeFilePath(jobid, file.name)
|
|
||||||
);
|
|
||||||
zip.file(path.parse(path.basename(file.name)).base, fileOnDisk);
|
zip.file(path.parse(path.basename(file.name)).base, fileOnDisk);
|
||||||
}
|
}
|
||||||
}),
|
})
|
||||||
);
|
);
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
billFileList.map(async (file) => {
|
billFileList.map(async (file) => {
|
||||||
if (files.includes(path.parse(path.basename(file.name)).base)) {
|
if (files.includes(path.parse(path.basename(file.name)).base)) {
|
||||||
// File is in the set of requested files.
|
// File is in the set of requested files.
|
||||||
const fileOnDisk: Buffer = await fs.readFile(
|
const fileOnDisk: Buffer = await fs.readFile(BillsRelativeFilePath(jobid, file.name));
|
||||||
BillsRelativeFilePath(jobid, file.name)
|
|
||||||
);
|
|
||||||
zip.file(path.parse(path.basename(file.name)).base, fileOnDisk);
|
zip.file(path.parse(path.basename(file.name)).base, fileOnDisk);
|
||||||
}
|
}
|
||||||
}),
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
//Send it as a response to download it automatically.
|
//Send it as a response to download it automatically.
|
||||||
@@ -82,14 +74,14 @@ export async function jobsDownloadMedia(req: Request, res: Response) {
|
|||||||
zip
|
zip
|
||||||
.generateNodeStream({
|
.generateNodeStream({
|
||||||
type: "nodebuffer",
|
type: "nodebuffer",
|
||||||
streamFiles: true,
|
streamFiles: true
|
||||||
//encodeFileName: (filename) => `${jobid}.zip`,
|
//encodeFileName: (filename) => `${jobid}.zip`,
|
||||||
})
|
})
|
||||||
.pipe(res);
|
.pipe(res);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error("Error downloading job media.", {
|
logger.error("Error downloading job media.", {
|
||||||
jobid,
|
jobid,
|
||||||
error: (error as Error).message,
|
error: (error as Error).message
|
||||||
});
|
});
|
||||||
res.status(500).json((error as Error).message);
|
res.status(500).json((error as Error).message);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,81 +20,47 @@ export async function JobsListMedia(req: Request, res: Response) {
|
|||||||
//We just uploaded files, we're going to send only those back.
|
//We just uploaded files, we're going to send only those back.
|
||||||
ret = await Promise.all(
|
ret = await Promise.all(
|
||||||
(req.files as Express.Multer.File[]).map(async (file) => {
|
(req.files as Express.Multer.File[]).map(async (file) => {
|
||||||
const relativeFilePath: string = JobRelativeFilePath(
|
const relativeFilePath: string = JobRelativeFilePath(jobid, file.filename);
|
||||||
jobid,
|
|
||||||
file.filename
|
|
||||||
);
|
|
||||||
|
|
||||||
const relativeThumbPath: string = await GenerateThumbnail(
|
const relativeThumbPath: string = await GenerateThumbnail(relativeFilePath);
|
||||||
relativeFilePath
|
|
||||||
);
|
|
||||||
|
|
||||||
const type: core.FileTypeResult | undefined = await ft.fromFile(
|
const type: core.FileTypeResult | undefined = await ft.fileTypeFromFile(relativeFilePath);
|
||||||
relativeFilePath
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
type,
|
type,
|
||||||
size: file.size,
|
size: file.size,
|
||||||
src: GenerateUrl([
|
src: GenerateUrl([FolderPaths.StaticPath, FolderPaths.JobsFolder, jobid, file.filename]),
|
||||||
FolderPaths.StaticPath,
|
thumbnail: GenerateUrl([FolderPaths.StaticPath, FolderPaths.JobsFolder, jobid, relativeThumbPath]),
|
||||||
FolderPaths.JobsFolder,
|
|
||||||
jobid,
|
|
||||||
file.filename,
|
|
||||||
]),
|
|
||||||
thumbnail: GenerateUrl([
|
|
||||||
FolderPaths.StaticPath,
|
|
||||||
FolderPaths.JobsFolder,
|
|
||||||
jobid,
|
|
||||||
relativeThumbPath,
|
|
||||||
]),
|
|
||||||
thumbnailHeight: 250,
|
thumbnailHeight: 250,
|
||||||
thumbnailWidth: 250,
|
thumbnailWidth: 250,
|
||||||
filename: file.filename,
|
filename: file.filename,
|
||||||
relativeFilePath,
|
relativeFilePath
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
const filesList: fs.Dirent[] = (
|
const filesList: fs.Dirent[] = (
|
||||||
await fs.readdir(PathToRoFolder(jobid), {
|
await fs.readdir(PathToRoFolder(jobid), {
|
||||||
withFileTypes: true,
|
withFileTypes: true
|
||||||
})
|
})
|
||||||
).filter((f) => f.isFile() && ListableChecker(f));
|
).filter((f) => f.isFile() && ListableChecker(f));
|
||||||
|
|
||||||
ret = await Promise.all(
|
ret = await Promise.all(
|
||||||
filesList.map(async (file) => {
|
filesList.map(async (file) => {
|
||||||
const relativeFilePath: string = JobRelativeFilePath(
|
const relativeFilePath: string = JobRelativeFilePath(jobid, file.name);
|
||||||
jobid,
|
|
||||||
file.name
|
|
||||||
);
|
|
||||||
|
|
||||||
const relativeThumbPath: string = await GenerateThumbnail(
|
const relativeThumbPath: string = await GenerateThumbnail(relativeFilePath);
|
||||||
relativeFilePath
|
const type: core.FileTypeResult | undefined = await ft.fileTypeFromFile(relativeFilePath);
|
||||||
);
|
|
||||||
const type: core.FileTypeResult | undefined = await ft.fromFile(
|
|
||||||
relativeFilePath
|
|
||||||
);
|
|
||||||
const fileSize = await fs.stat(relativeFilePath);
|
const fileSize = await fs.stat(relativeFilePath);
|
||||||
return {
|
return {
|
||||||
type,
|
type,
|
||||||
size: fileSize.size,
|
size: fileSize.size,
|
||||||
src: GenerateUrl([
|
src: GenerateUrl([FolderPaths.StaticPath, FolderPaths.JobsFolder, jobid, file.name]),
|
||||||
FolderPaths.StaticPath,
|
thumbnail: GenerateUrl([FolderPaths.StaticPath, FolderPaths.JobsFolder, jobid, relativeThumbPath]),
|
||||||
FolderPaths.JobsFolder,
|
|
||||||
jobid,
|
|
||||||
file.name,
|
|
||||||
]),
|
|
||||||
thumbnail: GenerateUrl([
|
|
||||||
FolderPaths.StaticPath,
|
|
||||||
FolderPaths.JobsFolder,
|
|
||||||
jobid,
|
|
||||||
relativeThumbPath,
|
|
||||||
]),
|
|
||||||
thumbnailHeight: 250,
|
thumbnailHeight: 250,
|
||||||
thumbnailWidth: 250,
|
thumbnailWidth: 250,
|
||||||
filename: file.name,
|
filename: file.name,
|
||||||
relativeFilePath,
|
relativeFilePath
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -26,14 +26,14 @@ export async function JobsMoveMedia(req: Request, res: Response) {
|
|||||||
// Setup lists for both file locations
|
// Setup lists for both file locations
|
||||||
const jobFileList: string[] = (
|
const jobFileList: string[] = (
|
||||||
await fs.readdir(PathToRoFolder(from_jobid), {
|
await fs.readdir(PathToRoFolder(from_jobid), {
|
||||||
withFileTypes: true,
|
withFileTypes: true
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
.filter((f) => f.isFile() && ListableChecker(f))
|
.filter((f) => f.isFile() && ListableChecker(f))
|
||||||
.map((dirent) => dirent.name);
|
.map((dirent) => dirent.name);
|
||||||
const billFileList: string[] = (
|
const billFileList: string[] = (
|
||||||
await fs.readdir(PathToRoBillsFolder(from_jobid), {
|
await fs.readdir(PathToRoBillsFolder(from_jobid), {
|
||||||
withFileTypes: true,
|
withFileTypes: true
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
.filter((f) => f.isFile() && ListableChecker(f))
|
.filter((f) => f.isFile() && ListableChecker(f))
|
||||||
@@ -47,38 +47,20 @@ export async function JobsMoveMedia(req: Request, res: Response) {
|
|||||||
files.forEach((file) => {
|
files.forEach((file) => {
|
||||||
if (jobFileList.includes(file)) {
|
if (jobFileList.includes(file)) {
|
||||||
movingQueue.push(
|
movingQueue.push(
|
||||||
fs.move(
|
fs.move(path.join(FolderPaths.Jobs, from_jobid, file), path.join(FolderPaths.Jobs, jobid, file))
|
||||||
path.join(FolderPaths.Jobs, from_jobid, file),
|
|
||||||
path.join(FolderPaths.Jobs, jobid, file)
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
movingQueue.push(
|
movingQueue.push(
|
||||||
fs.move(
|
fs.move(
|
||||||
path.join(
|
path.join(FolderPaths.Jobs, from_jobid, FolderPaths.ThumbsSubDir, file.replace(/\.[^/.]+$/, ".png")),
|
||||||
FolderPaths.Jobs,
|
path.join(FolderPaths.Jobs, jobid, FolderPaths.ThumbsSubDir, file.replace(/\.[^/.]+$/, ".png"))
|
||||||
from_jobid,
|
|
||||||
FolderPaths.ThumbsSubDir,
|
|
||||||
file.replace(/\.[^/.]+$/, ".png")
|
|
||||||
),
|
|
||||||
path.join(
|
|
||||||
FolderPaths.Jobs,
|
|
||||||
jobid,
|
|
||||||
FolderPaths.ThumbsSubDir,
|
|
||||||
file.replace(/\.[^/.]+$/, ".png")
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (billFileList.includes(file)) {
|
if (billFileList.includes(file)) {
|
||||||
movingQueue.push(
|
movingQueue.push(
|
||||||
fs.move(
|
fs.move(
|
||||||
path.join(
|
path.join(FolderPaths.Jobs, from_jobid, FolderPaths.BillsSubDir, file),
|
||||||
FolderPaths.Jobs,
|
|
||||||
from_jobid,
|
|
||||||
FolderPaths.BillsSubDir,
|
|
||||||
file
|
|
||||||
),
|
|
||||||
path.join(FolderPaths.Jobs, jobid, FolderPaths.BillsSubDir, file)
|
path.join(FolderPaths.Jobs, jobid, FolderPaths.BillsSubDir, file)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
@@ -114,7 +96,7 @@ export async function JobsMoveMedia(req: Request, res: Response) {
|
|||||||
from_jobid,
|
from_jobid,
|
||||||
jobid,
|
jobid,
|
||||||
files,
|
files,
|
||||||
err,
|
err
|
||||||
});
|
});
|
||||||
res.status(500).send(err);
|
res.status(500).send(err);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,20 +15,15 @@ export const JobMediaUploadMulter = multer({
|
|||||||
const jobid: string = (req.body.jobid || "").trim();
|
const jobid: string = (req.body.jobid || "").trim();
|
||||||
const DestinationFolder: string = PathToRoFolder(jobid);
|
const DestinationFolder: string = PathToRoFolder(jobid);
|
||||||
fs.ensureDirSync(DestinationFolder);
|
fs.ensureDirSync(DestinationFolder);
|
||||||
cb(
|
cb(jobid === "" || jobid === null ? new Error("Job ID not specified.") : null, DestinationFolder);
|
||||||
jobid === "" || jobid === null
|
|
||||||
? new Error("Job ID not specified.")
|
|
||||||
: null,
|
|
||||||
DestinationFolder
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
filename: function (req, file, cb) {
|
filename: function (req, file, cb) {
|
||||||
logger.debug("Uploading file: ", {
|
logger.debug("Uploading file: ", {
|
||||||
file: path.basename(file.originalname),
|
file: path.basename(file.originalname)
|
||||||
});
|
});
|
||||||
cb(null, generateUniqueFilename(file));
|
cb(null, generateUniqueFilename(file));
|
||||||
},
|
}
|
||||||
}),
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
export async function jobsUploadMedia(req: Request, res: Response) {
|
export async function jobsUploadMedia(req: Request, res: Response) {
|
||||||
@@ -39,7 +34,7 @@ export async function jobsUploadMedia(req: Request, res: Response) {
|
|||||||
logger.warning("Upload contained no files.");
|
logger.warning("Upload contained no files.");
|
||||||
res.status(400).send({
|
res.status(400).send({
|
||||||
status: false,
|
status: false,
|
||||||
message: "No file uploaded",
|
message: "No file uploaded"
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
//If we want to skip waiting for everything, just send it back that we're good.
|
//If we want to skip waiting for everything, just send it back that we're good.
|
||||||
@@ -69,7 +64,7 @@ export async function jobsUploadMedia(req: Request, res: Response) {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error("Error uploading job media.", {
|
logger.error("Error uploading job media.", {
|
||||||
jobid,
|
jobid,
|
||||||
error: (error as Error).message,
|
error: (error as Error).message
|
||||||
});
|
});
|
||||||
res.status(500).json((error as Error).message);
|
res.status(500).json((error as Error).message);
|
||||||
}
|
}
|
||||||
|
|||||||
3558
package-lock.json
generated
3558
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
58
package.json
58
package.json
@@ -1,48 +1,50 @@
|
|||||||
{
|
{
|
||||||
"name": "bodyshop-media-server",
|
"name": "bodyshop-media-server",
|
||||||
"version": "1.0.10",
|
"version": "1.0.12",
|
||||||
"license": "UNLICENSED",
|
"license": "UNLICENSED",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^16.14.0"
|
"node": ">=18.0.0"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"server": "nodemon server.ts",
|
"server": "nodemon server.ts",
|
||||||
"start": "node dist/server.js",
|
"start": "node dist/server.js",
|
||||||
"build": "tsc -p ."
|
"build": "tsc -p .",
|
||||||
|
"makeitpretty": "prettier --write \"**/*.{css,js,json,jsx,scss,ts}\""
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"axios": "^0.24.0",
|
"axios": "^1.7.2",
|
||||||
"bluebird": "^3.7.2",
|
"bluebird": "^3.7.2",
|
||||||
"body-parser": "^1.20.0",
|
"body-parser": "^1.20.2",
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
"dotenv": "10.0.0",
|
"dotenv": "16.4.5",
|
||||||
"express": "^4.17.3",
|
"express": "^4.19.2",
|
||||||
"file-type": "^16.5.3",
|
"file-type": "^19.2.0",
|
||||||
"fs-extra": "^10.1.0",
|
"fs-extra": "^11.2.0",
|
||||||
"gm": "^1.23.1",
|
"gm": "^1.25.0",
|
||||||
"helmet": "^5.0.2",
|
"helmet": "^7.1.0",
|
||||||
"image-thumbnail": "^1.0.14",
|
"image-thumbnail": "^1.0.15",
|
||||||
"jszip": "^3.10.0",
|
"jszip": "^3.10.1",
|
||||||
"morgan": "^1.10.0",
|
"morgan": "^1.10.0",
|
||||||
"multer": "^1.4.4",
|
"multer": "^1.4.4",
|
||||||
"nocache": "^3.0.4",
|
"nocache": "^4.0.0",
|
||||||
"response-time": "^2.3.2",
|
"response-time": "^2.3.2",
|
||||||
"simple-thumbnail": "^1.6.5",
|
"simple-thumbnail": "^1.6.5",
|
||||||
"winston": "^3.7.2",
|
"winston": "^3.13.1",
|
||||||
"winston-daily-rotate-file": "^4.6.1"
|
"winston-daily-rotate-file": "^5.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/cors": "^2.8.12",
|
"@types/cors": "^2.8.17",
|
||||||
"@types/express": "^4.17.13",
|
"@types/express": "^4.17.21",
|
||||||
"@types/fs-extra": "^9.0.13",
|
"@types/fs-extra": "^11.0.4",
|
||||||
"@types/gm": "^1.18.11",
|
"@types/gm": "^1.25.4",
|
||||||
"@types/image-thumbnail": "^1.0.1",
|
"@types/image-thumbnail": "^1.0.4",
|
||||||
"@types/morgan": "^1.9.3",
|
"@types/morgan": "^1.9.9",
|
||||||
"@types/multer": "^1.4.7",
|
"@types/multer": "^1.4.11",
|
||||||
"@types/node": "^16.11.32",
|
"@types/node": "^20.14.11",
|
||||||
"@types/response-time": "^2.3.5",
|
"@types/response-time": "^2.3.8",
|
||||||
"nodemon": "^2.0.15",
|
"nodemon": "^3.1.4",
|
||||||
"ts-node": "^10.7.0",
|
"prettier": "^3.3.3",
|
||||||
"typescript": "^4.6.4"
|
"ts-node": "^10.9.2",
|
||||||
|
"typescript": "^5.5.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
17
readme.md
17
readme.md
@@ -42,14 +42,13 @@ services:
|
|||||||
command: # CLI arguments
|
command: # CLI arguments
|
||||||
- --global.checkNewVersion=true
|
- --global.checkNewVersion=true
|
||||||
- --global.sendAnonymousUsage=false
|
- --global.sendAnonymousUsage=false
|
||||||
- --pilot.dashboard=false
|
|
||||||
- --entrypoints.https.address=:10443
|
- --entrypoints.https.address=:10443
|
||||||
- --entrypoints.https.forwardedHeaders.trustedIPs=173.245.48.0/20,103.21.244.0/22,103.22.200.0/22,103.31.4.0/22,141.101.64.0/18,108.162.192.0/18,190.93.240.0/20,188.114.96.0/20,197.234.240.0/22,198.41.128.0/17,162.158.0.0/15,104.16.0.0/12,172.64.0.0/13,131.0.72.0/22,2400:cb00::/32,2606:4700::/32,2803:f800::/32,2405:b500::/32,2405:8100::/32,2a06:98c0::/29,2c0f:f248::/32
|
- --entrypoints.https.forwardedHeaders.trustedIPs=173.245.48.0/20,103.21.244.0/22,103.22.200.0/22,103.31.4.0/22,141.101.64.0/18,108.162.192.0/18,190.93.240.0/20,188.114.96.0/20,197.234.240.0/22,198.41.128.0/17,162.158.0.0/15,104.16.0.0/12,172.64.0.0/13,131.0.72.0/22,2400:cb00::/32,2606:4700::/32,2803:f800::/32,2405:b500::/32,2405:8100::/32,2a06:98c0::/29,2c0f:f248::/32
|
||||||
- --log=true
|
- --log=true
|
||||||
- --log.level=ERROR # (Default: error) DEBUG, INFO, WARN, ERROR, FATAL, PANIC
|
- --log.level=ERROR # (Default: error) DEBUG, INFO, WARN, ERROR, FATAL, PANIC
|
||||||
- --providers.docker=true
|
- --providers.docker=true
|
||||||
- --providers.docker.exposedbydefault=false
|
- --providers.docker.exposedbydefault=false
|
||||||
- --providers.docker.network=t2_proxy
|
- --providers.docker.network=traefik_proxy
|
||||||
- --certificatesResolvers.dns-cloudflare.acme.email=cloudflare@thinkimex.com
|
- --certificatesResolvers.dns-cloudflare.acme.email=cloudflare@thinkimex.com
|
||||||
- --certificatesResolvers.dns-cloudflare.acme.storage=/acme.json
|
- --certificatesResolvers.dns-cloudflare.acme.storage=/acme.json
|
||||||
- --certificatesResolvers.dns-cloudflare.acme.keyType=EC384
|
- --certificatesResolvers.dns-cloudflare.acme.keyType=EC384
|
||||||
@@ -57,7 +56,7 @@ services:
|
|||||||
- --certificatesresolvers.dns-cloudflare.acme.dnschallenge.resolvers=1.1.1.1:53,1.0.0.1:53,2606:4700:4700::1111:53,2606:4700:4700::1001:53
|
- --certificatesresolvers.dns-cloudflare.acme.dnschallenge.resolvers=1.1.1.1:53,1.0.0.1:53,2606:4700:4700::1111:53,2606:4700:4700::1001:53
|
||||||
- --certificatesResolvers.dns-cloudflare.acme.dnsChallenge.delayBeforeCheck=90 # To delay DNS check and reduce LE hitrate
|
- --certificatesResolvers.dns-cloudflare.acme.dnsChallenge.delayBeforeCheck=90 # To delay DNS check and reduce LE hitrate
|
||||||
networks:
|
networks:
|
||||||
t2_proxy:
|
traefik_proxy:
|
||||||
ipv4_address: 192.168.90.254
|
ipv4_address: 192.168.90.254
|
||||||
ports:
|
ports:
|
||||||
- target: 10443
|
- target: 10443
|
||||||
@@ -77,7 +76,7 @@ services:
|
|||||||
container_name: cf-ddns
|
container_name: cf-ddns
|
||||||
image: oznu/cloudflare-ddns:latest
|
image: oznu/cloudflare-ddns:latest
|
||||||
networks:
|
networks:
|
||||||
- t2_proxy
|
- traefik_proxy
|
||||||
restart: always
|
restart: always
|
||||||
environment:
|
environment:
|
||||||
- API_KEY=b8BWRbkJckVK0IG6600Iq5AYx4HYZ7dLzDWUOZ2D
|
- API_KEY=b8BWRbkJckVK0IG6600Iq5AYx4HYZ7dLzDWUOZ2D
|
||||||
@@ -92,7 +91,7 @@ services:
|
|||||||
ims:
|
ims:
|
||||||
container_name: ims
|
container_name: ims
|
||||||
networks:
|
networks:
|
||||||
- t2_proxy
|
- traefik_proxy
|
||||||
volumes:
|
volumes:
|
||||||
- /mnt/ims:/media
|
- /mnt/ims:/media
|
||||||
environment:
|
environment:
|
||||||
@@ -117,7 +116,7 @@ services:
|
|||||||
image: amir20/dozzle:latest
|
image: amir20/dozzle:latest
|
||||||
restart: always
|
restart: always
|
||||||
networks:
|
networks:
|
||||||
- t2_proxy
|
- traefik_proxy
|
||||||
ports:
|
ports:
|
||||||
- "8080:8080"
|
- "8080:8080"
|
||||||
security_opt:
|
security_opt:
|
||||||
@@ -135,7 +134,7 @@ services:
|
|||||||
container_name: watchtower-ims
|
container_name: watchtower-ims
|
||||||
image: containrrr/watchtower
|
image: containrrr/watchtower
|
||||||
networks:
|
networks:
|
||||||
- t2_proxy
|
- traefik_proxy
|
||||||
volumes:
|
volumes:
|
||||||
- /var/run/docker.sock:/var/run/docker.sock
|
- /var/run/docker.sock:/var/run/docker.sock
|
||||||
environment:
|
environment:
|
||||||
@@ -154,8 +153,8 @@ services:
|
|||||||
- "traefik.enable=false"
|
- "traefik.enable=false"
|
||||||
|
|
||||||
networks:
|
networks:
|
||||||
t2_proxy:
|
traefik_proxy:
|
||||||
name: t2_proxy
|
name: traefik_proxy
|
||||||
driver: bridge
|
driver: bridge
|
||||||
ipam:
|
ipam:
|
||||||
config:
|
config:
|
||||||
|
|||||||
85
server.ts
85
server.ts
@@ -16,24 +16,17 @@ import cors from "cors";
|
|||||||
import helmet from "helmet";
|
import helmet from "helmet";
|
||||||
import responseTime from "response-time";
|
import responseTime from "response-time";
|
||||||
import nocache from "nocache";
|
import nocache from "nocache";
|
||||||
import {
|
import { BillsMediaUploadMulter, BillsUploadMedia } from "./bills/billsUploadMedia";
|
||||||
BillsMediaUploadMulter,
|
|
||||||
BillsUploadMedia,
|
|
||||||
} from "./bills/billsUploadMedia";
|
|
||||||
import ValidateImsToken from "./util/validateToken";
|
import ValidateImsToken from "./util/validateToken";
|
||||||
import { jobsDownloadMedia } from "./jobs/jobsDownloadMedia";
|
import { jobsDownloadMedia } from "./jobs/jobsDownloadMedia";
|
||||||
import { JobsDeleteMedia } from "./jobs/jobsDeleteMedia";
|
import { JobsDeleteMedia } from "./jobs/jobsDeleteMedia";
|
||||||
|
|
||||||
dotenv.config({
|
dotenv.config({
|
||||||
path: resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`),
|
path: resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`)
|
||||||
});
|
});
|
||||||
|
|
||||||
export const logger = winston.createLogger({
|
export const logger = winston.createLogger({
|
||||||
format: winston.format.combine(
|
format: winston.format.combine(winston.format.timestamp(), winston.format.json(), winston.format.prettyPrint()),
|
||||||
winston.format.timestamp(),
|
|
||||||
winston.format.json(),
|
|
||||||
winston.format.prettyPrint()
|
|
||||||
),
|
|
||||||
level: "http",
|
level: "http",
|
||||||
levels: { ...winston.config.syslog.levels, http: 8 },
|
levels: { ...winston.config.syslog.levels, http: 8 },
|
||||||
exceptionHandlers: [
|
exceptionHandlers: [
|
||||||
@@ -42,14 +35,11 @@ export const logger = winston.createLogger({
|
|||||||
datePattern: "YYYY-MM-DD-HH",
|
datePattern: "YYYY-MM-DD-HH",
|
||||||
zippedArchive: true,
|
zippedArchive: true,
|
||||||
maxSize: "20m",
|
maxSize: "20m",
|
||||||
maxFiles: "14d",
|
maxFiles: "14d"
|
||||||
}),
|
}),
|
||||||
new winston.transports.Console({
|
new winston.transports.Console({
|
||||||
format: winston.format.combine(
|
format: winston.format.combine(winston.format.colorize(), winston.format.simple())
|
||||||
winston.format.colorize(),
|
})
|
||||||
winston.format.simple()
|
|
||||||
),
|
|
||||||
}),
|
|
||||||
],
|
],
|
||||||
rejectionHandlers: [
|
rejectionHandlers: [
|
||||||
new DailyRotateFile({
|
new DailyRotateFile({
|
||||||
@@ -57,14 +47,11 @@ export const logger = winston.createLogger({
|
|||||||
datePattern: "YYYY-MM-DD-HH",
|
datePattern: "YYYY-MM-DD-HH",
|
||||||
zippedArchive: true,
|
zippedArchive: true,
|
||||||
maxSize: "20m",
|
maxSize: "20m",
|
||||||
maxFiles: "14d",
|
maxFiles: "14d"
|
||||||
}),
|
}),
|
||||||
new winston.transports.Console({
|
new winston.transports.Console({
|
||||||
format: winston.format.combine(
|
format: winston.format.combine(winston.format.colorize(), winston.format.simple())
|
||||||
winston.format.colorize(),
|
})
|
||||||
winston.format.simple()
|
|
||||||
),
|
|
||||||
}),
|
|
||||||
],
|
],
|
||||||
transports: [
|
transports: [
|
||||||
new DailyRotateFile({
|
new DailyRotateFile({
|
||||||
@@ -73,7 +60,7 @@ export const logger = winston.createLogger({
|
|||||||
zippedArchive: true,
|
zippedArchive: true,
|
||||||
maxSize: "20m",
|
maxSize: "20m",
|
||||||
maxFiles: "14d",
|
maxFiles: "14d",
|
||||||
level: "error",
|
level: "error"
|
||||||
}),
|
}),
|
||||||
|
|
||||||
new DailyRotateFile({
|
new DailyRotateFile({
|
||||||
@@ -82,25 +69,22 @@ export const logger = winston.createLogger({
|
|||||||
zippedArchive: true,
|
zippedArchive: true,
|
||||||
maxSize: "20m",
|
maxSize: "20m",
|
||||||
maxFiles: "14d",
|
maxFiles: "14d",
|
||||||
level: "debug",
|
level: "debug"
|
||||||
}),
|
}),
|
||||||
new DailyRotateFile({
|
new DailyRotateFile({
|
||||||
filename: path.join(FolderPaths.Root, "logs", "ALL-%DATE%.log"),
|
filename: path.join(FolderPaths.Root, "logs", "ALL-%DATE%.log"),
|
||||||
datePattern: "YYYY-MM-DD-HH",
|
datePattern: "YYYY-MM-DD-HH",
|
||||||
zippedArchive: true,
|
zippedArchive: true,
|
||||||
maxSize: "20m",
|
maxSize: "20m",
|
||||||
maxFiles: "14d",
|
maxFiles: "14d"
|
||||||
}),
|
})
|
||||||
],
|
]
|
||||||
});
|
});
|
||||||
|
|
||||||
if (process.env.NODE_ENV !== "production") {
|
if (process.env.NODE_ENV !== "production") {
|
||||||
logger.add(
|
logger.add(
|
||||||
new winston.transports.Console({
|
new winston.transports.Console({
|
||||||
format: winston.format.combine(
|
format: winston.format.combine(winston.format.colorize(), winston.format.simple())
|
||||||
winston.format.colorize(),
|
|
||||||
winston.format.simple()
|
|
||||||
),
|
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -118,27 +102,16 @@ const morganMiddleware = morgan(
|
|||||||
"combined", //":method :url :status :res[content-length] - :response-time ms"
|
"combined", //":method :url :status :res[content-length] - :response-time ms"
|
||||||
{
|
{
|
||||||
stream: {
|
stream: {
|
||||||
write: (message) => logger.http(message.trim()),
|
write: (message) => logger.http(message.trim())
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
app.use(morganMiddleware);
|
app.use(morganMiddleware);
|
||||||
app.use(helmet({ crossOriginResourcePolicy: { policy: "cross-origin" } }));
|
app.use(helmet({ crossOriginResourcePolicy: { policy: "cross-origin" } }));
|
||||||
app.post("/jobs/list", ValidateImsToken, JobRequestValidator, JobsListMedia);
|
app.post("/jobs/list", ValidateImsToken, JobRequestValidator, JobsListMedia);
|
||||||
app.post(
|
app.post("/jobs/upload", ValidateImsToken, JobMediaUploadMulter.array("file"), JobRequestValidator, jobsUploadMedia);
|
||||||
"/jobs/upload",
|
app.post("/jobs/download", ValidateImsToken, JobRequestValidator, jobsDownloadMedia);
|
||||||
ValidateImsToken,
|
|
||||||
JobMediaUploadMulter.array("file"),
|
|
||||||
JobRequestValidator,
|
|
||||||
jobsUploadMedia
|
|
||||||
);
|
|
||||||
app.post(
|
|
||||||
"/jobs/download",
|
|
||||||
ValidateImsToken,
|
|
||||||
JobRequestValidator,
|
|
||||||
jobsDownloadMedia
|
|
||||||
);
|
|
||||||
app.post(
|
app.post(
|
||||||
"/jobs/move", //JobRequestValidator,
|
"/jobs/move", //JobRequestValidator,
|
||||||
ValidateImsToken,
|
ValidateImsToken,
|
||||||
@@ -159,23 +132,13 @@ app.post(
|
|||||||
BillsUploadMedia
|
BillsUploadMedia
|
||||||
);
|
);
|
||||||
|
|
||||||
app.get(
|
app.get("/", ValidateImsToken, (req: express.Request, res: express.Response) => {
|
||||||
"/",
|
res.send("IMS running.");
|
||||||
ValidateImsToken,
|
});
|
||||||
(req: express.Request, res: express.Response) => {
|
|
||||||
res.send("IMS running.");
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
InitServer();
|
InitServer();
|
||||||
app.use(
|
app.use(FolderPaths.StaticPath, express.static(FolderPaths.Root, { etag: false, maxAge: 30 * 1000 }));
|
||||||
FolderPaths.StaticPath,
|
app.use("/assets", express.static("./assets", { etag: false, maxAge: 30 * 1000 }));
|
||||||
express.static(FolderPaths.Root, { etag: false, maxAge: 30 * 1000 })
|
|
||||||
);
|
|
||||||
app.use(
|
|
||||||
"/assets",
|
|
||||||
express.static("./assets", { etag: false, maxAge: 30 * 1000 })
|
|
||||||
);
|
|
||||||
app.listen(port, () => {
|
app.listen(port, () => {
|
||||||
logger.info(`ImEX Media Server is running at http://localhost:${port}`);
|
logger.info(`ImEX Media Server is running at http://localhost:${port}`);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
import fs from "fs-extra";
|
|
||||||
import { access } from "fs/promises";
|
|
||||||
import imageThumbnail from "image-thumbnail";
|
|
||||||
import path from "path";
|
|
||||||
import gm from "gm";
|
|
||||||
import ft from "file-type";
|
import ft from "file-type";
|
||||||
import core from "file-type/core";
|
import core from "file-type/core";
|
||||||
|
import fs from "fs-extra";
|
||||||
|
import { access } from "fs/promises";
|
||||||
|
import gm from "gm";
|
||||||
|
import imageThumbnail from "image-thumbnail";
|
||||||
|
import path from "path";
|
||||||
|
import { logger } from "../server";
|
||||||
import GenerateUrl from "./MediaUrlGen";
|
import GenerateUrl from "./MediaUrlGen";
|
||||||
import { AssetPaths, FolderPaths } from "./serverInit";
|
import { AssetPaths, FolderPaths } from "./serverInit";
|
||||||
import { logger } from "../server";
|
|
||||||
const simpleThumb = require("simple-thumbnail");
|
const simpleThumb = require("simple-thumbnail");
|
||||||
//const ffmpeg = require("ffmpeg-static");
|
//const ffmpeg = require("ffmpeg-static");
|
||||||
|
|
||||||
@@ -20,7 +20,7 @@ export default async function GenerateThumbnail(
|
|||||||
file: string
|
file: string
|
||||||
//thumbPath: string
|
//thumbPath: string
|
||||||
) {
|
) {
|
||||||
const type: core.FileTypeResult | undefined = await ft.fromFile(file);
|
const type: core.FileTypeResult | undefined = await ft.fileTypeFromFile(file);
|
||||||
let thumbnailExtension: string = GetThumbnailExtension(type);
|
let thumbnailExtension: string = GetThumbnailExtension(type);
|
||||||
let thumbPath: string = path.join(
|
let thumbPath: string = path.join(
|
||||||
path.dirname(file),
|
path.dirname(file),
|
||||||
@@ -54,7 +54,7 @@ export default async function GenerateThumbnail(
|
|||||||
responseType: "buffer",
|
responseType: "buffer",
|
||||||
height: 250,
|
height: 250,
|
||||||
width: 250,
|
width: 250,
|
||||||
failOnError: false,
|
failOnError: false
|
||||||
});
|
});
|
||||||
console.log("Image success.");
|
console.log("Image success.");
|
||||||
await fs.writeFile(thumbPath, thumbnail);
|
await fs.writeFile(thumbPath, thumbnail);
|
||||||
@@ -64,7 +64,7 @@ export default async function GenerateThumbnail(
|
|||||||
logger.error("Error when genenerating thumbnail:", {
|
logger.error("Error when genenerating thumbnail:", {
|
||||||
thumbPath,
|
thumbPath,
|
||||||
err,
|
err,
|
||||||
message: (err as Error).message,
|
message: (err as Error).message
|
||||||
});
|
});
|
||||||
return path.relative(path.dirname(file), AssetPaths.File);
|
return path.relative(path.dirname(file), AssetPaths.File);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,14 @@
|
|||||||
import path from "path";
|
import path from "path";
|
||||||
|
|
||||||
export default function (file: Express.Multer.File) {
|
export default function (file: Express.Multer.File) {
|
||||||
return `${path.parse(path.basename(file.originalname)).name}-${Math.floor(
|
return `${path.parse(sanitizeFileName(path.basename(file.originalname))).name}-${Math.floor(Date.now() / 1000)}${path.extname(file.originalname)}`;
|
||||||
Date.now() / 1000
|
|
||||||
)}${path.extname(file.originalname)}`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function generateUniqueBillFilename(
|
export function generateUniqueBillFilename(file: Express.Multer.File, invoice_number: string) {
|
||||||
file: Express.Multer.File,
|
return `${sanitizeFileName(invoice_number)}-${Math.floor(Date.now() / 1000)}${path.extname(file.originalname)}`;
|
||||||
invoice_number: string
|
}
|
||||||
) {
|
|
||||||
return `${invoice_number}-${Math.floor(Date.now() / 1000)}${path.extname(
|
function sanitizeFileName(fileName: string): string {
|
||||||
file.originalname
|
const restrictedChars = /[<>:"/\\|?*\x00-\x1F]/g;
|
||||||
)}`;
|
return fileName.replace(restrictedChars, "");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,36 +13,25 @@ var imageMagick = gm.subClass({ imageMagick: true });
|
|||||||
|
|
||||||
//gm.subClass();
|
//gm.subClass();
|
||||||
dotenv.config({
|
dotenv.config({
|
||||||
path: resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`),
|
path: resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`)
|
||||||
});
|
});
|
||||||
|
|
||||||
export async function ConvertHeicFiles(files: Express.Multer.File[]) {
|
export async function ConvertHeicFiles(files: Express.Multer.File[]) {
|
||||||
for (const file of files) {
|
for (const file of files) {
|
||||||
const type: core.FileTypeResult | undefined = await ft.fromFile(file.path);
|
const type: core.FileTypeResult | undefined = await ft.fileTypeFromFile(file.path);
|
||||||
if (type?.mime === "image/heic") {
|
if (type?.mime === "image/heic") {
|
||||||
logger.log(
|
logger.log("debug", `Converting ${file.filename} image to JPEG from HEIC.`);
|
||||||
"debug",
|
|
||||||
`Converting ${file.filename} image to JPEG from HEIC.`
|
|
||||||
);
|
|
||||||
const convertedFileName = `${
|
const convertedFileName = `${
|
||||||
path.parse(path.basename(file.originalname)).name
|
path.parse(path.basename(file.originalname)).name
|
||||||
}-${Math.floor(Date.now() / 1000)}.jpeg`;
|
}-${Math.floor(Date.now() / 1000)}.jpeg`;
|
||||||
try {
|
try {
|
||||||
await ConvertToJpeg(
|
await ConvertToJpeg(file.path, `${file.destination}/${convertedFileName}`);
|
||||||
file.path,
|
|
||||||
`${file.destination}/${convertedFileName}`
|
|
||||||
);
|
|
||||||
//Move the HEIC.
|
//Move the HEIC.
|
||||||
if (process.env.KEEP_CONVERTED_ORIGINALS) {
|
if (process.env.KEEP_CONVERTED_ORIGINALS) {
|
||||||
await fs.ensureDir(
|
await fs.ensureDir(path.join(file.destination, FolderPaths.ConvertedOriginalSubDir));
|
||||||
path.join(file.destination, FolderPaths.ConvertedOriginalSubDir)
|
|
||||||
);
|
|
||||||
await fs.move(
|
await fs.move(
|
||||||
file.path,
|
file.path,
|
||||||
`${path.join(
|
`${path.join(file.destination, FolderPaths.ConvertedOriginalSubDir)}/${file.filename}`
|
||||||
file.destination,
|
|
||||||
FolderPaths.ConvertedOriginalSubDir
|
|
||||||
)}/${file.filename}`
|
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
await fs.unlink(file.destination);
|
await fs.unlink(file.destination);
|
||||||
@@ -53,12 +42,7 @@ export async function ConvertHeicFiles(files: Express.Multer.File[]) {
|
|||||||
file.mimetype = "image/jpeg";
|
file.mimetype = "image/jpeg";
|
||||||
file.path = `${file.destination}/${convertedFileName}`;
|
file.path = `${file.destination}/${convertedFileName}`;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.log(
|
logger.log("error", `Error converting ${file.filename} image to JPEG from HEIC. ${JSON.stringify(error)}`);
|
||||||
"error",
|
|
||||||
`Error converting ${
|
|
||||||
file.filename
|
|
||||||
} image to JPEG from HEIC. ${JSON.stringify(error)}`
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import path, { resolve } from "path";
|
|||||||
import { logger } from "../server";
|
import { logger } from "../server";
|
||||||
|
|
||||||
dotenv.config({
|
dotenv.config({
|
||||||
path: resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`),
|
path: resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`)
|
||||||
});
|
});
|
||||||
|
|
||||||
const RootDirectory = process.env.MEDIA_PATH!.replace("~", os.homedir);
|
const RootDirectory = process.env.MEDIA_PATH!.replace("~", os.homedir);
|
||||||
@@ -21,11 +21,11 @@ export const FolderPaths = {
|
|||||||
ConvertedOriginalSubDir: "/ConvertedOriginal",
|
ConvertedOriginalSubDir: "/ConvertedOriginal",
|
||||||
StaticPath: "/static",
|
StaticPath: "/static",
|
||||||
JobsFolder,
|
JobsFolder,
|
||||||
VendorsFolder,
|
VendorsFolder
|
||||||
};
|
};
|
||||||
|
|
||||||
export const AssetPaths = {
|
export const AssetPaths = {
|
||||||
File: "/assets/file.png",
|
File: "/assets/file.png"
|
||||||
};
|
};
|
||||||
|
|
||||||
export function JobRelativeFilePath(jobid: string, filename: string) {
|
export function JobRelativeFilePath(jobid: string, filename: string) {
|
||||||
|
|||||||
@@ -4,14 +4,10 @@ import { resolve } from "path";
|
|||||||
import { logger } from "../server";
|
import { logger } from "../server";
|
||||||
|
|
||||||
dotenv.config({
|
dotenv.config({
|
||||||
path: resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`),
|
path: resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`)
|
||||||
});
|
});
|
||||||
|
|
||||||
export default function ValidateImsToken(
|
export default function ValidateImsToken(req: Request, res: Response, next: NextFunction) {
|
||||||
req: Request,
|
|
||||||
res: Response,
|
|
||||||
next: NextFunction
|
|
||||||
) {
|
|
||||||
const jobid: string = (req.body.jobid || "").trim();
|
const jobid: string = (req.body.jobid || "").trim();
|
||||||
|
|
||||||
const IMS_TOKEN: string = (process.env.IMS_TOKEN || "").trim();
|
const IMS_TOKEN: string = (process.env.IMS_TOKEN || "").trim();
|
||||||
|
|||||||
Reference in New Issue
Block a user