• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

teableio / teable / 8477114564

29 Mar 2024 04:16AM CUT coverage: 82.458% (+60.6%) from 21.83%
8477114564

push

github

web-flow
feat: support increment import (#484)

* fix: should not set content-length in `axios` header

* chore: remove sdk `BaseSelect` customer filter

* feat: support increment import

* test: add inplace import e2e

fix: sqlite conflict error in import e2e

* fix: `BaseSelect` filter ignore case sensitive

* fix: gen uniq name compatible pure number string

* chore: unify import fn name

* feat: add inplace import error tips

* chore: move import types to `@teable/openapi` from `@teable/core`

* feat: reset selected import field when shift sheet when inplace import mul-workbook in excel

* chore: adjust inplace import params description in `@teable/openapi`

* chore: lint error

* feat: inplace import auth validate

* test: compatible import e2e datecompare

3937 of 4142 branches covered (95.05%)

66 of 70 new or added lines in 3 files covered. (94.29%)

26136 of 31696 relevant lines covered (82.46%)

1210.33 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

11.29
/apps/nestjs-backend/src/features/attachments/plugins/minio.ts
1
/* eslint-disable @typescript-eslint/naming-convention */
4✔
2
import type { Readable as ReadableStream } from 'node:stream';
4✔
3
import { join } from 'path';
4✔
4
import { BadRequestException, Injectable } from '@nestjs/common';
4✔
5
import { getRandomString } from '@teable/core';
4✔
6
import * as minio from 'minio';
4✔
7
import sharp from 'sharp';
4✔
8
import { IStorageConfig, StorageConfig } from '../../../configs/storage';
4✔
9
import { second } from '../../../utils/second';
4✔
10
import type StorageAdapter from './adapter';
4✔
11
import type { IPresignParams, IPresignRes, IRespHeaders } from './types';
4✔
12

4✔
13
@Injectable()
4✔
14
export class MinioStorage implements StorageAdapter {
4!
15
  minioClient: minio.Client;
×
16

×
17
  constructor(@StorageConfig() readonly config: IStorageConfig) {
×
18
    const { endPoint, port, useSSL, accessKey, secretKey } = this.config.minio;
×
19
    this.minioClient = new minio.Client({
×
20
      endPoint: endPoint!,
×
21
      port: port!,
×
22
      useSSL: useSSL!,
×
23
      accessKey: accessKey!,
×
24
      secretKey: secretKey!,
×
25
    });
×
26
  }
×
27

×
28
  async presigned(
×
29
    bucket: string,
×
30
    dir: string,
×
31
    presignedParams: IPresignParams
×
32
  ): Promise<IPresignRes> {
×
33
    const { tokenExpireIn, uploadMethod } = this.config;
×
34
    const { expiresIn, contentLength, contentType, hash } = presignedParams;
×
35
    const token = getRandomString(12);
×
36
    const filename = hash ?? token;
×
37
    const path = join(dir, filename);
×
38
    const requestHeaders = {
×
39
      'Content-Type': contentType,
×
40
      'Content-Length': contentLength,
×
41
    };
×
42
    try {
×
43
      const url = await this.minioClient.presignedUrl(
×
44
        uploadMethod,
×
45
        bucket,
×
46
        path,
×
47
        expiresIn ?? second(tokenExpireIn),
×
48
        requestHeaders
×
49
      );
×
50
      return {
×
51
        url,
×
52
        path,
×
53
        token,
×
54
        uploadMethod,
×
55
        requestHeaders,
×
56
      };
×
57
      // eslint-disable-next-line @typescript-eslint/no-explicit-any
×
58
    } catch (e: any) {
×
59
      throw new BadRequestException(`Minio presigned error${e?.message ? `: ${e.message}` : ''}`);
×
60
    }
×
61
  }
×
62

×
63
  async getObjectMeta(bucket: string, path: string, _token: string) {
×
64
    const objectName = path;
×
65
    const { metaData, size, etag: hash } = await this.minioClient.statObject(bucket, objectName);
×
66
    const mimetype = metaData['content-type'] as string;
×
67
    const url = `/${bucket}/${objectName}`;
×
68
    if (!mimetype?.startsWith('image/')) {
×
69
      return {
×
70
        hash,
×
71
        size,
×
72
        mimetype,
×
73
        url,
×
74
      };
×
75
    }
×
76
    const stream = await this.minioClient.getObject(bucket, objectName);
×
77
    const metaReader = sharp();
×
78
    const sharpReader = stream.pipe(metaReader);
×
79
    const { width, height } = await sharpReader.metadata();
×
80
    return {
×
81
      hash,
×
82
      size,
×
83
      mimetype,
×
84
      width,
×
85
      height,
×
86
      url,
×
87
    };
×
88
  }
×
89

×
90
  async getPreviewUrl(
×
91
    bucket: string,
×
92
    path: string,
×
93
    expiresIn: number = second(this.config.urlExpireIn),
×
94
    respHeaders?: IRespHeaders
×
95
  ) {
×
96
    return this.minioClient.presignedGetObject(bucket, path, expiresIn, respHeaders);
×
97
  }
×
98

×
99
  async uploadFileWidthPath(
×
100
    bucket: string,
×
101
    path: string,
×
102
    filePath: string,
×
103
    metadata: Record<string, unknown>
×
104
  ) {
×
105
    const { etag: hash } = await this.minioClient.fPutObject(bucket, path, filePath, metadata);
×
106
    return {
×
107
      hash,
×
108
      url: `/${bucket}/${path}`,
×
109
    };
×
110
  }
×
111

×
112
  async uploadFile(
×
113
    bucket: string,
×
114
    path: string,
×
115
    stream: Buffer | ReadableStream,
×
116
    metadata?: Record<string, unknown>
×
117
  ) {
×
118
    const { etag: hash } = await this.minioClient.putObject(bucket, path, stream, metadata);
×
119
    return {
×
120
      hash,
×
121
      url: `/${bucket}/${path}`,
×
122
    };
×
123
  }
×
124
}
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc