Skip to content

Commit

Permalink
feat: add module asynchronous config using factory/existing/class (#65)
Browse files Browse the repository at this point in the history
* feature(azure-storage) add azure storage options async and factory methods

* test(azure-storage) adding unit tests for azure storage withConfigAsync

* doc(azure-storage) update readme doc with new methods to configure

Co-authored-by: Sivamuthu Kumar <[email protected]>
  • Loading branch information
ksivamuthu and ksivamuthu-cei authored Nov 9, 2020
1 parent b781d79 commit 58a8710
Show file tree
Hide file tree
Showing 4 changed files with 143 additions and 2 deletions.
21 changes: 21 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,27 @@ import { AzureStorageModule } from '@nestjs/azure-storage';
export class AppModule {}
```

If you want to use asynchronous configuration options using factory or class,

```typescript
const storageConfigFactory = async () => {
sasKey: process.env['AZURE_STORAGE_SAS_KEY'],
accountName: process.env['AZURE_STORAGE_ACCOUNT'],
containerName: 'nest-demo-container',
};

@Module({
controllers: [AppController],
providers: [AppService],
imports: [
AzureStorageModule.withConfigAsync({
useFactory: storageConfigFactory,
}),
],
})
export class AppModule {}
```

> You may provide a default `containerName` name for the whole module, this will apply to all controllers withing this module. You can also provide (override) the `containerName` in the controller, for each route.
## Story examples
Expand Down
62 changes: 62 additions & 0 deletions lib/azure-nest-storage.module.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { AZURE_STORAGE_MODULE_OPTIONS } from './azure-storage.constant';
import { AzureStorageModule } from './azure-nest-storage.module';
import {
AzureStorageOptions,
AzureStorageOptionsFactory,
} from './azure-storage.service';
import { ValueProvider, FactoryProvider } from '@nestjs/common/interfaces';

describe('AzureStorageModule', () => {
let options: AzureStorageOptions;

beforeEach(() => {
options = {
accountName: 'test_account',
containerName: 'test_container',
sasKey: 'FAKE_SAS_KEY',
};
});

it('should have storage options provider when options passed', async () => {
const module = AzureStorageModule.withConfig(options);
expect(module.providers.length).toBe(1);
expect((module.providers[0] as ValueProvider<any>).provide).toBe(
AZURE_STORAGE_MODULE_OPTIONS,
);
expect(await (module.providers[0] as ValueProvider<any>).useValue).toBe(
options,
);
});

it('should have storage options provider when async options passed using factory', async () => {
const factory = async () => options;
const module = AzureStorageModule.withConfigAsync({
useFactory: factory,
});
expect(module.providers.length).toBe(1);
expect((module.providers[0] as FactoryProvider<any>).provide).toBe(
AZURE_STORAGE_MODULE_OPTIONS,
);
expect(
await (module.providers[0] as FactoryProvider<any>).useFactory(),
).toBe(options);
});

it('should have storage options provider when async options passed using existing factory', async () => {
class TestAzStorageOptionsFactory implements AzureStorageOptionsFactory {
createAzureStorageOptions():
| AzureStorageOptions
| Promise<AzureStorageOptions> {
return options;
}
}

const module = AzureStorageModule.withConfigAsync({
useExisting: TestAzStorageOptionsFactory,
});
expect(module.providers.length).toBe(1);
expect((module.providers[0] as FactoryProvider<any>).provide).toBe(
AZURE_STORAGE_MODULE_OPTIONS,
);
});
});
45 changes: 44 additions & 1 deletion lib/azure-nest-storage.module.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { DynamicModule, Module } from '@nestjs/common';
import { DynamicModule, Module, Provider } from '@nestjs/common';
import { AzureMulterStorageService } from './azure-multer.service';
import { AZURE_STORAGE_MODULE_OPTIONS } from './azure-storage.constant';
import {
AzureStorageOptions,
AzureStorageService,
AzureStorageAsyncOptions,
AzureStorageOptionsFactory,
} from './azure-storage.service';

const PUBLIC_PROVIDERS = [AzureMulterStorageService, AzureStorageService];
Expand All @@ -19,4 +21,45 @@ export class AzureStorageModule {
providers: [{ provide: AZURE_STORAGE_MODULE_OPTIONS, useValue: options }],
};
}

static withConfigAsync(options: AzureStorageAsyncOptions): DynamicModule {
return {
module: AzureStorageModule,
providers: [...this.createAsyncConfigProviders(options)],
};
}

private static createAsyncConfigProviders(
options: AzureStorageAsyncOptions,
): Provider[] {
if (options.useExisting || options.useFactory) {
return [this.createAsyncConfigProvider(options)];
}

return [
this.createAsyncConfigProvider(options),
{
provide: options.useClass,
useClass: options.useClass,
},
];
}

private static createAsyncConfigProvider(
options: AzureStorageAsyncOptions,
): Provider {
if (options.useFactory) {
return {
provide: AZURE_STORAGE_MODULE_OPTIONS,
useFactory: async (...args: any[]) => await options.useFactory(...args),
inject: options.inject || [],
};
}
return {
provide: AZURE_STORAGE_MODULE_OPTIONS,
useFactory: async (optionsFactory: AzureStorageOptionsFactory) =>
await optionsFactory.createAzureStorageOptions(),
inject: [options.useExisting || options.useClass],
};
}
}
17 changes: 16 additions & 1 deletion lib/azure-storage.service.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as Azure from '@azure/storage-blob';
import { ServiceClientOptions } from '@azure/ms-rest-js';

import { Inject, Injectable, Logger } from '@nestjs/common';
import { Inject, Injectable, Logger, Type } from '@nestjs/common';
import { AZURE_STORAGE_MODULE_OPTIONS } from './azure-storage.constant';

export const APP_NAME = 'AzureStorageService';
Expand All @@ -13,6 +13,21 @@ export interface AzureStorageOptions {
clientOptions?: ServiceClientOptions;
}

export interface AzureStorageAsyncOptions {
useExisting?: Type<AzureStorageOptionsFactory>;
useClass?: Type<AzureStorageOptionsFactory>;
useFactory?: (
...args: any[]
) => Promise<AzureStorageOptions> | AzureStorageOptions;
inject?: any[];
}

export interface AzureStorageOptionsFactory {
createAzureStorageOptions():
| AzureStorageOptions
| Promise<AzureStorageOptions>;
}

export interface UploadedFileMetadata {
fieldname: string;
originalname: string;
Expand Down

0 comments on commit 58a8710

Please sign in to comment.