feat: Enhance ClamAV Docker entrypoint and configuration
- Updated docker-entrypoint.sh to improve ClamAV service initialization and logging. - Added checks for ClamAV and freshclam daemon status. - Optimized freshclam configuration for container usage, including logging to stdout and setting database directory. - Introduced caching mechanism for enabled file extensions in vinejs_provider.ts to reduce database queries. - Implemented a new command to list datasets needing DataCite DOI updates, with options for verbose output, count only, and IDs only. - Updated package dependencies to include p-limit and pino-pretty. - finalized ace command 'detect:missing-cross-references'
This commit is contained in:
parent
4c8cce27da
commit
6757bdb77c
10 changed files with 745 additions and 430 deletions
|
|
@ -6,17 +6,16 @@
|
|||
import type { ApplicationService } from '@adonisjs/core/types';
|
||||
import vine, { symbols, BaseLiteralType, Vine } from '@vinejs/vine';
|
||||
import type { FieldContext, FieldOptions } from '@vinejs/vine/types';
|
||||
// import type { MultipartFile, FileValidationOptions } from '@adonisjs/bodyparser/types';
|
||||
import type { MultipartFile } from '@adonisjs/core/bodyparser';
|
||||
import type { FileValidationOptions } from '@adonisjs/core/types/bodyparser';
|
||||
import { Request, RequestValidator } from '@adonisjs/core/http';
|
||||
import MimeType from '#models/mime_type';
|
||||
|
||||
|
||||
/**
|
||||
* Validation options accepted by the "file" rule
|
||||
*/
|
||||
export type FileRuleValidationOptions = Partial<FileValidationOptions> | ((field: FieldContext) => Partial<FileValidationOptions>);
|
||||
|
||||
/**
|
||||
* Extend VineJS
|
||||
*/
|
||||
|
|
@ -25,6 +24,7 @@ declare module '@vinejs/vine' {
|
|||
myfile(options?: FileRuleValidationOptions): VineMultipartFile;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extend HTTP request class
|
||||
*/
|
||||
|
|
@ -36,19 +36,54 @@ declare module '@adonisjs/core/http' {
|
|||
* Checks if the value is an instance of multipart file
|
||||
* from bodyparser.
|
||||
*/
|
||||
export function isBodyParserFile(file: MultipartFile | unknown): boolean {
|
||||
export function isBodyParserFile(file: MultipartFile | unknown): file is MultipartFile {
|
||||
return !!(file && typeof file === 'object' && 'isMultipartFile' in file);
|
||||
}
|
||||
export async function getEnabledExtensions() {
|
||||
const enabledExtensions = await MimeType.query().select('file_extension').where('enabled', true).exec();
|
||||
const extensions = enabledExtensions
|
||||
.map((extension) => {
|
||||
return extension.file_extension.split('|');
|
||||
})
|
||||
.flat();
|
||||
|
||||
return extensions;
|
||||
/**
|
||||
* Cache for enabled extensions to reduce database queries
|
||||
*/
|
||||
let extensionsCache: string[] | null = null;
|
||||
let cacheTimestamp = 0;
|
||||
const CACHE_DURATION = 5 * 60 * 1000; // 5 minutes
|
||||
|
||||
/**
|
||||
* Get enabled extensions with caching
|
||||
*/
|
||||
export async function getEnabledExtensions(): Promise<string[]> {
|
||||
const now = Date.now();
|
||||
|
||||
if (extensionsCache && now - cacheTimestamp < CACHE_DURATION) {
|
||||
return extensionsCache;
|
||||
}
|
||||
|
||||
try {
|
||||
const enabledExtensions = await MimeType.query().select('file_extension').where('enabled', true).exec();
|
||||
|
||||
const extensions = enabledExtensions
|
||||
.map((extension) => extension.file_extension.split('|'))
|
||||
.flat()
|
||||
.map((ext) => ext.toLowerCase().trim())
|
||||
.filter((ext) => ext.length > 0);
|
||||
|
||||
extensionsCache = [...new Set(extensions)]; // Remove duplicates
|
||||
cacheTimestamp = now;
|
||||
|
||||
return extensionsCache;
|
||||
} catch (error) {
|
||||
console.error('Error fetching enabled extensions:', error);
|
||||
return extensionsCache || [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear extensions cache
|
||||
*/
|
||||
export function clearExtensionsCache(): void {
|
||||
extensionsCache = null;
|
||||
cacheTimestamp = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* VineJS validation rule that validates the file to be an
|
||||
* instance of BodyParser MultipartFile class.
|
||||
|
|
@ -65,6 +100,7 @@ const isMultipartFile = vine.createRule(async (file: MultipartFile | unknown, op
|
|||
// At this point, you can use type assertion to explicitly tell TypeScript that file is of type MultipartFile
|
||||
const validatedFile = file as MultipartFile;
|
||||
const validationOptions = typeof options === 'function' ? options(field) : options;
|
||||
|
||||
/**
|
||||
* Set size when it's defined in the options and missing
|
||||
* on the file instance
|
||||
|
|
@ -72,30 +108,29 @@ const isMultipartFile = vine.createRule(async (file: MultipartFile | unknown, op
|
|||
if (validatedFile.sizeLimit === undefined && validationOptions.size) {
|
||||
validatedFile.sizeLimit = validationOptions.size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set extensions when it's defined in the options and missing
|
||||
* on the file instance
|
||||
*/
|
||||
// if (validatedFile.allowedExtensions === undefined && validationOptions.extnames) {
|
||||
// validatedFile.allowedExtensions = validationOptions.extnames;
|
||||
// }
|
||||
if (validatedFile.allowedExtensions === undefined && validationOptions.extnames !== undefined) {
|
||||
validatedFile.allowedExtensions = validationOptions.extnames; // await getEnabledExtensions();
|
||||
} else if (validatedFile.allowedExtensions === undefined && validationOptions.extnames === undefined) {
|
||||
validatedFile.allowedExtensions = await getEnabledExtensions();
|
||||
if (validatedFile.allowedExtensions === undefined) {
|
||||
if (validationOptions.extnames !== undefined) {
|
||||
validatedFile.allowedExtensions = validationOptions.extnames;
|
||||
} else {
|
||||
validatedFile.allowedExtensions = await getEnabledExtensions();
|
||||
}
|
||||
}
|
||||
/**
|
||||
* wieder löschen
|
||||
* Set extensions when it's defined in the options and missing
|
||||
* on the file instance
|
||||
*/
|
||||
// if (file.clientNameSizeLimit === undefined && validationOptions.clientNameSizeLimit) {
|
||||
// file.clientNameSizeLimit = validationOptions.clientNameSizeLimit;
|
||||
// }
|
||||
|
||||
/**
|
||||
* Validate file
|
||||
*/
|
||||
validatedFile.validate();
|
||||
try {
|
||||
validatedFile.validate();
|
||||
} catch (error) {
|
||||
field.report(`File validation failed: ${error.message}`, 'file.validation_error', field, validationOptions);
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Report errors
|
||||
*/
|
||||
|
|
@ -107,36 +142,37 @@ const isMultipartFile = vine.createRule(async (file: MultipartFile | unknown, op
|
|||
const MULTIPART_FILE: typeof symbols.SUBTYPE = symbols.SUBTYPE;
|
||||
|
||||
export class VineMultipartFile extends BaseLiteralType<MultipartFile, MultipartFile, MultipartFile> {
|
||||
|
||||
[MULTIPART_FILE]: string;
|
||||
// constructor(validationOptions?: FileRuleValidationOptions, options?: FieldOptions) {
|
||||
// super(options, [isMultipartFile(validationOptions || {})]);
|
||||
// this.validationOptions = validationOptions;
|
||||
// this.#private = true;
|
||||
// }
|
||||
|
||||
// clone(): this {
|
||||
// return new VineMultipartFile(this.validationOptions, this.cloneOptions()) as this;
|
||||
// }
|
||||
// #private;
|
||||
// constructor(validationOptions?: FileRuleValidationOptions, options?: FieldOptions, validations?: Validation<any>[]);
|
||||
// clone(): this;
|
||||
|
||||
public validationOptions;
|
||||
public validationOptions?: FileRuleValidationOptions;
|
||||
// extnames: (18) ['gpkg', 'htm', 'html', 'csv', 'txt', 'asc', 'c', 'cc', 'h', 'srt', 'tiff', 'pdf', 'png', 'zip', 'jpg', 'jpeg', 'jpe', 'xlsx']
|
||||
// size: '512mb'
|
||||
|
||||
// public constructor(validationOptions?: FileRuleValidationOptions, options?: FieldOptions, validations?: Validation<any>[]) {
|
||||
public constructor(validationOptions?: FileRuleValidationOptions, options?: FieldOptions) {
|
||||
// super(options, validations);
|
||||
super(options, [isMultipartFile(validationOptions || {})]);
|
||||
this.validationOptions = validationOptions;
|
||||
}
|
||||
|
||||
public clone(): any {
|
||||
// return new VineMultipartFile(this.validationOptions, this.cloneOptions(), this.cloneValidations());
|
||||
return new VineMultipartFile(this.validationOptions, this.cloneOptions());
|
||||
}
|
||||
|
||||
/**
|
||||
* Set maximum file size
|
||||
*/
|
||||
public maxSize(size: string | number): this {
|
||||
const newOptions = { ...this.validationOptions, size };
|
||||
return new VineMultipartFile(newOptions, this.cloneOptions()) as this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set allowed extensions
|
||||
*/
|
||||
public extensions(extnames: string[]): this {
|
||||
const newOptions = { ...this.validationOptions, extnames };
|
||||
return new VineMultipartFile(newOptions, this.cloneOptions()) as this;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
export default class VinejsProvider {
|
||||
|
|
@ -155,13 +191,8 @@ export default class VinejsProvider {
|
|||
/**
|
||||
* The container bindings have booted
|
||||
*/
|
||||
|
||||
boot(): void {
|
||||
// VineString.macro('translatedLanguage', function (this: VineString, options: Options) {
|
||||
// return this.use(translatedLanguageRule(options));
|
||||
// });
|
||||
|
||||
Vine.macro('myfile', function (this: Vine, options) {
|
||||
Vine.macro('myfile', function (this: Vine, options?: FileRuleValidationOptions) {
|
||||
return new VineMultipartFile(options);
|
||||
});
|
||||
|
||||
|
|
@ -175,6 +206,41 @@ export default class VinejsProvider {
|
|||
}
|
||||
return new RequestValidator(this.ctx).validateUsing(...args);
|
||||
});
|
||||
|
||||
// Ensure MIME validation macros are loaded
|
||||
this.loadMimeValidationMacros();
|
||||
this.loadFileScanMacros();
|
||||
this.loadFileLengthMacros();
|
||||
}
|
||||
|
||||
/**
|
||||
* Load MIME validation macros - called during boot to ensure they're available
|
||||
*/
|
||||
private async loadMimeValidationMacros(): Promise<void> {
|
||||
try {
|
||||
// Dynamically import the MIME validation rule to ensure macros are registered
|
||||
await import('#start/rules/allowed_extensions_mimetypes');
|
||||
} catch (error) {
|
||||
console.warn('Could not load MIME validation macros:', error);
|
||||
}
|
||||
}
|
||||
|
||||
private async loadFileScanMacros(): Promise<void> {
|
||||
try {
|
||||
// Dynamically import the MIME validation rule to ensure macros are registered
|
||||
await import('#start/rules/file_scan');
|
||||
} catch (error) {
|
||||
console.warn('Could not load MIME validation macros:', error);
|
||||
}
|
||||
}
|
||||
|
||||
private async loadFileLengthMacros(): Promise<void> {
|
||||
try {
|
||||
// Dynamically import the MIME validation rule to ensure macros are registered
|
||||
await import('#start/rules/file_length');
|
||||
} catch (error) {
|
||||
console.warn('Could not load MIME validation macros:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -190,5 +256,7 @@ export default class VinejsProvider {
|
|||
/**
|
||||
* Preparing to shutdown the app
|
||||
*/
|
||||
async shutdown() {}
|
||||
async shutdown() {
|
||||
clearExtensionsCache();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue