mirror of https://github.com/nocodb/nocodb
Browse Source
* feat: backblaze migrate * feat: scaleway migrate * feat: digital ocean * chore: migrate to aws-sdk v3 * fix: refactor storagePlugins * fix: handle error inside callback * fix: return data as Buffer * fix: backblaze handle * feat: acl support for storage plugins * feat: minio refactor * feat: R2 plugin * fix: lock file * fix: use buffer only for images * fix: update fileCreateByUrl * fix: upocloud case * fix: upocloud case * chore: sync dependenciespull/9139/head
Anbarasu
4 months ago
committed by
GitHub
29 changed files with 2270 additions and 2216 deletions
After Width: | Height: | Size: 10 KiB |
@ -0,0 +1,199 @@ |
|||||||
|
import fs from 'fs'; |
||||||
|
import { promisify } from 'util'; |
||||||
|
import axios from 'axios'; |
||||||
|
import { useAgent } from 'request-filtering-agent'; |
||||||
|
import { |
||||||
|
GetObjectCommand, |
||||||
|
type PutObjectCommandInput, |
||||||
|
} from '@aws-sdk/client-s3'; |
||||||
|
import { getSignedUrl } from '@aws-sdk/s3-request-presigner'; |
||||||
|
|
||||||
|
import { Upload } from '@aws-sdk/lib-storage'; |
||||||
|
import type { PutObjectRequest, S3 as S3Client } from '@aws-sdk/client-s3'; |
||||||
|
import type { IStorageAdapterV2, XcFile } from 'nc-plugin'; |
||||||
|
import type { Readable } from 'stream'; |
||||||
|
import { generateTempFilePath, waitForStreamClose } from '~/utils/pluginUtils'; |
||||||
|
|
||||||
|
interface GenerocObjectStorageInput { |
||||||
|
bucket: string; |
||||||
|
region?: string; |
||||||
|
access_key: string; |
||||||
|
access_secret: string; |
||||||
|
} |
||||||
|
|
||||||
|
export default class GenericS3 implements IStorageAdapterV2 { |
||||||
|
protected s3Client: S3Client; |
||||||
|
protected input: GenerocObjectStorageInput; |
||||||
|
|
||||||
|
constructor(input: unknown) { |
||||||
|
this.input = input as GenerocObjectStorageInput; |
||||||
|
} |
||||||
|
|
||||||
|
protected get defaultParams() { |
||||||
|
return { |
||||||
|
Bucket: this.input.bucket, |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
public async init(): Promise<any> { |
||||||
|
// Placeholder, should be initalized in child class
|
||||||
|
} |
||||||
|
|
||||||
|
public async test(): Promise<boolean> { |
||||||
|
try { |
||||||
|
const tempFile = generateTempFilePath(); |
||||||
|
const createStream = fs.createWriteStream(tempFile); |
||||||
|
await waitForStreamClose(createStream); |
||||||
|
await this.fileCreate('nc-test-file.txt', { |
||||||
|
path: tempFile, |
||||||
|
mimetype: 'text/plain', |
||||||
|
originalname: 'temp.txt', |
||||||
|
size: '', |
||||||
|
}); |
||||||
|
await promisify(fs.unlink)(tempFile); |
||||||
|
return true; |
||||||
|
} catch (e) { |
||||||
|
throw e; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public async fileRead(key: string): Promise<any> { |
||||||
|
const command = new GetObjectCommand({ |
||||||
|
Key: key, |
||||||
|
Bucket: this.input.bucket, |
||||||
|
}); |
||||||
|
|
||||||
|
const { Body } = await this.s3Client.send(command); |
||||||
|
|
||||||
|
const fileStream = Body as Readable; |
||||||
|
|
||||||
|
return new Promise((resolve, reject) => { |
||||||
|
const chunks: any[] = []; |
||||||
|
fileStream.on('data', (chunk) => { |
||||||
|
chunks.push(chunk); |
||||||
|
}); |
||||||
|
|
||||||
|
fileStream.on('end', () => { |
||||||
|
const buffer = Buffer.concat(chunks); |
||||||
|
resolve(buffer); |
||||||
|
}); |
||||||
|
|
||||||
|
fileStream.on('error', (err) => { |
||||||
|
reject(err); |
||||||
|
}); |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
async fileCreate(key: string, file: XcFile): Promise<any> { |
||||||
|
const fileStream = fs.createReadStream(file.path); |
||||||
|
|
||||||
|
return this.fileCreateByStream(key, fileStream, { |
||||||
|
mimetype: file?.mimetype, |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
async fileCreateByStream( |
||||||
|
key: string, |
||||||
|
stream: Readable, |
||||||
|
options?: { |
||||||
|
mimetype?: string; |
||||||
|
}, |
||||||
|
): Promise<void> { |
||||||
|
try { |
||||||
|
const streamError = new Promise<void>((_, reject) => { |
||||||
|
stream.on('error', (err) => { |
||||||
|
reject(err); |
||||||
|
}); |
||||||
|
}); |
||||||
|
|
||||||
|
const uploadParams = { |
||||||
|
...this.defaultParams, |
||||||
|
Body: stream, |
||||||
|
Key: key, |
||||||
|
ContentType: options?.mimetype || 'application/octet-stream', |
||||||
|
}; |
||||||
|
|
||||||
|
const upload = this.upload(uploadParams); |
||||||
|
|
||||||
|
return await Promise.race([upload, streamError]); |
||||||
|
} catch (error) { |
||||||
|
throw error; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
async fileCreateByUrl( |
||||||
|
key: string, |
||||||
|
url: string, |
||||||
|
{ fetchOptions: { buffer } = { buffer: false } }, |
||||||
|
): Promise<any> { |
||||||
|
try { |
||||||
|
const response = await axios.get(url, { |
||||||
|
httpAgent: useAgent(url, { stopPortScanningByUrlRedirection: true }), |
||||||
|
httpsAgent: useAgent(url, { stopPortScanningByUrlRedirection: true }), |
||||||
|
responseType: buffer ? 'arraybuffer' : 'stream', |
||||||
|
}); |
||||||
|
const uploadParams: PutObjectRequest = { |
||||||
|
...this.defaultParams, |
||||||
|
Body: response.data, |
||||||
|
Key: key, |
||||||
|
ContentType: response.headers['content-type'], |
||||||
|
}; |
||||||
|
|
||||||
|
const data = await this.upload(uploadParams); |
||||||
|
return { |
||||||
|
url: data, |
||||||
|
data: response.data, |
||||||
|
}; |
||||||
|
} catch (error) { |
||||||
|
throw error; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public async getSignedUrl( |
||||||
|
key, |
||||||
|
expiresInSeconds = 7200, |
||||||
|
pathParameters?: { [key: string]: string }, |
||||||
|
) { |
||||||
|
const command = new GetObjectCommand({ |
||||||
|
Key: key, |
||||||
|
Bucket: this.input.bucket, |
||||||
|
...pathParameters, |
||||||
|
}); |
||||||
|
return getSignedUrl(this.s3Client, command, { |
||||||
|
expiresIn: expiresInSeconds, |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
protected async upload(uploadParams: PutObjectCommandInput): Promise<any> { |
||||||
|
try { |
||||||
|
const upload = new Upload({ |
||||||
|
client: this.s3Client, |
||||||
|
params: { |
||||||
|
ACL: 'public-read', |
||||||
|
...uploadParams, |
||||||
|
}, |
||||||
|
}); |
||||||
|
|
||||||
|
const data = await upload.done(); |
||||||
|
|
||||||
|
return data.Location; |
||||||
|
} catch (error) { |
||||||
|
console.error('Error uploading file', error); |
||||||
|
throw error; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// TODO - implement
|
||||||
|
fileReadByStream(_key: string): Promise<Readable> { |
||||||
|
return Promise.resolve(undefined); |
||||||
|
} |
||||||
|
|
||||||
|
// TODO - implement
|
||||||
|
getDirectoryList(_path: string): Promise<string[]> { |
||||||
|
return Promise.resolve(undefined); |
||||||
|
} |
||||||
|
|
||||||
|
public async fileDelete(_path: string): Promise<any> { |
||||||
|
return Promise.resolve(undefined); |
||||||
|
} |
||||||
|
} |
@ -1,151 +1,42 @@ |
|||||||
import fs from 'fs'; |
import { S3 as S3Client } from '@aws-sdk/client-s3'; |
||||||
import { promisify } from 'util'; |
import type { S3ClientConfig } from '@aws-sdk/client-s3'; |
||||||
import AWS from 'aws-sdk'; |
import type { IStorageAdapterV2 } from 'nc-plugin'; |
||||||
import axios from 'axios'; |
import GenericS3 from '~/plugins/GenericS3/GenericS3'; |
||||||
import { useAgent } from 'request-filtering-agent'; |
|
||||||
import type { IStorageAdapterV2, XcFile } from 'nc-plugin'; |
interface LinodeObjectStorageInput { |
||||||
import type { Readable } from 'stream'; |
bucket: string; |
||||||
import { generateTempFilePath, waitForStreamClose } from '~/utils/pluginUtils'; |
region: string; |
||||||
|
access_key: string; |
||||||
export default class LinodeObjectStorage implements IStorageAdapterV2 { |
access_secret: string; |
||||||
private s3Client: AWS.S3; |
acl?: string; |
||||||
private input: any; |
} |
||||||
|
|
||||||
constructor(input: any) { |
export default class LinodeObjectStorage |
||||||
this.input = input; |
extends GenericS3 |
||||||
} |
implements IStorageAdapterV2 |
||||||
|
{ |
||||||
async fileCreate(key: string, file: XcFile): Promise<any> { |
protected input: LinodeObjectStorageInput; |
||||||
const fileStream = fs.createReadStream(file.path); |
constructor(input: unknown) { |
||||||
|
super(input as LinodeObjectStorageInput); |
||||||
return this.fileCreateByStream(key, fileStream, { |
} |
||||||
mimetype: file?.mimetype, |
|
||||||
}); |
protected get defaultParams() { |
||||||
} |
return { |
||||||
|
Bucket: this.input.bucket, |
||||||
async fileCreateByUrl(key: string, url: string): Promise<any> { |
ACL: this.input?.acl || 'public-read', |
||||||
const uploadParams: any = { |
|
||||||
ACL: 'public-read', |
|
||||||
}; |
|
||||||
return new Promise((resolve, reject) => { |
|
||||||
axios |
|
||||||
.get(url, { |
|
||||||
httpAgent: useAgent(url, { stopPortScanningByUrlRedirection: true }), |
|
||||||
httpsAgent: useAgent(url, { stopPortScanningByUrlRedirection: true }), |
|
||||||
// TODO - use stream instead of buffer
|
|
||||||
responseType: 'arraybuffer', |
|
||||||
}) |
|
||||||
.then((response) => { |
|
||||||
uploadParams.Body = response.data; |
|
||||||
uploadParams.Key = key; |
|
||||||
uploadParams.ContentType = response.headers['content-type']; |
|
||||||
|
|
||||||
// call S3 to retrieve upload file to specified bucket
|
|
||||||
this.s3Client.upload(uploadParams, (err1, data) => { |
|
||||||
if (err1) { |
|
||||||
console.log('Error', err1); |
|
||||||
reject(err1); |
|
||||||
} |
|
||||||
if (data) { |
|
||||||
resolve({ |
|
||||||
url: data.Location, |
|
||||||
data: response.data, |
|
||||||
}); |
|
||||||
} |
|
||||||
}); |
|
||||||
}) |
|
||||||
.catch((error) => { |
|
||||||
reject(error); |
|
||||||
}); |
|
||||||
}); |
|
||||||
} |
|
||||||
|
|
||||||
public async fileCreateByStream( |
|
||||||
key: string, |
|
||||||
stream: Readable, |
|
||||||
options?: { |
|
||||||
mimetype?: string; |
|
||||||
}, |
|
||||||
): Promise<any> { |
|
||||||
const uploadParams: any = { |
|
||||||
ACL: 'public-read', |
|
||||||
Body: stream, |
|
||||||
Key: key, |
|
||||||
ContentType: options?.mimetype || 'application/octet-stream', |
|
||||||
}; |
}; |
||||||
return new Promise((resolve, reject) => { |
|
||||||
// call S3 to retrieve upload file to specified bucket
|
|
||||||
this.s3Client.upload(uploadParams, (err, data) => { |
|
||||||
if (err) { |
|
||||||
console.log('Error', err); |
|
||||||
reject(err); |
|
||||||
} |
|
||||||
if (data) { |
|
||||||
resolve(data.Location); |
|
||||||
} |
|
||||||
}); |
|
||||||
}); |
|
||||||
} |
|
||||||
|
|
||||||
// TODO - implement
|
|
||||||
fileReadByStream(_key: string): Promise<Readable> { |
|
||||||
return Promise.resolve(undefined); |
|
||||||
} |
|
||||||
|
|
||||||
// TODO - implement
|
|
||||||
getDirectoryList(_path: string): Promise<string[]> { |
|
||||||
return Promise.resolve(undefined); |
|
||||||
} |
|
||||||
|
|
||||||
public async fileDelete(_path: string): Promise<any> { |
|
||||||
return Promise.resolve(undefined); |
|
||||||
} |
|
||||||
|
|
||||||
public async fileRead(key: string): Promise<any> { |
|
||||||
return new Promise((resolve, reject) => { |
|
||||||
this.s3Client.getObject({ Key: key } as any, (err, data) => { |
|
||||||
if (err) { |
|
||||||
return reject(err); |
|
||||||
} |
|
||||||
if (!data?.Body) { |
|
||||||
return reject(data); |
|
||||||
} |
|
||||||
return resolve(data.Body); |
|
||||||
}); |
|
||||||
}); |
|
||||||
} |
} |
||||||
|
|
||||||
public async init(): Promise<any> { |
public async init(): Promise<any> { |
||||||
const s3Options: any = { |
const s3Options: S3ClientConfig = { |
||||||
params: { Bucket: this.input.bucket }, |
|
||||||
region: this.input.region, |
region: this.input.region, |
||||||
|
credentials: { |
||||||
|
accessKeyId: this.input.access_key, |
||||||
|
secretAccessKey: this.input.access_secret, |
||||||
|
}, |
||||||
|
endpoint: `https://${this.input.region}.linodeobjects.com`, |
||||||
}; |
}; |
||||||
|
|
||||||
s3Options.accessKeyId = this.input.access_key; |
this.s3Client = new S3Client(s3Options); |
||||||
s3Options.secretAccessKey = this.input.access_secret; |
|
||||||
|
|
||||||
s3Options.endpoint = new AWS.Endpoint( |
|
||||||
`${this.input.region}.linodeobjects.com`, |
|
||||||
); |
|
||||||
|
|
||||||
this.s3Client = new AWS.S3(s3Options); |
|
||||||
} |
|
||||||
|
|
||||||
public async test(): Promise<boolean> { |
|
||||||
try { |
|
||||||
const tempFile = generateTempFilePath(); |
|
||||||
const createStream = fs.createWriteStream(tempFile); |
|
||||||
await waitForStreamClose(createStream); |
|
||||||
await this.fileCreate('nc-test-file.txt', { |
|
||||||
path: tempFile, |
|
||||||
mimetype: 'text/plain', |
|
||||||
originalname: 'temp.txt', |
|
||||||
size: '', |
|
||||||
}); |
|
||||||
await promisify(fs.unlink)(tempFile); |
|
||||||
return true; |
|
||||||
} catch (e) { |
|
||||||
throw e; |
|
||||||
} |
|
||||||
} |
} |
||||||
} |
} |
||||||
|
@ -1,156 +1,42 @@ |
|||||||
import fs from 'fs'; |
import { S3 as S3Client } from '@aws-sdk/client-s3'; |
||||||
import { promisify } from 'util'; |
import type { S3ClientConfig } from '@aws-sdk/client-s3'; |
||||||
import AWS from 'aws-sdk'; |
import type { IStorageAdapterV2 } from 'nc-plugin'; |
||||||
import axios from 'axios'; |
import GenericS3 from '~/plugins/GenericS3/GenericS3'; |
||||||
import { useAgent } from 'request-filtering-agent'; |
|
||||||
import type { IStorageAdapterV2, XcFile } from 'nc-plugin'; |
|
||||||
import type { Readable } from 'stream'; |
|
||||||
import { generateTempFilePath, waitForStreamClose } from '~/utils/pluginUtils'; |
|
||||||
|
|
||||||
export default class OvhCloud implements IStorageAdapterV2 { |
interface OvhCloudStorageInput { |
||||||
private s3Client: AWS.S3; |
bucket: string; |
||||||
private input: any; |
region: string; |
||||||
|
access_key: string; |
||||||
constructor(input: any) { |
access_secret: string; |
||||||
this.input = input; |
acl?: string; |
||||||
} |
} |
||||||
|
|
||||||
async fileCreate(key: string, file: XcFile): Promise<any> { |
export default class OvhCloud extends GenericS3 implements IStorageAdapterV2 { |
||||||
const fileStream = fs.createReadStream(file.path); |
protected input: OvhCloudStorageInput; |
||||||
|
|
||||||
return this.fileCreateByStream(key, fileStream, { |
|
||||||
mimetype: file?.mimetype, |
|
||||||
}); |
|
||||||
} |
|
||||||
|
|
||||||
async fileCreateByUrl(key: string, url: string): Promise<any> { |
constructor(input: unknown) { |
||||||
const uploadParams: any = { |
super(input as OvhCloudStorageInput); |
||||||
ACL: 'public-read', |
|
||||||
}; |
|
||||||
return new Promise((resolve, reject) => { |
|
||||||
axios |
|
||||||
.get(url, { |
|
||||||
httpAgent: useAgent(url, { stopPortScanningByUrlRedirection: true }), |
|
||||||
httpsAgent: useAgent(url, { stopPortScanningByUrlRedirection: true }), |
|
||||||
// TODO - use stream instead of buffer
|
|
||||||
responseType: 'arraybuffer', |
|
||||||
}) |
|
||||||
.then((response) => { |
|
||||||
uploadParams.Body = response.data; |
|
||||||
uploadParams.Key = key; |
|
||||||
uploadParams.ContentType = response.headers['content-type']; |
|
||||||
|
|
||||||
// call S3 to retrieve upload file to specified bucket
|
|
||||||
this.s3Client.upload(uploadParams, (err1, data) => { |
|
||||||
if (err1) { |
|
||||||
console.log('Error', err1); |
|
||||||
reject(err1); |
|
||||||
} |
|
||||||
if (data) { |
|
||||||
resolve({ |
|
||||||
url: data.Location, |
|
||||||
data: response.data, |
|
||||||
}); |
|
||||||
} |
|
||||||
}); |
|
||||||
}) |
|
||||||
.catch((error) => { |
|
||||||
reject(error); |
|
||||||
}); |
|
||||||
}); |
|
||||||
} |
} |
||||||
|
|
||||||
async fileCreateByStream( |
protected get defaultParams() { |
||||||
key: string, |
return { |
||||||
stream: Readable, |
Bucket: this.input.bucket, |
||||||
options?: { |
ACL: this.input?.acl || 'public-read', |
||||||
mimetype?: string; |
|
||||||
}, |
|
||||||
): Promise<void> { |
|
||||||
const uploadParams: any = { |
|
||||||
ACL: 'public-read', |
|
||||||
// ContentType: file.mimetype,
|
|
||||||
}; |
}; |
||||||
return new Promise((resolve, reject) => { |
|
||||||
// Configure the file stream and obtain the upload parameters
|
|
||||||
|
|
||||||
uploadParams.Body = stream; |
|
||||||
uploadParams.Key = key; |
|
||||||
uploadParams.ContentType = |
|
||||||
options?.mimetype || 'application/octet-stream'; |
|
||||||
|
|
||||||
// call S3 to retrieve upload file to specified bucket
|
|
||||||
this.s3Client.upload(uploadParams, (err, data) => { |
|
||||||
if (err) { |
|
||||||
console.log('Error', err); |
|
||||||
reject(err); |
|
||||||
} |
|
||||||
if (data) { |
|
||||||
resolve(data.Location); |
|
||||||
} |
|
||||||
}); |
|
||||||
}); |
|
||||||
} |
|
||||||
|
|
||||||
// TODO - implement
|
|
||||||
fileReadByStream(_key: string): Promise<Readable> { |
|
||||||
return Promise.resolve(undefined); |
|
||||||
} |
|
||||||
|
|
||||||
// TODO - implement
|
|
||||||
getDirectoryList(_path: string): Promise<string[]> { |
|
||||||
return Promise.resolve(undefined); |
|
||||||
} |
|
||||||
|
|
||||||
public async fileDelete(_path: string): Promise<any> { |
|
||||||
return Promise.resolve(undefined); |
|
||||||
} |
|
||||||
|
|
||||||
public async fileRead(key: string): Promise<any> { |
|
||||||
return new Promise((resolve, reject) => { |
|
||||||
this.s3Client.getObject({ Key: key } as any, (err, data) => { |
|
||||||
if (err) { |
|
||||||
return reject(err); |
|
||||||
} |
|
||||||
if (!data?.Body) { |
|
||||||
return reject(data); |
|
||||||
} |
|
||||||
return resolve(data.Body); |
|
||||||
}); |
|
||||||
}); |
|
||||||
} |
} |
||||||
|
|
||||||
public async init(): Promise<any> { |
public async init(): Promise<any> { |
||||||
const s3Options: any = { |
const s3Options: S3ClientConfig = { |
||||||
params: { Bucket: this.input.bucket }, |
|
||||||
region: this.input.region, |
region: this.input.region, |
||||||
|
credentials: { |
||||||
|
accessKeyId: this.input.access_key, |
||||||
|
secretAccessKey: this.input.access_secret, |
||||||
|
}, |
||||||
|
// TODO: Need to verify
|
||||||
|
// DOCS s3.<region_in_lowercase>.io.cloud.ovh.net
|
||||||
|
endpoint: `https://s3.${this.input.region}.cloud.ovh.net`, |
||||||
}; |
}; |
||||||
|
|
||||||
s3Options.accessKeyId = this.input.access_key; |
this.s3Client = new S3Client(s3Options); |
||||||
s3Options.secretAccessKey = this.input.access_secret; |
|
||||||
|
|
||||||
s3Options.endpoint = new AWS.Endpoint( |
|
||||||
`s3.${this.input.region}.cloud.ovh.net`, |
|
||||||
); |
|
||||||
|
|
||||||
this.s3Client = new AWS.S3(s3Options); |
|
||||||
} |
|
||||||
|
|
||||||
public async test(): Promise<boolean> { |
|
||||||
try { |
|
||||||
const tempFile = generateTempFilePath(); |
|
||||||
const createStream = fs.createWriteStream(tempFile); |
|
||||||
await waitForStreamClose(createStream); |
|
||||||
await this.fileCreate('nc-test-file.txt', { |
|
||||||
path: tempFile, |
|
||||||
mimetype: 'text/plain', |
|
||||||
originalname: 'temp.txt', |
|
||||||
size: '', |
|
||||||
}); |
|
||||||
await promisify(fs.unlink)(tempFile); |
|
||||||
return true; |
|
||||||
} catch (e) { |
|
||||||
throw e; |
|
||||||
} |
|
||||||
} |
} |
||||||
} |
} |
||||||
|
@ -0,0 +1,42 @@ |
|||||||
|
import { S3 as S3Client } from '@aws-sdk/client-s3'; |
||||||
|
|
||||||
|
import type { S3ClientConfigType } from '@aws-sdk/client-s3'; |
||||||
|
import type { IStorageAdapterV2 } from 'nc-plugin'; |
||||||
|
import GenericS3 from '~/plugins/GenericS3/GenericS3'; |
||||||
|
|
||||||
|
interface R2ObjectStorageInput { |
||||||
|
bucket: string; |
||||||
|
access_key: string; |
||||||
|
access_secret: string; |
||||||
|
hostname: string; |
||||||
|
region: string; |
||||||
|
} |
||||||
|
|
||||||
|
export default class R2 extends GenericS3 implements IStorageAdapterV2 { |
||||||
|
protected input: R2ObjectStorageInput; |
||||||
|
|
||||||
|
constructor(input: unknown) { |
||||||
|
super(input as R2ObjectStorageInput); |
||||||
|
} |
||||||
|
|
||||||
|
protected get defaultParams() { |
||||||
|
return { |
||||||
|
Bucket: this.input.bucket, |
||||||
|
// R2 does not support ACL
|
||||||
|
ACL: 'private', |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
public async init(): Promise<any> { |
||||||
|
const s3Options: S3ClientConfigType = { |
||||||
|
region: 'auto', |
||||||
|
endpoint: this.input.hostname, |
||||||
|
credentials: { |
||||||
|
accessKeyId: this.input.access_key, |
||||||
|
secretAccessKey: this.input.access_secret, |
||||||
|
}, |
||||||
|
}; |
||||||
|
|
||||||
|
this.s3Client = new S3Client(s3Options); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,18 @@ |
|||||||
|
import { XcStoragePlugin } from 'nc-plugin'; |
||||||
|
import R2 from './R2'; |
||||||
|
import type { IStorageAdapterV2 } from 'nc-plugin'; |
||||||
|
|
||||||
|
class R2Plugin extends XcStoragePlugin { |
||||||
|
private static storageAdapter: R2; |
||||||
|
|
||||||
|
public getAdapter(): IStorageAdapterV2 { |
||||||
|
return R2Plugin.storageAdapter; |
||||||
|
} |
||||||
|
|
||||||
|
public async init(config: any): Promise<any> { |
||||||
|
R2Plugin.storageAdapter = new R2(config); |
||||||
|
await R2Plugin.storageAdapter.init(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
export default R2Plugin; |
@ -0,0 +1,68 @@ |
|||||||
|
import { XcActionType, XcType } from 'nocodb-sdk'; |
||||||
|
import R2Plugin from './R2Plugin'; |
||||||
|
import type { XcPluginConfig } from 'nc-plugin'; |
||||||
|
|
||||||
|
const config: XcPluginConfig = { |
||||||
|
builder: R2Plugin, |
||||||
|
title: 'Cloudflare R2 Storage', |
||||||
|
version: '0.0.1', |
||||||
|
logo: 'plugins/r2.png', |
||||||
|
description: |
||||||
|
'Cloudflare R2 is an S3-compatible, zero egress-fee, globally distributed object storage.', |
||||||
|
tags: 'Storage', |
||||||
|
inputs: { |
||||||
|
title: 'Configure Cloudflare R2 Storage', |
||||||
|
items: [ |
||||||
|
{ |
||||||
|
key: 'bucket', |
||||||
|
label: 'Bucket Name', |
||||||
|
placeholder: 'Bucket Name', |
||||||
|
type: XcType.SingleLineText, |
||||||
|
required: true, |
||||||
|
}, |
||||||
|
{ |
||||||
|
key: 'hostname', |
||||||
|
label: 'Host Name', |
||||||
|
placeholder: 'e.g.: *****.r2.cloudflarestorage.com', |
||||||
|
type: XcType.SingleLineText, |
||||||
|
required: true, |
||||||
|
}, |
||||||
|
{ |
||||||
|
key: 'access_key', |
||||||
|
label: 'Access Key', |
||||||
|
placeholder: 'Access Key', |
||||||
|
type: XcType.SingleLineText, |
||||||
|
required: true, |
||||||
|
}, |
||||||
|
{ |
||||||
|
key: 'access_secret', |
||||||
|
label: 'Access Secret', |
||||||
|
placeholder: 'Access Secret', |
||||||
|
type: XcType.Password, |
||||||
|
required: true, |
||||||
|
}, |
||||||
|
], |
||||||
|
actions: [ |
||||||
|
{ |
||||||
|
label: 'Test', |
||||||
|
placeholder: 'Test', |
||||||
|
key: 'test', |
||||||
|
actionType: XcActionType.TEST, |
||||||
|
type: XcType.Button, |
||||||
|
}, |
||||||
|
{ |
||||||
|
label: 'Save', |
||||||
|
placeholder: 'Save', |
||||||
|
key: 'save', |
||||||
|
actionType: XcActionType.SUBMIT, |
||||||
|
type: XcType.Button, |
||||||
|
}, |
||||||
|
], |
||||||
|
msgOnInstall: |
||||||
|
'Successfully installed and attachment will be stored in Cloudflare R2 Storage', |
||||||
|
msgOnUninstall: '', |
||||||
|
}, |
||||||
|
category: 'Storage', |
||||||
|
}; |
||||||
|
|
||||||
|
export default config; |
@ -1,149 +1,44 @@ |
|||||||
import fs from 'fs'; |
import { S3 as S3Client } from '@aws-sdk/client-s3'; |
||||||
import { promisify } from 'util'; |
import type { S3ClientConfig } from '@aws-sdk/client-s3'; |
||||||
import AWS from 'aws-sdk'; |
|
||||||
import axios from 'axios'; |
|
||||||
import { useAgent } from 'request-filtering-agent'; |
|
||||||
import type { IStorageAdapterV2, XcFile } from 'nc-plugin'; |
|
||||||
import type { Readable } from 'stream'; |
|
||||||
import { generateTempFilePath, waitForStreamClose } from '~/utils/pluginUtils'; |
|
||||||
|
|
||||||
export default class ScalewayObjectStorage implements IStorageAdapterV2 { |
import type { IStorageAdapterV2 } from 'nc-plugin'; |
||||||
private s3Client: AWS.S3; |
import GenericS3 from '~/plugins/GenericS3/GenericS3'; |
||||||
private input: any; |
|
||||||
|
|
||||||
constructor(input: any) { |
interface ScalewayObjectStorageInput { |
||||||
this.input = input; |
bucket: string; |
||||||
|
region: string; |
||||||
|
access_key: string; |
||||||
|
access_secret: string; |
||||||
|
acl?: string; |
||||||
} |
} |
||||||
|
|
||||||
public async fileRead(key: string): Promise<any> { |
export default class ScalewayObjectStorage |
||||||
return new Promise((resolve, reject) => { |
extends GenericS3 |
||||||
this.s3Client.getObject({ Key: key } as any, (err, data) => { |
implements IStorageAdapterV2 |
||||||
if (err) { |
{ |
||||||
return reject(err); |
protected input: ScalewayObjectStorageInput; |
||||||
} |
|
||||||
if (!data?.Body) { |
|
||||||
return reject(data); |
|
||||||
} |
|
||||||
return resolve(data.Body); |
|
||||||
}); |
|
||||||
}); |
|
||||||
} |
|
||||||
|
|
||||||
public async test(): Promise<boolean> { |
constructor(input: unknown) { |
||||||
try { |
super(input as ScalewayObjectStorageInput); |
||||||
const tempFile = generateTempFilePath(); |
|
||||||
const createStream = fs.createWriteStream(tempFile); |
|
||||||
await waitForStreamClose(createStream); |
|
||||||
await this.fileCreate('nc-test-file.txt', { |
|
||||||
path: tempFile, |
|
||||||
mimetype: 'text/plain', |
|
||||||
originalname: 'temp.txt', |
|
||||||
size: '', |
|
||||||
}); |
|
||||||
await promisify(fs.unlink)(tempFile); |
|
||||||
return true; |
|
||||||
} catch (e) { |
|
||||||
throw e; |
|
||||||
} |
|
||||||
} |
} |
||||||
|
|
||||||
public async fileDelete(_path: string): Promise<any> { |
protected get defaultParams() { |
||||||
return Promise.resolve(undefined); |
return { |
||||||
|
Bucket: this.input.bucket, |
||||||
|
ACL: this.input?.acl || 'public-read', |
||||||
|
}; |
||||||
} |
} |
||||||
|
|
||||||
public async init(): Promise<any> { |
public async init(): Promise<any> { |
||||||
const s3Options: any = { |
const s3Options: S3ClientConfig = { |
||||||
params: { Bucket: this.input.bucket }, |
|
||||||
region: this.input.region, |
region: this.input.region, |
||||||
}; |
credentials: { |
||||||
|
accessKeyId: this.input.access_key, |
||||||
s3Options.accessKeyId = this.input.access_key; |
secretAccessKey: this.input.access_secret, |
||||||
s3Options.secretAccessKey = this.input.access_secret; |
|
||||||
|
|
||||||
s3Options.endpoint = new AWS.Endpoint(`s3.${this.input.region}.scw.cloud`); |
|
||||||
|
|
||||||
this.s3Client = new AWS.S3(s3Options); |
|
||||||
} |
|
||||||
|
|
||||||
async fileCreate(key: string, file: XcFile): Promise<any> { |
|
||||||
const fileStream = fs.createReadStream(file.path); |
|
||||||
|
|
||||||
return this.fileCreateByStream(key, fileStream, { |
|
||||||
mimetype: file?.mimetype, |
|
||||||
}); |
|
||||||
} |
|
||||||
|
|
||||||
async fileCreateByUrl(key: string, url: string): Promise<any> { |
|
||||||
const uploadParams: any = { |
|
||||||
ACL: 'public-read', |
|
||||||
}; |
|
||||||
return new Promise((resolve, reject) => { |
|
||||||
axios |
|
||||||
.get(url, { |
|
||||||
httpAgent: useAgent(url, { stopPortScanningByUrlRedirection: true }), |
|
||||||
httpsAgent: useAgent(url, { stopPortScanningByUrlRedirection: true }), |
|
||||||
// TODO - use stream instead of buffer
|
|
||||||
responseType: 'arraybuffer', |
|
||||||
}) |
|
||||||
.then((response) => { |
|
||||||
uploadParams.Body = response.data; |
|
||||||
uploadParams.Key = key; |
|
||||||
uploadParams.ContentType = response.headers['content-type']; |
|
||||||
|
|
||||||
// call S3 to retrieve upload file to specified bucket
|
|
||||||
this.s3Client.upload(uploadParams, (err1, data) => { |
|
||||||
if (err1) { |
|
||||||
console.log('Error', err1); |
|
||||||
reject(err1); |
|
||||||
} |
|
||||||
if (data) { |
|
||||||
resolve({ |
|
||||||
url: data.Location, |
|
||||||
data: response.data, |
|
||||||
}); |
|
||||||
} |
|
||||||
}); |
|
||||||
}) |
|
||||||
.catch((error) => { |
|
||||||
reject(error); |
|
||||||
}); |
|
||||||
}); |
|
||||||
} |
|
||||||
|
|
||||||
async fileCreateByStream( |
|
||||||
key: string, |
|
||||||
stream: Readable, |
|
||||||
options?: { |
|
||||||
mimetype?: string; |
|
||||||
}, |
}, |
||||||
): Promise<any> { |
endpoint: `https://s3.${this.input.region}.scw.cloud`, |
||||||
const uploadParams: any = { |
|
||||||
ACL: 'public-read', |
|
||||||
Body: stream, |
|
||||||
Key: key, |
|
||||||
ContentType: options?.mimetype || 'application/octet-stream', |
|
||||||
}; |
}; |
||||||
return new Promise((resolve, reject) => { |
|
||||||
// call S3 to retrieve upload file to specified bucket
|
|
||||||
this.s3Client.upload(uploadParams, (err, data) => { |
|
||||||
if (err) { |
|
||||||
console.log('Error', err); |
|
||||||
reject(err); |
|
||||||
} |
|
||||||
if (data) { |
|
||||||
resolve(data.Location); |
|
||||||
} |
|
||||||
}); |
|
||||||
}); |
|
||||||
} |
|
||||||
|
|
||||||
// TODO - implement
|
|
||||||
fileReadByStream(_key: string): Promise<Readable> { |
|
||||||
return Promise.resolve(undefined); |
|
||||||
} |
|
||||||
|
|
||||||
// TODO - implement
|
this.s3Client = new S3Client(s3Options); |
||||||
getDirectoryList(_path: string): Promise<string[]> { |
|
||||||
return Promise.resolve(undefined); |
|
||||||
} |
} |
||||||
} |
} |
||||||
|
@ -1,159 +1,41 @@ |
|||||||
import fs from 'fs'; |
import { S3 as S3Client } from '@aws-sdk/client-s3'; |
||||||
import { promisify } from 'util'; |
import type { S3ClientConfig } from '@aws-sdk/client-s3'; |
||||||
import AWS from 'aws-sdk'; |
import type { IStorageAdapterV2 } from 'nc-plugin'; |
||||||
import axios from 'axios'; |
import GenericS3 from '~/plugins/GenericS3/GenericS3'; |
||||||
import { useAgent } from 'request-filtering-agent'; |
|
||||||
import type { IStorageAdapterV2, XcFile } from 'nc-plugin'; |
|
||||||
import type { Readable } from 'stream'; |
|
||||||
import { generateTempFilePath, waitForStreamClose } from '~/utils/pluginUtils'; |
|
||||||
|
|
||||||
export default class Spaces implements IStorageAdapterV2 { |
interface SpacesObjectStorageInput { |
||||||
private s3Client: AWS.S3; |
bucket: string; |
||||||
private input: any; |
region: string; |
||||||
|
access_key: string; |
||||||
constructor(input: any) { |
access_secret: string; |
||||||
this.input = input; |
acl?: string; |
||||||
} |
} |
||||||
|
|
||||||
async fileCreate(key: string, file: XcFile): Promise<any> { |
export default class Spaces extends GenericS3 implements IStorageAdapterV2 { |
||||||
const fileStream = fs.createReadStream(file.path); |
protected input: SpacesObjectStorageInput; |
||||||
|
|
||||||
return this.fileCreateByStream(key, fileStream, { |
|
||||||
mimetype: file?.mimetype, |
|
||||||
}); |
|
||||||
} |
|
||||||
|
|
||||||
async fileCreateByUrl(key: string, url: string): Promise<any> { |
constructor(input: unknown) { |
||||||
const uploadParams: any = { |
super(input as SpacesObjectStorageInput); |
||||||
ACL: 'public-read', |
|
||||||
}; |
|
||||||
return new Promise((resolve, reject) => { |
|
||||||
axios |
|
||||||
.get(url, { |
|
||||||
httpAgent: useAgent(url, { stopPortScanningByUrlRedirection: true }), |
|
||||||
httpsAgent: useAgent(url, { stopPortScanningByUrlRedirection: true }), |
|
||||||
// TODO - use stream instead of buffer
|
|
||||||
responseType: 'arraybuffer', |
|
||||||
}) |
|
||||||
.then((response) => { |
|
||||||
uploadParams.Body = response.data; |
|
||||||
uploadParams.Key = key; |
|
||||||
uploadParams.ContentType = response.headers['content-type']; |
|
||||||
|
|
||||||
// call S3 to retrieve upload file to specified bucket
|
|
||||||
this.s3Client.upload(uploadParams, (err1, data) => { |
|
||||||
if (err1) { |
|
||||||
console.log('Error', err1); |
|
||||||
reject(err1); |
|
||||||
} |
|
||||||
if (data) { |
|
||||||
resolve({ |
|
||||||
url: data.Location, |
|
||||||
data: response.data, |
|
||||||
}); |
|
||||||
} |
|
||||||
}); |
|
||||||
}) |
|
||||||
.catch((error) => { |
|
||||||
reject(error); |
|
||||||
}); |
|
||||||
}); |
|
||||||
} |
} |
||||||
|
|
||||||
async fileCreateByStream( |
protected get defaultParams() { |
||||||
key: string, |
return { |
||||||
stream: Readable, |
Bucket: this.input.bucket, |
||||||
options?: { |
ACL: this.input?.acl || 'public-read', |
||||||
mimetype?: string; |
|
||||||
}, |
|
||||||
): Promise<void> { |
|
||||||
const uploadParams: any = { |
|
||||||
ACL: 'public-read', |
|
||||||
Body: stream, |
|
||||||
Key: key, |
|
||||||
ContentType: options?.mimetype || 'application/octet-stream', |
|
||||||
}; |
}; |
||||||
return new Promise((resolve, reject) => { |
|
||||||
// call S3 to retrieve upload file to specified bucket
|
|
||||||
this.s3Client.upload(uploadParams, (err, data) => { |
|
||||||
if (err) { |
|
||||||
console.log('Error', err); |
|
||||||
reject(err); |
|
||||||
} |
|
||||||
if (data) { |
|
||||||
resolve(data.Location); |
|
||||||
} |
|
||||||
}); |
|
||||||
}); |
|
||||||
} |
|
||||||
|
|
||||||
// TODO - implement
|
|
||||||
fileReadByStream(_key: string): Promise<Readable> { |
|
||||||
return Promise.resolve(undefined); |
|
||||||
} |
|
||||||
|
|
||||||
// TODO - implement
|
|
||||||
getDirectoryList(_path: string): Promise<string[]> { |
|
||||||
return Promise.resolve(undefined); |
|
||||||
} |
|
||||||
|
|
||||||
public async fileDelete(_path: string): Promise<any> { |
|
||||||
return Promise.resolve(undefined); |
|
||||||
} |
|
||||||
|
|
||||||
public async fileRead(key: string): Promise<any> { |
|
||||||
return new Promise((resolve, reject) => { |
|
||||||
this.s3Client.getObject({ Key: key } as any, (err, data) => { |
|
||||||
if (err) { |
|
||||||
return reject(err); |
|
||||||
} |
|
||||||
if (!data?.Body) { |
|
||||||
return reject(data); |
|
||||||
} |
|
||||||
return resolve(data.Body); |
|
||||||
}); |
|
||||||
}); |
|
||||||
} |
} |
||||||
|
|
||||||
public async init(): Promise<any> { |
public async init(): Promise<any> { |
||||||
// const s3Options: any = {
|
const s3Options: S3ClientConfig = { |
||||||
// params: {Bucket: process.env.NC_S3_BUCKET},
|
region: 'us-east-1', |
||||||
// region: process.env.NC_S3_REGION
|
forcePathStyle: false, |
||||||
// };
|
credentials: { |
||||||
//
|
accessKeyId: this.input.access_key, |
||||||
// s3Options.accessKeyId = process.env.NC_S3_KEY;
|
secretAccessKey: this.input.access_secret, |
||||||
// s3Options.secretAccessKey = process.env.NC_S3_SECRET;
|
}, |
||||||
|
endpoint: `https://${this.input.region || 'nyc3'}.digitaloceanspaces.com`, |
||||||
const s3Options: any = { |
|
||||||
params: { Bucket: this.input.bucket }, |
|
||||||
region: this.input.region, |
|
||||||
}; |
}; |
||||||
|
|
||||||
s3Options.accessKeyId = this.input.access_key; |
this.s3Client = new S3Client(s3Options); |
||||||
s3Options.secretAccessKey = this.input.access_secret; |
|
||||||
|
|
||||||
s3Options.endpoint = new AWS.Endpoint( |
|
||||||
`${this.input.region || 'nyc3'}.digitaloceanspaces.com`, |
|
||||||
); |
|
||||||
|
|
||||||
this.s3Client = new AWS.S3(s3Options); |
|
||||||
} |
|
||||||
|
|
||||||
public async test(): Promise<boolean> { |
|
||||||
try { |
|
||||||
const tempFile = generateTempFilePath(); |
|
||||||
const createStream = fs.createWriteStream(tempFile); |
|
||||||
await waitForStreamClose(createStream); |
|
||||||
await this.fileCreate('nc-test-file.txt', { |
|
||||||
path: tempFile, |
|
||||||
mimetype: 'text/plain', |
|
||||||
originalname: 'temp.txt', |
|
||||||
size: '', |
|
||||||
}); |
|
||||||
await promisify(fs.unlink)(tempFile); |
|
||||||
return true; |
|
||||||
} catch (e) { |
|
||||||
throw e; |
|
||||||
} |
|
||||||
} |
} |
||||||
} |
} |
||||||
|
@ -1,149 +1,45 @@ |
|||||||
import fs from 'fs'; |
import { S3 as S3Client } from '@aws-sdk/client-s3'; |
||||||
import { promisify } from 'util'; |
import type { S3ClientConfig } from '@aws-sdk/client-s3'; |
||||||
import AWS from 'aws-sdk'; |
import type { IStorageAdapterV2 } from 'nc-plugin'; |
||||||
import axios from 'axios'; |
import GenericS3 from '~/plugins/GenericS3/GenericS3'; |
||||||
import { useAgent } from 'request-filtering-agent'; |
|
||||||
import type { IStorageAdapterV2, XcFile } from 'nc-plugin'; |
|
||||||
import type { Readable } from 'stream'; |
|
||||||
import { generateTempFilePath, waitForStreamClose } from '~/utils/pluginUtils'; |
|
||||||
|
|
||||||
export default class UpoCloud implements IStorageAdapterV2 { |
interface UpoCloudStorgeInput { |
||||||
private s3Client: AWS.S3; |
bucket: string; |
||||||
private input: any; |
region: string; |
||||||
|
access_key: string; |
||||||
constructor(input: any) { |
access_secret: string; |
||||||
this.input = input; |
endpoint: string; |
||||||
|
acl?: string; |
||||||
} |
} |
||||||
|
|
||||||
async fileCreate(key: string, file: XcFile): Promise<any> { |
export default class UpoCloud extends GenericS3 implements IStorageAdapterV2 { |
||||||
const fileStream = fs.createReadStream(file.path); |
protected input: UpoCloudStorgeInput; |
||||||
|
|
||||||
return this.fileCreateByStream(key, fileStream, { |
|
||||||
mimetype: file?.mimetype, |
|
||||||
}); |
|
||||||
} |
|
||||||
|
|
||||||
async fileCreateByUrl(key: string, url: string): Promise<any> { |
constructor(input: unknown) { |
||||||
const uploadParams: any = { |
super(input as UpoCloudStorgeInput); |
||||||
ACL: 'public-read', |
|
||||||
}; |
|
||||||
return new Promise((resolve, reject) => { |
|
||||||
axios |
|
||||||
.get(url, { |
|
||||||
httpAgent: useAgent(url, { stopPortScanningByUrlRedirection: true }), |
|
||||||
httpsAgent: useAgent(url, { stopPortScanningByUrlRedirection: true }), |
|
||||||
// TODO - use stream instead of buffer
|
|
||||||
responseType: 'arraybuffer', |
|
||||||
}) |
|
||||||
.then((response) => { |
|
||||||
uploadParams.Body = response.data; |
|
||||||
uploadParams.Key = key; |
|
||||||
uploadParams.ContentType = response.headers['content-type']; |
|
||||||
|
|
||||||
// call S3 to retrieve upload file to specified bucket
|
|
||||||
this.s3Client.upload(uploadParams, (err1, data) => { |
|
||||||
if (err1) { |
|
||||||
console.log('Error', err1); |
|
||||||
reject(err1); |
|
||||||
} |
|
||||||
if (data) { |
|
||||||
resolve({ |
|
||||||
url: data.Location, |
|
||||||
data: response.data, |
|
||||||
}); |
|
||||||
} |
|
||||||
}); |
|
||||||
}) |
|
||||||
.catch((error) => { |
|
||||||
reject(error); |
|
||||||
}); |
|
||||||
}); |
|
||||||
} |
} |
||||||
|
|
||||||
async fileCreateByStream( |
protected get defaultParams() { |
||||||
key: string, |
return { |
||||||
stream: Readable, |
Bucket: this.input.bucket, |
||||||
options?: { |
ACL: this.input?.acl || 'public-read', |
||||||
mimetype?: string; |
|
||||||
}, |
|
||||||
): Promise<void> { |
|
||||||
const uploadParams: any = { |
|
||||||
ACL: 'public-read', |
|
||||||
Key: key, |
|
||||||
Body: stream, |
|
||||||
ContentType: options?.mimetype || 'application/octet-stream', |
|
||||||
}; |
}; |
||||||
return new Promise((resolve, reject) => { |
|
||||||
// call S3 to retrieve upload file to specified bucket
|
|
||||||
this.s3Client.upload(uploadParams, (err, data) => { |
|
||||||
if (err) { |
|
||||||
console.log('Error', err); |
|
||||||
reject(err); |
|
||||||
} |
|
||||||
if (data) { |
|
||||||
resolve(data.Location); |
|
||||||
} |
|
||||||
}); |
|
||||||
}); |
|
||||||
} |
|
||||||
|
|
||||||
// TODO - implement
|
|
||||||
fileReadByStream(_key: string): Promise<Readable> { |
|
||||||
return Promise.resolve(undefined); |
|
||||||
} |
|
||||||
|
|
||||||
// TODO - implement
|
|
||||||
getDirectoryList(_path: string): Promise<string[]> { |
|
||||||
return Promise.resolve(undefined); |
|
||||||
} |
|
||||||
|
|
||||||
public async fileDelete(_path: string): Promise<any> { |
|
||||||
return Promise.resolve(undefined); |
|
||||||
} |
|
||||||
|
|
||||||
public async fileRead(key: string): Promise<any> { |
|
||||||
return new Promise((resolve, reject) => { |
|
||||||
this.s3Client.getObject({ Key: key } as any, (err, data) => { |
|
||||||
if (err) { |
|
||||||
return reject(err); |
|
||||||
} |
|
||||||
if (!data?.Body) { |
|
||||||
return reject(data); |
|
||||||
} |
|
||||||
return resolve(data.Body); |
|
||||||
}); |
|
||||||
}); |
|
||||||
} |
} |
||||||
|
|
||||||
public async init(): Promise<any> { |
public async init(): Promise<any> { |
||||||
const s3Options: any = { |
const updatedEndpoint = this.input.endpoint.startsWith('https://') |
||||||
params: { Bucket: this.input.bucket }, |
? this.input.endpoint |
||||||
|
: `https://${this.input.endpoint}`; |
||||||
|
|
||||||
|
const s3Options: S3ClientConfig = { |
||||||
region: this.input.region, |
region: this.input.region, |
||||||
|
credentials: { |
||||||
|
accessKeyId: this.input.access_key, |
||||||
|
secretAccessKey: this.input.access_secret, |
||||||
|
}, |
||||||
|
endpoint: updatedEndpoint, |
||||||
}; |
}; |
||||||
|
|
||||||
s3Options.accessKeyId = this.input.access_key; |
this.s3Client = new S3Client(s3Options); |
||||||
s3Options.secretAccessKey = this.input.access_secret; |
|
||||||
|
|
||||||
s3Options.endpoint = new AWS.Endpoint(this.input.endpoint); |
|
||||||
|
|
||||||
this.s3Client = new AWS.S3(s3Options); |
|
||||||
} |
|
||||||
|
|
||||||
public async test(): Promise<boolean> { |
|
||||||
try { |
|
||||||
const tempFile = generateTempFilePath(); |
|
||||||
const createStream = fs.createWriteStream(tempFile); |
|
||||||
await waitForStreamClose(createStream); |
|
||||||
await this.fileCreate('nc-test-file.txt', { |
|
||||||
path: tempFile, |
|
||||||
mimetype: 'text/plain', |
|
||||||
originalname: 'temp.txt', |
|
||||||
size: '', |
|
||||||
}); |
|
||||||
await promisify(fs.unlink)(tempFile); |
|
||||||
return true; |
|
||||||
} catch (e) { |
|
||||||
throw e; |
|
||||||
} |
|
||||||
} |
} |
||||||
} |
} |
||||||
|
@ -1,149 +1,41 @@ |
|||||||
import fs from 'fs'; |
import { S3 as S3Client } from '@aws-sdk/client-s3'; |
||||||
import { promisify } from 'util'; |
import type { S3ClientConfig } from '@aws-sdk/client-s3'; |
||||||
import AWS from 'aws-sdk'; |
import type { IStorageAdapterV2 } from 'nc-plugin'; |
||||||
import axios from 'axios'; |
import GenericS3 from '~/plugins/GenericS3/GenericS3'; |
||||||
import { useAgent } from 'request-filtering-agent'; |
|
||||||
import type { IStorageAdapterV2, XcFile } from 'nc-plugin'; |
|
||||||
import type { Readable } from 'stream'; |
|
||||||
import { generateTempFilePath, waitForStreamClose } from '~/utils/pluginUtils'; |
|
||||||
|
|
||||||
export default class Vultr implements IStorageAdapterV2 { |
interface VultrObjectStorageInput { |
||||||
private s3Client: AWS.S3; |
bucket: string; |
||||||
private input: any; |
region: string; |
||||||
|
access_key: string; |
||||||
constructor(input: any) { |
hostname: string; |
||||||
this.input = input; |
access_secret: string; |
||||||
|
acl?: string; |
||||||
} |
} |
||||||
|
|
||||||
async fileCreate(key: string, file: XcFile): Promise<any> { |
export default class Vultr extends GenericS3 implements IStorageAdapterV2 { |
||||||
const fileStream = fs.createReadStream(file.path); |
protected input: VultrObjectStorageInput; |
||||||
|
|
||||||
return this.fileCreateByStream(key, fileStream, { |
constructor(input: unknown) { |
||||||
mimetype: file?.mimetype, |
super(input as VultrObjectStorageInput); |
||||||
}); |
|
||||||
} |
} |
||||||
|
|
||||||
async fileCreateByUrl(key: string, url: string): Promise<any> { |
protected get defaultParams() { |
||||||
const uploadParams: any = { |
return { |
||||||
ACL: 'public-read', |
Bucket: this.input.bucket, |
||||||
|
ACL: this.input?.acl || 'public-read', |
||||||
}; |
}; |
||||||
return new Promise((resolve, reject) => { |
|
||||||
axios |
|
||||||
.get(url, { |
|
||||||
httpAgent: useAgent(url, { stopPortScanningByUrlRedirection: true }), |
|
||||||
httpsAgent: useAgent(url, { stopPortScanningByUrlRedirection: true }), |
|
||||||
// TODO - use stream instead of buffer
|
|
||||||
responseType: 'arraybuffer', |
|
||||||
}) |
|
||||||
.then((response) => { |
|
||||||
uploadParams.Body = response.data; |
|
||||||
uploadParams.Key = key; |
|
||||||
uploadParams.ContentType = response.headers['content-type']; |
|
||||||
|
|
||||||
// call S3 to retrieve upload file to specified bucket
|
|
||||||
this.s3Client.upload(uploadParams, (err1, data) => { |
|
||||||
if (err1) { |
|
||||||
console.log('Error', err1); |
|
||||||
reject(err1); |
|
||||||
} |
|
||||||
if (data) { |
|
||||||
resolve({ |
|
||||||
url: data.Location, |
|
||||||
data: response.data, |
|
||||||
}); |
|
||||||
} |
|
||||||
}); |
|
||||||
}) |
|
||||||
.catch((error) => { |
|
||||||
reject(error); |
|
||||||
}); |
|
||||||
}); |
|
||||||
} |
|
||||||
|
|
||||||
async fileCreateByStream( |
|
||||||
key: string, |
|
||||||
stream: Readable, |
|
||||||
options?: { |
|
||||||
mimetype: string; |
|
||||||
}, |
|
||||||
): Promise<void> { |
|
||||||
const uploadParams: any = { |
|
||||||
ACL: 'public-read', |
|
||||||
Body: stream, |
|
||||||
Key: key, |
|
||||||
ContentType: options?.mimetype || 'application/octet-stream', |
|
||||||
}; |
|
||||||
return new Promise((resolve, reject) => { |
|
||||||
// call S3 to retrieve upload file to specified bucket
|
|
||||||
this.s3Client.upload(uploadParams, (err, data) => { |
|
||||||
if (err) { |
|
||||||
console.log('Error', err); |
|
||||||
reject(err); |
|
||||||
} |
|
||||||
if (data) { |
|
||||||
resolve(data.Location); |
|
||||||
} |
|
||||||
}); |
|
||||||
}); |
|
||||||
} |
|
||||||
|
|
||||||
// TODO - implement
|
|
||||||
fileReadByStream(_key: string): Promise<Readable> { |
|
||||||
return Promise.resolve(undefined); |
|
||||||
} |
|
||||||
|
|
||||||
// TODO - implement
|
|
||||||
getDirectoryList(_path: string): Promise<string[]> { |
|
||||||
return Promise.resolve(undefined); |
|
||||||
} |
|
||||||
|
|
||||||
public async fileDelete(_path: string): Promise<any> { |
|
||||||
return Promise.resolve(undefined); |
|
||||||
} |
|
||||||
|
|
||||||
public async fileRead(key: string): Promise<any> { |
|
||||||
return new Promise((resolve, reject) => { |
|
||||||
this.s3Client.getObject({ Key: key } as any, (err, data) => { |
|
||||||
if (err) { |
|
||||||
return reject(err); |
|
||||||
} |
|
||||||
if (!data?.Body) { |
|
||||||
return reject(data); |
|
||||||
} |
|
||||||
return resolve(data.Body); |
|
||||||
}); |
|
||||||
}); |
|
||||||
} |
} |
||||||
|
|
||||||
public async init(): Promise<any> { |
public async init(): Promise<any> { |
||||||
const s3Options: any = { |
const s3Options: S3ClientConfig = { |
||||||
params: { Bucket: this.input.bucket }, |
|
||||||
region: this.input.region, |
region: this.input.region, |
||||||
|
credentials: { |
||||||
|
accessKeyId: this.input.access_key, |
||||||
|
secretAccessKey: this.input.access_secret, |
||||||
|
}, |
||||||
|
endpoint: this.input.hostname, |
||||||
}; |
}; |
||||||
|
|
||||||
s3Options.accessKeyId = this.input.access_key; |
this.s3Client = new S3Client(s3Options); |
||||||
s3Options.secretAccessKey = this.input.access_secret; |
|
||||||
|
|
||||||
s3Options.endpoint = new AWS.Endpoint(this.input.hostname); |
|
||||||
|
|
||||||
this.s3Client = new AWS.S3(s3Options); |
|
||||||
} |
|
||||||
|
|
||||||
public async test(): Promise<boolean> { |
|
||||||
try { |
|
||||||
const tempFile = generateTempFilePath(); |
|
||||||
const createStream = fs.createWriteStream(tempFile); |
|
||||||
await waitForStreamClose(createStream); |
|
||||||
await this.fileCreate('nc-test-file.txt', { |
|
||||||
path: tempFile, |
|
||||||
mimetype: 'text/plain', |
|
||||||
originalname: 'temp.txt', |
|
||||||
size: '', |
|
||||||
}); |
|
||||||
await promisify(fs.unlink)(tempFile); |
|
||||||
return true; |
|
||||||
} catch (e) { |
|
||||||
throw e; |
|
||||||
} |
|
||||||
} |
} |
||||||
} |
} |
||||||
|
Loading…
Reference in new issue