mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-26 18:42:49 +00:00
implement file module and add files relation to booking entity and populate on fetch
This commit is contained in:
@@ -8,6 +8,7 @@ import appConfig from "./config/app.config";
|
|||||||
import databaseConfig from "./config/database.config";
|
import databaseConfig from "./config/database.config";
|
||||||
|
|
||||||
import { BookingsModule } from "./modules/bookings/bookings.module";
|
import { BookingsModule } from "./modules/bookings/bookings.module";
|
||||||
|
import { FilesModule } from "./modules/files/files.module";
|
||||||
import { ConsignmentsModule } from "./modules/consignments/consignments.module";
|
import { ConsignmentsModule } from "./modules/consignments/consignments.module";
|
||||||
import { TrainsModule } from "./modules/trains/trains.module";
|
import { TrainsModule } from "./modules/trains/trains.module";
|
||||||
import { CustomersModule } from "./modules/customers/customers.module";
|
import { CustomersModule } from "./modules/customers/customers.module";
|
||||||
@@ -31,6 +32,7 @@ import { DropdownSettingsModule } from "./modules/dropdown-settings/dropdown-set
|
|||||||
SharedAuthModule,
|
SharedAuthModule,
|
||||||
IamModule.forRoot(),
|
IamModule.forRoot(),
|
||||||
BookingsModule,
|
BookingsModule,
|
||||||
|
FilesModule,
|
||||||
ConsignmentsModule,
|
ConsignmentsModule,
|
||||||
TrainsModule,
|
TrainsModule,
|
||||||
CustomersModule,
|
CustomersModule,
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ export class BookingsController {
|
|||||||
@ApiBody({
|
@ApiBody({
|
||||||
description:
|
description:
|
||||||
"Booking form data. Attach files with any field name (e.g. passport, tin_certificate). " +
|
"Booking form data. Attach files with any field name (e.g. passport, tin_certificate). " +
|
||||||
"File metadata is stored in the documents JSONB column.",
|
"Each uploaded file is saved as a row in the files table (resource=bookings).",
|
||||||
type: CreateBookingDto,
|
type: CreateBookingDto,
|
||||||
})
|
})
|
||||||
create(
|
create(
|
||||||
@@ -156,4 +156,5 @@ export class BookingsController {
|
|||||||
getConsolidationDetails(@Param("id", ParseUUIDPipe) id: string) {
|
getConsolidationDetails(@Param("id", ParseUUIDPipe) id: string) {
|
||||||
return this.bookingsService.getConsolidationDetails(id);
|
return this.bookingsService.getConsolidationDetails(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
import { Module } from "@nestjs/common";
|
import { Module } from "@nestjs/common";
|
||||||
import { TypeOrmModule } from "@nestjs/typeorm";
|
import { TypeOrmModule } from "@nestjs/typeorm";
|
||||||
|
|
||||||
import { MinioModule } from "../minio/minio.module";
|
import { FilesModule } from "../files/files.module";
|
||||||
import { BookingsController } from "./bookings.controller";
|
import { BookingsController } from "./bookings.controller";
|
||||||
import { BookingsRepository } from "./bookings.repository";
|
import { BookingsRepository } from "./bookings.repository";
|
||||||
import { BookingsService } from "./bookings.service";
|
import { BookingsService } from "./bookings.service";
|
||||||
import { Booking } from "./entities/booking.entity";
|
import { Booking } from "./entities/booking.entity";
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [TypeOrmModule.forFeature([Booking]), MinioModule],
|
imports: [TypeOrmModule.forFeature([Booking]), FilesModule],
|
||||||
controllers: [BookingsController],
|
controllers: [BookingsController],
|
||||||
providers: [BookingsService, BookingsRepository],
|
providers: [BookingsService, BookingsRepository],
|
||||||
exports: [BookingsService],
|
exports: [BookingsService],
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { InjectRepository } from "@nestjs/typeorm";
|
|||||||
import { In, IsNull, Not, Repository } from "typeorm";
|
import { In, IsNull, Not, Repository } from "typeorm";
|
||||||
|
|
||||||
import { Booking } from "./entities/booking.entity";
|
import { Booking } from "./entities/booking.entity";
|
||||||
|
import { FileRecord } from "../files/entities/file.entity";
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class BookingsRepository extends BaseRepository<Booking> {
|
export class BookingsRepository extends BaseRepository<Booking> {
|
||||||
@@ -19,6 +20,36 @@ export class BookingsRepository extends BaseRepository<Booking> {
|
|||||||
return this.repository.findOne({ where: { reference } });
|
return this.repository.findOne({ where: { reference } });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Find a booking by reference with associated files (polymorphic join). */
|
||||||
|
async findByReferenceWithFiles(reference: string): Promise<Booking | null> {
|
||||||
|
const booking = await this.repository
|
||||||
|
.createQueryBuilder("booking")
|
||||||
|
.where("booking.reference = :reference", { reference })
|
||||||
|
.leftJoinAndMapMany(
|
||||||
|
"booking.files",
|
||||||
|
FileRecord,
|
||||||
|
"file",
|
||||||
|
"file.resource_id = booking.id AND file.resource = 'bookings'"
|
||||||
|
)
|
||||||
|
.getOne();
|
||||||
|
return booking ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Find a booking by ID with associated files (polymorphic join). */
|
||||||
|
async findByIdWithFiles(id: string): Promise<Booking | null> {
|
||||||
|
const booking = await this.repository
|
||||||
|
.createQueryBuilder("booking")
|
||||||
|
.where("booking.id = :id", { id })
|
||||||
|
.leftJoinAndMapMany(
|
||||||
|
"booking.files",
|
||||||
|
FileRecord,
|
||||||
|
"file",
|
||||||
|
"file.resource_id = booking.id AND file.resource = 'bookings'"
|
||||||
|
)
|
||||||
|
.getOne();
|
||||||
|
return booking ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
/** Find a compatible consolidation partner for the given booking. */
|
/** Find a compatible consolidation partner for the given booking. */
|
||||||
async findConsolidationPartner(booking: Booking): Promise<Booking | null> {
|
async findConsolidationPartner(booking: Booking): Promise<Booking | null> {
|
||||||
return this.repository.findOne({
|
return this.repository.findOne({
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import {
|
|||||||
} from "@nestjs/common";
|
} from "@nestjs/common";
|
||||||
import { IsNull, Not } from "typeorm";
|
import { IsNull, Not } from "typeorm";
|
||||||
|
|
||||||
import { MinioService } from "../minio/minio.service";
|
import { FilesService } from "../files/files.service";
|
||||||
import { BookingsRepository } from "./bookings.repository";
|
import { BookingsRepository } from "./bookings.repository";
|
||||||
import { CreateBookingDto } from "./dto/create-booking.dto";
|
import { CreateBookingDto } from "./dto/create-booking.dto";
|
||||||
import { FilterBookingDto } from "./dto/filter-booking.dto";
|
import { FilterBookingDto } from "./dto/filter-booking.dto";
|
||||||
@@ -28,7 +28,7 @@ const HIGH_VOLUME_THRESHOLD_TONS = 500;
|
|||||||
export class BookingsService {
|
export class BookingsService {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly bookingsRepository: BookingsRepository,
|
private readonly bookingsRepository: BookingsRepository,
|
||||||
private readonly minioService: MinioService,
|
private readonly filesService: FilesService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
// ── helpers ──────────────────────────────────────────────────────────
|
// ── helpers ──────────────────────────────────────────────────────────
|
||||||
@@ -97,29 +97,6 @@ export class BookingsService {
|
|||||||
return warnings;
|
return warnings;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Build file metadata from uploaded files and upload to MinIO. */
|
|
||||||
private async buildDocuments(
|
|
||||||
bookingId: string,
|
|
||||||
files: Express.Multer.File[],
|
|
||||||
): Promise<Record<string, { originalName: string; size: number; mimeType: string; url: string }>> {
|
|
||||||
console.log('[BookingsService] buildDocuments called with bookingId:', bookingId, 'files count:', files.length);
|
|
||||||
const docs: Record<string, { originalName: string; size: number; mimeType: string; url: string }> = {};
|
|
||||||
for (const file of files) {
|
|
||||||
console.log('[BookingsService] Processing file:', file.fieldname, file.originalname, 'size:', file.size);
|
|
||||||
const objectName = `bookings/${bookingId}/${Date.now()}_${file.originalname}`;
|
|
||||||
console.log('[BookingsService] Uploading to MinIO:', objectName);
|
|
||||||
const url = await this.minioService.uploadFile(objectName, file.buffer, file.mimetype);
|
|
||||||
console.log('[BookingsService] Upload successful, URL:', url);
|
|
||||||
docs[file.fieldname] = {
|
|
||||||
originalName: file.originalname,
|
|
||||||
size: file.size,
|
|
||||||
mimeType: file.mimetype,
|
|
||||||
url,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
console.log('[BookingsService] All files processed, docs:', docs);
|
|
||||||
return docs;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── CRUD ─────────────────────────────────────────────────────────────
|
// ── CRUD ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
@@ -157,15 +134,11 @@ export class BookingsService {
|
|||||||
status: "DRAFT",
|
status: "DRAFT",
|
||||||
allowConsolidation,
|
allowConsolidation,
|
||||||
priorityScore,
|
priorityScore,
|
||||||
documents: null,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Upload files to MinIO and update booking with documents
|
|
||||||
if (files.length > 0) {
|
if (files.length > 0) {
|
||||||
try {
|
try {
|
||||||
const documents = await this.buildDocuments(booking.id, files);
|
await this.filesService.uploadMany(booking.id, "bookings", files);
|
||||||
await this.bookingsRepository.update(booking.id, { documents });
|
|
||||||
booking.documents = documents;
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('[BookingsService] File upload failed, booking still created:', err);
|
console.error('[BookingsService] File upload failed, booking still created:', err);
|
||||||
warnings.push('File upload failed — booking was created without attached files.');
|
warnings.push('File upload failed — booking was created without attached files.');
|
||||||
@@ -210,10 +183,8 @@ export class BookingsService {
|
|||||||
const overweightWarnings = this.checkOverweight(containers, direction);
|
const overweightWarnings = this.checkOverweight(containers, direction);
|
||||||
warnings.push(...overweightWarnings);
|
warnings.push(...overweightWarnings);
|
||||||
|
|
||||||
// Merge documents - upload new files to MinIO
|
|
||||||
if (files.length > 0) {
|
if (files.length > 0) {
|
||||||
const newDocs = await this.buildDocuments(id, files);
|
await this.filesService.uploadMany(id, "bookings", files);
|
||||||
updates.documents = { ...(existing.documents ?? {}), ...newDocs };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const booking = await this.bookingsRepository.update(id, updates);
|
const booking = await this.bookingsRepository.update(id, updates);
|
||||||
@@ -256,18 +227,18 @@ export class BookingsService {
|
|||||||
return { items, total };
|
return { items, total };
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Get a single booking by ID, throwing if not found. */
|
/** Get a single booking by ID with files, throwing if not found. */
|
||||||
async findById(id: string): Promise<Booking> {
|
async findById(id: string): Promise<Booking> {
|
||||||
const booking = await this.bookingsRepository.findById(id);
|
const booking = await this.bookingsRepository.findByIdWithFiles(id);
|
||||||
if (!booking) {
|
if (!booking) {
|
||||||
throw new NotFoundException(`Booking ${id} not found`);
|
throw new NotFoundException(`Booking ${id} not found`);
|
||||||
}
|
}
|
||||||
return booking;
|
return booking;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Find booking by reference. */
|
/** Find booking by reference with files. */
|
||||||
async findByReference(reference: string): Promise<Booking> {
|
async findByReference(reference: string): Promise<Booking> {
|
||||||
const booking = await this.bookingsRepository.findByReference(reference);
|
const booking = await this.bookingsRepository.findByReferenceWithFiles(reference);
|
||||||
if (!booking) {
|
if (!booking) {
|
||||||
throw new NotFoundException(`Booking with reference "${reference}" not found`);
|
throw new NotFoundException(`Booking with reference "${reference}" not found`);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { BaseEntity } from "@edr/api-common";
|
import { BaseEntity } from "@edr/api-common";
|
||||||
import { Column, Entity } from "typeorm";
|
import { Column, Entity, OneToMany } from "typeorm";
|
||||||
|
import { FileRecord } from "../../files/entities/file.entity";
|
||||||
|
|
||||||
@Entity({ schema:"freight",name: "bookings" })
|
@Entity({ schema:"freight",name: "bookings" })
|
||||||
export class Booking extends BaseEntity {
|
export class Booking extends BaseEntity {
|
||||||
@@ -138,7 +139,10 @@ export class Booking extends BaseEntity {
|
|||||||
@Column({ name: "consolidation_partner_id", type: "uuid", nullable: true })
|
@Column({ name: "consolidation_partner_id", type: "uuid", nullable: true })
|
||||||
consolidationPartnerId?: string | null;
|
consolidationPartnerId?: string | null;
|
||||||
|
|
||||||
// ── documents (JSONB) ──────────────────────────────────────────────────
|
// ── files ────────────────────────────────────────────────────────────
|
||||||
@Column({ name: "documents", type: "jsonb", nullable: true })
|
@OneToMany(() => FileRecord, (file) => file.resourceId, {
|
||||||
documents?: Record<string, { originalName: string; size: number; mimeType: string; url?: string }> | null;
|
createForeignKeyConstraints: false,
|
||||||
|
})
|
||||||
|
files?: FileRecord[];
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import { BaseEntity } from "@edr/api-common";
|
||||||
|
import { Column, Entity } from "typeorm";
|
||||||
|
|
||||||
|
@Entity({ schema: "freight", name: "files" })
|
||||||
|
export class FileRecord extends BaseEntity {
|
||||||
|
@Column({ name: "resource_id", type: "uuid" })
|
||||||
|
resourceId!: string;
|
||||||
|
|
||||||
|
@Column({ name: "resource", type: "varchar", length: 100 })
|
||||||
|
resource!: string;
|
||||||
|
|
||||||
|
@Column({ name: "code", type: "varchar", length: 100 })
|
||||||
|
code!: string;
|
||||||
|
|
||||||
|
@Column({ name: "name", type: "varchar", length: 500 })
|
||||||
|
name!: string;
|
||||||
|
|
||||||
|
@Column({ name: "url", type: "text" })
|
||||||
|
url!: string;
|
||||||
|
|
||||||
|
@Column({ name: "size", type: "integer" })
|
||||||
|
size!: number;
|
||||||
|
|
||||||
|
@Column({ name: "mime_type", type: "varchar", length: 255 })
|
||||||
|
mimeType!: string;
|
||||||
|
}
|
||||||
28
apps/edr-freight-api/src/modules/files/files.controller.ts
Normal file
28
apps/edr-freight-api/src/modules/files/files.controller.ts
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import { Controller, Get, Param, ParseUUIDPipe, Res } from "@nestjs/common";
|
||||||
|
import { ApiOperation, ApiTags } from "@nestjs/swagger";
|
||||||
|
import { Response } from "express";
|
||||||
|
|
||||||
|
import { FilesService } from "./files.service";
|
||||||
|
|
||||||
|
@ApiTags("files")
|
||||||
|
@Controller("files")
|
||||||
|
export class FilesController {
|
||||||
|
constructor(private readonly filesService: FilesService) {}
|
||||||
|
|
||||||
|
@Get(":fileId")
|
||||||
|
@ApiOperation({
|
||||||
|
summary: "Download a file by ID",
|
||||||
|
description:
|
||||||
|
"Global endpoint — streams any uploaded file directly from MinIO by its UUID. " +
|
||||||
|
"No resource context (e.g. booking ID) required.",
|
||||||
|
})
|
||||||
|
async download(
|
||||||
|
@Param("fileId", ParseUUIDPipe) fileId: string,
|
||||||
|
@Res() res: Response,
|
||||||
|
) {
|
||||||
|
const { stream, record } = await this.filesService.streamById(fileId);
|
||||||
|
res.setHeader("Content-Type", record.mimeType);
|
||||||
|
res.setHeader("Content-Disposition", `attachment; filename="${record.name}"`);
|
||||||
|
stream.pipe(res);
|
||||||
|
}
|
||||||
|
}
|
||||||
16
apps/edr-freight-api/src/modules/files/files.module.ts
Normal file
16
apps/edr-freight-api/src/modules/files/files.module.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { Module } from "@nestjs/common";
|
||||||
|
import { TypeOrmModule } from "@nestjs/typeorm";
|
||||||
|
|
||||||
|
import { MinioModule } from "../minio/minio.module";
|
||||||
|
import { FilesController } from "./files.controller";
|
||||||
|
import { FilesRepository } from "./files.repository";
|
||||||
|
import { FilesService } from "./files.service";
|
||||||
|
import { FileRecord } from "./entities/file.entity";
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [TypeOrmModule.forFeature([FileRecord]), MinioModule],
|
||||||
|
controllers: [FilesController],
|
||||||
|
providers: [FilesService, FilesRepository],
|
||||||
|
exports: [FilesService],
|
||||||
|
})
|
||||||
|
export class FilesModule {}
|
||||||
28
apps/edr-freight-api/src/modules/files/files.repository.ts
Normal file
28
apps/edr-freight-api/src/modules/files/files.repository.ts
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import { BaseRepository } from "@edr/api-common";
|
||||||
|
import { Injectable } from "@nestjs/common";
|
||||||
|
import { InjectRepository } from "@nestjs/typeorm";
|
||||||
|
import { Repository } from "typeorm";
|
||||||
|
|
||||||
|
import { FileRecord } from "./entities/file.entity";
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class FilesRepository extends BaseRepository<FileRecord> {
|
||||||
|
constructor(
|
||||||
|
@InjectRepository(FileRecord)
|
||||||
|
repository: Repository<FileRecord>,
|
||||||
|
) {
|
||||||
|
super(repository);
|
||||||
|
}
|
||||||
|
|
||||||
|
findByResource(resourceId: string, resource: string): Promise<FileRecord[]> {
|
||||||
|
return this.repository.find({ where: { resourceId, resource } });
|
||||||
|
}
|
||||||
|
|
||||||
|
findByCode(
|
||||||
|
resourceId: string,
|
||||||
|
resource: string,
|
||||||
|
code: string,
|
||||||
|
): Promise<FileRecord | null> {
|
||||||
|
return this.repository.findOne({ where: { resourceId, resource, code } });
|
||||||
|
}
|
||||||
|
}
|
||||||
84
apps/edr-freight-api/src/modules/files/files.service.ts
Normal file
84
apps/edr-freight-api/src/modules/files/files.service.ts
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
import { Injectable, NotFoundException } from "@nestjs/common";
|
||||||
|
import { Readable } from "stream";
|
||||||
|
|
||||||
|
import { MinioService } from "../minio/minio.service";
|
||||||
|
import { FilesRepository } from "./files.repository";
|
||||||
|
import { FileRecord } from "./entities/file.entity";
|
||||||
|
|
||||||
|
export interface CreateFileInput {
|
||||||
|
resourceId: string;
|
||||||
|
resource: string;
|
||||||
|
code: string;
|
||||||
|
file: Express.Multer.File;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class FilesService {
|
||||||
|
constructor(
|
||||||
|
private readonly filesRepository: FilesRepository,
|
||||||
|
private readonly minioService: MinioService,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async upload(input: CreateFileInput): Promise<FileRecord> {
|
||||||
|
const { resourceId, resource, code, file } = input;
|
||||||
|
const objectName = `${resource}/${resourceId}/${Date.now()}_${file.originalname}`;
|
||||||
|
const url = await this.minioService.uploadFile(objectName, file.buffer, file.mimetype);
|
||||||
|
|
||||||
|
return this.filesRepository.create({
|
||||||
|
resourceId,
|
||||||
|
resource,
|
||||||
|
code,
|
||||||
|
name: file.originalname,
|
||||||
|
url,
|
||||||
|
size: file.size,
|
||||||
|
mimeType: file.mimetype,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async uploadMany(
|
||||||
|
resourceId: string,
|
||||||
|
resource: string,
|
||||||
|
files: Express.Multer.File[],
|
||||||
|
): Promise<FileRecord[]> {
|
||||||
|
return Promise.all(
|
||||||
|
files.map((file) =>
|
||||||
|
this.upload({ resourceId, resource, code: file.fieldname, file }),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async findById(id: string): Promise<FileRecord> {
|
||||||
|
const record = await this.filesRepository.findById(id);
|
||||||
|
if (!record) throw new NotFoundException(`File ${id} not found`);
|
||||||
|
return record;
|
||||||
|
}
|
||||||
|
|
||||||
|
findByResource(resourceId: string, resource: string): Promise<FileRecord[]> {
|
||||||
|
return this.filesRepository.findByResource(resourceId, resource);
|
||||||
|
}
|
||||||
|
|
||||||
|
async findByCode(
|
||||||
|
resourceId: string,
|
||||||
|
resource: string,
|
||||||
|
code: string,
|
||||||
|
): Promise<FileRecord> {
|
||||||
|
const record = await this.filesRepository.findByCode(resourceId, resource, code);
|
||||||
|
if (!record)
|
||||||
|
throw new NotFoundException(
|
||||||
|
`File with code "${code}" not found for ${resource} ${resourceId}`,
|
||||||
|
);
|
||||||
|
return record;
|
||||||
|
}
|
||||||
|
|
||||||
|
async streamById(id: string): Promise<{ stream: Readable; record: FileRecord }> {
|
||||||
|
const record = await this.findById(id);
|
||||||
|
const objectName = this.extractObjectName(record.url);
|
||||||
|
const stream = await this.minioService.getFileStream(objectName);
|
||||||
|
return { stream, record };
|
||||||
|
}
|
||||||
|
|
||||||
|
private extractObjectName(url: string): string {
|
||||||
|
const parts = url.split("/");
|
||||||
|
return parts.slice(4).join("/");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import { Inject, Injectable, Logger } from "@nestjs/common";
|
import { Inject, Injectable, Logger } from "@nestjs/common";
|
||||||
import { ConfigType } from "@nestjs/config";
|
import { ConfigType } from "@nestjs/config";
|
||||||
import { Client } from "minio";
|
import { Client } from "minio";
|
||||||
|
import { Readable } from "stream";
|
||||||
import { minioConfig } from "./minio.config";
|
import { minioConfig } from "./minio.config";
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
@@ -62,4 +63,13 @@ export class MinioService {
|
|||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getFileStream(objectName: string): Promise<Readable> {
|
||||||
|
try {
|
||||||
|
return this.client.getObject(this.bucket, objectName);
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.error(`Failed to get file ${objectName}:`, error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user