Initial commit
This commit is contained in:
commit
bb911f8ffb
|
|
@ -0,0 +1,3 @@
|
|||
node_modules
|
||||
dist
|
||||
./local-aws.sqlite
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"collection": "@nestjs/schematics",
|
||||
"sourceRoot": "src",
|
||||
"entryFile": "main.js"
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
{
|
||||
"name": "local-aws",
|
||||
"version": "1.0.0",
|
||||
"main": "index.js",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"start": "nest start",
|
||||
"start:dev": "nest start --watch"
|
||||
},
|
||||
"dependencies": {
|
||||
"@nestjs/common": "^9.3.10",
|
||||
"@nestjs/config": "^2.3.1",
|
||||
"@nestjs/core": "^9.3.10",
|
||||
"@nestjs/platform-express": "^9.3.10",
|
||||
"@nestjs/typeorm": "^9.0.1",
|
||||
"class-transformer": "^0.5.1",
|
||||
"joi": "^17.9.0",
|
||||
"js2xmlparser": "^5.0.0",
|
||||
"morgan": "^1.10.0",
|
||||
"rxjs": "^7.8.0",
|
||||
"sqlite3": "^5.1.6",
|
||||
"typeorm": "^0.3.12",
|
||||
"uuidv4": "^6.2.13"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nestjs/cli": "^9.3.0"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
import { Action } from './action.enum';
|
||||
import * as uuid from 'uuid';
|
||||
import * as Joi from 'joi';
|
||||
|
||||
export type AwsProperties = {
|
||||
accountId: string;
|
||||
region: string;
|
||||
}
|
||||
|
||||
export enum Format {
|
||||
Xml,
|
||||
Json,
|
||||
};
|
||||
|
||||
export abstract class AbstractActionHandler<T = Record<string, string | number | boolean>> {
|
||||
|
||||
abstract format: Format;
|
||||
abstract action: Action;
|
||||
abstract validator: Joi.ObjectSchema<T>;
|
||||
protected abstract handle(queryParams: T, awsProperties: AwsProperties): Record<string, any> | void;
|
||||
|
||||
async getResponse(queryParams: T, awsProperties: AwsProperties) {
|
||||
if (this.format === Format.Xml) {
|
||||
return await this.getXmlResponse(queryParams, awsProperties);
|
||||
}
|
||||
return await this.getJsonResponse(queryParams, awsProperties);
|
||||
}
|
||||
|
||||
private async getXmlResponse(queryParams: T, awsProperties: AwsProperties) {
|
||||
const response = {
|
||||
'@': {
|
||||
xmlns: "https://sns.amazonaws.com/doc/2010-03-31/"
|
||||
},
|
||||
ResponseMetadata: {
|
||||
RequestId: uuid.v4(),
|
||||
}
|
||||
}
|
||||
|
||||
const result = await this.handle(queryParams, awsProperties);
|
||||
|
||||
if (!result) {
|
||||
return response;
|
||||
}
|
||||
|
||||
return {
|
||||
[`${this.action}Result`]: {
|
||||
...result,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async getJsonResponse(queryParams: T, awsProperties: AwsProperties) {
|
||||
const result = await this.handle(queryParams, awsProperties);
|
||||
if (result) {
|
||||
return result;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,92 @@
|
|||
export enum Action {
|
||||
|
||||
// SecretsManager
|
||||
SecretsManagerCancelRotateSecret = 'secretsmanager.CancelRotateSecret',
|
||||
SecretsManagerCreateSecret = 'secretsmanager.CreateSecret',
|
||||
SecretsManagerDeleteResourcePolicy = 'secretsmanager.DeleteResourcePolicy',
|
||||
SecretsManagerDeleteSecret = 'secretsmanager.DeleteSecret',
|
||||
SecretsManagerDescribeSecret = 'secretsmanager.DescribeSecret',
|
||||
SecretsManagerGetRandomPassword = 'secretsmanager.GetRandomPassword',
|
||||
SecretsManagerGetResourcePolicy = 'secretsmanager.GetResourcePolicy',
|
||||
SecretsManagerGetSecretValue = 'secretsmanager.GetSecretValue',
|
||||
SecretsManagerListSecrets = 'secretsmanager.ListSecrets',
|
||||
SecretsManagerListSecretVersionIds = 'secretsmanager.ListSecretVersionIds',
|
||||
SecretsManagerPutResourcePolicy = 'secretsmanager.PutResourcePolicy',
|
||||
SecretsManagerPutSecretValue = 'secretsmanager.PutSecretValue',
|
||||
SecretsManagerRemoveRegionsFromReplication = 'secretsmanager.RemoveRegionsFromReplication',
|
||||
SecretsManagerReplicateSecretToRegions = 'secretsmanager.ReplicateSecretToRegions',
|
||||
SecretsManagerRestoreSecret = 'secretsmanager.RestoreSecret',
|
||||
SecretsManagerRotateSecret = 'secretsmanager.RotateSecret',
|
||||
SecretsManagerStopReplicationToReplica = 'secretsmanager.StopReplicationToReplica',
|
||||
SecretsManagerTagResource = 'secretsmanager.TagResource',
|
||||
SecretsManagerUntagResource = 'secretsmanager.UntagResource',
|
||||
SecretsManagerUpdateSecret = 'secretsmanager.UpdateSecret',
|
||||
SecretsManagerUpdateSecretVersionStage = 'secretsmanager.UpdateSecretVersionStage',
|
||||
SecretsManagerValidateResourcePolicy = 'secretsmanager.ValidateResourcePolicy',
|
||||
|
||||
// SNS
|
||||
SnsAddPermission = 'AddPermission',
|
||||
SnsCheckIfPhoneNumberIsOptedOut = 'CheckIfPhoneNumberIsOptedOut',
|
||||
SnsConfirmSubscription = 'ConfirmSubscription',
|
||||
SnsCreatePlatformApplication = 'CreatePlatformApplication',
|
||||
SnsCreatePlatformEndpoint = 'CreatePlatformEndpoint',
|
||||
SnsCreateSMSSandboxPhoneNumber = 'CreateSMSSandboxPhoneNumber',
|
||||
SnsCreateTopic = 'CreateTopic',
|
||||
SnsDeleteEndpoint = 'DeleteEndpoint',
|
||||
SnsDeletePlatformApplication = 'DeletePlatformApplication',
|
||||
SnsDeleteSMSSandboxPhoneNumber = 'DeleteSMSSandboxPhoneNumber',
|
||||
SnsDeleteTopic = 'DeleteTopic',
|
||||
SnsGetDataProtectionPolicy = 'GetDataProtectionPolicy',
|
||||
SnsGetEndpointAttributes = 'GetEndpointAttributes',
|
||||
SnsGetPlatformApplicationAttributes = 'GetPlatformApplicationAttributes',
|
||||
SnsGetSMSAttributes = 'GetSMSAttributes',
|
||||
SnsGetSMSSandboxAccountStatus = 'GetSMSSandboxAccountStatus',
|
||||
SnsGetSubscriptionAttributes = 'GetSubscriptionAttributes',
|
||||
SnsGetTopicAttributes = 'GetTopicAttributes',
|
||||
SnsListEndpointsByPlatformApplication = 'ListEndpointsByPlatformApplication',
|
||||
SnsListOriginationNumbers = 'ListOriginationNumbers',
|
||||
SnsListPhoneNumbersOptedOut = 'ListPhoneNumbersOptedOut',
|
||||
SnsListPlatformApplications = 'ListPlatformApplications',
|
||||
SnsListSMSSandboxPhoneNumbers = 'ListSMSSandboxPhoneNumbers',
|
||||
SnsListSubscriptions = 'ListSubscriptions',
|
||||
SnsListSubscriptionsByTopic = 'ListSubscriptionsByTopic',
|
||||
SnsListTagsForResource = 'ListTagsForResource',
|
||||
SnsListTopics = 'ListTopics',
|
||||
SnsOptInPhoneNumber = 'OptInPhoneNumber',
|
||||
SnsPublish = 'Publish',
|
||||
SnsPublishBatch = 'PublishBatch',
|
||||
SnsPutDataProtectionPolicy = 'PutDataProtectionPolicy',
|
||||
SnsRemovePermission = 'RemovePermission',
|
||||
SnsSetEndpointAttributes = 'SetEndpointAttributes',
|
||||
SnsSetPlatformApplicationAttributes = 'SetPlatformApplicationAttributes',
|
||||
SnsSetSMSAttributes = 'SetSMSAttributes',
|
||||
SnsSetSubscriptionAttributes = 'SetSubscriptionAttributes',
|
||||
SnsSetTopicAttributes = 'SetTopicAttributes',
|
||||
SnsSubscribe = 'Subscribe',
|
||||
SnsTagResource = 'TagResource',
|
||||
SnsUnsubscribe = 'Unsubscribe',
|
||||
SnsUntagResource = 'UntagResource',
|
||||
SnsVerifySMSSandboxPhoneNumber = 'VerifySMSSandboxPhoneNumber',
|
||||
|
||||
// SQS
|
||||
SqsAddPermisson = 'AddPermission',
|
||||
SqsChangeMessageVisibility = 'ChangeMessageVisibility',
|
||||
SqsChangeMessageVisibilityBatch = 'ChangeMessageVisibilityBatch',
|
||||
SqsCreateQueue = 'CreateQueue',
|
||||
SqsDeleteMessage = 'DeleteMessage',
|
||||
SqsDeleteMessageBatch = 'DeleteMessageBatch',
|
||||
SqsDeleteQueue = 'DeleteQueue',
|
||||
SqsGetQueueAttributes = 'GetQueueAttributes',
|
||||
SqsGetQueueUrl = 'GetQueueUrl',
|
||||
SqsListDeadLetterSourceQueues = 'ListDeadLetterSourceQueues',
|
||||
SqsListQueues = 'ListQueues',
|
||||
SqsListQueueTags = 'ListQueueTags',
|
||||
SqsPurgeQueue = 'PurgeQueue',
|
||||
SqsReceiveMessage = 'ReceiveMessage',
|
||||
SqsRemovePermission = 'RemovePermission',
|
||||
SqsSendMessage = 'SendMessage',
|
||||
SqsSendMessageBatch = 'SendMessageBatch',
|
||||
SqsSetQueueAttributes = 'SetQueueAttributes',
|
||||
SqsTagQueue = 'TagQueue',
|
||||
SqsUntagQueue = 'UntagQueue',
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
import { AbstractActionHandler } from './abstract-action.handler';
|
||||
import { Action } from './action.enum';
|
||||
|
||||
export type ActionHandlers = Record<Action, AbstractActionHandler>;
|
||||
export const ActionHandlers = Symbol.for('ACTION_HANDLERS');
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
import { BadRequestException, Body, Controller, Get, Inject, Post, Headers } from '@nestjs/common';
|
||||
import { ActionHandlers } from './app.constants';
|
||||
import * as Joi from 'joi';
|
||||
import { Action } from './action.enum';
|
||||
import { AbstractActionHandler, Format } from './abstract-action.handler';
|
||||
import * as js2xmlparser from 'js2xmlparser';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { CommonConfig } from './config/common-config.interface';
|
||||
|
||||
@Controller()
|
||||
export class AppController {
|
||||
|
||||
constructor(
|
||||
@Inject(ActionHandlers)
|
||||
private readonly actionHandlers: ActionHandlers,
|
||||
private readonly configService: ConfigService<CommonConfig>,
|
||||
) {}
|
||||
|
||||
@Post()
|
||||
async post(
|
||||
@Body() body: Record<string, any>,
|
||||
@Headers() headers: Record<string, any>,
|
||||
) {
|
||||
|
||||
const lowerCasedHeaders = Object.keys(headers).reduce((o, k) => {
|
||||
o[k.toLocaleLowerCase()] = headers[k];
|
||||
return o;
|
||||
}, {})
|
||||
|
||||
const queryParams = { ...body, ...lowerCasedHeaders };
|
||||
console.log({queryParams})
|
||||
const actionKey = queryParams['x-amz-target'] ? 'x-amz-target' : 'Action';
|
||||
|
||||
const { error: actionError } = Joi.object({
|
||||
[actionKey]: Joi.string().valid(...Object.values(Action)).required(),
|
||||
}).validate(queryParams, { allowUnknown: true });
|
||||
|
||||
if (actionError) {
|
||||
throw new BadRequestException(actionError);
|
||||
}
|
||||
|
||||
const action = queryParams[actionKey];
|
||||
const handler: AbstractActionHandler = this.actionHandlers[action];
|
||||
|
||||
const { error: validatorError, value: validQueryParams } = handler.validator.validate(queryParams, { allowUnknown: true, abortEarly: false });
|
||||
|
||||
if (validatorError) {
|
||||
throw new BadRequestException(validatorError);
|
||||
}
|
||||
|
||||
const awsProperties = { accountId: this.configService.get('AWS_ACCOUNT_ID'), region: this.configService.get('AWS_REGION') };
|
||||
|
||||
const jsonResponse = await handler.getResponse(validQueryParams, awsProperties);
|
||||
if (handler.format === Format.Xml) {
|
||||
const xmlResponse = js2xmlparser.parse(`${handler.action}Response`, jsonResponse);
|
||||
// console.log({xmlResponse})
|
||||
return xmlResponse;
|
||||
}
|
||||
return jsonResponse;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
import { Module } from '@nestjs/common';
|
||||
import { ConfigModule, ConfigService } from '@nestjs/config';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { ActionHandlers } from './app.constants';
|
||||
import { CommonConfig } from './config/common-config.interface';
|
||||
import localConfig from './config/local.config';
|
||||
import { SnsHandlers } from './sns/sns.constants';
|
||||
import { SnsModule } from './sns/sns.module';
|
||||
import { AppController } from './app.controller';
|
||||
import { AwsSharedEntitiesModule } from './aws-shared-entities/aws-shared-entities.module';
|
||||
import { SecretsManagerModule } from './secrets-manager/secrets-manager.module';
|
||||
import { SecretsManagerHandlers } from './secrets-manager/secrets-manager.constants';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
ConfigModule.forRoot({
|
||||
load: [localConfig],
|
||||
isGlobal: true,
|
||||
// validationSchema: configValidator,
|
||||
}),
|
||||
TypeOrmModule.forRootAsync({
|
||||
inject: [ConfigService],
|
||||
useFactory: (configService: ConfigService<CommonConfig>) => ({
|
||||
type: 'sqlite',
|
||||
database: configService.get('DB_DATABASE'),
|
||||
logging: configService.get('DB_LOGGING'),
|
||||
synchronize: configService.get('DB_SYNCHRONIZE'),
|
||||
entities: [__dirname + '/**/*.entity{.ts,.js}'],
|
||||
}),
|
||||
}),
|
||||
SnsModule,
|
||||
SecretsManagerModule,
|
||||
AwsSharedEntitiesModule,
|
||||
],
|
||||
controllers: [
|
||||
AppController,
|
||||
],
|
||||
providers: [
|
||||
{
|
||||
provide: ActionHandlers,
|
||||
useFactory: (...args) => args.reduce((m, hs) => ({ ...m, ...hs }), {}),
|
||||
inject: [
|
||||
SnsHandlers,
|
||||
SecretsManagerHandlers,
|
||||
],
|
||||
},
|
||||
],
|
||||
})
|
||||
export class AppModule {}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
import { BaseEntity, Column, Entity, Index, PrimaryColumn, PrimaryGeneratedColumn } from 'typeorm';
|
||||
|
||||
@Entity('attributes')
|
||||
export class Attribute extends BaseEntity {
|
||||
|
||||
@PrimaryGeneratedColumn({ name: 'id' })
|
||||
id: string;
|
||||
|
||||
@Column({ name: 'arn', nullable: false })
|
||||
@Index()
|
||||
arn: string;
|
||||
|
||||
@Column({ name: 'name', nullable: false })
|
||||
name: string;
|
||||
|
||||
@Column({ name: 'value', nullable: false })
|
||||
value: string;
|
||||
}
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { Attribute } from './attributes.entity';
|
||||
import { CreateAttributeDto } from './create-attribute.dto';
|
||||
|
||||
@Injectable()
|
||||
export class AttributesService {
|
||||
|
||||
constructor(
|
||||
@InjectRepository(Attribute)
|
||||
private readonly repo: Repository<Attribute>,
|
||||
) {}
|
||||
|
||||
async getByArn(arn: string): Promise<Attribute[]> {
|
||||
return await this.repo.find({ where: { arn }});
|
||||
}
|
||||
|
||||
async create(dto: CreateAttributeDto): Promise<Attribute> {
|
||||
return await this.repo.save(dto);
|
||||
}
|
||||
|
||||
async deleteByArnAndName(arn: string, name: string) {
|
||||
await this.repo.delete({ arn, name });
|
||||
}
|
||||
|
||||
async createMany(arn: string, records: { key: string, value: string }[]): Promise<void> {
|
||||
for (const record of records) {
|
||||
await this.create({ arn, name: record.key, value: record.value });
|
||||
}
|
||||
}
|
||||
|
||||
static attributePairs(queryParams: Record<string, string>): { key: string, value: string }[] {
|
||||
const pairs = [null];
|
||||
for (const param of Object.keys(queryParams)) {
|
||||
const [type, _, idx, slot] = param.split('.');
|
||||
|
||||
if (type === 'Attributes') {
|
||||
if (!pairs[+idx]) {
|
||||
pairs[+idx] = { key: '', value: ''};
|
||||
}
|
||||
pairs[+idx][slot] = queryParams[param];
|
||||
}
|
||||
}
|
||||
|
||||
pairs.shift();
|
||||
return pairs;
|
||||
}
|
||||
|
||||
static getXmlSafeAttributesMap(attributes: Record<string, string>) {
|
||||
return { Attributes: { entry: Object.keys(attributes).map(key => ({ key, value: attributes[key] })) } }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { Attribute } from './attributes.entity';
|
||||
import { AttributesService } from './attributes.service';
|
||||
import { Tag } from './tags.entity';
|
||||
import { TagsService } from './tags.service';
|
||||
|
||||
@Module({
|
||||
imports: [TypeOrmModule.forFeature([Attribute, Tag])],
|
||||
providers: [AttributesService, TagsService],
|
||||
exports: [AttributesService, TagsService],
|
||||
})
|
||||
export class AwsSharedEntitiesModule {}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
export interface CreateAttributeDto {
|
||||
arn: string;
|
||||
name: string;
|
||||
value: string;
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
export interface CreateTagDto {
|
||||
arn: string;
|
||||
name: string;
|
||||
value: string;
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
import { BaseEntity, Column, Entity, Index, PrimaryColumn, PrimaryGeneratedColumn } from 'typeorm';
|
||||
|
||||
@Entity('tags')
|
||||
export class Tag extends BaseEntity {
|
||||
|
||||
@PrimaryGeneratedColumn({ name: 'id' })
|
||||
id: string;
|
||||
|
||||
@Column({ name: 'arn', nullable: false})
|
||||
@Index()
|
||||
arn: string;
|
||||
|
||||
@Column({ name: 'name', nullable: false })
|
||||
name: string;
|
||||
|
||||
@Column({ name: 'value', nullable: false })
|
||||
value: string;
|
||||
}
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { Tag } from './tags.entity';
|
||||
import { CreateTagDto } from './create-tag.dto';
|
||||
|
||||
@Injectable()
|
||||
export class TagsService {
|
||||
|
||||
constructor(
|
||||
@InjectRepository(Tag)
|
||||
private readonly repo: Repository<Tag>,
|
||||
) {}
|
||||
|
||||
async getByArn(arn: string): Promise<Tag[]> {
|
||||
return await this.repo.find({ where: { arn }});
|
||||
}
|
||||
|
||||
async create(dto: CreateTagDto): Promise<Tag> {
|
||||
return await this.repo.save(dto);
|
||||
}
|
||||
|
||||
async createMany(arn: string, records: { Key: string, Value: string }[]): Promise<void> {
|
||||
for (const record of records) {
|
||||
await this.create({ arn, name: record.Key, value: record.Value });
|
||||
}
|
||||
}
|
||||
|
||||
async deleteByArnAndName(arn: string, name: string) {
|
||||
await this.repo.delete({ arn, name });
|
||||
}
|
||||
|
||||
static tagPairs(queryParams: Record<string, string>): { Key: string, Value: string }[] {
|
||||
const pairs = [null];
|
||||
for (const param of Object.keys(queryParams)) {
|
||||
const [type, _, idx, slot] = param.split('.');
|
||||
|
||||
if (type === 'Tags') {
|
||||
if (!pairs[+idx]) {
|
||||
pairs[+idx] = { Key: '', Value: ''};
|
||||
}
|
||||
pairs[+idx][slot] = queryParams[param];
|
||||
}
|
||||
}
|
||||
|
||||
pairs.shift();
|
||||
return pairs;
|
||||
}
|
||||
|
||||
static getXmlSafeAttributesMap(tags: Tag[]) {
|
||||
return { Tags: { member: tags.map(({ name, value }) => ({ Key: name, Value: value })) } };
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
export interface CommonConfig {
|
||||
AWS_ACCOUNT_ID: string;
|
||||
AWS_REGION: string;
|
||||
DB_DATABASE: string;
|
||||
DB_LOGGING?: boolean;
|
||||
DB_SYNCHRONIZE?: boolean;
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
import { CommonConfig } from "./common-config.interface";
|
||||
|
||||
export default (): CommonConfig => ({
|
||||
AWS_ACCOUNT_ID: '123456789012',
|
||||
AWS_REGION: 'us-east-1',
|
||||
DB_DATABASE: ':memory:', // 'local-aws.sqlite', // :memory:
|
||||
DB_LOGGING: true,
|
||||
DB_SYNCHRONIZE: true,
|
||||
});
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
import { AbstractActionHandler } from '../abstract-action.handler';
|
||||
import { Action } from '../action.enum';
|
||||
|
||||
export type ExistingActionHandlers = Record<Action, AbstractActionHandler>;
|
||||
export const ExistingActionHandlers = Symbol.for('EXISTING_ACTION_HANDLERS');
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
import { Provider } from '@nestjs/common';
|
||||
import { Action } from '../action.enum';
|
||||
import { ExistingActionHandlers } from './default-action-handler.constants';
|
||||
import * as Joi from 'joi';
|
||||
import { AbstractActionHandler, Format } from '../abstract-action.handler';
|
||||
|
||||
export const DefaultActionHandlerProvider = (symbol, format: Format, actions: Action[]): Provider => ({
|
||||
provide: symbol,
|
||||
useFactory: (existingActionHandlers: ExistingActionHandlers) => {
|
||||
const cloned = { ...existingActionHandlers };
|
||||
for (const action of actions) {
|
||||
if (!cloned[action]) {
|
||||
cloned[action] = new (class Default extends AbstractActionHandler { action = action; format = format; validator = Joi.object(); handle = () => {} });
|
||||
}
|
||||
}
|
||||
return cloned;
|
||||
},
|
||||
inject: [ExistingActionHandlers]
|
||||
});
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
import { Provider } from '@nestjs/common';
|
||||
import { AbstractActionHandler } from '../abstract-action.handler';
|
||||
import { ExistingActionHandlers } from './default-action-handler.constants';
|
||||
|
||||
export const ExistingActionHandlersProvider = (inject): Provider => ({
|
||||
provide: ExistingActionHandlers,
|
||||
useFactory: (...args: AbstractActionHandler[]) => args.reduce((m, h) => {
|
||||
m[h.action] = h;
|
||||
return m;
|
||||
}, {}),
|
||||
inject,
|
||||
});
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
export enum Group {
|
||||
Legacy = 'legacy',
|
||||
SecretsManager = 'secretsmanager',
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
import { ClassSerializerInterceptor } from '@nestjs/common';
|
||||
import { NestFactory, Reflector } from '@nestjs/core';
|
||||
import { AppModule } from './app.module';
|
||||
import * as morgan from 'morgan';
|
||||
|
||||
(async () => {
|
||||
|
||||
const port = 8081;
|
||||
const app = await NestFactory.create(AppModule);
|
||||
app.use(morgan('dev'));
|
||||
app.useGlobalInterceptors(new ClassSerializerInterceptor(app.get(Reflector)));
|
||||
|
||||
await app.listen(port, () => console.log(`Listening on port ${port}`));
|
||||
})();
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
import { BaseEntity, Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
|
||||
|
||||
@Entity('secret')
|
||||
export class Secret extends BaseEntity {
|
||||
|
||||
@PrimaryGeneratedColumn({ name: 'id' })
|
||||
id: string;
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
import { AbstractActionHandler } from '../abstract-action.handler';
|
||||
import { Action } from '../action.enum';
|
||||
|
||||
export type SecretsManagerHandlers = Record<Action, AbstractActionHandler>;
|
||||
export const SecretsManagerHandlers = Symbol.for('SecretsManagerHandlers');
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { Format } from '../abstract-action.handler';
|
||||
import { Action } from '../action.enum';
|
||||
import { AwsSharedEntitiesModule } from '../aws-shared-entities/aws-shared-entities.module';
|
||||
import { DefaultActionHandlerProvider } from '../default-action-handler/default-action-handler.provider';
|
||||
import { ExistingActionHandlersProvider } from '../default-action-handler/existing-action-handlers.provider';
|
||||
import { SecretsManagerHandlers } from './secrets-manager.constants';
|
||||
|
||||
const handlers = [
|
||||
|
||||
]
|
||||
|
||||
const actions = [
|
||||
Action.SecretsManagerCancelRotateSecret,
|
||||
Action.SecretsManagerCreateSecret,
|
||||
Action.SecretsManagerDeleteResourcePolicy,
|
||||
Action.SecretsManagerDeleteSecret,
|
||||
Action.SecretsManagerDescribeSecret,
|
||||
Action.SecretsManagerGetRandomPassword,
|
||||
Action.SecretsManagerGetResourcePolicy,
|
||||
Action.SecretsManagerGetSecretValue,
|
||||
Action.SecretsManagerListSecrets,
|
||||
Action.SecretsManagerListSecretVersionIds,
|
||||
Action.SecretsManagerPutResourcePolicy,
|
||||
Action.SecretsManagerPutSecretValue,
|
||||
Action.SecretsManagerRemoveRegionsFromReplication,
|
||||
Action.SecretsManagerReplicateSecretToRegions,
|
||||
Action.SecretsManagerRestoreSecret,
|
||||
Action.SecretsManagerRotateSecret,
|
||||
Action.SecretsManagerStopReplicationToReplica,
|
||||
Action.SecretsManagerTagResource,
|
||||
Action.SecretsManagerUntagResource,
|
||||
Action.SecretsManagerUpdateSecret,
|
||||
Action.SecretsManagerUpdateSecretVersionStage,
|
||||
Action.SecretsManagerValidateResourcePolicy,
|
||||
]
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([]),
|
||||
AwsSharedEntitiesModule,
|
||||
],
|
||||
providers: [
|
||||
...handlers,
|
||||
ExistingActionHandlersProvider(handlers),
|
||||
DefaultActionHandlerProvider(SecretsManagerHandlers, Format.Json, actions),
|
||||
],
|
||||
exports: [SecretsManagerHandlers],
|
||||
})
|
||||
export class SecretsManagerModule {}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { AbstractActionHandler, AwsProperties, Format } from '../abstract-action.handler';
|
||||
import { Action } from '../action.enum';
|
||||
import { SnsTopic } from './sns-topic.entity';
|
||||
import * as Joi from 'joi';
|
||||
import { TagsService } from '../aws-shared-entities/tags.service';
|
||||
|
||||
type QueryParams = {
|
||||
Name: string;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class CreateTopicHandler extends AbstractActionHandler<QueryParams> {
|
||||
|
||||
constructor(
|
||||
@InjectRepository(SnsTopic)
|
||||
private readonly snsTopicRepo: Repository<SnsTopic>,
|
||||
private readonly tagsService: TagsService,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
format = Format.Xml;
|
||||
action = Action.SnsCreateTopic;
|
||||
validator = Joi.object<QueryParams, true>({ Name: Joi.string().required() });
|
||||
|
||||
protected async handle(params: QueryParams, awsProperties: AwsProperties) {
|
||||
|
||||
const { Name: name } = params;
|
||||
|
||||
const topic = await this.snsTopicRepo.create({
|
||||
name,
|
||||
accountId: awsProperties.accountId,
|
||||
region: awsProperties.region,
|
||||
}).save();
|
||||
|
||||
const tags = TagsService.tagPairs(params);
|
||||
await this.tagsService.createMany(topic.topicArn, tags);
|
||||
|
||||
return { TopicArn: topic.topicArn };
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { AbstractActionHandler, AwsProperties, Format } from '../abstract-action.handler';
|
||||
import { Action } from '../action.enum';
|
||||
import * as Joi from 'joi';
|
||||
import { SnsTopicSubscription } from './sns-topic-subscription.entity';
|
||||
import { AttributesService } from '../aws-shared-entities/attributes.service';
|
||||
|
||||
type QueryParams = {
|
||||
SubscriptionArn: string;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class GetSubscriptionAttributesHandler extends AbstractActionHandler {
|
||||
|
||||
constructor(
|
||||
@InjectRepository(SnsTopicSubscription)
|
||||
private readonly snsTopicSubscriptionRepo: Repository<SnsTopicSubscription>,
|
||||
private readonly attributeService: AttributesService,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
format = Format.Xml;
|
||||
action = Action.SnsGetSubscriptionAttributes;
|
||||
validator = Joi.object<QueryParams, true>({ SubscriptionArn: Joi.string().required() });
|
||||
|
||||
protected async handle({ SubscriptionArn }: QueryParams, awsProperties: AwsProperties) {
|
||||
|
||||
const id = SubscriptionArn.split(':')[-1];
|
||||
const subscription = await this.snsTopicSubscriptionRepo.findOne({ where: { id }});
|
||||
const attributes = await this.attributeService.getByArn(SubscriptionArn);
|
||||
const attributeMap = attributes.reduce((m, a) => {
|
||||
m[a.name] = a.value;
|
||||
return m;
|
||||
}, {});
|
||||
|
||||
const response = {
|
||||
ConfirmationWasAuthenticated: 'true',
|
||||
PendingConfirmation: 'false',
|
||||
Owner: subscription.accountId,
|
||||
SubscriptionArn: subscription.arn,
|
||||
TopicArn: subscription.topicArn,
|
||||
...attributeMap,
|
||||
TracingConfig: attributeMap['TracingConfig'] ?? 'PassThrough',
|
||||
}
|
||||
|
||||
return AttributesService.getXmlSafeAttributesMap(response);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { AbstractActionHandler, AwsProperties, Format } from '../abstract-action.handler';
|
||||
import { Action } from '../action.enum';
|
||||
import { SnsTopic } from './sns-topic.entity';
|
||||
import * as Joi from 'joi';
|
||||
import { AttributesService } from '../aws-shared-entities/attributes.service';
|
||||
|
||||
type QueryParams = {
|
||||
TopicArn: string;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class GetTopicAttributesHandler extends AbstractActionHandler {
|
||||
|
||||
constructor(
|
||||
@InjectRepository(SnsTopic)
|
||||
private readonly snsTopicRepo: Repository<SnsTopic>,
|
||||
private readonly attributeService: AttributesService,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
format = Format.Xml;
|
||||
action = Action.SnsGetTopicAttributes;
|
||||
validator = Joi.object<QueryParams, true>({ TopicArn: Joi.string().required() });
|
||||
|
||||
protected async handle({ TopicArn }: QueryParams, awsProperties: AwsProperties) {
|
||||
|
||||
const name = TopicArn.split(':')[-1];
|
||||
const topic = await this.snsTopicRepo.findOne({ where: { name }});
|
||||
const attributes = await this.attributeService.getByArn(TopicArn);
|
||||
const attributeMap = attributes.reduce((m, a) => {
|
||||
m[a.name] = a.value;
|
||||
return m;
|
||||
}, {});
|
||||
|
||||
const response = {
|
||||
DisplayName: topic.name,
|
||||
Owner: topic.accountId,
|
||||
SubscriptionsConfirmed: '0',
|
||||
SubscriptionsDeleted: '0',
|
||||
SubscriptionsPending: '0',
|
||||
TopicArn: topic.topicArn,
|
||||
...attributeMap,
|
||||
TracingConfig: attributeMap['TracingConfig'] ?? 'PassThrough',
|
||||
}
|
||||
|
||||
return AttributesService.getXmlSafeAttributesMap(response);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { AbstractActionHandler, AwsProperties, Format } from '../abstract-action.handler';
|
||||
import { Action } from '../action.enum';
|
||||
import { SnsTopic } from './sns-topic.entity';
|
||||
import * as Joi from 'joi';
|
||||
import { TagsService } from '../aws-shared-entities/tags.service';
|
||||
|
||||
type QueryParams = {
|
||||
ResourceArn: string;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class ListTagsForResourceHandler extends AbstractActionHandler {
|
||||
|
||||
constructor(
|
||||
private readonly tagsService: TagsService,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
format = Format.Xml;
|
||||
action = Action.SnsListTagsForResource;
|
||||
validator = Joi.object<QueryParams, true>({ ResourceArn: Joi.string().required() });
|
||||
|
||||
protected async handle({ ResourceArn }: QueryParams, awsProperties: AwsProperties) {
|
||||
const tags = await this.tagsService.getByArn(ResourceArn);
|
||||
return TagsService.getXmlSafeAttributesMap(tags);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { AbstractActionHandler, AwsProperties, Format } from '../abstract-action.handler';
|
||||
import { Action } from '../action.enum';
|
||||
import { SnsTopic } from './sns-topic.entity';
|
||||
import * as Joi from 'joi';
|
||||
|
||||
type QueryParams = {
|
||||
NextToken: number;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class ListTopicsHandler extends AbstractActionHandler {
|
||||
|
||||
constructor(
|
||||
@InjectRepository(SnsTopic)
|
||||
private readonly snsTopicRepo: Repository<SnsTopic>,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
format = Format.Xml;
|
||||
action = Action.SnsListTopics;
|
||||
validator = Joi.object<QueryParams, true>({ NextToken: Joi.number().default(0) });
|
||||
|
||||
protected async handle({ NextToken: skip }: QueryParams, awsProperties: AwsProperties) {
|
||||
|
||||
const [ topics, total ] = await this.snsTopicRepo.findAndCount({ order: { name: 'DESC' }, take: 100, skip });
|
||||
const response = { Topics: topics.map(t => ({ Topic: { TopicArn: t.topicArn } }))};
|
||||
|
||||
if (total >= 100) {
|
||||
return {
|
||||
...response,
|
||||
NextToken: `${skip + 100}`
|
||||
}
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
import { Injectable } from '@nestjs/common';
|
||||
import { AbstractActionHandler, AwsProperties, Format } from '../abstract-action.handler';
|
||||
import { Action } from '../action.enum';
|
||||
import * as Joi from 'joi';
|
||||
import { AttributesService } from '../aws-shared-entities/attributes.service';
|
||||
|
||||
type QueryParams = {
|
||||
AttributeName: string;
|
||||
AttributeValue: string;
|
||||
TopicArn: string;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class SetSubscriptionAttributesHandler extends AbstractActionHandler<QueryParams> {
|
||||
|
||||
constructor(
|
||||
private readonly attributeService: AttributesService,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
format = Format.Xml;
|
||||
action = Action.SnsSetSubscriptionAttributes;
|
||||
validator = Joi.object<QueryParams, true>({
|
||||
AttributeName: Joi.string().required(),
|
||||
AttributeValue: Joi.string().required(),
|
||||
TopicArn: Joi.string().required(),
|
||||
});
|
||||
|
||||
protected async handle({ AttributeName, AttributeValue, TopicArn }: QueryParams, awsProperties: AwsProperties) {
|
||||
await this.attributeService.create({ name: AttributeName, value: AttributeValue, arn: TopicArn });
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
import { Injectable } from '@nestjs/common';
|
||||
import { AbstractActionHandler, AwsProperties, Format } from '../abstract-action.handler';
|
||||
import { Action } from '../action.enum';
|
||||
import * as Joi from 'joi';
|
||||
import { AttributesService } from '../aws-shared-entities/attributes.service';
|
||||
|
||||
type QueryParams = {
|
||||
AttributeName: string;
|
||||
AttributeValue: string;
|
||||
TopicArn: string;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class SetTopicAttributesHandler extends AbstractActionHandler<QueryParams> {
|
||||
|
||||
constructor(
|
||||
private readonly attributeService: AttributesService,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
format = Format.Xml;
|
||||
action = Action.SnsSetTopicAttributes;
|
||||
validator = Joi.object<QueryParams, true>({
|
||||
AttributeName: Joi.string().required(),
|
||||
AttributeValue: Joi.string().required(),
|
||||
TopicArn: Joi.string().required(),
|
||||
});
|
||||
|
||||
protected async handle({ AttributeName, AttributeValue, TopicArn }: QueryParams, awsProperties: AwsProperties) {
|
||||
await this.attributeService.create({ name: AttributeName, value: AttributeValue, arn: TopicArn });
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
import { BaseEntity, Column, Entity, PrimaryColumn, PrimaryGeneratedColumn } from 'typeorm';
|
||||
|
||||
@Entity('sns_topic_subscription')
|
||||
export class SnsTopicSubscription extends BaseEntity {
|
||||
|
||||
@PrimaryColumn({ name: 'id' })
|
||||
id: string;
|
||||
|
||||
@Column({ name: 'topic_arn' })
|
||||
topicArn: string;
|
||||
|
||||
@Column({ name: 'endpoint', nullable: true })
|
||||
endpoint: string;
|
||||
|
||||
@Column({ name: 'protocol' })
|
||||
protocol: string;
|
||||
|
||||
@Column({ name: 'account_id', nullable: false })
|
||||
accountId: string;
|
||||
|
||||
@Column({ name: 'region', nullable: false })
|
||||
region: string;
|
||||
|
||||
get arn() {
|
||||
return `${this.topicArn}:${this.id}`;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
import { BaseEntity, Column, Entity, PrimaryColumn } from 'typeorm';
|
||||
|
||||
@Entity('sns_topic')
|
||||
export class SnsTopic extends BaseEntity {
|
||||
|
||||
@PrimaryColumn({ name: 'name' })
|
||||
name: string;
|
||||
|
||||
@Column({ name: 'account_id', nullable: false })
|
||||
accountId: string;
|
||||
|
||||
@Column({ name: 'region', nullable: false })
|
||||
region: string;
|
||||
|
||||
get topicArn(): string {
|
||||
return `arn:aws:sns:${this.region}:${this.accountId}:${this.name}`;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
import { AbstractActionHandler } from '../abstract-action.handler';
|
||||
import { Action } from '../action.enum';
|
||||
|
||||
export type SnsHandlers = Record<Action, AbstractActionHandler>;
|
||||
export const SnsHandlers = Symbol.for('SNS_HANDLERS');
|
||||
|
|
@ -0,0 +1,91 @@
|
|||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { AbstractActionHandler, Format } from '../abstract-action.handler';
|
||||
import { Action } from '../action.enum';
|
||||
import { AwsSharedEntitiesModule } from '../aws-shared-entities/aws-shared-entities.module';
|
||||
import { ExistingActionHandlers } from '../default-action-handler/default-action-handler.constants';
|
||||
import { DefaultActionHandlerProvider } from '../default-action-handler/default-action-handler.provider';
|
||||
import { ExistingActionHandlersProvider } from '../default-action-handler/existing-action-handlers.provider';
|
||||
import { CreateTopicHandler } from './create-topic.handler';
|
||||
import { GetSubscriptionAttributesHandler } from './get-subscription-attributes.handler';
|
||||
import { GetTopicAttributesHandler } from './get-topic-attributes.handler';
|
||||
import { ListTagsForResourceHandler } from './list-tags-for-resource.handler';
|
||||
import { ListTopicsHandler } from './list-topics.handler';
|
||||
import { SetSubscriptionAttributesHandler } from './set-subscription-attributes.handler';
|
||||
import { SetTopicAttributesHandler } from './set-topic-attributes.handler';
|
||||
import { SnsTopicSubscription } from './sns-topic-subscription.entity';
|
||||
import { SnsTopic } from './sns-topic.entity';
|
||||
import { SnsHandlers } from './sns.constants';
|
||||
import { SubscribeHandler } from './subscribe.handler';
|
||||
|
||||
const handlers = [
|
||||
CreateTopicHandler,
|
||||
GetSubscriptionAttributesHandler,
|
||||
GetTopicAttributesHandler,
|
||||
ListTagsForResourceHandler,
|
||||
ListTopicsHandler,
|
||||
SetSubscriptionAttributesHandler,
|
||||
SetTopicAttributesHandler,
|
||||
SubscribeHandler,
|
||||
];
|
||||
|
||||
const actions = [
|
||||
Action.SnsAddPermission,
|
||||
Action.SnsCheckIfPhoneNumberIsOptedOut,
|
||||
Action.SnsConfirmSubscription,
|
||||
Action.SnsCreatePlatformApplication,
|
||||
Action.SnsCreatePlatformEndpoint,
|
||||
Action.SnsCreateSMSSandboxPhoneNumber,
|
||||
Action.SnsCreateTopic,
|
||||
Action.SnsDeleteEndpoint,
|
||||
Action.SnsDeletePlatformApplication,
|
||||
Action.SnsDeleteSMSSandboxPhoneNumber,
|
||||
Action.SnsDeleteTopic,
|
||||
Action.SnsGetDataProtectionPolicy,
|
||||
Action.SnsGetEndpointAttributes,
|
||||
Action.SnsGetPlatformApplicationAttributes,
|
||||
Action.SnsGetSMSAttributes,
|
||||
Action.SnsGetSMSSandboxAccountStatus,
|
||||
Action.SnsGetSubscriptionAttributes,
|
||||
Action.SnsGetTopicAttributes,
|
||||
Action.SnsListEndpointsByPlatformApplication,
|
||||
Action.SnsListOriginationNumbers,
|
||||
Action.SnsListPhoneNumbersOptedOut,
|
||||
Action.SnsListPlatformApplications,
|
||||
Action.SnsListSMSSandboxPhoneNumbers,
|
||||
Action.SnsListSubscriptions,
|
||||
Action.SnsListSubscriptionsByTopic,
|
||||
Action.SnsListTagsForResource,
|
||||
Action.SnsListTopics,
|
||||
Action.SnsOptInPhoneNumber,
|
||||
Action.SnsPublish,
|
||||
Action.SnsPublishBatch,
|
||||
Action.SnsPutDataProtectionPolicy,
|
||||
Action.SnsRemovePermission,
|
||||
Action.SnsSetEndpointAttributes,
|
||||
Action.SnsSetPlatformApplicationAttributes,
|
||||
Action.SnsSetSMSAttributes,
|
||||
Action.SnsSetSubscriptionAttributes,
|
||||
Action.SnsSetTopicAttributes,
|
||||
Action.SnsSubscribe,
|
||||
Action.SnsTagResource,
|
||||
Action.SnsUnsubscribe,
|
||||
Action.SnsUntagResource,
|
||||
Action.SnsVerifySMSSandboxPhoneNumber,
|
||||
]
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([SnsTopic, SnsTopicSubscription]),
|
||||
AwsSharedEntitiesModule,
|
||||
],
|
||||
providers: [
|
||||
...handlers,
|
||||
ExistingActionHandlersProvider(handlers),
|
||||
DefaultActionHandlerProvider(SnsHandlers, Format.Xml, actions),
|
||||
],
|
||||
exports: [
|
||||
SnsHandlers,
|
||||
]
|
||||
})
|
||||
export class SnsModule {}
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { AbstractActionHandler, AwsProperties, Format } from '../abstract-action.handler';
|
||||
import { Action } from '../action.enum';
|
||||
import * as Joi from 'joi';
|
||||
import { TagsService } from '../aws-shared-entities/tags.service';
|
||||
import { AttributesService } from '../aws-shared-entities/attributes.service';
|
||||
import { SnsTopicSubscription } from './sns-topic-subscription.entity';
|
||||
import { uuid } from 'uuidv4';
|
||||
|
||||
type QueryParams = {
|
||||
TopicArn: string;
|
||||
Protocol: string;
|
||||
Endpoint?: string;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class SubscribeHandler extends AbstractActionHandler<QueryParams> {
|
||||
|
||||
constructor(
|
||||
@InjectRepository(SnsTopicSubscription)
|
||||
private readonly snsTopicSubscription: Repository<SnsTopicSubscription>,
|
||||
private readonly tagsService: TagsService,
|
||||
private readonly attributeService: AttributesService,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
format = Format.Xml;
|
||||
action = Action.SnsSubscribe;
|
||||
validator = Joi.object<QueryParams, true>({
|
||||
Endpoint: Joi.string(),
|
||||
TopicArn: Joi.string().required(),
|
||||
Protocol: Joi.string().required(),
|
||||
});
|
||||
|
||||
protected async handle(params: QueryParams, awsProperties: AwsProperties) {
|
||||
|
||||
const subscription = await this.snsTopicSubscription.create({
|
||||
id: uuid(),
|
||||
topicArn: params.TopicArn,
|
||||
protocol: params.Protocol,
|
||||
endpoint: params.Endpoint,
|
||||
accountId: awsProperties.accountId,
|
||||
region: awsProperties.region,
|
||||
}).save();
|
||||
|
||||
const tags = TagsService.tagPairs(params);
|
||||
await this.tagsService.createMany(subscription.arn, tags);
|
||||
|
||||
const attributes = AttributesService.attributePairs(params);
|
||||
await this.attributeService.createMany(subscription.arn, attributes);
|
||||
|
||||
return { SubscriptionArn: subscription.arn };
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"declaration": true,
|
||||
"removeComments": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"target": "es2017",
|
||||
"sourceMap": true,
|
||||
"outDir": "./dist",
|
||||
"baseUrl": "./",
|
||||
"incremental": true,
|
||||
"resolveJsonModule": true,
|
||||
},
|
||||
"exclude": ["node_modules"],
|
||||
"include": ["src/**/*.ts"]
|
||||
}
|
||||
Loading…
Reference in New Issue