-
Notifications
You must be signed in to change notification settings - Fork 3
#118 feat: add File Lifetime #21
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,6 @@ | ||
import {IFileStorage} from './IFileStorage'; | ||
|
||
export interface IFileLocalStorage extends IFileStorage { | ||
getFilesPaths(): string[] | null; | ||
getFilesPaths(): string[] | null, | ||
getFileCreateTimeMs(fileName: string): Promise<number>, | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,6 @@ | ||
import {join} from 'path'; | ||
import {toInteger as _toInteger} from 'lodash'; | ||
import {OnModuleInit} from '@nestjs/common'; | ||
import {join} from 'path'; | ||
import {CronExpression} from '@nestjs/schedule'; | ||
import {normalizeBoolean} from '@steroidsjs/nest/infrastructure/decorators/fields/BooleanField'; | ||
import FileStorageEnum from '../enums/FileStorageEnum'; | ||
|
@@ -117,6 +117,33 @@ export class FileConfigService implements OnModuleInit, IFileModuleConfig { | |
*/ | ||
public deleteFileFromStorage: boolean; | ||
|
||
/** | ||
* Temporary file lifetime, stored in milliseconds. | ||
* The value is configured via env in seconds and will be | ||
* automatically converted to milliseconds. | ||
* Default: 10 seconds. | ||
* | ||
* Env: | ||
* - JUST_UPLOADED_TEMP_FILE_LIFETIME_S | ||
*/ | ||
public justUploadedTempFileLifetimeMs: number; | ||
|
||
/** | ||
* Unused file lifetime, stored in milliseconds. | ||
* The value is configured via env in seconds and will be | ||
* automatically converted to milliseconds. | ||
* Default: 86400 seconds (1 day). | ||
* | ||
* Env: | ||
* - JUST_UPLOADED_UNUSED_FILE_LIFETIME_S | ||
*/ | ||
public justUploadedUnusedFileLifetimeMs: number; | ||
|
||
/** | ||
* Temporary file lifetime | ||
*/ | ||
public justUploadedFileLifetimeMs: number; | ||
|
||
constructor( | ||
private custom: IFileModuleConfig, | ||
) { | ||
|
@@ -181,5 +208,11 @@ export class FileConfigService implements OnModuleInit, IFileModuleConfig { | |
}; | ||
|
||
this.deleteFileFromStorage = custom.deleteFileFromStorage; | ||
|
||
this.justUploadedTempFileLifetimeMs = custom.justUploadedTempFileLifetimeMs | ||
|| parseInt(process.env.JUST_UPLOADED_TEMP_FILE_LIFETIME_S || '10', 10) * 1000; | ||
|
||
this.justUploadedUnusedFileLifetimeMs = custom.justUploadedUnusedFileLifetimeMs | ||
|| parseInt(process.env.JUST_UPLOADED_UNUSED_FILE_LIFETIME_S || String(24 * 60 * 60), 10) * 1000; | ||
Comment on lines
+212
to
+216
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Давай дефолтные значения вынесем в константы. |
||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,8 @@ | ||
import {Readable} from 'stream'; | ||
import {join} from 'path'; | ||
import * as fs from 'fs'; | ||
import {existsSync, Stats} from 'fs'; | ||
import {stat} from 'fs/promises'; | ||
import * as md5File from 'md5-file'; | ||
import {DataMapper} from '@steroidsjs/nest/usecases/helpers/DataMapper'; | ||
import * as Sentry from '@sentry/node'; | ||
|
@@ -54,6 +56,15 @@ export class FileLocalStorage implements IFileLocalStorage { | |
return [this.rootUrl, file.folder, file.fileName].filter(Boolean).join('/'); | ||
} | ||
|
||
public async getFileCreateTimeMs(fileName: string) { | ||
const filePath = join(this.rootPath, fileName); | ||
if (!existsSync(filePath)) { | ||
throw new Error(`File ${fileName} not exist`); | ||
} | ||
const stats: Stats = await stat(filePath); | ||
return (stats.birthtime || stats.mtime).getTime(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Почему тут birthtime || mtime? |
||
} | ||
|
||
getFilesPaths(relativePath = ''): string[] | null { | ||
try { | ||
const folderPath = join(this.rootPath, relativePath); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -54,9 +54,10 @@ export class FileRepository extends CrudRepository<FileModel> implements IFileRe | |
} | ||
|
||
public async getUnusedFilesIds(config: { | ||
fileNameLike: string, | ||
ignoredTables: string[], | ||
isEmpty: boolean, | ||
fileNameLike?: string, | ||
ignoredTables?: string[], | ||
isEmpty?: boolean, | ||
Comment on lines
+57
to
+59
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Почему тут и в остальных местах ослаблены типы (параметры сделаны необязательными)? |
||
unusedFileLifetimeMs?: number, | ||
}): Promise<number[]> { | ||
// Массив объектов, где каждый объект содержит название таблицы и колонку в этой таблице, ссылающуюся на таблицу file | ||
const tablesWithFileReferenceColumn: Array<{table_name: string, col_name: string}> = await this.dbRepository.query(` | ||
|
@@ -83,7 +84,7 @@ export class FileRepository extends CrudRepository<FileModel> implements IFileRe | |
return []; | ||
} | ||
const tableFilesIds = await this.dbRepository.query(` | ||
SELECT DISTINCT "${table.col_name}" as id FROM ${table.table_name} | ||
SELECT DISTINCT "${table.col_name}" as id FROM "${table.table_name}" | ||
`); | ||
return tableFilesIds.map(item => item.id); | ||
})); | ||
|
@@ -101,6 +102,11 @@ export class FileRepository extends CrudRepository<FileModel> implements IFileRe | |
allFilesQb.andWhere('(model.fileSize = 0 OR model.fileSize IS NULL)'); | ||
} | ||
|
||
if (config.unusedFileLifetimeMs) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Давай добавим тут комментарий, где будет пояснено что мы делаем |
||
const thresholdDate = new Date(Date.now() - config.unusedFileLifetimeMs); | ||
allFilesQb.andWhere('model."createTime" < :threshold', {threshold: thresholdDate}); | ||
} | ||
|
||
return (await allFilesQb.getRawMany()) | ||
.map(file => file.model_id) | ||
.filter(fileId => !usedFilesIds.includes(fileId)); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Думаю что не совсем корректно будет возвращать true если файла, к примеру, не нашлось, или возникли проблемы с определением его лайфтайма.
Как насчет того чтобы бросать ошибку, а вместе использования ловить ошибку: