Jules
Initial clean CRM deployment with prebuilt assets
d9494a5
Raw
History Blame Contribute Delete
4.16 kB
import {
Controller,
Get,
Logger,
Param,
Res,
UseFilters,
UseGuards,
} from '@nestjs/common';
import { pipeline } from 'stream/promises';
import { Response } from 'express';
import { FileFolder } from 'twenty-shared/types';
import {
FileStorageException,
FileStorageExceptionCode,
} from 'src/engine/core-modules/file-storage/interfaces/file-storage-exception';
import { PRESIGNED_URL_NO_STORE_CACHE_CONTROL } from 'src/engine/core-modules/file/interfaces/file-folder.interface';
import { setFileResponseHeaders } from 'src/engine/core-modules/file/utils/set-file-response-headers.utils';
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
import { NoPermissionGuard } from 'src/engine/guards/no-permission.guard';
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
import { FlatEntityMapsRestApiExceptionFilter } from 'src/engine/metadata-modules/flat-entity/filters/flat-entity-maps-rest-api-exception.filter';
import { FrontComponentRestApiExceptionFilter } from 'src/engine/metadata-modules/front-component/filters/front-component-rest-api-exception.filter';
import {
FrontComponentException,
FrontComponentExceptionCode,
} from 'src/engine/metadata-modules/front-component/front-component.exception';
import { FrontComponentService } from 'src/engine/metadata-modules/front-component/front-component.service';
import { PermissionsRestApiExceptionFilter } from 'src/engine/metadata-modules/permissions/utils/permissions-rest-api-exception.filter';
import { WorkspaceMigrationRunnerRestApiExceptionFilter } from 'src/engine/workspace-manager/workspace-migration/filters/workspace-migration-runner-rest-api-exception.filter';
@Controller('rest/front-components')
@UseGuards(WorkspaceAuthGuard)
@UseFilters(
PermissionsRestApiExceptionFilter,
FrontComponentRestApiExceptionFilter,
FlatEntityMapsRestApiExceptionFilter,
WorkspaceMigrationRunnerRestApiExceptionFilter,
)
export class FrontComponentController {
private readonly logger = new Logger(FrontComponentController.name);
constructor(private readonly frontComponentService: FrontComponentService) {}
@Get([':frontComponentId', ':frontComponentId/:cacheKey'])
@UseGuards(NoPermissionGuard)
async getBuiltJs(
@Res() res: Response,
@Param('frontComponentId') frontComponentId: string,
@AuthWorkspace() workspace: WorkspaceEntity,
) {
const fileResponse = await this.frontComponentService
.getBuiltComponentPresignedUrlOrStream({
frontComponentId,
workspaceId: workspace.id,
})
.catch((error) => {
if (error instanceof FrontComponentException) {
throw error;
}
if (
error instanceof FileStorageException &&
error.code === FileStorageExceptionCode.FILE_NOT_FOUND
) {
throw new FrontComponentException(
'Front component built file not found',
FrontComponentExceptionCode.FRONT_COMPONENT_NOT_FOUND,
);
}
this.logger.error(
'getBuiltComponentPresignedUrlOrStream failed unexpectedly',
{ error },
);
throw new FrontComponentException(
'Error retrieving front component built file',
FrontComponentExceptionCode.FRONT_COMPONENT_NOT_READY,
);
});
if (fileResponse.type === 'redirect') {
res.setHeader('Cache-Control', PRESIGNED_URL_NO_STORE_CACHE_CONTROL);
return res.json({ url: fileResponse.presignedUrl });
}
setFileResponseHeaders(
res,
fileResponse.mimeType,
FileFolder.BuiltFrontComponent,
);
try {
await pipeline(fileResponse.stream, res);
} catch (error) {
this.logger.error('Front component stream failed mid-transfer', {
error,
});
if (!res.headersSent) {
throw new FrontComponentException(
'Error streaming front component built file',
FrontComponentExceptionCode.FRONT_COMPONENT_NOT_READY,
);
}
res.destroy();
}
}
}