diff --git a/site/docs/api/classes/BucketsService.md b/site/docs/api/classes/BucketsService.md index d1947e4f..619cd540 100644 --- a/site/docs/api/classes/BucketsService.md +++ b/site/docs/api/classes/BucketsService.md @@ -20,7 +20,7 @@ custom_edit_url: null #### Defined in -[services/buckers.service.ts:34](https://github.com/LabO8/nestjs-s3/blob/65a196f/src/services/buckers.service.ts#L34) +[services/buckets.service.ts:34](https://github.com/LabO8/nestjs-s3/blob/65a196f/src/services/buckers.service.ts#L34) ## Methods @@ -41,7 +41,7 @@ custom_edit_url: null #### Defined in -[services/buckers.service.ts:36](https://github.com/LabO8/nestjs-s3/blob/65a196f/src/services/buckers.service.ts#L36) +[services/buckets.service.ts:36](https://github.com/LabO8/nestjs-s3/blob/65a196f/src/services/buckers.service.ts#L36) ___ @@ -61,7 +61,7 @@ ___ #### Defined in -[services/buckers.service.ts:48](https://github.com/LabO8/nestjs-s3/blob/65a196f/src/services/buckers.service.ts#L48) +[services/buckets.service.ts:48](https://github.com/LabO8/nestjs-s3/blob/65a196f/src/services/buckers.service.ts#L48) ___ @@ -81,7 +81,7 @@ ___ #### Defined in -[services/buckers.service.ts:60](https://github.com/LabO8/nestjs-s3/blob/65a196f/src/services/buckers.service.ts#L60) +[services/buckets.service.ts:60](https://github.com/LabO8/nestjs-s3/blob/65a196f/src/services/buckers.service.ts#L60) ___ @@ -95,7 +95,7 @@ ___ #### Defined in -[services/buckers.service.ts:56](https://github.com/LabO8/nestjs-s3/blob/65a196f/src/services/buckers.service.ts#L56) +[services/buckets.service.ts:56](https://github.com/LabO8/nestjs-s3/blob/65a196f/src/services/buckers.service.ts#L56) ___ @@ -115,7 +115,7 @@ ___ #### Defined in -[services/buckers.service.ts:66](https://github.com/LabO8/nestjs-s3/blob/65a196f/src/services/buckers.service.ts#L66) +[services/buckets.service.ts:66](https://github.com/LabO8/nestjs-s3/blob/65a196f/src/services/buckers.service.ts#L66) ___ @@ -136,7 +136,7 @@ ___ #### Defined in -[services/buckers.service.ts:119](https://github.com/LabO8/nestjs-s3/blob/65a196f/src/services/buckers.service.ts#L119) +[services/buckets.service.ts:119](https://github.com/LabO8/nestjs-s3/blob/65a196f/src/services/buckers.service.ts#L119) ___ @@ -157,7 +157,7 @@ ___ #### Defined in -[services/buckers.service.ts:83](https://github.com/LabO8/nestjs-s3/blob/65a196f/src/services/buckers.service.ts#L83) +[services/buckets.service.ts:83](https://github.com/LabO8/nestjs-s3/blob/65a196f/src/services/buckers.service.ts#L83) ___ @@ -178,7 +178,7 @@ ___ #### Defined in -[services/buckers.service.ts:74](https://github.com/LabO8/nestjs-s3/blob/65a196f/src/services/buckers.service.ts#L74) +[services/buckets.service.ts:74](https://github.com/LabO8/nestjs-s3/blob/65a196f/src/services/buckers.service.ts#L74) ___ @@ -199,7 +199,7 @@ ___ #### Defined in -[services/buckers.service.ts:107](https://github.com/LabO8/nestjs-s3/blob/65a196f/src/services/buckers.service.ts#L107) +[services/buckets.service.ts:107](https://github.com/LabO8/nestjs-s3/blob/65a196f/src/services/buckers.service.ts#L107) ___ @@ -220,4 +220,4 @@ ___ #### Defined in -[services/buckers.service.ts:95](https://github.com/LabO8/nestjs-s3/blob/65a196f/src/services/buckers.service.ts#L95) +[services/buckets.service.ts:95](https://github.com/LabO8/nestjs-s3/blob/65a196f/src/services/buckers.service.ts#L95) diff --git a/src/helpers/prepare-options.helper.ts b/src/helpers/prepare-options.helper.ts index 2f608d83..de3f7b5c 100644 --- a/src/helpers/prepare-options.helper.ts +++ b/src/helpers/prepare-options.helper.ts @@ -1,14 +1,16 @@ -import { OptionsWithAutoPrefix } from '../types'; +import { DisableAutoPrefix, OptionsWithAutoPrefix, PrefixContext } from '../types'; export const prepareOptions = ( options: OptionsWithAutoPrefix, ): { - options: Omit; - disableAutoPrefix: boolean; -} => { + options: Omit; +} & DisableAutoPrefix & + PrefixContext => { const disableAutoPrefix = options?.disableAutoPrefix ?? false; + const prefixContext = options?.prefixContext ?? null; delete options?.disableAutoPrefix; + delete options?.prefixContext; - return { options, disableAutoPrefix }; + return { options, disableAutoPrefix, prefixContext }; }; diff --git a/src/services/buckers.service.ts b/src/services/buckets.service.ts similarity index 100% rename from src/services/buckers.service.ts rename to src/services/buckets.service.ts diff --git a/src/services/index.ts b/src/services/index.ts index f83f982d..5fcf49ff 100644 --- a/src/services/index.ts +++ b/src/services/index.ts @@ -1,4 +1,4 @@ -export * from './buckers.service'; +export * from './buckets.service'; export * from './objects.service'; export * from './prefix.service'; export * from './signed-url.service'; diff --git a/src/services/objects.service.ts b/src/services/objects.service.ts index 940f1ba3..8bfe379c 100644 --- a/src/services/objects.service.ts +++ b/src/services/objects.service.ts @@ -1,4 +1,6 @@ import { + CopyObjectCommand, + CopyObjectOutput, DeleteObjectCommand, DeleteObjectOutput, DeleteObjectsCommand, @@ -17,11 +19,14 @@ import { Inject, Injectable } from '@nestjs/common'; import * as fs from 'fs'; import { S3_SERVICE } from '../constants'; import { + CopyObjectOptions, DeleteObjectOptions, DeleteObjectsOptions, + DisableAutoPrefix, GetObjectOptions, ListObjectsOptions, ListObjectsV2Options, + PrefixContext, PutObjectOptions, } from '../types'; import { PrefixService } from './prefix.service'; @@ -40,13 +45,13 @@ export class ObjectsService { remote: string, options?: PutObjectOptions, ): Promise { - const { disableAutoPrefix, options: preparedOptions } = prepareOptions(options); + const { disableAutoPrefix, prefixContext, options: preparedOptions } = prepareOptions(options); return this.client.send( new PutObjectCommand({ Bucket: bucket, Body: body, - Key: disableAutoPrefix ? remote : this.prefixService.prefix(remote, bucket, options?.prefixContext), + Key: disableAutoPrefix ? remote : this.prefixService.prefix(remote, bucket, prefixContext), ...preparedOptions, }), ); @@ -68,12 +73,12 @@ export class ObjectsService { remote: string, options?: DeleteObjectOptions, ): Promise { - const { disableAutoPrefix, options: preparedOptions } = prepareOptions(options); + const { disableAutoPrefix, prefixContext, options: preparedOptions } = prepareOptions(options); return this.client.send( new DeleteObjectCommand({ Bucket: bucket, - Key: disableAutoPrefix ? remote : this.prefixService.prefix(remote, bucket, options?.prefixContext), + Key: disableAutoPrefix ? remote : this.prefixService.prefix(remote, bucket, prefixContext), ...preparedOptions, }), ); @@ -84,14 +89,14 @@ export class ObjectsService { remotes: string[], options?: DeleteObjectsOptions, ): Promise { - const { disableAutoPrefix, options: preparedOptions } = prepareOptions(options); + const { disableAutoPrefix, prefixContext, options: preparedOptions } = prepareOptions(options); return this.client.send( new DeleteObjectsCommand({ Bucket: bucket, Delete: { Objects: remotes.map((r) => ({ - Key: disableAutoPrefix ? r : this.prefixService.prefix(r, bucket, options?.prefixContext), + Key: disableAutoPrefix ? r : this.prefixService.prefix(r, bucket, prefixContext), })), }, ...preparedOptions, @@ -100,12 +105,49 @@ export class ObjectsService { } public async getObject(bucket: string, remote: string, options?: GetObjectOptions): Promise { - const { disableAutoPrefix, options: preparedOptions } = prepareOptions(options); + const { disableAutoPrefix, prefixContext, options: preparedOptions } = prepareOptions(options); return this.client.send( new GetObjectCommand({ Bucket: bucket, - Key: disableAutoPrefix ? remote : this.prefixService.prefix(remote, bucket, options?.prefixContext), + Key: disableAutoPrefix ? remote : this.prefixService.prefix(remote, bucket, prefixContext), + ...preparedOptions, + }), + ); + } + + public async copyObject( + sourceBucket: string, + sourceKey: string, + destinationBucket: string, + destinationKey: string, + options?: { + sourceOptions?: DisableAutoPrefix & PrefixContext; + destinationOptions?: CopyObjectOptions; + }, + ): Promise { + const sourceOpts = options?.sourceOptions ?? {}; + const destOpts = options?.destinationOptions ?? {}; + + const { disableAutoPrefix: sourceDisableAutoPrefix, prefixContext: sourcePrefixContext } = sourceOpts; + const { + disableAutoPrefix: destDisableAutoPrefix, + prefixContext: destPrefixContext, + options: preparedOptions, + } = prepareOptions(destOpts); + + const prefixedSourceKey = sourceDisableAutoPrefix + ? sourceKey + : this.prefixService.prefix(sourceKey, sourceBucket, sourcePrefixContext); + const prefixedDestinationKey = destDisableAutoPrefix + ? destinationKey + : this.prefixService.prefix(destinationKey, destinationBucket, destPrefixContext); + + return this.client.send( + new CopyObjectCommand({ + CopySource: encodeURIComponent(`${sourceBucket}/${prefixedSourceKey}`), + Bucket: destinationBucket, + Key: prefixedDestinationKey, ...preparedOptions, }), ); diff --git a/src/services/signed-url.service.ts b/src/services/signed-url.service.ts index 76fe68cc..243b2b20 100644 --- a/src/services/signed-url.service.ts +++ b/src/services/signed-url.service.ts @@ -25,8 +25,8 @@ export class SignedUrlService { expiresIn: number = DEFAULT_EXPIRES_IN, options?: PutObjectOptions, ): Promise { - const { disableAutoPrefix, options: preparedOptions } = prepareOptions(options); - const key = disableAutoPrefix ? remote : this.prefixService.prefix(remote, bucket, options?.prefixContext); + const { disableAutoPrefix, prefixContext, options: preparedOptions } = prepareOptions(options); + const key = disableAutoPrefix ? remote : this.prefixService.prefix(remote, bucket, prefixContext); const command = new PutObjectCommand({ Bucket: bucket, @@ -50,11 +50,11 @@ export class SignedUrlService { expiresIn: number = DEFAULT_EXPIRES_IN, options?: GetObjectOptions, ): Promise { - const { disableAutoPrefix, options: preparedOptions } = prepareOptions(options); + const { disableAutoPrefix, prefixContext, options: preparedOptions } = prepareOptions(options); const command = new GetObjectCommand({ Bucket: bucket, - Key: disableAutoPrefix ? remote : this.prefixService.prefix(remote, bucket, options?.prefixContext), + Key: disableAutoPrefix ? remote : this.prefixService.prefix(remote, bucket, prefixContext), ...preparedOptions, }); @@ -69,11 +69,11 @@ export class SignedUrlService { expiresIn: number = DEFAULT_EXPIRES_IN, options?: DeleteObjectOptions, ): Promise { - const { disableAutoPrefix, options: preparedOptions } = prepareOptions(options); + const { disableAutoPrefix, prefixContext, options: preparedOptions } = prepareOptions(options); const command = new DeleteObjectCommand({ Bucket: bucket, - Key: disableAutoPrefix ? remote : this.prefixService.prefix(remote, bucket, options?.prefixContext), + Key: disableAutoPrefix ? remote : this.prefixService.prefix(remote, bucket, prefixContext), ...preparedOptions, }); @@ -88,13 +88,13 @@ export class SignedUrlService { expiresIn: number = DEFAULT_EXPIRES_IN, options?: DeleteObjectsOptions, ): Promise { - const { disableAutoPrefix, options: preparedOptions } = prepareOptions(options); + const { disableAutoPrefix, prefixContext, options: preparedOptions } = prepareOptions(options); const command = new DeleteObjectsCommand({ Bucket: bucket, Delete: { Objects: remotes.map((r) => ({ - Key: disableAutoPrefix ? r : this.prefixService.prefix(r, bucket, options?.prefixContext), + Key: disableAutoPrefix ? r : this.prefixService.prefix(r, bucket, prefixContext), })), }, ...preparedOptions, diff --git a/src/types/object-command-options.type.ts b/src/types/object-command-options.type.ts index d4ecdfe9..2a17928c 100644 --- a/src/types/object-command-options.type.ts +++ b/src/types/object-command-options.type.ts @@ -16,6 +16,14 @@ export type PutObjectOptions = Omit & DisableAutoPrefix & PrefixContext; +export type CopyObjectOptions = Omit & + DisableAutoPrefix & + PrefixContext; export type ListObjectsOptions = Omit; export type ListObjectsV2Options = Omit; -export type OptionsWithAutoPrefix = PutObjectOptions | DeleteObjectOptions | DeleteObjectsOptions | GetObjectOptions; +export type OptionsWithAutoPrefix = + | PutObjectOptions + | DeleteObjectOptions + | DeleteObjectsOptions + | GetObjectOptions + | CopyObjectOptions; diff --git a/src/utils/deletion.service.ts b/src/utils/deletion.service.ts index 7bc20337..b742cc5e 100644 --- a/src/utils/deletion.service.ts +++ b/src/utils/deletion.service.ts @@ -24,7 +24,7 @@ export class DeletionService { deleteOptions?: DeleteObjectsOptions, listOptions?: Omit, ): Promise { - const { disableAutoPrefix, options: preparedOptions } = prepareOptions(deleteOptions); + const { disableAutoPrefix, prefixContext, options: preparedOptions } = prepareOptions(deleteOptions); let continuationToken = null; const result: DeleteObjectOutput[] = []; @@ -32,7 +32,7 @@ export class DeletionService { do { data = await this.objectsService.listObjectsV2(bucket, { - Prefix: disableAutoPrefix ? prefix : this.prefixService.prefix(prefix, bucket, deleteOptions?.prefixContext), + Prefix: disableAutoPrefix ? prefix : this.prefixService.prefix(prefix, bucket, prefixContext), ContinuationToken: continuationToken, ...listOptions, }); diff --git a/tests/e2e/objects-service.e2e.test.ts b/tests/e2e/objects-service.e2e.test.ts index 18a47e29..968034cb 100644 --- a/tests/e2e/objects-service.e2e.test.ts +++ b/tests/e2e/objects-service.e2e.test.ts @@ -12,7 +12,7 @@ describe('Object service', () => { const bucketName = uuidv4(); const testPath = path.resolve(__dirname, 'data'); - const testFiles = ['test.txt', 'test-file-path.txt', 'test-get.txt']; + const testFiles = ['test.txt', 'test-file-path.txt', 'test-get.txt', 'test-file-to-copy.txt', 'test-file-copied.txt']; beforeAll(async () => { testingModule = await Test.createTestingModule({ @@ -79,7 +79,7 @@ describe('Object service', () => { expect(result).not.toEqual(null); }); - it('should be get an object that exists from a existing bucket', async () => { + it('should get an object that exists from a existing bucket', async () => { const remote = 'test-get.txt'; await objectService.putObjectFromPath(bucketName, path.resolve(testPath, 'test.txt'), remote); @@ -87,4 +87,17 @@ describe('Object service', () => { expect(object.Body).toBeInstanceOf(Stream); }); + + it('should be able to copy a file by remote', async () => { + await objectService.putObjectFromPath(bucketName, path.resolve(testPath, 'test.txt'), 'test-file-to-copy.txt'); + + const result = await objectService.copyObject( + bucketName, + 'test-file-to-copy.txt', + bucketName, + 'test-file-copied.txt', + ); + + expect(result).not.toEqual(null); + }); });