mirror of
https://github.com/docmost/docmost.git
synced 2025-11-15 07:21:10 +10:00
storage module
This commit is contained in:
23
server/.env.example
Normal file
23
server/.env.example
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
PORT=3001
|
||||||
|
DEBUG_MODE=true
|
||||||
|
NODE_ENV=production
|
||||||
|
|
||||||
|
JWT_SECRET_KEY=ba8642edbed7f6c450e46875e8c835c7e417031abe1f7b03f3e56bb7481706d8
|
||||||
|
JWT_TOKEN_EXPIRES_IN=30d
|
||||||
|
|
||||||
|
DATABASE_URL="postgresql://postgres:password@localhost:5432/dc?schema=public"
|
||||||
|
|
||||||
|
# local | s3
|
||||||
|
STORAGE_DRIVER=local
|
||||||
|
|
||||||
|
# local config
|
||||||
|
LOCAL_STORAGE_PATH=/storage
|
||||||
|
|
||||||
|
# S3 Config
|
||||||
|
AWS_S3_ACCESS_KEY_ID=
|
||||||
|
AWS_S3_SECRET_ACCESS_KEY=
|
||||||
|
AWS_S3_REGION=
|
||||||
|
AWS_S3_BUCKET=
|
||||||
|
AWS_S3_ENDPOINT=
|
||||||
|
AWS_S3_URL=
|
||||||
|
AWS_S3_USE_PATH_STYLE_ENDPOINT=false
|
||||||
@ -27,6 +27,8 @@
|
|||||||
"migration:show": "npm run typeorm migration:show"
|
"migration:show": "npm run typeorm migration:show"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@aws-sdk/client-s3": "^3.417.0",
|
||||||
|
"@aws-sdk/s3-request-presigner": "^3.418.0",
|
||||||
"@hocuspocus/server": "^2.5.0",
|
"@hocuspocus/server": "^2.5.0",
|
||||||
"@hocuspocus/transformer": "^2.5.0",
|
"@hocuspocus/transformer": "^2.5.0",
|
||||||
"@nestjs/common": "^10.0.0",
|
"@nestjs/common": "^10.0.0",
|
||||||
@ -42,6 +44,8 @@
|
|||||||
"class-transformer": "^0.5.1",
|
"class-transformer": "^0.5.1",
|
||||||
"class-validator": "^0.14.0",
|
"class-validator": "^0.14.0",
|
||||||
"fastify": "^4.22.2",
|
"fastify": "^4.22.2",
|
||||||
|
"fs-extra": "^11.1.1",
|
||||||
|
"mime-types": "^2.1.35",
|
||||||
"pg": "^8.11.3",
|
"pg": "^8.11.3",
|
||||||
"reflect-metadata": "^0.1.13",
|
"reflect-metadata": "^0.1.13",
|
||||||
"rxjs": "^7.8.1",
|
"rxjs": "^7.8.1",
|
||||||
@ -55,7 +59,9 @@
|
|||||||
"@nestjs/testing": "^10.0.0",
|
"@nestjs/testing": "^10.0.0",
|
||||||
"@types/bcrypt": "^5.0.0",
|
"@types/bcrypt": "^5.0.0",
|
||||||
"@types/express": "^4.17.17",
|
"@types/express": "^4.17.17",
|
||||||
|
"@types/fs-extra": "^11.0.2",
|
||||||
"@types/jest": "^29.5.2",
|
"@types/jest": "^29.5.2",
|
||||||
|
"@types/mime-types": "^2.1.1",
|
||||||
"@types/node": "^20.3.1",
|
"@types/node": "^20.3.1",
|
||||||
"@types/supertest": "^2.0.12",
|
"@types/supertest": "^2.0.12",
|
||||||
"@types/uuid": "^9.0.3",
|
"@types/uuid": "^9.0.3",
|
||||||
|
|||||||
7
server/src/core/attachment/attachment.module.ts
Normal file
7
server/src/core/attachment/attachment.module.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { AttachmentService } from './attachment.service';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
providers: [AttachmentService],
|
||||||
|
})
|
||||||
|
export class AttachmentModule {}
|
||||||
18
server/src/core/attachment/attachment.service.spec.ts
Normal file
18
server/src/core/attachment/attachment.service.spec.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
|
import { AttachmentService } from './attachment.service';
|
||||||
|
|
||||||
|
describe('AttachmentService', () => {
|
||||||
|
let service: AttachmentService;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
|
providers: [AttachmentService],
|
||||||
|
}).compile();
|
||||||
|
|
||||||
|
service = module.get<AttachmentService>(AttachmentService);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be defined', () => {
|
||||||
|
expect(service).toBeDefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
4
server/src/core/attachment/attachment.service.ts
Normal file
4
server/src/core/attachment/attachment.service.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class AttachmentService {}
|
||||||
@ -3,8 +3,20 @@ import { UserModule } from './user/user.module';
|
|||||||
import { AuthModule } from './auth/auth.module';
|
import { AuthModule } from './auth/auth.module';
|
||||||
import { WorkspaceModule } from './workspace/workspace.module';
|
import { WorkspaceModule } from './workspace/workspace.module';
|
||||||
import { PageModule } from './page/page.module';
|
import { PageModule } from './page/page.module';
|
||||||
|
import { StorageModule } from './storage/storage.module';
|
||||||
|
import { AttachmentModule } from './attachment/attachment.module';
|
||||||
|
import { EnvironmentModule } from '../environment/environment.module';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [UserModule, AuthModule, WorkspaceModule, PageModule],
|
imports: [
|
||||||
|
UserModule,
|
||||||
|
AuthModule,
|
||||||
|
WorkspaceModule,
|
||||||
|
PageModule,
|
||||||
|
StorageModule.forRootAsync({
|
||||||
|
imports: [EnvironmentModule],
|
||||||
|
}),
|
||||||
|
AttachmentModule,
|
||||||
|
],
|
||||||
})
|
})
|
||||||
export class CoreModule {}
|
export class CoreModule {}
|
||||||
|
|||||||
2
server/src/core/storage/constants/storage.constants.ts
Normal file
2
server/src/core/storage/constants/storage.constants.ts
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export const STORAGE_DRIVER_TOKEN = 'STORAGE_DRIVER_TOKEN';
|
||||||
|
export const STORAGE_CONFIG_TOKEN = 'STORAGE_CONFIG_TOKEN';
|
||||||
2
server/src/core/storage/drivers/index.ts
Normal file
2
server/src/core/storage/drivers/index.ts
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export { LocalDriver } from './local.driver';
|
||||||
|
export { S3Driver } from './s3.driver';
|
||||||
71
server/src/core/storage/drivers/local.driver.ts
Normal file
71
server/src/core/storage/drivers/local.driver.ts
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
import {
|
||||||
|
StorageDriver,
|
||||||
|
LocalStorageConfig,
|
||||||
|
StorageOption,
|
||||||
|
} from '../interfaces';
|
||||||
|
import { join } from 'path';
|
||||||
|
import * as fs from 'fs-extra';
|
||||||
|
|
||||||
|
export class LocalDriver implements StorageDriver {
|
||||||
|
private readonly config: LocalStorageConfig;
|
||||||
|
|
||||||
|
constructor(config: LocalStorageConfig) {
|
||||||
|
this.config = config;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _fullPath(filePath: string): string {
|
||||||
|
return join(this.config.storagePath, filePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
async upload(filePath: string, file: Buffer): Promise<void> {
|
||||||
|
try {
|
||||||
|
await fs.outputFile(this._fullPath(filePath), file);
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(`Failed to upload file: ${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async read(filePath: string): Promise<Buffer> {
|
||||||
|
try {
|
||||||
|
return await fs.readFile(this._fullPath(filePath));
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(`Failed to read file: ${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async exists(filePath: string): Promise<boolean> {
|
||||||
|
try {
|
||||||
|
return await fs.pathExists(this._fullPath(filePath));
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(`Failed to check file existence: ${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getSignedUrl(filePath: string, expireIn: number): Promise<string> {
|
||||||
|
throw new Error('Signed URLs are not supported for local storage.');
|
||||||
|
}
|
||||||
|
|
||||||
|
getUrl(filePath: string): string {
|
||||||
|
return this._fullPath(filePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
async delete(filePath: string): Promise<void> {
|
||||||
|
try {
|
||||||
|
await fs.remove(this._fullPath(filePath));
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(`Failed to delete file: ${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getDriver(): typeof fs {
|
||||||
|
return fs;
|
||||||
|
}
|
||||||
|
|
||||||
|
getDriverName(): string {
|
||||||
|
return StorageOption.LOCAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
getConfig(): Record<string, any> {
|
||||||
|
return this.config;
|
||||||
|
}
|
||||||
|
}
|
||||||
115
server/src/core/storage/drivers/s3.driver.ts
Normal file
115
server/src/core/storage/drivers/s3.driver.ts
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
import { S3StorageConfig, StorageDriver, StorageOption } from '../interfaces';
|
||||||
|
import {
|
||||||
|
DeleteObjectCommand,
|
||||||
|
GetObjectCommand,
|
||||||
|
HeadObjectCommand,
|
||||||
|
NoSuchKey,
|
||||||
|
PutObjectCommand,
|
||||||
|
S3Client,
|
||||||
|
} from '@aws-sdk/client-s3';
|
||||||
|
import { streamToBuffer } from '../storage.utils';
|
||||||
|
import { Readable } from 'stream';
|
||||||
|
import * as mime from 'mime-types';
|
||||||
|
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
|
||||||
|
|
||||||
|
export class S3Driver implements StorageDriver {
|
||||||
|
private readonly s3Client: S3Client;
|
||||||
|
private readonly config: S3StorageConfig;
|
||||||
|
|
||||||
|
constructor(config: S3StorageConfig) {
|
||||||
|
this.config = config;
|
||||||
|
this.s3Client = new S3Client(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
async upload(filePath: string, file: Buffer): Promise<void> {
|
||||||
|
try {
|
||||||
|
const contentType =
|
||||||
|
mime.contentType(filePath) || 'application/octet-stream';
|
||||||
|
|
||||||
|
const command = new PutObjectCommand({
|
||||||
|
Bucket: this.config.bucket,
|
||||||
|
Key: filePath,
|
||||||
|
Body: file,
|
||||||
|
ContentType: contentType,
|
||||||
|
// ACL: "public-read",
|
||||||
|
});
|
||||||
|
|
||||||
|
await this.s3Client.send(command);
|
||||||
|
// we can get the path from location
|
||||||
|
|
||||||
|
console.log(`File uploaded successfully: ${filePath}`);
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(`Failed to upload file: ${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async read(filePath: string): Promise<Buffer> {
|
||||||
|
try {
|
||||||
|
const command = new GetObjectCommand({
|
||||||
|
Bucket: this.config.bucket,
|
||||||
|
Key: filePath,
|
||||||
|
});
|
||||||
|
|
||||||
|
const response = await this.s3Client.send(command);
|
||||||
|
|
||||||
|
return streamToBuffer(response.Body as Readable);
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(`Failed to read file from S3: ${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async exists(filePath: string): Promise<boolean> {
|
||||||
|
try {
|
||||||
|
const command = new HeadObjectCommand({
|
||||||
|
Bucket: this.config.bucket,
|
||||||
|
Key: filePath,
|
||||||
|
});
|
||||||
|
|
||||||
|
await this.s3Client.send(command);
|
||||||
|
return true;
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof NoSuchKey) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
getUrl(filePath: string): string {
|
||||||
|
return `${this.config.endpoint}/${this.config.bucket}/${filePath}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getSignedUrl(filePath: string, expiresIn: number): Promise<string> {
|
||||||
|
const command = new GetObjectCommand({
|
||||||
|
Bucket: this.config.bucket,
|
||||||
|
Key: filePath,
|
||||||
|
});
|
||||||
|
return await getSignedUrl(this.s3Client, command, { expiresIn });
|
||||||
|
}
|
||||||
|
|
||||||
|
async delete(filePath: string): Promise<void> {
|
||||||
|
try {
|
||||||
|
const command = new DeleteObjectCommand({
|
||||||
|
Bucket: this.config.bucket,
|
||||||
|
Key: filePath,
|
||||||
|
});
|
||||||
|
|
||||||
|
await this.s3Client.send(command);
|
||||||
|
} catch (err) {
|
||||||
|
throw new Error(
|
||||||
|
`Error deleting file ${filePath} from S3. ${err.message}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getDriver(): S3Client {
|
||||||
|
return this.s3Client;
|
||||||
|
}
|
||||||
|
|
||||||
|
getDriverName(): string {
|
||||||
|
return StorageOption.S3;
|
||||||
|
}
|
||||||
|
|
||||||
|
getConfig(): Record<string, any> {
|
||||||
|
return this.config;
|
||||||
|
}
|
||||||
|
}
|
||||||
2
server/src/core/storage/interfaces/index.ts
Normal file
2
server/src/core/storage/interfaces/index.ts
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export * from './storage-driver.interface';
|
||||||
|
export * from './storage.interface';
|
||||||
@ -0,0 +1,19 @@
|
|||||||
|
export interface StorageDriver {
|
||||||
|
upload(filePath: string, file: Buffer): Promise<void>;
|
||||||
|
|
||||||
|
read(filePath: string): Promise<Buffer>;
|
||||||
|
|
||||||
|
exists(filePath: string): Promise<boolean>;
|
||||||
|
|
||||||
|
getUrl(filePath: string): string;
|
||||||
|
|
||||||
|
getSignedUrl(filePath: string, expireIn: number): Promise<string>;
|
||||||
|
|
||||||
|
delete(filePath: string): Promise<void>;
|
||||||
|
|
||||||
|
getDriver(): any;
|
||||||
|
|
||||||
|
getDriverName(): string;
|
||||||
|
|
||||||
|
getConfig(): Record<string, any>;
|
||||||
|
}
|
||||||
33
server/src/core/storage/interfaces/storage.interface.ts
Normal file
33
server/src/core/storage/interfaces/storage.interface.ts
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import { S3ClientConfig } from '@aws-sdk/client-s3';
|
||||||
|
|
||||||
|
export enum StorageOption {
|
||||||
|
LOCAL = 'local',
|
||||||
|
S3 = 's3',
|
||||||
|
}
|
||||||
|
|
||||||
|
export type StorageConfig =
|
||||||
|
| { driver: StorageOption.LOCAL; config: LocalStorageConfig }
|
||||||
|
| { driver: StorageOption.S3; config: S3StorageConfig };
|
||||||
|
|
||||||
|
export interface LocalStorageConfig {
|
||||||
|
storagePath: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface S3StorageConfig
|
||||||
|
extends Omit<S3ClientConfig, 'endpoint' | 'bucket'> {
|
||||||
|
endpoint: string; // Enforce endpoint
|
||||||
|
bucket: string; // Enforce bucket
|
||||||
|
baseUrl?: string; // Optional CDN URL for assets
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface StorageOptions {
|
||||||
|
disk: StorageConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface StorageOptionsFactory {
|
||||||
|
createStorageOptions(): Promise<StorageConfig> | StorageConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface StorageModuleOptions {
|
||||||
|
imports?: any[];
|
||||||
|
}
|
||||||
66
server/src/core/storage/providers/storage.provider.ts
Normal file
66
server/src/core/storage/providers/storage.provider.ts
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
import {
|
||||||
|
STORAGE_CONFIG_TOKEN,
|
||||||
|
STORAGE_DRIVER_TOKEN,
|
||||||
|
} from '../constants/storage.constants';
|
||||||
|
import { EnvironmentService } from '../../../environment/environment.service';
|
||||||
|
import {
|
||||||
|
LocalStorageConfig,
|
||||||
|
S3StorageConfig,
|
||||||
|
StorageConfig,
|
||||||
|
StorageDriver,
|
||||||
|
StorageOption,
|
||||||
|
} from '../interfaces';
|
||||||
|
import { LocalDriver, S3Driver } from '../drivers';
|
||||||
|
|
||||||
|
function createStorageDriver(disk: StorageConfig): StorageDriver {
|
||||||
|
switch (disk.driver) {
|
||||||
|
case StorageOption.LOCAL:
|
||||||
|
return new LocalDriver(disk.config as LocalStorageConfig);
|
||||||
|
case StorageOption.S3:
|
||||||
|
return new S3Driver(disk.config as S3StorageConfig);
|
||||||
|
default:
|
||||||
|
throw new Error(`Unknown storage driver`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const storageDriverConfigProvider = {
|
||||||
|
provide: STORAGE_CONFIG_TOKEN,
|
||||||
|
useFactory: async (environmentService: EnvironmentService) => {
|
||||||
|
const driver = environmentService.getStorageDriver();
|
||||||
|
|
||||||
|
if (driver === StorageOption.LOCAL) {
|
||||||
|
return {
|
||||||
|
driver,
|
||||||
|
config: {
|
||||||
|
storagePath:
|
||||||
|
process.cwd() + '/' + environmentService.getLocalStoragePath(),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (driver === StorageOption.S3) {
|
||||||
|
return {
|
||||||
|
driver,
|
||||||
|
config: {
|
||||||
|
region: environmentService.getAwsS3Region(),
|
||||||
|
endpoint: environmentService.getAwsS3Endpoint(),
|
||||||
|
bucket: environmentService.getAwsS3Bucket(),
|
||||||
|
credentials: {
|
||||||
|
accessKeyId: environmentService.getAwsS3AccessKeyId(),
|
||||||
|
secretAccessKey: environmentService.getAwsS3SecretAccessKey(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(`Unknown storage driver: ${driver}`);
|
||||||
|
},
|
||||||
|
|
||||||
|
inject: [EnvironmentService],
|
||||||
|
};
|
||||||
|
|
||||||
|
export const storageDriverProvider = {
|
||||||
|
provide: STORAGE_DRIVER_TOKEN,
|
||||||
|
useFactory: (config) => createStorageDriver(config),
|
||||||
|
inject: [STORAGE_CONFIG_TOKEN],
|
||||||
|
};
|
||||||
24
server/src/core/storage/storage.module.ts
Normal file
24
server/src/core/storage/storage.module.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import { DynamicModule, Global, Module } from '@nestjs/common';
|
||||||
|
import { StorageModuleOptions } from './interfaces';
|
||||||
|
import { StorageService } from './storage.service';
|
||||||
|
import {
|
||||||
|
storageDriverConfigProvider,
|
||||||
|
storageDriverProvider,
|
||||||
|
} from './providers/storage.provider';
|
||||||
|
|
||||||
|
@Global()
|
||||||
|
@Module({})
|
||||||
|
export class StorageModule {
|
||||||
|
static forRootAsync(options: StorageModuleOptions): DynamicModule {
|
||||||
|
return {
|
||||||
|
module: StorageModule,
|
||||||
|
imports: options.imports || [],
|
||||||
|
providers: [
|
||||||
|
storageDriverConfigProvider,
|
||||||
|
storageDriverProvider,
|
||||||
|
StorageService,
|
||||||
|
],
|
||||||
|
exports: [StorageService],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
18
server/src/core/storage/storage.service.spec.ts
Normal file
18
server/src/core/storage/storage.service.spec.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
|
import { StorageService } from './storage.service';
|
||||||
|
|
||||||
|
describe('StorageService', () => {
|
||||||
|
let service: StorageService;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
|
providers: [StorageService],
|
||||||
|
}).compile();
|
||||||
|
|
||||||
|
service = module.get<StorageService>(StorageService);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be defined', () => {
|
||||||
|
expect(service).toBeDefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
34
server/src/core/storage/storage.service.ts
Normal file
34
server/src/core/storage/storage.service.ts
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
|
import { STORAGE_DRIVER_TOKEN } from './constants/storage.constants';
|
||||||
|
import { StorageDriver } from './interfaces';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class StorageService {
|
||||||
|
constructor(
|
||||||
|
@Inject(STORAGE_DRIVER_TOKEN) private storageDriver: StorageDriver,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async upload(filePath: string, fileContent: Buffer | any) {
|
||||||
|
await this.storageDriver.upload(filePath, fileContent);
|
||||||
|
}
|
||||||
|
|
||||||
|
async read(filePath: string): Promise<Buffer> {
|
||||||
|
return this.storageDriver.read(filePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
async exists(filePath: string): Promise<boolean> {
|
||||||
|
return this.storageDriver.exists(filePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
async signedUrl(path: string, expireIn: number): Promise<string> {
|
||||||
|
return this.storageDriver.getSignedUrl(path, expireIn);
|
||||||
|
}
|
||||||
|
|
||||||
|
url(filePath: string): string {
|
||||||
|
return this.storageDriver.getUrl(filePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
async delete(filePath: string): Promise<void> {
|
||||||
|
await this.storageDriver.delete(filePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
10
server/src/core/storage/storage.utils.ts
Normal file
10
server/src/core/storage/storage.utils.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import { Readable } from 'stream';
|
||||||
|
|
||||||
|
export function streamToBuffer(readableStream: Readable): Promise<Buffer> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const chunks: Uint8Array[] = [];
|
||||||
|
readableStream.on('data', (chunk) => chunks.push(chunk));
|
||||||
|
readableStream.on('end', () => resolve(Buffer.concat(chunks)));
|
||||||
|
readableStream.on('error', reject);
|
||||||
|
});
|
||||||
|
}
|
||||||
@ -23,4 +23,40 @@ export class EnvironmentService {
|
|||||||
getJwtTokenExpiresIn(): string {
|
getJwtTokenExpiresIn(): string {
|
||||||
return this.configService.get<string>('JWT_TOKEN_EXPIRES_IN');
|
return this.configService.get<string>('JWT_TOKEN_EXPIRES_IN');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getStorageDriver(): string {
|
||||||
|
return this.configService.get<string>('STORAGE_DRIVER');
|
||||||
|
}
|
||||||
|
|
||||||
|
getLocalStoragePath(): string {
|
||||||
|
return this.configService.get<string>('LOCAL_STORAGE_PATH');
|
||||||
|
}
|
||||||
|
|
||||||
|
getAwsS3AccessKeyId(): string {
|
||||||
|
return this.configService.get<string>('AWS_S3_ACCESS_KEY_ID');
|
||||||
|
}
|
||||||
|
|
||||||
|
getAwsS3SecretAccessKey(): string {
|
||||||
|
return this.configService.get<string>('AWS_S3_SECRET_ACCESS_KEY');
|
||||||
|
}
|
||||||
|
|
||||||
|
getAwsS3Region(): string {
|
||||||
|
return this.configService.get<string>('AWS_S3_REGION');
|
||||||
|
}
|
||||||
|
|
||||||
|
getAwsS3Bucket(): string {
|
||||||
|
return this.configService.get<string>('AWS_S3_BUCKET');
|
||||||
|
}
|
||||||
|
|
||||||
|
getAwsS3Endpoint(): string {
|
||||||
|
return this.configService.get<string>('AWS_S3_ENDPOINT');
|
||||||
|
}
|
||||||
|
|
||||||
|
getAwsS3Url(): string {
|
||||||
|
return this.configService.get<string>('AWS_S3_URL');
|
||||||
|
}
|
||||||
|
|
||||||
|
getAwsS3UsePathStyleEndpoint(): boolean {
|
||||||
|
return this.configService.get<boolean>('AWS_S3_USE_PATH_STYLE_ENDPOINT');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user