Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/attachments/attachments.module.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { Module } from '@nestjs/common';
import { ResourcesModule } from 'omnibox-backend/resources/resources.module';
import { PermissionsModule } from 'omnibox-backend/permissions/permissions.module';
import { AttachmentsController } from 'omnibox-backend/attachments/attachments.controller';
import { AttachmentsService } from 'omnibox-backend/attachments/attachments.service';
import { AuthModule } from 'omnibox-backend/auth/auth.module';
import { MinioModule } from 'omnibox-backend/minio/minio.module';

@Module({
exports: [AttachmentsService],
providers: [AttachmentsService],
controllers: [AttachmentsController],
imports: [PermissionsModule, ResourcesModule, AuthModule],
imports: [PermissionsModule, MinioModule, AuthModule],
})
export class AttachmentsModule {}
49 changes: 39 additions & 10 deletions src/attachments/attachments.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ import {
NotFoundException,
} from '@nestjs/common';
import { Response } from 'express';
import { MinioService } from 'omnibox-backend/resources/minio/minio.service';
import { MinioService } from 'omnibox-backend/minio/minio.service';
import { PermissionsService } from 'omnibox-backend/permissions/permissions.service';
import { ResourcePermission } from 'omnibox-backend/permissions/resource-permission.enum';
import { objectStreamResponse } from 'omnibox-backend/resources/utils';
import { objectStreamResponse } from 'omnibox-backend/minio/utils';

@Injectable()
export class AttachmentsService {
Expand Down Expand Up @@ -38,12 +38,16 @@ export class AttachmentsService {
}
}

minioPath(attachmentId: string): string {
return `attachments/${attachmentId}`;
}

async checkAttachment(
namespaceId: string,
resourceId: string,
attachmentId: string,
) {
const info = await this.minioService.info(attachmentId);
const info = await this.minioService.info(this.minioPath(attachmentId));
if (
info.metadata.namespaceId === namespaceId ||
info.metadata.resourceId === resourceId
Expand All @@ -53,6 +57,27 @@ export class AttachmentsService {
throw new NotFoundException(attachmentId);
}

async uploadAttachment(
namespaceId: string,
resourceId: string,
userId: string,
filename: string,
buffer: Buffer,
mimetype: string,
) {
await this.checkPermission(
namespaceId,
resourceId,
userId,
ResourcePermission.CAN_EDIT,
);
const { id } = await this.minioService.put(filename, buffer, mimetype, {
metadata: { namespaceId, resourceId, userId },
folder: 'attachments',
});
return id;
}

async uploadAttachments(
namespaceId: string,
resourceId: string,
Expand All @@ -72,13 +97,13 @@ export class AttachmentsService {
try {
const filename: string = encodeFileName(file.originalname);
file.originalname = filename;
const { id } = await this.minioService.put(
const id = await this.uploadAttachment(
namespaceId,
resourceId,
userId,
filename,
file.buffer,
file.mimetype,
{
metadata: { namespaceId, resourceId, userId },
},
);
uploaded.push({
name: filename,
Expand Down Expand Up @@ -111,7 +136,9 @@ export class AttachmentsService {
resourceId,
attachmentId,
);
const stream = await this.minioService.getObject(attachmentId);
const stream = await this.minioService.getObject(
this.minioPath(attachmentId),
);
return objectStreamResponse({ stream, ...info }, httpResponse);
}

Expand All @@ -128,7 +155,7 @@ export class AttachmentsService {
ResourcePermission.CAN_EDIT,
);
await this.checkAttachment(namespaceId, resourceId, attachmentId);
await this.minioService.removeObject(attachmentId);
await this.minioService.removeObject(this.minioPath(attachmentId));
return { id: attachmentId, success: true };
}

Expand All @@ -137,7 +164,9 @@ export class AttachmentsService {
userId: string,
httpResponse: Response,
) {
const objectResponse = await this.minioService.get(attachmentId);
const objectResponse = await this.minioService.get(
this.minioPath(attachmentId),
);
const { namespaceId, resourceId } = objectResponse.metadata;

await this.checkPermission(
Expand Down
7 changes: 5 additions & 2 deletions src/auth/auth.service.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { JwtService } from '@nestjs/jwt';
import { JsonWebTokenError, JwtService, TokenExpiredError } from '@nestjs/jwt';
import { User } from 'omnibox-backend/user/entities/user.entity';
import { DataSource, EntityManager } from 'typeorm';
import { MailService } from 'omnibox-backend/mail/mail.service';
Expand All @@ -25,6 +25,7 @@ import { NamespaceRole } from 'omnibox-backend/namespaces/entities/namespace-mem
@Injectable()
export class AuthService {
private readonly logger = new Logger(AuthService.name);
private readonly knownErrors = [JsonWebTokenError, TokenExpiredError];

constructor(
private readonly jwtService: JwtService,
Expand Down Expand Up @@ -280,7 +281,9 @@ export class AuthService {
try {
return this.jwtService.verify(token);
} catch (error) {
this.logger.error({ error });
if (!this.knownErrors.some((cls) => error instanceof cls)) {
this.logger.error({ error });
}
throw new UnauthorizedException('Invalid or expired token.');
}
}
Expand Down
10 changes: 10 additions & 0 deletions src/minio/minio.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { Module } from '@nestjs/common';
import { MinioService } from 'omnibox-backend/minio/minio.service';

@Module({
exports: [MinioService],
providers: [MinioService],
controllers: [],
imports: [],
})
export class MinioModule {}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Test, TestingModule } from '@nestjs/testing';
import { ConfigModule } from '@nestjs/config';
import { MinioService } from 'omnibox-backend/resources/minio/minio.service';
import { MinioService } from 'omnibox-backend/minio/minio.service';

export const base64img: string =
'iVBORw0KGgoAAAANSUhEUgAAAB4AAAAkCAYAAACe0YppAAAAAXNSR0IArs4c6QAAAGxlWElmTU0AKgAAAAgABAEaAAUAAAABAAAAPgEbAAUAAAABAAAARgEoAAMAAAABAAIAAIdpAAQAAAABAAAATgAAAAAAAACQAAAAAQAAAJAAAAABAAKgAgAEAAAAAQAAAB6gAwAEAAAAAQAAACQAAAAAS/p+JAAAAAlwSFlzAAAWJQAAFiUBSVIk8AAAAzdJREFUWAmlWE1LMlEUPpotKgwDQUGoEIIgpEW0UBFBxITatGzfH+j/tGwZtO8LiihRgsCQaGeLIFoUpVJQ+fYM3cv9ODNzezube+85zz3Pc8Yzd2aMzMzMDMnRRkZGKJFIWOivry96enqy/EGOWFDQjG1sbND6+rrp9tabm5u/Io+yWXyc+XzeJ0K0urrqG+MCzsSjo6OUSqW4HJ4vSBS3yZm4Wq1SJBLhcng+iII4V3MmLpfLgTkhCuJczZk4m82G5gwTpyZwIl5aWiLcSqpdX1/Tx8eH6iIXcWKDE3GtVhN4OZ6dnVG325VrTCAOIl3MiXhhYUHLNRwO6fT0lC4uLjQ/FpxIC/TtCCWenp6msbExbS9Oqff3dzo6OtL8WJgiLcCPI5SYOxiurq687S8vL/T6+qrlhkiIDbNQYu43UyvtdDoWByfWBAUST05O0tTUlLYHnXxzcyN9x8fHci4mnFgRE2Mgcb1eFzg5mp18eXlJaDbVIBaigyyQuFgsWnvNTgbp/f29heNEqyBfYtyTmUxGxXpz9fcVwWazKaZy5ETL4PfElxjHn/lQQAejk007ODgwXZ5o87RTQb7ElUpFxXlzroMReHh4oLe3Nw0P0UFnty/x3NyclggLroMF6Pb2VkzlyIkXQZY4l8tRLKa/FaGJ0MF+hiPUNE68wLDEXEeic83bRiTBiIeGGYd4FMGZXtYPggOjw3d3d7kc0mc2IwIoot1uS4yYWMTpdJomJiZEXBu5xBqAWXBFAGZd6rW1NWb7/7tQBIoxzSJeXl42MX9ec8Vol3p8fJySyaRFtLe3R4+Pj5afc+BFYHZ2VguhmO3tbc2nEa+srGhBLD4/P2lnZ8fy+zn6/T5tbW1pYRSDogaDgfRrl7pUKsmAmNzd3Ymp03h+fm7dVthoFiWJo9Eo++bQaDScCAUIH3A4Qk0zi5LEhULBeihg8+HhoZkjdN1qtSwMXofU21ESc18BvV6Pnp+frSRhjv39fQsCUvVRKYnn5+ctsPqKYwUDHDhe8RZqmlqcRwxS7oPr5OTE3Ou85p5WanEe8eLiopUQB/5vG0tNwolGcfF43INFfvNXhJr4r/N/ctvrkhk6znEAAAAASUVORK5CYII=';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export interface PutOptions {
id?: string;
metadata?: Record<string, any>;
bucket?: string;
folder?: string;
}

export interface PutResponse extends UploadedObjectInfo {
Expand Down Expand Up @@ -57,7 +58,7 @@ export class MinioService {
});

this.bucket =
this.configService.get<string>('OBB_MINIO_BUCKET') || 'default';
this.configService.get<string>('OBB_MINIO_BUCKET') || 'omnibox';

this.minioClient
.bucketExists(this.bucket)
Expand Down Expand Up @@ -167,9 +168,10 @@ export class MinioService {
metadata = {},
bucket = this.bucket,
} = options || {};
const path: string = options?.folder ? `${options.folder}/${id}` : id;
const info = await this.minioClient.putObject(
bucket,
id,
path,
buffer,
buffer.length,
{
Expand Down
22 changes: 1 addition & 21 deletions src/resources/utils.ts → src/minio/utils.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,5 @@
import { GetResponse as ObjectResponse } from 'omnibox-backend/minio/minio.service';
import { Response } from 'express';
import { ResourcesService } from 'omnibox-backend/resources/resources.service';
import { GetResponse as ObjectResponse } from 'omnibox-backend/resources/minio/minio.service';

export async function fileResponse(
resourceId: string,
response: Response,
resourcesService: ResourcesService,
) {
const { fileStream, resource } =
await resourcesService.downloadFile(resourceId);
const encodedName = encodeURIComponent(resource.name);
response.setHeader(
'Content-Disposition',
`attachment; filename="${encodedName}"`,
);
response.setHeader(
'Content-Type',
resource.attrs?.mimetype || 'application/octet-stream',
);
fileStream.pipe(response);
}

export function objectStreamResponse(
objectResponse: ObjectResponse,
Expand Down
5 changes: 2 additions & 3 deletions src/resources/file-resources.controller.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { Response } from 'express';
import { fileResponse } from 'omnibox-backend/resources/utils';
import { FileInterceptor } from '@nestjs/platform-express';
import { ResourcesService } from 'omnibox-backend/resources/resources.service';
import {
Expand Down Expand Up @@ -116,11 +115,11 @@ export class FileResourcesController {
);
}

@Get('files/:resourceId')
@Get(':resourceId')
async downloadFile(
@Param('resourceId') resourceId: string,
@Res() res: Response,
) {
return await fileResponse(resourceId, res, this.resourcesService);
return await this.resourcesService.fileResponse(resourceId, res);
}
}
3 changes: 1 addition & 2 deletions src/resources/internal.resource.controller.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { Response } from 'express';
import { fileResponse } from 'omnibox-backend/resources/utils';
import { Controller, Get, Param, Res } from '@nestjs/common';
import { ResourcesService } from 'omnibox-backend/resources/resources.service';
import { Public } from 'omnibox-backend/auth/decorators/public.decorator';
Expand All @@ -11,6 +10,6 @@ export class InternalResourcesController {
@Public()
@Get('files/:id')
async downloadFile(@Param('id') resourceId: string, @Res() res: Response) {
return await fileResponse(resourceId, res, this.resourcesService);
return await this.resourcesService.fileResponse(resourceId, res);
}
}
7 changes: 4 additions & 3 deletions src/resources/resources.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@ import { Resource } from 'omnibox-backend/resources/resources.entity';
import { ResourcesService } from 'omnibox-backend/resources/resources.service';
import { ResourcesController } from 'omnibox-backend/resources/resources.controller';
import { Task } from 'omnibox-backend/tasks/tasks.entity';
import { MinioService } from 'omnibox-backend/resources/minio/minio.service';
import { InternalResourcesController } from 'omnibox-backend/resources/internal.resource.controller';
import { PermissionsModule } from 'omnibox-backend/permissions/permissions.module';
import { FileResourcesController } from 'omnibox-backend/resources/file-resources.controller';
import { MinioModule } from 'omnibox-backend/minio/minio.module';

@Module({
exports: [ResourcesService, MinioService],
providers: [ResourcesService, MinioService],
exports: [ResourcesService],
providers: [ResourcesService],
controllers: [
ResourcesController,
InternalResourcesController,
Expand All @@ -21,6 +21,7 @@ import { FileResourcesController } from 'omnibox-backend/resources/file-resource
TypeOrmModule.forFeature([Resource]),
TypeOrmModule.forFeature([Task]),
PermissionsModule,
MinioModule,
],
})
export class ResourcesModule {}
54 changes: 44 additions & 10 deletions src/resources/resources.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,12 @@ import {
} from '@nestjs/common';
import { Task } from 'omnibox-backend/tasks/tasks.entity';
import { User } from 'omnibox-backend/user/entities/user.entity';
import { MinioService } from 'omnibox-backend/resources/minio/minio.service';
import { MinioService } from 'omnibox-backend/minio/minio.service';
import { WizardTask } from 'omnibox-backend/resources/wizard.task.service';
import { PermissionsService } from 'omnibox-backend/permissions/permissions.service';
import { PrivateSearchResourceDto } from 'omnibox-backend/wizard/dto/agent-request.dto';
import { ResourcePermission } from 'omnibox-backend/permissions/resource-permission.enum';
import { Response } from 'express';

const TASK_PRIORITY = 5;

Expand Down Expand Up @@ -425,15 +426,26 @@ export class ResourcesService {
});
}

minioPath(resourceId: string) {
return `resources/${resourceId}`;
}

chunkPath(
namespaceId: string,
fileHash: string,
chunkNumber: string | number,
) {
return `chunks/${namespaceId}/${fileHash}/${chunkNumber}`;
}

async uploadFileChunk(
namespaceId: string,
chunk: Express.Multer.File,
chunkNumber: string,
fileHash: string,
) {
const chunkObjectName = `${namespaceId}/chunks/${fileHash}/${chunkNumber}`;
await this.minioService.putChunkObject(
chunkObjectName,
this.chunkPath(namespaceId, fileHash, chunkNumber),
chunk.buffer,
chunk.size,
);
Expand All @@ -446,7 +458,7 @@ export class ResourcesService {
) {
const chunksName = chunksNumber
.split(',')
.map((chunkNumber) => `${namespaceId}/chunks/${fileHash}/${chunkNumber}`);
.map((chunkNumber) => this.chunkPath(namespaceId, fileHash, chunkNumber));
await Promise.all(
chunksName.map((name) => this.minioService.removeObject(name)),
);
Expand Down Expand Up @@ -486,12 +498,14 @@ export class ResourcesService {

const artifactName = resource.id;

const chunksName = Array.from(
{ length: totalChunks },
(_, i) => `${namespaceId}/chunks/${fileHash}/${i}`,
const chunksName = Array.from({ length: totalChunks }, (_, i) =>
this.chunkPath(namespaceId, fileHash, i),
);

await this.minioService.composeObject(artifactName, chunksName);
await this.minioService.composeObject(
this.minioPath(artifactName),
chunksName,
);
await Promise.all(
chunksName.map((name) => this.minioService.removeObject(name)),
);
Expand Down Expand Up @@ -536,7 +550,11 @@ export class ResourcesService {

const artifactName = resource.id;

await this.minioService.putObject(artifactName, file.buffer, file.mimetype);
await this.minioService.putObject(
this.minioPath(artifactName),
file.buffer,
file.mimetype,
);

resource.attrs = { ...resource.attrs, url: artifactName };
await this.resourceRepository.save(resource);
Expand All @@ -555,7 +573,9 @@ export class ResourcesService {
}
const artifactName = resource.id;

const fileStream = await this.minioService.getObject(artifactName);
const fileStream = await this.minioService.getObject(
this.minioPath(artifactName),
);
return { fileStream, resource };
}

Expand Down Expand Up @@ -596,4 +616,18 @@ export class ResourcesService {
take: limit,
});
}

async fileResponse(resourceId: string, response: Response) {
const { fileStream, resource } = await this.downloadFile(resourceId);
const encodedName = encodeURIComponent(resource.name);
response.setHeader(
'Content-Disposition',
`attachment; filename="${encodedName}"`,
);
response.setHeader(
'Content-Type',
resource.attrs?.mimetype || 'application/octet-stream',
);
fileStream.pipe(response);
}
}
Loading