chore(repo) init AppFileTransactions module

This commit is contained in:
Francesco Spilla 2025-02-07 19:03:56 +01:00
parent 58de1b1652
commit cc6fe98e20
15 changed files with 500 additions and 11 deletions

View File

@ -6,7 +6,7 @@ export class AppController {
constructor(private readonly appService: AppService) {}
@Get()
getData() {
return this.appService.getData();
async getData() {
return await this.appService.getData();
}
}

View File

@ -1,12 +1,19 @@
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { AppConfigModule } from './modules/config/config.module';
import { AppDatabaseModule } from './modules/database/database.module';
import { AppFileTransactionsModule } from './modules/file-transactions/file-transactions.module';
import { AppLoggerModule } from './modules/logger/logger.module';
@Module({
imports: [AppConfigModule, AppLoggerModule, AppDatabaseModule],
imports: [
AppConfigModule,
AppLoggerModule,
AppDatabaseModule,
AppFileTransactionsModule,
],
controllers: [AppController],
providers: [AppService],
})

View File

@ -2,9 +2,9 @@ import { Injectable, Logger } from '@nestjs/common';
@Injectable()
export class AppService {
logger = new Logger(AppService.name);
private readonly logger = new Logger(AppService.name);
getData(): { message: string } {
async getData() {
return ({ message: 'Hello API' });
}
}

View File

@ -1,16 +1,16 @@
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { localConfig } from './local.config';
import { loggerConfig } from '../logger/logger.config';
import { databaseConfig } from '../database/database.config';
import { keyvConfig } from '../keyv/keyv.config';
import { loggerConfig } from '../logger/logger.config';
import { localConfig } from './local.config';
@Module({
imports: [
ConfigModule.forRoot({
isGlobal: true,
expandVariables: true,
load: [localConfig, loggerConfig, databaseConfig],
load: [localConfig, loggerConfig, databaseConfig, keyvConfig],
}),
],
})

View File

@ -0,0 +1,137 @@
import { Logger } from '@nestjs/common';
import fs from 'node:fs';
import { randomUUID } from 'node:crypto';
import path from 'node:path';
import { FileTransactionsService } from './file-transactions.service';
import {
copyFilesToFolderOrFail,
deleteFiles,
renameFiles,
renameFilesOrFail,
} from './file-utils';
import dayjs from 'dayjs';
export class FileTransaction {
public static readonly TO_ADD_SUFFIX = 'toAdd';
public static readonly TO_DELETE_SUFFIX = 'toDelete';
private readonly manager: FileTransactionsService;
private readonly logger: Logger;
public readonly transactionId: string;
private readonly ts: Date;
public readonly filesToAdd: { src: string; dest: string }[];
public readonly filesToDelete: string[];
constructor(
manager: FileTransactionsService,
opts: Partial<{
transactionId: string;
filesToAdd: { src: string; dest: string }[];
filesToDelete: string[];
logger: Logger;
}>
) {
this.manager = manager;
this.logger = opts.logger ?? new Logger(FileTransaction.name);
this.filesToAdd = opts.filesToAdd ?? [];
this.filesToDelete = opts.filesToDelete ?? [];
this.transactionId = opts.transactionId ?? randomUUID().split('-').pop() as string;
this.ts = new Date();
}
async addFile(src: string, dest: string) {
const destFolderName = path.dirname(dest);
const destFileName = path.basename(dest);
const destFileNameWithSuffix = `${destFileName}.${this.transactionId}.${FileTransaction.TO_ADD_SUFFIX}`;
const destFilePathWithSuffix = path.join(
destFolderName,
destFileNameWithSuffix
);
this.logger.log(
`Adding file '${destFilePathWithSuffix}' in fileTransaction '${
this.transactionId
}' (started at ${dayjs(this.ts).format('YYYY-MM-DD HH:mm:ss')})`
);
this.filesToAdd.push({ src: src, dest: destFilePathWithSuffix });
if (fs.existsSync(dest)) {
await this.deleteFile(dest);
}
await copyFilesToFolderOrFail(this.logger, destFolderName, {
srcpath: src,
name: destFileNameWithSuffix,
});
await deleteFiles(this.logger, src);
this.manager.persist(this);
}
async deleteFile(src: string) {
const destFolderName = path.dirname(src);
const destFileName = path.basename(src);
const destFileNameWithSuffix = `${destFileName}.${this.transactionId}.${FileTransaction.TO_DELETE_SUFFIX}`;
const destFilePathWithSuffix = path.join(
destFolderName,
destFileNameWithSuffix
);
this.logger.log(
`Deleting file '${destFilePathWithSuffix}' in fileTransaction '${
this.transactionId
}' (started at ${dayjs(this.ts).format('YYYY-MM-DD HH:mm:ss')})`
);
this.filesToDelete.push(destFilePathWithSuffix);
await renameFilesOrFail(this.logger, { src, dest: destFilePathWithSuffix });
this.manager.persist(this);
}
async commitTransaction() {
this.logger.log(
`Committing fileTransaction '${this.transactionId}' (started at ${dayjs(
this.ts
).format('YYYY-MM-DD HH:mm:ss')})`
);
await renameFiles(
this.logger,
...this.filesToAdd.map((fileToAdd) => ({
src: fileToAdd.dest,
dest: fileToAdd.dest.replace(
`.${this.transactionId}.${FileTransaction.TO_ADD_SUFFIX}`,
''
),
}))
);
await deleteFiles(this.logger, ...this.filesToDelete);
this.manager.unregister(this);
}
async rollbackTransaction() {
this.logger.log(
`Rollbacking fileTransaction '${this.transactionId}' (started at ${dayjs(
this.ts
).format('YYYY-MM-DD HH:mm:ss')})`
);
await renameFiles(
this.logger,
...this.filesToAdd.map((fileToMove) => ({
src: fileToMove.dest,
dest: fileToMove.src,
}))
);
await renameFiles(
this.logger,
...this.filesToDelete.map((fileToRestore) => ({
src: fileToRestore,
dest: fileToRestore.replace(
`.${this.transactionId}.${FileTransaction.TO_DELETE_SUFFIX}`,
''
),
}))
);
this.manager.unregister(this);
}
}

View File

@ -0,0 +1,11 @@
import { Global, Module } from '@nestjs/common';
import { FileTransactionsService } from './file-transactions.service';
import { KeyvModule } from '../keyv/keyv.module';
@Global()
@Module({
imports: [KeyvModule],
providers: [FileTransactionsService],
exports: [FileTransactionsService],
})
export class AppFileTransactionsModule {}

View File

@ -0,0 +1,54 @@
import { Injectable, Logger, OnModuleInit } from '@nestjs/common';
import { FileTransaction } from './file-transaction';
import { createKeyv, Keyv } from '@keyv/redis';
import { parse, stringify } from 'flatted';
import { KeyvService } from '../keyv/keyv.service';
const FILE_TRANSACTIONS_KEY = `FILE_TRANSACTIONS_KEY`;
@Injectable()
export class FileTransactionsService implements OnModuleInit {
private readonly logger = new Logger(FileTransactionsService.name);
private readonly map: Record<string, FileTransaction> = {};
private readonly keyv: Keyv;
constructor(readonly keyvService: KeyvService) {
this.keyv = this.keyvService.create({
namespace: FileTransactionsService.name,
});
}
register(logger?: Logger) {
const tx = new FileTransaction(this, { logger });
this.persist(tx);
return tx;
}
persist(tx: FileTransaction) {
this.map[tx.transactionId] = tx;
this.keyv.set(FILE_TRANSACTIONS_KEY, this.map);
}
unregister(tx: FileTransaction) {
delete this.map[tx.transactionId];
this.keyv.set(FILE_TRANSACTIONS_KEY, this.map);
}
async onModuleInit() {
this.logger.log('Checking for dangling file transactions...');
const dangling = await this.keyv.get<Set<FileTransaction>>(
FILE_TRANSACTIONS_KEY
);
if (dangling) {
for (const danglingTx of Object.values(dangling)) {
const tx = new FileTransaction(this, {
...danglingTx,
logger: this.logger,
});
await tx.rollbackTransaction();
}
}
}
}

View File

@ -0,0 +1,106 @@
import { Logger } from '@nestjs/common';
import { promises as fs } from 'node:fs';
import path from 'path';
interface File {
srcpath: string;
name: string;
}
export const copyFilesToFolder = (logger: Logger, destFolder: string, ...files: File[]) =>
_copyFilesToFolder(logger, destFolder, false, ...files);
export const copyFilesToFolderOrFail = (logger: Logger, destFolder: string, ...files: File[]) =>
_copyFilesToFolder(logger, destFolder, true, ...files);
export const deleteFolder = (logger: Logger, folder: string) => _deleteFolder(logger, false, folder);
export const deleteFolderOrFail = (logger: Logger, folder: string) => _deleteFolder(logger, true, folder);
export const deleteFiles = (logger: Logger, ...files: string[]) => _deleteFiles(logger, false, ...files);
export const deleteFilesOrFail = (logger: Logger, ...files: string[]) => _deleteFiles(logger, true, ...files);
export const renameFiles = (logger: Logger, ...files: Array<{ src: string; dest: string }>) =>
_renameFiles(logger, false, ...files);
export const renameFilesOrFail = (logger: Logger, ...files: Array<{ src: string; dest: string }>) =>
_renameFiles(logger, true, ...files);
export const normalizeWin32PathToPosix = (filepath: string): string =>
path.win32.normalize(filepath).split(path.win32.sep).join(path.posix.sep);
const _copyFilesToFolder = async (
logger: Logger,
destFolder: string,
throwsOnError = false,
...files: File[]
): Promise<void> => {
await fs.mkdir(destFolder, { recursive: true });
for (const file of files) {
try {
const destpath = path.join(destFolder, file.name);
await fs.copyFile(file.srcpath, destpath);
logger.debug(`Copying file '${file.srcpath}' to '${destpath}'`);
} catch (err: any) {
const msg = `Could not copy '${file.srcpath}' to '${destFolder}'. Inner exception: ${err.message}`;
if (throwsOnError) {
logger.error(msg);
err.message = msg;
throw err;
}
logger.warn(msg);
}
}
};
const _deleteFolder = async (logger: Logger, throwsOnError = false, folder: string): Promise<void> => {
try {
await fs.rmdir(folder, { recursive: true });
logger.debug(`Deleting folder '${folder}'`);
} catch (err: any) {
if (err.code === 'ENOENT') {
return;
}
const msg = `Could not delete '${folder}'. Inner exception: ${err.message}`;
if (throwsOnError) {
logger.error(msg);
throw err;
}
logger.warn(msg);
}
};
const _deleteFiles = async (logger: Logger, throwsOnError = false, ...files: string[]): Promise<void> => {
for (const file of files) {
try {
await fs.unlink(file);
logger.debug(`Deleting file '${file}'`);
} catch (err: any) {
if (err.code === 'ENOENT') {
return;
}
const msg = `Could not delete '${file}'. Inner exception: ${err.message}`;
if (throwsOnError) {
logger.error(msg);
throw err;
}
logger.warn(msg);
}
}
};
const _renameFiles = async (
logger: Logger,
throwsOnError = false,
...files: Array<{ src: string; dest: string }>
): Promise<void> => {
for (const file of files) {
try {
await fs.rename(file.src, file.dest);
logger.debug(`Renaming file '${file.src}' to '${file.dest}'`);
} catch (err: any) {
const msg = `Could not rename '${file.src}' to '${file.dest}'. Inner exception: ${err.message}`;
if (throwsOnError) {
logger.error(msg);
err.message = msg;
throw err;
}
logger.warn(msg);
}
}
};

View File

@ -0,0 +1,17 @@
import coerceRecordTypes from '../config/utils/coerce-record-types';
import { registerAs } from '@nestjs/config';
import { z } from 'zod';
export const keyvSchema = z.object({
redis: z.string(),
});
export type KeyvConfig = z.infer<typeof keyvSchema>;
export const keyvConfig = registerAs('keyv', () => {
const env = coerceRecordTypes(process.env);
const config: KeyvConfig = keyvSchema.strict().parse({
redis: env['REDIS_CONNECTION_STRING'],
});
return config;
});

View File

@ -0,0 +1,9 @@
import { Global, Module } from '@nestjs/common';
import { KeyvService } from './keyv.service';
@Module({
imports: [],
providers: [KeyvService],
exports: [KeyvService],
})
export class KeyvModule {}

View File

@ -0,0 +1,16 @@
import { createKeyv, KeyvRedisOptions } from '@keyv/redis';
import { Inject, Injectable } from '@nestjs/common';
import { parse, stringify } from 'flatted';
import { keyvConfig, KeyvConfig } from './keyv.config';
@Injectable()
export class KeyvService {
constructor(@Inject(keyvConfig.KEY) private readonly config: KeyvConfig) {}
create(options?: KeyvRedisOptions) {
const keyv = createKeyv(this.config.redis, options);
keyv.serialize = stringify;
keyv.deserialize = parse;
return keyv;
}
}

View File

@ -15,3 +15,14 @@ services:
memory: 4096M
reservations:
memory: 2048M
redis:
image: redis:alpine
ports:
- '${REDIS_PORT}:6379'
restart: unless-stopped
deploy:
resources:
limits:
memory: 4096M
reservations:
memory: 2048M

View File

@ -12,6 +12,7 @@
"dependencies": {
"10": "^0.0.1",
"@autotelic/pino-seq-transport": "^0.1.0",
"@keyv/redis": "^4.2.0",
"@nestjs/common": "^10.0.2",
"@nestjs/config": "^4.0.0",
"@nestjs/core": "^10.0.2",
@ -19,7 +20,10 @@
"@nestjs/typeorm": "^11.0.0",
"@nx/devkit": "20.4.1",
"axios": "^1.6.0",
"cacheable": "^1.8.8",
"connection-string": "^4.4.0",
"dayjs": "^1.11.13",
"flatted": "^3.3.2",
"lodash": "^4.17.21",
"nestjs-pino": "^4.3.0",
"patch-package": "^8.0.0",

119
pnpm-lock.yaml generated
View File

@ -11,6 +11,9 @@ dependencies:
'@autotelic/pino-seq-transport':
specifier: ^0.1.0
version: 0.1.0
'@keyv/redis':
specifier: ^4.2.0
version: 4.2.0
'@nestjs/common':
specifier: ^10.0.2
version: 10.4.15(reflect-metadata@0.1.14)(rxjs@7.8.1)
@ -32,9 +35,18 @@ dependencies:
axios:
specifier: ^1.6.0
version: 1.7.9
cacheable:
specifier: ^1.8.8
version: 1.8.8
connection-string:
specifier: ^4.4.0
version: 4.4.0
dayjs:
specifier: ^1.11.13
version: 1.11.13
flatted:
specifier: ^3.3.2
version: 3.3.2
lodash:
specifier: ^4.17.21
version: 4.17.21
@ -2124,6 +2136,21 @@ packages:
tslib: 2.8.1
dev: true
/@keyv/redis@4.2.0:
resolution: {integrity: sha512-QszmBfZZ3wOKJ5z1hn0CTLf04WN/552ITrSDYC3Yg4jT6yVdlz2fJxi5CNrnZ8NIu/Qaj7OAkbSL+pyFUXp6oA==}
engines: {node: '>= 18'}
dependencies:
cluster-key-slot: 1.1.2
keyv: 5.2.3
redis: 4.7.0
dev: false
/@keyv/serialize@1.0.2:
resolution: {integrity: sha512-+E/LyaAeuABniD/RvUezWVXKpeuvwLEA9//nE9952zBaOdBd2mQ3pPoM8cUe2X6IcMByfuSLzmYqnYshG60+HQ==}
dependencies:
buffer: 6.0.3
dev: false
/@leichtgewicht/ip-codec@2.0.5:
resolution: {integrity: sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==}
dev: true
@ -3081,6 +3108,55 @@ packages:
requiresBuild: true
optional: true
/@redis/bloom@1.2.0(@redis/client@1.6.0):
resolution: {integrity: sha512-HG2DFjYKbpNmVXsa0keLHp/3leGJz1mjh09f2RLGGLQZzSHpkmZWuwJbAvo3QcRY8p80m5+ZdXZdYOSBLlp7Cg==}
peerDependencies:
'@redis/client': ^1.0.0
dependencies:
'@redis/client': 1.6.0
dev: false
/@redis/client@1.6.0:
resolution: {integrity: sha512-aR0uffYI700OEEH4gYnitAnv3vzVGXCFvYfdpu/CJKvk4pHfLPEy/JSZyrpQ+15WhXe1yJRXLtfQ84s4mEXnPg==}
engines: {node: '>=14'}
dependencies:
cluster-key-slot: 1.1.2
generic-pool: 3.9.0
yallist: 4.0.0
dev: false
/@redis/graph@1.1.1(@redis/client@1.6.0):
resolution: {integrity: sha512-FEMTcTHZozZciLRl6GiiIB4zGm5z5F3F6a6FZCyrfxdKOhFlGkiAqlexWMBzCi4DcRoyiOsuLfW+cjlGWyExOw==}
peerDependencies:
'@redis/client': ^1.0.0
dependencies:
'@redis/client': 1.6.0
dev: false
/@redis/json@1.0.7(@redis/client@1.6.0):
resolution: {integrity: sha512-6UyXfjVaTBTJtKNG4/9Z8PSpKE6XgSyEb8iwaqDcy+uKrd/DGYHTWkUdnQDyzm727V7p21WUMhsqz5oy65kPcQ==}
peerDependencies:
'@redis/client': ^1.0.0
dependencies:
'@redis/client': 1.6.0
dev: false
/@redis/search@1.2.0(@redis/client@1.6.0):
resolution: {integrity: sha512-tYoDBbtqOVigEDMAcTGsRlMycIIjwMCgD8eR2t0NANeQmgK/lvxNAvYyb6bZDD4frHRhIHkJu2TBRvB0ERkOmw==}
peerDependencies:
'@redis/client': ^1.0.0
dependencies:
'@redis/client': 1.6.0
dev: false
/@redis/time-series@1.1.0(@redis/client@1.6.0):
resolution: {integrity: sha512-c1Q99M5ljsIuc4YdaCwfUEXsofakb9c8+Zse2qxTadu8TalLXuAESzLvFAvNVbkmSlvlzIQOLpBCmWI9wTOt+g==}
peerDependencies:
'@redis/client': ^1.0.0
dependencies:
'@redis/client': 1.6.0
dev: false
/@sinclair/typebox@0.27.8:
resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==}
@ -4455,6 +4531,13 @@ packages:
responselike: 2.0.1
dev: true
/cacheable@1.8.8:
resolution: {integrity: sha512-OE1/jlarWxROUIpd0qGBSKFLkNsotY8pt4GeiVErUYh/NUeTNrT+SBksUgllQv4m6a0W/VZsLuiHb88maavqEw==}
dependencies:
hookified: 1.7.0
keyv: 5.2.3
dev: false
/call-bind-apply-helpers@1.0.1:
resolution: {integrity: sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g==}
engines: {node: '>= 0.4'}
@ -4660,6 +4743,11 @@ packages:
semver: 5.7.2
dev: false
/cluster-key-slot@1.1.2:
resolution: {integrity: sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==}
engines: {node: '>=0.10.0'}
dev: false
/co@4.6.0:
resolution: {integrity: sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==}
engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'}
@ -5922,7 +6010,6 @@ packages:
/flatted@3.3.2:
resolution: {integrity: sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA==}
dev: true
/follow-redirects@1.15.9:
resolution: {integrity: sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==}
@ -6031,6 +6118,11 @@ packages:
/function-bind@1.1.2:
resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
/generic-pool@3.9.0:
resolution: {integrity: sha512-hymDOu5B53XvN4QT9dBmZxPX4CWhBPPLguTZ9MMFeFa/Kg0xWVfylOVNlJji/E7yTZWFd/q9GO5TxDLq156D7g==}
engines: {node: '>= 4'}
dev: false
/gensync@1.0.0-beta.2:
resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==}
engines: {node: '>=6.9.0'}
@ -6244,6 +6336,10 @@ packages:
/highlight.js@10.7.3:
resolution: {integrity: sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==}
/hookified@1.7.0:
resolution: {integrity: sha512-XQdMjqC1AyeOzfs+17cnIk7Wdfu1hh2JtcyNfBf5u9jHrT3iZUlGHxLTntFBuk5lwkqJ6l3+daeQdHK5yByHVA==}
dev: false
/hosted-git-info@7.0.2:
resolution: {integrity: sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==}
engines: {node: ^16.14.0 || >=18.0.0}
@ -7293,6 +7389,12 @@ packages:
json-buffer: 3.0.1
dev: true
/keyv@5.2.3:
resolution: {integrity: sha512-AGKecUfzrowabUv0bH1RIR5Vf7w+l4S3xtQAypKaUpTdIR1EbrAcTxHCrpo9Q+IWeUlFE2palRtgIQcgm+PQJw==}
dependencies:
'@keyv/serialize': 1.0.2
dev: false
/kind-of@6.0.3:
resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==}
engines: {node: '>=0.10.0'}
@ -8968,6 +9070,17 @@ packages:
resolve: 1.22.10
dev: true
/redis@4.7.0:
resolution: {integrity: sha512-zvmkHEAdGMn+hMRXuMBtu4Vo5P6rHQjLoHftu+lBqq8ZTA3RCVC/WzD790bkKKiNFp7d5/9PcSD19fJyyRvOdQ==}
dependencies:
'@redis/bloom': 1.2.0(@redis/client@1.6.0)
'@redis/client': 1.6.0
'@redis/graph': 1.1.1(@redis/client@1.6.0)
'@redis/json': 1.0.7(@redis/client@1.6.0)
'@redis/search': 1.2.0(@redis/client@1.6.0)
'@redis/time-series': 1.1.0(@redis/client@1.6.0)
dev: false
/reflect-metadata@0.1.14:
resolution: {integrity: sha512-ZhYeb6nRaXCfhnndflDK8qI6ZQ/YcWZCISRAWICW9XYqMUwjZM9Z0DveWX/ABN01oxSHwVxKQmxeYZSsm0jh5A==}
@ -10751,6 +10864,10 @@ packages:
resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==}
dev: true
/yallist@4.0.0:
resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==}
dev: false
/yaml@1.10.2:
resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==}
engines: {node: '>= 6'}

View File

@ -2,5 +2,5 @@ import { Injectable, Logger } from '@nestjs/common';
@Injectable()
export class <%= className %>Service {
protected readonly logger = new Logger(<%= className %>Service.name);
private readonly logger = new Logger(<%= className %>Service.name);
}