Browse Source

Merge pull request #6854 from gitstart/NCDBOSS-101

fix: SDK for attachment type not working in node environment
nc-fix/date-picker
աӄա 1 year ago committed by GitHub
parent
commit
d2bc8625c4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      packages/nc-gui/components/cell/attachment/utils.ts
  2. 24
      packages/nocodb-sdk/src/lib/Api.ts
  3. 11
      packages/nocodb-sdk/src/lib/CustomAPI.ts
  4. 2
      packages/nocodb-sdk/src/lib/index.ts
  5. 18
      packages/nocodb/src/controllers/attachments-secure.controller.ts
  6. 26
      packages/nocodb/src/controllers/attachments.controller.ts
  7. 68
      packages/nocodb/src/schema/swagger.json
  8. 24
      packages/nocodb/src/services/attachments.service.ts

1
packages/nc-gui/components/cell/attachment/utils.ts

@ -195,7 +195,6 @@ export const [useProvideAttachmentCell, useAttachmentCell] = useInjectionState(
}, },
{ {
files, files,
json: '{}',
}, },
) )
newAttachments.push(...data) newAttachments.push(...data)

24
packages/nocodb-sdk/src/lib/Api.ts

@ -85,6 +85,26 @@ export interface AttachmentReqType {
title?: string; title?: string;
/** Attachment URL to be uploaded via upload-by-url */ /** Attachment URL to be uploaded via upload-by-url */
url?: string; url?: string;
/** The name of the attachment file name */
fileName?: string;
}
/**
* Model for File Request
*/
export interface FileReqType {
/** The mimetype of the file */
mimetype?: string;
/** The name of the input used to upload the file */
fieldname?: string;
/** The original name of the file */
originalname?: string;
/** The size of the file */
size?: number;
/** The encoding of the file */
encoding?: string;
/** An buffer array containing the file content */
buffer?: any;
} }
/** /**
@ -10308,7 +10328,9 @@ export class Api<
*/ */
path: string; path: string;
}, },
data: AttachmentReqType, data: {
files: FileReqType[];
},
params: RequestParams = {} params: RequestParams = {}
) => ) =>
this.request<any, any>({ this.request<any, any>({

11
packages/nocodb-sdk/src/lib/CustomAPI.ts

@ -152,6 +152,17 @@ export interface DataListPayloadType {
filters?: FilterType[]; filters?: FilterType[];
} }
export interface FileType {
fieldname: string;
originalname: string;
encoding: string;
mimetype: string;
destination: string;
filename: string;
size: number;
path: string;
}
export interface DataListParamsType { export interface DataListParamsType {
limit?: string; limit?: string;
offset?: string; offset?: string;

2
packages/nocodb-sdk/src/lib/index.ts

@ -14,7 +14,7 @@ export {
isVirtualCol, isVirtualCol,
isLinksOrLTAR, isLinksOrLTAR,
} from '~/lib/UITypes'; } from '~/lib/UITypes';
export { default as CustomAPI } from '~/lib/CustomAPI'; export { default as CustomAPI, FileType } from '~/lib/CustomAPI';
export { default as TemplateGenerator } from '~/lib/TemplateGenerator'; export { default as TemplateGenerator } from '~/lib/TemplateGenerator';
export * from '~/lib/passwordHelpers'; export * from '~/lib/passwordHelpers';
export * from '~/lib/mergeSwaggerSchema'; export * from '~/lib/mergeSwaggerSchema';

18
packages/nocodb/src/controllers/attachments-secure.controller.ts

@ -15,6 +15,9 @@ import {
import hash from 'object-hash'; import hash from 'object-hash';
import moment from 'moment'; import moment from 'moment';
import { AnyFilesInterceptor } from '@nestjs/platform-express'; import { AnyFilesInterceptor } from '@nestjs/platform-express';
import { Response as ResponseType } from 'express';
import type { Request as RequestType } from 'express';
import type { AttachmentReqType, FileType } from 'nocodb-sdk';
import { GlobalGuard } from '~/guards/global/global.guard'; import { GlobalGuard } from '~/guards/global/global.guard';
import { AttachmentsService } from '~/services/attachments.service'; import { AttachmentsService } from '~/services/attachments.service';
import { PresignedUrl } from '~/models'; import { PresignedUrl } from '~/models';
@ -29,7 +32,10 @@ export class AttachmentsSecureController {
@Post(['/api/v1/db/storage/upload', '/api/v2/storage/upload']) @Post(['/api/v1/db/storage/upload', '/api/v2/storage/upload'])
@HttpCode(200) @HttpCode(200)
@UseInterceptors(UploadAllowedInterceptor, AnyFilesInterceptor()) @UseInterceptors(UploadAllowedInterceptor, AnyFilesInterceptor())
async upload(@UploadedFiles() files: Array<any>, @Request() req) { async upload(
@UploadedFiles() files: Array<FileType>,
@Request() req: RequestType & { user: { id: string } },
) {
const path = `${moment().format('YYYY/MM/DD')}/${hash(req.user.id)}`; const path = `${moment().format('YYYY/MM/DD')}/${hash(req.user.id)}`;
const attachments = await this.attachmentsService.upload({ const attachments = await this.attachmentsService.upload({
@ -44,7 +50,10 @@ export class AttachmentsSecureController {
@HttpCode(200) @HttpCode(200)
@UseInterceptors(UploadAllowedInterceptor) @UseInterceptors(UploadAllowedInterceptor)
@UseGuards(MetaApiLimiterGuard, GlobalGuard) @UseGuards(MetaApiLimiterGuard, GlobalGuard)
async uploadViaURL(@Body() body: any, @Request() req) { async uploadViaURL(
@Body() body: Array<AttachmentReqType>,
@Request() req: RequestType & { user: { id: string } },
) {
const path = `${moment().format('YYYY/MM/DD')}/${hash(req.user.id)}`; const path = `${moment().format('YYYY/MM/DD')}/${hash(req.user.id)}`;
const attachments = await this.attachmentsService.uploadViaURL({ const attachments = await this.attachmentsService.uploadViaURL({
@ -56,7 +65,10 @@ export class AttachmentsSecureController {
} }
@Get('/dltemp/:param(*)') @Get('/dltemp/:param(*)')
async fileReadv3(@Param('param') param: string, @Response() res) { async fileReadv3(
@Param('param') param: string,
@Response() res: ResponseType,
) {
try { try {
const fpath = await PresignedUrl.getPath(`dltemp/${param}`); const fpath = await PresignedUrl.getPath(`dltemp/${param}`);

26
packages/nocodb/src/controllers/attachments.controller.ts

@ -14,6 +14,8 @@ import {
UseInterceptors, UseInterceptors,
} from '@nestjs/common'; } from '@nestjs/common';
import { AnyFilesInterceptor } from '@nestjs/platform-express'; import { AnyFilesInterceptor } from '@nestjs/platform-express';
import { Request as RequestType, Response as ResponseType } from 'express';
import type { AttachmentReqType, FileType } from 'nocodb-sdk';
import { UploadAllowedInterceptor } from '~/interceptors/is-upload-allowed/is-upload-allowed.interceptor'; import { UploadAllowedInterceptor } from '~/interceptors/is-upload-allowed/is-upload-allowed.interceptor';
import { GlobalGuard } from '~/guards/global/global.guard'; import { GlobalGuard } from '~/guards/global/global.guard';
import { AttachmentsService } from '~/services/attachments.service'; import { AttachmentsService } from '~/services/attachments.service';
@ -29,13 +31,12 @@ export class AttachmentsController {
@HttpCode(200) @HttpCode(200)
@UseInterceptors(UploadAllowedInterceptor, AnyFilesInterceptor()) @UseInterceptors(UploadAllowedInterceptor, AnyFilesInterceptor())
async upload( async upload(
@UploadedFiles() files: Array<any>, @UploadedFiles() files: Array<FileType>,
@Body() body: any, @Request() req: RequestType,
@Request() req: any,
) { ) {
const attachments = await this.attachmentsService.upload({ const attachments = await this.attachmentsService.upload({
files: files, files: files,
path: req.query?.path as string, path: req.query?.path?.toString(),
}); });
return attachments; return attachments;
@ -45,7 +46,10 @@ export class AttachmentsController {
@HttpCode(200) @HttpCode(200)
@UseInterceptors(UploadAllowedInterceptor) @UseInterceptors(UploadAllowedInterceptor)
@UseGuards(MetaApiLimiterGuard, GlobalGuard) @UseGuards(MetaApiLimiterGuard, GlobalGuard)
async uploadViaURL(@Body() body: any, @Query('path') path: string) { async uploadViaURL(
@Body() body: Array<AttachmentReqType>,
@Query('path') path: string,
) {
const attachments = await this.attachmentsService.uploadViaURL({ const attachments = await this.attachmentsService.uploadViaURL({
urls: body, urls: body,
path, path,
@ -58,7 +62,10 @@ export class AttachmentsController {
// , getCacheMiddleware(), catchError(fileRead)); // , getCacheMiddleware(), catchError(fileRead));
@Get('/download/:filename(*)') @Get('/download/:filename(*)')
// This route will match any URL that starts with // This route will match any URL that starts with
async fileRead(@Param('filename') filename: string, @Response() res) { async fileRead(
@Param('filename') filename: string,
@Response() res: ResponseType,
) {
try { try {
const file = await this.attachmentsService.getFile({ const file = await this.attachmentsService.getFile({
path: path.join('nc', 'uploads', filename), path: path.join('nc', 'uploads', filename),
@ -77,7 +84,7 @@ export class AttachmentsController {
@Param('param1') param1: string, @Param('param1') param1: string,
@Param('param2') param2: string, @Param('param2') param2: string,
@Param('filename') filename: string, @Param('filename') filename: string,
@Response() res, @Response() res: ResponseType,
) { ) {
try { try {
const file = await this.attachmentsService.getFile({ const file = await this.attachmentsService.getFile({
@ -97,7 +104,10 @@ export class AttachmentsController {
} }
@Get('/dltemp/:param(*)') @Get('/dltemp/:param(*)')
async fileReadv3(@Param('param') param: string, @Response() res) { async fileReadv3(
@Param('param') param: string,
@Response() res: ResponseType,
) {
try { try {
const fpath = await PresignedUrl.getPath(`dltemp/${param}`); const fpath = await PresignedUrl.getPath(`dltemp/${param}`);

68
packages/nocodb/src/schema/swagger.json

@ -14939,15 +14939,30 @@
"content": { "content": {
"multipart/form-data": { "multipart/form-data": {
"schema": { "schema": {
"$ref": "#/components/schemas/AttachmentReq" "type":"object",
"properties":{
"files":{
"type": "array",
"required": true,
"items": {
"$ref": "#/components/schemas/FileReq"
}
}
}
}, },
"examples": { "examples": {
"Example 1": { "Example 1": {
"value": { "value": {
"files": [
{
"mimetype": "image/jpeg", "mimetype": "image/jpeg",
"path": "download/noco/jango_fett/Table1/attachment/uVbjPVQxC_SSfs8Ctx.jpg", "fieldname":"files",
"originalname": "22bc-kavypmq4869759 (1).jpg",
"encoding":"7bit",
"size": 13052, "size": 13052,
"title": "22bc-kavypmq4869759 (1).jpg" "buffer": "<Buffer 89 50 4e 47 0d 0a 1a 0a 00 00 00 0d 49 48 44 52 00 00 03 20 00 00 02 58 08 02 00 00 00 15 14 15 27 00 00 00 09 70 48 59 73 00 00 0b 13 00 00 0b 13 01 >"
}
]
} }
} }
} }
@ -16478,12 +16493,59 @@
"url": { "url": {
"type": "string", "type": "string",
"description": "Attachment URL to be uploaded via upload-by-url" "description": "Attachment URL to be uploaded via upload-by-url"
},
"fileName":{
"type": "string",
"description": "The name of the attachment file name"
} }
}, },
"x-stoplight": { "x-stoplight": {
"id": "6cr1iwhbyxncd" "id": "6cr1iwhbyxncd"
} }
}, },
"FileReq": {
"description": "Model for File Request",
"type": "object",
"x-examples": {
"Example 1": {
"mimetype": "image/jpeg",
"fieldname":"files",
"originalname": "22bc-kavypmq4869759 (1).jpg",
"encoding":"7bit",
"size": 13052,
"buffer": "<Buffer 89 50 4e 47 0d 0a 1a 0a 00 00 00 0d 49 48 44 52 00 00 03 20 00 00 02 58 08 02 00 00 00 15 14 15 27 00 00 00 09 70 48 59 73 00 00 0b 13 00 00 0b 13 01 >"
}
},
"title": "File Request Model",
"properties": {
"mimetype": {
"type": "string",
"description": "The mimetype of the file"
},
"fieldname": {
"type": "string",
"description": "The name of the input used to upload the file"
},
"originalname": {
"type": "string",
"description": "The original name of the file"
},
"size": {
"type": "number",
"description": "The size of the file"
},
"encoding": {
"type": "string",
"description": "The encoding of the file"
},
"buffer": {
"description": "An buffer array containing the file content"
}
},
"x-stoplight": {
"id": null
}
},
"Audit": { "Audit": {
"description": "Model for Audit", "description": "Model for Audit",
"examples": [ "examples": [

24
packages/nocodb/src/services/attachments.service.ts

@ -3,6 +3,7 @@ import { AppEvents } from 'nocodb-sdk';
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { nanoid } from 'nanoid'; import { nanoid } from 'nanoid';
import slash from 'slash'; import slash from 'slash';
import type { AttachmentReqType, FileType } from 'nocodb-sdk';
import { AppHooksService } from '~/services/app-hooks/app-hooks.service'; import { AppHooksService } from '~/services/app-hooks/app-hooks.service';
import NcPluginMgrv2 from '~/helpers/NcPluginMgrv2'; import NcPluginMgrv2 from '~/helpers/NcPluginMgrv2';
import Local from '~/plugins/storage/Local'; import Local from '~/plugins/storage/Local';
@ -14,11 +15,7 @@ import { utf8ify } from '~/helpers/stringHelpers';
export class AttachmentsService { export class AttachmentsService {
constructor(private readonly appHooksService: AppHooksService) {} constructor(private readonly appHooksService: AppHooksService) {}
async upload(param: { async upload(param: { path?: string; files: FileType[] }) {
path?: string;
// todo: proper type
files: unknown[];
}) {
// TODO: add getAjvValidatorMw // TODO: add getAjvValidatorMw
const filePath = this.sanitizeUrlPath( const filePath = this.sanitizeUrlPath(
param.path?.toString()?.split('/') || [''], param.path?.toString()?.split('/') || [''],
@ -28,7 +25,7 @@ export class AttachmentsService {
const storageAdapter = await NcPluginMgrv2.storageAdapter(); const storageAdapter = await NcPluginMgrv2.storageAdapter();
const attachments = await Promise.all( const attachments = await Promise.all(
param.files?.map(async (file: any) => { param.files?.map(async (file) => {
const originalName = utf8ify(file.originalname); const originalName = utf8ify(file.originalname);
const fileName = `${nanoid(18)}${path.extname(originalName)}`; const fileName = `${nanoid(18)}${path.extname(originalName)}`;
@ -91,15 +88,7 @@ export class AttachmentsService {
return attachments; return attachments;
} }
async uploadViaURL(param: { async uploadViaURL(param: { path?: string; urls: AttachmentReqType[] }) {
path?: string;
urls: {
url: string;
fileName: string;
mimetype?: string;
size?: string | number;
}[];
}) {
// TODO: add getAjvValidatorMw // TODO: add getAjvValidatorMw
const filePath = this.sanitizeUrlPath( const filePath = this.sanitizeUrlPath(
param?.path?.toString()?.split('/') || [''], param?.path?.toString()?.split('/') || [''],
@ -116,12 +105,13 @@ export class AttachmentsService {
_fileName || url.split('/').pop(), _fileName || url.split('/').pop(),
)}`; )}`;
const attachmentUrl = await (storageAdapter as any).fileCreateByUrl( const attachmentUrl: string | null =
await storageAdapter.fileCreateByUrl(
slash(path.join(destPath, fileName)), slash(path.join(destPath, fileName)),
url, url,
); );
let attachmentPath; let attachmentPath: string | undefined;
// if `attachmentUrl` is null, then it is local attachment // if `attachmentUrl` is null, then it is local attachment
if (!attachmentUrl) { if (!attachmentUrl) {

Loading…
Cancel
Save